10 年来,XML 发展成为了一种常见的、受到广泛接受的标准,用来在组织内部和组织之间存储和交换数据。XML 本身仅仅是一种抽象,其成功完全依赖于一个或多个组织所设计的 XML 格式。与任何软件产品一样,随着业务需求的变化,这些 XML 格式也面临着维护问题。而且这些变化不是一般意义上的:迫于竞争和市场的原因,XML 格式经常需要针对多个组织同时进行更新。
仅仅维护一个 XML 模式还比较容易。但是如果修改影响到数百组织,问题就大了。对 XML 模式进行一次简单的修改可能要花费大量的时间和金钱,但是如果事先设计好,可能只需要再看一遍就行了。本文讨论两个问题:
- 如何控制这种影响
- 如何尽可能地减小这种影响
我们使用一个非常简单的例子,涉及到汽车、轮胎、挡风玻璃和有关的公司或者分销商。虽然不完全符合实际,但足以说明提高 XML 格式可维护性的必要性了。
首先,我们以 Volvo C30 和米其林轮胎为例建立一个 XML 文件来共享关于轮胎的信息。如 清单 1 所示。
清单 1. 共享轮胎信息的简单 XML 文件
<car>
<brand>Volvo</brand>
<type>C30</type>
<kind>Small family car</kind>
<tires>
<tire>
<brand>Michelin</brand>
<type>Winter</type>
<count>4</count>
</tire>
<tire>
<brand>Michelin</brand>
<type>Spare</type>
<count>1</count>
</tire>
</tires>
<windscreen count="1">
<brand>Car glass</brand>
</windscreen>
</car>
|
这个 XML 文件看起来很简单,是不是?初看起来可能没有什么问题。但是再深入地想想。真正的问题在于 XML 模式。它非常大,而且是一个整体。现在考虑一下这种只有少量元素类型的 XML 格式。设想一下如果用于一个真实的例子会变成什么样,如 清单 2 所示。
清单 2. 描述简单 XML 格式的 XML 模式
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:element name="car">
<xs:complexType>
<xs:complexContent>
<xs:extension base="brand">
<xs:sequence>
<xs:element ref="type"/>
<xs:element ref="kind"/>
<xs:element ref="tires"/>
<xs:element ref="windscreen"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
<xs:element name="kind" type="xs:string"/>
<xs:element name="tires">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" ref="tire"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="tire">
<xs:complexType>
<xs:complexContent>
<xs:extension base="brand">
<xs:sequence>
<xs:element ref="type"/>
<xs:element ref="count"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
<xs:element name="count" type="xs:integer"/>
<xs:element name="windscreen">
<xs:complexType>
<xs:complexContent>
<xs:extension base="brand">
<xs:attribute name="count" use="required" type="xs:integer"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
<xs:complexType name="brand">
<xs:sequence>
<xs:element ref="brand"/>
</xs:sequence>
</xs:complexType>
<xs:element name="brand" type="xs:string"/>
<xs:element name="type" type="xs:NCName"/>
</xs:schema>
|
如果您要说 “这有什么?反正没有人去看 XSD 文件”,先想想业务过程中可能会发生的变化。如果需要修改轮胎 XML 以反映其尺寸怎么办,比如:
[...] <tire> <brand>Michelin</brand> <type>Winter</type> <count>4</count> <size>20"</size> </tire> [...] |
这样一个小小的变化就意味着只要修改轮胎格式,生产挡风玻璃的公司就将得到一份新的 XSD。而且还需要修改自己的软件以便理解这种 XSD。这可不是好消息。因为给挡风玻璃公司增加了额外的工作和成本。轮胎公司也会收到一份新的 XSD,根据使用的软件也可能需要进行修改。可能没人阅读 XSD,但确实让很多人非常恼火。
为了避免此类恐怖的 XSD 文件,办法是为轮胎和挡风玻璃提供自己的名称空间并放到单独的 XSD 文件中。清单 3 显示了这样做的结果。
清单 3. 包含名称空间的 XML 文件
[...]
<tr:tires>
<tr:tire count="4">
<tr:brand>Michelin</tr:brand>
<tr:type>Winter</tr:type>
</tr:tire>
<tr:tire count="1">
<tr:brand>Michelin</tr:brand>
<tr:type>Spare</tr:type>
</tr:tire>
</tr:tires>
<wnd:windscreen count="1">
<wnd:brand>Car glass</wnd:brand>
</wnd:windscreen>
[...]
|
如果这样修改而不改变汽车 XML 的其他部分,XSD 文件就会变得更简短,更容易处理。大部分相关信息都转移到了在 XSD 之上导入的其他 XSD 中。现在的 XSD 文件如 清单 4 所示。
清单 4. 导入单独的轮胎和挡风玻璃 XSD 的汽车 XML 模式
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified"
targetNamespace="http://car.org/car"
xmlns:tr="http://car.org/tire"
xmlns:wnd="http://car.org/windscreen"
xmlns:car="http://car.org/car">
<xs:import namespace="http://car.org/tire" schemaLocation="tr.xsd"/>
<xs:import namespace="http://car.org/windscreen" schemaLocation="wnd.xsd"/>
<xs:element name="car">
<xs:complexType>
<xs:sequence>
<xs:element ref="car:brand"/>
<xs:element ref="car:type"/>
<xs:element ref="car:kind"/>
<xs:element ref="tr:tires"/>
<xs:element ref="wnd:windscreen"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="brand" type="xs:NCName"/>
<xs:element name="type" type="xs:NCName"/>
<xs:element name="kind" type="xs:string"/>
</xs:schema>
|
它变成了 XSD 的一个模块。XSD 文件实际上没有压缩多少,但重要的是 XSD 更具模块化,更容易处理。这样就可以修改轮胎模式的某些部分然后分发给所有的轮胎公司,而不用打扰与此无关的挡风玻璃公司。
模块化不仅仅有助于控制分布式的可维护问题。还可以提高单个元素的可重用性。比方说,假设轮胎公司同时生产自行车和汽车轮胎。轮胎公司可能希望使用相同的 XSD 文件描述自行车轮胎。但是购买这些轮胎的自行车公司不需要描述汽车的 XML 模式,因为大多数元素和汽车公司没有关系。比如自行车一般不装挡风玻璃。自行车公司希望他们的自行车 XSD 仅仅导入轮胎 XSD。
这种情况下,XML 可能如 清单 5 所示。
清单 5. 重用轮胎 XML 格式描述自行车的 XML 示例文件
<bicycle>
[...]
<tr:tire count="2">
<tr:brand>Gazelle</tr:brand>
<tr:type>Race</tr:type>
<tr:size>25"</tr:size>
</tr:tire>
[...]
</bicycle>
|
和汽车 XSD 非常相似,导入了轮胎,但这是针对自行车的。下一个例子我们再回到汽车。
最终,汽车 XSD 不仅要导入挡风玻璃和轮胎的 XSD,可能还有发动机 XSD、方向盘 XSD、座椅 XSD、车漆 XSD 等等。这将变得难以管理。对这个问题可以用一个单独的 XSD parts.xsd 来导入汽车的所有零部件。这样如果需要修改导入列表,只有 parts.xsd 文件专门用于管理这些依赖性的变化。清单 6 显示了完整的例子。
清单 6. 使用单独的文件包含导入列表的汽车 XML 模式
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified"
targetNamespace="http://car.org/car"
xmlns:tr="http://car.org/tire"
xmlns:wnd="http://car.org/windscreen"
xmlns:car="http://car.org/car">
<xs:include schemaLocation="parts.xsd"/>
<xs:element name="car">
<xs:complexType>
<xs:sequence>
<xs:element ref="car:brand"/>
<xs:element ref="car:type"/>
<xs:element ref="car:kind"/>
<xs:element ref="tr:tires"/>
<xs:element ref="wnd:windscreen"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="brand" type="xs:NCName"/>
<xs:element name="type" type="xs:NCName"/>
<xs:element name="kind" type="xs:string"/>
</xs:schema>
|
parts.xsd 文件如 清单 7 所示。
清单 7. parts.xsd:要包含的导入列表
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified"
targetNamespace="http://car.org/car"
xmlns:tr="http://car.org/tire"
xmlns:wnd="http://car.org/windscreen"
xmlns:car="http://car.org/car">
<xs:import namespace="http://car.org/tire" schemaLocation="tr.xsd"/>
<xs:import namespace="http://car.org/windscreen" schemaLocation="wnd.xsd"/>
</xs:schema>
|
使用 parts.xsd 明显的好处是 XSD 导入列表不需要复制到需要引用这些零部件的每个 XSD 文件。只需要包含一次就可以了。
随着实际应用中元素和零部件数量的增长,必须小心地注意类型定义。比方说,XML 格式中很常见 — 但存在风险 — 的一种做法是使用非限定的属性 — 即没有专门属于某个名称空间的属性,如 清单 8 所示。
清单 8. 带有非限定属性 count 的 XML
<tr:tires>
<tr:tire count="4">
<tr:brand>Michelin</tr:brand>
<tr:type>Winter</tr:type>
</tr:tire>
<tr:tire count="1">
<tr:brand>Michelin</tr:brand>
<tr:type>Spare</tr:type>
</tr:tire>
</tr:tires>
<wnd:windscreen count="1">
<wnd:brand>Car glass</wnd:brand>
</wnd:windscreen>
|
如果根据非限定属性选择元素,就很难预测结果。比方说,如果用下面的 XPath 查询选择属性 count 的值为 1 的所有元素,就无法确定结果属于哪个名称空间:
//[@count = 1] |
如果可以得到结果,那么当然可以 推断其名称空间,因为这个例子毕竟非常简单。但问题是这种情况可能很快失控。解决的办法是用特定名称空间限定每个属性。看一看 清单 9 所示例子中的微妙差别。
清单 9. 限定 count 属性的 XML
[...]
<tr:tires>
<tr:tire tr:count="4">
<tr:brand>Michelin</tr:brand>
<tr:type>Winter</tr:type>
</tr:tire>
<tr:tire tr:count="1">
<tr:brand>Michelin</tr:brand>
<tr:type>Spare</tr:type>
</tr:tire>
</tr:tires>
<wnd:windscreen wnd:count="1">
<wnd:brand>Car glass</wnd:brand>
</wnd:windscreen>
[...]
|
幸运的是,这也意味着 XML 模式中的微妙区别,如 清单 10 所示。
清单 10. 限定属性 count 的轮胎 XML 模式
[...]
<xs:element name="tire">
<xs:complexType>
<xs:sequence>
<xs:element ref="tr:brand"/>
<xs:element ref="tr:type"/>
</xs:sequence>
<xs:attribute name="count" use="required" form="qualified" type="xs:integer"/>
</xs:complexType>
</xs:element>
[...]
|
效果可能不明显,但值得注意。最好在能够做到的时候避免可能出现的冲突。
采用关系数据库作为存储后端的时候,往往会建立数据库表或字段到 XML 结构一对一的转换。这样做不仅限制了建模的自由度,而且会把数据库的局限传递给其他有关方面,即便它们不使用该数据库。清单 1 中的 XML 就是这种关系数据库中一对一转换的例子。
但是有可能需要文档的模型稍微不同。比方说,可能需要汽车从前到后的布局以便自动绘制出所有零部件。可以用 清单 11 所示的例子来说明,然后使用 XSLT 转换为可缩放向量图型(SVG)。尽管乍看起来可能很难,实际上并非如此。创建的 XML 文件如下:
清单 11. 使用不同结构的 XML
<car>
<brand>Volvo</brand>
<type>C30</type>
<kind>Small family car</kind>
<tr:tire tr:count="2">
<tr:brand>Michelin</tr:brand>
<tr:type>Winter</tr:type>
</tr:tire>
<wnd:windscreen wnd:count="1">
<wnd:brand>Car glass</wnd:brand>
</wnd:windscreen>
<tr:tire tr:count="2">
<tr:brand>Michelin</tr:brand>
<tr:type>Winter</tr:type>
</tr:tire>
<tr:tire tr:count="1">
<tr:brand>Michelin</tr:brand>
<tr:type>Spare</tr:type>
</tr:tire>
</car>
|
汽车 XSD 现在变成了 清单 12(特别要注意 car 元素中的 complexType)。
清单 12. 描述不同结构的汽车 XML 模式
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified"
targetNamespace="http://car.org/car"
xmlns:tr="http://car.org/tire"
xmlns:wnd="http://car.org/windscreen"
xmlns:car="http://car.org/car">
<xs:import namespace="http://car.org/tire" schemaLocation="tr.xsd"/>
<xs:import namespace="http://car.org/windscreen" schemaLocation="wnd.xsd"/>
<xs:element name="car">
<xs:complexType>
<xs:sequence>
<xs:element ref="car:brand"/>
<xs:element ref="car:type"/>
<xs:element ref="car:kind"/>
<xs:choice maxOccurs="unbounded">
<xs:element ref="tr:tire"/>
<xs:element ref="wnd:windscreen"/>
</xs:choice>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="brand" type="xs:NCName"/>
<xs:element name="type" type="xs:NCName"/>
<xs:element name="kind" type="xs:string"/>
</xs:schema>
|
将该文件存储在关系数据库中并在以后再检索相同的 XML 文档稍微复杂一些。对此而言 NXD 非常方便。Exist DB 是支持这类 NXD 的一种简单的开放源代码数据库。IBM DB2 Express-C 是另一种免费数据库,提供了关系数据库和 XML 数据库的集成解决方案。它支持用 SQL 或 XQuery 之类纯 XML 技术访问数据库。
很多情况下,多家公司同时使用同一 XML 模式的不同版本很正常。只要知道使用的是哪一个版本,该版本中哪些元素有变化,就可以了。学习利用 XSD 的标注元素描述版本和元素信息是一种好的习惯。标注可以包含两个元素:documentation
和 appinfo。
documentation 元素恰如其名 — 使用的总是文档,很少会忘掉!更有意思的是 appinfo 元素,因为它可以包含需要的任何内容。比方说,可以定义自定义元素 version
包含特定元素的版本,则 XSD 如 清单 13 所示。
清单 13. 包含自定义标注的 XML 模式
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified"
targetNamespace="http://car.org/tire"
xmlns:tr="http://car.org/tire"
xmlns:custom="http://car.org/custom">
<xs:element name="tires">
<xs:annotation>
<xs:appinfo>
<custom:version>0.91</custom:version>
</xs:appinfo>
<xs:documentation>
Describes a set of tires.
</xs:documentation>
</xs:annotation>
<xs:complexType>
|
虽然 XSD 解析器不知道如何处理自定义的版本,但是它们和其他类似的自定义属性可以帮助在更大的组织中管理 XSD。毕竟,XML 文件和 XML 模式是要同时供人类和计算机读取的,最好也从这个角度对待 XSD。
值得注意的最后一个 XML 模式特性是 extension
元素。上一节 中,我们看到了 XSD appinfo
元素的可扩展性。默认情况下,该元素可以容纳任何内容。增加 extension
元素是一种更加严格的扩展类型的方式。清单 14 中的 XSD 文件说明了如何扩展 basicTire 类型以包含 size
元素。
清单 14. 增加 size 元素的轮胎 XML 模式
<xs:element name="tire" type="tr:sizedTire"/>
<xs:complexType name="basicTire">
<xs:sequence>
<xs:element ref="tr:brand"/>
<xs:element ref="tr:type"/>
</xs:sequence>
<xs:attribute name="count" use="required" form="qualified" type="xs:integer"/>
</xs:complexType>
<xs:complexType name="sizedTire">
<xs:complexContent>
<xs:extension base="tr:basicTire">
<xs:sequence>
<xs:element ref="tr:size"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
|
如果需要,XML 模式甚至还允许明确禁止扩展
basicType。如果希望进一步了解 XSD 规范,请参阅 参考资料。可以看到本文没有机会描述的其他很多 XML 模式的有趣特性。
如您所见,如果希望在大型企业及外围企业中保持 XML 格式的可维护性,就不能将 XML 模式看作一种自动生成的技术文档。有很多办法可以提高 XML 模式的可维护性。甚至还可用其他语言描述 XML 格式,比如 Schematron 和 RELAX NG。无论采用何种形式,提前设计好 XML 格式才能满足通信各方的需求。
学习
- 您可以参考本文在 developerWorks 全球站点上的 英文原文。
-
使用 XML Schema 定义元素的基本知识(Ashvin Radiya,Vibha Dixit,developerWorks,2000 年 8 月):进一步了解 XML 模式。
-
技巧: 使用 XML Schema Standard Type Library 简化开发(Nicholas Chase,developerWorks,2007 年 7 月):学习使用 XML 模式 Standard Type Library 的技巧。
-
XML 标准索引:看一看 developerWorks 列出的最主要的 XML 标准。
-
IBM XML 认证:了解如何才能成为一名 IBM 认证的 XML 及相关技术的开发人员。
-
XML 技术库:developerWorks XML 库提供了大量的技术文章和技巧、教程、标准以及 IBM 红皮书。
- developerWorks Web 体系结构专区:阅读这些关于 Web 技术的文章和教程,提升 Web 开发技巧。
-
developerWorks 技术活动和网络广播:随时关注最新的技术进展。
- 技术书店:浏览关于本文所述主题和其他技术主题的书籍。
-
developerWorks podcasts:聆听面向软件开发人员的有趣访问和讨论。
-
developerWorks podcasts:聆听针对软件开发人员的有趣访谈和讨论。
获得产品和技术
-
Exist DB:下载和尝试这种开放源代码的数据库管理系统。Exist DB 完全建立在 XML 技术上,按照 XML 数据模型存储 XML 数据,特点是高效、基于索引的 XQuery 处理。
-
DB2 Express-C 9.5:尝试这种兼具灵活性、可靠性且功能强大的关系和 XML 数据服务器。
-
用于产品评估的 IBM 试用软件:使用这些试用软件开发您的下一个项目,可直接从 developerWorks 下载,包括来自 DB2®、Lotus®、Rational®、Tivoli® 和 WebSphere® 的应用程序开发工具和中间件产品。
讨论
-
XML 专区讨论论坛:参与和 XML 有关的讨论。
-
developerWorks XML 专区:分享您的观点,阅读本文后请把的您的建议和想法发表在该论坛上。XML 专区的编辑主持这个论坛,欢迎您的参与。
-
developerWorks 博客:阅读这些博客,加入 developerWorks 社区。
