Oscar Levin
Oscar Levin

Reputation: 21

How to include xml inside xsl:comment and maintain indents

I'm writing some xsl to transform a book outline in xml into a bunch of individual xml files (one for each chapter, plus some frontmatter, etc). I'll do this with <exsl:document>, and much of the content of the individual files will be written inside the xsl.

I use xsltproc, so xslt 1.0.

I want text comments, which I can get using <xsl:comment>, but also some xml that is "commented out". As mentioned in this question, this is not possible using <xsl:comment>.

The answer to that question uses <xsl:text disable-output-escaping="yes">&lt;!--</xsl:text> to wrap the commented xml. This works, except that as soon as you add it, the output no longer gets indented correctly.

For example, the following xsl:

<?xml version='1.0' encoding="UTF-8"?>

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

<xsl:template match="/">
<root>
  <xsl:comment>Text comment</xsl:comment>
  <child><name>A child</name></child>
  <xsl:text disable-output-escaping="yes">&lt;!--</xsl:text>
  <child><name>commented child</name></child>
  <xsl:text disable-output-escaping="yes">--&gt;</xsl:text>
</root>
</xsl:template>

</xsl:stylesheet>

gives the xml inside the comment, but no indentation:

<root><!--Text comment--><child><name>A child</name></child><!--<child><name>commented child</name></child>--></root>

while using this:

<xsl:template match="/">
<root>
  <xsl:comment>Text comment</xsl:comment>
  <child><name>A child</name></child>
  <xsl:comment><child><name>commented child</name></child></xsl:comment>
</root>
</xsl:template>

gives nice indentation, but no xml tags in the comment:

<?xml version="1.0" encoding="UTF-8"?>
<root>
  <!--Text comment-->
  <child>
    <name>A child</name>
  </child>
  <!--commented child-->
</root>

Is there any way to keep indenting but put xml code inside comments?

Upvotes: 2

Views: 770

Answers (1)

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243599

Here is a transformation that can be used to comment out any XML node or fragment and that:

  1. Does not use DOE
  2. Preserves indentation
  3. Even tries to express nested comments -- doesn't ignore comments or throw an error when a comment is encountered for commenting:

=============================

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="text" indent="yes"/>

  <xsl:template match="/">
    <xsl:value-of select="'&lt;!--&#xA;'"/>
      <xsl:apply-templates select="*" mode="escapeToText"/>
    <xsl:value-of select="'&#xA;-->'"/>
  </xsl:template>


  <xsl:template match="*" mode="escapeToText">
    <xsl:value-of select="concat('&lt;', name())"/>

    <xsl:apply-templates select="@*" mode="escapeToText"/>
    <xsl:value-of select="'>'"/>

    <xsl:apply-templates select="node()" mode="escapeToText"/>
    <xsl:value-of select="concat('&lt;/', name(), '>')"/>
  </xsl:template>

  <xsl:template match="@*" mode="escapeToText">
    <xsl:value-of select="concat(' ', name(), '=&quot;')"/>
    <xsl:call-template name="escapeDoubleDash">
      <xsl:with-param name="pText" select="string(.)"/>
    </xsl:call-template>
    <xsl:value-of select="'&quot;'"/>
  </xsl:template>

  <xsl:template match="text()" mode="escapeToText">
    <xsl:call-template name="escapeDoubleDash">
      <xsl:with-param name="pText" select="."/>
    </xsl:call-template>
  </xsl:template>

  <xsl:template match="processing-instruction()" mode="escapeToText">
    <xsl:value-of select="concat('&lt;?', name(), ' ', ., ' ?>')"/>
  </xsl:template>

  <xsl:template match="comment()">
    <xsl:value-of select="concat('&lt;!CM ', ., 'CM>')"/>
  </xsl:template>

  <xsl:template name="escapeDoubleDash">
   <xsl:param name="pText"/>

   <xsl:choose>
     <xsl:when test="contains($pText, '--')">
       <xsl:value-of select="substring-before($pText, '--')"/>
       <xsl:value-of select="'!-!-'"/>
       <xsl:call-template name="escapeDoubleDash">
         <xsl:with-param name="pText" select="substring-after($pText, '--')"/>
       </xsl:call-template>
     </xsl:when>
     <xsl:otherwise><xsl:value-of select="$pText"/></xsl:otherwise>
   </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

When this transformation is applied against any source xml document, for example this:

<input>
    <name>Jack</name>
    <age>23</age>
    <type-10-num>1</type-10-num>
    <type-20-num>2</type-20-num>
    <type-20-char>3</type-20-char>
    <type-180-num>4</type-180-num>
    <type-180-char>5</type-180-char>
    <type-180-str>6</type-180-str>
</input>

the wanted, indented result is produced:

<!--
<input>
    <name>Jack</name>
    <age>23</age>
    <type-10-num>1</type-10-num>
    <type-20-num>2</type-20-num>
    <type-20-char>3</type-20-char>
    <type-180-num>4</type-180-num>
    <type-180-char>5</type-180-char>
    <type-180-str>6</type-180-str>
</input>
-->

Here is how a commented-out, very complex XML document looks like -- let's apply this transformation on ... itself.

For completeness, I have added a comment and a processing instruction before the template matching attributes:

  <?somePI x="y" z="t" pqr ?>

  <!-- A Comment node -->

And the result is as expected:

<!--
<xsl:stylesheet version="1.0">
 <xsl:output method="text" indent="yes"></xsl:output>

  <xsl:template match="/">
    <xsl:value-of select="'<!!-!-
'"></xsl:value-of>
      <xsl:apply-templates select="*" mode="escapeToText"></xsl:apply-templates>
    <xsl:value-of select="'
!-!->'"></xsl:value-of>
  </xsl:template>


  <xsl:template match="*" mode="escapeToText">
    <xsl:value-of select="concat('<', name())"></xsl:value-of>

    <xsl:apply-templates select="@*" mode="escapeToText"></xsl:apply-templates>
    <xsl:value-of select="'>'"></xsl:value-of>

    <xsl:apply-templates select="node()" mode="escapeToText"></xsl:apply-templates>
    <xsl:value-of select="concat('</', name(), '>')"></xsl:value-of>
  </xsl:template>

  <?somePI x="y" z="t" pqr  ?>

  <!CM  A Comment node CM>
  <xsl:template match="@*" mode="escapeToText">
    <xsl:value-of select="concat(' ', name(), '="')"></xsl:value-of>
    <xsl:call-template name="escapeDoubleDash">
      <xsl:with-param name="pText" select="string(.)"></xsl:with-param>
    </xsl:call-template>
    <xsl:value-of select="'"'"></xsl:value-of>
  </xsl:template>

  <xsl:template match="text()" mode="escapeToText">
    <xsl:call-template name="escapeDoubleDash">
      <xsl:with-param name="pText" select="."></xsl:with-param>
    </xsl:call-template>
  </xsl:template>

  <xsl:template match="processing-instruction()" mode="escapeToText">
    <xsl:value-of select="concat('<?', name(), ' ', ., ' ?>')"></xsl:value-of>
  </xsl:template>

  <xsl:template match="comment()">
    <xsl:value-of select="concat('<!CM ', ., 'CM>')"></xsl:value-of>
  </xsl:template>

  <xsl:template name="escapeDoubleDash">
   <xsl:param name="pText"></xsl:param>

   <xsl:choose>
     <xsl:when test="contains($pText, '!-!-')">
       <xsl:value-of select="substring-before($pText, '!-!-')"></xsl:value-of>
       <xsl:value-of select="'!-!-'"></xsl:value-of>
       <xsl:call-template name="escapeDoubleDash">
         <xsl:with-param name="pText" select="substring-after($pText, '!-!-')"></xsl:with-param>
       </xsl:call-template>
     </xsl:when>
     <xsl:otherwise><xsl:value-of select="$pText"></xsl:value-of></xsl:otherwise>
   </xsl:choose>
  </xsl:template>
</xsl:stylesheet>
-->

Update:

In case you want the commented result to appear as part of an XML document -- that is, not to have <xsl:output method="text"/>, I suggest adding a special element, say <MyComment> and produce the result of the commenting as a text node child of <MyComment>. Generally, the xml fragment that is commented will appear with special characters escaped.

But there is one trick -- use CDATA section -- then you will see the text unescaped. This is best seen in an example:

The updated transformation is almost identical to the one provided above, but:

  1. There is no method="text" in the <xsl:output> directive
  2. The commented text is a text node of a <MyComment> element
  3. All "commented XML" -- actually represented as text appears in a CDATA section and is unescaped

Here is the modified transformation:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output indent="yes" cdata-section-elements="MyComment"/>

  <xsl:template match="/">
   <MyComment>
     <xsl:value-of select="'&lt;!--&#xA;'"/>
       <xsl:apply-templates select="*" mode="escapeToText"/>
     <xsl:value-of select="'&#xA;-->'"/>
    </MyComment>
  </xsl:template>


  <xsl:template match="*" mode="escapeToText">
    <xsl:value-of select="concat('&lt;', name())"/>

    <xsl:apply-templates select="@*" mode="escapeToText"/>
    <xsl:value-of select="'>'"/>

    <xsl:apply-templates select="node()" mode="escapeToText"/>
    <xsl:value-of select="concat('&lt;/', name(), '>')"/>
  </xsl:template>

  <xsl:template match="@*" mode="escapeToText">
    <xsl:value-of select="concat(' ', name(), '=&quot;')"/>
    <xsl:call-template name="escapeDoubleDash">
      <xsl:with-param name="pText" select="string(.)"/>
    </xsl:call-template>
    <xsl:value-of select="'&quot;'"/>
  </xsl:template>

  <xsl:template match="text()" mode="escapeToText">
    <xsl:call-template name="escapeDoubleDash">
      <xsl:with-param name="pText" select="."/>
    </xsl:call-template>
  </xsl:template>

  <xsl:template match="processing-instruction()" mode="escapeToText">
    <xsl:value-of select="concat('&lt;?', name(), ' ', ., ' ?>')"/>
  </xsl:template>

  <xsl:template match="comment()">
    <xsl:value-of select="concat('&lt;!CM ', ., 'CM>')"/>
  </xsl:template>

  <xsl:template name="escapeDoubleDash">
   <xsl:param name="pText"/>

   <xsl:choose>
     <xsl:when test="contains($pText, '--')">
       <xsl:value-of select="substring-before($pText, '--')"/>
       <xsl:value-of select="'!-!-'"/>
       <xsl:call-template name="escapeDoubleDash">
         <xsl:with-param name="pText" select="substring-after($pText, '--')"/>
       </xsl:call-template>
     </xsl:when>
     <xsl:otherwise><xsl:value-of select="$pText"/></xsl:otherwise>
   </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

When we apply this transformation to the following XML document:

<input>
    <name>Jack</name>
    <age>23</age>
    <type-10-num>1</type-10-num>
    <type-20-num>2</type-20-num>
    <type-20-char>3</type-20-char>
    <type-180-num>4</type-180-num>
    <type-180-char>5</type-180-char>
    <type-180-str>6</type-180-str>
</input>

The wanted, unescaped commenting is produced:

<MyComment><![CDATA[<!--
<input>
    <name>Jack</name>
    <age>23</age>
    <type-10-num>1</type-10-num>
    <type-20-num>2</type-20-num>
    <type-20-char>3</type-20-char>
    <type-180-num>4</type-180-num>
    <type-180-char>5</type-180-char>
    <type-180-str>6</type-180-str>
</input>
-->]]></MyComment>

We can get rid of the XML comments (<!-- -->) and the code that processes "nested comments" -- this is no longer needed.

Upvotes: 1

Related Questions