Reputation: 21
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"><!--</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"><!--</xsl:text>
<child><name>commented child</name></child>
<xsl:text disable-output-escaping="yes">--></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
Reputation: 243599
Here is a transformation that can be used to comment out any XML node or fragment and that:
=============================
<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="'<!--
'"/>
<xsl:apply-templates select="*" mode="escapeToText"/>
<xsl:value-of select="'
-->'"/>
</xsl:template>
<xsl:template match="*" mode="escapeToText">
<xsl:value-of select="concat('<', name())"/>
<xsl:apply-templates select="@*" mode="escapeToText"/>
<xsl:value-of select="'>'"/>
<xsl:apply-templates select="node()" mode="escapeToText"/>
<xsl:value-of select="concat('</', name(), '>')"/>
</xsl:template>
<xsl:template match="@*" mode="escapeToText">
<xsl:value-of select="concat(' ', name(), '="')"/>
<xsl:call-template name="escapeDoubleDash">
<xsl:with-param name="pText" select="string(.)"/>
</xsl:call-template>
<xsl:value-of select="'"'"/>
</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('<?', name(), ' ', ., ' ?>')"/>
</xsl:template>
<xsl:template match="comment()">
<xsl:value-of select="concat('<!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:
method="text"
in the <xsl:output>
directive<MyComment>
elementHere 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="'<!--
'"/>
<xsl:apply-templates select="*" mode="escapeToText"/>
<xsl:value-of select="'
-->'"/>
</MyComment>
</xsl:template>
<xsl:template match="*" mode="escapeToText">
<xsl:value-of select="concat('<', name())"/>
<xsl:apply-templates select="@*" mode="escapeToText"/>
<xsl:value-of select="'>'"/>
<xsl:apply-templates select="node()" mode="escapeToText"/>
<xsl:value-of select="concat('</', name(), '>')"/>
</xsl:template>
<xsl:template match="@*" mode="escapeToText">
<xsl:value-of select="concat(' ', name(), '="')"/>
<xsl:call-template name="escapeDoubleDash">
<xsl:with-param name="pText" select="string(.)"/>
</xsl:call-template>
<xsl:value-of select="'"'"/>
</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('<?', name(), ' ', ., ' ?>')"/>
</xsl:template>
<xsl:template match="comment()">
<xsl:value-of select="concat('<!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