View Javadoc
1   /*
2    * Copyright (c) 2002-2025 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit.html;
16  
17  import org.w3c.dom.Node;
18  import org.w3c.dom.traversal.NodeFilter;
19  import org.w3c.dom.traversal.NodeIterator;
20  
21  /**
22   * An implementation of {@link NodeIterator}.
23   *
24   * @author Ahmed Ashour
25   * @author Ronald Brill
26   */
27  public class DomNodeIterator implements NodeIterator {
28  
29      private final DomNode root_;
30      private final int whatToShow_;
31      private final NodeFilter filter_;
32      private DomNode referenceNode_;
33      private final boolean expandEntityReferences_;
34      private boolean pointerBeforeReferenceNode_;
35  
36      /**
37       * Creates a new instance.
38       *
39       * @param root The root node at which to begin the {@link NodeIterator}'s traversal
40       * @param whatToShow an optional int representing a bitmask created by combining
41       *        the constant properties of {@link NodeFilter}
42       * @param expandEntityReferences If false, the contents of
43       *          EntityReference nodes are not present in the logical view.
44       * @param filter an object implementing the {@link NodeFilter} interface
45       */
46      public DomNodeIterator(final DomNode root, final int whatToShow, final NodeFilter filter,
47              final boolean expandEntityReferences) {
48          root_ = root;
49          referenceNode_ = root;
50          whatToShow_ = whatToShow;
51          filter_ = filter;
52          expandEntityReferences_ = expandEntityReferences;
53          pointerBeforeReferenceNode_ = true;
54      }
55  
56      /**
57       * {@inheritDoc}
58       */
59      @Override
60      public DomNode getRoot() {
61          return root_;
62      }
63  
64      /**
65       * {@inheritDoc}
66       */
67      @Override
68      public int getWhatToShow() {
69          return whatToShow_;
70      }
71  
72      /**
73       * {@inheritDoc}
74       */
75      @Override
76      public boolean getExpandEntityReferences() {
77          return expandEntityReferences_;
78      }
79  
80      /**
81       * {@inheritDoc}
82       */
83      @Override
84      public NodeFilter getFilter() {
85          return filter_;
86      }
87  
88      /**
89       * Returns whether the {@link NodeIterator} is anchored before, or after the node.
90       * @return whether it is anchored before or after the node
91       */
92      public boolean isPointerBeforeReferenceNode() {
93          return pointerBeforeReferenceNode_;
94      }
95  
96      /**
97       * {@inheritDoc}
98       */
99      @Override
100     public void detach() {
101         // nothing to do
102     }
103 
104     /**
105      * {@inheritDoc}
106      */
107     @Override
108     public DomNode nextNode() {
109         return traverse(true);
110     }
111 
112     /**
113      * {@inheritDoc}
114      */
115     @Override
116     public DomNode previousNode() {
117         return traverse(false);
118     }
119 
120     private DomNode traverse(final boolean next) {
121         DomNode node = referenceNode_;
122         boolean beforeNode = pointerBeforeReferenceNode_;
123         do {
124             if (next) {
125                 if (beforeNode) {
126                     beforeNode = false;
127                 }
128                 else {
129                     final DomNode leftChild = getChild(node, true);
130                     if (leftChild == null) {
131                         final DomNode rightSibling = getSibling(node, false);
132                         if (rightSibling == null) {
133                             node = getFirstUncleNode(node);
134                         }
135                         else {
136                             node = rightSibling;
137                         }
138                     }
139                     else {
140                         node = leftChild;
141                     }
142                 }
143             }
144             else {
145                 if (beforeNode) {
146                     final DomNode left = getSibling(node, true);
147                     if (left == null) {
148                         final Node parent = node.getParentNode();
149                         if (parent == null) {
150                             node = null;
151                         }
152                     }
153 
154                     DomNode follow = left;
155                     if (follow != null) {
156                         while (follow.hasChildNodes()) {
157                             final DomNode toFollow = getChild(follow, false);
158                             if (toFollow == null) {
159                                 break;
160                             }
161                             follow = toFollow;
162                         }
163                     }
164                     node = follow;
165                 }
166                 else {
167                     beforeNode = true;
168                 }
169             }
170         }
171         while (node != null && (!isNodeVisible(node) || !isAccepted(node)));
172 
173         //apply filter here and loop
174 
175         referenceNode_ = node;
176         pointerBeforeReferenceNode_ = beforeNode;
177         return node;
178     }
179 
180     private boolean isNodeVisible(final Node node) {
181         return (whatToShow_ & HtmlDomTreeWalker.getFlagForNode(node)) != 0;
182     }
183 
184     private boolean isAccepted(final Node node) {
185         if (filter_ == null) {
186             return true;
187         }
188         return filter_.acceptNode(node) == NodeFilter.FILTER_ACCEPT;
189     }
190 
191     /**
192      * Helper method to get the first uncle node in document order (preorder
193      * traversal) from the given node.
194      */
195     private DomNode getFirstUncleNode(final DomNode node) {
196         if (node == null || node == root_) {
197             return null;
198         }
199 
200         final DomNode parent = node.getParentNode();
201         if (parent == null || parent == root_) {
202             return null;
203         }
204 
205         final DomNode uncle = getSibling(parent, false);
206         if (uncle != null) {
207             return uncle;
208         }
209 
210         return getFirstUncleNode(parent);
211     }
212 
213     private static DomNode getChild(final DomNode node, final boolean lookLeft) {
214         if (node == null) {
215             return null;
216         }
217 
218         final DomNode child;
219         if (lookLeft) {
220             child = node.getFirstChild();
221         }
222         else {
223             child = node.getLastChild();
224         }
225 
226         return child;
227     }
228 
229     private static DomNode getSibling(final DomNode node, final boolean lookLeft) {
230         if (node == null) {
231             return null;
232         }
233 
234         final DomNode sibling;
235         if (lookLeft) {
236             sibling = node.getPreviousSibling();
237         }
238         else {
239             sibling = node.getNextSibling();
240         }
241 
242         return sibling;
243     }
244 }