DJL
DJL

Reputation: 2200

Calling specific template directly from code

From .NET code is it possible to directly invoke a specific XSLT template? i.e. ordinarily given code such as this: (VB.Net sorry, this is old code!)

Dim xsl As New System.Xml.Xsl.XslCompiledTransform()
xsl.Load(SomeXSLFile)
xsl.Transform(SomeXML, SomeArgs, SomeOutput)

The engine would process all templates in the XSL file according to whatever matches the XML document.

But say as part of my XSL file I had a named template:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/">
    <xsl:text>Some output</xsl:text>
    <xsl:call-template name="Boilerplate"/>
  </xsl:template>
  <xsl:template name="Boilerplate">
    <xsl:text>Generated by XSL</xsl:text>
  </xsl:template>
</xsl:stylesheet>

Would there be a way to invoke this named template directly from the .NET code (either supplying a document or by assuming an empty document)

I can work around this by having rules in the XSL to detect specific XML patterns (e.g. <CallTemplate name="Boilerplate"/>) and act accordingly but hoped there was a more natural way to do it?

Upvotes: 0

Views: 149

Answers (2)

DJL
DJL

Reputation: 2200

Similar to Flynn1179's answer I have instead taken the existing XSL file and replace the match=/ template with one that just calls the named template instead.

This code is rough and should be tidied up before use. Also it assumes that only one XMLReader will ever be passed in, but does not check this.

Private Shared dicStaticTemplateContent As New Dictionary(Of String, String)
Private Shared Function GetStaticTemplateContent(xmrXSLFile As System.Xml.XmlReader, strTemplateName As String) As String
    If Not dicStaticTemplateContent.ContainsKey(strTemplateName) Then
        Dim xmd As New System.Xml.XmlDocument
        xmd.Load(xmrXSLFile)
        Dim nsm As New System.Xml.XmlNamespaceManager(xmd.NameTable)
        nsm.AddNamespace("xsl", "http://www.w3.org/1999/XSL/Transform")
        Dim xmnRootTemplate As System.Xml.XmlNode = xmd.SelectSingleNode("/xsl:stylesheet/xsl:template[@match='/']", nsm)
        Dim xmeNewRoot As System.Xml.XmlElement = xmd.CreateElement("template", "http://www.w3.org/1999/XSL/Transform")
        If (xmnRootTemplate Is Nothing) Then
            xmnRootTemplate = xmd.SelectSingleNode("/xsl:stylesheet/xsl:template[1]", nsm)
            If (xmnRootTemplate Is Nothing) Then
                xmd.DocumentElement.AppendChild(xmeNewRoot) 'quite pointless actually, if there are no templates then the thing won't work anyway, but this is a shortcut towards providing a somewhat more meaningful error anyway (from the xsl engine)
            Else
                xmd.DocumentElement.InsertBefore(xmeNewRoot, xmnRootTemplate)
            End If
        Else
            xmnRootTemplate.ParentNode.ReplaceChild(xmeNewRoot, xmnRootTemplate)
        End If
            xmeNewRoot.InnerXml = "<xsl:call-template name=""" + strTemplateName + """/>"
            xmeNewRoot.SetAttribute("match", "/")

            Dim xsl As New System.Xml.Xsl.XslCompiledTransform()
            Dim xss As New System.Xml.Xsl.XsltSettings(True, True)
            xsl.Load(xmd, xss, Nothing)

            Dim strResult = ProcessTransform(xmd, xsl, Nothing, Nothing)

            dicStaticTemplateContent.Add(strTemplateName, strResult)
        End If
    Return dicStaticTemplateContent(strTemplateName)
End Function

Not all code paths have been tested (specifically where there is no match=/ template)

Also, beware of injection attacks on the template name

Upvotes: 0

Flynn1179
Flynn1179

Reputation: 12075

Realistically, there isn't a way to do this directly, but it might be possible to create a stylesheet which includes your target stylesheet using xsl:include or xsl:import, and then invokes the named template. Something like this:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:import href="(your stylesheet)"/>

  <xsl:template match="/">
    <xsl:call-template name="(template name)"/>
  </xsl:template>
</xsl:stylesheet>

I VERY rarely use xsl:include or xsl:import though, I'm really not 100% sure how this would work, there might be an order of precendence issue with the template matching "\" between this stylesheet and yours, but hopefully this points you in a useful direction.

Upvotes: 2

Related Questions