Как мне использовать пространства имен xml с find / findall в lxml? - PullRequest
25 голосов
/ 18 ноября 2010

Я пытаюсь проанализировать содержимое в электронной таблице OpenOffice ODS. Формат ods - это, по сути, просто zip-файл с несколькими документами. Содержимое электронной таблицы хранится в файле «content.xml».

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

Ответы [ 4 ]

20 голосов
/ 18 ноября 2010

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

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

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

11 голосов
/ 19 декабря 2013

Вот способ получить все пространства имен в документе 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)
8 голосов
/ 24 февраля 2015

Возможно, первое, что следует заметить, это то, что пространства имен определены на Уровень элемента , а не Уровень документа.

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

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

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

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

У вас немного другой синтаксис для 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)
1 голос
/ 30 августа 2018

Etree не найдет элементы пространства имен, если в файле XML нет определений xmlns. Например:

import lxml.etree as etree

xml_doc = '<ns:root><ns:child></ns:child></ns:root>'

tree = etree.fromstring(xml_doc)

# finds nothing:
tree.find('.//ns:root', {'ns': 'foo'})
tree.find('.//{foo}root', {'ns': 'foo'})
tree.find('.//ns:root')
tree.find('.//ns:root')

Иногда это данные, которые вам дают. Итак, что вы можете сделать, когда нет пространства имен?

Мое решение: добавить один.

import lxml.etree as etree

xml_doc = '<ns:root><ns:child></ns:child></ns:root>'
xml_doc_with_ns = '<ROOT xmlns:ns="foo">%s</ROOT>' % xml_doc

tree = etree.fromstring(xml_doc_with_ns)

# finds what you're looking for:
tree.find('.//{foo}root')
...