2010-11-18 13 views
19

Estoy tratando de analizar el contenido en una hoja de cálculo de OpenOffice ODS. El formato de ods es esencialmente solo un archivo zip con una cantidad de documentos. El contenido de la hoja de cálculo se almacena en 'content.xml'.¿Cómo uso los espacios de nombres xml con find/findall en lxml?

import zipfile 
from lxml import etree 

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

El contenido de la hoja de cálculo está en una celda:

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

También podemos ir directamente a las filas:

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

Los elementos individuales saben acerca de los espacios de nombres:

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

Cómo hacer Uso los espacios de nombres directamente en find/findall?

La solución obvia no funciona.

Tratando de conseguir las filas de la tabla:

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

Ha intentado utilizar la API de Python para OpenOffice para procesar las hojas de cálculo? – jfs

+0

Hola, estoy usando etree.QName para acceder a Elementos y atributos con Namespace. es una manera ordenada con la ayuda de un diccionario de espacios de nombres, y también funciona con el método find y findall. para obtener más información, consulte: http://lxml.de/tutorial.html#namespaces –

Respuesta

16

Si root.nsmap contiene el prefijo de espacio de nombres table entonces usted podría:

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

findall(path) acepta {namespace}name sintaxis en lugar de namespace:name. Por lo tanto, path debe preprocesarse utilizando el diccionario de espacios de nombres en el formulario {namespace}name antes de pasarlo al findall().

+0

Interesante, pero parece haber un problema de nivel inferior: table.xpath ('.// ​​table: table-row', nsmap = table.nsmap) *** XPathResultError: Tipo de devolución desconocido: dict – saffsd

+0

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

6

Aquí hay una manera de obtener todos los espacios de nombres en el documento XML (y suponiendo que no hay conflicto de prefijo).

Utilizo esto al analizar documentos XML donde sé de antemano cuáles son las URL del espacio de nombres, y solo el prefijo.

 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

Tal vez el primero que se observa es que los espacios de nombres se definen a nivel de elemento , no nivel de documento.

mayoría de las veces, sin embargo, todos los espacios de nombres se declaran en elemento raíz del documento (office:document-content aquí), lo que nos ahorra analizarlo todo para recoger interiores xmlns ámbitos.

A continuación, un elemento nsmap incluye:

  • un espacio de nombres por defecto, con None prefijo (no siempre)
  • todos los espacios de nombres antepasados, a no ser reemplazado.

Si, como lo mencionan ChrisR, no se admite el espacio de nombres predeterminado, puede utilizar un dict comprehension para filtrar hacia fuera en una expresión más compacto.

Tiene una sintaxis ligeramente diferente para xpath y ElementPath.


Así que aquí está el código que puede utilizar para obtener todas las filas de la primera tabla (probado 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

Si necesita un nsmap que incluye el espacio de nombres predeterminado, use (Python 3): 'nsmap = {k si k no es None else 'predeterminado': v para k, v en root.nsmap.items()}' – skelliam

+0

Para Python 3 renombra iteritems () de arriba a solo elementos(). – skelliam

Cuestiones relacionadas