使用平面结构中的xpath导航到节点

时间:2009-03-05 11:16:28

标签: xml xslt xpath

我有一个扁平结构的xml文件。我们不控制这个xml文件的格式,只需要处理它。我已经重命名了这些字段,因为它们具有高度特定的域名,并没有对问题产生任何影响。

<attribute name="Title">Book A</attribute>
<attribute name="Code">1</attribute>
<attribute name="Author">
   <value>James Berry</value>
   <value>John Smith</value>
</attribute>
<attribute name="Title">Book B</attribute>
<attribute name="Code">2</attribute>
<attribute name="Title">Book C</attribute>
<attribute name="Code">3</attribute>
<attribute name="Author">
    <value>James Berry</value>
</attribute>

需要注意的关键事项:文件不是特别分层的。书籍由name ='Title'的属性元素的出现界定。但name ='Author'属性节点是可选的。

是否有一个简单的xpath语句可以用来查找“n”的作者?很容易识别书籍'n'的标题,但作者的价值是可选的。你不能只考虑下面的作者,因为在第2册的情况下,这将给作者提供第3册。

我已经写了一个状态机来解析它作为一系列元素,但我不禁想到会有一种直接获得我想要的结果的方法。

5 个答案:

答案 0 :(得分:3)

我们希望@name'Partner'的“attribute”元素跟随@name'Title'的“attribute”元素,其值为'Book n',而没有@name'的任何其他“attribute”元素标题'在他们之间(因为如果有,那么作者撰写了其他一些书)。

不同地说,这意味着我们想要一个作者, 第一个 前面的标题(它所属的那个)是我们正在寻找的那个:

//attribute[@name='Author']
[preceding-sibling::attribute[@name='Title'][1][contains(.,'Book N')]]

N = C =&gt;找到<attribute name="Author"><value>James Berry</value></attribute>

N = B =&gt;找不到任何东西

使用XSLT 2.0中提供的键和/或分组功能可以使这更容易(如果文件很大,也会更快)。

(SO代码解析器似乎认为'//'代表'评论',但在XPath中它不是!!!叹息。)

答案 1 :(得分:2)

好吧,我使用Elementtree从上面的XML中提取数据。 我已将此XML保存在名为foo.xml

的文件中
from xml.etree.ElementTree import fromstring

def extract_data():
    """Returns list of dict of book and
    its authors."""

    f = open('foo.xml', 'r+')
    xml = f.read()
    elem = fromstring(xml)
    attribute_list = elem.findall('attribute')
    dic = {}
    lst = []

    for attribute in attribute_list:
        if attribute.attrib['name'] == 'Title':
            key = attribute.text
        if attribute.attrib['name'] == 'Author':
            for v in attribute.findall('value'):
                lst.append(v.text)
            value = lst
            lst = []
            dic[key] = value
    return dic

当你运行这个功能时,你会得到这个:

{'Book A': ['James Berry', 'John Smith'], 'Book C': ['James Berry']}

我希望这就是你要找的东西。如果没有,那么只需指定一点。 :)

答案 2 :(得分:1)

正如 bambax 在他的回答中所提到的,使用XSLT密钥的解决方案更有效,特别是对于大型XML文档:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes"/>
 <!--                                            -->
 <xsl:key name="kAuthByTitle" 
  match="attribute[@name='Author']"
  use="preceding-sibling::attribute[@name='Title'][1]"/>
 <!--                                            -->
    <xsl:template match="/">
      Book C Author:
      <xsl:copy-of select=
         "key('kAuthByTitle', 'Book C')"/>
  <!--                                            -->
         ====================
      Book B Author:
      <xsl:copy-of select=
         "key('kAuthByTitle', 'Book B')"/>
    </xsl:template>
</xsl:stylesheet>

对此XML文档应用上述转换时:

<t>
    <attribute name="Title">Book A</attribute>
    <attribute name="Code">1</attribute>
    <attribute name="Author">
        <value>James Berry</value>
        <value>John Smith</value>
    </attribute>
    <attribute name="Title">Book B</attribute>
    <attribute name="Code">2</attribute>
    <attribute name="Title">Book C</attribute>
    <attribute name="Code">3</attribute>
    <attribute name="Author">
        <value>James Berry</value>
    </attribute>
</t>

产生正确的输出:

  Book C Author:
  <attribute name="Author">
    <value>James Berry</value>
</attribute>

     ====================
  Book B Author:

请注意,应尽可能避免使用"//" XPath缩写,因为它通常会导致在每次评估XPath表达式时扫描整个XML文档。

答案 3 :(得分:0)

我不确定你真的想去那里:我发现最简单的是从作者那里得到以前的标题,然后检查下面的第一作者或标题确实是一个标题。难看!

/books/attribute[@name="Author"]
  [preceding-sibling::attribute[@name="Title" and string()="Book B"]
                               [following-sibling::attribute[ @name="Author" 
                                                             or @name="Title"
                                                            ]
                                 [1]
                                 [@name="Author"]
                               ]
  ][1]

(我添加了 books 标签来包装文件)。

我使用xml_grep2使用libxml2 BTW进行了测试,但仅使用了您提供的示例数据,因此欢迎进行更多测试。

答案 4 :(得分:0)

选择所有标题并应用模板

<xsl:template match="/">
  <xsl:apply-templates select="//attribute[@name='Title']"/>
</xsl:template>

在模板输出标题中,检查是否存在下一个标题。如果没有,请输出以下作者。如果确实存在,请检查以下书籍的以下作者节点是否与当前书籍的以下作者节点相同。如果是,则表示当前的书没有作者:

<xsl:template match="*">
   <book>
     <title><xsl:value-of select="."/></title> 
   <author>
   <xsl:if test="not(following::attribute[@name='Title']) or following::attribute[@name='Author'] != following::attribute[@name='Title']/following::attribute[@name='Author']">
   <xsl:value-of select="following::attribute[@name='Author']"/>
   </xsl:if>
   </author>
   </book>
</xsl:template>