2010-11-18 17 views
19

Sto cercando di analizzare il contenuto in un foglio di calcolo ODS di OpenOffice. Il formato ods è essenzialmente solo un file zip con un numero di documenti. Il contenuto del foglio di calcolo è memorizzato in "content.xml".Come utilizzare gli spazi dei nomi xml con find/findall in lxml?

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 fare Io uso gli spazi dei nomi direttamente in find/findall?

La soluzione 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: ':' 
+0

Hai provato a usare Python API per OpenOffice per elaborare i fogli di calcolo? – jfs

+0

Ciao Sto usando etree.QName per accedere a Elements e attributes con Namespace. è un modo pernicioso con l'aiuto di un dizionario di spazi dei nomi, e funziona anche con il metodo find e findall. per ulteriori informazioni, consultare: http://lxml.de/tutorial.html#namespaces –

risposta

16

Se root.nsmap contiene il prefisso dello spazio dei nomi table allora si potrebbe:

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

findall(path) accetta {namespace}name sintassi invece di namespace:name. Pertanto, è necessario preelaborare path utilizzando il dizionario dei nomi per il modulo {namespace}name prima di passarlo a findall().

+0

Interessante, ma sembra esserci un problema di livello inferiore: table.xpath ('.// ​​table: table-row', nsmap = table.nsmap) *** XPathResultError: tipo di ritorno sconosciuto: dict – saffsd

+0

@saffsd: Nota: * namespaces = * not * nsmap = *. Prova: 'root.xpath ('.// ​​table: table-row', namespaces = {'table': 'urn: oasis: nomi: tc: opendocument: xmlns: table: 1.0'})' – jfs

6

Ecco un modo per ottenere tutti gli spazi dei nomi nel documento XML (e supponendo che non vi sia alcun conflitto di prefissi).

Io lo uso quando analizzo documenti XML dove so in anticipo quali sono gli URL dello spazio dei nomi 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) 
5

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

Più spesso, però, tutti gli spazi dei nomi vengono dichiarati in elemento radice del documento (office:document-content qui), che ci salva l'analisi tutto per raccogliere interiori xmlns ambiti.

Poi un nsmap elemento include:

  • uno spazio dei nomi di default, con None prefisso (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 comprehension per filtrare fuori in un'espressione più compatto.

Si ha 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) 
+0

Se hai bisogno di un nsmap che include lo spazio dei nomi predefinito, usa (Python 3): 'nsmap = {k se k non è None 'default': v per k, v in root.nsmap.items()}' – skelliam

+0

Per Python 3 rinominare iteritems () qui sopra solo per gli elementi(). – skelliam

Problemi correlati