簡體   English   中英

如何在lxml中使用帶有find / findall的xml命名空間?

[英]How do I use xml namespaces with find/findall in lxml?

我正在嘗試解析OpenOffice ODS電子表格中的內容。 ods格式本質上只是一個包含許多文檔的zipfile。 電子表格的內容存儲在'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: ':'

如果root.nsmap包含table名稱空間前綴,那么您可以:

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

findall(path)接受{namespace}name語法而不是namespace:name 因此,在將命名空間字典傳遞給findall()之前,應該使用命名空間字典將path預處理到{namespace}name表單。

這是獲取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)

也許首先要注意的是命名空間是在元素級別而不是文檔級別定義的。

但最常見的是,所有命名空間都在文檔的根元素( office:document-content here)中聲明,這使我們可以解析它以收集內部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)

如果XML文件中沒有xmlns定義,則Etree將找不到命名空間元素。 例如:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM