Честно говоря, я довольно сильно удивился, не найдя статьи по подобной теме на хабре. А тема-то довольно актуальная и нужная, поэтому возьму на себя смелость немного ее осветить.
Краткий экскурс
Работая с xml в питоне многие пользуются довольно удобным встроенным модулем xml.dom.minidom. Вся информация в нем, включая содержимое тегов, представляется как эдакие ноды, работа с которыми ведется напрямую. Вот кусок кода обработки xml-файла:
Copy Source | Copy HTML
- app_xml = xmlp.parse("base.xml")
- id = app_xml.createElement(att)
- node = app_xml.createTextNode("simple.App")
- id.appendChild(node)
- root.appendChild(id)
- res = open("base.xml", "w")
- res.writelines(app_xml.toprettyxml())
- 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
- <?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
- root = etree.Element("initialWindow")
- etree.SubElement(root, "title").text = title
- etree.SubElement(root, "content").text = content
- etree.SubElement(root, "height").text = str(height)
- etree.SubElement(root, "width").text = str(width)
- app_window = etree.tostring(root)
- ...
- root = etree.Element("application", xmlns="http://ns.adobe.com/air/application/1.5")
- etree.SubElement(root, "id").text = id
- etree.SubElement(root, "version").text = version
- etree.SubElement(root, "filename").text = filename
- etree.SubElement(root, "name").text = self.name
- root.append(etree.XML(app_window))
- handle = etree.tostring(root, pretty_print=True, encoding='utf-8', xml_declaration=True)
- applic = open(self.fullpath+"/"+self.name+"-app.xml", "w")
- applic.writelines(handle)
- applic.close()
Куда понятнее и нагляднее, не находите? Этот код позволяет сгенерировать вот такой чистенький и красивенький XML:
Copy Source | Copy HTML
- <?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>
- </initialWindow>
- </application>
Думаю, 90% кода в объяснении не нуждаются. Вся уличная магия заключена в строчке handle = etree.tostring(root, pretty_print=True, encoding='utf-8', xml_declaration=True). Здесь pretty_print — замена того самого злосчастного toprettyxml(), только, в отличие от него, работающая нормально. Также мы задаем кодировку и приделываем стандартную строку заголовка XML-документа.
По утверждениям, этот модуль, как ни странно, работает раза в два быстрее, чем стандартный. Устанавливается он элементарно через setuptools:
$ sudo easy_install lxml
Нормальный туториал лежит на официальном сайте, вот прямая ссылка.
Да здравствует красивый, удобный и валидный код! Удачи вам всем, пишите комментарии.