J'ai découvert que cElementTree est environ 30 fois plus rapide que xml.dom.minidom
et je suis en train de réécrire mon code d'encodage/décodage XML. Cependant, je dois sortir du XML contenant des sections CDATA et il ne semble pas y avoir de moyen de le faire avec ElementTree.
Cela peut-il être fait?
Après un peu de travail, j'ai trouvé la réponse moi-même. En regardant le code source ElementTree.py, j'ai trouvé qu'il y avait un traitement spécial des commentaires XML et des instructions de prétraitement. Ce qu'ils font est de créer une fonction d'usine pour le type d'élément spécial qui utilise une valeur de balise spéciale (non chaîne) pour la différencier des éléments normaux.
def Comment(text=None):
element = Element(Comment)
element.text = text
return element
Ensuite, dans la fonction _write
de ElementTree qui génère le XML, il existe une gestion de casse spéciale pour les commentaires:
if tag is Comment:
file.write("<!-- %s -->" % _escape_cdata(node.text, encoding))
Afin de prendre en charge les sections CDATA, j'ai créé une fonction d'usine nommée CDATA
, étendu la classe ElementTree et modifié la fonction _write
afin de gérer les éléments CDATA.
Cela n'aide toujours pas si vous voulez analyser un XML avec des sections CDATA puis le ré-exporter avec les sections CDATA, mais vous permet au moins de créer des XML avec des sections CDATA par programme, ce que je devais faire.
L'implémentation semble fonctionner à la fois avec ElementTree et 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")
Voici une variante de la solution de Gooli qui fonctionne pour 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")
En réalité, ce code a un bogue, car vous ne voyez pas ]]>
apparaître dans les données que vous insérez en tant que CDATA
selon Y at-il un moyen d'échapper à un jeton de fin CDATA en XML?
dans ce cas, vous devriez le diviser en deux CDATA, en séparant le ]]>
entre les deux.
fondamentalement data = data.replace("]]>", "]]]]><![CDATA[>")
(pas nécessairement correct, veuillez vérifier)
Ce n'est pas possible, autant que je sache ... ce qui est dommage. Fondamentalement, les modules ElementTree supposent que le lecteur est conforme à 100% à XML. Par conséquent, peu importe s'ils produisent une section au format CDATA ou à un autre format générant le texte équivalent.
Voir ce sujet sur la liste de diffusion Python pour plus d’informations. En gros, ils recommandent plutôt une sorte de bibliothèque XML basée sur DOM.
Je ne sais pas si les versions précédentes du code proposé fonctionnaient très bien et si le module ElementTree a été mis à jour, mais j'ai rencontré des problèmes pour utiliser cette astuce:
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
Le problème avec cette approche est qu’après avoir passé cette exception, le sérialiseur la traite à nouveau comme une balise normale. Je recevais quelque chose comme:
<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>
Et bien sûr, nous savons que cela ne causera que beaucoup d’erreurs. Pourquoi cela se passait-il?
La réponse est dans ce petit gars:
return etree._original_serialize_xml(write, elem, qnames, namespaces)
Nous ne voulons pas examiner le code une nouvelle fois via la fonction de sérialisation d'origine si nous avons piégé notre CDATA et que nous l'avons passé avec succès à travers . Par conséquent, dans le bloc "if", nous ne devons restituer la fonction de sérialisation d'origine que lorsque CDATA n'était pas là. Nous manquions "else" avant de retourner la fonction originale.
De plus, dans ma version du module ElementTree, la fonction serialize demandait désespérément l'argument "short_empty_element". Donc, la version la plus récente que je recommanderais ressemble à ceci (aussi avec "queue"):
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!")
La sortie que j'ai eu était:
<Element 'Database' at 0x10062e228>
<Element '![CDATA[' at 0x1021cc9a8>
<?xml version='1.0' encoding='utf-8'?>
<text>
This is just some sample text.
</text>
yay!
Je vous souhaite le même résultat!
Cela a fini par travailler pour moi dans Python 2.7. Semblable à la réponse d'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
J'ai découvert un moyen de faire fonctionner CDATA en utilisant les commentaires:
node.append(etree.Comment(' --><![CDATA[' + data.replace(']]>', ']]]]><![CDATA[>') + ']]><!-- '))
Le DOM a (au moins au niveau 2) une interface DATASection et une opération Document :: createCDATASection. Ce sont des interfaces d'extension , Prises en charge uniquement si une implémentation prend en charge la fonctionnalité "Xml".
depuis xml.dom import minidom
my_xmldoc = minidom.parse (fichier xml)
my_xmldoc.createCDATASection (données)
maintenant vous avez le noeud de cadata l'ajouter où vous voulez ....
Je suis arrivé ici à la recherche d'un moyen "d'analyser un XML avec des sections CDATA, puis de le sortir à nouveau avec les sections CDATA".
J'ai pu faire cela (peut-être que lxml a été mis à jour depuis ce post?) Avec ce qui suit: (c'est un peu difficile - désolé ;-). Quelqu'un d’autre aurait peut-être un meilleur moyen de trouver les sections CDATA par programme, mais j’étais trop paresseux.
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",)
Voici ma version qui est basée sur les réponses de Gooli et d'Amaury ci-dessus. Cela fonctionne à la fois pour ElementTree 1.2.6 et 1.3.0, qui utilisent des méthodes très différentes pour le faire.
Notez que gooli ne fonctionne pas avec 1.3.0, ce qui semble être la norme actuelle dans Python 2.7.x.
Notez également que cette version n'utilise pas non plus la méthode 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)
pour python3 et ElementTree, vous pouvez utiliser le prochain récepteur
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)
si vous avez besoin de xml en str, vous pouvez utiliser
ET.tostring(tree)
ou le hack suivant (qui correspond presque au code dans tostring()
)
fake_file = BytesIO()
tree.write(fake_file, encoding="utf-8", xml_declaration=True)
result_xml_text = str(fake_file.getvalue(), encoding="utf-8")
et obtenir le résultat
<?xml version='1.0' encoding='utf-8'?>
<my_name>
<![CDATA[<p>some text</p>]]>
</my_name>