Articles
Transforming an XML document
starting on another node than the root
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.
Limits of the .Net implementation regard the MSXML implementation
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.
Setting parameters onte the stylesheet
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>
Writing code to modify the stylesheet
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);
}
Testing application
You may download a test application.
To use the application:
- open a XML file,
- open a stylesheet,
- define a XPath to the starting node and set a starting mode,
- click on the "GO" button.
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.
Some useful links...
Transforming only a part of XML Extract of Oleg Tkachenko's Blog | |
Download the source files size 24 Kb, last modification date 06/08/2013 | |
Contacts us Any comments, questions? Just contact us. |