通过“ElementTree”在 Python 中使用命名空间解析 XML

2024-11-27 10:43:00
admin
原创
15
摘要:问题描述:我有以下 XML,我想使用 Python 进行解析ElementTree:<rdf:RDF xml:base="http://dbpedia.org/ontology/" xmlns:rdf="http://www.w3.org/1999/02/22-rd...

问题描述:

我有以下 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标签。


解决方案 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:部分,在字典中查找相应的命名空间 URL namespaces,然后更改搜索以查找 XPath 表达式{http://www.w3.org/2002/07/owl}Class。当然,您也可以自己使用相同的语法:

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

另请参阅ElementTree 文档的使用命名空间解析 XML部分。

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

解决方案 2:

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

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

更新

五年后,我仍然遇到这个问题的变种。如我上面所示,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)

解决方案 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)

解决方案 4:

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

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

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

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

解决方案 5:

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

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

解决方案 6:

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

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

解决方案 7:

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

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

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

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

现在,注册所有命名空间以供解析和写入:

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

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

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

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

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

tree.write(destination)

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

解决方案 8:

一个稍微长一点的替代方法是创建另一个ElementNS继承ET.Element并包含命名空间的类,然后为这个类创建一个构造函数并将其传递给解析器:

import xml.etree.ElementTree as ET


def parse_namespaces(source):
    return dict(node for _e, node in ET.iterparse(source, events=['start-ns']))


def create_element_factory(namespaces):
    def element_factory(tag, attrib):
        el = ElementNS(tag, attrib)
        el.namespaces = namespaces
        return el
    return element_factory


class ElementNS(ET.Element):
    namespaces = None

    # Patch methods to include namespaces
    def find(self, path):
        return super().find(path, self.namespaces)

    def findtext(self, path, default=None):
        return super().findtext(path, default, self.namespaces)

    def findall(self, path):
        return super().findall(path, self.namespaces)

    def iterfind(self, path):
        return super().iterfind(path, self.namespaces)


def parse(source):
    # Set up parser with namespaced element factory
    namespaces = parse_namespaces(source)
    element_factory = create_element_factory(namespaces)
    tree_builder = ET.TreeBuilder(element_factory=element_factory)
    parser = ET.XMLParser(target=tree_builder)
    element_tree = ET.ElementTree()

    return element_tree.parse(source, parser=parser)

然后findall无需传递命名空间即可使用:

document = parse("filename")
document.findall("owl:Class")
相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   657  
  如何借鉴华为IPD体系优化企业研发?在当今竞争激烈的市场环境中,企业要想保持技术领先和产品竞争力,必须拥有一套高效且严谨的研发管理体系。华为作为全球领先的ICT解决方案提供商,其集成产品开发(IPD, Integrated Product Development)体系与质量管理体系(如ISO 9000系列)的融合实践,...
IPD项目管理   15  
  IPD流程图的7种经典绘制方法详解在产品开发领域,集成产品开发(Integrated Product Development,简称IPD)流程被广泛应用,以提高产品开发的效率和质量。IPD流程图作为这一流程的可视化工具,其绘制方法至关重要。本文将详细介绍七种经典的IPD流程图绘制方法,帮助项目管理人员和团队更好地理解和...
IPD研发管理体系   18  
  IPD流程:企业创新管理的核心引擎在当今快速变化的市场环境中,企业要想持续保持竞争力,就必须不断进行创新。而IPD(Integrated Product Development,集成产品开发)流程作为一种先进的产品开发管理模式,正逐渐成为众多企业提升创新能力、加速产品上市速度、降低开发成本的重要选择。本文将深入探讨IP...
IPD管理   18  
  IPD流程与传统产品开发流程的概述在产品开发领域,企业不断寻求高效、系统的管理方法以确保产品能够顺利从概念转化为市场成功的产品。集成产品开发(Integrated Product Development,简称IPD)流程与传统产品开发流程是两种截然不同的管理理念和方法。传统产品开发流程往往以职能部门为核心,各部门按顺序...
IPD流程中PDCP是什么意思   16  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用