【问题标题】:XSLT: How to find the count of unique children of a node?XSLT:如何找到节点的唯一子节点的计数?
【发布时间】:2015-11-09 13:35:17
【问题描述】:

我的 XML 如下所示:

<foo>
    <bar name="a">
        <baz name="xyz">
            <time>2</time>
            <date>3</date>
        </baz>
    </bar>
    <bar name="b">
        <baz name="xyz">
            <time>2</time>
            <date>3</date>
        </baz>
    </bar>
    <bar name="c">
        <baz name="xyz">
            <time>2</time>
            <date>3</date>
        </baz>
    </bar>
</foo>

我正在编写一个需要像这样运行的 XSL:如果所有 baz 子代都相同,则 doSomething 否则 doSomethingElse。我当前的节点是foo

我是 XSLT 的新手,我知道 XSL 中的条件。到目前为止,它看起来像这样:

<xsl:template match="foo">   
<xsl:choose>
    <xsl:when test="[My condition]"> 
        doSomething()
    </xsl:when>
    <xsl:otherwise>
        doSomethingElse()
    </xsl:otherwise>
</xsl:choose>
</xsl:template>

在当前示例中,它应该是doSomething(),因为所有baz 元素都是相同的。

如果我找出唯一baz 元素的数量,我可以测试它是否等于一。如果是,那我就doSomething() 否则doSomethingElse()

我应该如何实现这个? MyCondition 应该是什么?

PS:我的 XSL 版本是 1.0

【问题讨论】:

  • 你有能力使用XSLT2吗?在这种情况下,它会产生的不同。
  • 那是开放的“相同”还是封闭的“相同”?开放意味着:没有说明 &lt;baz&gt; 可能包含的内容,但无论如何它们都必须包含相同的内容。关闭意味着:&lt;baz&gt; 恰好有 N 个我关心的属性,并且它们在所有属性中都必须相同。前者难度很大,后者难度适中,取决于数N。
  • @Flynn1179,是的,我想我可以使用 2.0。我使用了 W3Schools 的一些代码,他们使用的是 1.0。但我尝试将
  • @Tomalak 所有baz 元素将具有相同的标签,但可能具有不同的值。
  • 我想说,不要假设。如有疑问,您可以使用&lt;xsl:value-of select="system-property('xsl:version')" /&gt; 获取您可用的 XSL 版本。

标签: xml xslt xslt-1.0


【解决方案1】:

如果所有 baz 子代都相同,则 doSomething 否则 doSomethingElse。我当前的节点是foo

这很令人困惑,因为:

  • baz 不是foo 的孩子;
  • 您的标题显示“找出唯一孩子的数量” - 但不是 必须找到它才能知道它们是否相同。

尝试类似:

<xsl:template match="foo">
    <xsl:variable name="first-baz" select="(bar/baz)[1]" />
    <xsl:choose>
        <xsl:when test="bar/baz[date!=$first-baz/date or time!=$first-baz/time]">
            <!-- THEY ARE NOT ALL SAME -->
        </xsl:when>
        <xsl:otherwise>
            <!-- THEY ARE ALL SAME -->
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

请注意,这假定每个baz 都有一个date 和一个time。否则你需要测试not(date=$first-baz/date)等。

另请参阅: http://www.jenitennison.com/xslt/grouping/muenchian.html


补充:

现在,假设所有 bar/baz 元素具有相同的标签(不是 必须是datetime,但是说abc),会是什么 该案例的测试属性?

这使它变得更加复杂。你仍然可以构造一个密钥:

<xsl:key name="first-baz" match="foo/bar[1]/baz[1]/*" use="name()" />

然后进行测试:

<xsl:when test="bar/baz/*[. != key('first-baz', name())]">

如果存在baz 的任何子节点,其字符串值不同于作为第一个baz 子节点的同名节点,则返回true。

请注意,此处定义的键是文档范围的。如果要将测试限制在当前的 foo 祖先,则必须在键中包含其 id。

【讨论】:

  • 您不需要遍历所有baz 孩子来检查每个孩子是否等于/不等于第一个?
  • 不,因为test 属性中的XPath 只检查是否存在至少一个不相等的。如果你找到了,就不需要再“循环”了。
  • @Core_Dumped XSLT 处理器执行所有必要的循环 - 您只需要指定条件。
  • 我明白了!很有意思!感谢您的回答和澄清。
  • @michael.hor257k 现在,假设所有 bar/baz 元素具有相同的标签(不一定是 datetime,而是说 abc),这种情况下的测试属性是什么?我可以以某种方式循环每个bar/baz 的所有孩子并在不知道实际标签的情况下进行此值检查吗?
【解决方案2】:

如果您想以通用方式执行此操作,那么您实际上是在将 XSLT 1.0 的功能扩展到其设计限制之外。但这是可以完成的。

编写一个名为 deep-equal 的命名模板,它接受两个元素作为其参数,当且仅当它们不相等时才返回一个包含字符“F”的字符串(出于您的目的)。

它可能看起来像这样:

<xsl:template name="deep-equal">
  <xsl:param name="a"/>
  <xsl:param name="b"/>
  <xsl:choose>
    <xsl:when test="local-name(a) != local-name(b)">F</xsl:when>
    <xsl:when test="namespace-uri(a) != namespace-uri(b)">F</xsl:when>
    <xsl:when test="normalize-space(a) != normalize-space(b)">F</xsl:when>
    <xsl:when test="count(a/*) != count(b/*)">F</xsl:when>
    <xsl:otherwise>
      <xsl:variable name="diffs">
        <xsl:for-each select="*">
          <xsl:variable name="position" select="position()"/>
          <xsl:call-template name="deep-equal">
            <xsl:with-param name="a" select="."/>
            <xsl:with-param name="b" select="$b/*[$position]"/>
          </xsl:call-template>
        </xsl:for-each>
      </xsl:variable>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

将此应用于所有相关的元素对:

    <xsl:variable name="comparison">
      <xsl:for-each select="baz[position() &gt;= 2]">
       <xsl:call-template name="deep-equal">
         <xsl:with-param name="a" select="../baz[1]"/>
         <xsl:with-param name="b" select="."/>
       </xsl:call-template>
      </xsl:for-each>
    </xsl:variable>

测试结果是否包含“F”:

<xsl:if test="contains($comparison, 'F')">...</xsl:if>

【讨论】:

  • 我在另一个问题之后回到了这个问题。这真的不行,不是吗?我的意思是,如果它未能通过 4 次测试,它会打开一个变量并向其中写入一些内容——但对于任何实际目的,该变量仍然超出范围。即使我删除变量,并在参数引用中添加缺少的 $,并将 baz[position() &amp;gt;= 2] 更改为 bar[position() &amp;gt;= 1],即使在最简单的情况下我仍然无法使其工作:xsltransform.net/ejivdGS - 更不用说一种真正通用的方式,它不依赖于调用模板的上下文。
  • 抱歉,我确信可以做到,但我并没有强烈的动机编写 XSLT 1.0 代码来证明这一点。
  • 我相信它也可以完成 - 但我不明白如何按照您概述的方式完成。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-26
相关资源
最近更新 更多