0

Let's say I have following list:

<list>
  <item>text1</item>
  <item>text2</item>
  <item>text3</item>
  <item type="type1">text4</item>
  <item type="type1">text5</item>
  <item type="type1">text6</item>
  <item>text7</item>
  <item>text8</item>
  <item type="type2">text9</item>
  <item type="type2">text10</item>
  <item>text11</item>
  <item>text12</item>
  <item type="...">...</item>
  ...
  <item>...</item>
  ...
</list>

One important things, that I don't know what value will have type attribute! It will be generated programmatically. Not this one. I know only that some items could have the same attribute.

And I want to transform this list into:

  text1
  text2
  text3
  text4, text5, text6
  text7
  text8
  text9, text10
  text11
  text12

So, for each item, that does not have "type" attribute I output just text inside item. For each items that have the same attribute I output all items texts in one line. How to achieve this in xslt? And how to select all items that have the same attribute, when attribute is generated programmatically?

Community
  • 1
  • 1
Alexey Vol
  • 1,723
  • 1
  • 16
  • 20

2 Answers2

2

You want to group items by type. Grouping is easily done with a key:

<xsl:key name="byType" match="item[@type]" use="@type" />

This creates an index of all items that have a type, using their respective types as the key.

Next you want to generate an output line only if one of two conditions is true:

  • The item has no type, or
  • The item is the first of its type

That can be expressed as:

<xsl:if test="not(@type) or generate-id() = generate-id(key('byType', @type)[1])">

The first item of its type will output all the others separated with a comma. The following items of the same type should not output anything (as they have already been printed).

We can define the list of items we want to print on each line as the union of these two nodesets:

  • the current item (for the cases where the item has no type)
  • all the items that have the same type as the current item (via the key)

In XPath this is . | key('byType', @type) (nodesets can never not contain the same node twice)

So this:

<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text" encoding="UTF-8" />

    <xsl:key name="byType" match="item[@type]" use="@type" />

    <xsl:template match="/*">
        <xsl:apply-templates select="item" />
    </xsl:template>

    <xsl:template match="item">
        <xsl:if test="not(@type) or generate-id() = generate-id(key('byType', @type)[1])">
            <xsl:for-each select=". | key('byType', @type)">
                <xsl:value-of select="." />
                <xsl:if test="position() &lt; last()">, </xsl:if>
            </xsl:for-each>
            <xsl:text>&#xA;</xsl:text>
        </xsl:if>
    </xsl:template>
</xsl:transform>

outputs

text1
text2
text3
text4, text5, text6
text7
text8
text9, text10
text11
text12
...
...
Tomalak
  • 332,285
  • 67
  • 532
  • 628
1

You want to use Muenchian grouping. Something like this should work

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method="text" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />

    <xsl:key name="items-by-type" match="item" use="@type" />
    <xsl:template match="/list">


        <xsl:for-each select="item[count(. | key('items-by-type', @type)[1]) = 1]">
            <xsl:if test="not(@type)">
                <xsl:value-of select="."/> 
                <xsl:text>&#10;</xsl:text>
            </xsl:if>
            <xsl:if test="@type">
                <xsl:for-each select="key('items-by-type', @type)">
                    <xsl:value-of select="." />    
                    <xsl:if test="position() != last()"><xsl:text>, </xsl:text></xsl:if>
                </xsl:for-each>                
                <xsl:text>&#10;</xsl:text>

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


    </xsl:template>
</xsl:transform>

http://xsltransform.net/3NJ3914/3

Use a key to group your items. If @type is not set, just print the item. Otherwise, iterate through all the items of that @type and print them.

Dan Field
  • 20,885
  • 5
  • 55
  • 71