views:

50

answers:

5

I have the following XML:

<assessment>
    <section>
        <item>
            <attributes>
                <variables>
                    <variable>
                        <variable_name value="MORTIMER"/>
                    </variable>
                </variables>
            </attributes>
        </item>
        <item>
            <attributes>
                <variables>
                    <variable>
                        <variable_name value="FRED"/>
                    </variable>
                </variables>
            </attributes>
        </item>
        <item>
            <attributes>
                <variables>
                    <variable>
                        <variable_name value="MORTIMER"/>
                    </variable>
                </variables>
            </attributes>
        </item>
    </section>
</assessment>

I have the following XSLT to process that XML:

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

 <xsl:key name="kValueByVal" match="item//variables//variable_name" 
          use="@value"/>

 <xsl:template match="assessment">
     <xsl:for-each select="
      .//item//variables//variable_name/@value
      ">
        <xsl:value-of select=
        "concat(., ' ', count(key('kValueByVal', .)), '&#xA;')"/>
         <br/>
     </xsl:for-each>
 </xsl:template>
</xsl:stylesheet>

It outputs the following, which is almost what I want:

MORTIMER 2
FRED 1
MORTIMER 2

It lists each of the variable_names and how many times each occurs. The only problem is that it gives this count once for each time the variable_name occurs instead of only once.

This is what I want it to output:

MORTIMER 2
FRED 1

How do I modify the XSLT code to give me that? Note that we're using XSLT 1.0.

The following solution, which seems like it should work, outputs nothing:

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

 <xsl:key name="kValueByVal" match="item//variables//variable_name"
          use="@value"/>

 <xsl:template match="assessment">
     <xsl:for-each select=".//item//variables//variable_name/@value[generate-id()
                                                                    =
                                                                    generate-id(key('kValueByVal',.)[1])]">
        <xsl:value-of select=
        "concat(., ' ', count(key('kValueByVal', .)), '&#xA;')"/>
         <br/>
     </xsl:for-each>
 </xsl:template>
</xsl:stylesheet>
+1  A: 

Seeing as you're using XSLT 2.0, I'd use the built-in grouping features. (For earlier versions, you'd probably want to look into Muenchian grouping.)

<?xml version="1.0" ?>
<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
 <xsl:output method="text"/>

 <xsl:key name="kValueByVal" match="item//variables//variable_name" 
          use="@value"/>

 <xsl:template match="assessment">
     <xsl:for-each-group select=".//item//variables//variable_name" group-by="@value">
                <xsl:value-of select="concat(current-grouping-key(), ' ', count(current-group()), '&#xA;')"/>
        <br/>
     </xsl:for-each-group>
 </xsl:template>
</xsl:stylesheet>
Matt Gibson
Thanks, but I get the following error when I try that:"Error during XSLT transformation: XSLT transformation failed."
Paul Reiners
These are the errors I get:D:\software\source\applications\Athena\main\src\vue\exam\inspector\xslt>xalan repository.inspection-pi.xml variables.xsltXSLT Warning: The element xsl:for-each-group is an unknown XSLT element. (variables.xslt, line 11, column 87.)XPathParserException: The function 'current-grouping-key' was not found.expression = 'concat(current-grouping-key(), ' ', count(current-group()), '')' Remaining tokens are: ( 'current-grouping-key' '(' ')' ',' '' '' ',' 'count' '(' 'current-group' '(' ')' ')' ',' '''' ')') (variables.xslt, line 12, column 110)
Paul Reiners
Perhaps a processor really isn't 2.0. Is there a 1.0 solution?
Paul Reiners
+1  A: 

You get what you are asking for:

<xsl:for-each select=".//item//variables//variable_name/@value"> 

Wich means: for each one of these attributes

When grouping, you must say: for each one of these one of a kind

And, how do you know wich are one of a kind? With Muenchian method:

<xsl:for-each select=".//item//variables//variable_name/@value[generate-id()
                                                               =
                                                               generate-id(key('kValueByVal',.)[1])]">

That means: the ones been the first with that key.

EDIT: Also, avoid // when you know input schema.

EDIT: Now I can see that you change the key... So, for you new key, who is first of a kind? Yes! variable_name element:

<xsl:for-each select=".//item//variables//variable_name[generate-id()
                                                        =
                                                        generate-id(key('kValueByVal',@value)[1])]">
Alejandro
I tried that myself, but that's not outputting anything.
Paul Reiners
@Paul Reiners: that's because you've change the key. Following the minor change you make in your many question about the same stylesheet, it's dificult.
Alejandro
A: 
<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
 <xsl:output method="text"/>

 <xsl:key name="kValueByVal" match="item//variables//variable_name" 
          use="@value"/>

 <xsl:template match="assessment">
     <xsl:for-each 
              select="//item//variables//variable_name[
                          generate-id() = 
                              generate-id(key('kValueByVal', @value)[1])]">
        <xsl:value-of select=
        "concat(./@value, ' ', count(key('kValueByVal', ./@value)), '&#xA;')"/>
         <br/>
     </xsl:for-each>
 </xsl:template>
</xsl:stylesheet>
Paul Reiners
+1  A: 

This will do it in XSLT 1.0.

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

 <xsl:key name="kValueByVal" match="item//variables//variable_name" 
          use="@value"/>

 <xsl:template match="assessment">
     <xsl:for-each select="
      //item//variables//variable_name[not(@value=ancestor::item/preceding-sibling::item//variables//variable_name/@value)]
      ">
        <xsl:value-of select=
        "concat(@value, ' ', count(key('kValueByVal', @value)), '&#xA;')"/>
         <br/>
     </xsl:for-each>
 </xsl:template>
</xsl:stylesheet>

The output I get is

MORTIMER 2
<br />FRED 1
<br />

Note that it assumes a bit more about the document structure (the ancestor::item bit), but you should be able to take it from there.

harpo
+2  A: 

You really need to understand how the Muenchian grouping works, otherwise you'd be asking variations of the same questions forever.

Do read Jeni Tennison's tutorial.

Here is a solution for your latest question:

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

 <xsl:key name="kVarNameByVal" match="variable_name"
          use="@value"/>

 <xsl:template match=
  "variable_name[generate-id()
                =
                 generate-id(key('kVarNameByVal', @value)[1])
                ]
  ">
        <xsl:value-of select=
        "concat(@value, ' ', count(key('kVarNameByVal', @value)), '&#xA;')"/>
         <br/>
 </xsl:template>
</xsl:stylesheet>

When this transformation is performed on the provided XML document, the wanted result is produced:

MORTIMER 2
FRED 1
Dimitre Novatchev
I did finally get around to reading that tutorial, thanks! And now I'm finally starting to understand this stuff.
Paul Reiners