Search code examples
xmlxsltxpath-1.0

How can I conditionally transform an XML node, without omitting its' instances outside of this condition?



I am transforming all  <p>  tags and renaming these nodes to  <Body_Text>, on the condition that they are within the  <body>  element (using  xsl:if).

My code is doing this, however it is omitting all  <p>  elements that are outside of the  <body>  element, which I don't want it to do.


Here is the XML to be transformed:

<root>
<p>asdf</p>
<body>
<p>asdfasdf</p>
<p>asdfasdf</p>
</body>
<footer>
<p>asdf</p>
</footer>
</root>


...here is the XSL:

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

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

    <!-- Body_Text -->
    <xsl:template match="p">
      <xsl:if test="parent::body">
        <Body_Text>
          <xsl:apply-templates select="@*|node()"/>
        </Body_Text>
      </xsl:if>
    </xsl:template>

</xsl:stylesheet>


...and here is the undesired result (omitting all  <p>  elements that are outside of the  <body>  element, which I don't want it to do):

<root>
<body>
<Body_Text>asdfasdf</Body_Text>
<Body_Text>asdfasdf</Body_Text>
</body>
<footer/>
</root>


This was tested at http://xslt.online-toolz.com/tools/xslt-transformation.php.


This is the desired result, which I am not getting:

<root>
<p>asdf</p>
<body>
<Body_Text>asdfasdf</Body_Text>
<Body_Text>asdfasdf</Body_Text>
</body>
<footer>
<p>asdf</p>
</footer>
</root>

Solution

  • No xsl:if is needed -- just specify proper template patterns.

    This transformation:

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     <xsl:output omit-xml-declaration="yes" indent="yes"/>
     <xsl:strip-space elements="*"/>
    
     <xsl:template match="node()|@*">
         <xsl:copy>
           <xsl:apply-templates select="node()|@*"/>
         </xsl:copy>
     </xsl:template>
    
     <xsl:template match="body//p">
      <Body_Text><xsl:apply-templates select="@*|node()"/></Body_Text>
     </xsl:template>
    </xsl:stylesheet>
    

    when applied on the provided XML document:

    <root>
        <p>asdf</p>
        <body>
            <p>asdfasdf</p>
            <p>asdfasdf</p>
        </body>
        <footer>
            <p>asdf</p>
        </footer>
    </root>
    

    produces the wanted, correct result:

    <root>
       <p>asdf</p>
       <body>
          <Body_Text>asdfasdf</Body_Text>
          <Body_Text>asdfasdf</Body_Text>
       </body>
       <footer>
          <p>asdf</p>
       </footer>
    </root>
    

    Explanation:

    The template pattern of:

     <xsl:template match="body//p">
    

    causes this template to be selected for execution only on p elements that are descendents of a body element. All the rest nodes are copied "as-is" by the identity rule.