XPath用于获取具有匹配子节点的节点和用于其他子节点的最高值

时间:2017-09-22 16:31:23

标签: xml xslt xpath xslt-1.0

我需要XPath 1.0中的帮助来过滤下面的XML,这样我才会得到具有不同“id”且具有最高'valid / date'的条形码:

<foo name="fooName">
    <bar name="barName">
        <id>1111</id>
        <validity>
            <date>20170920</date>
        </validity>
    </bar>
    <bar name="barName">
        <id>1111</id>
        <validity>
            <date>20170922</date>
        </validity>
    </bar>
    <bar name="barName">
        <id>1111</id>
        <validity>
            <date>20170921</date>
        </validity>
    </bar>
    <bar name="barName">
        <id>2222</id>
        <validity>
            <date>20170921</date>
        </validity>
    </bar>
    <bar name="barName">
        <id>2222</id>
        <validity>
            <date>20170923</date>
        </validity>
    </bar>
</foo>

我尝试了很多选项和研究,但无法弄清楚确切的解决方案。

过滤后的预期XML应如下所示:

<foo name="fooName">
    <bar name="barName">
        <id>1111</id>
        <validity>
            <date>20170922</date>
        </validity>
    </bar>
    <bar name="barName">
        <id>2222</id>
        <validity>
            <date>20170923</date>
        </validity>
    </bar>
</foo>

2 个答案:

答案 0 :(得分:1)

你应该阅读“Muenchian grouping”,michael.hor257k已经给你一个指针。 (网络搜索会发现很多其他人。)

Muenchian分组所做的是,如果没有它,原则上可以做得更快。在某些情况下,增加的速度使“原则上可能”和“在实践中可行”之间存在差异。但在某些情况下,解决这个问题的简单方法就足够了。

问题1:您只需在输出中为每个不同的“ID”设置一个“bar”元素。 (请注意,您的示例输出显示您的描述错误:您不希望“只有具有唯一'id'的栏”,因为ID为1111或2222的条都没有输入中的唯一ID。您需要单个输出对于'id'的每个不同值。不一样。)

解决这个问题的一种方法:为'bar'写两个模板,一个为第一次出现给定'id'而触发(实际上是找到最大有效性/日期值的工作),另一个是这会导致以后出现的'bar'与'id'被忽略。

<xsl:template match="bar" priority="10.0">
   <!--* find the highest validity/date with this ID here,
       * do what needs to be done. *-->
   ...
</xsl:template>
<xsl:template match="bar[id = preceding-sibling::bar/id]"
              priority="20.0"/>

我已经给出明确的优先事项来警告未来 - 我在这里尝试一些聪明的东西(并防止未来 - 我通过改变匹配模式以改变相对优先级来搞砸它)

另一种方法是在模板中选择/当'bar'。

<xsl:template match="bar">
  <xsl:variable name="id" select="string(id)"/>
  <xsl:choose>
    <xsl:when test="preceding::bar[id=$id]"/>
    <xsl:otherwise>
      <!--* this is the first of this ID, deal with this ID now *-->
      ...
    </
  </
</

第二种模式可以更容易地制定找到实际想要复制到输出的“条形”元素所需的逻辑。您希望不处理每个ID的第一个实例,而是处理具有最高有效性/日期值的实例:

<xsl:template match="bar">
  <xsl:variable name="id" select="string(id)"/>

  <xsl:choose>
    <!--* the behavior of comparisons here requires a little
        * bit of standing on our heads.  We want this 'bar' if
        * its validity/date value is greater than or equal to
        * all other such values for this ID.  So first we filter
        * out all cases where there is a higher validity/date value
        * on another 'bar' with this ID. *-->
    <xsl:when test="validity/date &lt; //bar[id=$id]/validity/date"/>

    <!--* The 'otherwise' case handles situations where this
        * is the only 'bar' with this ID, or where there is no
        * higher validity/date value. *-->
    <xsl:otherwise>
      <xsl:copy-of select="."/>
    </
  </
</

如果这是在“可管理”输入上运行的一次性或运行很少的样式表,这可能足够快,并且这种模式可能比Muenchian分组更容易理解,除非您已经非常了解键和他们的用途。如果它太慢,Muenchian分组将告诉你什么通常是一个更快的方式来完成同样的事情。

[注意:答案的初始版本有一个maxdate变量

<xsl:variable name="maxdate" 
              select="max(//bar[id=$id]/validity/date)"/>

并简单地将当前值与它进行比较:

<xsl:when test="validity/date = $maxdate">
  <xsl:copy-of select="."/>
</

但XPath 1.0中唯一的聚合函数是count()和sum()。我会说“看看这在XSLT 2.0中有多容易?”但是如果你在2.0中,整个事情就像是

<xsl:sequence select="for $v in distinct-values(//bar/id)
    for $max in max(//bar[id=$v]/validity/date)
    return //bar[id=$v and validity/date = $max]"/>

并且max()函数在使事情变得如此简单方面起到了相对温和的作用。]

答案 1 :(得分:0)

正如所建议的那样,我想出了下面的xslt,似乎工作正常:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
    <xsl:key name="bars-by-id" match="foo/bar" use="id" />
    <xsl:template match="foo">
        <foo name="fooName">
            <xsl:for-each select="bar[count(. | key('bars-by-id', id)[1]) = 1]">
                <xsl:variable name="currentID" select="id" />
                <xsl:variable name="barsForID" select="key('bars-by-id', $currentID)"/>
                <xsl:copy-of select="$barsForID[not(../bar[id=$currentID]/validity/date > validity/date)]" />
            </xsl:for-each>
        </foo>
    </xsl:template>
</xsl:stylesheet>

感谢您的建议,这确实有所帮助。请随时纠正我。