1

我有一组针对 XMLSPY 和 Java 代码进行验证的 XSD。我需要将这组 XSD 作为嵌入资源引入 Visual Studio 2012 .net。不幸的是,当我尝试使用自定义 XmlResolver 来处理 xsd:include 来解决它们时,我收到一个错误,即已经声明了一个全局元素。错误很奇怪,因为该元素只声明了一次。

Visual Studio Solution
    |-----------    Visual Studio Project
            |-----------    Schemas (Embedded Resource)
                    |-----------    Directory A
                            |------------ set of XSDs that are referenced by XSDs in Directory B and to a globaltype definition file located in this directory
                    |-----------    Directory B
                            |------------- set of XSDs that reference each other and those in Directory A, the XSD call from the main is located here

验证 Util 类

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;

namespace ABC.XYZ.Utils
{
    public static class XmlUtil
    {
        private static bool isValid;

        public static bool ValidateXml(string targetNamespace, string schemaUri, string xml)
        {
            isValid = true;

            var schemaReaderSettings = new XmlReaderSettings() { ValidationType = ValidationType.Schema };
            schemaReaderSettings.ValidationEventHandler += MyValidationHandler;

            schemaReaderSettings.Schemas.XmlResolver = new XmlResourceResolver();
            var schemaReader = XmlReader.Create(GetSchemaStream(schemaUri), schemaReaderSettings);
            schemaReaderSettings.Schemas.Add(targetNamespace, schemaReader);

            var x = XElement.Parse(xml);
            var sr = new System.IO.StringReader(x.ToString());

            XmlReader validatingReader = XmlReader.Create(sr, schemaReaderSettings);

            while (validatingReader.Read())
            {
            }

            validatingReader.Close();

            return isValid;
        }

        private static void MyValidationHandler(object sender, ValidationEventArgs args)
        {
            Console.WriteLine("***Validation error");
            Console.WriteLine("\tSeverity:{0}", args.Severity);
            Console.WriteLine("\tMessage:{0}", args.Message);
            isValid = false;
        }

        private static Stream GetSchemaStream(string relativeFileName)
        {
            var resourceFileName =
                Assembly.GetExecutingAssembly()
                    .GetManifestResourceNames()
                    .FirstOrDefault(p => p.EndsWith(relativeFileName));
            return Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceFileName);
        }

    }
}

自定义 XmlResolver

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Xml;

namespace ABC.XYZ.Utils
{
    public class XmlResourceResolver : XmlResolver
    {
        public const string AssemblyDefaultNamespace = "ABC.XYZ";
        public const string SchemasNamespace = "Schemas";

        public override Uri ResolveUri(Uri baseUri, string relativeUri)
        {
            var result = new UriBuilder("res://", AssemblyDefaultNamespace, -1, SchemasNamespace.Replace(".", "/"));
            result.Path += "/" + relativeUri.Replace("../", "/").TrimStart('/');
            return result.Uri;
        }

        public override object GetEntity(Uri absoluteUri, string role, Type ofObjectToReturn)
        {
            if (absoluteUri.Scheme != "res") return null;
            Debug.WriteLine("Loading resource based on location {0}", absoluteUri);

            var assembly = Assembly.GetExecutingAssembly();
            var name = String.Format(CultureInfo.InvariantCulture, "{0}{1}",
                absoluteUri.Host,
                absoluteUri.GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped).Replace("/", "."));

            // try for an exact match based on schemaLocation hint path
            var resourceName = (from x in assembly.GetManifestResourceNames()
                                where name.Equals(x, StringComparison.OrdinalIgnoreCase)
                                select x).FirstOrDefault();

            // if not match based on filename alone
            if (resourceName == null)
            {
                var schemaDocumentName = Path.GetFileName(absoluteUri.AbsolutePath);
                Debug.WriteLine("Unable to locate exact match, looking for match based on filename {0}", schemaDocumentName);
                resourceName = (from x in assembly.GetManifestResourceNames()
                                where x.Contains(SchemasNamespace) && 
                                      x.EndsWith("." + schemaDocumentName, StringComparison.OrdinalIgnoreCase)
                                select x).FirstOrDefault();
            }

            Debug.WriteLine("Loading resource {0}", resourceName);
            var stream = assembly.GetManifestResourceStream(resourceName);
            return stream;
        }
    }

}

对此问题的任何见解将不胜感激。

4

2 回答 2

0

问题是当您使用 XMLSpy 时,XSD 是文件系统中的文件。然后发生了什么,对于每个文件都有一个基本 URI,因此解析器将使用该信息来确保一旦加载 XSD,就不会再次加载相同的 XSD,基于 URI 比较。

现在,您通过从程序集中作为流加载来执行此操作的方式,所有信息都消失了(您的流没有基本 URI)。您的解析器将不断地从不同的地方一遍又一遍地加载相同的 XSD,从而产生这种冲突。

我所知道的所有 XSD 处理器,不使用任何其他方法来过滤相同 XSD 内容的多个包含,而是基本源 URI。

在 .NET 中,最简单的方法可能是(同样,取决于您的图形的复杂程度)尝试本文中的解决方案;整个想法是提供一个基本 URI,它应该提供避免多次包含所需的信息。

另一种选择可能是确保在您的自定义解析器中,对于您正在解析的任何给定 URI,您只返回一次流(在所有其他情况下返回 null)。只要您不使用 xsd:redefine 组合,就可以保证工作(在这种情况下,解决方案是对架构文件图进行拓扑排序并确保首先加载所有 xsd:redefines)。

对于@CMSperbergMcQueen 而言,一种保证有效的方法是重构所有 XSD,以便每个命名空间只有一个 XSD 嵌入式资源;每个 XSD 都会删除所有导入(称为“悬空”的技术)。将这些 XSD 添加到 XML Schema 集中作为独立的 XSD 并编译。除非遇到 .NET 错误,否则结果应该是已编译的 XmlSchemaSet。

于 2013-08-16T16:53:29.387 回答
0

XSD 1.0 鼓励但不要求验证器检测同一模式文档的多个包含(或导入)并且只包含一次。

结果是,从多个其他模式文档中多次包含相同的模式文档是使用 XSD 创建互操作性噩梦的最简单方法。(不是唯一的方法,只是最简单的方法。)如果您拥有架构文档,请将导入的所有包含和所有架构位置信息隔离到驱动程序文件中,并从“正常”文件中删除所有包含和导入的所有架构位置提示' 架构文档。

于 2013-08-16T14:04:14.077 回答