2009-06-15 15 views
5

.NET 2.0/VS2005Come risolvere XSL in una trasformazione che carica XSL da una stringa?

Sto cercando di utilizzare la classe XslCompiledTransform per eseguire una trasformazione XSL. Ho due file XSL, il primo dei quali comprende un riferimento all'altro, sotto forma di un <xsl:include> dichiarazione:

Main.xsl:

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

Ora, se potessi caricare il "Main xsl" presentare se stesso come un URI, il mio codice trasformazione sarebbe semplice come:

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

il problema è che ricevo il contenuto del file Main.xsl come una stringa e la necessità di caricare la stringa come XmlReader/IXpathNavigable . Questa è una restrizione necessaria in questo momento. Quando provo a fare lo stesso usando XmlReader/XpathDocument, fallisce perché il codice cerca "Included.xsl" nella cartella C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\! Ovviamente, lo XmlResolver non è in grado di risolvere l'URL relativo perché riceve solo una stringa come input XSL.

miei sforzi in questa direzione sembrano:

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

ho cercato di utilizzare il metodo del XmlUrlResolver ResolveUri per ottenere un Stream riferimento al file XSL da includere, ma sono confuso su come per usare questo flusso. IOW, come faccio a dire l'oggetto XslCompiledTransform di utilizzare questo flusso in aggiunta al 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?? 


Qualsiasi aiuto è molto apprezzato. Ci scusiamo per il lungo post!

Come riferimento, l'eccezione StackTrace assomiglia a questo:

[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

Sto lavorando a qualcosa di simile a ciò che la tua domanda sembra richiedere e ho trovato un articolo di MSDN - [Risolvi lo sconosciuto: Costruire XmlResolvers personalizzati in .NET Framework] (http://msdn.microsoft.com/en-us/ library/aa302284.aspx) - sembra fornire una soluzione molto promettente. –

risposta

2

sto probabilmente manca l'ovvio, ma c'è una ragione non basta cambiare l'URI di Included.xsl per essere un vero e proprio URL ? Questo potrebbe essere fatto nel documento XSL se si ha accesso o si utilizza la manipolazione della stringa altrimenti?

+0

David: Grazie per la risposta. Il motivo è che non è possibile codificare in modo sicuro alcun percorso in qualsiasi punto dell'applicazione, come regola generale. In questo caso, sarebbe la mia ultima risorsa. ;-) – Cerebrus

+0

Non sono sicuro che possa essere evitato. L'esempio di flusso funziona perché stai caricando Main.xsl dalla stessa posizione fisica di Include.xsl. Quindi tornando alla manipolazione delle stringhe potresti semplicemente aggiungere Request.PhysicalApplicationPath all'URI Altrimenti, in che modo il codice saprà dove cercare Include.xsl? È sempre necessario il puntatore in più poiché proviene da una stringa Tnx –

+0

Hmmm ... Non sono riuscito a farlo derivando un XmlUrlResolver personalizzato (che era il modo più pulito). A causa dei limiti di tempo, dovrò farlo tramite la manipolazione delle stringhe. Grazie per l'idea. – Cerebrus

5

Utilizzare un XmlUrlResolver personalizzato

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

e utilizzarlo in funzione del carico di XslCompiledTransform,

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

Karthik: Grazie per la risposta. Questa è la direzione che sto attualmente perseguendo. Mi chiedo se esiste un modo per evitare di codificare in modo rigido la parte "http: // mypath /" nell'XmlUrlResolver personalizzato. Qualche idea ? – Cerebrus

+0

Questo può essere un parametro configurabile o se è ospitato sullo stesso server utilizza Server.MapPath. A proposito, come si ottiene il Main.xsl? Accedendo a un percorso HTTP? – Gee

2

Come risposta di Gee cita, si desidera utilizzare un costume XmlResolver (di cui XmlUrlResolver è già derivato), ma se si sostituisce anche il metodo GetEntity, è possibile risolvere i riferimenti nel documento XSLT primario in modi divertenti e interessanti. Un volutamente semplice esempio di come si potrebbe risolvere il riferimento alla 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); 
    } 
} 

quando si carica il principale.xsl documento che avrebbe cambiato il codice relativo al seguente:

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

L'esempio precedente si è basata su informazioni che ho raccolto-up in questo articolo di MSDN Resolving the Unknown: Building Custom XmlResolvers in the .NET Framework.

0

ho già successo con il fare le trasformazioni che utilizzano tutti in memoria:

Avere un XSLT che contiene la seguente include:

importazione href = "Common.xslt" e importazione 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 ""; 
      } 
     } 

Nota che assolutamente tutti i contenuti sono come stringhe: stylesheet, xmlToParse e il contenuto delle e delle importazioni "comuni" "XHTML"

Problemi correlati