Python
June 2009 6

Как я учился работать с XML

image
Честно говоря, я довольно сильно удивился, не найдя статьи по подобной теме на хабре. А тема-то довольно актуальная и нужная, поэтому возьму на себя смелость немного ее осветить.


Краткий экскурс


Работая с xml в питоне многие пользуются довольно удобным встроенным модулем xml.dom.minidom. Вся информация в нем, включая содержимое тегов, представляется как эдакие ноды, работа с которыми ведется напрямую. Вот кусок кода обработки xml-файла:
Copy Source | Copy HTML
  1. app_xml = xmlp.parse("base.xml")
  2. id = app_xml.createElement(att)
  3. node = app_xml.createTextNode("simple.App")
  4. id.appendChild(node)
  5. root.appendChild(id)
  6. res = open("base.xml""w")
  7. res.writelines(app_xml.toprettyxml())
  8. res.close()

Весь этот код, как несложно догадаться, открывает существующий xml-файл, парсит и добавляет к нему один-единственный тег id со строкой «simple.App». Громоздко? Да не то слово. Мало того, обнаружился еще один неприятный баг — со времен Python 2.4 функция toprettyxml(), предназначенная для выдачи содержимого ноды или дерева нод в текстовом виде, зачем-то добавляет к каждой строчке символ перевода каретки, в результате чего вместо
<id>simple.App</id>

выдается
<id>
   simple.App
</id>

На первый взгляд, это не критично, поскольку значение остается нетронутым, однако в некоторых случаях и для некоторых парсеров (если, например, собираетесь использовать сгенеренный XML в других разработках) — при обработке числовых данных будет выводиться ошибка. В частности, этим грешит сборщик Adobe AIR-приложений, биндинги к которому я, собственно, и писал.
Поиски в интернете дали результат. Получалось, что либо я должен использовать в своем коде хак в виде лишней функции строчек эдак на двадцать, либо использовать стандартный toxml(), который хоть и генерит валидные файлы — но всю инфу в них располагает в одну строчку, то есть весь мой дескриптор превратился в кашу вида
Copy Source | Copy HTML
  1. <?xml version='1.0' encoding='utf-8'?><application xmlns="http://ns.adobe.com/air/application/1.5"><id>simple.test.program</id><version>0.1</version><filename>testapp</filename><name>testapp</name><initialWindow><title>Test AIR Application</title><content>test.html</content><height>320</height><width>240</width><visible>true</visible><resizable>true</resizable></initialWindow></application>

Назовите меня эстетом, но при больших объемах файла (да еще и с комментариями) искать в подобной куче ошибочные значения — то еще удовольствие.
И тогда я стал искать альтернативный вариант.

Свет в конце тоннеля


И этот вариант пришел в виде модуля lxml. Он упоминался как раз в теме, в которой ругали xml.dom.minidom за творимое им безобразие :)
А теперь давайте взглянем на код генерации хэндла приложения без всяких добавлений и переписываний:
Copy Source | Copy HTML
  1. root = etree.Element("initialWindow")
  2. etree.SubElement(root, "title").text = title
  3. etree.SubElement(root, "content").text = content
  4. etree.SubElement(root, "height").text = str(height)
  5. etree.SubElement(root, "width").text = str(width)
  6. app_window = etree.tostring(root)
  7. ...
  8. root = etree.Element("application", xmlns="http://ns.adobe.com/air/application/1.5")
  9. etree.SubElement(root, "id").text = id
  10. etree.SubElement(root, "version").text = version
  11. etree.SubElement(root, "filename").text = filename
  12. etree.SubElement(root, "name").text = self.name
  13. root.append(etree.XML(app_window))
  14. handle = etree.tostring(root, pretty_print=True, encoding='utf-8', xml_declaration=True)
  15. applic = open(self.fullpath+"/"+self.name+"-app.xml", "w")
  16. applic.writelines(handle)
  17. applic.close()

Куда понятнее и нагляднее, не находите? Этот код позволяет сгенерировать вот такой чистенький и красивенький XML:
Copy Source | Copy HTML
  1. <?xml version='1.0' encoding='utf-8'?>
  2. <application xmlns="http://ns.adobe.com/air/application/1.5">
  3.   <id>simple.test.program</id>
  4.   <version>0.1</version>
  5.   <filename>testapp</filename>
  6.   <name>testapp</name>
  7.   <initialWindow>
  8.     <title>Test AIR Application</title>
  9.     <content>test.html</content>
  10.     <height>320</height>
  11.     <width>240</width>
  12.   </initialWindow>
  13. </application>

Думаю, 90% кода в объяснении не нуждаются. Вся уличная магия заключена в строчке handle = etree.tostring(root, pretty_print=True, encoding='utf-8', xml_declaration=True). Здесь pretty_print — замена того самого злосчастного toprettyxml(), только, в отличие от него, работающая нормально. Также мы задаем кодировку и приделываем стандартную строку заголовка XML-документа.

По утверждениям, этот модуль, как ни странно, работает раза в два быстрее, чем стандартный. Устанавливается он элементарно через setuptools:
$ sudo easy_install lxml

Нормальный туториал лежит на официальном сайте, вот прямая ссылка.

Да здравствует красивый, удобный и валидный код! Удачи вам всем, пишите комментарии.
+61
68.9k 177
Comments 55