|
Fiches TechniqueTransforming an XML document
|
Limits of the .Net implementation regard the MSXML implementation |
Setting parameters onte the stylesheet |
Writing code to modify the stylesheet |
Testing application |
Some useful links... |
One of the most attractive feature of the XML format is its ability to describe hierarchical structure, with every node being able to act as a full document. And sometimes, with complex documents you may want to display on a web site, you might have to transform some nodes of the document into HTML pages.
On one hand, this task is easy to do with MSXML. In fact, the transformNode and transformNodeToObject methods start the transformation with the selected node.
On the other hand, the transformation in .Net always starts with the root node, even if the parameter is a child node. To overcome this limit, the documentation suggests to create a new document from the element you want to transform. In its blog, Oleg Tkachenko presents a more elegant solution with the implementation of a XPathNavigator.
Both methods still have the disadvantage to "cut" the element you have to transform from its document. After that, you cannot access its ancestors or siblings.
Thanksfully, while performing the transformation, it is possible to set parameters onto the stylesheet. So, why not setting the current element as parameter ?
For instance, lets take a look at the following stylesheet:
<?xml version="1.0" encoding="utf-8" ?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" > <-- context parameter --> <xsl:param name="ContextStartingNode" /> <-- go directly to the context --> <xsl:template match="/"> <xsl:apply-templates select="$ContextStartingNode" > </xsl:template> <-- templates --> ... </xsl:stylesheet>
To set the parameter, we just have to write the following code:
System.Xml.XPath.IXPathNavigable input; System.Xml.XmlDocument stylesheet; System.IO.TextWriter output; ... XslTransform transform = new XslTransform(); XsltArgumentList args = new XsltArgumentList(); // select the context node XPathNavigator navigator = input.CreateNavigator(); XPathNodeIterator it = navigator.Select(xpath); args.AddParam("ContextStartingNode", "", it); // create the transform object XmlResolver resolver = new XmlUrlResolver(); resolver.Credentials = System.Net.CredentialCache.DefaultCredentials; System.Security.Policy.Evidence evidence = this.GetType().Assembly.Evidence; transform.Load(stylesheet, resolver, evidence); // perform the transformation transform.Transform(input, args, output, resolver);
With the approach, we can also change the "mode" in which we will start the transformation:
<xsl:template match="/"> <xsl:apply-templates select="$ContextStartingNode" mode="mode" > </xsl:template>
Now, to be able the use this technique whenever we want, we need to write some code to dynamically inject the code into a stylesheet.
/// <summary> /// Transforms the input Xml document, using the nodes matching the XPath /// as context node, and starting with the specified mode /// </summary> /// <param name="input">input document</param> /// <param name="stylesheet">stylesheet to be used</param> /// <param name="output">output stream</param> /// <param name="xpath">xpath for the context node</param> /// <param name="startingMode">starting mode</param> public void Transform(System.Xml.XPath.IXPathNavigable input, System.Xml.XmlDocument stylesheet, System.IO.TextWriter output, string xpath, string startingMode) { // try to load the transform XslTransform transform = new XslTransform(); XsltArgumentList args = new XsltArgumentList(); // modify the stylesheet if((xpath != "/") || (startingMode != null && startingMode.Length > 0)) { string prefix = stylesheet.GetPrefixOfNamespace( @"http://www.w3.org/1999/XSL/Transform"); XmlElement param = stylesheet.CreateElement(prefix, "param", @"http://www.w3.org/1999/XSL/Transform"); param.SetAttribute("name", "ContextStartingNode"); stylesheet.DocumentElement.PrependChild(param); XmlElement template = stylesheet.CreateElement(prefix, "template", @"http://www.w3.org/1999/XSL/Transform"); template.SetAttribute("match", "/"); XmlElement rule = stylesheet.CreateElement(prefix, "apply-templates", @"http://www.w3.org/1999/XSL/Transform"); if(xpath != "/") { rule.SetAttribute("select", "$ContextStartingNode"); XPathNavigator navigator = input.CreateNavigator(); XPathNodeIterator it = navigator.Select(xpath); args.AddParam("ContextStartingNode", "", it); } else { rule.SetAttribute("select", "/"); } if(startingMode != null && startingMode.Length > 0) { rule.SetAttribute("mode", startingMode); } template.AppendChild(rule); stylesheet.DocumentElement.AppendChild(template); XmlNamespaceManager nsmgr = new XmlNamespaceManager(stylesheet.NameTable); nsmgr.AddNamespace("xsl", "http://www.w3.org/1999/XSL/Transform"); XmlElement @default = stylesheet.SelectSingleNode( @"/*/xsl:template[@match='\' and not(@mode)]", nsmgr) as XmlElement; if(@default != null) { @default.SetAttribute("mode", "DefaultStartingMode"); } } // create the transform object XmlResolver resolver = new XmlUrlResolver(); resolver.Credentials = System.Net.CredentialCache.DefaultCredentials; System.Security.Policy.Evidence evidence = this.GetType().Assembly.Evidence; transform.Load(stylesheet, resolver, evidence); // perform the transformation transform.Transform(input, args, output, resolver); }
You may download a test application.
To use the application:
As you may notice on the screenshot above, the XML document nodes have been copied starting from the selected node and a "path" attribute has been added.
Transforming only a part of XML Extract of Oleg Tkachenko's Blog
|
|
Download the source files size : 5 Ko, last modification date : 12/26/2002
|
|
Contacts us Any comments, questions? Just contact us.
|