XSD 1.0能否按属性值区分同名Parent元素的类型?
问题描述
需要为以下XML文件编写符合XSD 1.0规范的Schema(用于客户生成XML,我方校验),要求Parent元素的类型由DeterminingAttribute属性值决定。使用环境为.NET Framework 4.7.2,核心工具如XmlReader不支持XSD 1.1,且不能使用商业插件。
目标XML示例
<?xml version="1.0" encoding="utf-8"?> <Wrapper> <Parent Id="1" DeterminingAttribute="A"> <Data> <base1></base1> <base2></base2> <A1></A1> <A2></A2> </Data> </Parent> <Parent Id="2" DeterminingAttribute="B"> <Data> <base1></base1> <base2></base2> <B1></B1> <B2></B2> </Data> </Parent> </Wrapper>
(注:原始XML中第二个Parent标签拼写错误为Parrent,需修正为Parent才能通过基础校验)
现有XSD及报错
尝试编写的XSD如下,Visual Studio显示格式合法,但代码校验时抛出错误:提示Parent元素的父元素需包含至少一个元素,且Data元素不合法。
<?xml version="1.0" encoding="utf-8"?> <xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="Wrapper"> <xs:complexType> <xs:sequence minOccurs="0"> <xs:element minOccurs="0" name="Parent" type="Parent-base"></xs:element> </xs:sequence> </xs:complexType> </xs:element> <xs:complexType name="Parent-base" abstract="true"> <xs:attribute name="Id" type="xs:unsignedInt"></xs:attribute> </xs:complexType> <xs:complexType name="Parent-spec-A"> <xs:complexContent> <xs:extension base="Parent-base"> <xs:sequence> <xs:element name="Data" type="Data-spec-A"></xs:element> </xs:sequence> <xs:attribute name="DeterminingAttribute" type="xs:string" fixed="A"></xs:attribute> </xs:extension> </xs:complexContent> </xs:complexType> <xs:complexType name="Parent-spec-B"> <xs:complexContent> <xs:extension base="Parent-base"> <xs:sequence> <xs:element name="Data" type="Data-spec-B"></xs:element> </xs:sequence> <xs:attribute name="DeterminingAttribute" type="xs:string" fixed="B"></xs:attribute> </xs:extension> </xs:complexContent> </xs:complexType> <xs:complexType name="Data-spec-A"> <xs:sequence> <xs:element name="base1" type="xs:unsignedByte" /> <xs:element name="base2" type="xs:string" /> <xs:element name="A1" type="xs:unsignedShort" /> <xs:element name="A2" type="xs:unsignedShort" /> </xs:sequence> </xs:complexType> <xs:complexType name="Data-spec-B"> <xs:sequence> <xs:element name="base1" type="xs:unsignedByte" /> <xs:element name="base2" type="xs:string" /> <xs:element name="B1" type="xs:unsignedShort" /> <xs:element name="B2" type="xs:unsignedShort" /> </xs:sequence> </xs:complexType> </xs:schema>
解决方案
XSD 1.0不支持条件类型赋值(即根据属性值动态绑定元素类型,这是XSD 1.1的特性),所以需要调整设计,以下是两种可行方案:
方案1:修改XML结构,使用替换组(Substitution Groups)
这种方案需要调整XML中Parent元素的命名,让不同类型的Parent使用不同标签名,通过XSD替换组实现类型约束,同时保留原有的属性逻辑。
调整后的XML示例
<?xml version="1.0" encoding="utf-8"?> <Wrapper> <ParentA Id="1" DeterminingAttribute="A"> <Data> <base1></base1> <base2></base2> <A1></A1> <A2></A2> </Data> </ParentA> <ParentB Id="2" DeterminingAttribute="B"> <Data> <base1></base1> <base2></base2> <B1></B1> <B2></B2> </Data> </ParentB> </Wrapper>
对应的XSD
<?xml version="1.0" encoding="utf-8"?> <xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <!-- 定义抽象的Parent元素作为替换组的头 --> <xs:element name="Parent" abstract="true"/> <!-- Wrapper元素定义:接受任意数量的Parent替换组成员 --> <xs:element name="Wrapper"> <xs:complexType> <xs:sequence> <xs:element ref="Parent" minOccurs="0" maxOccurs="unbounded"/> </xs:sequence> </xs:complexType> </xs:element> <!-- 基础Parent类型 --> <xs:complexType name="Parent-base"> <xs:sequence> <xs:element name="Data" type="Data-base"/> </xs:sequence> <xs:attribute name="Id" type="xs:unsignedInt" use="required"/> <xs:attribute name="DeterminingAttribute" type="xs:string" use="required"/> </xs:complexType> <!-- 基础Data类型:包含公共元素base1、base2 --> <xs:complexType name="Data-base"> <xs:sequence> <xs:element name="base1" type="xs:unsignedByte" nillable="true"/> <xs:element name="base2" type="xs:string" nillable="true"/> <!-- 预留扩展点,用于不同类型的Data添加专属元素 --> <xs:any minOccurs="0" maxOccurs="unbounded" processContents="lax"/> </xs:sequence> </xs:complexType> <!-- ParentA的具体类型 --> <xs:element name="ParentA" substitutionGroup="Parent"> <xs:complexType> <xs:complexContent> <xs:extension base="Parent-base"> <xs:sequence> <xs:element name="Data"> <xs:complexType> <xs:complexContent> <xs:extension base="Data-base"> <xs:sequence> <xs:element name="A1" type="xs:unsignedShort" nillable="true"/> <xs:element name="A2" type="xs:unsignedShort" nillable="true"/> </xs:sequence> </xs:extension> </xs:complexContent> </xs:complexType> </xs:element> </xs:sequence> <xs:attribute name="DeterminingAttribute" fixed="A"/> </xs:extension> </xs:complexContent> </xs:complexType> </xs:element> <!-- ParentB的具体类型 --> <xs:element name="ParentB" substitutionGroup="Parent"> <xs:complexType> <xs:complexContent> <xs:extension base="Parent-base"> <xs:sequence> <xs:element name="Data"> <xs:complexType> <xs:complexContent> <xs:extension base="Data-base"> <xs:sequence> <xs:element name="B1" type="xs:unsignedShort" nillable="true"/> <xs:element name="B2" type="xs:unsignedShort" nillable="true"/> </xs:sequence> </xs:extension> </xs:complexContent> </xs:complexType> </xs:element> </xs:sequence> <xs:attribute name="DeterminingAttribute" fixed="B"/> </xs:extension> </xs:complexContent> </xs:complexType> </xs:element> </xs:schema>
说明:
- 使用
substitutionGroup定义可替换的Parent元素,ParentA和ParentB都属于Parent替换组,能被Wrapper接受 - 为
Data定义基础类型,再分别扩展出对应A、B的专属元素 - 设置
nillable="true"允许元素为空(匹配原始XML中空元素的情况) DeterminingAttribute设置固定值,确保类型与属性值一致
方案2:保留原XML结构,宽松XSD配合自定义校验
如果无法修改XML结构,只能先编写一个宽松的XSD覆盖所有可能的情况,再通过.NET代码添加自定义逻辑校验DeterminingAttribute与Data子元素的对应关系。
宽松版XSD
<?xml version="1.0" encoding="utf-8"?> <xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="Wrapper"> <xs:complexType> <xs:sequence> <xs:element name="Parent" minOccurs="0" maxOccurs="unbounded"> <xs:complexType> <xs:sequence> <xs:element name="Data"> <xs:complexType> <xs:sequence> <xs:element name="base1" type="xs:unsignedByte" nillable="true"/> <xs:element name="base2" type="xs:string" nillable="true"/> <!-- 允许A1/A2或B1/B2出现,通过代码校验互斥 --> <xs:choice minOccurs="0"> <xs:sequence> <xs:element name="A1" type="xs:unsignedShort" nillable="true"/> <xs:element name="A2" type="xs:unsignedShort" nillable="true"/> </xs:sequence> <xs:sequence> <xs:element name="B1" type="xs:unsignedShort" nillable="true"/> <xs:element name="B2" type="xs:unsignedShort" nillable="true"/> </xs:sequence> </xs:choice> </xs:sequence> </xs:complexType> </xs:element> </xs:sequence> <xs:attribute name="Id" type="xs:unsignedInt" use="required"/> <xs:attribute name="DeterminingAttribute" type="xs:string" use="required"/> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> </xs:schema>
自定义校验代码示例(C#)
using System; using System.Xml; using System.Xml.Schema; public class XmlValidator { public static void ValidateXml(string xmlPath, string xsdPath) { XmlSchemaSet schemaSet = new XmlSchemaSet(); schemaSet.Add("", xsdPath); XmlReaderSettings settings = new XmlReaderSettings(); settings.ValidationType = ValidationType.Schema; settings.Schemas = schemaSet; settings.ValidationEventHandler += ValidationEventHandler; // 先通过XSD基础校验 using (XmlReader reader = XmlReader.Create(xmlPath, settings)) { while (reader.Read()) { } } // 再执行自定义逻辑校验 XmlDocument doc = new XmlDocument(); doc.Load(xmlPath); XmlNodeList parentNodes = doc.SelectNodes("//Parent"); foreach (XmlNode parent in parentNodes) { string attrValue = parent.Attributes["DeterminingAttribute"]?.Value; XmlNode dataNode = parent.SelectSingleNode("Data"); if (attrValue == "A") { if (dataNode.SelectSingleNode("A1") == null || dataNode.SelectSingleNode("A2") == null) { throw new XmlSchemaValidationException($"Parent Id={parent.Attributes["Id"].Value}的DeterminingAttribute为A,但Data中缺少A1或A2元素"); } if (dataNode.SelectSingleNode("B1") != null || dataNode.SelectSingleNode("B2") != null) { throw new XmlSchemaValidationException($"Parent Id={parent.Attributes["Id"].Value}的DeterminingAttribute为A,Data中不应包含B1或B2元素"); } } else if (attrValue == "B") { if (dataNode.SelectSingleNode("B1") == null || dataNode.SelectSingleNode("B2") == null) { throw new XmlSchemaValidationException($"Parent Id={parent.Attributes["Id"].Value}的DeterminingAttribute为B,但Data中缺少B1或B2元素"); } if (dataNode.SelectSingleNode("A1") != null || dataNode.SelectSingleNode("A2") != null) { throw new XmlSchemaValidationException($"Parent Id={parent.Attributes["Id"].Value}的DeterminingAttribute为B,Data中不应包含A1或A2元素"); } } else { throw new XmlSchemaValidationException($"Parent Id={parent.Attributes["Id"].Value}的DeterminingAttribute值无效,只能是A或B"); } } Console.WriteLine("XML校验通过"); } private static void ValidationEventHandler(object sender, ValidationEventArgs e) { throw new XmlSchemaValidationException("XSD基础校验失败:" + e.Message); } }
说明:
- 宽松XSD允许
Data中出现A类或B类元素,先通过基础结构校验 - 自定义代码遍历每个
Parent元素,校验DeterminingAttribute值与Data子元素的匹配关系,确保属性值和元素类型一一对应
内容的提问来源于stack exchange,提问作者Nikola
相关产品推荐
相关产品推荐

