Подтвердить что ты не робот

Как вывести CDATA с помощью ElementTree

Я обнаружил, что cElementTree примерно в 30 раз быстрее, чем xml.dom.minidom, и я переписываю код кодирования/декодирования XML. Тем не менее, мне нужно вывести XML, который содержит разделы CDATA, и, похоже, нет способа сделать это с ElementTree.

Можно ли это сделать?

4b9b3361

Ответ 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("&", "&amp;")
        # if "<" in text:
            # text = text.replace("<", "&lt;")
        # if ">" in text:
            # text = text.replace(">", "&gt;")
        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>