2009-06-15 5 views
5

.NET 2.0/VS2005Comment résoudre les inclusions XSL dans une transformation qui charge XSL à partir d'une chaîne?

Je suis en train d'utiliser la classe XslCompiledTransform pour effectuer une transformation XSL. J'ai deux fichiers XSL, dont la première comprend une référence à l'autre sous la forme d'une déclaration <xsl:include>:

Main.xsl:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
    <xsl:include href="Included.xsl" /> 
    ... 
    ... 
</xsl:stylesheet> 

Si je pouvais charger le « Main .xsl » fichier lui-même comme un URI, mon code de transformation serait aussi simple que:

// This is a function that works. For demo only. 
private string Transform(string xslFileURI) 
{ 
    XslCompiledTransform xslt = new XslCompiledTransform(); 

    // This load works just fine, if I provide the path to "Main.xsl". 
    // The xsl:include is automatically resolved. 
    xslTransform.Load(xslFileURI); 

    StringWriter sw = new StringWriter(); 
    xslt.Transform(Server.MapPath("~/XML/input.xml"), null, sw); 
    return sw.ToString(); 
} 

le problème est que je reçois le contenu du fichier Main.xsl comme une chaîne et besoin de charger la chaîne en tant XmlReader/IXpathNavigable . Ceci est une restriction nécessaire pour le moment. Lorsque j'essaie de faire la même chose en utilisant un XmlReader/XpathDocument, il échoue parce que le code recherche "Included.xsl" dans le dossier C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\! Évidemment, le XmlResolver n'est pas capable de résoudre l'URL relative car il ne reçoit qu'une chaîne comme entrée XSL.

Mes efforts dans ce sens ressemblent:

// This doesn't work! Halp! 
private string Transform(string xslContents) 
{ 
    XslCompiledTransform xslt = new XslCompiledTransform(); 
    XmlUrlResolver resolver = new XmlUrlResolver(); 
    resolver.Credentials = CredentialCache.DefaultCredentials; 

    //METHOD 1: This method does not work. 
    XmlReaderSettings settings = new XmlReaderSettings(); 
    settings.XmlResolver = resolver; 
    XmlReader xR = XmlReader.Create(new StringReader(xslContents), settings); 
    xslt.Load(xR); // fails 

    // METHOD 2: Does not work either. 
    XPathDocument xpDoc = new XPathDocument(new StringReader(xslContents)); 
    xslt.Load(xpDoc, new XsltSettings(true, true), resolver); //fails. 

    StringWriter sw = new StringWriter(); 
    xslt.Transform(Server.MapPath("~/XML/input.xml"), null, sw); 
    return sw.ToString(); 
} 

J'ai essayé d'utiliser la méthode ResolveUri du XmlUrlResolver pour obtenir un Stream faisant référence au fichier XSL à inclure, mais je suis confus quant à la façon utiliser ce flux. OIEau, comment puis-je dire l'objet XslCompiledTransform à utiliser ce flux en plus de la Main.xsl XmlReader:

Uri mainURI = new Uri(Request.PhysicalApplicationPath + "Main.xsl"); 
Uri uri = resolver.ResolveUri(mainURI, "Included.xsl"); 

// I can verify that the Included.xsl file loads in the Stream below. 
Stream s = resolver.GetEntity(uri, null, typeof(Stream)) as Stream; 

// How do I use this Stream in the function above?? 


Toute aide est grandement appréciée. Désolé pour le long post!

À titre de référence, l'exception StackTrace ressemble à ceci:

[FileNotFoundException: Could not find file 'C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\Included.xsl'.] 
    System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath) +328 
    System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy) +1038 
    System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize) +113 
    System.Xml.XmlDownloadManager.GetStream(Uri uri, ICredentials credentials) +78 
    System.Xml.XmlUrlResolver.GetEntity(Uri absoluteUri, String role, Type ofObjectToReturn) +51 
    System.Xml.Xsl.Xslt.XsltLoader.CreateReader(Uri uri, XmlResolver xmlResolver) +22 
    System.Xml.Xsl.Xslt.XsltLoader.LoadStylesheet(Uri uri, Boolean include) +33 
    System.Xml.Xsl.Xslt.XsltLoader.LoadInclude() +349 
    System.Xml.Xsl.Xslt.XsltLoader.LoadRealStylesheet() +704 
    System.Xml.Xsl.Xslt.XsltLoader.LoadDocument() +293 
    System.Xml.Xsl.Xslt.XsltLoader.LoadStylesheet(XmlReader reader, Boolean include) +173 
+0

Je travaille sur quelque chose de similaire à ce que votre question semble exiger et j'ai trouvé un article MSDN - [Résoudre l'Inconnu: Construire XmlResolvers Custom dans le .NET Framework] (http://msdn.microsoft.com/fr-fr/ library/aa302284.aspx) - cela semble fournir une solution très prometteuse. –

Répondre

2

Je suis probablement le manque évident, mais est-il une raison que vous ne changez pas seulement l'URI de Included.xsl être une véritable URL ? Cela peut être fait dans le document XSL si vous avez accès ou en utilisant la manipulation de chaînes autrement?

+0

David: Merci pour la réponse. La raison en est que je ne peux pas coder les chemins n'importe où dans l'application, en règle générale. Dans ce cas, ce serait mon dernier recours. ;-) – Cerebrus

+0

Je ne suis pas sûr que cela puisse être évité. L'exemple de flux fonctionne parce que vous chargez Main.xsl à partir du même emplacement physique que Include.xsl. Pour revenir à la manipulation de chaînes, vous pouvez simplement ajouter le Request.PhysicalApplicationPath à l'URI. Sinon, comment le code saura-t-il où rechercher Include.xsl? Il va toujours avoir besoin du pointeur supplémentaire car vous venez d'une chaîne Tnx –

+0

Hmmm ... Je ne pouvais pas le faire en dérivant un Custom XmlUrlResolver (qui était la manière propre). En raison des contraintes de temps, je vais devoir le faire via la manipulation de chaînes. Merci pour l'idée. – Cerebrus

5

Utilisez un XmlUrlResolver personnalisé

class MyXmlUrlResolver : XmlUrlResolver 
    { 
     public override Uri ResolveUri(Uri baseUri, string relativeUri) 
     { 
      if (baseUri != null) 
       return base.ResolveUri(baseUri, relativeUri); 
      else 
       return base.ResolveUri(new Uri("http://mypath/"), relativeUri); 
     } 
    } 

et l'utiliser dans la fonction de charge de XslCompiledTransform,

resolver=new MyXmlUrlResolver(); 
xslt.Load(xR,null,resolver); 
+0

Karthik: Merci pour la réponse. C'est la direction que je poursuis actuellement. Je me demande s'il existe un moyen d'éviter de coder en dur la partie "http: // mypath /" dans le fichier XmlUrlResolver personnalisé. Des idées ? – Cerebrus

+0

Cela peut être un paramètre configurable ou s'il est hébergé sur le même serveur, utilisez Server.MapPath. BTW, Comment obtenez-vous le Main.xsl? En accédant à un chemin HTTP? – Gee

2

Comme la réponse de Gee mentionne, vous souhaitez utiliser une coutume XmlResolver (dont XmlUrlResolver est déjà dérivé), mais si vous remplacez également la méthode GetEntity, vous pouvez résoudre des références dans le document XSLT principal de manière amusante et intéressante. Un exemple volontairement simple de la façon dont vous pouvez résoudre la référence à Included.xsl:

public class CustomXmlResolver : XmlResolver 
{ 
    public CustomXmlResolver() { } 

    public override ICredentials Credentials 
    { 
     set { } 
    } 

    public override object GetEntity(Uri absoluteUri, string role, Type ofObjectToReturn) 
    { 
     MemoryStream entityStream = null; 

     switch (absoluteUri.Scheme) 
     { 
      case "custom-scheme": 

       string absoluteUriOriginalString = absoluteUri.OriginalString; 
       string ctgXsltEntityName = absoluteUriOriginalString.Substring(absoluteUriOriginalString.IndexOf(":") + 1); 
       string entityXslt = ""; 

       // TODO: Replace the following with your own code to load data for referenced entities. 
       switch (ctgXsltEntityName) 
       { 
        case "Included.xsl": 
         entityXslt = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\n <xsl:template name=\"Included\">\n\n </xsl:template>\n</xsl:stylesheet>"; 
         break; 
       } 

       UTF8Encoding utf8Encoding = new UTF8Encoding(); 
       byte[] entityBytes = utf8Encoding.GetBytes(entityXslt); 
       entityStream = new MemoryStream(entityBytes); 

       break; 
     } 

     return entityStream; 
    } 

    public override Uri ResolveUri(Uri baseUri, string relativeUri) 
    { 
     // You might want to resolve all reference URIs using a custom scheme. 
     if (baseUri != null) 
      return base.ResolveUri(baseUri, relativeUri); 
     else 
      return new Uri("custom-scheme:" + relativeUri); 
    } 
} 

Lorsque vous chargez le principal.xsl document changerait le code correspondant à ce qui suit:

xslt.Load(xpDoc, new XsltSettings(true, true), new CustomXmlResolver()); 

L'exemple ci-dessus était basé sur les informations que je RAMASSÉS dans l'article MSDN Resolving the Unknown: Building Custom XmlResolvers in the .NET Framework.

0

J'ai déjà avec succès à faire des transformations en utilisant tous en mémoire:

Avoir un xslt contenant les éléments suivants comprend:

import href = "Common.xslt" et import href = "Xhtml.xslt"

private string Transform(string styleSheet, string xmlToParse) 
      { 
       XslCompiledTransform xslt = new XslCompiledTransform(); 

       MemoryResourceResolver resolver = new MemoryResourceResolver();    


       XmlTextReader xR = new XmlTextReader(new StringReader(styleSheet));   

       xslt.Load(xR, null, resolver); 

       StringWriter sw = new StringWriter();     


       using (var inputReader = new StringReader(xmlToParse)) 
       { 
        var input = new XmlTextReader(inputReader); 
        xslt.Transform(input, 
             null, 
             sw); 
       } 

       return sw.ToString(); 

      }  

    public class MemoryResourceResolver : XmlResolver 
     { 

      public override object GetEntity(Uri absoluteUri, 
       string role, Type ofObjectToReturn) 
      { 
       if (absoluteUri.ToString().Contains("Common")) 
       { 
        return new MemoryStream(Encoding.UTF8.GetBytes("Xml with with common data")); 
       } 

       if (absoluteUri.ToString().Contains("Xhtml")) 
       { 
        return new MemoryStream(Encoding.UTF8.GetBytes("Xml with with xhtml data")); 
       }   

       return ""; 
      } 
     } 

Notez que absolument tout le contenu est sous forme de chaînes: stylesheet, xmlToParse et le contenu des importations « communes » et « Xhtml »

Questions connexes