Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I resolve <xsl:import> and <xsl:include> elements with relative paths when using xsltc.exe XslCompiledTransforms?

As part of our web application's build process, I have set up our XSLT stylesheets to be built with Microsoft's xsltc.exe compiler whenever we run a full compile. During local development this has worked great, as the code is compiled and hosted in the same location. However, once this was put on the build server, problems arose.

The build server will compile the XSLT stylesheets just like I do locally, but then a script runs that deploys the compiled code to our internal staging web server. Once these binaries have moved from where they were compiled, the relative paths in <xsl:import> and <xsl:include> elements no longer resolve correctly, causing exceptions that look like this when the XSLT stylesheets are ran.

Could not find a part of the path 'e:\{PATH}\xslt\docbook\VERSION'.
    at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
    at 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, Boolean useLongPath)
    at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options, String msgPath, Boolean bFromProxy)
    at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize)
    at System.Xml.XmlUrlResolver.GetEntity(Uri absoluteUri, String role, Type ofObjectToReturn)
    at System.Xml.Xsl.Runtime.XmlQueryContext.GetDataSource(String uriRelative, String uriBase)

Here's a general idea of the code as it stands now:

var xslt = new XslCompiledTransform();
xslt.Load(typeof(Namespace.XslTransforms.CompiledXsltStylesheet));
xslt.Transform("input.xml", "output.xml");

Right now I'm using the XslCompiledTransform.Load() method with a single 'Type' parameter to bring in the xsltc.exe-based pre-compiled XSLT stylesheets. I can tell from the stack trace that the .NET framework is using the XmlUrlResolver to try to resolve the actual location of these external stylesheets, but I don't see a way to provide an overridden implementation of XmlResolver where I could pass in a new baseUri that points to where these stylesheets live on the web server.

I assume I can resolve this by no longer pre-compiling with xsltc.exe and loading the XSLT stylesheets via XmlReaders, since that will let me use the other XslCompiledTransform.Load() methods which have a parameter where I could provide my own XmlResolver implementation. However, I like the pre-compilation option for syntax validation and performance, so I don't want to give it up unless I absolutely have to.

Is there a way to use xsltc.exe to pre-compile these XSLT stylesheets, yet still provide a way to explicitly state the baseUri for relative path resolution of <xsl:include> and <xsl:import> elements at runtime?

like image 908
Technetium Avatar asked Jan 24 '12 18:01

Technetium


2 Answers

After a lot of playing around with this, I found out that I was right that the code I provided automatically uses the System.Xml.XmlUrlResolver to resolve the <xsl:include> and <xsl:import> relative paths at run-time. However, the use of the XmlUrlResolver is not bound to the System.Xml.XslCompiledTransform when it is placed in a binary by xsltc.exe. The XmlResolver is actually chosen by the XmlResolver property on the System.Xml.XmlReaderSettings on the System.Xml.XmlReader that performs the transformation at run-time. Once I set my own custom XmlResolver on the XsltReaderSettings I was using, I was able to control relative path resolution.

If you want to override this XmlResolver as I did, the following code can be used as a guide:

var customXmlResolver = new SomeCustomXmlResolver();  // Derives from XmlResolver
var xmlReaderSettings = new XmlReaderSettings {
  XmlResolver = customXmlResolver
};

var xslt = new XslCompiledTransform();
xslt.Load(typeof(Namespace.XslTransforms.CompiledXsltStylesheet));

using (var xmlReader = XmlReader.Create("input.xml", xmlReaderSettings)) {
  using (var xmlWriter = XmlWriter.Create("output.xml")) {
    xslt.Transform(xmlReader, null, xmlWriter, customXmlResolver);
  }
}

I am still using xsltc.exe to compile my XSLT stylesheets, but when I load these compiled stylesheets on the web server, the injected SomeCustomXmlResolver rewrites the paths in overridden ResolveUri() and GetEntity() methods so that the referenced files that live in the <xsl:include> and <xsl:import>-based relative paths can be found. As an added bonus, by adding the same XmlResolver to the end of the Transform() method, document() operations in the XML will also have their relative paths resolved correctly.

like image 116
Technetium Avatar answered Oct 31 '22 23:10

Technetium


Is there a way to use xsltc.exe to pre-compile these XSLT stylesheets, yet still provide a way to explicitly state the baseUri for relative path resolution of <xsl:include> and <xsl:import> elements at runtime?

Try to use:

XslCompiledTransform.CompileToType()

One of the arguments that this static method accepts is:

XmlResolver stylesheetResolver
like image 2
Dimitre Novatchev Avatar answered Oct 31 '22 22:10

Dimitre Novatchev