views:

61

answers:

3

Hi,
For the below xml i want to render the unescaped text parts on browser from xslt e.g.:

<one>
&lt;text&gt;
</one>

I want the markup rendered on browser to be:

&lt;text&gt;

But when I use the apply templates which looks like below

<xsl:template match="text()" mode="literalHTML">
    <xsl:copy-of select=".">
    </xsl:copy-of>
</xsl:template>

The above XML is getting rendered as:

<text>

How can I modify this template so that it prints &lt;text&gt; on browser?

Best Regards, Keshav

+1  A: 

This can be achieved in XSLT 1.0 using quite tricky recursive processing.

Fortunately, one can use FXSL ( a library of XSLT templates) to solve the same task in just a few minutes:

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="http://fxsl.sf.net/"
xmlns:testmap="testmap"
exclude-result-prefixes="xsl f testmap">
   <xsl:import href="str-dvc-map.xsl"/>

   <testmap:testmap/>

   <xsl:output omit-xml-declaration="yes" indent="yes"/>

   <xsl:template match="/">
     <xsl:variable name="vTestMap" select="document('')/*/testmap:*[1]"/>
     <xsl:call-template name="str-map">
       <xsl:with-param name="pFun" select="$vTestMap"/>
       <xsl:with-param name="pStr" select="/*/text()"/>
     </xsl:call-template>
   </xsl:template>

    <xsl:template name="escape" mode="f:FXSL"
     match="*[namespace-uri() = 'testmap']">
      <xsl:param name="arg1"/>

      <xsl:choose>
       <xsl:when test="$arg1 = '&lt;'">&amp;lt;</xsl:when>
       <xsl:when test="$arg1 = '&gt;'">&amp;gt;</xsl:when>
       <xsl:otherwise><xsl:value-of select="$arg1"/></xsl:otherwise>
      </xsl:choose>
    </xsl:template>

</xsl:stylesheet>

when this transformation is applied on the following XML document:

<one>
&lt;text&gt;
</one>

the wanted result is produced:

&amp;lt;text&amp;gt;

and it displays in the browser as: &lt;text&gt;

Dimitre Novatchev
@dimitre,is it not possible to achieve this without using fsxl?
keshav.veerapaneni
@keshav.veerapaneni: It is possible, but this is the purpose of FXSL: never ever to have to write by hand any tedious, recursive processing (and possibly to commit errors) -- just to use a template that exists there for you. Also, my solution will not crash due to stack overflow even if the text is millions of characters long. The chosen template (see the name in the `<xsl-import>` instruction) uses DVC (Divide and Conquer) style recursion, which guarantees that the maximum recursion depth is only log2(N) -- 19 for N = 1000000 (1M).
Dimitre Novatchev
hi dimitre, i have also written a solution with recurion which i pasted above, colud you please suggest if there are any optimizaitons possible for overflow in it
keshav.veerapaneni
@keshav.veerapaneni: a DVC-style recursion (see Alejandro's code) would definitely be an improvement.
Dimitre Novatchev
+1  A: 

The "tricky recursive processing"

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <xsl:output method="xml" omit-xml-declaration="yes"/>
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="text()" name="text">
        <xsl:param name="text" select="."/>
        <xsl:if test="$text != ''">
            <xsl:variable name="first" select="substring($text,1,1)"/>
            <xsl:choose>
                <xsl:when test="$first = '&lt;'">&amp;lt;</xsl:when>
                <xsl:when test="$first = '&gt;'">&amp;gt;</xsl:when>
                <xsl:otherwise>
                    <xsl:value-of select="$first"/>
                </xsl:otherwise>
            </xsl:choose>
            <xsl:call-template name="text">
                <xsl:with-param name="text" select="substring($text,2,(string-length($text)-1) div 2 + 1)"/>
            </xsl:call-template>
            <xsl:call-template name="text">
                <xsl:with-param name="text" select="substring($text,(string-length($text)-1) div 2 + 3)"/>
            </xsl:call-template>
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>

Result:

<one>
&amp;lt;text&amp;gt;
</one>

EDIT: A DVC pattern to avoid overflow.

Alejandro
hi alejandro, will this not cause stackoverflow if the string is very long?
keshav.veerapaneni
Good solution, but see my answer to the comments of the OP to my answer. FXSL is a tool that saves you time and protects you from making errors (in the code that is already written for you that otherwise you'd have to write again and again).
Dimitre Novatchev
@Dimitre: I think FXSL is not only a proof of concept but also an excelent design pattern. It shows that Temporaly Result (like XSLT 2.0) its a better specification choice than Result Tree Fragment, because it allows data modeling. I've posted this answer because so many people (not you) are afraid of recursion.
Alejandro
@keshav.veerapaneni: I've update the answer with DVC pattern.
Alejandro
hi alejandro,i have also written a similar solution using recursion but was worried about the overflow, i have also pasted my solution below. could you please suggest if there are any optimizations possible
keshav.veerapaneni
A: 

I have also written a solution using recursion earlier but the only thing i was worried about was the performance and if there would be some memory or some stackoverflow because of this. I was wondering if there would be any solution using the <xsl:value-of> or <copy-of>? Also please let me know if there are any improvements possible in the below solution if the recursion could be converted to loop or etc.

<xsl:template match="text()" mode="literalHTML">
    <xsl:variable name="txt" select="."/>
    <xsl:value-of select="smc:escapeChar(smc:escapeChar(smc:escapeChar($txt,'&amp;','&amp;amp;'),'&lt;','&amp;lt;'),'&gt;','&amp;gt;')"/>
</xsl:template>
<xsl:function name="smc:escapeChar">
    <xsl:param name="txt"/>
    <xsl:param name="char"/>
    <xsl:param name="subs"/>
    <xsl:result>
        <xsl:variable name="result">
            <xsl:choose>
                <xsl:when test="contains($txt, $char)">
                    <xsl:variable name="after" select="substring-after($txt,$char)"/>
                    <xsl:value-of select="substring-before($txt,$char)"/>
                    <xsl:value-of select="$subs"/>
                    <xsl:value-of select="smc:escapeChar($after,$char,$subs)"/>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:value-of select="$txt"/>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:variable>
        <xsl:value-of select="$result"></xsl:value-of>
        </xsl:result>
</xsl:function>
keshav.veerapaneni
@keshav.veerapaneni: Of course that `contains()` is better because the processor would do some of the work, but this lead you to call the function for each character going over and over on the same string. The each character recursion allows node set comparation in XSLT 1.0 (replace each when/@test with only one with `$first = $characters`, been $characters some node set map even inline in the stylesheet or in other document). But for XSLT 2.0 (as your answer) it would be better to use character maps http://www.w3.org/TR/xslt20/#character-maps
Alejandro