XSLT: How to find the count of unique children of

2019-03-04 00:30发布

问题:

My XML looks like this:

<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>

I am writing an XSL that needs to function like this: If all the baz children are same then doSomething else doSomethingElse. My current node is foo.

I am new to XSLT and I am aware of the conditionals in XSL. It looks something like this as of now:

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

In the current example, it should doSomething() as all the baz elements are the same.

If I find out the number of unique baz elements, I can test whether it is equal to one. If it is, then I will doSomething() else doSomethingElse()

How should I implement this? What should MyCondition be?

PS: My XSL version is 1.0

回答1:

If all the baz children are same then doSomething else doSomethingElse. My current node is foo.

This is confusing because:

  • baz are not children of foo;
  • your title says "find the count of unique children" - but it is not necessary to find it in order to know if they are same.

Try something like:

<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>

Note that this assumes that every baz has a date and a time. Otherwise you need to test for not(date=$first-baz/date) etc.

See also: http://www.jenitennison.com/xslt/grouping/muenchian.html


Added:

Now, assuming all the bar/baz elements have the same tags (not necessarily date and time but say a, b and c), what would be the test attribute for that case?

This makes it significantly more complex. Still you could construct a key:

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

then make your test:

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

This returns true if any child of baz exists whose string-value is different from an equally named node that is child of the first baz.

Note that the key, as defined here, is document-wide. If you want to restrict the test to the current foo ancestor, then you must include its id in the key.



回答2:

If you want to do this in a generic way then you are really stretching the capability of XSLT 1.0 beyond its design limits. But it can be done.

Write a named template called deep-equal that takes two elements as its arguments and returns a string containing the character "F" if and only if they are not equal (for your purposes).

It might look like this:

<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>

Apply this to all relevant pairs of elements:

    <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>

Test if the result contains an "F":

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