1
votes

In this question I asked if it is possible to prevent output of consecutive identical values using XSLT. In this question I extend the input data so that the Muenchian Grouping method for XSLT 1.0 syntax will be clearer.

Is it possible to match only the first occurrence of a value in an XML doc using XSL transforms?

I'd like to print out every value inside f2,f3 but only the first instance of the contents of f1, retaining order.

XML Data

    <doc> 
    <datum>
        <f1>One</f1>
        <f2>Monday</f2>
        <f3>January</f3>
      </datum>
      <datum>
        <f1>One</f1>
        <f2>Tuesday</f2>
        <f3>February</f3>
      </datum>
      <datum>
        <f1>Two</f1>
        <f2>Wednesday</f2>
        <f3>March</f3>
    </datum>
    </doc>

Output

      One
      -Monday-January
      -Tuesday-February
      Two
      -Wednesday-March

This is the solution that was proposed for XSLT 1.0 by Dimitre Novatchev for an input data set containing the fields f1 and f2.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="text"/>

 <xsl:key name="kDatumByF1" match="datum" use="f1"/>

 <xsl:template match=
   "datum[generate-id() = generate-id(key('kDatumByF1', f1)[1])]">
   <xsl:value-of select="concat('&#xA;', f1)"/>
   <xsl:apply-templates select="key('kDatumByF1', f1)/f2" mode="inGroup"/>
 </xsl:template>

 <xsl:template match="f2" mode="inGroup">
   - <xsl:value-of select="."/>
 </xsl:template>
 <xsl:template match="text()"/>
</xsl:stylesheet>

It works quite well but I am unsure of the syntax to extend to include the field 'f3' in the output.

2

2 Answers

2
votes
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="text"/>

 <xsl:key name="kDatumByF1" match="datum" use="f1"/>

 <xsl:template match=
   "datum[generate-id() = generate-id(key('kDatumByF1', f1)[1])]">
   <xsl:value-of select="concat('&#xA;', f1)"/>
   <xsl:apply-templates select="key('kDatumByF1', f1)/*[self::f2 | self::f3]" mode="inGroup"/>
 </xsl:template>

 <xsl:template match="f2" mode="inGroup">
   - <xsl:value-of select="."/>
 </xsl:template>

 <xsl:template match="f3" mode="inGroup">
  <xsl:value-of select="concat('-',.)"/>
 </xsl:template>

 <xsl:template match="text()"/>
</xsl:stylesheet>
1
votes

This transformation:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     <xsl:output method="text"/>
     <xsl:strip-space elements="*"/>
     <xsl:key name="kDatumByF1" match="datum" use="f1"/>

     <xsl:template match=
       "datum[generate-id() = generate-id(key('kDatumByF1', f1)[1])]">
       <xsl:value-of select="concat(f1, '&#xA;')"/>
       <xsl:apply-templates mode="inGroup"
           select="key('kDatumByF1', f1)/*[self::f2 or self::f3]" />
     </xsl:template>

     <xsl:template match="f2|f3" mode="inGroup">
       <xsl:value-of select="concat('- ',., ' ')"/>
       <xsl:if test="not(following-sibling::*[self::f2 or self::f3])">
        <xsl:text>&#xA;</xsl:text>
       </xsl:if>
     </xsl:template>
     <xsl:template match="text()"/>
</xsl:stylesheet>

when applied on the provided XML document:

<doc>
    <datum>
        <f1>One</f1>
        <f2>Monday</f2>
        <f3>January</f3>
    </datum>
    <datum>
        <f1>One</f1>
        <f2>Tuesday</f2>
        <f3>February</f3>
    </datum>
    <datum>
        <f1>Two</f1>
        <f2>Wednesday</f2>
        <f3>March</f3>
    </datum>
</doc>

produces the wanted, correct result:

One
- Monday - January 
- Tuesday - February 
Two
- Wednesday - March 

Explanation:

Proper use of the Muenchian grouping method.

Do note:

This solution produces the wanted result even if f2 and f3 can come in any order.