4

I have an xml document

 <body>
      <heading>Main Heading</heading>
      <para>
       <text>para 1</text>
      </para>
      <para>
        <heading>heading 2</heading>
        <text> para 2</text>
      </para>
      <para>
         <heading>heading 3</heading>
         <text>para 3</text>
       </para>
    </body>

I want to match the first occurrence of heading element and add some text to it. I just want the first occurrence of heading and heading should be a child node of para element.

so the output should be like below

 <body>
      <heading>Main Heading</heading>
      <para>
       <text>para 1</text>
      </para>
     <para>
        <heading>**This is a first heading found** heading 2</heading>
        <text> para 2</text>
      </para>
     <para>
    <heading>heading 3</heading>
        <text>para 3</text>
      </para>
    </body>
Nimantha
  • 6,405
  • 6
  • 28
  • 69
atif
  • 1,137
  • 7
  • 22
  • 35
  • Technically the first occurrence of `` element is the one for Main Heading. So your requirement is the first heading element which is a child of ``? – Adolfo Perez Mar 28 '13 at 16:31

2 Answers2

7

This should do what you want:

para[heading][1]/heading[1]

It would match the first heading element inside the first para-that-contains-any-headings.

Example XSLT:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:template match="@*|node()">
    <xsl:copy><xsl:apply-templates select="@*|node()" /></xsl:copy>
  </xsl:template>

  <xsl:template match="para[heading][1]/heading[1]">
    <xsl:copy>**First heading** <xsl:apply-templates select="@*|node()" /></xsl:copy>
  </xsl:template>
</xsl:stylesheet>

Output on your example:

<body>
      <heading>Main Heading</heading>
      <para>
       <text>para 1</text>
      </para>
      <para>
        <heading>**First heading** heading 2</heading>
        <text> para 2</text>
      </para>
      <para>
         <heading>heading 3</heading>
         <text>para 3</text>
       </para>
    </body>

If there might be <para> elements elsewhere in the XML than just nested directly under the <body> then you would be safer with a more specific match expression, namely

/body/para[heading][1]/heading[1]
Ian Roberts
  • 120,891
  • 16
  • 170
  • 183
0

You need to work with the position() of para since your first <heading> is a child of <para>. And para occurs once before it has a child called <heading> so you need the second <para>.

Apply this XSLT:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">

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

<xsl:template match="//para[position()=2]/heading">
    <xsl:element name="heading">
        <xsl:value-of select="concat('**This is a first heading found**',' ',.)"/>
    </xsl:element>
</xsl:template>


</xsl:stylesheet>

to your source XML and you will get this output:

<?xml version="1.0" encoding="UTF-8"?>
<body>
<heading>Main Heading</heading>
<para>
    <text>para 1</text>
</para>
<para>
    <heading>**This is a first heading found** heading 2</heading>
    <text> para 2</text>
</para>
<para>
    <heading>heading 3</heading>
    <text>para 3</text>
</para>
</body>

PS: I just noticed you want XSLT 2.0 - my solution is XSLT 1.0. I am not sure if there is a difference.

Nimantha
  • 6,405
  • 6
  • 28
  • 69
Peter
  • 1,786
  • 4
  • 21
  • 40
  • Thanks peter, but cant we have a more generic solution instead of saying position =2 because it might happen that the input will have heading in para 3 instead of 2 so although this work but I am looking for more generic solution. – atif Mar 28 '13 at 16:35