Je veux utiliser la méthode de "findall" pour localiser certains éléments du fichier source XML dans le module ElementTree.
Toutefois, le fichier XML source (test.xml) a un espace de noms. Je tronque une partie du fichier XML comme exemple:
<?xml version="1.0" encoding="iso-8859-1"?>
<XML_HEADER xmlns="http://www.test.com">
<TYPE>Updates</TYPE>
<DATE>9/26/2012 10:30:34 AM</DATE>
<COPYRIGHT_NOTICE>All Rights Reserved.</COPYRIGHT_NOTICE>
<LICENSE>newlicense.htm</LICENSE>
<DEAL_LEVEL>
<PAID_OFF>N</PAID_OFF>
</DEAL_LEVEL>
</XML_HEADER>
L'exemple de code Python est ci-dessous:
from xml.etree import ElementTree as ET
tree = ET.parse(r"test.xml")
el1 = tree.findall("DEAL_LEVEL/PAID_OFF") # Return None
el2 = tree.findall("{http://www.test.com}DEAL_LEVEL/{http://www.test.com}PAID_OFF") # Return <Element '{http://www.test.com}DEAL_LEVEL/PAID_OFF' at 0xb78b90>
Bien que cela puisse fonctionner, car il existe un espace de noms "{http://www.test.com}", il est très pratique d'ajouter un espace de noms devant chaque balise.
Comment puis-je ignorer l'espace de noms lorsque j'utilise les méthodes "find", "findall", etc.?
Au lieu de modifier le document XML lui-même, il est préférable de l'analyser, puis de modifier les balises dans le résultat. De cette façon, vous pouvez gérer plusieurs espaces de noms et alias d'espaces de noms:
from StringIO import StringIO
import xml.etree.ElementTree as ET
# instead of ET.fromstring(xml)
it = ET.iterparse(StringIO(xml))
for _, el in it:
if '}' in el.tag:
el.tag = el.tag.split('}', 1)[1] # strip all namespaces
root = it.root
Ceci est basé sur la discussion ici: http://bugs.python.org/issue18304
Si vous supprimez l'attribut xmlns du fichier xml avant de l'analyser, il n'y aura pas d'espace de nom ajouté à chaque balise dans l'arborescence.
import re
xmlstring = re.sub(' xmlns="[^"]+"', '', xmlstring, count=1)
Jusqu'à présent, les réponses inséraient explicitement la valeur de l'espace de noms dans le script. Pour une solution plus générique, je préférerais extraire l'espace de noms du xml:
import re
def get_namespace(element):
m = re.match('\{.*\}', element.tag)
return m.group(0) if m else ''
Et utilisez-le dans la méthode find:
namespace = get_namespace(tree.getroot())
print tree.find('./{0}parent/{0}version'.format(namespace)).text
Voici une extension de la réponse de nonagon, qui supprime également les attributs de l'espace de nom:
from StringIO import StringIO
import xml.etree.ElementTree as ET
# instead of ET.fromstring(xml)
it = ET.iterparse(StringIO(xml))
for _, el in it:
if '}' in el.tag:
el.tag = el.tag.split('}', 1)[1] # strip all namespaces
for at in el.attrib.keys(): # strip namespaces of attributes too
if '}' in at:
newat = at.split('}', 1)[1]
el.attrib[newat] = el.attrib[at]
del el.attrib[at]
root = it.root
Améliorer la réponse de ericspod:
Au lieu de changer le mode d'analyse globalement, nous pouvons envelopper cela dans un objet supportant la construction with.
from xml.parsers import expat
class DisableXmlNamespaces:
def __enter__(self):
self.oldcreate = expat.ParserCreate
expat.ParserCreate = lambda encoding, sep: self.oldcreate(encoding, None)
def __exit__(self, type, value, traceback):
expat.ParserCreate = self.oldcreate
Ceci peut alors être utilisé comme suit
import xml.etree.ElementTree as ET
with DisableXmlNamespaces():
tree = ET.parse("test.xml")
L'avantage de cette méthode est qu'elle ne modifie aucun comportement pour le code non lié en dehors du bloc with. J'ai fini par créer ceci après avoir eu des erreurs dans des bibliothèques non liées après avoir utilisé la version d'ericspod qui utilisait également expat.
Vous pouvez également utiliser la structure élégante de formatage de chaîne:
ns='http://www.test.com'
el2 = tree.findall("{%s}DEAL_LEVEL/{%s}PAID_OFF" %(ns,ns))
ou, si vous êtes sûr que PAID_OFF n'apparaît que dans un niveau de l'arborescence:
el2 = tree.findall(".//{%s}PAID_OFF" % ns)
Si vous utilisez ElementTree
et non cElementTree
, vous pouvez forcer Expat à ignorer le traitement de l'espace de noms en remplaçant ParserCreate()
:
from xml.parsers import expat
oldcreate = expat.ParserCreate
expat.ParserCreate = lambda encoding, sep: oldcreate(encoding, None)
ElementTree
essaie d'utiliser Expat en appelant ParserCreate()
mais n'offre aucune option pour ne pas fournir de chaîne de séparation d'espace de nom; le code ci-dessus le fera ignorer mais il sera averti que cela pourrait casser d'autres choses.
Combinons réponse de nonagon avec réponse de mzjn'a à une question connexe :
def parse_xml(xml_path: Path) -> Tuple[ET.Element, Dict[str, str]]:
xml_iter = ET.iterparse(xml_path, events=["start-ns"])
xml_namespaces = dict(prefix_namespace_pair for _, prefix_namespace_pair in xml_iter)
return xml_iter.root, xml_namespaces
En utilisant cette fonction, nous:
Créez un itérateur pour obtenir les deux espaces de noms et un objet d'arborescence analysé .
Parcourez l'itérateur créé pour obtenir les espaces de noms dictés, nous pourrons ensuite transmettre chaque appel find()
ou findall()
comme indiqué par iMom .
Je pense que c’est la meilleure approche car il n’ya pas de manipulation d’un fichier XML source ni de la sortie analysée xml.etree.ElementTree
que ce soit.
J'aimerais aussi créditer réponse de barny pour avoir fourni une pièce essentielle de ce puzzle (que vous pouvez obtenir la racine analysée à partir de l'itérateur). Jusque-là, j'ai parcouru deux fois l'arborescence XML dans mon application (une pour obtenir des espaces de noms, l'autre pour une racine).
Je pourrais être en retard pour cela mais je ne pense pas que re.sub
soit une bonne solution.
Cependant, la réécriture xml.parsers.expat
ne fonctionne pas pour les versions Python 3.x,
Le principal responsable est le xml/etree/ElementTree.py
voir en bas du code source
# Import the C accelerators
try:
# Element is going to be shadowed by the C implementation. We need to keep
# the Python version of it accessible for some "creative" by external code
# (see tests)
_Element_Py = Element
# Element, SubElement, ParseError, TreeBuilder, XMLParser
from _elementtree import *
except ImportError:
pass
Ce qui est un peu triste.
La solution consiste à s'en débarrasser en premier.
import _elementtree
try:
del _elementtree.XMLParser
except AttributeError:
# in case deleted twice
pass
else:
from xml.parsers import expat # NOQA: F811
oldcreate = expat.ParserCreate
expat.ParserCreate = lambda encoding, sep: oldcreate(encoding, None)
Testé sur Python 3.6.
Essayez la déclaration try
est utile si vous rechargez ou importez un module deux fois dans votre code et que vous obtenez des erreurs étranges telles que
en fait, le code source d'etree semble vraiment compliqué.