XSD 元素替换(Element Substitution)

XSD 的「元素替换」(Substitution Group)是 XSD 中最优雅、最强大的多态机制之一,
它可以让一个元素“代替”另一个元素出现,非常像面向对象中的继承 + 多态。

核心概念(3 分钟彻底搞懂)

角色英文作用必须满足的条件
头元素(Head)head element被别人替换的“父元素”必须是全局元素(直接写在 xs:schema 下)
成员元素(Member)member element可以代替头元素出现的“子元素”必须用 substitutionGroup="头元素名"
类型要求成员元素的类型必须是头元素类型的派生类型(可以相同)包括 extension、restriction、甚至同一个类型

最经典的例子:XML 购货单(PO – Purchase Order)

<!-- 实际 XML 中可以这样写(三种都合法) -->
<shipTo>张三</shipTo>                     <!-- 原始 -->
<customerNumber>10086</customerNumber>     <!-- 用子元素替换了 shipTo -->
<vipCustomer vipLevel="gold">李四</vipCustomer> <!-- 又一个替换 -->

对应的完整 XSD(背下来就无敌了):

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
           targetNamespace="http://example.com/po"
           xmlns:po="http://example.com/po"
           elementFormDefault="qualified">

  <!-- 1. 头元素(必须是全局元素) -->
  <xs:element name="shipTo" type="po:AddressType"/>

  <!-- 2. 成员元素 1:用客户编号代替地址 -->
  <xs:element name="customerNumber" type="xs:integer"
              substitutionGroup="po:shipTo"/>   <!-- 关键就是这一行 -->

  <!-- 3. 成员元素 2:VIP客户,带额外属性 -->
  <xs:element name="vipCustomer" substitutionGroup="po:shipTo">
    <xs:complexType>
      <xs:complexContent>
        <xs:extension base="po:AddressType">
          <xs:attribute name="vipLevel" type="xs:string"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <!-- 地址类型 -->
  <xs:complexType name="AddressType">
    <xs:sequence>
      <xs:element name="name"   type="xs:string"/>
      <xs:element name="street" type="xs:string"/>
      <xs:element name="city"   type="xs:string"/>
    </xs:sequence>
  </xs:complexType>

  <!-- 订单根元素 -->
  <xs:element name="purchaseOrder">
    <xs:complexType>
      <xs:sequence>
        <!-- 这里只能写头元素,但实例中可以用任意成员替换它 -->
        <xs:element ref="po:shipTo"/>     
        <xs:element name="items" type="po:ItemsType"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>

</xs:schema>

运行结果:下面三段 XML 全部通过验证!

<!-- 写法1:用原始元素 -->
<po:purchaseOrder>
  <po:shipTo>...</po:shipTo>
</po:purchaseOrder>

<!-- 写法2:用 customerNumber 完全代替地址 -->
<po:purchaseOrder>
  <po:customerNumber>88888</po:customerNumber>
</po:purchaseOrder>

<!-- 写法3:用带 vipLevel 的 vipCustomer 代替 -->
<po:purchaseOrder>
  <po:vipCustomer vipLevel="platinum">王五</po:vipCustomer>
</po:purchaseOrder>

常见使用场景(真实项目都在用)

场景头元素常见成员元素典型项目
XHTML<h><h1> ~ <h6>XHTML 1.0/1.1 官方就是这样
DocBook<para><simpara>, <formalpara>技术文档系统
Spring Beans<bean><util:list>, <aop:aspect>Spring 2.x–5.x
Maven pom.xml<plugin><build>, <reporting> 下的插件Maven 本身
SOAP 消息头<soap:Header>各种 ws-security、ws-addressing 头Web Service 标准
支付系统<payment><alipay>, <wechatpay>, <paypal>统一支付网关

重要规则和坑(90%的人在这里翻车)

规则例子说明
头元素必须是全局元素(直接位于 <xs:schema> 下)正确:<xs:element name="shipTo" ...> 错误:局部元素
成员元素必须显式写 substitutionGroup="xxx"不能靠类型相同自动替换,必须写这行
成员类型必须是头元素类型的派生(可以是同一个类型)允许 <xs:restriction><xs:extension>,也允许相同类型
在内容模型中只能写头元素(ref=头元素)不能直接写成员元素,否则失去多态效果
可以多级替换(成员又可以是另一个替换组的头)<a><b><c> 形成替换链
可以用 block="substitution" 禁止某个元素被替换(很少用)<xs:element name="shipTo" block="substitution"/>
可以用 abstract="true" 让头元素不能直接出现(推荐!)<xs:element name="payment" abstract="true" type="PaymentType"/>

推荐写法(最安全、最清晰):

<!-- 头元素设为 abstract,强制必须用子元素 -->
<xs:element name="payment" type="po:PaymentType" abstract="true"/>

<!-- 成员元素 -->
<xs:element name="alipay"   substitutionGroup="po:payment" type="po:AlipayType"/>
<xs:element name="wechat"   substitutionGroup="po:payment" type="po:WechatType"/>
<xs:element name="creditCard" substitutionGroup="po:payment" type="po:CardType"/>

这样 <payment> 就永远不会出现在实例文档中,强迫开发者使用具体支付方式。

一句话总结:

元素替换 = 全局头元素 + substitutionGroup属性 + 类型派生
写 XSD 时只要看到“同一个位置可以出现多种不同元素”的需求,99% 就是用 Substitution Group!

需要我帮你把某个具体业务(比如支付、通知、报表)改造成元素替换写法吗?把你的 XML 示例贴出来,10 秒给你最标准的 XSD。

文章已创建 2838

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部