含多参数的XSD unique与keyref技术问题及XML引用验证
Alright, let's break down how to handle multi-parameter unique and keyref constraints in XSD, using your sample XML as a reference. First, let's recap the structure we're working with (note: I added a root element since XSD validation requires a single root node):
示例XML结构
<Root> <Register name="Error"> <Bits name="count"> <lsb>0</lsb> <msb>3</msb> </Bits> </Register> <Register name="Date"> <Bits name="minutes"> <lsb>0</lsb> <msb>1</msb> </Bits> <Bits name="seconds"> <lsb>2</lsb> <msb>3</msb> </Bits> </Register> <parameter name="errors_count"> <register> <!-- 预期引用:/Register@Error/Bits@count --> </register> </parameter> </Root>
核心需求梳理
Your goal is to validate that the <register> element inside <parameter> correctly references an existing <Bits> element, identified by two combined parameters:
- The
nameattribute of its parent<Register>(e.g., "Error") - The
nameattribute of the<Bits>itself (e.g., "count")
This is a classic composite key scenario for XSD's key/keyref mechanism.
实现多参数的key/keyref验证
Step 1: Define a Composite key for Register+Bits Pairs
First, we need to enforce that every <Bits> element has a unique combination of its parent <Register>'s name and its own name. We do this with a <xs:key> that uses two <xs:field> entries:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="Root"> <xs:complexType> <xs:sequence> <xs:element name="Register" maxOccurs="unbounded"> <xs:complexType> <xs:sequence> <xs:element name="Bits" maxOccurs="unbounded"> <xs:complexType> <xs:sequence> <xs:element name="lsb" type="xs:int"/> <xs:element name="msb" type="xs:int"/> </xs:sequence> <xs:attribute name="name" type="xs:string" use="required"/> </xs:complexType> </xs:element> </xs:sequence> <xs:attribute name="name" type="xs:string" use="required"/> </xs:complexType> </xs:element> <xs:element name="parameter" maxOccurs="unbounded"> <xs:complexType> <xs:sequence> <xs:element name="register"> <!-- 调整结构:拆分引用为两个明确字段,便于keyref验证 --> <xs:complexType> <xs:sequence> <xs:element name="reg-name" type="xs:string"/> <xs:element name="bit-name" type="xs:string"/> </xs:sequence> </xs:complexType> </xs:element> </xs:sequence> <xs:attribute name="name" type="xs:string" use="required"/> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> <!-- 定义复合键:Register@name + Bits@name 组合唯一 --> <xs:key name="RegisterBitsUniquePair"> <xs:selector xpath="Register/Bits"/> <!-- 选择所有Bits元素 --> <xs:field xpath="../@name"/> <!-- 取上层Register的name属性 --> <xs:field xpath="@name"/> <!-- 取Bits自身的name属性 --> </xs:key> <!-- 定义keyref:关联parameter的引用到复合键 --> <xs:keyref name="ParameterToBitsRef" refer="RegisterBitsUniquePair"> <xs:selector xpath="parameter/register"/> <!-- 选择所有register引用元素 --> <xs:field xpath="reg-name"/> <!-- 对应复合键的Register@name --> <xs:field xpath="bit-name"/> <!-- 对应复合键的Bits@name --> </xs:keyref> </xs:element> </xs:schema>
Step 2: Adjust the XML for Validatable References
Notice we modified the <register> element to split the path into two explicit fields (reg-name and bit-name). Your original comment uses a XPath string, but XSD's keyref can't parse XPath strings directly—it needs to match explicit node/attribute values. The adjusted XML would look like this:
<Root> <!-- ... 原Register/Bits内容不变 ... --> <parameter name="errors_count"> <register> <reg-name>Error</reg-name> <bit-name>count</bit-name> </register> </parameter> </Root>
Validation Behavior
- If you try to add a duplicate
<Bits name="count">under<Register name="Error">, thekeyconstraint will throw an error (duplicate composite key). - If you try to set
<reg-name>InvalidReg</reg-name>or<bit-name>InvalidBit</bit-name>in<parameter>, thekeyrefconstraint will fail, as no matching Register+Bits pair exists.
What If You Need to Keep the XPath String Format?
If you must retain the XPath string (like /Register@Error/Bits@count) in <register>, XSD 1.0's key/keyref won't work. Instead, you can use XSD 1.1 assertions (<xs:assert>) to parse the string and validate the existence of the target element. For example:
<xs:assert test="exists(../../*[local-name()='Register' and @name=substring-before(substring-after(register/text(), '@'), '/')]/Bits[@name=substring-after(register/text(), '@')])"/>
This uses XPath functions to extract the register and bit names from the string and check if the target element exists. Note that this requires an XSD 1.1-compliant validator.
内容的提问来源于stack exchange,提问作者morandg

