it-swarm.dev

Jak výstup CDATA pomocí ElementTree

Zjistil jsem, že cElementTree je asi 30 krát rychlejší než xml.dom.minidom a přepisuji kódování/dekódování XML. Potřebuji však výstup XML, který obsahuje sekce CDATA, a zdá se, že to není způsob, jak to s ElementTree udělat.

Lze to udělat?

36
gooli

Po trochu práce jsem našel odpověď sám. Při pohledu na zdrojový kód ElementTree.py jsem zjistil, že existuje speciální zpracování komentářů XML a instrukcí pro předzpracování. To, co dělají, je vytvoření tovární funkce pro speciální typ elementu, který používá speciální (non-string) hodnotu tagu k odlišení od běžných prvků.

def Comment(text=None):
    element = Element(Comment)
    element.text = text
    return element

Pak ve funkci _write funkce ElementTree, která ve skutečnosti vydává XML, je k dispozici zvláštní případ zpracování komentářů:

if tag is Comment:
    file.write("<!-- %s -->" % _escape_cdata(node.text, encoding))

Za účelem podpory sekcí CDATA jsem vytvořil tovární funkci nazvanou CDATA, rozšířil třídu ElementTree a změnil funkci _write pro zpracování prvků CDATA.

To ještě nepomůže, pokud chcete analyzovat XML s CDATA sekcemi a pak jej znovu vydat s CDATA sekcemi, ale alespoň vám umožní programově vytvářet XML s CDATA sekcemi, což je to, co jsem potřeboval udělat.

Implementace vypadá, že pracuje s ElementTree a 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 __== "__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")
24
gooli

lxml má podporu pro CDATA a API jako ElementTree.

16
iny

Zde je varianta gooliho řešení, které funguje pro 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 __== "__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")
10
Amaury

Ve skutečnosti tento kód obsahuje chybu, protože ]]> se nezobrazuje v údajích, které vkládáte jako CDATA

jak na Existuje způsob, jak uniknout konci tokenu CDATA v xml?

měli byste jej v tomto případě rozdělit na dvě CDATA a rozdělit mezi nimi ]]>.

v podstatě data = data.replace("]]>", "]]]]><![CDATA[>")
(nemusí být nutně správné, ověřte prosím)

6
Andraz Tori

Není to možné AFAIK ... což je škoda. Moduly ElementTree v zásadě předpokládají, že čtečka je kompatibilní se 100% XML, takže by neměla záležet na tom, zda mají na výstupu část jako CDATA nebo nějaký jiný formát, který generuje ekvivalentní text.

Pro více informací viz toto vlákno na mailing listu Python. V podstatě doporučují místo toho XML knihovnu XML.

6
Dan Lenski

Nevím, zda předchozí verze navrhovaného kódu fungovaly velmi dobře a zda byl modul ElementTree aktualizován, ale já jsem se potýkal s problémy s používáním tohoto triku:

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

Problém s tímto přístupem je, že po absolvování této výjimky je serializátor opět zpracovává jako normální tag poté. Dostával jsem něco jako:

<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>

A samozřejmě víme, že to způsobí jen spoustu chyb. Proč se to stalo?

Odpověď je v tomto malém chlapci:

return etree._original_serialize_xml(write, elem, qnames, namespaces)

Nechceme znovu zkoumat kód znovu pomocí původní funkce seriálu, pokud jsme uvěznili naše CDATA a úspěšně ji předali. tam. Před návratem původní funkce nám chybí "jiní".

Navíc v mé verzi modulu ElementTree, serializační funkce zoufale žádala o argument "short_empty_element". Poslední verze, kterou bych doporučil, vypadá takto (také s „ocasem“):

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!")

Výstup, který jsem dostal, byl:

<Element 'Database' at 0x10062e228>
<Element '![CDATA[' at 0x1021cc9a8>

<?xml version='1.0' encoding='utf-8'?>
<text>
This is just some sample text.
</text>


yay!

Přeji vám stejný výsledek!

5
Kamil

Toto skončilo pro mě v Pythonu 2.7. Podobně jako odpověď Amaury.

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
4
zlalanne

Objevil jsem hack, abych mohl CDATA pracovat pomocí komentářů:

node.append(etree.Comment(' --><![CDATA[' + data.replace(']]>', ']]]]><![CDATA[>') + ']]><!-- '))
1
user3155571

DOM má (alespoň v úrovni 2) rozhraní DATASection a operaci Document :: createCDATASection. Jsou to rozhraní rozhraní , Podporovaná pouze v případě, že implementace podporuje funkci "Xml".

z xml.dom import minidom

my_xmldoc = minidom.parse (xmlfile)

my_xmldoc.createCDATASection (data)

teď u mít cadata uzel přidat ji kdekoliv chcete.

1
johnpaultthomas

Přijaté řešení nemůže pracovat s Python 2.7 . Existuje však další balíček nazvaný lxml který (i když poněkud pomalejší) sdílí převážně identickou syntaxi s xml.etree.ElementTree. lxml je schopen zapisovat i analyzovat CDATA. Dokumentace zde

1
elwc

Dostal jsem se sem hledat způsob, jak "analyzovat XML s CDATA sekcemi a pak jej znovu vydat s CDATA sekcemi". 

Byl jsem schopen to udělat (možná lxml byl aktualizován od tohoto příspěvku?) S následujícím: (je to trochu hrubé - omlouvám se ;-). Někdo jiný může mít lepší způsob, jak programově najít sekce CDATA, ale byl jsem příliš líný.

 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",)
0
tom stratton

Zde je moje verze, která je založena na odpovědích gooli a amaury. Funguje pro oba ElementTree 1.2.6 a 1.3.0, které používají velmi odlišné metody.

Všimněte si, že gooli to nefunguje s 1.3.0, což se zdá být současným standardem v Pythonu 2.7.x.

Všimněte si také, že tato verze nepoužívá metodu CDATA (), kterou používá 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)
0
Michael

pro python3 a ElementTree můžete použít další příjem 

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)

pokud potřebujete xml jako str, můžete použít 

ET.tostring(tree)

nebo další hack (který je téměř stejný jako kód v tostring())

fake_file = BytesIO()
tree.write(fake_file, encoding="utf-8", xml_declaration=True)
result_xml_text = str(fake_file.getvalue(), encoding="utf-8")

a získat výsledek 

<?xml version='1.0' encoding='utf-8'?>
<my_name>
  <![CDATA[<p>some text</p>]]>
</my_name>
0