-1

I have the following XML document:

<?xml version="1.0" encoding="UTF-8"?>
<cars>
    <car body="Wagon">
        <text>Red</text>
    </car>
    <car body="Sedan">
        <text>Yellow</text>
    </car>
    <car body="Sedan">
        <text></text>
    </car>
    <car body="Wagon">
        <textlist>
            <text>Red</text>
            <text>Green</text>
            <text>Black</text>
            <text>Blue</text>
        </textlist>
    </car>
    <car body="Sedan">
        <textlist>
            <text>Yellow</text>
            <text>Orange</text>
        </textlist>
    </car>
    <car body="Fastback">
        <textlist>
            <text>Yellow</text>
            <text>Red</text>
            <text>Green</text>
            <text>Black</text>
            <text>Blue</text>
        </textlist>
    </car>
    <car body="Fastback">
        <textlist>
            <text>Pink</text>
            <text>Red</text>
            <text>Orange</text>
        </textlist>
    </car>
</cars>

Using XSLT 1.0 I need to transform the XML document to this format:

<?xml version="1.0" encoding="UTF-8"?>
<cars>
    <car type="Wagon">Red</car>
    <car type="Sedan">Yellow</car>
    <car type="Wagon">Green</car>
    <car type="Wagon">Black</car>
    <car type="Wagon">Blue</car>
    <car type="Sedan">Orange</car>
</cars>

Notice that:

  1. body="Fastback" is excluded
  2. Duplicates are excluded (Red Wagon appears twice)
  3. Textlist multiple items are put as individual elements in the output XML
  4. Ignore empty values
general exception
  • 4,202
  • 9
  • 54
  • 82

1 Answers1

2

Here is a sample:

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

<xsl:output indent="yes"/>

<xsl:key name="k1" 
  match="car[not(@body = 'Fastback')]//text"
  use="concat(ancestor::car/@body, '|', .)"/>

<xsl:template match="cars">
  <xsl:copy>
    <xsl:apply-templates select="car[not(@body =  'Fastback')]//text
      [generate-id() = generate-id(key('k1', concat(ancestor::car/@body, '|', .))[1])]"/>
  </xsl:copy>
</xsl:template>

<xsl:template match="text">
  <car type="{ancestor::car/@body}">
    <xsl:value-of select="."/>
  </car>
</xsl:template>

</xsl:stylesheet>

It uses Muechian grouping, see http://www.jenitennison.com/xslt/grouping/muenchian.xml if you are not familiar with that XSLT 1.0 approach.

Martin Honnen
  • 160,499
  • 6
  • 90
  • 110
  • Trying to understand how this works, what do the square brackets at the end of `key('k1', concat(ancestor::car/@body, '|', .))[1]` signify ?? – general exception Jul 13 '12 at 13:08
  • The [1] selects the head of the keyed group. As a consequence the xsl:apply-templates is applied on on the first node of each group, not every node of every group. – Sean B. Durkin Jul 14 '12 at 14:58
  • An expression in square brackets is a predicate, an expression with an integer number (e.g. `[1]`) is a positional predicate, short form for `[position() = 1]`. So `key('key-name', some key value expression)` selects a node-set and with the positional predicate `key('key-name', some key value expression)[1]` we select only the first node (in document order) in the node-set returned by the `key` function call. With the complete predicate `[generate-id() = generate-id(key('k1', concat(ancestor::car/@body, '|', .))[1])]` we ensure that only the first item in each group is processed, see the link. – Martin Honnen Jul 14 '12 at 16:03