Added two iterators (iterChildOrTop and iterAttributeOrTop) for XSLT support.
[tx] / iterators.py
1 # -*- coding:utf-8 -*-
2
3 # Iterator for traversing xml tree in various way
4 # Copyright (C) 2005  Frédéric Jolliton <frederic@jolliton.com>
5
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19
20 # [xpath20] is http://www.w3.org/TR/xpath20/
21
22 # [xpath20] "The parent, ancestor, ancestor-or-self, preceding, and
23 # preceding-sibling axes are reverse axes; all other axes are forward
24 # axes. The ancestor, descendant, following, preceding and self axes
25 # partition a document (ignoring attribute and namespace nodes): they
26 # do not overlap and together they contain all the nodes in the
27 # document."
28
29 __all__ = [
30         # XPath 2.0 axis
31         'iterChild' ,
32         'iterDescendant' ,
33         'iterAttribute' ,
34         'iterSelf' ,
35         'iterDescendantOrSelf' ,
36         'iterFollowingSibling' ,
37         'iterFollowing' ,
38         'iterParent' ,
39         'iterAncestor' ,
40         'iterPrecedingSibling' ,
41         'iterPreceding' ,
42         'iterAncestorOrSelf' ,
43         # XSLT Pattern axis
44         'iterChildOrTop' ,
45         'iterAttributeOrTop' ,
46         # TX Extension
47         'iterDescendantOrSelfFull' ,
48         'iterChildReversed' ,
49         'iterDescendantReversed' ,
50         'iterDescendantOrSelfReversed'
51 ]
52
53 from nodes import *
54
55 #----------------------------------------------------------------------------
56 #
57 # Forward Axis
58 #
59 #----------------------------------------------------------------------------
60
61 # [xpath20] "The child axis contains the children of the context node,
62 # which are the nodes returned by the dm:children accessor in [XQuery
63 # 1.0 and XPath 2.0 Data Model]. Note: Only document nodes and element
64 # nodes have children. If the context node is any other kind of node,
65 # or if the context node is an empty document or element node, then
66 # the child axis is an empty sequence. The children of a document node
67 # or element node may be element, processing instruction, comment, or
68 # text nodes. Attribute, namespace, and document nodes can never
69 # appear as children."
70
71 def iterChild( node ) :
72
73         '''Children of the node 'node' in document order.'''
74
75         if isinstance( node , ( Document , Element ) ) :
76                 for child in node.children :
77                         yield child
78
79 # [xslt20] "If the context node is a parentless element, comment,
80 # processing-instruction, or text node then the child-or-top axis
81 # selects the context node; otherwise it selects the children of the
82 # context node. It is a forwards axis whose principal node kind is
83 # element."
84
85 def iterChildOrTop( node ) :
86
87         if node.parent is None \
88                     and isinstance( node , ( Element , Comment , ProcessingInstruction , Text ) ) :
89                 yield node
90         elif isinstance( node , ( Document , Element ) ) :
91                 for child in node.children :
92                         yield child
93
94 # [xpath20] "the descendant axis is defined as the transitive closure
95 # of the child axis; it contains the descendants of the context node
96 # (the children, the children of the children, and so on)"
97
98 def iterDescendant( node ) :
99
100         '''Descendants of the node 'node' in document order.'''
101
102         if isinstance( node , ( Document , Element ) ) :
103                 current = node
104                 if current.children :
105                         current = current.children[ 0 ]
106                         while current is not node :
107                                 yield current
108                                 if isinstance( current , Element ) :
109                                         if current.children :
110                                                 current = current.children[ 0 ]
111                                                 continue
112                                 while current is not node and current.next is None :
113                                         current = current.parent
114                                 if current is not node :
115                                         current = current.next                                          
116
117 # [xpath20] "the attribute axis contains the attributes of the context
118 # node, which are the nodes returned by the dm:attributes accessor in
119 # [XQuery 1.0 and XPath 2.0 Data Model]; the axis will be empty unless
120 # the context node is an element"
121
122 def iterAttribute( node ) :
123
124         '''Attributes of the node 'node' in stable order.'''
125
126         if isinstance( node , Element ) :
127                 for attribute in node.attributes :
128                         yield attribute
129
130 # [xslt20] "If the context node is an attribute node with no parent,
131 # then the attribute-or-top axis selects the context node; otherwise
132 # it selects the attributes of the context node. It is a forwards axis
133 # whose principal node kind is attribute."
134
135 def iterAttributeOrTop( node ) :
136
137         if node.parent is None and isinstance( node , Attribute ) :
138                 yield node
139         elif isinstance( node , Element ) :
140                 for attribute in node.attributes :
141                         yield attribute
142
143 # [xpath20] "the self axis contains just the context node itself"
144
145 def iterSelf( node ) :
146
147         ''''node' and descendants of 'node' in document order.'''
148
149         if isinstance( node , Node ) :
150                 yield node
151
152 # [xpath20] "the descendant-or-self axis contains the context node and
153 # the descendants of the context node"
154
155 def iterDescendantOrSelf( node ) :
156
157         ''''node' and descendants of 'node' in document order.'''
158
159         if isinstance( node , Node ) : # FIXME: Really allow Attribute ?
160                 yield node
161                 if isinstance( node , ( Document , Element ) ) :
162                         current = node
163                         if current.children :
164                                 current = current.children[ 0 ]
165                                 while current is not node :
166                                         yield current
167                                         if isinstance( current , Element ) :
168                                                 if current.children :
169                                                         current = current.children[ 0 ]
170                                                         continue
171                                         while current is not node and current.next is None :
172                                                 current = current.parent
173                                         if current is not node :
174                                                 current = current.next                                          
175
176 def iterDescendantOrSelfFull( node ) :
177
178         ''''node' and descendants of 'node' in document order.'''
179
180         if isinstance( node , ( Document , Element , Text ) ) :
181                 yield node
182                 current = node
183                 if current.children :
184                         current = current.children[ 0 ]
185                         while current is not node :
186                                 yield current
187                                 if isinstance( current , Element ) :
188                                         for attribute in current.attributes :
189                                                 yield attribute
190                                         if current.children :
191                                                 current = current.children[ 0 ]
192                                                 continue
193                                 while current is not node and current.next is None :
194                                         current = current.parent
195                                 if current is not node :
196                                         current = current.next
197
198 # [xpath20] "the following-sibling axis contains the context node's
199 # following siblings, those children of the context node's parent that
200 # occur after the context node in document order; if the context node
201 # is an attribute or namespace node, the following-sibling axis is
202 # empty"
203
204 def iterFollowingSibling( node ) :
205
206         '''Following siblings of node 'node' in document order.'''
207
208         if isinstance( node , Node ) and not isinstance( node , Attribute ) :
209                 parent = node.parent
210                 if parent is not None :
211                         found = False
212                         for sibling in parent.children :
213                                 if found :
214                                         yield sibling
215                                 elif sibling is node :
216                                         found = True
217
218 # [xpath20] "the following axis contains all nodes that are
219 # descendants of the root of the tree in which the context node is
220 # found, are not descendants of the context node, and occur after the
221 # context node in document order"
222
223 def iterFollowing( node ) :
224
225         '''Following nodes of node 'node' in document order.'''
226
227         if isinstance( node , Node ) and not isinstance( node , Attribute ) :
228                 current = node
229                 while current is not None :
230                         for sibling in iterFollowingSibling( current ) :
231                                 for descendant in iterDescendantOrSelf( sibling ) :
232                                         yield descendant
233                         current = current.parent
234
235 #----------------------------------------------------------------------------
236 #
237 # Reverse Axis
238 #
239 #----------------------------------------------------------------------------
240
241 def iterChildReversed( node ) :
242
243         '''Children of the node 'node' in reverse document order.'''
244
245         if isinstance( node , ( Document , Element ) ) :
246                 for child in reversed( node.children ) :
247                         yield child
248
249 def iterDescendantReversed( node ) :
250
251         '''Descendants of the node 'node' in reverse document order.'''
252
253         if isinstance( node , ( Document , Element ) ) :
254                 for node in iterChildReversed( node ) :
255                         for descendant in iterDescendantReversed( node ) :
256                                 yield descendant
257                         yield node
258
259 def iterDescendantOrSelfReversed( node ) :
260
261         ''''node' and descendants of 'node' in reverse document order.'''
262
263         if isinstance( node , ( Document , Element ) ):
264                 for descendant in iterDescendantReversed( node ) :
265                         yield descendant
266                 yield node
267
268 # [xpath20] "the parent axis contains the sequence returned by the
269 # dm:parent accessor in [XQuery 1.0 and XPath 2.0 Data Model], which
270 # returns the parent of the context node, or an empty sequence if the
271 # context node has no parent. Note: An attribute node may have an
272 # element node as its parent, even though the attribute node is not a
273 # child of the element node."
274
275 def iterParent( node ) :
276
277         '''Parent of node 'node'.'''
278
279         if isinstance( node , Node ) :
280                 parent = node.parent
281                 if parent is not None :
282                         yield parent
283
284 # [xpath20] "the ancestor axis is defined as the transitive closure of
285 # the parent axis; it contains the ancestors of the context node (the
286 # parent, the parent of the parent, and so on) Note: The ancestor axis
287 # includes the root node of the tree in which the context node is
288 # found, unless the context node is the root node."
289
290 def iterAncestor( node ) :
291
292         '''Ancestors of node 'node' up to root node in reverse document order.'''
293
294         if isinstance( node , Node ) :
295                 while 1 :
296                         node = node.parent
297                         if node is None :
298                                 break
299                         yield node
300
301 # [xpath20] "the preceding-sibling axis contains the context node's
302 # preceding siblings, those children of the context node's parent that
303 # occur before the context node in document order; if the context node
304 # is an attribute or namespace node, the preceding-sibling axis is
305 # empty"
306
307 def iterPrecedingSibling( node ) :
308
309         '''Preceding siblings of node 'node' in reverse document order.'''
310
311         if isinstance( node , Node ) and not isinstance( node , Attribute ) :
312                 parent = node.parent
313                 if parent is not None :
314                         found = False
315                         for sibling in reversed( parent.children ) :
316                                 if found :
317                                         yield sibling
318                                 elif sibling is node :
319                                         found = True
320
321 # [xpath20] "the preceding axis contains all nodes that are
322 # descendants of the root of the tree in which the context node is
323 # found, are not ancestors of the context node, and occur before the
324 # context node in document order"
325
326 def iterPreceding( node ) :
327
328         '''Predecing nodes of node 'node' in reverse document order.'''
329
330         if isinstance( node , Node ) and not isinstance( node , Attribute ) :
331                 while node is not None :
332                         for sibling in iterPrecedingSibling( node ) :
333                                 for descendant in iterDescendantOrSelfReversed( sibling ) :
334                                         yield descendant
335                         node = node.parent
336
337 # [xpath20] "the ancestor-or-self axis contains the context node and
338 # the ancestors of the context node; thus, the ancestor-or-self axis
339 # will always include the root node"
340
341 def iterAncestorOrSelf( node ) :
342
343         ''''node' and ancestors of node 'node' in reverse document order.'''
344
345         if isinstance( node , Node ) :
346                 yield node
347                 for ancestor in iterAncestor( node ) :
348                         yield ancestor
349
350 # Local Variables:
351 # tab-width: 4
352 # python-indent: 4
353 # End: