【问题标题】:How do I use xml namespaces with find/findall in lxml?如何在 lxml 中使用带有 find/findall 的 xml 命名空间?
【发布时间】:2010-11-18 01:07:30
【问题描述】:

我正在尝试解析 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: ':'

【问题讨论】:

  • 您是否尝试过使用 Python API for OpenOffice 来处理电子表格?
  • 您好,我正在使用 etree.QName 通过命名空间访问元素和属性。在命名空间字典的帮助下,它是一种巧妙的方式,它也适用于 find 和 findall 方法。更多信息请参考:lxml.de/tutorial.html#namespaces

标签: python xml lxml xml-namespaces elementtree


【解决方案1】:

如果root.nsmap 包含table 命名空间前缀,那么您可以:

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

findall(path) 接受 {namespace}name 语法而不是 namespace:name。因此path 应该使用命名空间字典预处理到{namespace}name 表单,然后再将其传递给findall()

【讨论】:

  • 有趣,但似乎有一个更底层的问题:table.xpath('.//table:table-row', nsmap=table.nsmap) *** XPathResultError: Unknown return type : 字典
  • @saffsd:注意:namespaces= 不是 nsmap=。试试看:root.xpath('.//table:table-row', namespaces={'table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0'})
  • 顺便说一句,如果 xml 正确定义了命名空间,root.nsmap 应该自动填充文档中的命名空间。这是一个可靠的解决方案。
【解决方案2】:

也许首先要注意的是命名空间 在元素级别定义,而不是文档级别。

大多数情况下,所有命名空间都在文档的 根元素(此处为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)

【讨论】:

  • 如果您需要包含默认命名空间的 nsmap,请使用 (Python 3):nsmap = {k if k is not None else 'default':v for k,v in root.nsmap.items()}
  • 对于 Python 3,将上面的 iteritems() 重命名为 items()。
  • 也可以nsmap = root.nsmap; nsmap.pop(None),可能会更清楚一些。
【解决方案3】:

这是一种获取 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)

【讨论】:

    【解决方案4】:

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

    【讨论】:

      猜你喜欢
      • 2014-03-01
      • 2016-08-15
      • 1970-01-01
      • 1970-01-01
      • 2010-09-12
      • 2018-06-03
      • 2011-02-20
      • 2012-04-28
      • 1970-01-01
      相关资源
      最近更新 更多