Pergunta

Estou tentando analisar o conteúdo em uma planilha ODS OpenOffice. O formato ODS é essencialmente apenas um zipfile com vários documentos. O conteúdo da planilha é armazenado em 'Content.xml'.

import zipfile
from lxml import etree

zf = zipfile.ZipFile('spreadsheet.ods')
root = etree.parse(zf.open('content.xml'))

O conteúdo da planilha está em uma célula:

table = root.find('.//{urn:oasis:names:tc:opendocument:xmlns:table:1.0}table')

Também podemos ir direto para as linhas:

rows = root.findall('.//{urn:oasis:names:tc:opendocument:xmlns:table:1.0}table-row')

Os elementos individuais sabem sobre os espaços para nome:

>>> table.nsmap['table']
'urn:oasis:names:tc:opendocument:xmlns:table:1.0'

Como uso os namespaces diretamente no find/findall?

A solução óbvia não funciona.

Tentando tirar as fileiras da tabela:

>>> root.findall('.//table:table')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "lxml.etree.pyx", line 1792, in lxml.etree._ElementTree.findall (src/lxml/lxml.etree.c:41770)
  File "lxml.etree.pyx", line 1297, in lxml.etree._Element.findall (src/lxml/lxml.etree.c:37027)
  File "/usr/lib/python2.6/dist-packages/lxml/_elementpath.py", line 225, in findall
    return list(iterfind(elem, path))
  File "/usr/lib/python2.6/dist-packages/lxml/_elementpath.py", line 200, in iterfind
    selector = _build_path_iterator(path)
  File "/usr/lib/python2.6/dist-packages/lxml/_elementpath.py", line 184, in _build_path_iterator
    selector.append(ops[token[0]](_next, token))
KeyError: ':'
Foi útil?

Solução

Se root.nsmap contém o table Prefixo de espaço para nome da names então você pode:

root.xpath('.//table:table', namespaces=root.nsmap)

findall(path) aceita {namespace}name sintaxe em vez de namespace:name. Portanto path deve ser pré -processado usando o dicionário de namespace para o {namespace}name forma antes de passar para findall().

Outras dicas

Aqui está uma maneira de obter todos os espaços para nome no documento XML (e supondo que não haja conflito de prefixo).

Eu uso isso ao analisar documentos XML, onde conheço antecipadamente quais são os URLs de namespace e apenas o prefixo.

        doc = etree.XML(XML_string)

        # Getting all the name spaces.
        nsmap = {}
        for ns in doc.xpath('//namespace::*'):
            if ns[0]: # Removes the None namespace, neither needed nor supported.
                nsmap[ns[0]] = ns[1]
        doc.xpath('//prefix:element', namespaces=nsmap)

Talvez a primeira coisa a perceber seja que os namespaces sejam definidos em Nível de elemento, não Nível de documento.

Na maioria das vezes, porém, todos os namespaces são declarados no elemento raiz do documento (office:document-content aqui), o que nos salva analisando tudo para coletar interno xmlns escopos.

Em seguida, um elemento NSMAP inclui:

  • um espaço para nome padrão, com None prefixo (nem sempre)
  • Todos os namespaces de ancestrais, a menos que substituídos.

Se, como Chris mencionou, o espaço para nome padrão não for suportado, você pode usar um Compreensão do dicto para filtrá -lo em uma expressão mais compacta.

Você tem uma sintaxe ligeiramente diferente para xpath eElementPath.


Então, aqui está o código que você pode usar para obter todas as linhas da sua primeira tabela (testadas com: lxml=3.4.2) :

import zipfile
from lxml import etree

# Open and parse the document
zf = zipfile.ZipFile('spreadsheet.ods')
tree = etree.parse(zf.open('content.xml'))

# Get the root element
root = tree.getroot()

# get its namespace map, excluding default namespace
nsmap = {k:v for k,v in root.nsmap.iteritems() if k}

# use defined prefixes to access elements
table = tree.find('.//table:table', nsmap)
rows = table.findall('table:table-row', nsmap)

# or, if xpath is needed:
table = tree.xpath('//table:table', namespaces=nsmap)[0]
rows = table.xpath('table:table-row', namespaces=nsmap)

Etree não encontrará elementos nomes se houver xmlns Definições no arquivo XML. Por exemplo:

import lxml.etree as etree

xml_doc = '<ns:root><ns:child></ns:child></ns:root>'

tree = etree.fromstring(xml_doc)

# finds nothing:
tree.find('.//ns:root', {'ns': 'foo'})
tree.find('.//{foo}root', {'ns': 'foo'})
tree.find('.//ns:root')
tree.find('.//ns:root')

Às vezes, esses são os dados que você recebe. Então, o que você pode fazer quando não há espaço para nome?

Minha solução: adicione um.

import lxml.etree as etree

xml_doc = '<ns:root><ns:child></ns:child></ns:root>'
xml_doc_with_ns = '<ROOT xmlns:ns="foo">%s</ROOT>' % xml_doc

tree = etree.fromstring(xml_doc_with_ns)

# finds what you're looking for:
tree.find('.//{foo}root')
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top