objectify.txt 42 KB

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