0
votes

I have a XSLT document that I am using to transform one XML document into another XML document. Part of the transformation takes some of the input XML, escapes it (e.g. <mytag someattribute="value"/> converts to &lt;mytag someattribute=\"value\"/&gt;) and then inserts it into the output XML document somewhere.

The problem that I am having is that if a tag has a namespace specified, it skips the namespace during the escaping process.

I am performing the transformation in a .NET application (Framework 4.0) using System.Xml.Xsl.XslCompiledTransform to perform the transformation. (It only supports XSLT 1.0).

My Templates

<xsl:template match="*" mode="serialize">
    <xsl:text>&lt;</xsl:text>
    <xsl:value-of select="name()"/>
    <xsl:apply-templates select="@*" mode="serialize" />
    <xsl:choose>
        <xsl:when test="node()">
            <xsl:text>&gt;</xsl:text>
            <xsl:apply-templates mode="serialize" />
            <xsl:text>&lt;/</xsl:text>
            <xsl:value-of select="name()"/>
            <xsl:text>&gt;</xsl:text>
        </xsl:when>
        <xsl:otherwise>
            <xsl:text> /&gt;</xsl:text>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

<xsl:template match="@*" mode="serialize">
    <xsl:text> </xsl:text>
    <xsl:value-of select="name()"/>
    <xsl:text>=\"</xsl:text>
    <xsl:value-of select="."/>
    <xsl:text>\"</xsl:text>
</xsl:template>

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

Example Input XML

<link xmlns:xlink="http://www.w3.org/1999/xlink" 
    action="goto" xlink:href="http://localhost"/>

Expected Output

&ltlink xmlns:xlink=\"http://www.w3.org/1999/xlink\" action=\"goto\" xlink:href=\"http://localhost\"/&gt;

Actual Output

&ltlink action=\"goto\" xlink:href=\"http://localhost\"/&gt;

How can I change the templates so that the namespaces are output as well?

1
namespace-uri() will give you the namesapce of the node, so just maybe another value-of? - Jacob
Well, which version of XSLT do you use, which version of which XSLT processor? Perhaps there is support in XSLT/XPath directly or there is easy access to an extension function. If you want to do it in pure XSLT, we need to know whether there is support for the namespace axis. You might want to check existing solutions like lenzconsulting.com/xml-to-string. - Martin Honnen
@MartinHonnen I'm doing the transformation in a .NET application so I'm using .NET Framework 4.0 (System.Xml.Xsl.XslTransform). It supports XSL 1.0. - Steve Kalemkiewicz
XslTransform has been obsoleted years ago by XslCompiledTransform but in both processors you can add extension script or objects which could employ msdn.microsoft.com/en-us/library/… or use an XmlWriter. That should give you a good XML serialization including namespaces and needed XML escaping of e.g. the ampersand. Otherwise use the XPath namespace axis if you want to find namespace info with pure XSLT/XPath 1.0. - Martin Honnen
See lenzconsulting.com/xml-to-string/xml-to-string.xsl and any namespace::* use on how to do it with pure XSLT/XPath to output namespace declarations. If you simply want XML serialization then you can pass your node from XSLT to a .NET function taking an XPathNavigator or XPathNodeIterator and simply use the OuterXml and return that to XSLT from your .NET function (docs.microsoft.com/en-us/dotnet/standard/data/xml/…) - Martin Honnen

1 Answers

0
votes

Using @MartinHonnen's advice I found that I could modify my existing XSLT slightly to solve the problem. I added a new template to apply to the root tag of the XML I was trying to escape (I could have modified the existing match="*" template but I would get the namespace on all tags) to handle the namespaces:

<xsl:template match="myroottag" mode="serialize">
    <xsl:text>&lt;</xsl:text>
    <xsl:value-of select="name()"/>
    <xsl:text> </xsl:text>
    <!-- This for-each adds the namespaces -->
    <xsl:for-each select="namespace::*">
        <xsl:text> xmlns:</xsl:text>
        <xsl:value-of select="name()"/>
        <xsl:text>=\"</xsl:text>
        <xsl:value-of select="."/>
        <xsl:text>\" </xsl:text>
    </xsl:for-each>
    <xsl:apply-templates select="@*" mode="serialize" />
    <xsl:choose>
        <xsl:when test="node()">
            <xsl:text>&gt;</xsl:text>
            <xsl:apply-templates mode="serialize" />
            <xsl:text>&lt;/</xsl:text>
            <xsl:value-of select="name()"/>
            <xsl:text>&gt;</xsl:text>
        </xsl:when>
        <xsl:otherwise>
            <xsl:text> /&gt;</xsl:text>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>