2010-11-18 32 views
19

Próbuję parsować zawartość w arkuszu kalkulacyjnym ODS OpenOffice. Format ods to w zasadzie plik zip z wieloma dokumentami. Treść arkusza kalkulacyjnego jest przechowywana w "content.xml".Jak używać przestrzeni nazw xml z find/findall w lxml?

import zipfile 
from lxml import etree 

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

Zawartość arkusza znajduje się w komórce:

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

Możemy też pójść prosto do wierszy:

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

poszczególnych elementów wiedzieć o przestrzeniach nazw:

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

Jak to zrobić Używam przestrzeni nazw bezpośrednio w find/findall?

Oczywiste rozwiązanie nie działa.

próbuje uzyskać wiersze z tabeli:

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

Czy próbowałeś używać Pythona API dla OpenOffice przetwarzać arkusze kalkulacyjne? – jfs

+0

Witam Używam etree.QName, aby uzyskać dostęp do elementów i atrybutów w przestrzeni nazw. jest to staroświecki sposób z pomocą słownika przestrzeni nazw i działa również z metodą find i findall. Więcej informacji można znaleźć na stronie: http://lxml.de/tutorial.html#namespaces –

Odpowiedz

16

Jeśli root.nsmap zawiera przedrostek table namespace następnie można:

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

findall(path) akceptuje {namespace}name składni zamiast namespace:name. Dlatego też path należy wstępnie przetworzyć przy użyciu słownika przestrzeni nazw do formularza {namespace}name przed przekazaniem go do findall().

+0

Interesujące, ale wydaje się, że istnieje niższy poziom problemu: table.xpath (".// table: table-row", nsmap = table.nsmap) *** XPathResultError: Nieznany typ zwrotu: dict – saffsd

+0

@saffsd: Uwaga: * namespaces = * not * nsmap = *. Spróbuj: 'root.xpath ('.// ​​table: table-row', namespaces = {'table': 'urn: oasis: names: tc: opendocument: xmlns: table: 1.0'})' – jfs

6

Oto sposób na uzyskanie wszystkich przestrzeni nazw w dokumencie XML (i przypuśćmy, że nie ma konfliktu przedrostków).

Używam tego podczas analizowania dokumentów XML, o ile wiem z góry, jakie są adresy URL przestrzeni nazw, i tylko przedrostek.

 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

Może pierwszą rzeczą jest, aby zauważyć, że przestrzenie nazw są zdefiniowane w Element poziomie, nie poziomie dokumentu.

Najczęściej jednak, wszystkie przestrzenie nazw są zadeklarowane w elemencie głównego dokumentu (office:document-content tutaj), co oszczędza nam parsowania to wszystko zebrać wewnętrzne xmlns zakresów.

Następnie element nsmap obejmuje:

  • domyślnej przestrzeni nazw, z None prefiksu (nie zawsze)
  • wszystkich przodków przestrzenie nazw, chyba że zostaną zamienione.

Jeżeli, jak ChrisR mentionned domyślna przestrzeń nazw nie jest obsługiwany, można użyć dict comprehension filtrować go w bardziej zwartej wypowiedzi.

Masz nieco inną składnię dla xpath i ElementPath.


Więc oto kod można użyć, aby uzyskać wszystkie wiersze swojej pierwszej tabeli za (testowane z: 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

Jeśli potrzebujesz nsmap, który zawiera domyślny obszar nazw, użyj (Python 3): 'nsmap = {k jeśli k nie jest żadnym innym" default ": v dla k, v w root.nsmap.items()}' – skelliam

+0

Dla Pythona 3 zmień nazwę iteritems () powyżej do samych elementów(). – skelliam

Powiązane problemy