views:

1036

answers:

2

.NET 2.0/VS2005

I am trying to use the XslCompiledTransform class to perform a XSL Transformation. I have two XSL files, the first of which includes a reference to the other in the form of an <xsl:include> statement :

Main.xsl:

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

Now, If I could load the "Main.xsl" file itself as a URI, my transformation code would be as simple as :

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

The problem is that I receive the contents of the Main.xsl file as a string and need to load the string as an XmlReader/IXpathNavigable. This is a necessary restriction at this time. When I try to do the same using an XmlReader/XpathDocument, it fails because the code looks for "Included.xsl" in the C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\ folder! Obviously, the XmlResolver is not able to resolve the relative URL because it only receives a string as input XSL.

My efforts in this direction look like:

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

I have tried to use the ResolveUri method of the XmlUrlResolver to obtain a Stream referencing the XSL file to be included, but am confused as to how to use this Stream. IOW, how do I tell the XslCompiledTransform object to use this stream in addition to the 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??


Any help is greatly appreciated. Sorry for the long post!

For your reference, the Exception StackTrace looks like this:

[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
+1  A: 

Hi Cerberus,

I am probably missing the obvious but is there a reason you don't just change the URI of Included.xsl to be a true URL? This could either be done in the XSL doc if you have access or using string manipulation otherwise?

David E
David: Thanks for the reply. The reason is that I cannot hard code any paths anywhere in the application, as a general rule. In this case, it would be my last resort. ;-)
Cerebrus
I am not sure that it can be avoided. The stream example works because you are loading Main.xsl from the same physical location as the Include.xsl. Hence going back to string manipulation you could just add the Request.PhysicalApplicationPath to the URIOtherwise, how will the code know where to look for Include.xsl? It is always going to need the extra pointer as you are coming from a stringTnx
David E
Hmmm... I was unable to do it by deriving a Custom XmlUrlResolver (which was the cleaner way). Because of time constraints, I'm going to have to do it via String manipulation. Thanks for the idea.
Cerebrus
my pleasure, sorry I couldn't give you a cleaner way
David E
+1  A: 

Hey Cerberus, Use a custom XmlUrlResolver

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

And use it in load function of XslCompiledTransform,

resolver=new MyXmlUrlResolver();
xslt.Load(xR,null,resolver);
Gee
Karthik: Thanks for the reply. This is the direction I'm currently pursuing. I'm wondering if there is a way to avoid hard coding the "http://mypath/" part in the custom XmlUrlResolver. Any ideas ?
Cerebrus
That can be a configurable parameter or if it is hosted on the same server use Server.MapPath. BTW, How do you get the Main.xsl? By accessing a HTTP path?
Gee