tags:

views:

29

answers:

2

I want to wrap the children nodes of <foo/> that are not <bar/> or <baz/> with <corge/>.

Note: <bar/> and <baz/> will always be the first child nodes of <foo/>

Converting this:

<root>
    <foo>
       <bar>bar</bar>
       <baz>baz</baz>
       <qux>qux</qux>
       <grault>grault</grault>
    </foo>
     <foo>
       <bar>bar</bar>
       <baz>baz</baz>
       <qux>qux</qux>
       <quux>quux</quux>
    </foo>
</root>

to this:

<root>
    <foo>
       <bar>bar</bar>
       <baz>baz</baz>
       <corge>
           <qux>qux</qux>
           <grault>grault</grault>
       </corge>
    </foo>
    <foo>
       <bar>bar</bar>
       <baz>baz</baz>
       <corge>
           <qux>qux</qux>
           <quux>quux</quux>
       </corge>
    </foo>
</root>

What is a good way to do this using XSL?

+1  A: 

This stylesheet:

<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()|@*"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="foo/*"/>
    <xsl:template match="foo/bar|foo/baz">
        <xsl:call-template name="identity"/>
    </xsl:template>
    <xsl:template match="foo/*[not(self::bar or self::baz)][1]">
        <corge>
            <xsl:apply-templates select="../*[not(self::bar or self::baz)]"
                                 mode="corge"/>
        </corge>
    </xsl:template>
    <xsl:template match="foo/*" mode="corge">
        <xsl:call-template name="identity"/>
    </xsl:template>
</xsl:stylesheet>

Output:

<root>
    <foo>
        <bar>bar</bar>
        <baz>baz</baz>
        <corge>
            <qux>qux</qux>
            <grault>grault</grault>
        </corge>
    </foo>
    <foo>
        <bar>bar</bar>
        <baz>baz</baz>
        <corge>
            <qux>qux</qux>
            <quux>quux</quux>
        </corge>
    </foo>
</root>

Note: Pull style and modes for applying templates. If there is no element for wrapping, corge element will be not output.

Other stylesheet (compact code, less reusable):

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <xsl:template match="root">
        <root>
            <xsl:apply-templates/>
        </root>
    </xsl:template>
    <xsl:template match="foo">
        <foo>
            <xsl:copy-of select="bar|baz"/>
            <corge>
                <xsl:copy-of select="*[not(self::bar or self::baz)]"/>
            </corge>
        </foo>
    </xsl:template>
</xsl:stylesheet>
Alejandro
+1  A: 

This is how I would've done it...

XML

<?xml version="1.0" encoding="UTF-8"?>
<root>
  <foo>
    <bar>bar</bar>
    <baz>baz</baz>
    <qux>qux</qux>
    <grault>grault</grault>
  </foo>
  <foo>
    <bar>bar</bar>
    <baz>baz</baz>
    <qux>qux</qux>
    <quux>quux</quux>
  </foo>
</root>

XSL

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
  <xsl:output indent="yes"/>
  <xsl:strip-space elements="*"/>
  <xsl:template match="node()|@*">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*"/>
    </xsl:copy>
  </xsl:template>
  <xsl:template match="foo">
    <foo>
      <xsl:apply-templates select="bar|baz"/>
      <corge>
        <xsl:apply-templates select="*[name() != 'bar' and name() != 'baz']"/>
      </corge>
    </foo>
  </xsl:template>
</xsl:stylesheet>

OUTPUT

<?xml version="1.0" encoding="UTF-8"?>
<root>
   <foo>
      <bar>bar</bar>
      <baz>baz</baz>
      <corge>
         <qux>qux</qux>
         <grault>grault</grault>
      </corge>
   </foo>
   <foo>
      <bar>bar</bar>
      <baz>baz</baz>
      <corge>
         <qux>qux</qux>
         <quux>quux</quux>
      </corge>
   </foo>
</root>
DevNull
+1 for push style example.
Alejandro