View Javadoc
1   /*
2    * Copyright (c) 2002-2026 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 
126         do {
127             if (next) {
128                 if (beforeNode) {
129                     beforeNode = false;
130                 }
131                 else if (node != null) {
132                     final DomNode firstChild = node.getFirstChild();
133                     if (firstChild != null) {
134                         node = firstChild;
135                     }
136                     else {
137                         final DomNode nextSibling = node.getNextSibling();
138                         node = nextSibling == null ? getFirstUncleNode(node) : nextSibling;
139                     }
140                 }
141             }
142             else {
143                 if (beforeNode) {
144                     DomNode follow = node == null ? null : node.getPreviousSibling();
145                     while (follow != null && follow.hasChildNodes()) {
146                         follow = follow.getLastChild();
147                     }
148                     node = follow;
149                 }
150                 else {
151                     beforeNode = true;
152                 }
153             }
154         }
155         while (node != null && (!isNodeVisible(node) || !isAccepted(node)));
156 
157         referenceNode_ = node;
158         pointerBeforeReferenceNode_ = beforeNode;
159         return node;
160     }
161 
162     private boolean isNodeVisible(final Node node) {
163         return (whatToShow_ & HtmlDomTreeWalker.getFlagForNode(node)) != 0;
164     }
165 
166     private boolean isAccepted(final Node node) {
167         if (filter_ == null) {
168             return true;
169         }
170         return filter_.acceptNode(node) == NodeFilter.FILTER_ACCEPT;
171     }
172 
173     /**
174      * Helper method to get the first uncle node in document order (preorder
175      * traversal) from the given node.
176      */
177     private DomNode getFirstUncleNode(final DomNode startNode) {
178         DomNode curr = startNode;
179         while (curr != null && curr != root_) {
180             final DomNode parent = curr.getParentNode();
181             if (parent == null || parent == root_) {
182                 return null;
183             }
184             final DomNode uncle = parent.getNextSibling();
185             if (uncle != null) {
186                 return uncle;
187             }
188             curr = parent;
189         }
190         return null;
191     }
192 }