【问题标题】:How to get an attribute of an Element that is namespaced如何获取命名空间的元素的属性
【发布时间】:2015-09-12 20:07:47
【问题描述】:

我正在解析每天从供应商处收到的 XML 文档,它大量使用命名空间。我在这里将问题最小化为最小子集:

我需要解析一些元素,它们都是具有特定属性的元素的子元素。
我可以使用lxml.etree.Element.findall(TAG, root.nsmap) 来查找我需要检查其属性的候选节点。

然后,我尝试通过我知道它使用的名称检查每个Elements 的属性:这里具体是ss:Name。如果该属性的值是所需的值,我将深入研究Element(继续做其他事情)。

我该怎么做?

我解析的XML大致是

<FOO xmlns="SOME_REALLY_LONG_STRING"
 some gorp declaring a bunch of namespaces one of which is 
 xmlns:ss="THE_VERY_SAME_REALLY_LONG_STRING_AS_ROOT"
>
    <child_of_foo>
        ....
    </child_of_foo>
    ...
    <SomethingIWant ss:Name="bar" OTHER_ATTRIBS_I_DONT_CARE_ABOUT>
        ....
        <MoreThingsToLookAtLater>
            ....
        </MoreThingsToLookAtLater>
        ....
    </SomethingIWant>
    ...
</FOO>

我找到了我想要的第一个元素 SomethingIWant 就像这样(最终我想要它们,所以我确实找到了所有)

import lxml
from lxml import etree

tree = etree.parse(myfilename)
root = tree.getroot()
# i want just the first one for now
my_sheet = root.findall('ss:RecordSet', root.nsmap)[0]

现在我想从这个元素中获取ss:Name 属性来检查它,但我不确定如何?

我知道my_sheet.attrib 会显示原始 URI,后跟属性名称,但我不希望这样。我需要检查它是否具有特定命名空间属性的特定值。 (因为如果它是错误的,我可以完全跳过这个元素的进一步处理)。

我尝试使用lxml.etree.ElementTree.attrib.get(),但似乎没有得到任何有用的东西。

有什么想法吗?

【问题讨论】:

  • 您实际上是在使用lxml 库还是仅使用标准python 中的xml 解析器? etree 到底是什么,lxml.etree
  • 根据@har07 回答再次更新
  • 所以你想从之前选择的元素中获取命名空间中的属性,比如你的代码 sn-p 中的my_sheet。在这种情况下,我的更新与您更新的问题相关。
  • 如果您愿意,您甚至可以在一个 xpath 表达式中选择具有某个属性等于某个值的所有元素。类似于:root.xpath('//ss:RecordSet[@ss:Name="bar"]')

标签: python xml xml-parsing lxml


【解决方案1】:

lxml 相对于标准 python XML 解析器的优势之一是lxml 通过xpath() 方法完全支持 XPath 1.0 规范。所以我大部分时间都会使用xpath() 方法。您当前案例的工作示例:

from lxml import etree

xml = """<FOO xmlns="SOME_REALLY_LONG_STRING"
 xmlns:ss="THE_VERY_SAME_REALLY_LONG_STRING_AS_ROOT"
>
    <child_of_foo>
        ....
    </child_of_foo>
    ...
    <SomethingIWant ss:Name="bar">
        ....
    </SomethingIWant>
    ...
</FOO>"""

root = etree.fromstring(xml)
ns = {'ss': 'THE_VERY_SAME_REALLY_LONG_STRING_AS_ROOT'}

# i want just the first one for now
result = root.xpath('//@ss:Name', namespaces=ns)[0]
print(result)

输出:

bar

更新:

修改示例演示如何从当前 element 获取命名空间中的属性:

ns = {'ss': 'THE_VERY_SAME_REALLY_LONG_STRING_AS_ROOT', 'd': 'SOME_REALLY_LONG_STRING'}

element = root.xpath('//d:SomethingIWant', namespaces=ns)[0]
print(etree.tostring(element))

attribute = element.xpath('@ss:Name', namespaces=ns)[0]
print(attribute)

输出:

<SomethingIWant xmlns="SOME_REALLY_LONG_STRING" xmlns:ss="THE_VERY_SAME_REALLY_LONG_STRING_AS_ROOT" ss:Name="bar">
        ....
    </SomethingIWant>
    ...

bar

【讨论】:

  • 非常感谢!这看起来非常正确,但是有一个微妙之处我没有足够清楚地说明我正在更新问题.. 如果result 是我正在寻找的,我将需要对该元素的整体引用因为这意味着它有我必须处理的孩子。更新问题以强调这一点;我的错。
  • 更新了我的答案以显示如何使用xpath() 来获取属性及其父元素。希望我能正确理解您的要求
  • 唯一的问题是 d 的 URI 实际上是 ss 的 URI,因此他们从未真正使用 d ;但我认为我实际上可以使用ss 来处理那个......无论哪种方式,这只会简化你给我的东西;这是完美的!再次感谢
  • 当我尝试使用 nsmap = root.nsmap 并传递 namespaces=nsmap 时,我总是得到一个错误,即 XPath 不支持空的命名空间前缀。因为有一个指向键 None 的条目与键“ss”的值相同
【解决方案2】:

我很确定这是一种非常糟糕的非 Python 非理想方式;似乎必须有更好的方法......但我发现我可以这样做:

SS_REAL = "{%s}" % root.nsmap.get('ss')

然后我可以这样做: my_sheet.get( SS_REAL + "NAME" )

它让我得到了我想要的……但这不可能是正确的方法……

【讨论】:

  • 这是一种肮脏的做法,但为了让节目继续运行,这是有帮助的。
  • 我知道,因此我的第一行并且不接受我自己的答案作为答案;这是为了展示我在提出问题后发现的一种方式
【解决方案3】:

我的解决方案:

https://pastebin.com/F5HAw6zQ

#!/usr/bin/python
# -*- coding: utf-8 -*-

from sys import argv
import xml.etree.ElementTree as ET

NS = 'x' # default namespace key # (any string is OK)

class XMLParser(object):
    def __init__(self):
        self.ns = {}     # namespace dict
        self.root = None # XML's root element

    # extracts the namespace (usually from the root element)
    def get_namespace(self, tag):
        return tag.split('}')[0][1:]

    # loads the XML file (here: from string)
    def load_xml(self, xmlstring):
        root = ET.fromstring(xmlstring)
        self.root = root
        self.ns[NS] = self.get_namespace(root.tag)
        return True

    # transforms XPath without namespaces to XPath with namespace
    # AND detects if last element is an attribute
    def ns_xpath(self, xpath):
        tags = xpath.split('/')
        if tags[-1].startswith('@'):
            attrib = tags.pop()[1:]
        else:
            attrib = None
        nsxpath = '/'.join(['%s:%s' % (NS, tag) for tag in tags])
        return nsxpath, attrib

    # `find` and `findall` method in one place honoring attributes in XPath
    def xfind(self, xpath, e=None, findall=False):
        if not e:
            e = self.root
        if not findall:
            f = e.find
        else:
            f = e.findall
        nsxpath, attrib = self.ns_xpath(xpath)
        e = f(nsxpath, self.ns)
        if attrib:
            return e.get(attrib)
        return e

def main(xmlstring):
    p = XMLParser()
    p.load_xml(xmlstring)
    xpaths = {
        'Element a:': 'a',
        'Element b:': 'a/b',
        'Attribute c:': 'a/b/@c'
        }
    for key, xpath in xpaths.items():
        print key, xpath, p.xfind(xpath)

if __name__ == "__main__":
    xmlstring = """<root xmlns="http://www.example.com">
        <a>
            <b c="Hello, world!">
            </b>
        </a>
    </root>"""
    main(xmlstring)

结果:

Element a: a <Element '{http://www.example.com}a' at 0x2bbcb30>
Element b: a/b <Element '{http://www.example.com}b' at 0x2bbcb70>
Attribute c: a/b/@c Hello, world!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-02-12
    • 2023-03-09
    • 2014-09-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多