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

风流意气都作罢 提交于 2019-12-07 06:31:58

问题


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?


回答1:


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.




回答2:


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



回答3:


I don't know if this breaks your system, but how about instead of

  1. compiling with xsltc.exe
  2. deploying the binary
  3. loading the binary with this Load()

you

  1. deploy the stylesheets, however many are required with the import/include directives
  2. load the main stylesheet with this Load(), specifying the resolver for the import/incldue

It appears you'll still get the benefit of a "compiled" stylesheet, at least in run-time.



来源:https://stackoverflow.com/questions/8991755/how-do-i-resolve-xslimport-and-xslinclude-elements-with-relative-paths-whe

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!