使用命名空间的Python XML XPath查询标签和属性

2 投票
3 回答
724 浏览
提问于 2025-04-17 22:43

我觉得我可能做错了什么,每个我在StackOverflow上看到的例子似乎都表明这样做是可以的。

我正在尝试使用lxml的etree库,通过XPath搜索来解析一个garmin的tcx文件:

<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<TrainingCenterDatabase xmlns="http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2 http://www.garmin.com/xmlschemas/TrainingCenterDatabasev2.xsd">

  <Workouts>
    <Workout Sport="Biking">
      <Name>3P2 WK16 - 3</Name>
      <Step xsi:type="Step_t">
        <StepId>1</StepId>
        <Name>[MP19]6:28-6:38</Name>
        <Duration xsi:type="Distance_t">
          <Meters>13000</Meters>
        </Duration>
        <Intensity>Active</Intensity>
        <Target xsi:type="Speed_t">
          <SpeedZone xsi:type="PredefinedSpeedZone_t">
            <Number>2</Number>
          </SpeedZone>
        </Target>
      </Step>
     ......
     </Workout>
</Workouts>
</TrainingCenterDatabase>

我想只返回类型为PredefinedSpeedZone_t的SpeedZone元素。我原以为可以这样做:

root = ET.parse(open('file.tcx'))
xsi = {'xsi': 'http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2'}
    for speed_zone in root.xpath(".//xsi:SpeedZone[@xsi:type='PredefinedSpeedZone_t']", namespaces=xsi):
        print speed_zone

但似乎并不是这样。我尝试了很多组合,添加或删除命名空间,但都没有成功。如果我去掉属性搜索,直接用".//xsi:SpeedZone",那么就能返回:

<Element {http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2}SpeedZone at 0x2595188>

这正是我所期待的结果。

我想我可以在for循环里面处理这个,但我觉得应该可以在一行内完成!

3 个回答

1

解决这个问题的一种方法是避免指定属性名称,而是使用 *

.//xsi:SpeedZone[@*='PredefinedSpeedZone_t']

另一种选择(虽然没有前一种那么好)是实际获取所有的 SpeedZone 标签,然后在循环中检查属性值:

attribute_name = '{%s}type' % root.nsmap['xsi']
for speed_zone in root.xpath(".//xsi:SpeedZone", namespaces=xsi):
    if speed_zone.attrib.get(attribute_name) == 'PredefinedSpeedZone_t':
        print speed_zone

希望这对你有帮助。

1

如果其他方法都不行,你仍然可以使用

".//xsi:SpeedZone[@*[name() = 'xsi:type' and . = 'PredefinedSpeedZone_t']]"

使用 name() 这个方法虽然没有直接使用带命名空间的属性那么方便,但至少 etree 能够理解这个方法。

4

我来得有点晚,但我觉得其他的回答有点让人困惑。

在问题中的Python代码以及另外两个回答中,xsi这个前缀是和http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2这个网址绑定的。但是在包含Garmin数据的XML文档中,xsi是和http://www.w3.org/2001/XMLSchema-instance这个网址绑定的。

因为这里涉及到两个命名空间,我觉得下面的代码能更清楚地说明发生了什么。与tcd前缀相关的命名空间是默认命名空间。

from lxml import etree

NSMAP = {"tcd": "http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2",
         "xsi": "http://www.w3.org/2001/XMLSchema-instance"}

root = etree.parse('file.tcx')

for speed_zone in root.xpath(".//tcd:SpeedZone[@xsi:type='PredefinedSpeedZone_t']",
                             namespaces=NSMAP):
    print speed_zone

输出:

<Element {http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2}SpeedZone at 0x25b7e18>

撰写回答