web-dev-qa-db-fra.com

Supprimez l'espace de nom et le préfixe xml en python à l'aide de lxml

J'ai un fichier xml que je dois ouvrir et y apporter des modifications, l'une de ces modifications consiste à supprimer l'espace de nom et le préfixe, puis à les enregistrer dans un autre fichier. Voici le fichier xml:

<?xml version='1.0' encoding='UTF-8'?>
<package xmlns="http://Apple.com/iTunes/importer">
  <provider>some data</provider>
  <language>en-GB</language>
</package>

Je peux apporter les autres modifications dont j'ai besoin, mais je ne sais pas comment supprimer l'espace de nom et le préfixe. Voici le XML XML dont j'ai besoin:

<?xml version='1.0' encoding='UTF-8'?>
<package>
  <provider>some data</provider>
  <language>en-GB</language>
</package>

Et voici mon script qui va ouvrir et analyser le XML et le sauvegarder:

metadata = '/Users/user1/Desktop/Python/metadata.xml'
from lxml import etree
parser = etree.XMLParser(remove_blank_text=True)
open(metadata)
tree = etree.parse(metadata, parser)
root = tree.getroot()
tree.write('/Users/user1/Desktop/Python/done.xml', pretty_print = True, xml_declaration = True, encoding = 'UTF-8')

Alors, comment pourrais-je ajouter du code dans mon script qui supprimera l’espace de nom et le préfixe?

13
speedyrazor

Remplacer la balise comme le suggère Uku Loskit. En plus de cela, utilisez lxml.objectify.deannotate .

from lxml import etree, objectify

metadata = '/Users/user1/Desktop/Python/metadata.xml'
parser = etree.XMLParser(remove_blank_text=True)
tree = etree.parse(metadata, parser)
root = tree.getroot()

####    
for elem in root.getiterator():
    if not hasattr(elem.tag, 'find'): continue  # (1)
    i = elem.tag.find('}')
    if i >= 0:
        elem.tag = elem.tag[i+1:]
objectify.deannotate(root, cleanup_namespaces=True)
####

tree.write('/Users/user1/Desktop/Python/done.xml',
           pretty_print=True, xml_declaration=True, encoding='UTF-8')

UPDATE

Certaines balises telles que Comment renvoient une fonction lors de l'accès à l'attribut tag. ajouté une garde pour ça. (1)

26
falsetru
>>> root.tag
'{http://latest/nmc-omc/cmNrm.doc#measCollec}measCollecFile'
>>> etree.QName(root.tag).localname
'measCollecFile'

la source

Addendum: lxml.etree.QName accepte également les éléments en construction. Donc etree.QName(root.tag).localname est équivalent à:

etree.QName(root).localname
14
SergiyKolesnikov
import xml.etree.ElementTree as ET
def remove_namespace(doc, namespace):
    """Remove namespace in the passed document in place."""
    ns = u'{%s}' % namespace
    nsl = len(ns)
    for elem in doc.getiterator():
        if elem.tag.startswith(ns):
            elem.tag = elem.tag[nsl:]

metadata = '/Users/user1/Desktop/Python/metadata.xml'
tree = ET.parse(metadata)
root = tree.getroot()

remove_namespace(root, u'http://Apple.com/iTunes/importer')
tree.write('/Users/user1/Desktop/Python/done.xml',
       pretty_print=True, xml_declaration=True, encoding='UTF-8')

Utilisé un extrait de code de ici Cette méthode pourrait facilement être étendue pour supprimer tout attribut d’espace de nom en recherchant les balises commençant par "xmlns".

4
Uku Loskit

Vous pouvez également utiliser XSLT pour supprimer les espaces de noms ...

XSLT 1.0 (test.xsl)

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output indent="yes"/>
  <xsl:strip-space elements="*"/>

  <xsl:template match="node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="*" priority="1">
    <xsl:element name="{local-name()}" namespace="">
      <xsl:apply-templates select="@*|node()"/>
    </xsl:element>
  </xsl:template>

  <xsl:template match="@*">
    <xsl:attribute name="{local-name()}" namespace="">
      <xsl:value-of select="."/>
    </xsl:attribute>
  </xsl:template>

</xsl:stylesheet>

Python

from lxml import etree

tree = etree.parse("metadata.xml")
xslt = etree.parse("test.xsl")

new_tree = tree.xslt(xslt)

print(etree.tostring(new_tree, pretty_print=True, xml_declaration=True, 
                     encoding="UTF-8").decode("UTF-8"))

Sortie

<?xml version='1.0' encoding='UTF-8'?>
<package>
  <provider>some data</provider>
  <language>en-GB</language>
</package>
1
Daniel Haley

tout ce que vous devez faire c'est:

objectify.deannotate(root, cleanup_namespaces=True)

après avoir récupéré la racine, en utilisant root = tree.getroot()

0
kmonsoor

Voici deux autres moyens de supprimer des espaces de noms. Le premier utilise l'assistant lxml.etree.QName tandis que le second utilise des expressions rationnelles. Les deux fonctions permettent une liste facultative d’espaces de nommage. Si aucune liste d'espaces de noms n'est fournie, tous les espaces de noms sont supprimés. Les clés d'attribut sont également nettoyées.

from lxml import etree
import re

def remove_namespaces_qname(doc, namespaces=None):

    for el in doc.getiterator():

        # clean tag
        q = etree.QName(el.tag)
        if q is not None:
            if namespaces is not None:
                if q.namespace in namespaces:
                    el.tag = q.localname
            else:
                el.tag = q.localname

            # clean attributes
            for a, v in el.items():
                q = etree.QName(a)
                if q is not None:
                    if namespaces is not None:
                        if q.namespace in namespaces:
                            del el.attrib[a]
                            el.attrib[q.localname] = v
                    else:
                        del el.attrib[a]
                        el.attrib[q.localname] = v
    return doc


def remove_namespace_re(doc, namespaces=None):

    if namespaces is not None:
        ns = list(map(lambda n: u'{%s}' % n, namespaces))

    for el in doc.getiterator():

        # clean tag
        m = re.match(r'({.+})(.+)', el.tag)
        if m is not None:
            if namespaces is not None:
                if m.group(1) in ns:
                    el.tag = m.group(2)
            else:
                el.tag = m.group(2)

            # clean attributes
            for a, v in el.items():
                m = re.match(r'({.+})(.+)', a)
                if m is not None:
                    if namespaces is not None:
                        if m.group(1) in ns:
                            del el.attrib[a]
                            el.attrib[m.group(2)] = v
                    else:
                        del el.attrib[a]
                        el.attrib[m.group(2)] = v
    return doc
0
Bruce