How to split a grouping into 3 parts

Discussion in 'XML' started by Hvid Hat, Mar 5, 2008.

  1. Hvid Hat

    Hvid Hat Guest

    Hi

    At first, I thought I could only solve my problem with a C# method inside
    my XSLT but I'm beginning to think it might be possible with XSLT only. So
    I'm trying, but I need help :) How can I split a grouping into 3 parts?

    I've got the following grouping of countries which is working fine:

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:eek:utput method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:key name="x" match="Country" use="." />
    <xsl:template match="Persons">
    <xsl:for-each select="Person[generate-id(Country) = generate-id(key('x',
    Country)[1])]">
    <xsl:value-of select="Country" /><br />
    </xsl:for-each>
    </xsl:template>
    </xsl:stylesheet>

    But how can I split the grouping into 3 parts. Say there's 11 (unique) countries.
    How can I split them into 3 parts so I can produce 3 lists of 4, 4 and 3
    elements, e.g.

    <ul>
    <li>Argentina</li>
    <li>Belgium</li>
    <li>Canada</li>
    <li>Denmark</li>
    </ul>
    <ul>
    <li>England</li>
    <li>France</li>
    <li>Greece</li>
    <li>Hungary</li>
    </ul>
    <ul>
    <li>Iceland</li>
    <li>Japan</li>
    <li>Kenya</li>
    </ul
     
    Hvid Hat, Mar 5, 2008
    #1
    1. Advertising

  2. Hvid Hat

    Pavel Lepin Guest

    Hvid Hat <> wrote in
    <>:
    > <?xml version="1.0" encoding="UTF-8"?>
    > <xsl:stylesheet version="1.0"
    > xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    > <xsl:eek:utput method="xml" version="1.0" encoding="UTF-8"
    > indent="yes"/> <xsl:key name="x" match="Country" use="."
    > /> <xsl:template match="Persons">
    > <xsl:for-each select="Person[generate-id(Country) =
    > generate-id(key('x',
    > Country)[1])]">
    > <xsl:value-of select="Country" /><br />
    > </xsl:for-each>
    > </xsl:template>
    > </xsl:stylesheet>
    >
    > But how can I split the grouping into 3 parts. Say there's
    > 11 (unique) countries. How can I split them into 3 parts
    > so I can produce 3 lists of 4, 4 and 3 elements, e.g.


    Define a grouping key that would allow you to select only
    first occurrences of country elements in your document.
    Then use count() and position() XPath functions to split it
    into n chunks.

    --
    In Soviet Russia, XML documents transform *you*.
     
    Pavel Lepin, Mar 6, 2008
    #2
    1. Advertising

  3. Hvid Hat

    Hvid Hat Guest

    Hello Pavel,

    > Define a grouping key that would allow you to select only first
    > occurrences of country elements in your document. Then use count() and
    > position() XPath functions to split it into n chunks.


    I figured, I had to use a combination of position(), count() and the mod
    operator to achieve it, but I'm still not comfortable enough with XSLT to
    know how to go about it. I've looked at http://www.dpawson.co.uk/xsl/sect2/N4486.html
    for grouping by position without luck. I'm just not into the XSLT way of
    thinking (yet).

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:eek:utput method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:key name="x" match="Country" use="."/>
    <xsl:template match="Persons">
    <xsl:for-each select="Person[generate-id(Country) = generate-id(key('x',
    Country)[1])]">
    <xsl:value-of select="position()"/> of <xsl:value-of select="last()"/> -
    <xsl:value-of select="Country"/>
    </xsl:for-each>
    </xsl:template>
    </xsl:stylesheet
     
    Hvid Hat, Mar 6, 2008
    #3
  4. Hvid Hat wrote:

    > I figured, I had to use a combination of position(), count() and the mod
    > operator to achieve it, but I'm still not comfortable enough with XSLT
    > to know how to go about it. I've looked at
    > http://www.dpawson.co.uk/xsl/sect2/N4486.html for grouping by position
    > without luck. I'm just not into the XSLT way of thinking (yet).


    Here is an example XML document:

    <root>
    <country>USA</country>
    <country>Germany</country>
    <country>France</country>
    <country>Russia</country>
    <country>Spain</country>
    <country>Russia</country>
    <country>Italy</country>
    <country>Canada</country>
    <country>USA</country>
    <country>Portugal</country>
    <country>France</country>
    <country>Denmark</country>
    <country>Sweden</country>
    </root>

    Here is an XSLT 1.0 stylesheet that uses Muenchian grouping to save the
    unique countries in a variable, then the exslt:node-set function
    (supported by XslCompiledTransform) to get a node-set to be split into
    three components:

    <xsl:stylesheet
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:exslt="http://exslt.org/common"
    exclude-result-prefixes="exslt"
    version="1.0">

    <xsl:eek:utput method="html" indent="yes"/>

    <xsl:param name="number-of-parts" select="3"/>

    <xsl:key name="by-country" match="country" use="."/>

    <xsl:variable
    name="unique-countries-rtf">
    <xsl:for-each
    select="/root/country[generate-id() =
    generate-id(key('by-country', .)[1])]">
    <xsl:copy-of select="."/>
    </xsl:for-each>
    </xsl:variable>

    <xsl:variable
    name="unique-countries"
    select="exslt:node-set($unique-countries-rtf)/country"/>

    <xsl:variable name="items-per-list"
    select="ceiling(count($unique-countries) div $number-of-parts)"/>

    <xsl:template match="root">
    <div>
    <xsl:apply-templates
    select="$unique-countries[position() mod $items-per-list = 1]"
    mode="group"/>
    </div>
    </xsl:template>

    <xsl:template match="country" mode="group">
    <ul>
    <xsl:apply-templates
    select=". | following-sibling::country[position() &lt;
    $items-per-list]"
    mode="item"/>
    </ul>
    </xsl:template>

    <xsl:template match="country" mode="item">
    <li>
    <xsl:value-of select="."/>
    </li>
    </xsl:template>

    </xsl:stylesheet>

    Result looks like this:

    <div>
    <ul>
    <li>USA</li>
    <li>Germany</li>
    <li>France</li>
    <li>Russia</li>
    </ul>
    <ul>
    <li>Spain</li>
    <li>Italy</li>
    <li>Canada</li>
    <li>Portugal</li>
    </ul>
    <ul>
    <li>Denmark</li>
    <li>Sweden</li>
    </ul>
    </div>


    If you don't want to use exslt:node-set then I think you need to chain
    two stylesheets where the first creates a list with the unique countries
    and the second splits them into three groups.

    --

    Martin Honnen
    http://JavaScript.FAQTs.com/
     
    Martin Honnen, Mar 7, 2008
    #4
  5. Hvid Hat

    Pavel Lepin Guest

    Martin Honnen <> wrote in
    <47d1372f$0$10999$-online.net>:
    > Here is an example XML document:
    >
    > <root>
    > <country>USA</country>
    > <country>Germany</country>
    > <country>France</country>
    > <country>Russia</country>
    > <country>Spain</country>
    > <country>Russia</country>
    > <country>Italy</country>
    > <country>Canada</country>
    > <country>USA</country>
    > <country>Portugal</country>
    > <country>France</country>
    > <country>Denmark</country>
    > <country>Sweden</country>
    > </root>


    [transformation snipped]

    > If you don't want to use exslt:node-set then I think you
    > need to chain two stylesheets where the first creates a
    > list with the unique countries and the second splits them
    > into three groups.


    Not at all.

    <xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:eek:utput method="xml" indent="yes"/>
    <xsl:key name="countries" match="/root/country"
    use="string(.)"/>
    <xsl:key name="unique-countries" match="/root/country"
    use="count(.|key('countries',.)[1])"/>
    <xsl:variable name="unique-countries"
    select="key('unique-countries',1)"/>
    <xsl:variable name="parts" select="3"/>
    <xsl:template match="root">
    <result>
    <xsl:apply-templates select="$unique-countries"
    mode="group">
    <xsl:with-param name="parts" select="$parts"/>
    <xsl:with-param name="elts"
    select="$unique-countries"/>
    </xsl:apply-templates>
    </result>
    </xsl:template>
    <xsl:template match="/root/country" mode="group">
    <xsl:param name="parts"/>
    <xsl:param name="elts"/>
    <xsl:variable name="total" select="count($elts)"/>
    <xsl:variable name="per-group"
    select="ceiling($total div $parts)"/>
    <xsl:variable name="pos" select="position()-1"/>
    <xsl:variable name="group"
    select="floor($pos div $per-group)"/>
    <xsl:if test="$pos mod $per-group=0">
    <group>
    <xsl:apply-templates
    select=
    "
    $elts
    [floor((position()-1) div $per-group)=$group]
    "/>
    </group>
    </xsl:if>
    </xsl:template>
    <xsl:template match="/root/country">
    <list-elt>
    <xsl:apply-templates/>
    </list-elt>
    </xsl:template>
    </xsl:stylesheet>

    Xalan will choke on this due to key() invocation in a use
    attribute of an xsl:key element, of course. libxslt and
    Saxon won't.

    --
    In Soviet Russia, XML documents transform *you*.
     
    Pavel Lepin, Mar 7, 2008
    #5
  6. Pavel Lepin wrote:

    > Xalan will choke on this due to key() invocation in a use
    > attribute of an xsl:key element, of course. libxslt and
    > Saxon won't.


    Saxon 6 complains too: "key() function cannot be used here". .NET's
    XslCompiledTransform also complains: "The 'key()' function cannot be
    used in 'use' and 'match' attributes of 'xsl:key' element.".
    And with Saxon 9 and XSLT 2 I don't think you need keys at all, you can
    use xsl:for-each-group and temporary trees if needed.


    --

    Martin Honnen
    http://JavaScript.FAQTs.com/
     
    Martin Honnen, Mar 7, 2008
    #6
  7. Hvid Hat

    Pavel Lepin Guest

    Martin Honnen <> wrote in
    <47d14595$0$11004$-online.net>:
    > Pavel Lepin wrote:
    >> Xalan will choke on this due to key() invocation in a use
    >> attribute of an xsl:key element, of course. libxslt and
    >> Saxon won't.

    >
    > Saxon 6 complains too: "key() function cannot be used
    > here". .NET's XslCompiledTransform also complains: "The
    > 'key()' function cannot be used in 'use' and 'match'
    > attributes of 'xsl:key' element.". And with Saxon 9 and
    > XSLT 2 I don't think you need keys at all, you can use
    > xsl:for-each-group and temporary trees if needed.


    The differences are due to unclear status of E13 in XSLT 1.0
    Errata. Frankly, I think the idea is ridiculous. The WG
    tried to remove an incredibly useful feature, using 'cycle
    prevention' as a justification. Hell, why not disallow
    recursion? it might be infinite, after all.

    --
    In Soviet Russia, XML documents transform *you*.
     
    Pavel Lepin, Mar 7, 2008
    #7
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. oliver
    Replies:
    5
    Views:
    419
    Paul McGuire
    Mar 11, 2005
  2. Markus Dehmann
    Replies:
    6
    Views:
    158
    Michele Dondi
    Dec 14, 2007
  3. iMath
    Replies:
    2
    Views:
    144
    Dave Angel
    May 7, 2013
  4. Roy Smith
    Replies:
    35
    Views:
    340
    Oscar Benjamin
    Jun 13, 2013
  5. Replies:
    0
    Views:
    107
Loading...

Share This Page