2010-11-18 2 views
19

Я пытаюсь разобрать содержимое в электронной таблице OpenSffice ODS. Формат ods - это просто zip-файл с несколькими документами. Содержимое электронной таблицы хранится в 'content.xml'.Как использовать пространства имен xml с find/findall в lxml?

import zipfile 
from lxml import etree 

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

Содержание таблицы находится в ячейке:

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

Мы также можем идти прямо к рядам:

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

Отдельные элементы знать о пространствах имен:

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

Как сделать Я использую пространства имен непосредственно в find/findall?

Очевидное решение не работает.

Попытка получить строки из таблицы:

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

Вы пытались использовать Python API для OpenOffice для обработки электронных таблиц? – jfs

+0

Привет Я использую имя etree.QName для доступа к элементам и атрибутам с помощью пространства имен. его опрятный способ с помощью словаря пространств имен, и он также работает с методом find и findall. для получения дополнительной информации см.: http://lxml.de/tutorial.html#namespaces –

ответ

16

Если root.nsmap содержит префикс table пространства имен, то вы можете:

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

findall(path) принимает {namespace}name синтаксиса вместо namespace:name. Поэтому path должен быть предварительно обработан с использованием словарного словаря до формы {namespace}name, прежде чем передать его findall().

+0

Интересно, но, похоже, проблема нижнего уровня: table.xpath ('.// ​​table: table-row', nsmap = table.nsmap) *** XPathResultError: Неизвестный тип возврата: dict – saffsd

+0

@saffsd: Примечание: * namespaces = * not * nsmap = *. Попробуйте: 'root.xpath ('.// ​​table: table-row', namespaces = {'table': 'urn: oasis: names: tc: opendocument: xmlns: table: 1.0'})' – jfs

6

Вот способ получить все пространства имен в документе XML (и предположим, что конфликт префикса отсутствует).

Я использую это при анализе XML-документов, где я заранее знаю, что такое URL-адреса пространства имен, и только префикс.

 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

Может быть, первое, что нужно заметить, что пространство имен определяются на уровне Element, а не уровне документа.

Чаще всего, хотя, все пространства имен объявлены в документа корневого элемента (office:document-content здесь), который спасает нас разборе все это собрать внутренние xmlns областей.

Тогда элемент nsmap включает в себя:

  • пространство имен по умолчанию, с None префикса (не всегда)
  • всех пространств имен предков, если переопределены.

Если, как упоминалось ChrisR пространство имен по умолчанию не поддерживается, вы можете использовать dict comprehension, чтобы отфильтровать его в более компактном выражении.

У вас есть несколько иной синтаксис для xpath и ElementPath.


Так вот код, который вы могли бы использовать, чтобы получить все строки вашей первой таблицы в (проверено: 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

Если вам нужно nsmap, который включает пространство имен по умолчанию, использует (Python 3): 'nsmap = {k, если k не является None else 'default': v для k, v в root.nsmap.items()}' – skelliam

+0

Для Python 3 переименовать iteritems () выше только к элементам(). – skelliam