2009-06-15 10 views
5

.NET 2.0/VS2005¿Cómo resolver XSL incluye en una Transformación que carga XSL desde una Cadena?

Estoy tratando de utilizar la clase XslCompiledTransform para realizar una transformación XSL. Tengo dos archivos XSL, el primero de los cuales incluye una referencia a la otra en forma de una declaración <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> 

Ahora, si pudiera cargar el "Principal .xsl" presentar a sí mismo como un URI, mi código de transformación sería tan simple como:

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

el problema es que yo reciba el contenido del archivo Main.xsl como una cadena y la necesidad de cargar la cadena como una XmlReader/IXpathNavigable . Esta es una restricción necesaria en este momento. Cuando intento hacer lo mismo con un XmlReader/XpathDocument, falla porque el código busca "Included.xsl" en la carpeta C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\! Obviamente, el XmlResolver no puede resolver la URL relativa porque solo recibe una cadena como entrada XSL.

Mis esfuerzos en este sentido se parecen:

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

He intentado utilizar el método de la XmlUrlResolver ResolveUri para obtener un Stream referencia al archivo XSL para ser incluidos, pero estoy confundido en cuanto a cómo para usar esta corriente OIA, ¿cómo le digo el objeto XslCompiledTransform utilizar esta corriente, además 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?? 


Cualquier ayuda es muy apreciada. Perdón por la larga publicación!

Para su referencia, la excepción StackTrace se parece a esto:

[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

Estoy trabajando en algo similar a lo que su pregunta parece requerir y encontré un artículo de MSDN - [Resolviendo lo Desconocido: creando XmlResolvers personalizados en .NET Framework] (http://msdn.microsoft.com/en-us/ library/aa302284.aspx) - que parece proporcionar una solución muy prometedora. –

Respuesta

2

probablemente estoy perdiendo lo que es obvio, pero hay una razón que no sólo cambia el URI del Included.xsl sea una verdadera URL ? ¿Esto podría hacerse en el documento XSL si tiene acceso o usa manipulación de cadenas de lo contrario?

+0

David: Gracias por la respuesta. La razón es que no puedo codificar ningún camino en ninguna parte de la aplicación, como regla general. En este caso, sería mi último recurso. ;-) – Cerebrus

+0

No estoy seguro de que se pueda evitar. El ejemplo de flujo funciona porque está cargando Main.xsl desde la misma ubicación física que Include.xsl. Por lo tanto, volviendo a la manipulación de cadenas, podría simplemente agregar Request.PhysicalApplicationPath al URI De lo contrario, ¿cómo sabrá el código dónde buscar Include.xsl? Siempre va a necesitar el puntero extra ya que viene de una cadena Tnx –

+0

Hmmm ... No pude hacerlo derivando un Custom XmlUrlResolver (que era la manera más limpia). Debido a las limitaciones de tiempo, voy a tener que hacerlo a través de la manipulación de cadenas. Gracias por la idea – Cerebrus

5

Use un XmlUrlResolver encargo

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

y utilizarlo en función de la carga de XslCompiledTransform,

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

Karthik: Gracias por la respuesta. Esta es la dirección que estoy siguiendo actualmente. Me pregunto si hay una manera de evitar la codificación dura de la parte "http: // mypath /" en el XmlUrlResolver personalizado. Algunas ideas ? – Cerebrus

+0

Eso puede ser un parámetro configurable o si está alojado en el mismo servidor use Server.MapPath. Por cierto, ¿cómo se obtiene el Main.xsl? Al acceder a una ruta HTTP? – Gee

2

como la respuesta de Gee menciona, desea utilizar una costumbre XmlResolver (de los cuales XmlUrlResolver ya se deriva), pero si también reemplaza el método GetEntity, puede resolver las referencias en el documento XSLT principal de maneras divertidas e interesantes. Un deliberadamente sencillo ejemplo de cómo se puede resolver la referencia a 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); 
    } 
} 

cuando se carga el principal.XSL documento que cambiaría el código correspondiente a la siguiente:

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

El ejemplo anterior se basó en información Tomé-up en el artículo de MSDN Resolving the Unknown: Building Custom XmlResolvers in the .NET Framework.

0

Ya tengo éxito con la realización de transformaciones usando toda la memoria:

Tener un XSLT que contiene la siguiente incluye:

importación href = "Common.xslt" y importación 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 ""; 
      } 
     } 

Tenga en cuenta que absolutamente todo el contenido es como cadenas: styleSheet, xmlToParse y el contenido de las importaciones y "común" "xhtml"

Cuestiones relacionadas