1
2
3 '''
4 lxml mate.
5 '''
6
7
8 __ver_major__ = 0
9 __ver_minor__ = 5
10 __ver_patch__ = 2
11 __ver_sub__ = ""
12 __version__ = "%d.%d.%d%s" % (__ver_major__,__ver_minor__,__ver_patch__,__ver_sub__)
13
14
15
16 from lxml import etree, objectify
17 import types
22 u'''Proxy class for objectify.ObjectifiedElement instance which can be created by objectify.Element() or SubElement() or XML() or fromstring().
23
24 main purpose is to intercept AttributeException when access a non-existent tag.
25
26 How to access xml tag
27 ---------------------
28 .
29 such as root.element.name
30
31 []
32 such as root['element']['name']
33
34 hybrid
35 such as root['element'].name
36
37 note
38 The tag can only be accessed by [] when it is one of the reserved keywords.
39 Tag not in the xml tree can be accessed directly. A new tag will be created. No exceptions will be raised.
40
41
42 How to access xml tag's attributes
43 ----------------------------------
44 []
45 .attrib['style'], an exception will be raised when style dos'nt exist.
46 .attrib['style'] = 'big'
47 .get/set
48 .attrib.get( 'style', None )
49 .attrib.set( 'style', 'big' )
50
51
52 How to access class attributes and methods
53 ------------------------------------------
54 .
55 attributes are reserved keywords and they can only be accessed by this way, for example
56 .pyval
57 .text
58 .insert etc.
59 or they are considered xml tags rather than attributes.
60
61
62 Reserved keywords
63 -----------------
64 The following keywords are used as methods or attributes' names.
65
66 **pyval** : returns the python value carried by leaf tag. read-only.
67
68 **text** : returns leaf tag's text content. read-only.
69
70 **obj** : returns ObjectifiedElement object referenced by this class instance. read-only.
71
72 **tag** : returns tag names. can be modified by . way such as \*.tag='newTagName'. readable and writable.
73
74 **attrib** : returns tag attributes dict. readable and writeable.
75
76 **parent** : returns parent node. read-only.
77
78 **children** : returns all children's list. read-only.
79
80 **len** : returns the number of children.
81
82 **insert** : insert a child node at the specified position.
83
84 **remove** : remove a child node at the specified position.
85
86 **index** : returns the position of the specified object.
87
88 **swap** : swap two nodes' position.
89
90 Examples
91 --------
92
93 create a brand new xml:
94
95 >>> p = ObjectifiedElmentProxy( rootag='Person' )
96 >>> p.name = 'peter'
97 >>> p.age = 13
98 >>> print( p )
99 <Person>
100 <name>peter</name>
101 <age>13</age>
102 </Person>
103
104 create from xml string:
105
106 >>> p = ObjectifiedElementProxy( xmlStr="<Person><name>peter</name><age>13</age></Person>" )
107 >>> print( p )
108 <Person>
109 <name>peter</name>
110 <age>13</age>
111 </Person>
112
113 multiple levels examples:
114
115 >>> r = ObjectifiedElementProxy()
116 >>> r.person.name = 'jack'
117 >>> r.person.age = 10
118 >>> print( r )
119 <root>
120 <person>
121 <name>jack</name>
122 <age>10</age>
123 </person>
124 </root>
125
126 to insert child like '<person><name>peter</name><age>13</age></person>':
127
128 >>> r.insert( 'person' )('name','peter')('age',13)
129
130 >>> p = r('person').person[-1]
131 >>> p.name = 'david'
132 >>> p.age = 16
133 >>> print( r )
134 <root>
135 <person>
136 <name>jack</name>
137 <age>10</age>
138 </person>
139 <person>
140 <name>peter</name>
141 <age>13</age>
142 </person>
143 <person>
144 <name>david</name>
145 <age>16</age>
146 </person>
147 </root>
148
149 >>> print( r.station[1].name.pyval )
150 peter
151
152
153 Notes
154 -----
155 xml attrib names and tag names are case insensitive.
156
157 Nodes with text attribute are called leaf nodes. Theoretically, leaf nodes should not have children, but not required.
158
159 '''
160
161 - def __init__( self, objectifiedElement=None, xmlFileName=None, xmlStr=None, rootag='root', attrib=None, nsmap=None, **kwargs ):
162 u'''
163
164 initialize from ObjectifiedElement or xml file or xml string or create a brand new.
165
166 Arguments
167 ---------
168 objectifiedElement : ObjectifiedElement, optional
169 an ObjectifiedElement object.
170
171 xmlFileName : str, optional
172 xml's filename.
173
174 xmlStr : str, optional
175 xml's content.
176
177 rootag : str, optional
178 create ObjectifiedElement instance which root tag's name is rootag.
179
180 attrib, nsmap, kwargs : optional
181 refer to objectify.Element()
182
183 '''
184
185 self._____o____ = None
186
187 if objectifiedElement is not None:
188 self._____o____ = objectifiedElement
189
190 elif xmlFileName:
191 self._____o____ = objectify.XML( xmlFileName )
192
193 elif xmlStr:
194 self._____o____ = objectify.fromstring( xmlStr )
195
196 else:
197 self._____o____ = objectify.Element( rootag, attrib=attrib, nsmap=nsmap, **kwargs )
198
199
200 - def __call__( self, tag, pyval=None, attrib=None, nsmap=None, **kwargs ):
201 u'''Insert a new child node.
202
203 insert a new child node to the end.
204
205 Arguments
206 ---------
207 e : str
208 the new tag to be inserted.
209 pyval : legal python data type
210 tag's python value.
211 attrib,nsmap,kwargs : optional
212 attribs for the new tag. see also objectify.Element() or SubElement().
213
214 Returns
215 -------
216 ObjectifiedElementProxy instance
217
218 See Also
219 --------
220 insert
221
222 note the difference between the two methods' return values.
223
224 Examples
225 --------
226 >>> p=ObjectifiedElementProxy( rootag='Person' )
227 >>> p( 'name', pyval='jack' )('age', pyval=13 )
228 >>> print( p )
229 <Person>
230 <name py:pytype="str">jack</name>
231 <age py:pytype="int">13</age>
232 </Person>
233 '''
234
235 self.insert( tag, None, attrib, nsmap, **kwargs )
236
237 self [ tag ][-1] = pyval
238
239 return self
240
241
243
244 if name == '_____o____':
245 return object.__getattribute__(name)
246
247 if hasattr( self._____o____, name ):
248 e = getattr( self._____o____, name )
249
250 if name in ( 'tag','pyval','text', 'attrib' ) or isinstance( e, ( types.FunctionType, types.BuiltinFunctionType ) ):
251 return e
252
253 else:
254
255 e = objectify.SubElement( self._____o____, name )
256
257
258 return ObjectifiedElementProxy( e )
259
260
262
263 if name == '_____o____':
264 object.__setattr__( self, name, value )
265 return
266
267 setattr( self._____o____, name, value )
268
269
272
273
275 u'''children's number'''
276
277 return len( self.children )
278
279
281 if isinstance( name, int ):
282 return ObjectifiedElementProxy( self._____o____[name] )
283
284 if isinstance( name, slice ):
285 return [ ObjectifiedElementProxy( o ) for o in self._____o____[name] ]
286
287 if isinstance( name, str ):
288 if name == '_____o____':
289 return object.__getattribute__( name )
290
291 o = self._____o____
292 try:
293 e = o.__getitem__( name )
294 except:
295 e = objectify.SubElement( self._____o____, name )
296
297 return ObjectifiedElementProxy( e )
298
299 raise Exception
300
301
303 if e == '_____o____':
304 object.__setitem__( self, e, v )
305 return
306
307 self._____o____[e] = v
308
309
315
316
317 - def insert( self, e, i=None, attrib=None, nsmap=None, **kwargs ):
318 u'''Insert a new child node.
319
320 insert a new child node at the specified position.
321
322 Arguments
323 ---------
324 e : str
325 the new tag to be inserted.
326 i : int, optional
327 if i is integer : position of the new tag. else append to the end.
328 attrib,nsmap,kwargs : optional
329 attribs for the new tag. see also objectify.Element() or SubElement().
330
331 '''
332
333 v = objectify.SubElement( self._____o____, e, attrib=attrib, nsmap=nsmap, **kwargs )
334 s = ObjectifiedElementProxy( v )
335
336 if i is not None:
337 self._____o____.insert( i, v )
338
339 return s
340
341
342 - def swap( self, i, j ):
343 u'''swap two child nodes' position.
344
345 Arguments
346 ---------
347 i,j : int
348 position of the child nodes to be swapped.
349 '''
350
351 self._____o____[i] = self._____o____[j]
352
353
355 u'''remove the child node.
356
357 Arguments
358 ---------
359 i : int or ObjectifiedElement or ObjectifiedElementProxy or list
360 position of the child node or Element which will be removed.
361
362 '''
363
364 if isinstance( i, list ):
365 for k in i:
366 self.remove( k )
367
368 elif isinstance( i, int ):
369 return self.obj.remove( self.children[i].obj )
370
371 elif isinstance( i, objectify.ObjectifiedElement ):
372 return self.obj.remove( i )
373
374 elif isinstance( i, ObjectifiedElementProxy ):
375 return self.obj.remove( i.obj )
376
377
379 u'''return the position of o.
380
381 Arguments
382 ---------
383 o : ObjectifiedElementProxy
384 the ObjectifiedElementProxy instance to be positioned.
385
386 Returns
387 -------
388 int
389 '''
390
391 return self._____o____.index( o.obj )
392
393
394 - def xpath( self, path ):
395 u'''find elements list in accordance with path.
396
397 Arguments
398 ---------
399 path : str
400 please refer to lxml.objectify.ObjectifiedElement.xpath.
401
402 Returns
403 -------
404 list
405 a list of ObjectifiedElementProxy instance.
406
407
408 Xpath grammer review
409 --------------------
410
411 ========== ===========
412 expression description
413 ========== ===========
414 nodename to select all children of the node name
415 / select from root node.
416 // select from current node
417 . select the current code.
418 .. select the parent node of the current node.
419 @ select attrib.
420 [] condition
421 text() tag text
422 * arbitrary node
423 ========== ============
424 '''
425
426 return [ ObjectifiedElementProxy(k) for k in self._____o____.xpath( path ) ]
427
428
429 @property
432
433
434 @property
437
438
439 @property
449
450
451 @property
453 return self._____o____
454
455
456 @property
458 if hasattr( self._____o____, 'pyval' ):
459 if isinstance( self._____o____, objectify.StringElement ):
460 return self._____o____.pyval.strip()
461
462 return self._____o____.pyval
463
464
467
468
470 u'''To determine whether a null node.
471
472 no text \ no attribs \ no children.
473 '''
474
475 o = self._____o____
476
477 if o.text and o.text.strip():
478 return False
479
480 n = 0
481 for k in o.attrib:
482 if k[0] != '{':
483 n += 1
484
485 if n > 0:
486 return False
487
488 n = 0
489 for c in self.children:
490 if not c.is_empty():
491 n += 1
492
493 if n > 0:
494 return False
495
496 return True
497
498
508
509
510 - def tostring( self, encoding='utf-8', xml_declaration=True, standalone=None, with_comments=True,
511 pytype=False, xsi=True, xsi_nil=True, cleanup_namespaces=True, doctype=None,
512 with_tail=True, exclusive=False, inclusive_ns_prefixes=None ):
513
514
515
516 objectify.deannotate( self._____o____, pytype=pytype, xsi=xsi, xsi_nil=xsi_nil, cleanup_namespaces=cleanup_namespaces )
517
518 s = etree.tostring( self._____o____, encoding=encoding, pretty_print= True,
519 xml_declaration=xml_declaration, with_tail=with_tail,
520 standalone=standalone, doctype=doctype,
521 exclusive=exclusive, with_comments=with_comments,
522 inclusive_ns_prefixes=inclusive_ns_prefixes )
523
524 return s
525
526
528
529 s = self.tostring( pytype=True, xml_declaration=False ).decode()
530 return s
531
532
533 - def dump( self, xmlFile, encoding='utf-8' ):
534 '''save xml to file.
535
536 Arguments
537 ---------
538 xmlFile : str
539 xml's filename.
540
541 '''
542
543 f = open( xmlFile, 'w' )
544 s = self.tostring( encoding=encoding ).decode()
545 f.write( s )
546 f.close()
547
548
549
550 if __name__ == '__main__':
551 r = ObjectifiedElementProxy()
552 r.person.name = 'jack'
553 r.person.age = 10
554 r.insert( 'person' )('name','peter')('age',13)
555 p = r('person').person[-1]
556 p.name = 'david'
557 p.age = 16
558 print( r )
559
560 print( r.tostring().decode() )
561
562 print( r.person[1].name.pyval )
563
564 r.dump( 'x.xml' )
565