2010-11-18 16 views
19

Ich versuche, Inhalt in einer OpenOffice ODS-Tabelle zu analysieren. Das ods-Format ist im Wesentlichen nur eine Zip-Datei mit einer Anzahl von Dokumenten. Der Inhalt der Tabelle wird in 'content.xml' gespeichert.Wie verwende ich XML-Namespaces mit find/findall in Lxml?

import zipfile 
from lxml import etree 

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

Der Inhalt der Tabelle ist in einer Zelle:

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

Wir haben auch gerade für die Zeilen gehen kann:

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

Die einzelnen Elemente kennen die Namespaces:

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

Wie? Ich benutze die Namespaces direkt in find/findall?

Die offensichtliche Lösung funktioniert nicht.

Der Versuch, die Zeilen aus der Tabelle zu erhalten:

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

Haben Sie versucht, Python API für Openoffice zu verwenden, um die Tabellen zu bearbeiten? – jfs

+0

Hallo, ich benutze etree.QName für den Zugriff auf Elemente und Attribute mit Namespace. Es ist ein schöner Weg mit Hilfe eines Wörterbuchs von Namespaces, und es funktioniert auch mit finden und finden. Weitere Informationen finden Sie unter: http://lxml.de/tutorial.html#namespaces –

Antwort

16

Wenn root.nsmap enthält den table Namespacepräfix dann könnten Sie:

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

findall(path) nimmt {namespace}name Syntax statt namespace:name. Daher sollte path mit dem Namespace-Dictionary vorverarbeitet werden, bevor es an findall() übergeben wird.

+0

Interessant, aber es scheint ein niedrigeres Problem zu geben: table.xpath ('.// ​​table: table-row', nsmap = table.nsmap) *** XPathResultError: Unbekannter Rückgabetyp: dict – saffsd

+0

@saffsd: Anmerkung: * namespaces = * not * nsmap = *. Versuchen Sie: 'root.xpath ('.// ​​table: Tabellenzeile', namespaces = {'Tabelle': 'urn: oasis: Namen: tc: opendocument: xmlns: Tabelle: 1.0'})' – jfs

6

Hier ist eine Möglichkeit, alle Namespaces im XML-Dokument zu erhalten (und vorausgesetzt, es gibt keinen Präfixkonflikt).

Ich benutze dies beim Parsen von XML-Dokumenten, wo ich im Voraus weiß, was die Namespace-URLs sind, und nur das Präfix.

 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

Vielleicht das erste, was ist zu bemerken, dass die Namensräume bei Element definiert sind Ebene, nicht Dokumentebene.

Meistens jedoch alle Namensräume im Wurzelelement (office:document-content hier) des Dokuments deklariert sind, die uns rettet Parsen es alle inneren xmlns Bereiche zu sammeln.

Dann ein Element nsmap umfasst:

  • Standard-Namespace, mit None Präfix (nicht immer)
  • alle Vorfahren Namensraum, es sei denn, außer Kraft gesetzt.

Wenn, wie ChrisR erwähnt, wird der Standard-Namespace nicht unterstützt wird, können Sie dict comprehension verwenden es in einem kompakteren Ausdruck auszufiltern.

Sie haben eine etwas andere Syntax für Xpath und ElementPath.


Also hier ist der Code, den Sie erhalten alle die nutzen könnten erste Tabellenzeilen (getestet mit: 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

Wenn Sie eine benötigen nsmap, das den Standard-Namespace enthält, benutze (Python 3): 'nsmap = {k wenn k nicht ist None else 'default': v für k, v in root.nsmap.items()}' – skelliam

+0

Benenne iteritems für Python 3 () oben auf nur Elemente(). – skelliam