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 java.io.Serializable;
18  
19  import org.w3c.dom.Node;
20  import org.w3c.dom.traversal.NodeFilter;
21  import org.w3c.dom.traversal.NodeIterator;
22  
23  /**
24   * An implementation of {@link NodeIterator}.
25   *
26   * @author Ahmed Ashour
27   * @author Ronald Brill
28   */
29  public class DomNodeIterator implements NodeIterator, Serializable {
30  
31      private final DomNode root_;
32      private final int whatToShow_;
33      private final NodeFilter filter_;
34      private DomNode referenceNode_;
35      private final boolean expandEntityReferences_;
36      private boolean pointerBeforeReferenceNode_;
37  
38      /**
39       * Creates a new instance.
40       *
41       * @param root The root node at which to begin the {@link NodeIterator}'s traversal
42       * @param whatToShow an optional int representing a bitmask created by combining
43       *        the constant properties of {@link NodeFilter}
44       * @param expandEntityReferences If false, the contents of
45       *          EntityReference nodes are not present in the logical view.
46       * @param filter an object implementing the {@link NodeFilter} interface
47       */
48      public DomNodeIterator(final DomNode root, final int whatToShow, final NodeFilter filter,
49              final boolean expandEntityReferences) {
50          root_ = root;
51          referenceNode_ = root;
52          whatToShow_ = whatToShow;
53          filter_ = filter;
54          expandEntityReferences_ = expandEntityReferences;
55          pointerBeforeReferenceNode_ = true;
56      }
57  
58      /**
59       * {@inheritDoc}
60       */
61      @Override
62      public DomNode getRoot() {
63          return root_;
64      }
65  
66      /**
67       * {@inheritDoc}
68       */
69      @Override
70      public int getWhatToShow() {
71          return whatToShow_;
72      }
73  
74      /**
75       * {@inheritDoc}
76       */
77      @Override
78      public boolean getExpandEntityReferences() {
79          return expandEntityReferences_;
80      }
81  
82      /**
83       * {@inheritDoc}
84       */
85      @Override
86      public NodeFilter getFilter() {
87          return filter_;
88      }
89  
90      /**
91       * Returns whether the {@link NodeIterator} is anchored before, or after the node.
92       * @return whether it is anchored before or after the node
93       */
94      public boolean isPointerBeforeReferenceNode() {
95          return pointerBeforeReferenceNode_;
96      }
97  
98      /**
99       * {@inheritDoc}
100      */
101     @Override
102     public void detach() {
103         // nothing to do
104     }
105 
106     /**
107      * {@inheritDoc}
108      */
109     @Override
110     public DomNode nextNode() {
111         return traverse(true);
112     }
113 
114     /**
115      * {@inheritDoc}
116      */
117     @Override
118     public DomNode previousNode() {
119         return traverse(false);
120     }
121 
122     private DomNode traverse(final boolean next) {
123         DomNode node = referenceNode_;
124         boolean beforeNode = pointerBeforeReferenceNode_;
125         do {
126             if (next) {
127                 if (beforeNode) {
128                     beforeNode = false;
129                 }
130                 else {
131                     final DomNode leftChild = getChild(node, true);
132                     if (leftChild == null) {
133                         final DomNode rightSibling = getSibling(node, false);
134                         if (rightSibling == null) {
135                             node = getFirstUncleNode(node);
136                         }
137                         else {
138                             node = rightSibling;
139                         }
140                     }
141                     else {
142                         node = leftChild;
143                     }
144                 }
145             }
146             else {
147                 if (beforeNode) {
148                     DomNode follow = getSibling(node, true);
149                     if (follow != null) {
150                         while (follow.hasChildNodes()) {
151                             final DomNode toFollow = getChild(follow, false);
152                             if (toFollow == null) {
153                                 break;
154                             }
155                             follow = toFollow;
156                         }
157                     }
158                     node = follow;
159                 }
160                 else {
161                     beforeNode = true;
162                 }
163             }
164         }
165         while (node != null && (!isNodeVisible(node) || !isAccepted(node)));
166 
167         //apply filter here and loop
168 
169         referenceNode_ = node;
170         pointerBeforeReferenceNode_ = beforeNode;
171         return node;
172     }
173 
174     private boolean isNodeVisible(final Node node) {
175         return (whatToShow_ & HtmlDomTreeWalker.getFlagForNode(node)) != 0;
176     }
177 
178     private boolean isAccepted(final Node node) {
179         if (filter_ == null) {
180             return true;
181         }
182         return filter_.acceptNode(node) == NodeFilter.FILTER_ACCEPT;
183     }
184 
185     /**
186      * Helper method to get the first uncle node in document order (preorder
187      * traversal) from the given node.
188      */
189     private DomNode getFirstUncleNode(final DomNode node) {
190         if (node == null || node == root_) {
191             return null;
192         }
193 
194         final DomNode parent = node.getParentNode();
195         if (parent == null || parent == root_) {
196             return null;
197         }
198 
199         final DomNode uncle = getSibling(parent, false);
200         if (uncle != null) {
201             return uncle;
202         }
203 
204         return getFirstUncleNode(parent);
205     }
206 
207     private static DomNode getChild(final DomNode node, final boolean lookLeft) {
208         if (node == null) {
209             return null;
210         }
211 
212         final DomNode child;
213         if (lookLeft) {
214             child = node.getFirstChild();
215         }
216         else {
217             child = node.getLastChild();
218         }
219 
220         return child;
221     }
222 
223     private static DomNode getSibling(final DomNode node, final boolean lookLeft) {
224         if (node == null) {
225             return null;
226         }
227 
228         final DomNode sibling;
229         if (lookLeft) {
230             sibling = node.getPreviousSibling();
231         }
232         else {
233             sibling = node.getNextSibling();
234         }
235 
236         return sibling;
237     }
238 }