tags:

views:

48

answers:

2

Looking to use XSLT to transform my XML. The sample XML is as follows:

<root>
<info>
    <firstname>Bob</firstname>
    <lastname>Joe</lastname>
</info>
<notes>
    <note>text1</note>
    <note>text2</note>
</notes>
<othernotes>
    <note>text3</note>
    <note>text4</note>
</othernotes>

I'm looking to extract all "note" elements, and have them under a parent node "notes".

The result I'm looking for is as follows:

<root>
<info>
    <firstname>Bob</firstname>
    <lastname>Joe</lastname>
</info>
<notes>
    <note>text1</note>
    <note>text2</note>
    <note>text3</note>
    <note>text4</note>
</notes>
</root>

The XSLT I attempted to use is allowing me to extract all my "note", however, I can't figure out how I can wrap them back within a "notes" node.

Here's the XSLT I'm using:

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

    <xsl:template match="notes|othernotes">
        <xsl:apply-templates select="note"/>
    </xsl:template>
    <xsl:template match="*">
    <xsl:copy><xsl:apply-templates/></xsl:copy>
    </xsl:template>

</xsl:stylesheet>

The result I'm getting with the above XSLT is:

<root>
<info>
    <firstname>Bob</firstname>
    <lastname>Joe</lastname>
</info>
    <note>text1</note>
    <note>text2</note>
    <note>text3</note>
    <note>text4</note>
</root>

Thanks

+2  A: 

You can generate elements like this:

<xsl:element name="notes">
   <!-- inject content of notes element here using e.g. <xsl:copy> or <xsl:copy-of> -->
</xsl:element>

With slight modification the above approach works also for generating elements in a specific XML namespace. However since you are not looking to generate elements in namespaces there exists a shortcut:

<notes>
  <!-- inject content of notes element here using e.g. <xsl:copy> or <xsl:copy-of> -->
</notes>

In your specific example I would restructure your stylesheet to do the following:

<xsl:template match="root">
   <root>
     <xsl:copy-of select="info"/>
     <notes>
        <xsl:copy-of select="*/note"/>
     </notes>
   </root>
</xsl:template>
Nice and clean, +1. I would use <xsl:copy> in place of re-creating <root>, matter of personal preference I guess. Also, to create elements in a specific namespace, you can easily declare a prefix and use it: `<ns:root>`. `<xsl:element>` is really only needed if the element name should be dynamic.
Tomalak
Thanks so much. Brian
Brian
+1  A: 

You'll be looking for something like this:-

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

<xsl:template match="/root">
  <xsl:copy>
     <xsl:apply-templates select="@*|node()[local-name() != 'notes' and local-name() != 'othernotes']
  </xsl:copy>
  <notes>
     <xsl:apply-templates select="othernotes/note | notes/note" />
  </notes>
</xsl:template>

You take control of the structure of the root node. First copy everything under the root that isn't named "notes" or "othernote". Then directly create a "notes" element, then union all the "note" elements that are under either a "othernotes" or a "notes" element.

AnthonyWJones
`select="@*|node()[not(self::notes or self::othernotes)]"` ;-) (though in this example `select="info"` would have the same effect...)
Tomalak
@Tomalak: quite so in fact I did have that but reversed it back to using local-name, I can't think why now. @user268396's solution works in the very specific example, I was going for minimum assumptions, (other siblings to info may be present, info might contain a note element not subject to this merging etc.)
AnthonyWJones
That's why I gave +1 to the solution. I had a similar one ready but you were faster, so I did not post it. :)
Tomalak