使用带有ns的标记和属性的python xml xpath查询

时间:2014-03-17 20:18:38

标签: python xml xpath xml-parsing lxml

我必须在这里做一些内在错误的事情,我所见过的每一个例子以及在SO上搜索似乎都表明这会起作用。

我尝试使用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 个答案:

答案 0 :(得分:3)

我有点迟了,但其他答案让我感到困惑。

在问题的Python代码和其他两个答案中,xsi前缀绑定到http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2 URI。但是在包含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>

答案 1 :(得分: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

希望有所帮助。

答案 2 :(得分:1)

如果其他所有方法都失败了,你仍然可以使用

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

使用name()并不像直接寻址命名空间属性那样好,但至少etree能理解它。