2010-11-18 6 views
19

J'essaie d'analyser le contenu d'une feuille de calcul OpenOffice ODS. Le format ods est essentiellement un fichier zip avec un certain nombre de documents. Le contenu de la feuille de calcul est stocké dans "content.xml".Comment utiliser les espaces de noms xml avec find/findall dans lxml?

import zipfile 
from lxml import etree 

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

Le contenu de la feuille de calcul est dans une cellule:

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

Nous pouvons aussi aller directement pour les lignes:

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

Les éléments individuels connaissent les espaces de noms:

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

Comment faire J'utilise les espaces de noms directement dans find/findall?

La solution évidente ne fonctionne pas.

Essayer d'obtenir les lignes de la table:

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

Avez-vous essayé d'utiliser l'API Python pour OpenOffice pour traiter les feuilles de calcul? – jfs

+0

Salut J'utilise etree.QName pour accéder à Elements et attributs avec Namespace. C'est une manière soignée avec l'aide d'un dictionnaire d'espaces de noms, et cela fonctionne aussi avec la méthode find et findall. Pour plus d'informations, veuillez vous référer à: http://lxml.de/tutorial.html#namespaces –

Répondre

16

Si root.nsmap contient le préfixe d'espace de noms table alors vous pouvez:

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

findall(path) accepte {namespace}name syntaxe au lieu de namespace:name. Par conséquent path doit être prétraité à l'aide du dictionnaire d'espace de noms au formulaire {namespace}name avant de le transmettre à findall().

+0

Intéressant, mais il semble y avoir un problème de niveau inférieur: table.xpath ('.// ​​table: table-row', nsmap = table.nsmap) *** XPathResultError: Type de retour inconnu: dict – saffsd

+0

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

6

Voici un moyen d'obtenir tous les espaces de noms dans le document XML (et en supposant qu'il n'y ait pas de conflit de préfixes).

Je l'utilise lors de l'analyse de documents XML où je sais à l'avance quelles sont les URL de l'espace de noms, et seulement le préfixe.

 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

Peut-être la première chose à noter est que les espaces de noms sont définis au niveau de l'élément , non niveau de document.

Le plus souvent cependant, tous les espaces de noms sont déclarés dans l'élément racine du document (office:document-content ici), ce qui nous permet d'économiser l'analyse tout pour recueillir xmlns intérieures étendues.

Ensuite, un nsmap élément comprend:

  • un espace de noms par défaut, avec le préfixe None (pas toujours)
  • tous les ancêtres namespaces, sauf contrordre.

Si, comme ChrisR mentionné, l'espace de noms par défaut est pas pris en charge, vous pouvez utiliser un dict comprehension pour filtrer sur dans une expression plus compacte.Vous avez une syntaxe légèrement différente pour xpath et ElementPath.


Alors, voici le code que vous pouvez utiliser pour obtenir toutes vos lignes de tableau (testé avec: 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 vous avez besoin d'un nsmap qui inclut l'espace de nom par défaut, utilisez (Python 3): 'nsmap = {k si k n'est pas None else 'par défaut': v pour k, v dans root.nsmap.items()}' – skelliam

+0

Pour Python 3 renommer iteritems () ci-dessus à seulement des éléments(). – skelliam

Questions connexes