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">
<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 Answers 2

1

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>
Sign up to request clarification or add additional context in comments.

1 Comment

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.
1

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.

3 Comments

select="@*|node()[not(self::notes or self::othernotes)]" ;-) (though in this example select="info" would have the same effect...)
@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.)
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. :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.