【问题标题】:Parsing XML with namespace in Python via 'ElementTree'通过“ElementTree”在 Python 中使用命名空间解析 XML
【发布时间】:2013-01-28 23:32:27
【问题描述】:

我有以下 XML 想要使用 Python 的 ElementTree 解析:

<rdf:RDF xml:base="http://dbpedia.org/ontology/"
    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    xmlns:owl="http://www.w3.org/2002/07/owl#"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
    xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
    xmlns="http://dbpedia.org/ontology/">

    <owl:Class rdf:about="http://dbpedia.org/ontology/BasketballLeague">
        <rdfs:label xml:lang="en">basketball league</rdfs:label>
        <rdfs:comment xml:lang="en">
          a group of sports teams that compete against each other
          in Basketball
        </rdfs:comment>
    </owl:Class>

</rdf:RDF>

我想找到所有owl:Class 标签,然后提取其中所有rdfs:label 实例的值。我正在使用以下代码:

tree = ET.parse("filename")
root = tree.getroot()
root.findall('owl:Class')

由于命名空间,我收到以下错误。

SyntaxError: prefix 'owl' not found in prefix map

我尝试阅读http://effbot.org/zone/element-namespaces.htm 的文档,但由于上述 XML 有多个嵌套命名空间,我仍然无法正常工作。

请告诉我如何更改代码以找到所有owl:Class 标签。

【问题讨论】:

标签: python xml xml-parsing xml-namespaces elementtree


【解决方案1】:

您需要为.find()findall()iterfind() 方法提供一个明确的命名空间字典:

namespaces = {'owl': 'http://www.w3.org/2002/07/owl#'} # add more as needed

root.findall('owl:Class', namespaces)

前缀在您传入的namespaces 参数中查找。这意味着您可以使用任何您喜欢的命名空间前缀; API 拆分了 owl: 部分,在 namespaces 字典中查找相应的命名空间 URL,然后将搜索更改为查找 XPath 表达式 {http://www.w3.org/2002/07/owl}Class。当然,您也可以自己使用相同的语法:

root.findall('{http://www.w3.org/2002/07/owl#}Class')

另请参阅 ElementTree 文档的 Parsing XML with Namespaces section

如果你能切换到lxml library的话就更好了;该库支持相同的 ElementTree API,但在元素的 .nsmap 属性中为您收集命名空间,并且通常具有出色的命名空间支持。

【讨论】:

  • 谢谢。知道如何直接从 XML 中获取命名空间,而无需对其进行硬编码吗?或者我怎么能忽略它?我试过 findall('{*}Class') 但它在我的情况下不起作用。
  • 您必须自己扫描树中的xmlns 属性;如答案所述,lxml 为您执行此操作,xml.etree.ElementTree 模块没有。但是,如果您尝试匹配特定的(已经硬编码的)元素,那么您也在尝试匹配特定命名空间中的特定元素。与元素名称一样,该名称空间不会在文档之间更改。您也可以使用元素名称对其进行硬编码。
  • @Jon: register_namespace 只影响序列化,不影响搜索。
  • 可能有用的小补充:当使用cElementTree 代替ElementTree 时,findall 不会将命名空间作为关键字参数,而是简单地作为普通参数,即使用@987654343 @.
  • @Bludwarf:文档确实提到了它(现在,如果不是你写的时候),但你必须仔细阅读它们。请参阅Parsing XML with Namespaces 部分:有一个示例对比了findall 的使用,没有然后与namespace 参数,但在Element object 部分中没有提到该参数作为方法方法的参数之一。
【解决方案2】:

以下是如何使用 lxml 执行此操作,而无需对名称空间进行硬编码或扫描它们的文本(正如 Martijn Pieters 所述):

from lxml import etree
tree = etree.parse("filename")
root = tree.getroot()
root.findall('owl:Class', root.nsmap)

更新

5 年后,我仍然遇到这个问题的变体。正如我在上面展示的那样,lxml 有帮助,但并非在所有情况下都如此。评论者在合并文档时可能对这种技术有一个有效的观点,但我认为大多数人在简单地搜索文档时遇到困难。

这是另一个案例以及我是如何处理的:

<?xml version="1.0" ?><Tag1 xmlns="http://www.mynamespace.com/prefix">
<Tag2>content</Tag2></Tag1>

xmlns 不带前缀意味着不带前缀的标签获得这个默认命名空间。这意味着当您搜索 Tag2 时,您需要包含命名空间才能找到它。但是,lxml 创建了一个以 None 为键的 nsmap 条目,我找不到搜索它的方法。所以,我像这样创建了一个新的命名空间字典

namespaces = {}
# response uses a default namespace, and tags don't mention it
# create a new ns map using an identifier of our choice
for k,v in root.nsmap.iteritems():
    if not k:
        namespaces['myprefix'] = v
e = root.find('myprefix:Tag2', namespaces)

【讨论】:

  • 完整的命名空间 URL 你应该硬编码的命名空间标识符。本地前缀 (owl) 可以因文件而异。因此,按照这个答案的建议去做是一个非常糟糕的主意。
  • @MattiVirkkunen 如果 owl 定义可以在文件之间更改,我们不应该使用每个文件中定义的定义而不是硬编码吗?
  • @LoïcFaure-Lacroix:通常 XML 库会让您将这部分抽象出来。您甚至不需要知道或关心文件本身使用的前缀,您只需定义自己的前缀用于解析或使用完整的命名空间名称。
  • 这个答案帮助我至少能够使用查找功能。无需创建自己的前缀。我刚刚做了 key = list(root.nsmap.keys())[0] 然后将密钥添加为前缀:root.find(f'{key}:Tag2', root.nsmap)
【解决方案3】:

注意:这是一个对 Python 的 ElementTree 标准库有用的答案,无需使用硬编码的命名空间。

要从 XML 数据中提取命名空间的前缀和 URI,您可以使用 ElementTree.iterparse 函数,仅解析命名空间启动事件 (start-ns):

>>> from io import StringIO
>>> from xml.etree import ElementTree
>>> my_schema = u'''<rdf:RDF xml:base="http://dbpedia.org/ontology/"
...     xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
...     xmlns:owl="http://www.w3.org/2002/07/owl#"
...     xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
...     xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
...     xmlns="http://dbpedia.org/ontology/">
... 
...     <owl:Class rdf:about="http://dbpedia.org/ontology/BasketballLeague">
...         <rdfs:label xml:lang="en">basketball league</rdfs:label>
...         <rdfs:comment xml:lang="en">
...           a group of sports teams that compete against each other
...           in Basketball
...         </rdfs:comment>
...     </owl:Class>
... 
... </rdf:RDF>'''
>>> my_namespaces = dict([
...     node for _, node in ElementTree.iterparse(
...         StringIO(my_schema), events=['start-ns']
...     )
... ])
>>> from pprint import pprint
>>> pprint(my_namespaces)
{'': 'http://dbpedia.org/ontology/',
 'owl': 'http://www.w3.org/2002/07/owl#',
 'rdf': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
 'rdfs': 'http://www.w3.org/2000/01/rdf-schema#',
 'xsd': 'http://www.w3.org/2001/XMLSchema#'}

然后字典可以作为参数传递给搜索函数:

root.findall('owl:Class', my_namespaces)

【讨论】:

  • 这对于我们这些无法访问 lxml 并且不想硬编码命名空间的人很有用。
  • 我收到了错误:ValueError: write to closed 这行 filemy_namespaces = dict([node for _, node in ET.iterparse(StringIO(my_schema), events=['start-ns'])])。有什么想法想错了吗?
  • 可能该错误与拒绝 ASCII 字符串的类 io.StringIO 有关。我已经用 Python3 测试了我的食谱。将 unicode 字符串前缀 'u' 添加到示例字符串中,它也适用于 Python 2 (2.7)。
  • 你也可以使用字典理解来代替dict([...])
  • 这正是我想要的!谢谢!
【解决方案4】:

我一直在使用与此类似的代码,并发现它总是值得阅读文档...像往常一样!

findall() 只会找到 当前标签的直接子元素。所以,并不是全部。

尝试让您的代码使用以下内容可能是值得的,特别是如果您正在处理大而复杂的 xml 文件,以便还包含子子元素(等)。 如果你自己知道元素在你的 xml 中的位置,那么我想它会没事的!只是觉得这值得记住。

root.iter()

参考:https://docs.python.org/3/library/xml.etree.elementtree.html#finding-interesting-elements “Element.findall() 仅查找带有标签且是当前元素的直接子元素的元素。Element.find() 查找具有特定标签的第一个子元素,并且 Element.text 访问元素的文本内容。Element.get()访问元素的属性:"

【讨论】:

  • ElementTree 文档有点不清楚且容易误解,恕我直言。 可以获得所有后代的。代替elem.findall("X"),使用elem.findall(".//X")
【解决方案5】:

以命名空间格式获取命名空间,例如{myNameSpace},您可以执行以下操作:

root = tree.getroot()
ns = re.match(r'{.*}', root.tag).group(0)

这样,您可以稍后在代码中使用它来查找节点,例如使用字符串插值 (Python 3)。

link = root.find(f"{ns}link")

【讨论】:

    【解决方案6】:

    我的解决方案基于@Martijn Pieters 的评论:

    register_namespace 只影响序列化,不影响搜索。

    所以这里的诀窍是使用不同的字典进行序列化和搜索。

    namespaces = {
        '': 'http://www.example.com/default-schema',
        'spec': 'http://www.example.com/specialized-schema',
    }
    

    现在,注册所有用于解析和写入的命名空间:

    for name, value in namespaces.iteritems():
        ET.register_namespace(name, value)
    

    对于搜索(find()findall()iterfind()),我们需要一个非空前缀。将修改后的字典传递给这些函数(这里我修改了原始字典,但必须在命名空间注册后才能进行)。

    self.namespaces['default'] = self.namespaces['']
    

    现在,find() 系列的函数可以与 default 前缀一起使用:

    print root.find('default:myelem', namespaces)
    

    但是

    tree.write(destination)
    

    不对默认命名空间中的元素使用任何前缀。

    【讨论】:

      【解决方案7】:

      这基本上是 Davide Brunato 的回答,但是我发现他的回答存在严重问题,默认命名空间是空字符串,至少在我的 python 3.6 安装中是这样。我从他的代码中提炼出来的对我有用的函数如下:

      from io import StringIO
      from xml.etree import ElementTree
      def get_namespaces(xml_string):
          namespaces = dict([
                  node for _, node in ElementTree.iterparse(
                      StringIO(xml_string), events=['start-ns']
                  )
          ])
          namespaces["ns0"] = namespaces[""]
          return namespaces
      

      其中ns0 只是空命名空间的占位符,您可以将其替换为您喜欢的任何随机字符串。

      如果我这样做:

      my_namespaces = get_namespaces(my_schema)
      root.findall('ns0:SomeTagWithDefaultNamespace', my_namespaces)
      

      它还为使用默认命名空间的标签生成正确答案。

      【讨论】:

        猜你喜欢
        • 2019-06-23
        • 2015-03-06
        • 2014-09-24
        • 2019-03-26
        相关资源
        最近更新 更多