Count xml tag values irrespective of the tag name using XSLT

I have a scenario where I need to count the xml tag values irrespective of its tag name. Below is my xml,

<?xml version="1.0" encoding="UTF-8"?>
<output>
   <result>
      <value>
         <color1>G</color1>
         <color2>Y</color2>
      </value>
      <value>
         <color1>Y</color1>
         <color2>R</color2>
      </value>
   </result>
   <result>
      <value>
         <color1>G</color1>
         <color2>R</color2>
      </value>
      <value>
         <color1>Y</color1>
         <color2>R</color2>
      </value>
   </result>
</output>
  1. Here I need to count the number of occurrences of G,Y,R from the xpath //output/result
  2. The value inside //output/result should remain and be able to add it in the output. Below is the xslt that I used, but it always gives count as 0.

     <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"     
          xmlns:xs="http://www.w3.org/2001/XMLSchema" 
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0">
    
        <xsl:template match="/">
           <output>
              <xsl:for-each select="//output/result">
                 <result>
        <green><xsl:value-of select="count(/value[./color[*]='G'])" /></green>
         <red><xsl:value-of select="count(/value[./color[*]='R'])" /></red>
         <yellow><xsl:value-of select="count(/value[./color[*]='Y'])" /</yellow>
                   <xsl:value-of select="." />
                </result>
             </xsl:for-each>
          </output>
       </xsl:template>
    </xsl:stylesheet>
    

The desired output is as below.

<?xml version="1.0" encoding="UTF-8"?>
<output>
   <result>
     <GreenCount>1</GreenCount>
<RedCount>1</RedCount>
<YellowCount>2</YellowCount>
      <value>
         <color1>G</color1>
         <color2>Y</color2>
      </value>
      <value>
         <color1>Y</color1>
         <color2>R</color2>
      </value>

   </result>
   <result>
     <GreenCount>1</GreenCount>
<RedCount>2</RedCount>
<YellowCount>1</YellowCount>

      <value>
         <color1>G</color1>
         <color2>R</color2>
      </value>
      <value>
         <color1>Y</color1>
         <color2>R</color2>
      </value>
   </result>
</output>

Upvotes: 2

Views: 328

Answers (1)

Jim Garrison
Jim Garrison

Reputation: 86774

You're trying to program XSL as if it were an imperative language. It's not, the input XML is driving the process and the XSL "reacts" as input tags are matched to templates.

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0">
    <xsl:output method="xml" indent="yes"/>
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="result">
        <xsl:copy>
            <GreenCount><xsl:value-of select="count(value/*[text() = 'G'])"/> </GreenCount>
            <RedCount><xsl:value-of select="count(value/*[text() = 'R'])"/></RedCount>
            <YellowCount><xsl:value-of select="count(value/*[text() = 'Y'])"/></YellowCount>
            <xsl:apply-templates/>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

The first template is an "identity transform" that copies input to output unchanged, since you are just adding tags to the input.

Then, for each result (not XSL for-each, just letting the natural order of processing feed you the result elements):

  1. Copy the result tag
  2. Generate the three summary tags by counting children of value tags having the desired text
  3. For the rest of the contents of the result tag, recursively reinvoke the process using the identity transform to copy them to the output.

This produces the following output:

<?xml version="1.0" encoding="UTF-8"?>
<output>
    <result>
      <GreenCount>1</GreenCount>
      <RedCount>1</RedCount>
      <YellowCount>2</YellowCount>
        <value>
            <color1>G</color1>
            <color2>Y</color2>
        </value>
        <value>
            <color1>Y</color1>
            <color2>R</color2>
        </value>
    </result>
    <result>
      <GreenCount>1</GreenCount>
      <RedCount>2</RedCount>
      <YellowCount>1</YellowCount>
        <value>
            <color1>G</color1>
            <color2>R</color2>
        </value>
        <value>
            <color1>Y</color1>
            <color2>R</color2>
        </value>
    </result>
</output>

Upvotes: 1

Related Questions