tags:

views:

25

answers:

2

Hi all,

I need to transform following repeating pattern

<root>
    <bar>bar 1</bar>
    <baz>baz 1</baz>
    <qux>qux 1</qux>
    <bar>bar 2</bar>
    <baz>baz 2</baz>
    <qux>qux 2</qux>
</root>

Into this;

<root>
    <foo>
        <bar>bar 1</bar>
        <baz>baz 1</baz>
        <qux>qux 1</qux>
    </foo>
    <foo>
        <bar>bar 2</bar>
        <baz>baz 2</baz>
        <qux>qux 2</qux>
    </foo>
</root>

But don't want to use loop solutions.

A: 

Many, many solutions. This one use fine grained traversal:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <xsl:template match="node()|@*" name="identity">
        <xsl:copy>
            <xsl:apply-templates select="node()[1]|@*"/>
        </xsl:copy>
        <xsl:apply-templates select="following-sibling::node()[1]"/>
    </xsl:template>
    <xsl:template match="bar">
        <foo>
            <xsl:call-template name="identity"/>
        </foo>
        <xsl:apply-templates select="following-sibling::bar[1]"/>
    </xsl:template>
    <xsl:template match="qux">
        <xsl:copy>
            <xsl:apply-templates select="node()[1]|@*"/>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

Output:

<root>
    <foo>
        <bar>bar 1</bar>
        <baz>baz 1</baz>
        <qux>qux 1</qux>
    </foo>
    <foo>
        <bar>bar 2</bar>
        <baz>baz 2</baz>
        <qux>qux 2</qux>
    </foo>
</root>

Other solution: push style with key.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <xsl:key name="kElementByPrecedingBar" match="root/*[not(self::bar)]"
                  use="generate-id(preceding-sibling::bar[1])"/>
    <xsl:template match="node()|@*" name="identity">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="bar" mode="wrap">
        <foo>
            <xsl:apply-templates select=".|key('kElementByPrecedingBar',
                                               generate-id())"/>
        </foo>
    </xsl:template>
    <xsl:template match="root">
        <xsl:copy>
            <xsl:apply-templates select="bar" mode="wrap"/>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>
Alejandro
A: 

This transformation:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:key name="kFollowing" match="*[not(name()=name(/*/*[1]))]"
  use="generate-id(preceding-sibling::*[name()=name(/*/*[1])][1])"/>

 <xsl:template match="node()|@*" name="identity">
  <xsl:copy>
   <xsl:apply-templates select="node()|@*"/>
  </xsl:copy>
 </xsl:template>

 <xsl:template match="/*/*"/>

 <xsl:template match="/*/*[name()=name(/*/*[1])]">
  <foo>
   <xsl:apply-templates select=".|key('kFollowing', generate-id())" mode="copy"/>
  </foo>
 </xsl:template>

 <xsl:template match="*" mode="copy">
  <xsl:call-template name="identity"/>
 </xsl:template>
</xsl:stylesheet>

when applied on the provided XML document:

<root>
    <bar>bar 1</bar>
    <baz>baz 1</baz>
    <qux>qux 1</qux>
    <bar>bar 2</bar>
    <baz>baz 2</baz>
    <qux>qux 2</qux>
</root>

produces the wanted, correct result:

<root>
    <foo>
        <bar>bar 1</bar>
        <baz>baz 1</baz>
        <qux>qux 1</qux>
    </foo>
    <foo>
        <bar>bar 2</bar>
        <baz>baz 2</baz>
        <qux>qux 2</qux>
    </foo>
</root>

Do note:

  1. The transformation doesn't need to know and hardcode any element name -- it just uses the fact that the sequence of elements names is repeating.

  2. The use of keys to find all members of a group.

  3. The use of modes to process the same nodes in more than one way.

Dimitre Novatchev