    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *     http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.jxpath.ri.model.dom;
    019
    020import java.util.ArrayList;
    021import java.util.List;
    022import java.util.Objects;
    023
    024import org.apache.commons.jxpath.ri.QName;
    025import org.apache.commons.jxpath.ri.model.NodeIterator;
    026import org.apache.commons.jxpath.ri.model.NodePointer;
    027import org.w3c.dom.Attr;
    028import org.w3c.dom.Element;
    029import org.w3c.dom.NamedNodeMap;
    030import org.w3c.dom.Node;
    031
    032/**
    033 * An iterator of attributes of a DOM Node.
    034 */
    035public class DOMAttributeIterator implements NodeIterator {
    036
    037    private final NodePointer parent;
    038    private final QName qName;
    039    private final List<Attr> attributes;
    040    private int position;
    041
    042    /**
    043     * Constructs a new DOMAttributeIterator.
    044     *
    045     * @param parent pointer
    046     * @param qName   to test
    047     */
    048    public DOMAttributeIterator(final NodePointer parent, final QName qName) {
    049        this.parent = parent;
    050        this.qName = qName;
    051        attributes = new ArrayList<>();
    052        final Node node = (Node) parent.getNode();
    053        if (node.getNodeType() == Node.ELEMENT_NODE) {
    054            final String lname = qName.getName();
    055            if (!lname.equals("*")) {
    056                final Attr attr = getAttribute((Element) node, qName);
    057                if (attr != null) {
    058                    attributes.add(attr);
    059                }
    060            } else {
    061                final NamedNodeMap map = node.getAttributes();
    062                final int count = map.getLength();
    063                for (int i = 0; i < count; i++) {
    064                    final Attr attr = (Attr) map.item(i);
    065                    if (testAttr(attr)) {
    066                        attributes.add(attr);
    067                    }
    068                }
    069            }
    070        }
    071    }
    072
    073    /**
    074     * Gets the named attribute.
    075     *
    076     * @param element to search
    077     * @param qName    to match
    078     * @return Attr found
    079     */
    080    private Attr getAttribute(final Element element, final QName qName) {
    081        final String testPrefix = qName.getPrefix();
    082        String testNS = null;
    083        if (testPrefix != null) {
    084            testNS = parent.getNamespaceResolver().getNamespaceURI(testPrefix);
    085        }
    086        if (testNS != null) {
    087            Attr attr = element.getAttributeNodeNS(testNS, qName.getName());
    088            if (attr != null) {
    089                return attr;
    090            }
    091            // This may mean that the parser does not support NS for
    092            // attributes, example - the version of Crimson bundled
    093            // with JDK 1.4.0
    094            final NamedNodeMap nnm = element.getAttributes();
    095            for (int i = 0; i < nnm.getLength(); i++) {
    096                attr = (Attr) nnm.item(i);
    097                if (testAttr(attr)) {
    098                    return attr;
    099                }
    100            }
    101            return null;
    102        }
    103        return element.getAttributeNode(qName.getName());
    104    }
    105
    106    @Override
    107    public NodePointer getNodePointer() {
    108        if (position == 0) {
    109            if (!setPosition(1)) {
    110                return null;
    111            }
    112            position = 0;
    113        }
    114        int index = position - 1;
    115        if (index < 0) {
    116            index = 0;
    117        }
    118        return new DOMAttributePointer(parent, attributes.get(index));
    119    }
    120
    121    @Override
    122    public int getPosition() {
    123        return position;
    124    }
    125
    126    @Override
    127    public boolean setPosition(final int position) {
    128        this.position = position;
    129        return position >= 1 && position <= attributes.size();
    130    }
    131
    132    /**
    133     * Test an attribute.
    134     *
    135     * @param attr to test
    136     * @return whether test succeeded
    137     */
    138    private boolean testAttr(final Attr attr) {
    139        final String nodePrefix = DOMNodePointer.getPrefix(attr);
    140        final String nodeLocalName = DOMNodePointer.getLocalName(attr);
    141        if (nodePrefix != null && nodePrefix.equals("xmlns")) {
    142            return false;
    143        }
    144        if (nodePrefix == null && nodeLocalName.equals("xmlns")) {
    145            return false;
    146        }
    147        final String testLocalName = qName.getName();
    148        if (testLocalName.equals("*") || testLocalName.equals(nodeLocalName)) {
    149            final String testPrefix = qName.getPrefix();
    150            if (testPrefix == null || Objects.equals(testPrefix, nodePrefix)) {
    151                return true;
    152            }
    153            if (nodePrefix == null) {
    154                return false;
    155            }
    156            return Objects.equals(parent.getNamespaceURI(testPrefix), parent.getNamespaceURI(nodePrefix));
    157        }
    158        return false;
    159    }
    160}