6d0c3c0372adb6f086782eb7e524d7c25c8b1c65
[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         # TX Extension
44         'iterDescendantOrSelfFull' ,
45         'iterChildReversed' ,
46         'iterDescendantReversed' ,
47         'iterDescendantOrSelfReversed'
48 ]
49
50 from nodes import *
51
52 #----------------------------------------------------------------------------
53 #
54 # Forward Axis
55 #
56 #----------------------------------------------------------------------------
57
58 # [xpath20] "The child axis contains the children of the context node,
59 # which are the nodes returned by the dm:children accessor in [XQuery
60 # 1.0 and XPath 2.0 Data Model]. Note: Only document nodes and element
61 # nodes have children. If the context node is any other kind of node,
62 # or if the context node is an empty document or element node, then
63 # the child axis is an empty sequence. The children of a document node
64 # or element node may be element, processing instruction, comment, or
65 # text nodes. Attribute, namespace, and document nodes can never
66 # appear as children."
67
68 def iterChild( node ) :
69
70         '''Children of the node 'node' in document order.'''
71
72         if isinstance( node , ( Document , Element ) ) :
73                 for child in node.children :
74                         yield child
75
76 # [xpath20] "the descendant axis is defined as the transitive closure
77 # of the child axis; it contains the descendants of the context node
78 # (the children, the children of the children, and so on)"
79
80 def iterDescendant( node ) :
81
82         '''Descendants of the node 'node' in document order.'''
83
84         if isinstance( node , ( Document , Element ) ) :
85                 current = node
86                 if current.children :
87                         current = current.children[ 0 ]
88                         while current is not node :
89                                 yield current
90                                 if isinstance( current , Element ) :
91                                         if current.children :
92                                                 current = current.children[ 0 ]
93                                                 continue
94                                 while current is not node and current.next is None :
95                                         current = current.parent
96                                 if current is not node :
97                                         current = current.next                                          
98
99 # [xpath20] "the attribute axis contains the attributes of the context
100 # node, which are the nodes returned by the dm:attributes accessor in
101 # [XQuery 1.0 and XPath 2.0 Data Model]; the axis will be empty unless
102 # the context node is an element"
103
104 def iterAttribute( node ) :
105
106         '''Attributes of the node 'node' in stable order.'''
107
108         if isinstance( node , Element ) :
109                 for attribute in node.attributes :
110                         yield attribute
111
112 # [xpath20] "the self axis contains just the context node itself"
113
114 def iterSelf( node ) :
115
116         ''''node' and descendants of 'node' in document order.'''
117
118         if isinstance( node , Node ) :
119                 yield node
120
121 # [xpath20] "the descendant-or-self axis contains the context node and
122 # the descendants of the context node"
123
124 def iterDescendantOrSelf( node ) :
125
126         ''''node' and descendants of 'node' in document order.'''
127
128         if isinstance( node , Node ) : # FIXME: Really allow Attribute ?
129                 yield node
130                 if isinstance( node , ( Document , Element ) ) :
131                         current = node
132                         if current.children :
133                                 current = current.children[ 0 ]
134                                 while current is not node :
135                                         yield current
136                                         if isinstance( current , Element ) :
137                                                 if current.children :
138                                                         current = current.children[ 0 ]
139                                                         continue
140                                         while current is not node and current.next is None :
141                                                 current = current.parent
142                                         if current is not node :
143                                                 current = current.next                                          
144
145 def iterDescendantOrSelfFull( node ) :
146
147         ''''node' and descendants of 'node' in document order.'''
148
149         if isinstance( node , ( Document , Element , Text ) ) :
150                 yield node
151                 current = node
152                 if current.children :
153                         current = current.children[ 0 ]
154                         while current is not node :
155                                 yield current
156                                 if isinstance( current , Element ) :
157                                         for attribute in current.attributes :
158                                                 yield attribute
159                                         if current.children :
160                                                 current = current.children[ 0 ]
161                                                 continue
162                                 while current is not node and current.next is None :
163                                         current = current.parent
164                                 if current is not node :
165                                         current = current.next
166
167 # [xpath20] "the following-sibling axis contains the context node's
168 # following siblings, those children of the context node's parent that
169 # occur after the context node in document order; if the context node
170 # is an attribute or namespace node, the following-sibling axis is
171 # empty"
172
173 def iterFollowingSibling( node ) :
174
175         '''Following siblings of node 'node' in document order.'''
176
177         if isinstance( node , Node ) and not isinstance( node , Attribute ) :
178                 parent = node.parent
179                 if parent is not None :
180                         found = False
181                         for sibling in parent.children :
182                                 if found :
183                                         yield sibling
184                                 elif sibling is node :
185                                         found = True
186
187 # [xpath20] "the following axis contains all nodes that are
188 # descendants of the root of the tree in which the context node is
189 # found, are not descendants of the context node, and occur after the
190 # context node in document order"
191
192 def iterFollowing( node ) :
193
194         '''Following nodes of node 'node' in document order.'''
195
196         if isinstance( node , Node ) and not isinstance( node , Attribute ) :
197                 current = node
198                 while current is not None :
199                         for sibling in iterFollowingSibling( current ) :
200                                 for descendant in iterDescendantOrSelf( sibling ) :
201                                         yield descendant
202                         current = current.parent
203
204 #----------------------------------------------------------------------------
205 #
206 # Reverse Axis
207 #
208 #----------------------------------------------------------------------------
209
210 def iterChildReversed( node ) :
211
212         '''Children of the node 'node' in reverse document order.'''
213
214         if isinstance( node , ( Document , Element ) ) :
215                 for child in reversed( node.children ) :
216                         yield child
217
218 def iterDescendantReversed( node ) :
219
220         '''Descendants of the node 'node' in reverse document order.'''
221
222         if isinstance( node , ( Document , Element ) ) :
223                 for node in iterChildReversed( node ) :
224                         for descendant in iterDescendantReversed( node ) :
225                                 yield descendant
226                         yield node
227
228 def iterDescendantOrSelfReversed( node ) :
229
230         ''''node' and descendants of 'node' in reverse document order.'''
231
232         if isinstance( node , ( Document , Element ) ):
233                 for descendant in iterDescendantReversed( node ) :
234                         yield descendant
235                 yield node
236
237 # [xpath20] "the parent axis contains the sequence returned by the
238 # dm:parent accessor in [XQuery 1.0 and XPath 2.0 Data Model], which
239 # returns the parent of the context node, or an empty sequence if the
240 # context node has no parent. Note: An attribute node may have an
241 # element node as its parent, even though the attribute node is not a
242 # child of the element node."
243
244 def iterParent( node ) :
245
246         '''Parent of node 'node'.'''
247
248         if isinstance( node , Node ) :
249                 parent = node.parent
250                 if parent is not None :
251                         yield parent
252
253 # [xpath20] "the ancestor axis is defined as the transitive closure of
254 # the parent axis; it contains the ancestors of the context node (the
255 # parent, the parent of the parent, and so on) Note: The ancestor axis
256 # includes the root node of the tree in which the context node is
257 # found, unless the context node is the root node."
258
259 def iterAncestor( node ) :
260
261         '''Ancestors of node 'node' up to root node in reverse document order.'''
262
263         if isinstance( node , Node ) :
264                 while 1 :
265                         node = node.parent
266                         if node is None :
267                                 break
268                         yield node
269
270 # [xpath20] "the preceding-sibling axis contains the context node's
271 # preceding siblings, those children of the context node's parent that
272 # occur before the context node in document order; if the context node
273 # is an attribute or namespace node, the preceding-sibling axis is
274 # empty"
275
276 def iterPrecedingSibling( node ) :
277
278         '''Preceding siblings of node 'node' in reverse document order.'''
279
280         if isinstance( node , Node ) and not isinstance( node , Attribute ) :
281                 parent = node.parent
282                 if parent is not None :
283                         found = False
284                         for sibling in reversed( parent.children ) :
285                                 if found :
286                                         yield sibling
287                                 elif sibling is node :
288                                         found = True
289
290 # [xpath20] "the preceding axis contains all nodes that are
291 # descendants of the root of the tree in which the context node is
292 # found, are not ancestors of the context node, and occur before the
293 # context node in document order"
294
295 def iterPreceding( node ) :
296
297         '''Predecing nodes of node 'node' in reverse document order.'''
298
299         if isinstance( node , Node ) and not isinstance( node , Attribute ) :
300                 while node is not None :
301                         for sibling in iterPrecedingSibling( node ) :
302                                 for descendant in iterDescendantOrSelfReversed( sibling ) :
303                                         yield descendant
304                         node = node.parent
305
306 # [xpath20] "the ancestor-or-self axis contains the context node and
307 # the ancestors of the context node; thus, the ancestor-or-self axis
308 # will always include the root node"
309
310 def iterAncestorOrSelf( node ) :
311
312         ''''node' and ancestors of node 'node' in reverse document order.'''
313
314         if isinstance( node , Node ) :
315                 yield node
316                 for ancestor in iterAncestor( node ) :
317                         yield ancestor
318
319 # Local Variables:
320 # tab-width: 4
321 # python-indent: 4
322 # End: