如何解决从字符串加载 XSL 的转换中包含的 XSL?

Posted

技术标签:

【中文标题】如何解决从字符串加载 XSL 的转换中包含的 XSL?【英文标题】:How to resolve XSL includes in a Transformation that loads XSL from a String? 【发布时间】:2010-11-02 23:52:02 【问题描述】:

.NET 2.0/VS2005

我正在尝试使用 XslCompiledTransform 类来执行 XSL 转换。我有两个 XSL 文件,其中第一个文件以 <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>

现在,如果我可以将“Main.xsl”文件本身作为 URI 加载,我的转换代码将很简单:

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

问题是我将 Main.xsl 文件的内容作为字符串接收,并且需要将该字符串作为XmlReader/IXpathNavigable 加载。 此时这是一个必要的限制。当我尝试使用 XmlReader/XpathDocument 执行相同操作时,它失败了,因为代码在C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\ 文件夹!显然,XmlResolver 无法解析相对 URL,因为它只接收一个字符串作为输入 XSL。

我在这个方向上的努力如下:

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

我尝试使用 XmlUrlResolver 的 ResolveUri 方法来获取引用要包含的 XSL 文件的 Stream,但对如何使用此 Stream 感到困惑。 IOW,除了 Main.xsl XmlReader,我如何告诉XslCompiledTransform 对象使用此流:

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??

任何帮助是极大的赞赏。抱歉发了这么长的帖子!

供您参考,Exception StackTrace 如下所示:

[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

【问题讨论】:

我正在研究类似于您的问题似乎需要的东西,我发现了一篇 MSDN 文章 - Resolving the Unknown: Building Custom XmlResolvers in the .NET Framework - 这似乎提供了一个非常有前途的解决方案。 【参考方案1】:

我可能遗漏了显而易见的内容,但是您是否有理由不将 Included.xsl 的 URI 更改为真正的 URL?如果您有访问权限,可以在 XSL 文档中完成此操作,否则可以使用字符串操作?

【讨论】:

大卫:感谢您的回复。原因是我不能在应用程序的任何地方硬编码任何路径,作为一般规则。在这种情况下,这将是我最后的手段。 ;-) 我不确定是否可以避免。流示例有效,因为您从与 Include.xsl 相同的物理位置加载 Main.xsl。因此,回到字符串操作,您可以将 Request.PhysicalApplicationPath 添加到 URI 否则,代码如何知道在哪里查找 Include.xsl?它总是需要额外的指针,因为你来自一个字符串 Tnx 嗯...我无法通过派生自定义 XmlUrlResolver 来做到这一点(这是更清洁的方式)。由于时间限制,我将不得不通过字符串操作来完成。感谢您的想法。 我很高兴,很抱歉我不能给你一个更干净的方式【参考方案2】:

使用自定义 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);
        
    

并在XslCompiledTransform的加载函数中使用,

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

【讨论】:

Karthik:感谢您的回复。这是我目前追求的方向。我想知道是否有办法避免在自定义 XmlUrlResolver 中对“mypath”部分进行硬编码。有什么想法吗? 这可以是一个可配置的参数,或者如果它托管在同一台服务器上,请使用 Server.MapPath。顺便说一句,你如何获得 Main.xsl?通过访问 HTTP 路径?【参考方案3】:

正如 Gee 的回答所提到的,您想使用自定义 XmlResolver(其中 XmlUrlResolver 已经派生了),但如果您还覆盖方法 GetEntity,您可以在主 XSLT 文档中解析引用,并且有趣的方式。一个故意简单的示例,说明如何解决对 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);
    

当您加载 Main.xsl 文档时,您需要将相关代码更改为以下内容:

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

以上示例基于我在 MSDN 文章 Resolving the Unknown: Building Custom XmlResolvers in the .NET Framework 中获得的信息。

【讨论】:

【参考方案4】:

我已经成功使用内存中的所有内容进行转换:

拥有一个包含以下内容的 xslt 包括:

import href="Common.xslt" 和 导入 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 "";
            
        

请注意,绝对所有内容都是字符串:styleSheet、xmlToParse 以及“Common”和“Xhtml”导入的内容

【讨论】:

以上是关于如何解决从字符串加载 XSL 的转换中包含的 XSL?的主要内容,如果未能解决你的问题,请参考以下文章

来自 xsd 的 XSL 转换

从应用程序中包含的文件加载 android 数组

如何在分配的空间中存储以 ASCII 转换的计数器寄存器中包含的值

如何重新加载 UIViewController 中包含的 tableView?

如何使用 XSLT 3.0 中的 xsl:stream、xsl:accumulator、xs:fork?

ARM NEON:从 NEON 寄存器(Q/D 寄存器)中包含的地址加载数据