it-swarm.dev

Analisando XML com namespace em Python via 'ElementTree'

Eu tenho o seguinte XML que eu quero analisar usando ElementTree do Python:

<rdf:RDF xml:base="http://dbpedia.org/ontology/"
    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    xmlns:owl="http://www.w3.org/2002/07/owl#"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
    xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
    xmlns="http://dbpedia.org/ontology/">

    <owl:Class rdf:about="http://dbpedia.org/ontology/BasketballLeague">
        <rdfs:label xml:lang="en">basketball league</rdfs:label>
        <rdfs:comment xml:lang="en">
          a group of sports teams that compete against each other
          in Basketball
        </rdfs:comment>
    </owl:Class>

</rdf:RDF>

Eu quero encontrar todas as tags owl:Class e então extrair o valor de todas as instâncias rdfs:label dentro delas. Estou usando o seguinte código:

tree = ET.parse("filename")
root = tree.getroot()
root.findall('owl:Class')

Por causa do namespace, estou recebendo o seguinte erro.

SyntaxError: prefix 'owl' not found in prefix map

Eu tentei ler o documento em http://effbot.org/zone/element-namespaces.htm mas ainda não consigo fazer isso funcionar, pois o XML acima tem vários namespaces aninhados.

Por favor, deixe-me saber como alterar o código para encontrar todas as tags owl:Class.

140
Sudar

ElementTree não é muito inteligente sobre namespaces. Você precisa dar aos métodos .find(), findall() e iterfind() um dicionário de namespace explícito. Isso não está documentado muito bem:

namespaces = {'owl': 'http://www.w3.org/2002/07/owl#'} # add more as needed

root.findall('owl:Class', namespaces)

Os prefixos são apenas pesquisados ​​no parâmetro namespaces que você passa. Isso significa que você pode usar qualquer prefixo de namespace que desejar; a API separa a parte owl:, pesquisa a URL do namespace correspondente no dicionário namespaces e, em seguida, altera a pesquisa para procurar a expressão XPath {http://www.w3.org/2002/07/owl}Class. Você pode usar a mesma sintaxe também, é claro:

root.findall('{http://www.w3.org/2002/07/owl#}Class')

Se você pode mudar para a biblioteca lxml as coisas são melhores; essa biblioteca suporta a mesma API ElementTree, mas coleta namespaces para você em um atributo .nsmap em elementos.

195
Martijn Pieters

Veja como fazer isso com lxml sem precisar codificar os namespaces ou digitalizar o texto para eles (como Martijn Pieters menciona):

from lxml import etree
tree = etree.parse("filename")
root = tree.getroot()
root.findall('owl:Class', root.nsmap)
53
Brad Dre

Nota : Esta é uma resposta útil para a biblioteca padrão ElementTree do Python sem usar namespaces codificados .

Para extrair os prefixos e URI do espaço de nomes dos dados XML, pode utilizar a função ElementTree.iterparse, analisando apenas os eventos iniciais do espaço de nomes ( start-ns ):

>>> from io import StringIO
>>> from xml.etree import ElementTree
>>> my_schema = u'''<rdf:RDF xml:base="http://dbpedia.org/ontology/"
...     xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
...     xmlns:owl="http://www.w3.org/2002/07/owl#"
...     xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
...     xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
...     xmlns="http://dbpedia.org/ontology/">
... 
...     <owl:Class rdf:about="http://dbpedia.org/ontology/BasketballLeague">
...         <rdfs:label xml:lang="en">basketball league</rdfs:label>
...         <rdfs:comment xml:lang="en">
...           a group of sports teams that compete against each other
...           in Basketball
...         </rdfs:comment>
...     </owl:Class>
... 
... </rdf:RDF>'''
>>> my_namespaces = dict([
...     node for _, node in ElementTree.iterparse(
...         StringIO(my_schema), events=['start-ns']
...     )
... ])
>>> from pprint import pprint
>>> pprint(my_namespaces)
{'': 'http://dbpedia.org/ontology/',
 'owl': 'http://www.w3.org/2002/07/owl#',
 'rdf': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
 'rdfs': 'http://www.w3.org/2000/01/rdf-schema#',
 'xsd': 'http://www.w3.org/2001/XMLSchema#'}

Então o dicionário pode ser passado como argumento para as funções de busca:

root.findall('owl:Class', my_namespaces)
23
Davide Brunato

Eu tenho usado código semelhante para isso e descobri que vale sempre a pena ler a documentação ... como de costume!

findall () só encontrará elementos que são filhos diretos da tag atual. Então, não é realmente tudo.

Pode valer a pena tentar fazer com que seu código funcione com o seguinte, especialmente se você estiver lidando com arquivos xml grandes e complexos, para que esses sub-sub-elementos (etc.) também sejam incluídos. Se você conhece a si mesmo onde os elementos estão no seu xml, então eu suponho que vai ficar bem! Apenas pensei que isso valesse a pena lembrar.

root.iter()

ref: https://docs.python.org/3/library/xml.etree.elementtree.html#finding-interesting-elements "Element.findall () encontra apenas elementos com uma tag que são filhos diretos do elemento atual. O Element.find () encontra o primeiro filho com uma determinada tag, e o Element.text acessa o conteúdo do texto do elemento. Element.get () acessa os atributos do elemento: "

6
MJM

Para obter o espaço de nomes no seu formato de espaço de nomes, por ex. {myNameSpace}, você pode fazer o seguinte:

root = tree.getroot()
ns = re.match(r'{.*}', root.tag).group(0)

Dessa forma, você pode usá-lo posteriormente em seu código para localizar nós, por exemplo, usando a interpolação de string (Python 3).

link = root.find(f'{ns}link')
2
Bram Vanroy

Eu sei que estou alguns anos atrasado, mas acabei de criar um pacote que manipulará a conversão de um dicionário para um XML válido com namespaces. O pacote está hospedado em PyPi @ https://pypi.python.org/pypi/xmler .

Usando este pacote você pode pegar um dicionário que se parece com isso:

myDict = {
    "RootTag": {                        # The root tag. Will not necessarily be root. (see #customRoot)
        "@ns": "soapenv",           # The namespace for the RootTag. The RootTag will appear as <soapenv:RootTag ...>
        "@attrs": {                     # @attrs takes a dictionary. each key-value pair will become an attribute
            { "xmlns:soapenv": "http://schemas.xmlsoap.org/soap/envelope/" }
        },
        "childTag": {
            "@attrs": {
                "someAttribute": "colors are Nice"
            },
            "grandchild": "This is a text tag"
        }
    }
}

e obter saída XML que se parece com isso:

<soapenv:RootTag xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
    <childTag someAttribute="colors are Nice">
        <grandchild>This is a text tag</grandchild>
    </childTag>
</soapenv:RootTag>

Espero que isso seja útil para as pessoas no futuro

1
watzon

Minha solução é baseada no comentário de @Martijn Pieters:

register_namespace influencia apenas a serialização, não a pesquisa.

Então, o truque aqui é usar dicionários diferentes para serialização e pesquisa.

namespaces = {
    '': 'http://www.example.com/default-schema',
    'spec': 'http://www.example.com/specialized-schema',
}

Agora, registre todos os namespaces para análise e gravação:

for name, value in namespaces.iteritems():
    ET.register_namespace(name, value)

Para a procura (find(), findall(), iterfind()), precisamos de um prefixo não vazio. Passe essas funções em um dicionário modificado (aqui modifico o dicionário original, mas isso deve ser feito somente depois que os namespaces estiverem registrados).

self.namespaces['default'] = self.namespaces['']

Agora, as funções da família find() podem ser usadas com o prefixo default:

print root.find('default:myelem', namespaces)

mas

tree.write(destination)

não usa nenhum prefixo para elementos no espaço de nomes padrão.

0
peter.slizik