All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jodd.lagarto.dom.Node Maven / Gradle / Ivy

The newest version!
// Copyright (c) 2003-2014, Jodd Team (jodd.org). All Rights Reserved.

package jodd.lagarto.dom;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * DOM node.
 */
@SuppressWarnings({"ForLoopReplaceableByForEach", "ClassReferencesSubclass"})
public abstract class Node implements Cloneable {

    /**
     * Node types.
     */
    public enum NodeType {
        DOCUMENT, ELEMENT, TEXT, COMMENT, CDATA, DOCUMENT_TYPE, XML_DECLARATION
    }

    // node values

    protected final String nodeName;
    protected final String nodeRawName;
    protected final NodeType nodeType;
    protected Document ownerDocument;    // root document node
    protected String nodeValue;

    // attributes

    protected List attributes;

    // parent

    protected Node parentNode;

    // children

    protected List childNodes = Collections.emptyList();

    //protected Set descendants = new HashSet<>();

    protected int childElementNodesCount;
    protected Element[] childElementNodes;

    // siblings

    protected int siblingIndex;
    protected int siblingElementIndex = -1;
    protected int siblingNameIndex = -1;

    /**
     * Creates new node.
     */
    protected Node(Document document, NodeType nodeType, String nodeName) {
        this.ownerDocument = document;
        this.nodeRawName = nodeName;
        if (nodeName != null) {
            this.nodeName = nodeName;
        } else {
            this.nodeName = null;
        }
        this.nodeType = nodeType;
    }

    // ---------------------------------------------------------------- clone

    /**
     * Copies all non-final values to the empty cloned object.
     * Cache-related values are not copied.
     */
    protected  T cloneTo(T dest) {
//		dest.nodeValue = nodeValue;		// already  in clone implementations!
        dest.parentNode = parentNode;

        if (attributes != null) {
            dest.attributes = new ArrayList(attributes.size());
            for (int i = 0, attributesSize = attributes.size(); i < attributesSize; i++) {
                Attribute attr = attributes.get(i);
                dest.attributes.add(attr.clone());
            }
        }

        if (childNodes != null) {
            dest.childNodes = new ArrayList(childNodes.size());
            for (int i = 0, childNodesSize = childNodes.size(); i < childNodesSize; i++) {
                Node child = childNodes.get(i);
                Node childClone = child.clone();

                childClone.parentNode = dest;    // fix parent!
                dest.childNodes.add(childClone);
            }
        }

        return dest;
    }

    @Override
    public abstract Node clone();

    // ---------------------------------------------------------------- basic

    /**
     * Returns {@link NodeType node type}.
     */
    public NodeType getNodeType() {
        return nodeType;
    }

    /**
     * Returns nodes name or null if name is not available.
     */
    public String getNodeName() {
        return nodeName;
    }

    /**
     * Returns nodes raw name - exactly as it was given in the input.
     */
    public String getNodeRawName() {
        return nodeRawName;
    }

    /**
     * Returns node value or null if value is not available.
     */
    public String getNodeValue() {
        return nodeValue;
    }

    /**
     * Sets node value.
     */
    public void setNodeValue(String value) {
        this.nodeValue = value;
    }

    /**
     * Returns owner document, root node for this DOM tree.
     */
    public Document getOwnerDocument() {
        return ownerDocument;
    }

    // ---------------------------------------------------------------- tree

    /**
     * Removes this node from DOM tree.
     */
    public void detachFromParent() {
        if (parentNode == null) {
            return;
        }
        if (parentNode.childNodes != null) {
            parentNode.childNodes.remove(siblingIndex);
            parentNode.reindexChildren();
        }
        parentNode = null;
    }

    /**
     * Appends child node. Don't use this node in the loop,
     * since it might be slow due to {@link #reindexChildren()}.
     */
    public void addChild(Node node) {
        node.detachFromParent();
        node.parentNode = this;
        initChildNodes(node);
        childNodes.add(node);
        reindexChildrenOnAdd(1);

        //addDescendant(this, node);
    }

   /* private void addDescendant(Node curNode , Node node){
        while (curNode != null){
            curNode.descendants.add(node);
            curNode = curNode.getParentNode();
        }
    }

    private void removeDescendant(Node curNode , Node node){
        while (curNode != null){
            curNode.descendants.remove(node);
            curNode = curNode.getParentNode();
        }
    }

    private void removeDescendants(Node curNode , List nodes){
        while (curNode != null){
            curNode.descendants.removeAll(nodes);
            curNode = curNode.getParentNode();
        }
    }
*/

    /**
     * Appends several child nodes at once.
     * Reindex is done only once, after all children are added.
     */
    public void addChild(Node... nodes) {
        for (Node node : nodes) {
            node.detachFromParent();
            node.parentNode = this;
            initChildNodes(node);
            childNodes.add(node);
        }
        reindexChildrenOnAdd(nodes.length);
    }

    /**
     * Inserts node at given index.
     */
    public void insertChild(Node node, int index) {
        node.detachFromParent();
        node.parentNode = this;
        try {
            initChildNodes(node);
            childNodes.add(index, node);
        } catch (IndexOutOfBoundsException ignore) {
            throw new LagartoDOMException("Invalid node index: " + index);
        }
        reindexChildren();
    }

    /**
     * Inserts several nodes at ones. Reindex is done onl once,
     * after all children are added.
     */
    public void insertChild(Node[] nodes, int index) {
        for (Node node : nodes) {
            node.detachFromParent();
            node.parentNode = this;
            try {
                initChildNodes(node);
                childNodes.add(index, node);
                index++;
            } catch (IndexOutOfBoundsException ignore) {
                throw new LagartoDOMException("Invalid node index: " + index);
            }
        }
        reindexChildren();
    }

    /**
     * Inserts node before provided node.
     */
    public void insertBefore(Node newChild, Node refChild) {
        int siblingIndex = refChild.getSiblingIndex();
        refChild.parentNode.insertChild(newChild, siblingIndex);
    }

    /**
     * Inserts several child nodes before provided node.
     */
    public void insertBefore(Node[] newChilds, Node refChild) {
        int siblingIndex = refChild.getSiblingIndex();
        refChild.parentNode.insertChild(newChilds, siblingIndex);
    }

    /**
     * Inserts node after provided node.
     */
    public void insertAfter(Node newChild, Node refChild) {
        int siblingIndex = refChild.getSiblingIndex() + 1;
        if (siblingIndex == refChild.parentNode.getChildNodesCount()) {
            refChild.parentNode.addChild(newChild);
        } else {
            refChild.parentNode.insertChild(newChild, siblingIndex);
        }
    }

    /**
     * Inserts several child nodes after referent node.
     */
    public void insertAfter(Node[] newChilds, Node refChild) {
        int siblingIndex = refChild.getSiblingIndex() + 1;
        if (siblingIndex == refChild.parentNode.getChildNodesCount()) {
            refChild.parentNode.addChild(newChilds);
        } else {
            refChild.parentNode.insertChild(newChilds, siblingIndex);
        }
    }

    /**
     * Removes child node at given index.
     * Returns removed node or null if index is invalid.
     */
    public Node removeChild(int index) {
        if (childNodes == null) {
            return null;
        }
        Node node;
        try {
            node = childNodes.get(index);
        } catch (IndexOutOfBoundsException ignore) {
            return null;
        }
        node.detachFromParent();
        return node;
    }

    /**
     * Removes child node. It works only with direct children, i.e.
     * if provided child node is not a child nothing happens.
     */
    public void removeChild(Node childNode) {
        if (childNode.getParentNode() != this) {
            return;
        }
        childNode.detachFromParent();
    }

    /**
     * Removes all child nodes. Each child node will be detached from this parent.
     */
    public void removeAllChilds() {
        List removedNodes = childNodes;
        childNodes = null;
        childElementNodes = null;
        childElementNodesCount = 0;

        if (removedNodes != null) {

            //removeDescendants(this, removedNodes);

            for (int i = 0, removedNodesSize = removedNodes.size(); i < removedNodesSize; i++) {
                Node removedNode = removedNodes.get(i);
                removedNode.detachFromParent();
            }
        }
    }

    /**
     * Returns parent node or null if no parent exist.
     */
    public Node getParentNode() {
        return parentNode;
    }

    // ---------------------------------------------------------------- attributes

    /**
     * Returns true if node has attributes.
     */
    public boolean hasAttributes() {
        if (attributes == null) {
            return false;
        }
        return !attributes.isEmpty();
    }

    /**
     * Returns total number of attributes.
     */
    public int getAttributesCount() {
        if (attributes == null) {
            return 0;
        }
        return attributes.size();
    }

    /**
     * Returns attribute at given index or null if index not found.
     */
    public Attribute getAttribute(int index) {
        if (attributes == null) {
            return null;
        }
        if ((index < 0) || (index >= attributes.size())) {
            return null;
        }
        return attributes.get(index);
    }

    /**
     * Returns true if node contains an attribute.
     */
    public boolean hasAttribute(String name) {
        if (attributes == null) {
            return false;
        }
        for (int i = 0, attributesSize = attributes.size(); i < attributesSize; i++) {
            Attribute attr = attributes.get(i);
            if (attr.getName().equals(name)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns attribute value. Returns null when
     * attribute doesn't exist or when attribute exist but doesn't
     * specify a value.
     */
    public String getAttribute(String name) {
        Attribute attribute = getAttributeInstance(name);
        if (attribute == null) {
            return null;
        }
        return attribute.getValue();
    }

    protected Attribute getAttributeInstance(String name) {
        if (attributes == null) {
            return null;
        }

        for (int i = 0, attributesSize = attributes.size(); i < attributesSize; i++) {
            Attribute attr = attributes.get(i);
            if (attr.getName().equals(name)) {
                return attr;
            }
        }
        return null;
    }

    protected int indexOfAttributeInstance(String name) {
        if (attributes == null) {
            return -1;
        }


        for (int i = 0, attributesSize = attributes.size(); i < attributesSize; i++) {
            Attribute attr = attributes.get(i);
            if (attr.getName().equals(name)) {
                return i;
            }
        }
        return -1;
    }

    public boolean removeAttribute(String name) {
        int index = indexOfAttributeInstance(name);
        if (index == -1) {
            return false;
        }
        attributes.remove(index);
        return true;
    }

    /**
     * Sets attribute value. Value may be null.
     */
    public void setAttribute(String name, String value) {
        initAttributes();

        String rawAttributeName = name;

        // search if attribute with the same name exist
        for (int i = 0, attributesSize = attributes.size(); i < attributesSize; i++) {
            Attribute attr = attributes.get(i);
            if (attr.getName().equals(name)) {
                attr.setValue(value);
                return;
            }
        }
        attributes.add(new Attribute(rawAttributeName, name, value));
    }

    /**
     * Sets attribute that doesn't need a value.
     */
    public void setAttribute(String name) {
        setAttribute(name, null);
    }

    /**
     * Returns true if attribute containing some word.
     */
    public boolean isAttributeContaining(String name, String word) {
        Attribute attr = getAttributeInstance(name);
        if (attr == null) {
            return false;
        }
        return attr.isContaining(word);
    }

    // ---------------------------------------------------------------- children count

    /**
     * Returns true if node has child nodes.
     */
    public boolean hasChildNodes() {
        if (childNodes == null) {
            return false;
        }
        return !childNodes.isEmpty();
    }

    /**
     * Returns number of all child nodes.
     */
    public int getChildNodesCount() {
        if (childNodes == null) {
            return 0;
        }
        return childNodes.size();
    }

    /**
     * Returns number of child elements.
     */
    public int getChildElementsCount() {
        return childElementNodesCount;
    }

    /**
     * Returns number of child elements with given name.
     */
    public int getChildElementsCount(String elementName) {
        Node lastChild = getLastChildElement(elementName);
        return lastChild.siblingNameIndex + 1;
    }

    // ---------------------------------------------------------------- children

    /**
     * Returns an array of all children nodes. Returns an empty array
     * if there are no children.
     */
    public Node[] getChildNodes() {
        if (childNodes == null) {
            return new Node[0];
        }
        return childNodes.toArray(new Node[childNodes.size()]);
    }

    /**
     * Returns an array of all children nodes. Returns an empty array
     * if there are no children.
     */
    public List getChildNodesAsList() {
        if (childNodes == null) {
            return Collections.emptyList();
        }
        return childNodes;
    }


    /**
     * Returns an array of all children elements.
     */
    public Element[] getChildElements() {
        initChildElementNodes();
        return childElementNodes.clone();
    }

    /**
     * Returns a child node at given index or null
     * if child doesn't exist for that index.
     */
    public Node getChild(int index) {
        if (childNodes == null) {
            return null;
        }
        if ((index < 0) || (index >= childNodes.size())) {
            return null;
        }
        return childNodes.get(index);
    }

    /**
     * Returns a child node with given hierarchy.
     * Just a shortcut for successive calls of {@link #getChild(int)}.
     */
    public Node getChild(int... indexes) {
        Node node = this;
        for (int index : indexes) {
            node = node.getChild(index);
        }
        return node;
    }

    /**
     * Returns a child element node at given index.
     * If index is out of bounds, null is returned.
     */
    public Element getChildElement(int index) {
        initChildElementNodes();
        if ((index < 0) || (index >= childElementNodes.length)) {
            return null;
        }
        return childElementNodes[index];
    }

    // ---------------------------------------------------------------- first child

    /**
     * Returns first child or null if no children exist.
     */
    public Node getFirstChild() {
        if (childNodes == null) {
            return null;
        }
        if (childNodes.isEmpty()) {
            return null;
        }
        return childNodes.get(0);
    }

    /**
     * Returns first child element node or
     * null if no element children exist.
     */
    public Element getFirstChildElement() {
        initChildElementNodes();
        if (childElementNodes.length == 0) {
            return null;
        }
        return childElementNodes[0];
    }

    /**
     * Returns first child element with given name or
     * null if no such children exist.
     */
    public Element getFirstChildElement(String elementName) {
        if (childNodes == null) {
            return null;
        }
        for (int i = 0, childNodesSize = childNodes.size(); i < childNodesSize; i++) {
            Node child = childNodes.get(i);
            if (child.getNodeType() == NodeType.ELEMENT && elementName.equals(child.getNodeName())) {
                child.initSiblingNames();
                return (Element) child;
            }
        }
        return null;
    }

    // ---------------------------------------------------------------- last child

    /**
     * Returns last child or null if no children exist.
     */
    public Node getLastChild() {
        if (childNodes == null) {
            return null;
        }
        if (childNodes.isEmpty()) {
            return null;
        }
        return childNodes.get(getChildNodesCount() - 1);
    }

    /**
     * Returns last child element node or
     * null if no such child node exist.
     */
    public Element getLastChildElement() {
        initChildElementNodes();
        if (childElementNodes.length == 0) {
            return null;
        }
        return childElementNodes[childElementNodes.length - 1];
    }

    /**
     * Returns last child element with given name or
     * null if no such child node exist.
     */
    public Element getLastChildElement(String elementName) {
        if (childNodes == null) {
            return null;
        }
        int from = childNodes.size() - 1;
        for (int i = from; i >= 0; i--) {
            Node child = childNodes.get(i);
            if (child.getNodeType() == NodeType.ELEMENT && elementName.equals(child.getNodeName())) {
                child.initSiblingNames();
                return (Element) child;
            }
        }
        return null;
    }

    // ---------------------------------------------------------------- internal

    /**
     * Checks the health of child nodes. Useful during complex tree manipulation,
     * to check if everything is OK. Not optimized for speed, should be used just
     * for testing purposes.
     */
    public boolean check() {

        if (childNodes == null) {
            return true;
        }

        // children
        int siblingElementIndex = 0;
        for (int i = 0, childNodesSize = childNodes.size(); i < childNodesSize; i++) {
            Node childNode = childNodes.get(i);

            if (childNode.siblingIndex != i) {
                return false;
            }

            if (childNode.getNodeType() == NodeType.ELEMENT) {
                if (childNode.siblingElementIndex != siblingElementIndex) {
                    return false;
                }
                siblingElementIndex++;
            }
        }

        if (childElementNodesCount != siblingElementIndex) {
            return false;
        }

        // child element nodes
        if (childElementNodes != null) {
            if (childElementNodes.length != childElementNodesCount) {
                return false;
            }

            int childCount = getChildNodesCount();
            for (int i = 0; i < childCount; i++) {
                Node child = getChild(i);
                if (child.siblingElementIndex >= 0) {
                    if (childElementNodes[child.siblingElementIndex] != child) {
                        return false;
                    }
                }
            }
        }

        // sibling names
        if (siblingNameIndex != -1) {
            List siblings = parentNode.childNodes;
            int index = 0;
            for (int i = 0, siblingsSize = siblings.size(); i < siblingsSize; i++) {
                Node sibling = siblings.get(i);
                if (sibling.siblingNameIndex == -1
                        && nodeType == NodeType.ELEMENT
                        && nodeName.equals(sibling.getNodeName())) {
                    if (sibling.siblingNameIndex != index++) {
                        return false;
                    }
                }
            }
        }

        // process children
        for (Node childNode : childNodes) {
            if (!childNode.check()) {
                return false;
            }
        }
        return true;
    }

    /**
     * Reindex children nodes. Must be called on every children addition/removal.
     * Iterates {@link #childNodes} list and:
     * 
    *
  • calculates three different sibling indexes,
  • *
  • calculates total child element node count,
  • *
  • resets child element nodes array (will be init lazy later by @{#initChildElementNodes}.
  • *
*/ protected void reindexChildren() { int siblingElementIndex = 0; for (int i = 0, childNodesSize = childNodes.size(); i < childNodesSize; i++) { Node childNode = childNodes.get(i); childNode.siblingIndex = i; childNode.siblingNameIndex = -1; // reset sibling name info if (childNode.getNodeType() == NodeType.ELEMENT) { childNode.siblingElementIndex = siblingElementIndex; siblingElementIndex++; } } childElementNodesCount = siblingElementIndex; childElementNodes = null; // reset child element nodes } /** * Optimized variant of {@link #reindexChildren()} for addition. * Only added children are optimized. */ protected void reindexChildrenOnAdd(int addedCount) { int childNodesSize = childNodes.size(); int previousSize = childNodes.size() - addedCount; int siblingElementIndex = childElementNodesCount; for (int i = previousSize; i < childNodesSize; i++) { Node childNode = childNodes.get(i); childNode.siblingIndex = i; childNode.siblingNameIndex = -1; // reset sibling name info if (childNode.getNodeType() == NodeType.ELEMENT) { childNode.siblingElementIndex = siblingElementIndex; siblingElementIndex++; } } childElementNodesCount = siblingElementIndex; childElementNodes = null; // reset child element nodes } /** * Initializes list of child elements. */ protected void initChildElementNodes() { if (childElementNodes == null) { childElementNodes = new Element[childElementNodesCount]; int childCount = getChildNodesCount(); for (int i = 0; i < childCount; i++) { Node child = getChild(i); if (child.siblingElementIndex >= 0) { childElementNodes[child.siblingElementIndex] = (Element) child; } } } } /** * Initializes siblings elements of the same name. */ protected void initSiblingNames() { if (siblingNameIndex == -1) { List siblings = parentNode.childNodes; int index = 0; for (int i = 0, siblingsSize = siblings.size(); i < siblingsSize; i++) { Node sibling = siblings.get(i); if (nodeType == NodeType.ELEMENT && nodeName.equals(sibling.getNodeName())) { sibling.siblingNameIndex = index++; } } } } /** * Initializes attributes when needed. */ protected void initAttributes() { if (attributes == null) { attributes = new ArrayList(5); } } /** * Initializes child nodes list when needed. * Also fix owner document for new node, if needed. */ protected void initChildNodes(Node newNode) { if (childNodes.equals(Collections.emptyList())) { childNodes = new ArrayList<>(); } if (ownerDocument != null) { if (newNode.ownerDocument != ownerDocument) { changeOwnerDocument(newNode, ownerDocument); } } } /** * Changes owner document for given node and all its children. */ protected void changeOwnerDocument(Node node, Document ownerDocument) { node.ownerDocument = ownerDocument; int childCount = node.getChildNodesCount(); for (int i = 0; i < childCount; i++) { Node child = node.getChild(i); changeOwnerDocument(child, ownerDocument); } } // ---------------------------------------------------------------- siblings index /** * Get the list index of this node in its node sibling list. * For example, if this is the first node sibling, returns 0. * Index address all nodes, i.e. of all node types. */ public int getSiblingIndex() { return siblingIndex; } public int getSiblingElementIndex() { return siblingElementIndex; } public int getSiblingNameIndex() { initSiblingNames(); return siblingNameIndex; } // ---------------------------------------------------------------- next /** * Returns this node's next sibling of any type or * null if this is the last sibling. */ public Node getNextSibling() { List siblings = parentNode.childNodes; int index = siblingIndex + 1; if (index >= siblings.size()) { return null; } return siblings.get(index); } /** * Returns this node's next element. */ public Node getNextSiblingElement() { parentNode.initChildElementNodes(); if (siblingElementIndex == -1) { int max = parentNode.getChildNodesCount(); for (int i = siblingIndex; i < max; i++) { Node sibling = parentNode.childNodes.get(i); if (sibling.getNodeType() == NodeType.ELEMENT) { return sibling; } } return null; } int index = siblingElementIndex + 1; if (index >= parentNode.childElementNodesCount) { return null; } return parentNode.childElementNodes[index]; } /** * Returns this node's next element with the same name. */ public Node getNextSiblingName() { if (nodeName == null) { return null; } initSiblingNames(); int index = siblingNameIndex + 1; int max = parentNode.getChildNodesCount(); for (int i = siblingIndex + 1; i < max; i++) { Node sibling = parentNode.childNodes.get(i); if ((index == sibling.siblingNameIndex) && nodeName.equals(sibling.getNodeName())) { return sibling; } } return null; } // ---------------------------------------------------------------- prev /** * Returns this node's previous sibling of any type * or null if this is the first sibling. */ public Node getPreviousSibling() { if (parentNode == null){ return null; } List siblings = parentNode.childNodes; int index = siblingIndex - 1; if (index < 0) { return null; } return siblings.get(index); } /** * Returns this node's previous sibling of element type * or null if this is the first sibling. */ public Node getPreviousSiblingElement() { parentNode.initChildElementNodes(); if (siblingElementIndex == -1) { for (int i = siblingIndex - 1; i >= 0; i--) { Node sibling = parentNode.childNodes.get(i); if (sibling.getNodeType() == NodeType.ELEMENT) { return sibling; } } return null; } int index = siblingElementIndex - 1; if (index < 0) { return null; } return parentNode.childElementNodes[index]; } /** * Returns this node's previous sibling element with the same name. */ public Node getPreviousSiblingName() { if (nodeName == null) { return null; } initSiblingNames(); int index = siblingNameIndex - 1; for (int i = siblingIndex; i >= 0; i--) { Node sibling = parentNode.childNodes.get(i); if ((index == sibling.siblingNameIndex) && nodeName.equals(sibling.getNodeName())) { return sibling; } } return null; } // ---------------------------------------------------------------- html /** * Returns the text content of this node and its descendants. * * @see #appendTextContent(Appendable) */ public String getTextContent() { StringBuilder sb = new StringBuilder(getChildNodesCount() + 1); appendTextContent(sb); return sb.toString(); } /** * Appends the text content to an Appendable * (StringBuilder, CharBuffer...). * This way we can reuse the Appendable instance * during the creation of text content and have better performances. */ public void appendTextContent(Appendable appendable) { if (nodeValue != null) { if ((nodeType == NodeType.TEXT) || (nodeType == NodeType.CDATA)) { try { appendable.append(nodeValue); } catch (IOException ioex) { throw new LagartoDOMException(ioex); } } } if (childNodes != null) { for (int i = 0, childNodesSize = childNodes.size(); i < childNodesSize; i++) { Node childNode = childNodes.get(i); childNode.appendTextContent(appendable); } } } // ---------------------------------------------------------------- visit /** * Visits the DOM tree. */ public void visit(NodeVisitor nodeVisitor) { visitNode(nodeVisitor); } /** * Visits children nodes. */ protected void visitChildren(NodeVisitor nodeVisitor) { if (childNodes != null) { for (int i = 0, childNodesSize = childNodes.size(); i < childNodesSize; i++) { Node childNode = childNodes.get(i); childNode.visit(nodeVisitor); } } } /** * Visits single node. Implementations just needs to call * the correct visitor callback function. */ protected abstract void visitNode(NodeVisitor nodeVisitor); // ---------------------------------------------------------------- misc /** * Returns CSS path to this node from document root. */ public String getCssPath() { StringBuilder path = new StringBuilder(); Node node = this; while (node != null) { String nodeName = node.getNodeName(); if (nodeName != null) { StringBuilder sb = new StringBuilder(); sb.append(' '); sb.append(nodeName); String id = node.getAttribute("id"); if (id != null) { sb.append('#').append(id); } path.insert(0, sb); } node = node.getParentNode(); } if (path.charAt(0) == ' ') { return path.substring(1); } return path.toString(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy