I attempted to implement the answer provided by Pavel Minaev and want to point out that this is very dangerous for large strings as each character in the input string is recursed over individually, causing the recursion depth to quickly run out. I attempted to run it over a few lines of text and it caused a stack overflow (lol).
Instead, I use a template that does not need to examine each individual char, rather it will out put the text until it finds a string that needs to be replaced. This can then be used to escape characters:
<xsl:template name="Search-And-Replace">
<xsl:param name="Input-String"/>
<xsl:param name="Search-String"/>
<xsl:param name="Replace-String"/>
<xsl:choose>
<xsl:when test="$Search-String and contains($Input-String, $Search-String)">
<xsl:value-of select="substring-before($Input-String, $Search-String)"/>
<xsl:value-of select="$Replace-String"/>
<xsl:call-template name="Search-And-Replace">
<xsl:with-param name="Input-String" select="substring-after($Input-String, $Search-String)"/>
<xsl:with-param name="Search-String" select="$Search-String"/>
<xsl:with-param name="Replace-String" select="$Replace-String"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$Input-String"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Then its just a matter of calling that template for the char that you want to escape..
<xsl:call-template name="Search-And-Replace">
<xsl:with-param name="Input-String" select="Hi I am a string & I am awesome"/>
<xsl:with-param name="Search-String" select="'&'"/>
<xsl:with-param name="Replace-String" select="'&amp;'"/>
</xsl:call-template>
In order to escape multiple characters in the one string, I used a wrapper template that uses variables...
<xsl:template name="EscapeText">
<xsl:param name="text" />
<xsl:variable name="a">
<xsl:call-template name="Search-And-Replace">
<xsl:with-param name="Input-String" select="$text"/>
<xsl:with-param name="Search-String" select="'&'"/>
<xsl:with-param name="Replace-String" select="'&amp;'"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="b">
<xsl:call-template name="Search-And-Replace">
<xsl:with-param name="Input-String" select="$a"/>
<xsl:with-param name="Search-String" select="'"'"/>
<xsl:with-param name="Replace-String" select="'&quot;'"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="c">
<xsl:call-template name="Search-And-Replace">
<xsl:with-param name="Input-String" select="$b"/>
<xsl:with-param name="Search-String">'</xsl:with-param>
<xsl:with-param name="Replace-String" select="'&apos;'"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="d">
<xsl:call-template name="Search-And-Replace">
<xsl:with-param name="Input-String" select="$c"/>
<xsl:with-param name="Search-String" select="'>'"/>
<xsl:with-param name="Replace-String" select="'&gt;'"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="e">
<xsl:call-template name="Search-And-Replace">
<xsl:with-param name="Input-String" select="$d"/>
<xsl:with-param name="Search-String" select="'<'"/>
<xsl:with-param name="Replace-String" select="'&lt;'"/>
</xsl:call-template>
</xsl:variable>
<!--this is the final output-->
<xsl:value-of select="$e"/>
</xsl:template>
This proved to be much safer for large strings as it no longer has to recurse for each individual character in the input string.