Domanda

Sto cercando di contenuti parse in un foglio di calcolo di OpenOffice ODS. Il formato ODS è essenzialmente solo un file zip con una serie di documenti. Il contenuto del foglio di calcolo è memorizzato in 'content.xml'.

import zipfile
from lxml import etree

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

Il contenuto del foglio di calcolo è in una cella:

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

Possiamo anche andare dritto per le righe:

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

I singoli elementi conoscono gli spazi dei nomi:

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

Come posso utilizzare gli spazi dei nomi direttamente nel find / findall?

La soluzione più ovvia non funziona.

Cercando di ottenere le righe della tabella:

>>> 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: ':'
È stato utile?

Soluzione

Se root.nsmap contiene il table namespace prefix allora si potrebbe:

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

findall(path) accetta la sintassi {namespace}name invece di namespace:name. Pertanto path deve essere pre-elaborato utilizzando namespace dizionario alla forma {namespace}name prima di passarlo a findall().

Altri suggerimenti

Ecco un modo per ottenere tutti gli spazi dei nomi nel documento XML (e supponendo non c'è alcun conflitto prefisso).

Io uso questo durante l'analisi di documenti XML in cui io non so in anticipo che cosa gli URL namespace sono, e solo il prefisso.

        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)

Forse la prima cosa da notare è che gli spazi dei nomi sono definiti a livello elemento , non a livello di documento.

Il più delle volte, però, tutti gli spazi dei nomi vengono dichiarati nella del documento elemento radice (office:document-content qui), che ci salva l'analisi tutto per raccogliere scopi xmlns interne.

Poi un nsmap elemento comprende:

  • uno spazio dei nomi di default, con il prefisso None (non sempre)
  • tutti gli antenati spazi dei nomi, a meno che sovrascritto.

Se, come menzionate ChrisR, lo spazio dei nomi di default non è supportato, è possibile utilizzare un dict comprensione per filtrare fuori in un'espressione più compatto.

Hai una sintassi leggermente diversa per XPath e ElementPath .


Quindi, ecco il codice è possibile utilizzare per ottenere tutte le righe la tua prima della tabella (Testato con: 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 non troverà elementi namespace se non ci sono definizioni xmlns nel file XML. Per esempio:

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')

A volte che sono i dati che vi vengono dati. Allora, che cosa si può fare quando non c'è spazio dei nomi?

La mia soluzione:. Aggiungere una

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')
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top