views:

62

answers:

1

With XSLT, how can I change the following:

<root>
  <element id="1" State="Texas" County="Dallas" Population="2412827" />
  <element id="2" State="Texas" County="Harris" Population="3984349" />
  <element id="3" state="Georgia" County="Fulton" Population="1014932" />
  <element id="4" state="Georgia" County="Richmond" Population="212775" />
</root>

into:

<body>
  <h2>Texas</h2>
  <table>
    <tr><td>Dallas</td><td>2412827</td></tr>
    <tr><td>Harris</td><td>3984349</td></tr>
    <tr><td>Total</td><td>6397176</td></tr>
  <h2>Georgia</h2>
  <table>
    <tr><td>Fulton</td><td>1014932</td></tr>
    <tr><td>Richmond</td><td>212775</td></tr>
    <tr><td>Total</td><td>1227707</td></tr>
  </table>
</body>

without explicitly coding each state's name, because I would be screwed if Puerto Rico ever became a state.

+3  A: 

XSLT 1.0
Define a key "state", from which we can easily select all states given a state name. Than apply Muenchian grouping to find the unique states in the input.

Then it gets simple. The "element" template will be applied once per state name, and uses the key() to fetch all entries for that state.

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

    <xsl:output method="xml" indent="yes" />

    <xsl:key name="state" match="element" use="@State" />

    <xsl:template match="root">
        <body>
            <xsl:apply-templates select="element[generate-id(.)=generate-id(key('state',@State)[1])]"/>
        </body>
    </xsl:template>

    <xsl:template match="element">
        <h2><xsl:value-of select="@State" /></h2>
        <table>
            <xsl:for-each select="key('state',@State)">
                <tr>
                    <td>
                        <xsl:value-of select="@County" />
                    </td>
                    <td>
                        <xsl:value-of select="@Population" />
                    </td>
                </tr>
            </xsl:for-each>

            <tr>
                <td>
                    <xsl:text>Total</xsl:text>
                </td>
                <td>
                    <xsl:value-of select="sum(key('state',@State)/@Population)"/>
                </td>
            </tr>

        </table>
    </xsl:template>

</xsl:stylesheet>

XSLT 2.0

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

    <xsl:output method="xml" indent="yes" />

    <xsl:template match="root">
        <body>
            <xsl:for-each-group select="element" group-by="@State">
                <h2><xsl:value-of select="@State" /></h2>
                <table>
                    <xsl:for-each select="current-group()">
                        <tr>
                            <td>
                                <xsl:value-of select="@County" />
                            </td>
                            <td>
                                <xsl:value-of select="@Population" />
                            </td>
                        </tr>
                    </xsl:for-each>
                    <tr>
                        <td>
                            <xsl:text>Total</xsl:text>
                        </td>
                        <td>
                            <xsl:value-of select="format-number(sum(current-group()/@Population), '#########')"/>
                        </td>
                    </tr>
                </table>
            </xsl:for-each-group>
        </body>
    </xsl:template>

</xsl:stylesheet>
Lachlan Roche
+1 Beat me to it by a few seconds. :) I suspect this is a homework question, at least this is how it feels.
Tomalak
Not a homework assignment. I am just not too advanced with XSLT. Also somebody else told me it wasn't possible and it just did not seem to me to be the case. I knew SO would be able to help me out.
kzh