objectify.txt 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409
  1. ==============
  2. lxml.objectify
  3. ==============
  4. :Authors:
  5. Stefan Behnel, Holger Joukl
  6. lxml supports an alternative API similar to the Amara_ bindery or
  7. gnosis.xml.objectify_ through a `custom Element implementation`_. The main idea
  8. is to hide the usage of XML behind normal Python objects, sometimes referred
  9. to as data-binding. It allows you to use XML as if you were dealing with a
  10. normal Python object hierarchy.
  11. Accessing the children of an XML element deploys object attribute access. If
  12. there are multiple children with the same name, slicing and indexing can be
  13. used. Python data types are extracted from XML content automatically and made
  14. available to the normal Python operators.
  15. .. contents::
  16. ..
  17. 1 The lxml.objectify API
  18. 1.1 Element access through object attributes
  19. 1.2 Creating objectify trees
  20. 1.3 Tree generation with the E-factory
  21. 1.4 Namespace handling
  22. 2 Asserting a Schema
  23. 3 ObjectPath
  24. 4 Python data types
  25. 4.1 Recursive tree dump
  26. 4.2 Recursive string representation of elements
  27. 5 How data types are matched
  28. 5.1 Type annotations
  29. 5.2 XML Schema datatype annotation
  30. 5.3 The DataElement factory
  31. 5.4 Defining additional data classes
  32. 5.5 Advanced element class lookup
  33. 6 What is different from lxml.etree?
  34. .. _Amara: http://uche.ogbuji.net/tech/4suite/amara/
  35. .. _gnosis.xml.objectify: http://gnosis.cx/download/
  36. .. _`benchmark page`: performance.html#lxml-objectify
  37. .. _`custom Element implementation`: element_classes.html
  38. To set up and use ``objectify``, you need both the ``lxml.etree``
  39. module and ``lxml.objectify``:
  40. .. sourcecode:: pycon
  41. >>> from lxml import etree
  42. >>> from lxml import objectify
  43. The objectify API is very different from the ElementTree API. If it
  44. is used, it should not be mixed with other element implementations
  45. (such as trees parsed with ``lxml.etree``), to avoid non-obvious
  46. behaviour.
  47. The `benchmark page`_ has some hints on performance optimisation of
  48. code using lxml.objectify.
  49. To make the doctests in this document look a little nicer, we also use
  50. this:
  51. .. sourcecode:: pycon
  52. >>> import lxml.usedoctest
  53. Imported from within a doctest, this relieves us from caring about the exact
  54. formatting of XML output.
  55. ..
  56. >>> try: from StringIO import StringIO
  57. ... except ImportError:
  58. ... from io import BytesIO # Python 3
  59. ... def StringIO(s):
  60. ... if isinstance(s, str): s = s.encode('UTF-8')
  61. ... return BytesIO(s)
  62. ..
  63. >>> import sys
  64. >>> from lxml import etree as _etree
  65. >>> if sys.version_info[0] >= 3:
  66. ... class etree_mock(object):
  67. ... def __getattr__(self, name): return getattr(_etree, name)
  68. ... def tostring(self, *args, **kwargs):
  69. ... s = _etree.tostring(*args, **kwargs)
  70. ... if isinstance(s, bytes) and bytes([10]) in s: s = s.decode("utf-8") # CR
  71. ... if s[-1] == '\n': s = s[:-1]
  72. ... return s
  73. ... else:
  74. ... class etree_mock(object):
  75. ... def __getattr__(self, name): return getattr(_etree, name)
  76. ... def tostring(self, *args, **kwargs):
  77. ... s = _etree.tostring(*args, **kwargs)
  78. ... if s[-1] == '\n': s = s[:-1]
  79. ... return s
  80. >>> etree = etree_mock()
  81. The lxml.objectify API
  82. ======================
  83. In ``lxml.objectify``, element trees provide an API that models the behaviour
  84. of normal Python object trees as closely as possible.
  85. Element access through object attributes
  86. ----------------------------------------
  87. The main idea behind the ``objectify`` API is to hide XML element access
  88. behind the usual object attribute access pattern. Asking an element for an
  89. attribute will return the sequence of children with corresponding tag names:
  90. .. sourcecode:: pycon
  91. >>> root = objectify.Element("root")
  92. >>> b = objectify.SubElement(root, "b")
  93. >>> print(root.b[0].tag)
  94. b
  95. >>> root.index(root.b[0])
  96. 0
  97. >>> b = objectify.SubElement(root, "b")
  98. >>> print(root.b[0].tag)
  99. b
  100. >>> print(root.b[1].tag)
  101. b
  102. >>> root.index(root.b[1])
  103. 1
  104. For convenience, you can omit the index '0' to access the first child:
  105. .. sourcecode:: pycon
  106. >>> print(root.b.tag)
  107. b
  108. >>> root.index(root.b)
  109. 0
  110. >>> del root.b
  111. Iteration and slicing also obey the requested tag:
  112. .. sourcecode:: pycon
  113. >>> x1 = objectify.SubElement(root, "x")
  114. >>> x2 = objectify.SubElement(root, "x")
  115. >>> x3 = objectify.SubElement(root, "x")
  116. >>> [ el.tag for el in root.x ]
  117. ['x', 'x', 'x']
  118. >>> [ el.tag for el in root.x[1:3] ]
  119. ['x', 'x']
  120. >>> [ el.tag for el in root.x[-1:] ]
  121. ['x']
  122. >>> del root.x[1:2]
  123. >>> [ el.tag for el in root.x ]
  124. ['x', 'x']
  125. If you want to iterate over all children or need to provide a specific
  126. namespace for the tag, use the ``iterchildren()`` method. Like the other
  127. methods for iteration, it supports an optional tag keyword argument:
  128. .. sourcecode:: pycon
  129. >>> [ el.tag for el in root.iterchildren() ]
  130. ['b', 'x', 'x']
  131. >>> [ el.tag for el in root.iterchildren(tag='b') ]
  132. ['b']
  133. >>> [ el.tag for el in root.b ]
  134. ['b']
  135. XML attributes are accessed as in the normal ElementTree API:
  136. .. sourcecode:: pycon
  137. >>> c = objectify.SubElement(root, "c", myattr="someval")
  138. >>> print(root.c.get("myattr"))
  139. someval
  140. >>> root.c.set("c", "oh-oh")
  141. >>> print(root.c.get("c"))
  142. oh-oh
  143. In addition to the normal ElementTree API for appending elements to trees,
  144. subtrees can also be added by assigning them to object attributes. In this
  145. case, the subtree is automatically deep copied and the tag name of its root is
  146. updated to match the attribute name:
  147. .. sourcecode:: pycon
  148. >>> el = objectify.Element("yet_another_child")
  149. >>> root.new_child = el
  150. >>> print(root.new_child.tag)
  151. new_child
  152. >>> print(el.tag)
  153. yet_another_child
  154. >>> root.y = [ objectify.Element("y"), objectify.Element("y") ]
  155. >>> [ el.tag for el in root.y ]
  156. ['y', 'y']
  157. The latter is a short form for operations on the full slice:
  158. .. sourcecode:: pycon
  159. >>> root.y[:] = [ objectify.Element("y") ]
  160. >>> [ el.tag for el in root.y ]
  161. ['y']
  162. You can also replace children that way:
  163. .. sourcecode:: pycon
  164. >>> child1 = objectify.SubElement(root, "child")
  165. >>> child2 = objectify.SubElement(root, "child")
  166. >>> child3 = objectify.SubElement(root, "child")
  167. >>> el = objectify.Element("new_child")
  168. >>> subel = objectify.SubElement(el, "sub")
  169. >>> root.child = el
  170. >>> print(root.child.sub.tag)
  171. sub
  172. >>> root.child[2] = el
  173. >>> print(root.child[2].sub.tag)
  174. sub
  175. Note that special care must be taken when changing the tag name of an element:
  176. .. sourcecode:: pycon
  177. >>> print(root.b.tag)
  178. b
  179. >>> root.b.tag = "notB"
  180. >>> root.b
  181. Traceback (most recent call last):
  182. ...
  183. AttributeError: no such child: b
  184. >>> print(root.notB.tag)
  185. notB
  186. Creating objectify trees
  187. ------------------------
  188. As with ``lxml.etree``, you can either create an ``objectify`` tree by
  189. parsing an XML document or by building one from scratch. To parse a
  190. document, just use the ``parse()`` or ``fromstring()`` functions of
  191. the module:
  192. .. sourcecode:: pycon
  193. >>> fileobject = StringIO('<test/>')
  194. >>> tree = objectify.parse(fileobject)
  195. >>> print(isinstance(tree.getroot(), objectify.ObjectifiedElement))
  196. True
  197. >>> root = objectify.fromstring('<test/>')
  198. >>> print(isinstance(root, objectify.ObjectifiedElement))
  199. True
  200. To build a new tree in memory, ``objectify`` replicates the standard
  201. factory function ``Element()`` from ``lxml.etree``:
  202. .. sourcecode:: pycon
  203. >>> obj_el = objectify.Element("new")
  204. >>> print(isinstance(obj_el, objectify.ObjectifiedElement))
  205. True
  206. After creating such an Element, you can use the `usual API`_ of
  207. lxml.etree to add SubElements to the tree:
  208. .. sourcecode:: pycon
  209. >>> child = objectify.SubElement(obj_el, "newchild", attr="value")
  210. .. _`usual API`: tutorial.html#the-element-class
  211. New subelements will automatically inherit the objectify behaviour
  212. from their tree. However, all independent elements that you create
  213. through the ``Element()`` factory of lxml.etree (instead of objectify)
  214. will not support the ``objectify`` API by themselves:
  215. .. sourcecode:: pycon
  216. >>> subel = objectify.SubElement(obj_el, "sub")
  217. >>> print(isinstance(subel, objectify.ObjectifiedElement))
  218. True
  219. >>> independent_el = etree.Element("new")
  220. >>> print(isinstance(independent_el, objectify.ObjectifiedElement))
  221. False
  222. Tree generation with the E-factory
  223. ----------------------------------
  224. To simplify the generation of trees even further, you can use the E-factory:
  225. .. sourcecode:: pycon
  226. >>> E = objectify.E
  227. >>> root = E.root(
  228. ... E.a(5),
  229. ... E.b(6.1),
  230. ... E.c(True),
  231. ... E.d("how", tell="me")
  232. ... )
  233. >>> print(etree.tostring(root, pretty_print=True))
  234. <root xmlns:py="http://codespeak.net/lxml/objectify/pytype">
  235. <a py:pytype="int">5</a>
  236. <b py:pytype="float">6.1</b>
  237. <c py:pytype="bool">true</c>
  238. <d py:pytype="str" tell="me">how</d>
  239. </root>
  240. This allows you to write up a specific language in tags:
  241. .. sourcecode:: pycon
  242. >>> ROOT = objectify.E.root
  243. >>> TITLE = objectify.E.title
  244. >>> HOWMANY = getattr(objectify.E, "how-many")
  245. >>> root = ROOT(
  246. ... TITLE("The title"),
  247. ... HOWMANY(5)
  248. ... )
  249. >>> print(etree.tostring(root, pretty_print=True))
  250. <root xmlns:py="http://codespeak.net/lxml/objectify/pytype">
  251. <title py:pytype="str">The title</title>
  252. <how-many py:pytype="int">5</how-many>
  253. </root>
  254. ``objectify.E`` is an instance of ``objectify.ElementMaker``. By default, it
  255. creates pytype annotated Elements without a namespace. You can switch off the
  256. pytype annotation by passing False to the ``annotate`` keyword argument of the
  257. constructor. You can also pass a default namespace and an ``nsmap``:
  258. .. sourcecode:: pycon
  259. >>> myE = objectify.ElementMaker(annotate=False,
  260. ... namespace="http://my/ns", nsmap={None : "http://my/ns"})
  261. >>> root = myE.root( myE.someint(2) )
  262. >>> print(etree.tostring(root, pretty_print=True))
  263. <root xmlns="http://my/ns">
  264. <someint>2</someint>
  265. </root>
  266. Namespace handling
  267. ------------------
  268. During tag lookups, namespaces are handled mostly behind the scenes.
  269. If you access a child of an Element without specifying a namespace,
  270. the lookup will use the namespace of the parent:
  271. .. sourcecode:: pycon
  272. >>> root = objectify.Element("{http://ns/}root")
  273. >>> b = objectify.SubElement(root, "{http://ns/}b")
  274. >>> c = objectify.SubElement(root, "{http://other/}c")
  275. >>> print(root.b.tag)
  276. {http://ns/}b
  277. Note that the ``SubElement()`` factory of ``lxml.etree`` does not
  278. inherit any namespaces when creating a new subelement. Element
  279. creation must be explicit about the namespace, and is simplified
  280. through the E-factory as described above.
  281. Lookups, however, inherit namespaces implicitly:
  282. .. sourcecode:: pycon
  283. >>> print(root.b.tag)
  284. {http://ns/}b
  285. >>> print(root.c)
  286. Traceback (most recent call last):
  287. ...
  288. AttributeError: no such child: {http://ns/}c
  289. To access an element in a different namespace than its parent, you can
  290. use ``getattr()``:
  291. .. sourcecode:: pycon
  292. >>> c = getattr(root, "{http://other/}c")
  293. >>> print(c.tag)
  294. {http://other/}c
  295. For convenience, there is also a quick way through item access:
  296. .. sourcecode:: pycon
  297. >>> c = root["{http://other/}c"]
  298. >>> print(c.tag)
  299. {http://other/}c
  300. The same approach must be used to access children with tag names that are not
  301. valid Python identifiers:
  302. .. sourcecode:: pycon
  303. >>> el = objectify.SubElement(root, "{http://ns/}tag-name")
  304. >>> print(root["tag-name"].tag)
  305. {http://ns/}tag-name
  306. >>> new_el = objectify.Element("{http://ns/}new-element")
  307. >>> el = objectify.SubElement(new_el, "{http://ns/}child")
  308. >>> el = objectify.SubElement(new_el, "{http://ns/}child")
  309. >>> el = objectify.SubElement(new_el, "{http://ns/}child")
  310. >>> root["tag-name"] = [ new_el, new_el ]
  311. >>> print(len(root["tag-name"]))
  312. 2
  313. >>> print(root["tag-name"].tag)
  314. {http://ns/}tag-name
  315. >>> print(len(root["tag-name"].child))
  316. 3
  317. >>> print(root["tag-name"].child.tag)
  318. {http://ns/}child
  319. >>> print(root["tag-name"][1].child.tag)
  320. {http://ns/}child
  321. or for names that have a special meaning in lxml.objectify:
  322. .. sourcecode:: pycon
  323. >>> root = objectify.XML("<root><text>TEXT</text></root>")
  324. >>> print(root.text.text)
  325. Traceback (most recent call last):
  326. ...
  327. AttributeError: 'NoneType' object has no attribute 'text'
  328. >>> print(root["text"].text)
  329. TEXT
  330. Asserting a Schema
  331. ==================
  332. When dealing with XML documents from different sources, you will often
  333. require them to follow a common schema. In lxml.objectify, this
  334. directly translates to enforcing a specific object tree, i.e. expected
  335. object attributes are ensured to be there and to have the expected
  336. type. This can easily be achieved through XML Schema validation at
  337. parse time. Also see the `documentation on validation`_ on this
  338. topic.
  339. .. _`documentation on validation`: validation.html
  340. First of all, we need a parser that knows our schema, so let's say we
  341. parse the schema from a file-like object (or file or filename):
  342. .. sourcecode:: pycon
  343. >>> f = StringIO('''\
  344. ... <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  345. ... <xsd:element name="a" type="AType"/>
  346. ... <xsd:complexType name="AType">
  347. ... <xsd:sequence>
  348. ... <xsd:element name="b" type="xsd:string" />
  349. ... </xsd:sequence>
  350. ... </xsd:complexType>
  351. ... </xsd:schema>
  352. ... ''')
  353. >>> schema = etree.XMLSchema(file=f)
  354. When creating the validating parser, we must make sure it `returns
  355. objectify trees`_. This is best done with the ``makeparser()``
  356. function:
  357. .. sourcecode:: pycon
  358. >>> parser = objectify.makeparser(schema = schema)
  359. .. _`returns objectify trees`: #advance-element-class-lookup
  360. Now we can use it to parse a valid document:
  361. .. sourcecode:: pycon
  362. >>> xml = "<a><b>test</b></a>"
  363. >>> a = objectify.fromstring(xml, parser)
  364. >>> print(a.b)
  365. test
  366. Or an invalid document:
  367. .. sourcecode:: pycon
  368. >>> xml = "<a><b>test</b><c/></a>"
  369. >>> a = objectify.fromstring(xml, parser)
  370. Traceback (most recent call last):
  371. lxml.etree.XMLSyntaxError: Element 'c': This element is not expected.
  372. Note that the same works for parse-time DTD validation, except that
  373. DTDs do not support any data types by design.
  374. ObjectPath
  375. ==========
  376. For both convenience and speed, objectify supports its own path language,
  377. represented by the ``ObjectPath`` class:
  378. .. sourcecode:: pycon
  379. >>> root = objectify.Element("{http://ns/}root")
  380. >>> b1 = objectify.SubElement(root, "{http://ns/}b")
  381. >>> c = objectify.SubElement(b1, "{http://ns/}c")
  382. >>> b2 = objectify.SubElement(root, "{http://ns/}b")
  383. >>> d = objectify.SubElement(root, "{http://other/}d")
  384. >>> path = objectify.ObjectPath("root.b.c")
  385. >>> print(path)
  386. root.b.c
  387. >>> path.hasattr(root)
  388. True
  389. >>> print(path.find(root).tag)
  390. {http://ns/}c
  391. >>> find = objectify.ObjectPath("root.b.c")
  392. >>> print(find(root).tag)
  393. {http://ns/}c
  394. >>> find = objectify.ObjectPath("root.{http://other/}d")
  395. >>> print(find(root).tag)
  396. {http://other/}d
  397. >>> find = objectify.ObjectPath("root.{not}there")
  398. >>> print(find(root).tag)
  399. Traceback (most recent call last):
  400. ...
  401. AttributeError: no such child: {not}there
  402. >>> find = objectify.ObjectPath("{not}there")
  403. >>> print(find(root).tag)
  404. Traceback (most recent call last):
  405. ...
  406. ValueError: root element does not match: need {not}there, got {http://ns/}root
  407. >>> find = objectify.ObjectPath("root.b[1]")
  408. >>> print(find(root).tag)
  409. {http://ns/}b
  410. >>> find = objectify.ObjectPath("root.{http://ns/}b[1]")
  411. >>> print(find(root).tag)
  412. {http://ns/}b
  413. Apart from strings, ObjectPath also accepts lists of path segments:
  414. .. sourcecode:: pycon
  415. >>> find = objectify.ObjectPath(['root', 'b', 'c'])
  416. >>> print(find(root).tag)
  417. {http://ns/}c
  418. >>> find = objectify.ObjectPath(['root', '{http://ns/}b[1]'])
  419. >>> print(find(root).tag)
  420. {http://ns/}b
  421. You can also use relative paths starting with a '.' to ignore the actual root
  422. element and only inherit its namespace:
  423. .. sourcecode:: pycon
  424. >>> find = objectify.ObjectPath(".b[1]")
  425. >>> print(find(root).tag)
  426. {http://ns/}b
  427. >>> find = objectify.ObjectPath(['', 'b[1]'])
  428. >>> print(find(root).tag)
  429. {http://ns/}b
  430. >>> find = objectify.ObjectPath(".unknown[1]")
  431. >>> print(find(root).tag)
  432. Traceback (most recent call last):
  433. ...
  434. AttributeError: no such child: {http://ns/}unknown
  435. >>> find = objectify.ObjectPath(".{http://other/}unknown[1]")
  436. >>> print(find(root).tag)
  437. Traceback (most recent call last):
  438. ...
  439. AttributeError: no such child: {http://other/}unknown
  440. For convenience, a single dot represents the empty ObjectPath (identity):
  441. .. sourcecode:: pycon
  442. >>> find = objectify.ObjectPath(".")
  443. >>> print(find(root).tag)
  444. {http://ns/}root
  445. ObjectPath objects can be used to manipulate trees:
  446. .. sourcecode:: pycon
  447. >>> root = objectify.Element("{http://ns/}root")
  448. >>> path = objectify.ObjectPath(".some.child.{http://other/}unknown")
  449. >>> path.hasattr(root)
  450. False
  451. >>> path.find(root)
  452. Traceback (most recent call last):
  453. ...
  454. AttributeError: no such child: {http://ns/}some
  455. >>> path.setattr(root, "my value") # creates children as necessary
  456. >>> path.hasattr(root)
  457. True
  458. >>> print(path.find(root).text)
  459. my value
  460. >>> print(root.some.child["{http://other/}unknown"].text)
  461. my value
  462. >>> print(len( path.find(root) ))
  463. 1
  464. >>> path.addattr(root, "my new value")
  465. >>> print(len( path.find(root) ))
  466. 2
  467. >>> [ el.text for el in path.find(root) ]
  468. ['my value', 'my new value']
  469. As with attribute assignment, ``setattr()`` accepts lists:
  470. .. sourcecode:: pycon
  471. >>> path.setattr(root, ["v1", "v2", "v3"])
  472. >>> [ el.text for el in path.find(root) ]
  473. ['v1', 'v2', 'v3']
  474. Note, however, that indexing is only supported in this context if the children
  475. exist. Indexing of non existing children will not extend or create a list of
  476. such children but raise an exception:
  477. .. sourcecode:: pycon
  478. >>> path = objectify.ObjectPath(".{non}existing[1]")
  479. >>> path.setattr(root, "my value")
  480. Traceback (most recent call last):
  481. ...
  482. TypeError: creating indexed path attributes is not supported
  483. It is worth noting that ObjectPath does not depend on the ``objectify`` module
  484. or the ObjectifiedElement implementation. It can also be used in combination
  485. with Elements from the normal lxml.etree API.
  486. Python data types
  487. =================
  488. The objectify module knows about Python data types and tries its best to let
  489. element content behave like them. For example, they support the normal math
  490. operators:
  491. .. sourcecode:: pycon
  492. >>> root = objectify.fromstring(
  493. ... "<root><a>5</a><b>11</b><c>true</c><d>hoi</d></root>")
  494. >>> root.a + root.b
  495. 16
  496. >>> root.a += root.b
  497. >>> print(root.a)
  498. 16
  499. >>> root.a = 2
  500. >>> print(root.a + 2)
  501. 4
  502. >>> print(1 + root.a)
  503. 3
  504. >>> print(root.c)
  505. True
  506. >>> root.c = False
  507. >>> if not root.c:
  508. ... print("false!")
  509. false!
  510. >>> print(root.d + " test !")
  511. hoi test !
  512. >>> root.d = "%s - %s"
  513. >>> print(root.d % (1234, 12345))
  514. 1234 - 12345
  515. However, data elements continue to provide the objectify API. This means that
  516. sequence operations such as ``len()``, slicing and indexing (e.g. of strings)
  517. cannot behave as the Python types. Like all other tree elements, they show
  518. the normal slicing behaviour of objectify elements:
  519. .. sourcecode:: pycon
  520. >>> root = objectify.fromstring("<root><a>test</a><b>toast</b></root>")
  521. >>> print(root.a + ' me') # behaves like a string, right?
  522. test me
  523. >>> len(root.a) # but there's only one 'a' element!
  524. 1
  525. >>> [ a.tag for a in root.a ]
  526. ['a']
  527. >>> print(root.a[0].tag)
  528. a
  529. >>> print(root.a)
  530. test
  531. >>> [ str(a) for a in root.a[:1] ]
  532. ['test']
  533. If you need to run sequence operations on data types, you must ask the API for
  534. the *real* Python value. The string value is always available through the
  535. normal ElementTree ``.text`` attribute. Additionally, all data classes
  536. provide a ``.pyval`` attribute that returns the value as plain Python type:
  537. .. sourcecode:: pycon
  538. >>> root = objectify.fromstring("<root><a>test</a><b>5</b></root>")
  539. >>> root.a.text
  540. 'test'
  541. >>> root.a.pyval
  542. 'test'
  543. >>> root.b.text
  544. '5'
  545. >>> root.b.pyval
  546. 5
  547. Note, however, that both attributes are read-only in objectify. If you want
  548. to change values, just assign them directly to the attribute:
  549. .. sourcecode:: pycon
  550. >>> root.a.text = "25"
  551. Traceback (most recent call last):
  552. ...
  553. TypeError: attribute 'text' of 'StringElement' objects is not writable
  554. >>> root.a.pyval = 25
  555. Traceback (most recent call last):
  556. ...
  557. TypeError: attribute 'pyval' of 'StringElement' objects is not writable
  558. >>> root.a = 25
  559. >>> print(root.a)
  560. 25
  561. >>> print(root.a.pyval)
  562. 25
  563. In other words, ``objectify`` data elements behave like immutable Python
  564. types. You can replace them, but not modify them.
  565. Recursive tree dump
  566. -------------------
  567. To see the data types that are currently used, you can call the module level
  568. ``dump()`` function that returns a recursive string representation for
  569. elements:
  570. .. sourcecode:: pycon
  571. >>> root = objectify.fromstring("""
  572. ... <root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  573. ... <a attr1="foo" attr2="bar">1</a>
  574. ... <a>1.2</a>
  575. ... <b>1</b>
  576. ... <b>true</b>
  577. ... <c>what?</c>
  578. ... <d xsi:nil="true"/>
  579. ... </root>
  580. ... """)
  581. >>> print(objectify.dump(root))
  582. root = None [ObjectifiedElement]
  583. a = 1 [IntElement]
  584. * attr1 = 'foo'
  585. * attr2 = 'bar'
  586. a = 1.2 [FloatElement]
  587. b = 1 [IntElement]
  588. b = True [BoolElement]
  589. c = 'what?' [StringElement]
  590. d = None [NoneElement]
  591. * xsi:nil = 'true'
  592. You can freely switch between different types for the same child:
  593. .. sourcecode:: pycon
  594. >>> root = objectify.fromstring("<root><a>5</a></root>")
  595. >>> print(objectify.dump(root))
  596. root = None [ObjectifiedElement]
  597. a = 5 [IntElement]
  598. >>> root.a = 'nice string!'
  599. >>> print(objectify.dump(root))
  600. root = None [ObjectifiedElement]
  601. a = 'nice string!' [StringElement]
  602. * py:pytype = 'str'
  603. >>> root.a = True
  604. >>> print(objectify.dump(root))
  605. root = None [ObjectifiedElement]
  606. a = True [BoolElement]
  607. * py:pytype = 'bool'
  608. >>> root.a = [1, 2, 3]
  609. >>> print(objectify.dump(root))
  610. root = None [ObjectifiedElement]
  611. a = 1 [IntElement]
  612. * py:pytype = 'int'
  613. a = 2 [IntElement]
  614. * py:pytype = 'int'
  615. a = 3 [IntElement]
  616. * py:pytype = 'int'
  617. >>> root.a = (1, 2, 3)
  618. >>> print(objectify.dump(root))
  619. root = None [ObjectifiedElement]
  620. a = 1 [IntElement]
  621. * py:pytype = 'int'
  622. a = 2 [IntElement]
  623. * py:pytype = 'int'
  624. a = 3 [IntElement]
  625. * py:pytype = 'int'
  626. Recursive string representation of elements
  627. -------------------------------------------
  628. Normally, elements use the standard string representation for str() that is
  629. provided by lxml.etree. You can enable a pretty-print representation for
  630. objectify elements like this:
  631. .. sourcecode:: pycon
  632. >>> objectify.enable_recursive_str()
  633. >>> root = objectify.fromstring("""
  634. ... <root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  635. ... <a attr1="foo" attr2="bar">1</a>
  636. ... <a>1.2</a>
  637. ... <b>1</b>
  638. ... <b>true</b>
  639. ... <c>what?</c>
  640. ... <d xsi:nil="true"/>
  641. ... </root>
  642. ... """)
  643. >>> print(str(root))
  644. root = None [ObjectifiedElement]
  645. a = 1 [IntElement]
  646. * attr1 = 'foo'
  647. * attr2 = 'bar'
  648. a = 1.2 [FloatElement]
  649. b = 1 [IntElement]
  650. b = True [BoolElement]
  651. c = 'what?' [StringElement]
  652. d = None [NoneElement]
  653. * xsi:nil = 'true'
  654. This behaviour can be switched off in the same way:
  655. .. sourcecode:: pycon
  656. >>> objectify.enable_recursive_str(False)
  657. How data types are matched
  658. ==========================
  659. Objectify uses two different types of Elements. Structural Elements (or tree
  660. Elements) represent the object tree structure. Data Elements represent the
  661. data containers at the leafs. You can explicitly create tree Elements with
  662. the ``objectify.Element()`` factory and data Elements with the
  663. ``objectify.DataElement()`` factory.
  664. When Element objects are created, lxml.objectify must determine which
  665. implementation class to use for them. This is relatively easy for tree
  666. Elements and less so for data Elements. The algorithm is as follows:
  667. 1. If an element has children, use the default tree class.
  668. 2. If an element is defined as xsi:nil, use the NoneElement class.
  669. 3. If a "Python type hint" attribute is given, use this to determine the element
  670. class, see below.
  671. 4. If an XML Schema xsi:type hint is given, use this to determine the element
  672. class, see below.
  673. 5. Try to determine the element class from the text content type by trial and
  674. error.
  675. 6. If the element is a root node then use the default tree class.
  676. 7. Otherwise, use the default class for empty data classes.
  677. You can change the default classes for tree Elements and empty data Elements
  678. at setup time. The ``ObjectifyElementClassLookup()`` call accepts two keyword
  679. arguments, ``tree_class`` and ``empty_data_class``, that determine the Element
  680. classes used in these cases. By default, ``tree_class`` is a class called
  681. ``ObjectifiedElement`` and ``empty_data_class`` is a ``StringElement``.
  682. Type annotations
  683. ----------------
  684. The "type hint" mechanism deploys an XML attribute defined as
  685. ``lxml.objectify.PYTYPE_ATTRIBUTE``. It may contain any of the following
  686. string values: int, long, float, str, unicode, NoneType:
  687. .. sourcecode:: pycon
  688. >>> print(objectify.PYTYPE_ATTRIBUTE)
  689. {http://codespeak.net/lxml/objectify/pytype}pytype
  690. >>> ns, name = objectify.PYTYPE_ATTRIBUTE[1:].split('}')
  691. >>> root = objectify.fromstring("""\
  692. ... <root xmlns:py='%s'>
  693. ... <a py:pytype='str'>5</a>
  694. ... <b py:pytype='int'>5</b>
  695. ... <c py:pytype='NoneType' />
  696. ... </root>
  697. ... """ % ns)
  698. >>> print(root.a + 10)
  699. 510
  700. >>> print(root.b + 10)
  701. 15
  702. >>> print(root.c)
  703. None
  704. Note that you can change the name and namespace used for this
  705. attribute through the ``set_pytype_attribute_tag(tag)`` module
  706. function, in case your application ever needs to. There is also a
  707. utility function ``annotate()`` that recursively generates this
  708. attribute for the elements of a tree:
  709. .. sourcecode:: pycon
  710. >>> root = objectify.fromstring("<root><a>test</a><b>5</b></root>")
  711. >>> print(objectify.dump(root))
  712. root = None [ObjectifiedElement]
  713. a = 'test' [StringElement]
  714. b = 5 [IntElement]
  715. >>> objectify.annotate(root)
  716. >>> print(objectify.dump(root))
  717. root = None [ObjectifiedElement]
  718. a = 'test' [StringElement]
  719. * py:pytype = 'str'
  720. b = 5 [IntElement]
  721. * py:pytype = 'int'
  722. XML Schema datatype annotation
  723. ------------------------------
  724. A second way of specifying data type information uses XML Schema types as
  725. element annotations. Objectify knows those that can be mapped to normal
  726. Python types:
  727. .. sourcecode:: pycon
  728. >>> root = objectify.fromstring('''\
  729. ... <root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  730. ... xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  731. ... <d xsi:type="xsd:double">5</d>
  732. ... <i xsi:type="xsd:int" >5</i>
  733. ... <s xsi:type="xsd:string">5</s>
  734. ... </root>
  735. ... ''')
  736. >>> print(objectify.dump(root))
  737. root = None [ObjectifiedElement]
  738. d = 5.0 [FloatElement]
  739. * xsi:type = 'xsd:double'
  740. i = 5 [IntElement]
  741. * xsi:type = 'xsd:int'
  742. s = '5' [StringElement]
  743. * xsi:type = 'xsd:string'
  744. Again, there is a utility function ``xsiannotate()`` that recursively
  745. generates the "xsi:type" attribute for the elements of a tree:
  746. .. sourcecode:: pycon
  747. >>> root = objectify.fromstring('''\
  748. ... <root><a>test</a><b>5</b><c>true</c></root>
  749. ... ''')
  750. >>> print(objectify.dump(root))
  751. root = None [ObjectifiedElement]
  752. a = 'test' [StringElement]
  753. b = 5 [IntElement]
  754. c = True [BoolElement]
  755. >>> objectify.xsiannotate(root)
  756. >>> print(objectify.dump(root))
  757. root = None [ObjectifiedElement]
  758. a = 'test' [StringElement]
  759. * xsi:type = 'xsd:string'
  760. b = 5 [IntElement]
  761. * xsi:type = 'xsd:integer'
  762. c = True [BoolElement]
  763. * xsi:type = 'xsd:boolean'
  764. Note, however, that ``xsiannotate()`` will always use the first XML Schema
  765. datatype that is defined for any given Python type, see also
  766. `Defining additional data classes`_.
  767. The utility function ``deannotate()`` can be used to get rid of 'py:pytype'
  768. and/or 'xsi:type' information:
  769. .. sourcecode:: pycon
  770. >>> root = objectify.fromstring('''\
  771. ... <root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  772. ... xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  773. ... <d xsi:type="xsd:double">5</d>
  774. ... <i xsi:type="xsd:int" >5</i>
  775. ... <s xsi:type="xsd:string">5</s>
  776. ... </root>''')
  777. >>> objectify.annotate(root)
  778. >>> print(objectify.dump(root))
  779. root = None [ObjectifiedElement]
  780. d = 5.0 [FloatElement]
  781. * xsi:type = 'xsd:double'
  782. * py:pytype = 'float'
  783. i = 5 [IntElement]
  784. * xsi:type = 'xsd:int'
  785. * py:pytype = 'int'
  786. s = '5' [StringElement]
  787. * xsi:type = 'xsd:string'
  788. * py:pytype = 'str'
  789. >>> objectify.deannotate(root)
  790. >>> print(objectify.dump(root))
  791. root = None [ObjectifiedElement]
  792. d = 5 [IntElement]
  793. i = 5 [IntElement]
  794. s = 5 [IntElement]
  795. You can control which type attributes should be de-annotated with the keyword
  796. arguments 'pytype' (default: True) and 'xsi' (default: True).
  797. ``deannotate()`` can also remove 'xsi:nil' attributes by setting 'xsi_nil=True'
  798. (default: False):
  799. .. sourcecode:: pycon
  800. >>> root = objectify.fromstring('''\
  801. ... <root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  802. ... xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  803. ... <d xsi:type="xsd:double">5</d>
  804. ... <i xsi:type="xsd:int" >5</i>
  805. ... <s xsi:type="xsd:string">5</s>
  806. ... <n xsi:nil="true"/>
  807. ... </root>''')
  808. >>> objectify.annotate(root)
  809. >>> print(objectify.dump(root))
  810. root = None [ObjectifiedElement]
  811. d = 5.0 [FloatElement]
  812. * xsi:type = 'xsd:double'
  813. * py:pytype = 'float'
  814. i = 5 [IntElement]
  815. * xsi:type = 'xsd:int'
  816. * py:pytype = 'int'
  817. s = '5' [StringElement]
  818. * xsi:type = 'xsd:string'
  819. * py:pytype = 'str'
  820. n = None [NoneElement]
  821. * xsi:nil = 'true'
  822. * py:pytype = 'NoneType'
  823. >>> objectify.deannotate(root, xsi_nil=True)
  824. >>> print(objectify.dump(root))
  825. root = None [ObjectifiedElement]
  826. d = 5 [IntElement]
  827. i = 5 [IntElement]
  828. s = 5 [IntElement]
  829. n = u'' [StringElement]
  830. Note that ``deannotate()`` does not remove the namespace declarations
  831. of the ``pytype`` namespace by default. To remove them as well, and
  832. to generally clean up the namespace declarations in the document
  833. (usually when done with the whole processing), pass the option
  834. ``cleanup_namespaces=True``. This option is new in lxml 2.3.2. In
  835. older versions, use the function ``lxml.etree.cleanup_namespaces()``
  836. instead.
  837. The DataElement factory
  838. -----------------------
  839. For convenience, the ``DataElement()`` factory creates an Element with a
  840. Python value in one step. You can pass the required Python type name or the
  841. XSI type name:
  842. .. sourcecode:: pycon
  843. >>> root = objectify.Element("root")
  844. >>> root.x = objectify.DataElement(5, _pytype="int")
  845. >>> print(objectify.dump(root))
  846. root = None [ObjectifiedElement]
  847. x = 5 [IntElement]
  848. * py:pytype = 'int'
  849. >>> root.x = objectify.DataElement(5, _pytype="str", myattr="someval")
  850. >>> print(objectify.dump(root))
  851. root = None [ObjectifiedElement]
  852. x = '5' [StringElement]
  853. * myattr = 'someval'
  854. * py:pytype = 'str'
  855. >>> root.x = objectify.DataElement(5, _xsi="integer")
  856. >>> print(objectify.dump(root))
  857. root = None [ObjectifiedElement]
  858. x = 5 [IntElement]
  859. * py:pytype = 'int'
  860. * xsi:type = 'xsd:integer'
  861. XML Schema types reside in the XML schema namespace thus ``DataElement()``
  862. tries to correctly prefix the xsi:type attribute value for you:
  863. .. sourcecode:: pycon
  864. >>> root = objectify.Element("root")
  865. >>> root.s = objectify.DataElement(5, _xsi="string")
  866. >>> objectify.deannotate(root, xsi=False)
  867. >>> print(etree.tostring(root, pretty_print=True))
  868. <root xmlns:py="http://codespeak.net/lxml/objectify/pytype" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  869. <s xsi:type="xsd:string">5</s>
  870. </root>
  871. ``DataElement()`` uses a default nsmap to set these prefixes:
  872. .. sourcecode:: pycon
  873. >>> el = objectify.DataElement('5', _xsi='string')
  874. >>> namespaces = list(el.nsmap.items())
  875. >>> namespaces.sort()
  876. >>> for prefix, namespace in namespaces:
  877. ... print("%s - %s" % (prefix, namespace))
  878. py - http://codespeak.net/lxml/objectify/pytype
  879. xsd - http://www.w3.org/2001/XMLSchema
  880. xsi - http://www.w3.org/2001/XMLSchema-instance
  881. >>> print(el.get("{http://www.w3.org/2001/XMLSchema-instance}type"))
  882. xsd:string
  883. While you can set custom namespace prefixes, it is necessary to provide valid
  884. namespace information if you choose to do so:
  885. .. sourcecode:: pycon
  886. >>> el = objectify.DataElement('5', _xsi='foo:string',
  887. ... nsmap={'foo': 'http://www.w3.org/2001/XMLSchema'})
  888. >>> namespaces = list(el.nsmap.items())
  889. >>> namespaces.sort()
  890. >>> for prefix, namespace in namespaces:
  891. ... print("%s - %s" % (prefix, namespace))
  892. foo - http://www.w3.org/2001/XMLSchema
  893. py - http://codespeak.net/lxml/objectify/pytype
  894. xsi - http://www.w3.org/2001/XMLSchema-instance
  895. >>> print(el.get("{http://www.w3.org/2001/XMLSchema-instance}type"))
  896. foo:string
  897. Note how lxml chose a default prefix for the XML Schema Instance
  898. namespace. We can override it as in the following example:
  899. .. sourcecode:: pycon
  900. >>> el = objectify.DataElement('5', _xsi='foo:string',
  901. ... nsmap={'foo': 'http://www.w3.org/2001/XMLSchema',
  902. ... 'myxsi': 'http://www.w3.org/2001/XMLSchema-instance'})
  903. >>> namespaces = list(el.nsmap.items())
  904. >>> namespaces.sort()
  905. >>> for prefix, namespace in namespaces:
  906. ... print("%s - %s" % (prefix, namespace))
  907. foo - http://www.w3.org/2001/XMLSchema
  908. myxsi - http://www.w3.org/2001/XMLSchema-instance
  909. py - http://codespeak.net/lxml/objectify/pytype
  910. >>> print(el.get("{http://www.w3.org/2001/XMLSchema-instance}type"))
  911. foo:string
  912. Care must be taken if different namespace prefixes have been used for the same
  913. namespace. Namespace information gets merged to avoid duplicate definitions
  914. when adding a new sub-element to a tree, but this mechanism does not adapt the
  915. prefixes of attribute values:
  916. .. sourcecode:: pycon
  917. >>> root = objectify.fromstring("""<root xmlns:schema="http://www.w3.org/2001/XMLSchema"/>""")
  918. >>> print(etree.tostring(root, pretty_print=True))
  919. <root xmlns:schema="http://www.w3.org/2001/XMLSchema"/>
  920. >>> s = objectify.DataElement("17", _xsi="string")
  921. >>> print(etree.tostring(s, pretty_print=True))
  922. <value xmlns:py="http://codespeak.net/lxml/objectify/pytype" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" py:pytype="str" xsi:type="xsd:string">17</value>
  923. >>> root.s = s
  924. >>> print(etree.tostring(root, pretty_print=True))
  925. <root xmlns:schema="http://www.w3.org/2001/XMLSchema">
  926. <s xmlns:py="http://codespeak.net/lxml/objectify/pytype" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" py:pytype="str" xsi:type="xsd:string">17</s>
  927. </root>
  928. It is your responsibility to fix the prefixes of attribute values if you
  929. choose to deviate from the standard prefixes. A convenient way to do this for
  930. xsi:type attributes is to use the ``xsiannotate()`` utility:
  931. .. sourcecode:: pycon
  932. >>> objectify.xsiannotate(root)
  933. >>> print(etree.tostring(root, pretty_print=True))
  934. <root xmlns:schema="http://www.w3.org/2001/XMLSchema">
  935. <s xmlns:py="http://codespeak.net/lxml/objectify/pytype" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" py:pytype="str" xsi:type="schema:string">17</s>
  936. </root>
  937. Of course, it is discouraged to use different prefixes for one and the same
  938. namespace when building up an objectify tree.
  939. Defining additional data classes
  940. --------------------------------
  941. You can plug additional data classes into objectify that will be used in
  942. exactly the same way as the predefined types. Data classes can either inherit
  943. from ``ObjectifiedDataElement`` directly or from one of the specialised
  944. classes like ``NumberElement`` or ``BoolElement``. The numeric types require
  945. an initial call to the NumberElement method ``self._setValueParser(function)``
  946. to set their type conversion function (string -> numeric Python type). This
  947. call should be placed into the element ``_init()`` method.
  948. The registration of data classes uses the ``PyType`` class:
  949. .. sourcecode:: pycon
  950. >>> class ChristmasDate(objectify.ObjectifiedDataElement):
  951. ... def call_santa(self):
  952. ... print("Ho ho ho!")
  953. >>> def checkChristmasDate(date_string):
  954. ... if not date_string.startswith('24.12.'):
  955. ... raise ValueError # or TypeError
  956. >>> xmas_type = objectify.PyType('date', checkChristmasDate, ChristmasDate)
  957. The PyType constructor takes a string type name, an (optional) callable type
  958. check and the custom data class. If a type check is provided it must accept a
  959. string as argument and raise ValueError or TypeError if it cannot handle the
  960. string value.
  961. PyTypes are used if an element carries a ``py:pytype`` attribute denoting its
  962. data type or, in absence of such an attribute, if the given type check callable
  963. does not raise a ValueError/TypeError exception when applied to the element
  964. text.
  965. If you want, you can also register this type under an XML Schema type name:
  966. .. sourcecode:: pycon
  967. >>> xmas_type.xmlSchemaTypes = ("date",)
  968. XML Schema types will be considered if the element has an ``xsi:type``
  969. attribute that specifies its data type. The line above binds the XSD type
  970. ``date`` to the newly defined Python type. Note that this must be done before
  971. the next step, which is to register the type. Then you can use it:
  972. .. sourcecode:: pycon
  973. >>> xmas_type.register()
  974. >>> root = objectify.fromstring(
  975. ... "<root><a>24.12.2000</a><b>12.24.2000</b></root>")
  976. >>> root.a.call_santa()
  977. Ho ho ho!
  978. >>> root.b.call_santa()
  979. Traceback (most recent call last):
  980. ...
  981. AttributeError: no such child: call_santa
  982. If you need to specify dependencies between the type check functions, you can
  983. pass a sequence of type names through the ``before`` and ``after`` keyword
  984. arguments of the ``register()`` method. The PyType will then try to register
  985. itself before or after the respective types, as long as they are currently
  986. registered. Note that this only impacts the currently registered types at the
  987. time of registration. Types that are registered later on will not care about
  988. the dependencies of already registered types.
  989. If you provide XML Schema type information, this will override the type check
  990. function defined above:
  991. .. sourcecode:: pycon
  992. >>> root = objectify.fromstring('''\
  993. ... <root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  994. ... <a xsi:type="date">12.24.2000</a>
  995. ... </root>
  996. ... ''')
  997. >>> print(root.a)
  998. 12.24.2000
  999. >>> root.a.call_santa()
  1000. Ho ho ho!
  1001. To unregister a type, call its ``unregister()`` method:
  1002. .. sourcecode:: pycon
  1003. >>> root.a.call_santa()
  1004. Ho ho ho!
  1005. >>> xmas_type.unregister()
  1006. >>> root.a.call_santa()
  1007. Traceback (most recent call last):
  1008. ...
  1009. AttributeError: no such child: call_santa
  1010. Be aware, though, that this does not immediately apply to elements to which
  1011. there already is a Python reference. Their Python class will only be changed
  1012. after all references are gone and the Python object is garbage collected.
  1013. Advanced element class lookup
  1014. -----------------------------
  1015. In some cases, the normal data class setup is not enough. Being based
  1016. on ``lxml.etree``, however, ``lxml.objectify`` supports very
  1017. fine-grained control over the Element classes used in a tree. All you
  1018. have to do is configure a different `class lookup`_ mechanism (or
  1019. write one yourself).
  1020. .. _`class lookup`: element_classes.html
  1021. The first step for the setup is to create a new parser that builds
  1022. objectify documents. The objectify API is meant for data-centric XML
  1023. (as opposed to document XML with mixed content). Therefore, we
  1024. configure the parser to let it remove whitespace-only text from the
  1025. parsed document if it is not enclosed by an XML element. Note that
  1026. this alters the document infoset, so if you consider the removed
  1027. spaces as data in your specific use case, you should go with a normal
  1028. parser and just set the element class lookup. Most applications,
  1029. however, will work fine with the following setup:
  1030. .. sourcecode:: pycon
  1031. >>> parser = objectify.makeparser(remove_blank_text=True)
  1032. What this does internally, is:
  1033. .. sourcecode:: pycon
  1034. >>> parser = etree.XMLParser(remove_blank_text=True)
  1035. >>> lookup = objectify.ObjectifyElementClassLookup()
  1036. >>> parser.set_element_class_lookup(lookup)
  1037. If you want to change the lookup scheme, say, to get additional
  1038. support for `namespace specific classes`_, you can register the
  1039. objectify lookup as a fallback of the namespace lookup. In this case,
  1040. however, you have to take care that the namespace classes inherit from
  1041. ``objectify.ObjectifiedElement``, not only from the normal
  1042. ``lxml.etree.ElementBase``, so that they support the ``objectify``
  1043. API. The above setup code then becomes:
  1044. .. sourcecode:: pycon
  1045. >>> lookup = etree.ElementNamespaceClassLookup(
  1046. ... objectify.ObjectifyElementClassLookup() )
  1047. >>> parser.set_element_class_lookup(lookup)
  1048. .. _`namespace specific classes`: element_classes.html#namespace-class-lookup
  1049. See the documentation on `class lookup`_ schemes for more information.
  1050. What is different from lxml.etree?
  1051. ==================================
  1052. Such a different Element API obviously implies some side effects to the normal
  1053. behaviour of the rest of the API.
  1054. * len(<element>) returns the sibling count, not the number of children of
  1055. <element>. You can retrieve the number of children with the
  1056. ``countchildren()`` method.
  1057. * Iteration over elements does not yield the children, but the siblings. You
  1058. can access all children with the ``iterchildren()`` method on elements or
  1059. retrieve a list by calling the ``getchildren()`` method.
  1060. * The find, findall and findtext methods require a different implementation
  1061. based on ETXPath. In ``lxml.etree``, they use a Python implementation based
  1062. on the original iteration scheme. This has the disadvantage that they may
  1063. not be 100% backwards compatible, and the additional advantage that they now
  1064. support any XPath expression.