001package org.jsoup.select;
002
003import org.jsoup.helper.Validate;
004import org.jsoup.nodes.Element;
005import org.jsoup.nodes.Node;
006import org.jsoup.select.NodeFilter.FilterResult;
007
008/**
009 A depth-first node traversor. Use to walk through all nodes under and including the specified root node, in document
010 order. The {@link NodeVisitor#head(Node, int)} and {@link NodeVisitor#tail(Node, int)} methods will be called for
011 each node.
012 <p> During traversal, structural changes to nodes are supported (e.g. {{@link Node#replaceWith(Node)},
013 {@link Node#remove()}}
014 </p>
015 */
016public class NodeTraversor {
017    /**
018     Run a depth-first traverse of the root and all of its descendants.
019     @param visitor Node visitor.
020     @param root the initial node point to traverse.
021     @see NodeVisitor#traverse(Node root)
022     */
023    public static void traverse(NodeVisitor visitor, Node root) {
024        Validate.notNull(visitor);
025        Validate.notNull(root);
026        Node node = root;
027        int depth = 0;
028
029        while (node != null) {
030            Node parent = node.parentNode(); // remember parent to find nodes that get replaced in .head
031            int origSize = parent != null ? parent.childNodeSize() : 0;
032            Node next = node.nextSibling();
033
034            visitor.head(node, depth); // visit current node
035
036            // check for modifications to the tree
037            if (parent != null && !node.hasParent()) { // removed or replaced
038                if (origSize == parent.childNodeSize()) { // replaced
039                    node = parent.childNode(node.siblingIndex()); // replace ditches parent but keeps sibling index
040                    continue;
041                }
042                // else, removed
043                node = next;
044                if (node == null) {
045                    // was last in parent. need to walk up the tree, tail()ing on the way, until we find a suitable next. Otherwise, would revisit ancestor nodes.
046                    node = parent;
047                    while (true) {
048                        depth--;
049                        visitor.tail(node, depth);
050                        if (node == root) break;
051                        if (node.nextSibling() != null) {
052                            node = node.nextSibling();
053                            break;
054                        }
055                        node = node.parentNode();
056                        if (node == null) break;
057                    }
058                    if (node == root || node == null) break; // done, break outer
059                }
060                continue; // don't tail removed
061            }
062
063            if (node.childNodeSize() > 0) { // descend
064                node = node.childNode(0);
065                depth++;
066            } else {
067                while (true) {
068                    assert node != null; // as depth > 0, will have parent
069                    if (!(node.nextSibling() == null && depth > 0)) break;
070                    visitor.tail(node, depth); // when no more siblings, ascend
071                    node = node.parentNode();
072                    depth--;
073                }
074                visitor.tail(node, depth);
075                if (node == root)
076                    break;
077                node = node.nextSibling();
078            }
079        }
080    }
081
082    /**
083     Run a depth-first traversal of each Element.
084     @param visitor Node visitor.
085     @param elements Elements to traverse.
086     */
087    public static void traverse(NodeVisitor visitor, Elements elements) {
088        Validate.notNull(visitor);
089        Validate.notNull(elements);
090        for (Element el : elements)
091            traverse(visitor, el);
092    }
093
094    /**
095     Run a depth-first filtered traversal of the root and all of its descendants.
096     @param filter NodeFilter visitor.
097     @param root the root node point to traverse.
098     @return The filter result of the root node, or {@link FilterResult#STOP}.
099
100     @see NodeFilter
101     */
102    public static FilterResult filter(NodeFilter filter, Node root) {
103        Node node = root;
104        int depth = 0;
105
106        while (node != null) {
107            FilterResult result = filter.head(node, depth);
108            if (result == FilterResult.STOP)
109                return result;
110            // Descend into child nodes:
111            if (result == FilterResult.CONTINUE && node.childNodeSize() > 0) {
112                node = node.childNode(0);
113                ++depth;
114                continue;
115            }
116            // No siblings, move upwards:
117            while (true) {
118                assert node != null; // depth > 0, so has parent
119                if (!(node.nextSibling() == null && depth > 0)) break;
120                // 'tail' current node:
121                if (result == FilterResult.CONTINUE || result == FilterResult.SKIP_CHILDREN) {
122                    result = filter.tail(node, depth);
123                    if (result == FilterResult.STOP)
124                        return result;
125                }
126                Node prev = node; // In case we need to remove it below.
127                node = node.parentNode();
128                depth--;
129                if (result == FilterResult.REMOVE)
130                    prev.remove(); // Remove AFTER finding parent.
131                result = FilterResult.CONTINUE; // Parent was not pruned.
132            }
133            // 'tail' current node, then proceed with siblings:
134            if (result == FilterResult.CONTINUE || result == FilterResult.SKIP_CHILDREN) {
135                result = filter.tail(node, depth);
136                if (result == FilterResult.STOP)
137                    return result;
138            }
139            if (node == root)
140                return result;
141            Node prev = node; // In case we need to remove it below.
142            node = node.nextSibling();
143            if (result == FilterResult.REMOVE)
144                prev.remove(); // Remove AFTER finding sibling.
145        }
146        // root == null?
147        return FilterResult.CONTINUE;
148    }
149
150    /**
151     Run a depth-first filtered traversal of each Element.
152     @param filter NodeFilter visitor.
153     @see NodeFilter
154     */
155    public static void filter(NodeFilter filter, Elements elements) {
156        Validate.notNull(filter);
157        Validate.notNull(elements);
158        for (Element el : elements)
159            if (filter(filter, el) == FilterResult.STOP)
160                break;
161    }
162}