使用XSD验证XML……但仍允许可扩展性。

34

也许只是我自己的问题,但似乎如果你有一个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="User">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="GivenName" />
                <xs:element name="SurName" />
            </xs:sequence>
            <xs:attribute name="ID" type="xs:unsignedByte" use="required" />
        </xs:complexType>
    </xs:element>
</xs:schema>

定义此文档模式的模式

<?xml version="1.0" encoding="utf-8" ?>
<User ID="1">
    <GivenName></GivenName>
    <SurName></SurName>
</User>

如果您添加另一个元素,比如EmailAddress,并混淆顺序,则验证将失败。

<?xml version="1.0" encoding="utf-8" ?>
<User ID="1">
    <SurName></SurName>
    <EmailAddress></EmailAddress>
    <GivenName></GivenName>
</User>

我不想将EmailAddress添加到文档中并标记为可选。

我只想要一个XSD,验证文档必须满足最基本的要求。

有没有办法做到这一点?

编辑:

如下所示,您可以在xs:sequence内部使用xs:any来允许更多元素,但不幸的是,您必须维护元素的顺序。

或者,我可以使用xs:all,它不强制执行元素的顺序,但遗憾的是,我无法在其中放置xs:any


5
有一些不错的回答和讨论,但我必须选择Abel的回答,因为它非常详细,并且解释了为什么我所寻找的东西不存在。 - CaffGeek
5个回答

57
你的问题有解决方案,但结果可能不太好看。原因如下:
非确定性内容模型的违规
你触及了W3C XML Schema的核心。你所要求的——变量顺序和未知元素——违反了XSD最困难、也是最基本的原则,即“非歧义性”规则,或更正式地说,唯一粒子属性约束:
引用如下:
内容模型必须形成这样一个过程,在验证期间,可以在不检查该项的内容或属性,并且没有关于序列其余项目的任何信息的情况下,唯一确定序列中的每个项目。
通俗易懂地说:当XML被验证并且XSD处理器遇到时,它必须能够在不首先检查它是否后跟的情况下进行验证,即不能向前查看。在您的情况下,这是不可能的。此规则存在的目的是允许通过有限状态机实现,这应该使实现变得相当简单和快速。
这是最受争议的问题之一,是SGML和DTD(内容模型必须是确定性的)以及XML的遗产,XML默认定义元素的顺序很重要(因此,尝试相反,使顺序不重要是困难的)。

正如Marc_s所建议的那样,Relax_NG是一种替代方案,允许使用非确定性内容模型。但如果您被W3C XML Schema束缚住了怎么办?

不起作用的半有效解决方案

您已经注意到xs:all非常严格。原因很简单:同样的非确定性规则适用,这就是为什么不允许xs:anymin/maxOccurs大于1和序列。

此外,您可能已经尝试了各种组合choicesequenceany。当Microsoft XSD处理器遇到这种无效情况时,会抛出以下错误:

错误:元素'http://example.com/Chad:SurName'的多个定义导致内容模型变得模糊。内容模型必须形成这样一个模型,以便在验证元素信息项序列期间,在其中直接、间接或隐含地包含的粒子中,用于尝试逐个验证序列中每个项目的粒子可以唯一确定,而无需检查该项目的内容或属性,并且不需要有关其余序列中的项目的任何信息。

O'Reilly的XML Schema(是的,这本书有缺陷)中,这个问题得到了很好的解释。幸运的是,该书的部分内容可以在线获取。我强烈建议您仔细阅读第7.4.1.3节关于唯一粒子归属规则,他们的解释和示例比我能提供的更清晰。

一个可行的解决方案

在大多数情况下,从不确定性设计转换为确定性设计是可能的。这通常看起来并不美观,但如果您必须坚持使用W3C XML Schema和/或如果您绝对必须允许非严格规则到您的XML,则这是一个解决方案。你的情况的噩梦是,你想强制执行一件事情(2个预定义元素),同时想要它非常松散(顺序无关紧要并且任何东西都可以放在之间,在之前和之后)。如果我不试图给你好的建议,而只是直接带你去一个解决方案,它将如下所示:
<xs:element name="User">
    <xs:complexType>
        <xs:sequence>
            <xs:any minOccurs="0" processContents="lax" namespace="##other" />
            <xs:choice>
                <xs:sequence>                        
                    <xs:element name="GivenName" />
                    <xs:any minOccurs="0" processContents="lax" namespace="##other" />
                    <xs:element name="SurName" />
                </xs:sequence>
                <xs:sequence>
                    <xs:element name="SurName" />
                    <xs:any minOccurs="0" processContents="lax" namespace="##other" />
                    <xs:element name="GivenName" />
                </xs:sequence>
            </xs:choice>
            <xs:any minOccurs="0" processContents="lax" namespace="##any" />
        </xs:sequence>
        <xs:attribute name="ID" type="xs:unsignedByte" use="required" />
    </xs:complexType>
</xs:element>

上面的代码实际上非常有效。但是有一些注意事项。第一个是 xs:any,其命名空间为 ##other。您不能使用 ##any,除了最后一个之外,因为这将允许像 GivenName 这样的元素被用作替代,这意味着 User 的定义变得模糊不清。
第二个注意事项是,如果要使用此技巧超过两个或三个,您将不得不写下所有组合。这是一个维护噩梦。这就是我提出以下建议的原因:
一个建议的解决方案,一种变量内容容器的变体
更改您的定义。这有助于使您的读者或用户更加清晰。它还具有易于维护的优点。一系列解决方案在 XFront 上 进行了解释,这是一个不太可读的链接,您可能已经从 Oleg 的帖子中看到过。它是一个很好的阅读材料,但大部分内容都没有考虑到您需要在可变内容容器内放置至少两个元素的最低要求。
目前的最佳做法是将数据分为必填字段和非必填字段。您可以添加一个元素 <Required>,或者相反,添加一个元素 <ExtendedInfo>(或称为 PropertiesOptionalData)。它看起来如下:
<xs:element name="User2">
    <xs:complexType>
        <xs:sequence>
            <xs:element name="GivenName" />
            <xs:element name="SurName" />
            <xs:element name="ExtendedInfo" minOccurs="0">
                <xs:complexType>
                    <xs:sequence>
                        <xs:any minOccurs="0" maxOccurs="unbounded" processContents="lax" namespace="##any" />
                    </xs:sequence>
                </xs:complexType>
            </xs:element>
        </xs:sequence>
    </xs:complexType>
</xs:element>

目前看来这似乎不是很理想,但让它再发展一下。拥有一组有序的固定元素并不是什么大问题。你不是唯一一个会抱怨W3C XML Schema表面上的这个缺陷,但正如我之前所说,如果你必须使用它,你将不得不忍受它的限制,或者接受以更高的拥有成本开发这些限制的解决方案。

备选方案

我相信你已经知道了,属性的顺序默认情况下是无法确定的。如果您所有的内容都是简单类型,您可以选择更丰富地使用属性。

最后的话

无论采取何种方法,您都将失去很多数据的可验证性。通常最好允许内容提供者添加内容类型,但仅在可以验证时才允许。这可以通过从宽松处理切换到严格处理并使类型本身更严格来实现。但过于严格也不好,正确的平衡将取决于您评估的用例和权衡某些实现策略的折衷。


@John,不用谢。现在我只希望它也能帮助Chad解决他的问题;-) - Abel
1
哇...好答案。它绝对解释了背后的原理!我另一个想法是使用xsl/xsd组合,通过运行输入xml并仅保留我希望验证(最小要求)的元素来进行验证,然后将其与xsd进行验证。如果通过,则允许原始xml通过。但我认为它不够高效。 - CaffGeek
@Chad:如果你考虑使用XSLT + XPath,请考虑从W3C XML Schema转换到Schematron。它也是一种ISO标准的XML Schema语言,可以很好地与XSLT、XPath和一些正则表达式配合使用。Schematron的工作方式与其他方法相反:它是基于规则的,您可以为树模式定义规则,而不是使用XSD定义语法。如果您有一些XSLT的经验,那么采用它的6个元素应该很容易。 - Abel

6
在阅读了marc_s的回答和你们在评论中的讨论后,我决定再添加一些内容。
在我的看法中,没有完美的解决方案可以解决你的问题,Chad。有一些方法可以在XSD中实现可扩展的内容模型,但是所有我所知道的实现都有一些限制。因为你没有写出你计划在哪个环境中使用可扩展的XSD,我只能向你推荐一些链接,这些链接可能会帮助你选择可以在你的环境中实现的方式:
  1. http://www.xfront.com/ExtensibleContentModels.html(或http://www.xfront.com/ExtensibleContentModels.pdf)和http://www.xfront.com/VariableContentContainers.html
  2. http://www.xml.com/lpt/a/993(或http://www.xml.com/pub/a/2002/07/03/schema_design.html
  3. http://msdn.microsoft.com/en-us/library/ms950793.aspx

3
链接到xfront网站,值得点赞,该网站对该主题的处理仍是经典的。 - Abel

4

您应该能够使用<xs:any>元素来扩展模式以实现可扩展性 - 有关详细信息,请参见W3Schools

<?xml version="1.0" encoding="utf-8"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:element name="User">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="GivenName" />
                <xs:element name="SurName" />
                <xs:any minOccurs="0" maxOccurs="unbounded" processContents="lax" />
            </xs:sequence>
            <xs:attribute name="ID" type="xs:unsignedByte" use="required" />
        </xs:complexType>
    </xs:element>
</xs:schema>

当您添加processContents="lax"时,.NET XML验证应该会成功。
有关更多详细信息,请参见xs:any的MSDN文档
更新:如果您需要更灵活、更宽松的验证,您可能需要查看其他定义XML模式的方法,例如RelaxNG。 XML Schema故意对其规则要求非常严格,因此也许这不是手头工作的正确工具。

当,那几乎可以工作,顺序变得重要。GivenName和SurName必须分别成为第一个和第二个元素...至少我认为这是xs:sequence的效果。我可以将其更改为xs:all。但是,然后我就无法使用xs:any了... - CaffGeek
这告诉我XSD本质上是有问题的。XML应该是可扩展的。元素的顺序不应该有影响。首先,为什么可以定义一个序列?其次,为什么我们不能编写可扩展的XSD? - CaffGeek
我认识到“序列就是序列”,但我认为它的存在是不好的,因为它违反了XML的基本原则——可扩展性。除此之外,为什么我不能定义一个真正可扩展的XSD呢?使用xs:sequencexs:any有点奏效,但我必须确保序列化元素首先出现并按顺序排列……这在XML的本质上是无关紧要的。 - CaffGeek
@Chad:也许,如果你真的需要这种灵活性,XML模式可能不是最合适的工具。你是否看过RelaxNG?http://www.relaxng.org/ - marc_s
我需要定义一个最低一致性级别。我从未说过“任何地方,任何时候都可以”。我需要定义一个最小文档,并允许其可扩展性。如果您需要在XML中定义元素的“序列”,我建议您使用带有序列属性的元素来定义它。依赖文档中元素的顺序感觉像是对XML本质的冒犯。 - CaffGeek

1
如果您能使用它,RelaxNG将简洁地解决此问题。确定性不是模式的要求。您可以将RNG或RNC模式转换为XSD,但在这种情况下,它会近似。是否足够好取决于您自己。
这种情况的RNC模式如下:
start = User
User = element User {
   attribute ID { xsd:unsignedByte },
   ( element GivenName { text } &
     element SurName { text } &
     element * - (SurName | GivenName) { any })
}

any = element * { (attribute * { text } | text | any)* }

任何规则都匹配任何格式良好的XML片段。因此,这将要求User元素包含GivenName和SurName元素,这些元素中包含任意顺序的文本,并允许包含几乎任何内容的其他元素。

1

嗯,你总是可以使用DTD :-) 除非DTD也要求有序。使用“无序”语法进行验证非常昂贵。你可以尝试使用xsd:choice和min和max occurs来进行调整,但它可能也会出现问题。你还可以编写XSD扩展/派生模式。

从你提出的问题来看,似乎你根本不想用XSD。你只需加载它,然后使用XPath验证你想要的最小限制,但仅仅反对XSD,并在它成为无处不在的标准多年后表达抱怨,真的是没有任何意义。


使用XPath进行加载和验证的问题在于它最终会变成代码,并且很难更改。我并不反对XSD,我经常使用它们,但直到遇到这个问题,我才真正意识到它们的缺陷。在我看来,如果您拥有一个数据格式,其最佳销售特点是可扩展性,但不允许在结构中进行真正可扩展的定义...那么您就失败了。或者也许我只是疯了。 - CaffGeek
@Chad / @zb_z:如果有帮助的话:Tim Bray和其他在W3C非常知名的人士也认为他们“错过了目标”。我已经试图解释这个问题,顺便说一下,这也适用于DTD,它被称为“唯一粒子约束”,请参见上面(或下面);-) - Abel
如果您真的非常坚持进行任意标签重新排列,那么这里列出了一些选项。您可以将XPath保存在配置文件中。关键点是这种奢侈品需要付出巨大的性能代价。这就是为什么XML的DTD将其剔除并且XSD的支持非常有限的核心原因。如果您看到过DTD和XSD检查器有时会抱怨语法是不确定的,那么另一种观点是:解析器无法根据当前和下一个标记决定下一步该做什么,它需要无限向前看并检查/枚举排列组合。 - ZXX
这些确实是支持反对非确定性模式的论点。但与此同时,实际上,在当时已经证明这并不像看起来那么困难,并且性能下降可以忽略不计。Schematron和Relax NG都表明,非确定性不是问题。当然,你的模式是否良好设计是完全另一回事。 - Abel
1
有没有使用 Haskell 或其他回溯的任何性能结果和实现?预期 XSD 验证为 0 向前查看,这对于自动代码生成和序列化非常重要,具有重要的性能、稳定性和流式传输后果。需要保证最坏情况的复杂度。如果我的记忆没有错,Relax NG 没有 XSD 的基数限制(交错验证更昂贵),并且基本上不是 0 向前查看。我们不要忘记,使用 1 向前查看可以解析 C 代码,但仍然没有 NFA。 - ZXX

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接