如何获取命名空间元素的属性
How to get an attribute of an Element that is namespaced
我正在解析我每天从供应商那里收到的 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()
但我似乎没有获得任何有用的东西。
有什么想法吗?
我很确定这是一种非常糟糕的非 PYTHONIC 非理想方式;似乎必须有更好的方法......但我发现我可以这样做:
SS_REAL = "{%s}" % root.nsmap.get('ss')
然后我可以做:
my_sheet.get( SS_REAL + "NAME" )
它让我得到我想要的..但这不可能是正确的方法..
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
我的解决方案:
#!/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!
我正在解析我每天从供应商那里收到的 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()
但我似乎没有获得任何有用的东西。
有什么想法吗?
我很确定这是一种非常糟糕的非 PYTHONIC 非理想方式;似乎必须有更好的方法......但我发现我可以这样做:
SS_REAL = "{%s}" % root.nsmap.get('ss')
然后我可以做:
my_sheet.get( SS_REAL + "NAME" )
它让我得到我想要的..但这不可能是正确的方法..
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
我的解决方案:
#!/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!