XSD 1.1含xsi:type谓词的XPath断言验证失败问题排查
问题:XSD 1.1断言无法正确验证genreRef指向Mystery类型genre的约束
实例XML文档
<books xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <book> <genreRef>ID000</genreRef> </book> <genres> <genre xsi:type="Mystery" referenceId="ID000"/> <genre xsi:type="Action" referenceId="ID001"/> <genre xsi:type="Fantasy" referenceId="ID002"/> </genres> </books>
需求说明
需要约束<genreRef>元素仅指向类型为Mystery的<genre>元素,若指向Action或Fantasy类型的<genre>则视为错误,要求通过XSD 1.1的断言实现该校验。
编写的XSD 1.1 Schema
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <xs:element name="books"> <xs:complexType> <xs:sequence> <xs:element name="book" maxOccurs="unbounded"> <xs:complexType> <xs:sequence> <xs:element name="genreRef" type="xs:IDREF"/> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="genres"> <xs:complexType> <xs:sequence> <xs:element name="genre" type="Genre" maxOccurs="unbounded"/> </xs:sequence> </xs:complexType> </xs:element> </xs:sequence> <xs:assert test="every $i in book satisfies some $j in genres/genre[@xsi:type = 'Mystery'] satisfies $i/genreRef eq $j/@referenceId" /> </xs:complexType> </xs:element> <xs:complexType name="Genre" abstract="true"> <xs:sequence> </xs:sequence> <xs:attribute name="referenceId" use="required" type="xs:ID"/> </xs:complexType> <xs:complexType name="Mystery"> <xs:complexContent> <xs:extension base="Genre"/> </xs:complexContent> </xs:complexType> <xs:complexType name="Action"> <xs:complexContent> <xs:extension base="Genre"/> </xs:complexContent> </xs:complexType> <xs:complexType name="Fantasy"> <xs:complexContent> <xs:extension base="Genre"/> </xs:complexContent> </xs:complexType> </xs:schema>
遇到的错误
Element books does not satisfy assertion every $i in book satisfies some $j in genres/genre[@xsi:type = 'Mystery'] satisfies $i/genreRef eq $j/@referenceId
问题原因与修复方案
原因分析
直接通过@xsi:type = 'Mystery'匹配元素类型的方式存在两个问题:
xsi:type属性的类型是xs:QName,直接与字符串'Mystery'比较时,SAXON的类型处理逻辑可能导致匹配失败;- 依赖属性值判断类型不够可靠,若属性被篡改,校验会失效,正确的做法是直接判断元素的实际类型。
修复步骤
将<xs:assert>的test表达式修改为使用XPath的instance of语法直接校验<genre>元素的实际类型:
<xs:assert test="every $i in book satisfies some $j in genres/genre satisfies $j instance of element(*, Mystery) and $i/genreRef eq $j/@referenceId" />
或者更简洁的过滤写法:
<xs:assert test="every $i in book satisfies some $j in genres/genre[. instance of element(*, Mystery)] satisfies $i/genreRef eq $j/@referenceId" />
修复后的完整XSD
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <xs:element name="books"> <xs:complexType> <xs:sequence> <xs:element name="book" maxOccurs="unbounded"> <xs:complexType> <xs:sequence> <xs:element name="genreRef" type="xs:IDREF"/> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="genres"> <xs:complexType> <xs:sequence> <xs:element name="genre" type="Genre" maxOccurs="unbounded"/> </xs:sequence> </xs:complexType> </xs:element> </xs:sequence> <xs:assert test="every $i in book satisfies some $j in genres/genre satisfies $j instance of element(*, Mystery) and $i/genreRef eq $j/@referenceId" /> </xs:complexType> </xs:element> <xs:complexType name="Genre" abstract="true"> <xs:sequence> </xs:sequence> <xs:attribute name="referenceId" use="required" type="xs:ID"/> </xs:complexType> <xs:complexType name="Mystery"> <xs:complexContent> <xs:extension base="Genre"/> </xs:complexContent> </xs:complexType> <xs:complexType name="Action"> <xs:complexContent> <xs:extension base="Genre"/> </xs:complexContent> </xs:complexType> <xs:complexType name="Fantasy"> <xs:complexContent> <xs:extension base="Genre"/> </xs:complexContent> </xs:complexType> </xs:schema>
这个修改通过instance of element(*, Mystery)直接判断<genre>元素的实际类型为Mystery,再结合IDREF与ID的匹配校验,能准确实现你需要的约束,且在SAXON中可以正常生效。
内容的提问来源于stack exchange,提问作者Roger Costello
相关产品推荐
相关产品推荐

