Recently I finished a project where I was working pretty extensively with XML technologies, specifically XSLT and XSL-FO. My design called for reading in a variety of XSLT templates, hydrating them with XML data and converting them to an FO file for PDF processing. There was various issues here including some manual massaging that was necessary. First of all, I was using StyleVision from Altova to create the initial XSL-FO templates. That worked out great except that I needed to insert some manual XSL code into the resulting templates and I did not want to deal with StyleVision overwriting my XSL-FO files everytime I made changes to the templates. Since I was involved in the architecture of the provider model that was to process everything I've described, I had the ability to make manual adjustments to the XSL-FO files before hydrating them with XML data. One of the things I had to manually insert is some conditional code that checks the XML data and decides whether or not the template should have a watermark on it. Coming up with the code for this was not an big deal and I Googled for about 5 minutes and found it. Knowing where to insert it wasn't that difficult either. I got ahold of a short XSL-FO tutorial that described the different sections of a standard XSL-FO file and I quicky realized where I had to insert the code for my watermarks. The tricky part was doing all of this using the XML namespaces in the .NET Framework. What's the big deal you ask? Well, the problem was dealing with namespaces. Normally when you're dealing with an XML file (XSL, etc.) that has tags identified by non-default namespaces, such as the “xsl:” and “fo:” namespaces, you have to create a NamespaceManager object and use that in your queries. The SelectNodes and SelectSingleNodes methods have an argument that you can pass this object into and thus query the XML file where different namespaces are concerned. The problem is in creating an XML code fragment that contains tags in one namespace and injecting it into an XML file that may already have one or more namespaces defined. After a day of messing with this, I figured out a solution which worked quite well, and this quickly led me to a second solution that works as well.
First of all, what's an XML fragment? The System.Xml namespaces give us a cool object called XmlDocumentFragment which is created by using the CreateDocumentFragment method from the XmlDocument object. This ensures the fragment is a part of this document. You can then set the InnerXml property of the fragment object to whatever well-formed XML you want. Afterwhich you can use the AppendChild method of any node object to append the fragment object as a child node anywhere you want in your XML file. This is fine and dandy, except when the fragment you are creating contains tags in one namespace and the node you're inserting it into is in another.
An XML file can have as many namespaces defined at the top as you need. Once defined, you can use those namespaces to prefix tags all over your XML file. However when dealing with fragments, you have no place to define namespaces so when you try to insert the fragment into a node, you'll get a namespace exception. OK, so here's what I did to overcome this:
I needed to create a NamespaceManager object that I will need for a couple of XPath queries first:
Dim o_NamespaceManager As Xml.XmlNamespaceManager = New Xml.XmlNamespaceManager(New NameTable)
o_NamespaceManager.AddNamespace("xsl", "http://www.w3.org/1999/XSL/Transform")
o_NamespaceManager.AddNamespace("fo", "http://www.w3.org/1999/XSL/Format")
First of all, I took the XML fargment I was creating and wrapped it in a top-level XML tag and added namespace declarations to that tag. This make my XML code fragment a fully qualified XML file. I then read this mini-file into an XmlDocument object using the LoadXml method. Then I created a namespace object and used it in a SelectSingleNode call to pull out my fragment as an XmlNode object.
Dim o_DraftInjection As StringBuilder = New StringBuilder
Dim o_Writer As StringWriter = New StringWriter(o_DraftInjection)
With o_Writer
.WriteLine("<?xml version=""1.0"" encoding=""UTF-8""?>")
.WriteLine("<xsl:stylesheet version=""2.0""
xmlns:xsl=""http://www.w3.org/1999/XSL/Transform""
xmlns:fo=""http://www.w3.org/1999/XSL/Format"">")
.WriteLine("<xsl:variable name=""status"">")
.WriteLine(" <xsl:value-of select=""agreementData/status/@code""/>")
.WriteLine("</xsl:variable>")
.WriteLine("<xsl:if test=""$status = 'Draft' "">")
.WriteLine(" <fo:region-start region-name=""xsl-watermark"" extent=""0pt"" overflow=""visible""/>")
.WriteLine("</xsl:if>")
.WriteLine("</xsl:stylesheet>")
End With
Dim o_NewDoc As XmlDocument = New XmlDocument
o_NewDoc.LoadXml(o_DraftInjection.ToString())
Dim o_NodeInsert As XmlNode = o_NewDoc.SelectSingleNode("xsl:stylesheet", o_NamespaceManager)
The next thing is decide where in my original XML file (the one I am trying to inject with the fragment) I need to place my code. After determining that, I extracted an XmlNode from that XML file, afterwhich I created an XmlDocumentFragment object from my main XML file.
Dim o_LayoutNode As XmlNode =
o_XslDoc.SelectSingleNode("xsl:stylesheet/xsl:variable/fo:layout-master-set/fo:simple-page-master",
o_NamespaceManager)
Dim o_Fragment As XmlDocumentFragment = o_XslDoc.CreateDocumentFragment()
Notice that I am using the NamespaceManager object I created earlier to execute both XPath queries.
The next thing I did was set InnerXml property of my fragment object to the OuterXml property of the node I extracted from the small XML file I created around the fragment to begin with.
o_Fragment.InnerXml = o_NodeInsert.InnerXml
Since the fragment object was created by the document that I want to inject my code into, it works out well. In case you are asking yourself why I did not append one XML file with a Node extracted from another; that's not something you can do. The node (or fragment) objects must be linked to the original XML file by being created with the factory methods it provides.
Finally I simply appended the fragment object to the node I extracted from my original XML file:
o_LayoutNode.AppendChild(o_Fragment)
Now here's what was interesting about the results of all of this and what led me to find out something I was not aware of. The namespace declarations normally found at the top of an XML file can be added at any place in an XML file. By solving my problem in the method I've just described, I notived that “xmlns:” delcarations were automatically inserted in my fragment tags when they were extracted. In noticing this I realized I could do what I was trying to attempt in the first place which was simply creating a fragment of code using a StringWriter, then setting that string into the InnerXml property of a fragment object. All I had to do was add the namespace declarations to the first line of my code fragment (the xsl:variable line) instead of having to wrap it up in an actual mini-XML file. Either way would work and the difference in code is negligible.
I hope this helps anyone who has had a difficult time massaging XML data from one document it and inserting it into another.