Preserving entity references when transforming XML with XSLT?

If you know what entities will be used and how they are defined, you can do the following (quite primitive and error-prone, but still better than nothing):

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:xs="http://www.w3.org/2001/XMLSchema"
 xmlns:my="my:my">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:character-map name="mapEntities">
  <xsl:output-character character="&amp;" string="&amp;"/>
 </xsl:character-map>

 <xsl:variable name="vEntities" select=
 "'stackoverflow',
 'How can I preserve the entity reference when transforming with XSLT\?\?'
 "/>

 <xsl:variable name="vReplacements" select=
 "'&amp;so;', '&amp;question;'"/>

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

 <xsl:template match="/">
  <xsl:text disable-output-escaping="yes"><![CDATA[<!DOCTYPE doc [ <!ENTITY so "stackoverflow">
<!ENTITY question
"How can I preserve the entity reference when transforming with XSLT??"> ]>
]]>
  </xsl:text>

  <xsl:apply-templates/>
 </xsl:template>

 <xsl:template match="text()">
  <xsl:value-of select=
  "my:multiReplace(.,
                   $vEntities,
                   $vReplacements,
                   count($vEntities)
                   )
  " disable-output-escaping="yes"/>
 </xsl:template>

 <xsl:function name="my:multiReplace">
  <xsl:param name="pText" as="xs:string"/>
  <xsl:param name="pEnts" as="xs:string*"/>
  <xsl:param name="pReps" as="xs:string*"/>
  <xsl:param name="pCount" as="xs:integer"/>

  <xsl:sequence select=
  "if($pCount > 0)
     then
      my:multiReplace(replace($pText,
                              $pEnts[1],
                              $pReps[1]
                              ),
                      subsequence($pEnts,2),
                      subsequence($pReps,2),
                      $pCount -1
                      )
      else
       $pText
  "/>
 </xsl:function>
</xsl:stylesheet>

when applied on the provided XML document:

<!DOCTYPE doc [ <!ENTITY so "stackoverflow">
<!ENTITY question
"How can I preserve the entity reference when transforming with XSLT??"> ]>
<doc>
    <text>Hello &so;!</text>
    <text>&question;</text>
</doc>

the wanted result is produced:

<!DOCTYPE doc [ <!ENTITY so "stackoverflow">
<!ENTITY question
"How can I preserve the entity reference when transforming with XSLT??"> ]>

  <doc>
      <text>Hello &so;!</text>
      <text>&question;</text>
</doc>

Do note:

  1. The special (RegEx) characters in the replacements must be escaped.

  2. We needed to resolve to DOE, which isn't recommended, because it violates the principles of the XSLT architecture and processing model -- in other words this solution is a nasty hack.


This can be an especially troublesome issue if you are using something like S1000D. It uses entities and @boardno attributes to link to figures. It's a throwback to its SGML roots.

Because this automatic entity expanding behavior, which is correct but undesireable, I often have to drop back to tools like sed, awk and batch scripts to manage certain data analysis tasks when using S1000D as input.

IMHO, this would be a great change proposal to one of the upcoming XSLT specifications that a compliant processor accept a runtime parameter that can turn on and off entitiy expansions.


If you use a Java implementation of an XSLT 2.0 processor (like Saxon 9 Java) you might want to check whether http://andrewjwelch.com/lexev/ helps out, you can preprocess your XML with entity and character references that way to get them marked up as XML elements you can then transform as necessary.


I use this solution and it works well :

<xsl:variable name="prolog" select="substring-before(unparsed-text(document-uri(.)),'&lt;root')"/>

<xsl:template match="/">
    <xsl:value-of select="$prolog" disable-output-escaping="yes"/>
  <xsl:apply-templates/>
</xsl:template>