当前位置: 首页 > 知识库问答 >
问题:

XslCompiledTransform和自定义XmlUrlResolver:"具有相同键的条目已存在"

沈嘉瑞
2023-03-14

有没有办法调试由自定义XMLURLSolver从数据库加载的XSLT文档,或者有人知道下面的错误消息是关于什么的?

我有一个导入通用xslt文档的XSLT样式表:

<xsl:import href="db://common.hist.org"/>

该方案由一个自定义的XmlResolver处理,它从数据库加载XSLT文档,但我得到一个错误:

具有相同密钥的条目已存在。

xsl:import引用的公共XSLT文档包含一些公共XSLT模板,每个模板都有一个唯一的名称。

将XSLT文档从本地文件系统移动到数据库后开始发生此错误。当使用指向本地文件的默认导入方案以及从本地文件系统加载XSLT文档时,不会发生错误。

我还尝试在创建XslCompiledTransform实例时启用调试,但不知何故,不可能“步入”基于数据库的XSLT。

_xslHtmlOutput = new XslCompiledTransform(XSLT_DEBUG);

更新:以下基本上是请求的解析器代码,但在我的代码中没有发生异常;因此,我想下面的代码中没有明显的原因。(这段代码实际上用于加载包含导入的XSLT样式表,在注释导入时,一切都按预期工作。)

public class XmlDBResolver : XmlUrlResolver
{
    private IDictionary<string,string> GetUriComponents(String uri)
    {
        bool useXmlPre = false;
        uri = uri.Replace("db://", "");
        useXmlPre = uri.StartsWith("xml/");
        uri = uri.Replace("xml/", "");
        IDictionary<string, string> dict = new Dictionary<string, string>();
        string app = null, area = null, subArea = null;

        if (!String.IsNullOrWhiteSpace(uri))
        {
            string[] components = uri.Split('.');

            if (components == null)
                throw new Exception("Invalid Xslt URI");

            switch (components.Count())
            {
                case 3:
                    app = components[0];
                    break;
                case 4:
                    area = components[0];
                    app = components[1];
                    break;
                case 5:
                    subArea = components[0];
                    area = components[1];
                    app = components[2];
                    break;
                default:
                    throw new Exception("Invalid Xslt URI");
            }

            dict.Add("application", app);
            dict.Add("area", area);
            dict.Add("subArea", subArea);
            dict.Add("xmlPreTransform", String.Format("{0}", useXmlPre));
        }

        return dict;
    }

    public override System.Net.ICredentials Credentials
    {
        set { /* TODO: check if we need credentials */ }
    }

    public override object GetEntity(Uri absoluteUri, string role, Type ofObjectToReturn)
    {
        /*
         *  db://<app>.hist.org
         *  db://<area>.<app>.hist.org
         *  db://<subArea>.<area>.<app>.hist.org
         * 
         * */

        Tracing.TraceHelper.WriteLine(String.Format("GetEntity {0}", absoluteUri));

        XmlReader reader = null;

        switch (absoluteUri.Scheme)
        {
            case "db":
                string origString = absoluteUri.OriginalString;
                IDictionary<string, string> xsltDict = GetUriComponents(origString);

                if(String.IsNullOrWhiteSpace(xsltDict["area"]))
                {
                    reader = DatabaseServiceFactory.DatabaseService.GetApplicationXslt(xsltDict["application"]);
                }
                else if (!String.IsNullOrWhiteSpace(xsltDict["area"]) && String.IsNullOrWhiteSpace(xsltDict["subArea"]) && !Boolean.Parse(xsltDict["xmlPreTransform"]))
                {
                    reader = DatabaseServiceFactory.DatabaseService.GetAreaXslt(xsltDict["application"], xsltDict["area"]);
                }
                else if (!String.IsNullOrWhiteSpace(xsltDict["area"]) && !String.IsNullOrWhiteSpace(xsltDict["subArea"]))
                {
                    if(Boolean.Parse(xsltDict["xmlPreTransform"]))
                        reader = DatabaseServiceFactory.DatabaseService.GetSubareaXmlPreTransformXslt(xsltDict["application"], xsltDict["area"], xsltDict["subArea"]);
                    else
                        reader = DatabaseServiceFactory.DatabaseService.GetSubareaXslt(xsltDict["application"], xsltDict["area"], xsltDict["subArea"]);
                }
                return reader;

            default:
                return base.GetEntity(absoluteUri, role, ofObjectToReturn);
        }
    }

为了完整性起见,IDatabaseService接口(相关部分):

public interface IDatabaseService
{
    ...
    XmlReader GetApplicationXslt(String applicationName);
    XmlReader GetAreaXslt(String applicationName, String areaName);
    XmlReader GetSubareaXslt(String applicationName, String areaName, String subAreaName);
    XmlReader GetSubareaXmlPreTransformXslt(String applicationName, String areaName, String subAreaName);
}

更新:我试图通过临时从Web服务器加载样式表来隔离问题,这是有效的。我了解到SQL服务器显然只存储没有XML声明的XML片段,而不是存储在Web服务器上的样式表。

更新:异常的堆栈跟踪:

系统Xml。Xsl。XslLoadException:XSLT Kompilierungsfehler。费勒·贝(91616)---

共有1个答案

姬昀
2023-03-14

您的IDatabaseService接口方法返回对象。构造这些时,请确保将一个baseUri传递给构造函数;例如。:

public XmlReader GetApplicationXslt(string applicationName)
{
    …
    var baseUri = string.Format("db://{0}.hist.org", applicationName);
    return XmlReader.Create(input: …, 
                            settings: …,
                            baseUri: baseUri);  // <-- this one is important!
}

如果指定这个参数,一切都可能正常。请参阅此答案的最后一节,了解我提出此建议的原因。

首先,让我们简单思考一下哪些组件可能会导致错误:

将XSLT文档从本地文件系统移动到数据库后,开始出现此错误。当使用指向本地文件的默认导入方案以及从本地文件系统加载XSLT文档时,不会发生此错误

将样式表放入数据库意味着您必须…

  1. 更改了样式表中的导入路径(引入了db://...路径)
  2. 实现并连接了一个自定义XmlDbResolver,用于处理db://导入方案
  3. IDatabaseService的形式实现的数据库访问代码,它支持XmlDbResolver

如果样式表除了导入路径之外没有改变,则错误似乎可能出现在您的XmlResolver类和/或IDatabaseService实现中。由于您尚未显示后者的代码,我们无法在没有猜测的情况下调试您的代码。

我已经使用您的XmlDbResolver创建了一个模拟项目(完整描述如下)。

更新:我已经能够重现错误。请参阅OP的评论

我在Visual Studio 2010中创建了一个控制台应用程序项目(您可以通过使用Git(git clonehttps://gist.github.com/fbbd5e7319bd6c281c50b4ebb1cee1f9.git)克隆此Gist并检查第二次提交来检索它,git check out d00629)。我将在下面更详细地描述解决方案的每个项目。

(请注意,SqlServerDatabase.mdfTestInput.xml以及. xslt项目项的Copy to输出目录属性应设置为始终。)

这是一个基于服务的数据库,我将附加到SQL Server Express 2008的本地实例。(这是通过App.config中的连接字符串完成的;请参见下文。)

我在这个数据库中设置了以下项目:

此表包含两列,定义如下:

表格最初为空。测试数据将在运行时添加到数据库中(请参阅下面的Program.cs和CommonHistOrg.xslt)。

该文件包含上述数据库的连接字符串条目。

<?xml version="1.0"?>
<configuration>
  <connectionStrings>
    <add name="SqlServerDatabase"
         connectionString="Data Source=.\SQLEXPRESS;
                           AttachDbFilename=|DataDirectory|\SqlServerDatabase.mdf;
                           Integrated Security=True;
                           User Instance=True"
         />
  </connectionStrings>
</configuration>

这个文件包含IDatabaseService接口的定义,我在这里不再重复。

它包含一个实现IDatabaseService的类。它将数据读取/写入上述数据库:

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.IO;
using System.Xml;

class SqlServerDatabaseService : IDatabaseService
{
    // creates a connection based on connection string from App.config: 
    SqlConnection CreateConnection()
    {
        return new SqlConnection(connectionString: ConfigurationManager.ConnectionStrings["SqlServerDatabase"].ConnectionString);
    }

    // stores an XML document into the 'ApplicationDocuments' table: 
    public void StoreApplicationDocument(string applicationName, XmlReader document)
    {
        using (var connection = CreateConnection())
        {
            SqlCommand command = connection.CreateCommand();
            command.CommandText = "INSERT INTO ApplicationDocuments (ApplicationName, Document) VALUES (@applicationName, @document)";
            command.Parameters.Add(new SqlParameter("@applicationName", applicationName));
            command.Parameters.Add(new SqlParameter("@document", new SqlXml(document)));
                                                             //  ^^^^^^^^^^^^^^^^^^^^
            connection.Open();
            int numberOfRowsInserted = command.ExecuteNonQuery();
            connection.Close();
        }
    }

    // reads an XML document from the 'ApplicationDocuments' table:
    public XmlReader GetApplicationXslt(string applicationName)
    {
        using (var connection = CreateConnection())
        {
            SqlCommand command = connection.CreateCommand();
            command.CommandText = "SELECT Document FROM ApplicationDocuments WHERE ApplicationName = @applicationName";
            command.Parameters.Add(new SqlParameter("@applicationName", applicationName));

            connection.Open();
            var plainXml = (string)command.ExecuteScalar();
            connection.Close();

            if (plainXml != null)
            {
                return XmlReader.Create(new StringReader(plainXml));
            }
            else
            {
                throw new KeyNotFoundException(message: string.Format("Database does not contain a application document named '{0}'.", applicationName));
            }
        }
    }

    … // (all other methods throw a NotImplementedException)
}

该类包含XmlDbResolver类,该类与您的XmlDbResolver类相同,只是有两个变化:

>

  • 公共构造函数接受IDatabaseService对象。这是用来代替DatabaseServiceFactory的。数据库服务。

    我不得不删除对跟踪的调用。TraceHelper。WriteLine(写入线)。

    这是db://common。历史。org样式表,将在运行时放入数据库(请参见下面的程序.cs):

    <?xml version="1.0" encoding="utf-8"?>
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      <xsl:template match="Foo">
        <Bar/>
      </xsl:template>
    </xsl:stylesheet>
    

    这是一个引用上述db://common的样式表。历史。组织样式表:

    <?xml version="1.0" encoding="utf-8"?>
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      <xsl:import href="db://common.hist.org"/>
    </xsl:stylesheet>
    

    这是我们将使用上述测试样式表转换的XML测试输入文档。xslt:

    <?xml version="1.0" encoding="utf-8" ?>
    <Foo/>
    

    这包含测试应用程序代码:

    using System;
    using System.Text;
    using System.Xml;
    using System.Xml.Xsl;
    
    class Program
    {
        static void Main(string[] args)
        {
            var databaseService = new SqlServerDatabaseService();
    
            // put CommonHistOrg.xslt into the 'ApplicationDocuments' database table:
            databaseService.StoreApplicationDocument(
                applicationName: "common",
                document:        XmlReader.Create("CommonHistOrg.xslt"));
    
            // load the XSLT stylesheet:
            var xslt = new XslCompiledTransform();
            xslt.Load(@"TestStylesheet.xslt", 
                settings: XsltSettings.Default, 
                stylesheetResolver: new XmlDbResolver(databaseService));
    
            // load the XML test input:
            var input = XmlReader.Create("TestInput.xml");
    
            // transform the test input and store the result in 'output':
            var output = new StringBuilder();
            xslt.Transform(input, XmlWriter.Create(output));
    
            // display the transformed output:
            Console.WriteLine(output.ToString());
            Console.ReadLine();
        }
    }
    

    在我的机器上很有魅力:输出是一个带有空根元素的XML文档

    >

    databaseService.StoreApplicationDocument(
        applicationName: "test",
        document:        XmlReader.Create("TestStylesheet.xslt"));
    

    而不是

    xslt.Load(@"TestStylesheet.xslt", …);
    

    xslt.Load(@"db://test.hist.org", …);
    

    这会触发OP报告的错误。

    经过一些调试,我发现以下情况不会导致此问题。

    >

    事实上,

    事实上,错误是在某处触发的。NET Framework代码。这就是我决定下载并安装的原因。NET Framework参考源。(我更改了解决方案以使用框架的3.5版进行调试。)安装此并重新启动VS然后允许您在调试会话期间查看和逐步浏览框架代码。

    从调用xslt开始。加载(…;)在我们的主方法中,我进入了框架代码,最终在XsltLoader中找到了一个方法。cs。有一个名为documentUrisInUse的混合字典,它显然存储了已加载样式表的基本uri。因此,如果我们加载多个具有空基URI或缺少基URI的样式表,该方法将尝试向该字典添加两次;这就是导致错误的原因。

    因此,一旦您为IDatabaseService返回的每个样式表分配了一个唯一的基URI,一切都应该正常工作。您可以通过将baseUri传递给XmlReader构造函数来实现这一点。请参阅我答案开头的代码示例。您还可以通过下载或克隆此Gist(git-clone)来检索更新的工作解决方案https://gist.github.com/fbbd5e7319bd6c281c50b4ebb1cee1f9.git)。

  •  类似资料:
    • 问题内容: 我有以下代码来获取地图: 如何打印带有重复键的消息“重复键”? 问题答案: 如何打印带有重复键的消息“重复键”? 使用当前代码,您将收到消息“重复键”,其中包含至少2个实例的列表,这些实例具有与对象相同的值,例如。 如何获得对应的密钥? 到目前为止,还无法获得相应的键,当前从合并功能中获得的实际上是与相同键映射的2个值,这些值需要合并以仅保留对应键的一个值。 您的问题是 Java 9

    • 我有下面的对象数组, 首先,我需要用忽略时间的日期分组。 所以, 如果是像这样的日期,我可以使用。 我仍然可以使用它,但现在它涉及到一些条件,将那些转换为日期格式。 此外,我还需要按那些按日期结果分组的对象进行分组,以便它们的落在一个时间间隔内。 因此,如果,它应该在组中, 因此,首先应该按照创建日期对结果进行分组,然后再按照定义的时隙对每个组中的元素进行分组。 我想,如果我可以使用条件分组,我可

    • 问题内容: 我一直在寻找类似于Java TreeSet在实例化时接收自定义比较器的功能,因此我不需要使用对象的默认相等性(和哈希码)条件。 我能想到的最接近的方法是将我的对象包装在一个私有的自定义类中,但这似乎很麻烦:(最终在编程时成为一种重复出现的主题,所以我想知道是否已经有一些东西可供我们使用。也许在公共图书馆? 谢谢 问题答案: 不,您已经找到了应该使用的解决方案。 即使是,它是令人难以接受

    • 我今天面试了,我的面试官问我如何在HashMap中存储具有相同键的多个值?她给了我这个例子—— 我在如何使用HashMap时给了她以下解决方案: 整数是字符串的长度,ArrayList将存储该特定长度的字符串。 面试官说这是使用HashMap的一种方式,但还有另一种方式我不需要ArrayList或任何其他数据结构。在面试期间,我无法想出任何解决方案,现在在谷歌搜索了足够多之后,我仍然一无所获。有人

    • 我需要为WordPress提供一个插件,该插件将具有很少的自定义APIendpoint,并且我已经安装了这两个插件 WordPress REST API V2 JWT-Auth 我已经创建自定义终端节点: 我需要保护这个endpoint,以便只有那些发送了JWT令牌的请求(通过发送用户名和密码 /wp-json/jwt-auth/v1/tokenendpoint生成)才能被处理,否则它应该返回40

    • 有一个由5个节点组成的Cassandra集群。最近进行了从2.2.7到3.9版本的逐节点更新。更新是按照Datastax描述的过程进行的:升级指令。一切都很顺利。整个过程耗时约1小时。然而,几个小时后,我发现了以下问题:在更新期间,有些数据不一致,即对于特定的分区键和聚类键,只返回一行。但是对于同一个查询,有时返回一个,有时返回两个。 结果示例: 因此,正如您所看到的,第一行有字段:field3和