Search code examples
xmlxsltxpathxslt-1.0xslt-grouping

XSLT 1.0 grouping multiple same nodes on same level with different values


I have a list of elements:

<vehiciles>
  <vehicile value="_CAR">CAR</vehicile>
  <vehicile value="01">vehicile1</vehicile>
  <vehicile value="02">vehicile2</vehicile>
  <vehicile value="03">vehicile3</vehicile>
  <vehicile value="_TRUCK">TRUCK</vehicile>
  <vehicile value="04">vehicile4</vehicile>
  <vehicile value="05">vehicile5</vehicile>
  <vehicile value="06">vehicile6</vehicile>
</vehiciles>

Unfortunately i cannot change structure, but i must group it (in html select/optgroup tag) by category indicated by vehicile that value starts with underscore.

Result i like to achieve:

<select>
  <optgroup label="CAR">
    <option value="01">vehicile1</option>
    <option value="02">vehicile2</option>
    <option value="03">vehicile3</option> 
  </optgroup>
  <opgroup label="TRUCK">
    <option value="04">vehicile4</option>
    <option value="05">vehicile5</option>
    <option value="06">vehicile6</option>
  </optgroup>
</select>

What i tried was:

<xsl:template match="field" mode="dropdown_list">
  <select>
    <xsl:choose>
      <xsl:when test="vehiciles/vehicile[starts-with(@value, '_')]">
        <xsl:for-each select="vehiciles/vehicile[starts-with(@value, '_')]">
          <xsl:variable name="lastValue" select="following-sibling::*[starts-with(@value, '_')][@value]" />
          <optgroup>
            <xsl:attribute name="label">
              <xsl:value-of select="text()"/>
            </xsl:attribute>

            <xsl:for-each select="following-sibling::*[not(preceding::vehicile[1][@value = $lastValue])]">
              <option value="{@value}">
                <xsl:value-of select="text()"/>
              </option>
            </xsl:for-each>

          </optgroup>
        </xsl:for-each>

     </xsl:when>
     <xsl:otherwise>
       <!-- something here -->
     </xsl:otherwise>
   </xsl:choose>
 </select>
</xsl:template>

It output second loop nice, but first contains all element. Trying figure out it for hours without luck.

Tried to do by recursion but failed as well as Muenchian grouping.

Is there way to look up from certain node up to first sibling that match criteria? Or another way?

Any help appreciated.


Solution

  • Try it this way:

    XSLT 1.0

    <xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    
    <xsl:key name="vehicile" match="vehicile[not(starts-with(@value, '_'))]" use="generate-id(preceding-sibling::vehicile[starts-with(@value, '_')][1])" />
    
    <xsl:template match="/vehiciles">
        <select>
            <xsl:for-each select="vehicile[starts-with(@value, '_')]">
                <optgroup label="{substring-after(@value, '_')}">
                    <xsl:for-each select="key('vehicile', generate-id())">
                        <option value="{@value}">
                            <xsl:value-of select="."/>
                        </option>
                    </xsl:for-each>
                </optgroup>
            </xsl:for-each>
        </select>
    </xsl:template>
    
    </xsl:stylesheet>