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  


Application d'une transformation XSLT
sur un noeud quelconque d'un document XML

read the English version

Limitations de l'implémentation en .Net par rapport à MSXML
Passage de paramètres à la feuille de style
Modification dynamique de la feuille de style
Application de test
Quelques liens utiles...

Un des principaux attraits du XML est sa capacité à décrire une structure hiérarchique, chaque élément pouvant être considéré comme un document XML à part entière. Ainsi, dans le cas d'un document XML complexe que l'on souhaite afficher sur un site web, on peut devoir transformer certains noeuds pour générer des pages HTML.

Limitations de l'implémentation en .Net par rapport à MSXML

Avec MSXML, la tâche est facile. En effet, les méthodes transformNode et transformNodeToObject permettent d'effectuer une transformation sur le noeud sélectionné.

En revanche, en .Net, la transformation s'applique forcément sur le document entier, même si le paramètre passé est un élément. Pour contourner cette limitation, la documentation propose une méthode qui consiste à créer un nouveau document à partir de l'élément que l'on souhaite transformer. Dans son Blog, Oleg Tkachenko propose une autre méthode, plus élégante, qui consiste à implementer un XPathNavigatorReader.

L'inconvénient de ces deux méthodes est qu'elles dissocient l'élement du document initial. Il ne devient donc plus possible d'accéder ni à ses ancètres, ni à ses frères.

Passage de paramètres à la feuille de style

Heureusement, lors de l'exécution de la transformation, il est possible passer des paramètres au processeur. Alors pourquoi ne pas lui passer le noeud courant ?

Prenons l'exemple de la feuille de style ci-dessous :

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

Pour passer le contexte au processeur, il suffit d'écrire le code suivant :

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

On remarque qu'avec cette approche, il est possible de changer de mode dans le template sur l'élément racine :

    <xsl:template match="/">
        <xsl:apply-templates select="$ContextStartingNode" mode="mode" >
    </xsl:template>

Modification dynamique de la feuille de style

Maintenant, pour que cette approche soit applicable de manière générique, il ne reste plus qu'à modifier dynamiquement une feuille de style quelconque de manière transparente.

Cette modification consiste à ajouter un paramètre et un template sur l'élément racine. Dans le cas où un tel template existait déjà, il faudra le modifier pour lui ajouter un mode particulier.

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

Les noms du paramètre et du mode par défault ont été choisi par soucis de clareté. Il faudrait vérifier qu'ils n'existent pas déjà. Un bon moyen de trouver des noms uniques pourraient se faire via des GUID.

Application de test

Vous pouvez télécharger l'application de test.

Le mode d'emploi est relativement simple :

  1. charger un fichier XML,
  2. charger une feuille de style,
  3. sélectionner un chemin XPath et un mode de départ
  4. cliquer sur "GO".

Comme vous pouvez le constater sur la capture d'écran ci-dessus, le document XML a été recopié à partir du noeud sélectionné, en ajoutant un attribut contenant le chemin jusqu'au noeud.

Quelques liens utiles...

Transforming only a part of XML
Extrait du blog d'Oleg Tkachenko
Télécharger le source des tests
taille : 25 Ko, dernière modification : 22/05/2004
Nous contacter
Vous avez des remarques, des questions ? N'hésitez pas à nous contacter.

les informations fournies ici le sont en tant que telles, sans aucune garantie d'aucune sorte.
Copyright © FlowGroup SAS, 2002-2005