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}