XML, XSL, XAML, XPS......XACTLY?
For some reason at every company I have worked for I have been dubbed the "Reporting dude." I don't know how this title was bestowed on me but so be it. Unfortunately reports aren't particularly sexy, you never hear people saying "Whoa check out that pivot table!"
All that aside, recently I came across a reporting challenge that I thought was particularly interesting. Currently in WPF there isn't much in the way of report designers (at least none that I have seen). We do have some very simple integration with the XPS framework, which makes printing XAML very easy. However, all the examples out there of generating an XPS document relies on the fact that your created the XAML objects in code. Ok, but that goes against a major tenet of WPF of separating the design and the implementation.
The challenge I was faced with was to create a reporting architecture in WPF that would support changes to report formats without invasive code changes, Support multiple output formats if desired (XPS, HTML, Excel, etc..) and the nice to have feature is to allow reports to be built using a visual designer if possible.
Well looking at the requirements and the toolset available first thought we were going to have to make a lot of compromises. I thought some more about it and took a lesson from my web developer toolset. Why not use XML to model the data and XSL to transform the data to the desired format?
I knew this would work for HTML and Excel outputs, what I didn't know was how it would work for XAML. So I gave it a go and here is how it went.
1. Model some test data in XML:
We need a sample chunk of data to work with in order to test our idea. So I started with this :
<?xml version="1.0" encoding="utf-8" ?>
<EmployeeInfo>
<Employee>
<FirstName>Bob</FirstName>
<LastName>Smith</LastName>
<City>San Diego</City>
<State>CA</State>
</Employee>
</EmployeeInfo>
2. Transform the data in to a XAML document:
Now that we have sample data to work with we need to write and XSLT document that will take the XML data above and turn it into a XAML document (I wasn't sure what I was going to get with this step. I didn't know how nicely XSL would play with XAML.)
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="EmployeeInfo">
<StackPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Orientation="Vertical">
<TextBlock>
<TextBlock.Text>
<xsl:value-of select="Employee/FirstName"/>
</TextBlock.Text>
</TextBlock>
<TextBlock>
<TextBlock.Text>
<xsl:value-of select="Employee/LastName"/>
</TextBlock.Text>
</TextBlock>
<TextBlock>
<TextBlock.Text>
<xsl:value-of select="Employee/City"/>
</TextBlock.Text>
</TextBlock>
<TextBlock>
<TextBlock.Text>
<xsl:value-of select="Employee/State"/>
</TextBlock.Text>
</TextBlock>
</StackPanel>
</xsl:template>
</xsl:stylesheet>
3. From here the rest is fairly straight forward. You do the actual transformation in code (something like this)
XmlReader xreader = XmlReader.Create("SampleData.xml");
MemoryStream mem = new MemoryStream();
TextWriter twriter = new StreamWriter(mem, System.Text.Encoding.UTF8);
XslCompiledTransform trans = new XslCompiledTransform();
trans.Load("XAMLOutput.xslt");
trans.Transform(xreader, null, twriter);
4. From there all we have to do is to take the transformed output and turn it into an XPS document. What I did was take the transformed output and get it in string format and then created a function to create an XPS document
void XamlToXPS(string srcXaml,string destXpsFile)
{
StringReader stringReader = new StringReader(srcXaml);
XmlReader xmlReader = XmlReader.Create(stringReader);
object parsedDocObject = XamlReader.Load(xmlReader);
XpsDocument document = new XpsDocument(destXpsFile, FileAccess.ReadWrite);
XpsPackagingPolicy packagePolicy = new XpsPackagingPolicy(document);
XpsSerializationManager serializationMgr = new XpsSerializationManager(packagePolicy, false);
serializationMgr.SaveAsXaml(parsedDocObject);
document.Close();
}
That's it!
I tested this much more complex chunks of xaml and didn't find any issues. One thing to note is that you have to make sure the XAML namespace is specified for the root element of the document in order for the
XAML reader to understand the XAML elements (see the bolded area of my sample XSLT).
This solution separates the data from the implementation, it allows for format changes to the report without invasive code changes, and it adds the nice to have feature of allowing report design through a visual designer. Since XPS is simply loose xaml packaged up, you can design the report using Blend. Then take that xaml and paste it into the XSLT file and manipulate it to show the data from the XML where needed.