Flow Group SASFlow Group
L'erp des Processus Relationnels
Systeme d'Information – Audit Industriel
> Home > Expertise & Knowledge > Technical Articles

Articles


Transforming an XML document
starting on another node than the root

lire la version francophone Version française

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.

For your information

We just have to add a parameter and a template on the root node. If such a template already exists, we only have to define it in a specific mode to avoid a conflict.

/// <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); }
Warning

The names of the parameter and the default mode have been choosen only to be explicit. To avoid potential problems, we should make sure they do not already exists. GUID might be a good usefull.

Testing application

You may download a test application.

To use the application:

  1. open a XML file,
  2. open a stylesheet,
  3. define a XPath to the starting node and set a starting mode,
  4. click on the "GO" button.
Capture d'écran de l'application de test

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...


all the informations here are provided as is, without any warranty of any kind.
© 2021 Flow Group SAS - Flow Group est une marque déposée de GL Conseil SA.