Produits  
  Services et Conseils  
  Fiches Technique  
  SipXTapi avec .Net  
  transformNode en .Net  
  Blowfish et le CF  
  MD5 et le CF  
  Compression HTTP et le CF  
 
 

  Fiches Technique  


Transforming an XML document
starting on another node than the root

lire la version francophone

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.

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

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.

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 : 5 Ko, last modification date : 12/26/2002
Contacts us
Any comments, questions? Just contact us.

all the informations here are provided as is, without any warranty of any kind.
Copyright © FlowGroup SAS, 2002-2005