使用 XmlSchemaSet 加载 XSD

使用 XmlSchemaSet 加载 XSD,并进行验证的代码,网上一搜便有。写这篇文章的目的,是因为在实际使用过程中遇到两个问题:1,如何处理 XSD 与 XML 名称空间不匹配? 2,如何加载有 Import 元素的 XSD?


先上一段最简单的代码

XmlSchemaSet schemas = new XmlSchemaSet();
XmlSchema schema = XmlSchema.Read(new XmlTextReader("Sample.xsd"), null);
schemas.Add(schema);

XmlReaderSettings settings = new XmlReaderSettings();
settings.Schemas.Add(schemas);
settings.ValidationType = ValidationType.Schema;
settings.ValidationEventHandler += new ValidationEventHandler((o, e) =>
    {
        Console.WriteLine(e.Message);
    }); 

string xml = @"<test xmlns='http://example.com' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>   
           <el xmlns='http://chenxu.me'>112.1</el>                
         </test>";

//验证
XmlReader reader = XmlReader.Create(new StringReader(xml), settings);
while (reader.Read()) ;

上述的代码便可实现对 xml 的验证。


也可以用 Linq to XML 对上述代码进行简化

XmlSchemaSet schemas = new XmlSchemaSet();
XmlSchema schema = XmlSchema.Read(new XmlTextReader("Sample.xsd"), null);
schemas.Add(schema);

string xml = @"<test xmlns='http://example.com' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>   
                   <el xmlns='http://chenxu.me'>112.1</el>                
               </test>";
               
XDocument.Parse(xml).Validate(schemas, (o,e)=> Console.WriteLine(e.Message));

上述代码运行起来不会有任何问题,XSD 也能通过验证。


问题一:如何处理 XSD 与 XML 名称空间不匹配?

在继续之前,我先展示一下上面提到的 Sample.xsd

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://example.com" targetNamespace="http://example.com" elementFormDefault="qualified" attributeFormDefault="unqualified" xmlns:cx="http://chenxu.me">
<xs:import namespace="http://chenxu.me" schemaLocation="Sample2.xsd"/>
    <xs:element name="test">
        <xs:annotation>
            <xs:documentation>Comment describing your root element</xs:documentation>
        </xs:annotation>
        <xs:complexType>
	    <xs:sequence>
                <xs:element  ref="cx:el" minOccurs="1" />
	    </xs:sequence>
        </xs:complexType>
    </xs:element>
</xs:schema>

这个 xsd 中又引用了 Sample2.xsd

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://chenxu.me" targetNamespace="http://chenxu.me" elementFormDefault="qualified" attributeFormDefault="unqualified">
   <xs:element name="el">        
        <xs:simpleType>
            <xs:restriction base="xs:string">
                <xs:pattern value="[0-9]+\.+[0-9]+" />
            </xs:restriction>
        </xs:simpleType>
    </xs:element>
</xs:schema>

现在,请大家比较下 XSD 与 XML 关于 test 这个元素的名称空间的定义。使用上述 C# 代码进行验证的时候只有当两者的名称空间一致才会进行验证,否则只会警告,而不会有错误,这仍然会通过验证。(也就是说,当两者名称对不上号的时候,即使 XSD 中定义的是 test,而 XML 中为 test1,仍然能通过验证)

而有些时候,我们希望在名称空间不一致的请问下仍然对此进行验证,此时需要设置 XmlReaderSettings.ValidationFlag。而该 Flag 无法在 Linq to XML 中进行设置,让警告也能被显示出来,修改后的代码如下:

XmlReaderSettings settings = new XmlReaderSettings();
settings.Schemas.Add(schemas);
settings.ValidationType = ValidationType.Schema;
settings.ValidationFlags = XmlSchemaValidationFlags.ReportValidationWarnings; //<-- 请注意


问题二:如何加载有 Import 元素的 XSD?

使用 XmlSchemaSet.Add 进行添加的时候,默认就会自动对 XSD 中的元素进行解析,如果发现有 Import 或 Include 的元素就会去进行加载。

那还有什么好说的?

一般情况下,的确不会有问题。但是假设你需要手动加载三个 XSD 的时候,而其中第一个 XSD 内部又 Import 了第三个 XSD。此时,加载顺序就很重要,因为如果同一个 XSD 被加载多次,就会报 “xxx元素已经被定义” 的错误。

正确的加载顺序是,先递归找到最内层需要 Import 的 XSD,然后再加载外层的 XSD,因为当进行 XSD 解析的时候,如果发现需要 Import 的 XSD 已经存在了,就不会再 Import 一次。

回到上面的例子,需要先加载第三个 XSD,然后再加载第一个和第二个。


举个例子,加载 WSDL 的时候把所有相关的 XSD 加载进来,用于之后对请求和响应的验证:

HashSet<string> _url = new HashSet<string>(); 
bool Validate(string message)
{
    try    
    {
        XmlSchemaSet schemas = new XmlSchemaSet();
        LoadServices(_schemaLocation, schemas);
 
        bool success = true;
        XDocument.Parse(message).Validate(schemas, (o, e) =>
        {
            Console.WriteLine(e.Message);
            success = false;
        });
 
        return success;
    }
    catch (Exception)
    {
        throw;
    }
} 

//加载 wsdl
public void LoadServices(string schemaLocation, XmlSchemaSet schemas)
{
    ServiceDescription desc = System.Web.Services.Description.ServiceDescription.Read(new XmlTextReader(schemaLocation));
    foreach (Import import in desc.Imports)
    {
        LoadServices(import.Location, schemas);
    }
 
    foreach (XmlSchema xschema in desc.Types.Schemas)
    {
        LoadSchema(xschema, schemas);
    }
} 

//加载 XSD
public void LoadSchema(XmlSchema schema, XmlSchemaSet schemas)
{
    foreach (XmlSchemaImport include in schema.Includes)
    {
        if (_url.Contains(include.SchemaLocation))
        {
            continue;
        }
        else        
        {
            _url.Add(include.SchemaLocation);
        }
        
        XmlSchema sc = XmlSchema.Read(new XmlTextReader(include.SchemaLocation), null);
        LoadSchema(sc, schemas);
        schemas.Add(sc);
    }
}


扩展阅读

XmlSchemaSet for Schema Compilation

文章索引

[隐 藏]

本站采用知识共享署名 3.0 中国大陆许可协议进行许可。 ©2014 Charley Box | 关于本站 | 浙ICP备13014059号