tags:

views:

307

answers:

2

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!

+5  A: 

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;
}
Chris Hynes
Thanks! I'm going to check that out right now.
Pandincus
@Chris Hynes: Your answer and sample code were exactly what I needed. I modified your sample code a little bit because you can't set the Value property of an XmlElement (apparently), and I'll post my code in my answer momentarily.Obalix's solution was also correct, but I can only accept one answer and the sample code was really above-and-beyond -- made my life easy. Thanks Chris!
Pandincus
Modified accordingly -- I didn't actually run the code, just wrote it out * blush *.
Chris Hynes
You can also return `XPathNavigator` or `XPathNavigator[]`, which is easier than returning `XPathNodeIterator`, and you don't need to call `Select(".")`
Max Toro
+2  A: 

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.

Obalix
Why I didn't look at the documentation for XsltArgumentList.AddParam is beyond me. (Too late in the day?) I'm going to check out returning a Node or NodeSet and see if that works. Thank you!
Pandincus