Как вывести CDATA с помощью ElementTree
Я обнаружил, что cElementTree примерно в 30 раз быстрее, чем xml.dom.minidom
, и я переписываю код кодирования/декодирования XML. Тем не менее, мне нужно вывести XML, который содержит разделы CDATA, и, похоже, нет способа сделать это с ElementTree.
Можно ли это сделать?
Ответы
Ответ 1
После небольшой работы я нашел ответ сам. Посмотрев исходный код ElementTree.py, я обнаружил, что существует специальная обработка комментариев XML и инструкций по предварительной обработке. То, что они делают, создает функцию factory для специального типа элемента, которая использует специальное (нестрочное) значение тега, чтобы отличать его от обычных элементов.
def Comment(text=None):
element = Element(Comment)
element.text = text
return element
Затем в функции _write
ElementTree, которая фактически выводит XML, есть специальная обработка сообщений для комментариев:
if tag is Comment:
file.write("<!-- %s -->" % _escape_cdata(node.text, encoding))
Для поддержки разделов CDATA я создаю функцию factory под названием CDATA
, расширяет класс ElementTree и меняет функцию _write
для обработки элементов CDATA.
Это все еще не помогает, если вы хотите проанализировать XML с разделами CDATA, а затем вывести его снова с разделами CDATA, но он по крайней мере позволяет создавать XML файлы с разделами CDATA программным образом, что и требовалось сделать.
Реализация, похоже, работает как с ElementTree, так и cElementTree.
import elementtree.ElementTree as etree
#~ import cElementTree as etree
def CDATA(text=None):
element = etree.Element(CDATA)
element.text = text
return element
class ElementTreeCDATA(etree.ElementTree):
def _write(self, file, node, encoding, namespaces):
if node.tag is CDATA:
text = node.text.encode(encoding)
file.write("\n<![CDATA[%s]]>\n" % text)
else:
etree.ElementTree._write(self, file, node, encoding, namespaces)
if __name__ == "__main__":
import sys
text = """
<?xml version='1.0' encoding='utf-8'?>
<text>
This is just some sample text.
</text>
"""
e = etree.Element("data")
cdata = CDATA(text)
e.append(cdata)
et = ElementTreeCDATA(e)
et.write(sys.stdout, "utf-8")
Ответ 2
lxml поддерживает CDATA и API, как ElementTree.
Ответ 3
Вот вариант решения gooli, который работает для python 3.2:
import xml.etree.ElementTree as etree
def CDATA(text=None):
element = etree.Element('![CDATA[')
element.text = text
return element
etree._original_serialize_xml = etree._serialize_xml
def _serialize_xml(write, elem, qnames, namespaces):
if elem.tag == '![CDATA[':
write("\n<%s%s]]>\n" % (
elem.tag, elem.text))
return
return etree._original_serialize_xml(
write, elem, qnames, namespaces)
etree._serialize_xml = etree._serialize['xml'] = _serialize_xml
if __name__ == "__main__":
import sys
text = """
<?xml version='1.0' encoding='utf-8'?>
<text>
This is just some sample text.
</text>
"""
e = etree.Element("data")
cdata = CDATA(text)
e.append(cdata)
et = etree.ElementTree(e)
et.write(sys.stdout.buffer.raw, "utf-8")
Ответ 4
Решение:
import xml.etree.ElementTree as ElementTree
def CDATA(text=None):
element = ElementTree.Element('![CDATA[')
element.text = text
return element
ElementTree._original_serialize_xml = ElementTree._serialize_xml
def _serialize_xml(write, elem, qnames, namespaces,short_empty_elements, **kwargs):
if elem.tag == '![CDATA[':
write("\n<{}{}]]>\n".format(elem.tag, elem.text))
if elem.tail:
write(_escape_cdata(elem.tail))
else:
return ElementTree._original_serialize_xml(write, elem, qnames, namespaces,short_empty_elements, **kwargs)
ElementTree._serialize_xml = ElementTree._serialize['xml'] = _serialize_xml
if __name__ == "__main__":
import sys
text = """
<?xml version='1.0' encoding='utf-8'?>
<text>
This is just some sample text.
</text>
"""
e = ElementTree.Element("data")
cdata = CDATA(text)
root.append(cdata)
фона:
Я не знаю, хорошо ли работали предыдущие версии предложенного кода и был ли обновлен модуль ElementTree, но я столкнулся с проблемами при использовании этого трюка:
etree._original_serialize_xml = etree._serialize_xml
def _serialize_xml(write, elem, qnames, namespaces):
if elem.tag == '![CDATA[':
write("\n<%s%s]]>\n" % (
elem.tag, elem.text))
return
return etree._original_serialize_xml(
write, elem, qnames, namespaces)
etree._serialize_xml = etree._serialize['xml'] = _serialize_xml
Проблема этого подхода в том, что после передачи этого исключения сериализатор снова обрабатывает его как обычный тег. Я получал что-то вроде:
<textContent>
<![CDATA[this was the code I wanted to put inside of CDATA]]>
<![CDATA[>this was the code I wanted to put inside of CDATA</![CDATA[>
</textContent>
И, конечно, мы знаем, что это приведет только к множеству ошибок.
Почему это произошло, хотя?
Ответ в этом маленьком парне:
return etree._original_serialize_xml(write, elem, qnames, namespaces)
Мы не хотим еще раз проверять код с помощью оригинальной функции сериализации, если мы перехватили наши CDATA и успешно передали его.
Поэтому в блоке "if" мы должны возвращать исходную функцию сериализации только тогда, когда CDATA не было. Мы пропустили "else" перед возвратом исходной функции.
Более того, в моей версии модуля ElementTree функция serialize отчаянно запрашивала аргумент "short_empty_element". Поэтому самая последняя версия, которую я бы порекомендовал, выглядит следующим образом (также с "tail"):
from xml.etree import ElementTree
from xml import etree
#in order to test it you have to create testing.xml file in the folder with the script
xmlParsedWithET = ElementTree.parse("testing.xml")
root = xmlParsedWithET.getroot()
def CDATA(text=None):
element = ElementTree.Element('![CDATA[')
element.text = text
return element
ElementTree._original_serialize_xml = ElementTree._serialize_xml
def _serialize_xml(write, elem, qnames, namespaces,short_empty_elements, **kwargs):
if elem.tag == '![CDATA[':
write("\n<{}{}]]>\n".format(elem.tag, elem.text))
if elem.tail:
write(_escape_cdata(elem.tail))
else:
return ElementTree._original_serialize_xml(write, elem, qnames, namespaces,short_empty_elements, **kwargs)
ElementTree._serialize_xml = ElementTree._serialize['xml'] = _serialize_xml
text = """
<?xml version='1.0' encoding='utf-8'?>
<text>
This is just some sample text.
</text>
"""
e = ElementTree.Element("data")
cdata = CDATA(text)
root.append(cdata)
#tests
print(root)
print(root.getchildren()[0])
print(root.getchildren()[0].text + "\n\nyay!")
Вывод, который я получил, был:
<Element 'Database' at 0x10062e228>
<Element '![CDATA[' at 0x1021cc9a8>
<?xml version='1.0' encoding='utf-8'?>
<text>
This is just some sample text.
</text>
yay!
Я желаю вам того же результата!
Ответ 5
Это невозможно AFAIK... что жаль. В принципе, модули ElementTree предполагают, что читатель на 100% совместим с XML, поэтому не имеет значения, выводит ли он раздел как CDATA или какой-либо другой формат, который генерирует эквивалентный текст.
Для получения дополнительной информации см. этот поток в списке рассылки Python. В принципе, они рекомендуют вместо этого какую-либо XML-библиотеку на основе DOM.
Ответ 6
На самом деле этот код имеет ошибку, так как вы не ловите ]]>
, появляющегося в данных, которые вы вставляете в качестве CDATA
как Есть ли способ избежать торрента конца CDATA в xml?
вы должны разбить его на два CDATA в этом случае, разделив ]]>
между ними.
в основном data = data.replace("]]>", "]]]]><![CDATA[>")
(не обязательно правильно, пожалуйста, проверьте)
Ответ 7
Это закончилось для меня в Python 2.7. Как ответ Амори.
import xml.etree.ElementTree as ET
ET._original_serialize_xml = ET._serialize_xml
def _serialize_xml(write, elem, encoding, qnames, namespaces):
if elem.tag == '![CDATA[':
write("<%s%s]]>%s" % (elem.tag, elem.text, elem.tail))
return
return ET._original_serialize_xml(
write, elem, encoding, qnames, namespaces)
ET._serialize_xml = ET._serialize['xml'] = _serialize_xml
Ответ 8
Я обнаружил хак, чтобы заставить CDATA работать с комментариями:
node.append(etree.Comment(' --><![CDATA[' + data.replace(']]>', ']]]]><![CDATA[>') + ']]><!-- '))
Ответ 9
DOM имеет (по крайней мере на уровне 2) интерфейс
DATASection и операции Document:: createCDATASection. Они есть
расширения, поддерживается только в том случае, если реализация поддерживает
Функция "xml".
из xml.dom import minidom
my_xmldoc = minidom.parse(XMLFILE)
my_xmldoc.createCDATASection(данные)
Теперь у вас есть кадата node добавьте его везде, где захотите....
Ответ 10
Принятое решение не может работать с Python 2.7. Однако есть еще один пакет под названием lxml, который (хотя и немного медленнее) делил в значительной степени идентичный синтаксис с xml.etree.ElementTree
. lxml
способен писать и анализировать CDATA
. Документация здесь
Ответ 11
Здесь моя версия, которая основана на обоих gooli и amaury, отвечает выше. Он работает как для ElementTree 1.2.6, так и для 1.3.0, которые используют очень разные методы для этого.
Обратите внимание, что gooli не работает с 1.3.0, который, кажется, является текущим стандартом в Python 2.7.x.
Также обратите внимание, что эта версия не использует метод CDATA(), который используется gooli.
import xml.etree.cElementTree as ET
class ElementTreeCDATA(ET.ElementTree):
"""Subclass of ElementTree which handles CDATA blocks reasonably"""
def _write(self, file, node, encoding, namespaces):
"""This method is for ElementTree <= 1.2.6"""
if node.tag == '![CDATA[':
text = node.text.encode(encoding)
file.write("\n<![CDATA[%s]]>\n" % text)
else:
ET.ElementTree._write(self, file, node, encoding, namespaces)
def _serialize_xml(write, elem, qnames, namespaces):
"""This method is for ElementTree >= 1.3.0"""
if elem.tag == '![CDATA[':
write("\n<![CDATA[%s]]>\n" % elem.text)
else:
ET._serialize_xml(write, elem, qnames, namespaces)
Ответ 12
Я нашел здесь способ "разобрать XML с разделами CDATA, а затем вывести его снова с разделами CDATA".
Я смог это сделать (может быть, lxml был обновлен после этого сообщения?) со следующим: (немного грубо - извините;-). У кого-то еще может быть лучший способ найти разделы CDATA программно, но я был слишком ленив.
parser = etree.XMLParser(encoding='utf-8') # my original xml was utf-8 and that was a lot of the problem
tree = etree.parse(ppath, parser)
for cdat in tree.findall('./ProjectXMPMetadata'): # the tag where my CDATA lives
cdat.text = etree.CDATA(cdat.text)
# other stuff here
tree.write(opath, encoding="UTF-8",)
Ответ 13
для python3 и ElementTree вы можете использовать следующую запись
import xml.etree.ElementTree as ET
ET._original_serialize_xml = ET._serialize_xml
def serialize_xml_with_CDATA(write, elem, qnames, namespaces, short_empty_elements, **kwargs):
if elem.tag == 'CDATA':
write("<![CDATA[{}]]>".format(elem.text))
return
return ET._original_serialize_xml(write, elem, qnames, namespaces, short_empty_elements, **kwargs)
ET._serialize_xml = ET._serialize['xml'] = serialize_xml_with_CDATA
def CDATA(text):
element = ET.Element("CDATA")
element.text = text
return element
my_xml = ET.Element("my_name")
my_xml.append(CDATA("<p>some text</p>")
tree = ElementTree(my_xml)
если вам нужен xml как str, вы можете использовать
ET.tostring(tree)
или следующий взлом (который почти такой же, как код внутри tostring()
)
fake_file = BytesIO()
tree.write(fake_file, encoding="utf-8", xml_declaration=True)
result_xml_text = str(fake_file.getvalue(), encoding="utf-8")
и получи результат
<?xml version='1.0' encoding='utf-8'?>
<my_name>
<![CDATA[<p>some text</p>]]>
</my_name>
Ответ 14
Вы можете переопределить функцию ElementTree _escape_cdata
:
import xml.etree.ElementTree as ET
def _escape_cdata(text, encoding):
try:
if "&" in text:
text = text.replace("&", "&")
# if "<" in text:
# text = text.replace("<", "<")
# if ">" in text:
# text = text.replace(">", ">")
return text
except TypeError:
raise TypeError(
"cannot serialize %r (type %s)" % (text, type(text).__name__)
)
ET._escape_cdata = _escape_cdata
Обратите внимание, что вам может не потребоваться передавать дополнительный параметр encoding
, в зависимости от версии вашей библиотеки/питона.
Теперь вы можете записать CDATA в obj.text
как:
root = ET.Element('root')
body = ET.SubElement(root, 'body')
body.text = '<![CDATA[perform extra angle brackets escape for this text]]>'
print(ET.tostring(root))
и получите чистый узел CDATA:
<root>
<body>
<![CDATA[perform extra angle brackets escape for this text]]>
</body>
</root>