Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.NET Extension Objects with XSLT -- how to iterate over a collection?

Tags:

c#

.net

email

xslt

Help me, Stackoverflow!

I have a simple .NET 3.5 console app that reads some data and sends emails. I'm representing the email format in an XSLT stylesheet so that we can easily change the wording of the email without needing to recompile the app.

We're using Extension Objects to pass data to the XSLT when we apply the transformation:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
    xmlns:EmailNotification="ext:EmailNotification">

-- this way, we can have statements like:

<p>
    Dear <xsl:value-of select="EmailNotification:get_FullName()" />:
</p>

The above works fine. I pass the object via code like this (some irrelevant code omitted for brevity):

// purely an example structure
public struct EmailNotification
{
    public string FullName { get; set; }
}

// Somewhere in some method ... 

var notification = new Notification("John Smith");

// ...

XsltArgumentList xslArgs = new XsltArgumentList();
xslArgs.AddExtensionObject("ext:EmailNotification", notification);

// ...

// The part where it breaks! (This is where we do the transformation)
xslt.Transform(fakeXMLDocument.CreateNavigator(), xslArgs, XmlWriter.Create(transformedXMLString));

So, all of the above code works. However, I wanted to get a little fancy (always my downfall) and pass a collection, so that I could do something like this:

<p>The following accounts need to be verified:</p>
<xsl:for-each select="EmailNotification:get_SomeCollection()">
    <ul>
        <li>
            <xsl:value-of select="@SomeAttribute" />
        </li>
    </ul>
<xsl:for-each>

When I pass the collection in the extension object and attempt to transform, I get the following error:

"Extension function parameters or return values which have Clr type 'String[]' are not supported."

or List, or IEnumerable, or whatever I try to pass in.

So, my questions are:

  1. How can I pass in a collection to my XSLT?

  2. What do I put for the xsl:value-of select="" inside the xsl:for-each ?

Is what I am trying to do impossible?


Edit: After I saw the two answers below, I took Chris Hynes' code and modified it very slightly to suit my needs. Solution is as follows:

// extension object
public struct EmailNotification
{
    public List<String> StringsSetElsewhere { private get; set; }
    public XPathNavigator StringsNodeSet
    {
        get
        {
            XmlDocument xmlDoc = new XmlDocument();
            XmlElement root = xmlDoc.CreateElement("Strings");
            xmlDoc.AppendChild(root);
            foreach (var s in StringsSetElsewhere)
            {
                XmlElement element = xmlDoc.CreateElement("String");
                element.InnerText = s;
                root.AppendChild(element);
            }
            return xmlDoc.CreateNavigator();
        }
    }
}

And in my XSLT ...

<xsl:for-each select="EmailNotification:get_StringsNodeSet()//String">
    <ul>
        <li>
            <xsl:value-of select="." />
        </li>
    </ul>
</xsl:for-each>

Worked perfectly!

like image 917
Pandincus Avatar asked Mar 16 '10 21:03

Pandincus


2 Answers

The XSLT for-each expects a node set to iterate over -- you can't directly pass an array back from your C# code. You should return an XPathNodeIterator.

Something like this:

public static XPathNodeIterator GetSomeCollection()
{    
    XmlDocument xmlDoc = new XmlDocument();

    string[] stringsToReturn = new string[] { "String1", "String2", "String3" };

    XmlElement root = xmlDoc.CreateElement("Strings");

    xmlDoc.AppendChild(root);

    foreach (string s in stringsToReturn)
    {
        XmlElement el = xmlDoc.CreateElement("String");

        el.InnerText = s;

        root.AppendChild(el);
    }

    XPathNodeIterator xNodeIt = xmlDoc.CreateNavigator().Select(".");

    return xNodeIt;
}
like image 123
Chris Hynes Avatar answered Oct 25 '22 04:10

Chris Hynes


The documentation states:

The parameter should correspond to a W3C type. The following table shows the W3C types, either XPath or XSLT, and the corresponding.NET class.

And lists the following types:

  • String
  • Boolean
  • Number
  • Result Tree Fragment
  • Node Set
  • Node

So, the answer is not you can't ... well at least not directly. But the best bet in your case is, IMHO, the Node or Node Set.

like image 2
AxelEckenberger Avatar answered Oct 25 '22 03:10

AxelEckenberger