179

我有以下要使用 Python 解析的 XML 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标签。

4

7 回答 7

241

你需要给.find(),findall()iterfind()方法一个明确的命名空间字典:

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

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

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

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

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

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

于 2013-02-13T12:18:22.470 回答
62

这是使用 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)
于 2014-11-07T18:22:52.877 回答
39

注意:这是一个对 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)
于 2016-05-24T09:09:56.317 回答
7

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

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() 访问元素的属性:"

于 2016-08-16T09:51:36.693 回答
7

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

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

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

link = root.find(f"{ns}link")
于 2018-10-01T12:25:59.707 回答
0

我的解决方案基于@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)

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

于 2019-05-30T11:00:02.053 回答
0

这基本上是 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

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

如果我这样做:

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

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

于 2021-04-07T16:13:53.000 回答