The XPath data model that XSLT uses doesn't allow to distinguish any CDATA sections -- any of these are represented just as (part of) a text node. So CDATA preservation cannot be achieved with XSLT or XPath alone in full generality. It could be achieved with a DOM based approach.
If in the output of the transformation CDATA sections are needed for the text nodes of elements with specific names, and not needed for others, then this is possible to accomplish in XSLT specifying a cdata-section-elements
attribute in the <xsl:output>
declaration.
Here is a short example how this is done:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"
cdata-section-elements="a b c d e"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<Record>
<a>10:30</a>
<b>20:30</b>
<c>10:60</c>
<d>1:15</d>
<e>1:03</e>
</Record>
the wanted, correct result is produced:
<Record>
<a><![CDATA[10:30]]></a>
<b><![CDATA[20:30]]></b>
<c><![CDATA[10:60]]></c>
<d><![CDATA[1:15]]></d>
<e><![CDATA[1:03]]></e>
</Record>
In case the set of element names aren't known in advance, one can use a stylesheet that produces another stylesheet, that should be finally applied to the XML document to produce the wanted result:
<xsl:stylesheet version="1.0" xmlns:x="http://www.w3.org/1999/XSL/Transform"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xxx="xxx">
<xsl:namespace-alias stylesheet-prefix="xxx" result-prefix="xsl"/>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kElemByName" match="*[text()[normalize-space()]]" use="name()"/>
<xsl:variable name="vDistinctNamedElems" select=
"//*[generate-id()=generate-id(key('kElemByName',name())[1])]"/>
<xsl:variable name="vDistinctNames">
<xsl:for-each select="$vDistinctNamedElems">
<xsl:value-of select="concat(name(), ' ')"/>
</xsl:for-each>
</xsl:variable>
<xsl:template match="node()|@*">
<xxx:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xxx:output omit-xml-declaration="yes" indent="yes"
cdata-section-elements="{$vDistinctNames}"/>
<xxx:strip-space elements="*"/>
<xxx:template match="node()|@*">
<xxx:copy>
<xxx:apply-templates select="node()|@*"/>
</xxx:copy>
</xxx:template>
</xxx:stylesheet>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the same XML document (above), the result is another XSLT stylesheet:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:x="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output omit-xml-declaration="yes" indent="yes" cdata-section-elements="a b c d e "/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
In this stylesheet all the element names in the cdata-section-elements
attribute, are dynamically generated (using the Muenchian method for grouping).
When we finally apply the so produced XSLT stylesheet on the same XML document, we get the wanted result:
<Record>
<a><![CDATA[10:30]]></a>
<b><![CDATA[20:30]]></b>
<c><![CDATA[10:60]]></c>
<d><![CDATA[1:15]]></d>
<e><![CDATA[1:03]]></e>
</Record>
Explanation:
Dynamically producing a new transformation, using the XSLT instruction xsl:namespace-alias
.
Muenchian grouping to determine all distinct element names.