Сохранение исходного типа документа и объявление lxml.etree, проанализированного xml

Я использую Python lxml, и я пытаюсь прочитать документ XML, изменить и записать его обратно, но исходное объявление doctype и xml исчезает. Мне интересно, есть ли простой способ вернуть его обратно через lxml или каким-либо другим решением?

2 ответа

Решение

Следующее будет включать DOCTYPE и декларацию XML:

from lxml import etree
from StringIO import StringIO

tree = etree.parse(StringIO('''<?xml version="1.0" encoding="iso-8859-1"?>
 <!DOCTYPE root SYSTEM "test" [ <!ENTITY tasty "eggs"> ]>
  <root>
   <a>&tasty;</a>
 </root>
'''))

docinfo = tree.docinfo
print etree.tostring(tree, xml_declaration=True, encoding=docinfo.encoding)

Заметка, tostring не сохраняет DOCTYPE если вы создаете Element (например, используя fromstring), он работает только при обработке XML с использованием parse,

Обновление: как отметил Я. Ф. Себастьян, мое утверждение о fromstring неправда.

Вот некоторый код, чтобы подчеркнуть различия между Element а также ElementTree сериализация:

from lxml import etree
from StringIO import StringIO

xml_str = '''<?xml version="1.0" encoding="iso-8859-1"?>
 <!DOCTYPE root SYSTEM "test" [ <!ENTITY tasty "eggs"> ]>
  <root>
   <a>&tasty;</a>
 </root>
'''

# get the ElementTree using parse
parse_tree = etree.parse(StringIO(xml_str))
encoding = parse_tree.docinfo.encoding
result = etree.tostring(parse_tree, xml_declaration=True, encoding=encoding)
print "%s\nparse ElementTree:\n%s\n" % ('-'*20, result)

# get the ElementTree using fromstring
fromstring_tree = etree.fromstring(xml_str).getroottree()
encoding = fromstring_tree.docinfo.encoding
result = etree.tostring(fromstring_tree, xml_declaration=True, encoding=encoding)
print "%s\nfromstring ElementTree:\n%s\n" % ('-'*20, result)

# DOCTYPE is lost, and no access to encoding
fromstring_element = etree.fromstring(xml_str)
result = etree.tostring(fromstring_element, xml_declaration=True)
print "%s\nfromstring Element:\n%s\n" % ('-'*20, result)

и вывод:

--------------------
parse ElementTree:
<?xml version='1.0' encoding='iso-8859-1'?>
<!DOCTYPE root SYSTEM "test" [
<!ENTITY tasty "eggs">
]>
<root>
   <a>eggs</a>
 </root>

--------------------
fromstring ElementTree:
<?xml version='1.0' encoding='iso-8859-1'?>
<!DOCTYPE root SYSTEM "test" [
<!ENTITY tasty "eggs">
]>
<root>
   <a>eggs</a>
 </root>

--------------------
fromstring Element:
<?xml version='1.0' encoding='ASCII'?>
<root>
   <a>eggs</a>
 </root>

Вы также можете сохранить DOCTYPE и объявление XML с помощью fromstring():

import sys
from StringIO import StringIO
from lxml import etree

xml = r'''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
 <head>
 <title>example</title>
 </head>
 <body>
 <p>This is an example</p>
 </body>
</html>'''

tree = etree.fromstring(xml).getroottree() # or etree.parse(file)
tree.write(sys.stdout, xml_declaration=True, encoding=tree.docinfo.encoding)

Выход

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
 <head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
 <title>example</title>
 </head>
 <body>
 <p>This is an example</p>
 </body>
</html>

Обратите внимание, что присутствуют объявление xml (с правильной кодировкой) и тип документа. Он даже (возможно, неправильно) использует ' вместо " в декларации XML и добавляет Content-Type к <head>,

Для примера ввода @John Keyes он дает те же результаты, что и etree.tostring() в ответ.

Другие вопросы по тегам