UML软件工程组织

 

 

准备 XML 及相关技术认证,第 2 部分: 信息建模
 
作者:Mark Lorenz  来源:IBM
 

XML 数据和文档

这一节将介绍两种基本的 XML 文档类型,即叙述性文档和类记录的文档。您将了解这两类文档的特点并看到应用的例子。要记住,很难对叙述性格式和类似记录的格式下严格的定义,因为所有文档归根到底都是数据。

叙述性 XML 文档

叙述性文档 是指定单词、符号和格式化信息供人类消费的一种格式。与类数据记录的文档相比,叙述风格的文档一般更松散,不那么拘泥于固定的格式。多数叙述性文档的文法对文档结构的个数没有限制,而一些类记录的文法只允许一种结构。

叙述性文档的例子

叙述风格的 XML 文法可以定义主要用于打印的文档。可扩展 HTML(XHTML)是一种用于 Web 的 XML 文法,DocBook 是一种用于技术出版物的标记,这些都是叙述性标记文法的例子。清单 1 显示了一个简单的 DocBook 文档。

清单 1. DocBook 叙述性文档

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
<?altova_sps http://www.altova.com/sps/Template/Publishing/docbook.sps?>
<article>
<title>Article Title</title>
<sect1>
<title>Section1 Title</title>
<para>Text</para>
</sect1>
</article>

除了呈现打印的文字外,叙述性文档还有很多用处。比如,Speech Synthesis Markup Language (SSML) 定义了作为合成语音呈现的文档。VoiceXML (VXML) 是 W3C 定义的一种面向语音的 XML 文法,用于人机双向语音交互。清单 2 给出了一个小例子。

清单 2. VoiceXML 叙述性文档

<?xml version="1.0" encoding="UTF-8"?>
<vxml xmlns="http://www.w3.org/2001/vxml"
xmlns:xsi="http://www.w3.org/2001/Xhref="XMLSchema-instance"
xsi:schemaLocation="http://www.w3.org/2001/vxml
http://www.w3.org/TR/voicexml20/vxml.xsd" version="2.0">
<form>
<block>Hello from VXML!</block>
</form>
</vxml>

类记录的数据文档

XML 项目的奠基者可能对 XML 在类记录数据方面的广泛应用既惊讶又欣慰。作为复杂的标准通用标记语言(SGML)的替代品,他们曾经设想 XML 作为一种元语言用于为叙述风格的文档创建文法。但是,XML 简单而严格的特性使其非常适合为属性化元素建立层次结构的文法,有时候比表格形式的关系模型更好。

类记录数据 XML 文法通常比叙述性 XML 文法更严格(叙述是供人类消费的),数据更符合计算机消费。有大量的面向工业的标准叙述性文法。若要让匿名的用户阅读文档,标准化的、众所周知的文法将非常重要。另一方面,有很多类记录的文法是专用的,常常仅限于某个应用程序。

类记录的文档例子

如果使用过 Web 应用程序,可能遇到过 Web 应用程序部署描述文件和 JavaServer Pages? (JSP) 标记库描述文件。这些都是 XML 用于定义数据记录的很好的例子。Web 应用程序服务器需要关于所部署工件的语义和位置的结构化描述。基于 XML 的 web.xml 文档的层次化特性很好地满足了需要。清单 3 显示了一个简单的 Spring Framework Web 应用程序的部署描述文件。

清单 3. 描述 Web servlet 可部署单元的 DTD

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<servlet>
<servlet-name>example</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>example</servlet-name>
<url-pattern>/example/*</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>

清单 4 中的 JSP 标记库描述文件用于把 taglib 名称和实现自定义 JSP 标记逻辑的 Java 类联系起来。

清单 4. JSP 标记库描述文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
"http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
<taglib>
<tlib-version>1.2</tlib-version>
<jsp-version>1.1</jsp-version>
<short-name>bc</short-name>
<tag>
<name>BreadCrumb</name>
<tag-class>com.rogers60.taglib.BreadCrumbTag</tag-class>
</tag>
</taglib>

结构良好的 XML

这一节通过比较两个相似的 HTML 和 XHTML 的例子,强调两者的区别,说明结构良好的 XML 文件有什么要求。

HTML

HTML 是一种基于标准通用标记语言(SGML)的相当松散的标记语言。在标签缺少或者出现错误的情况下浏览器都会尽量呈现。结果常常因为厂商或者版本的不同而不同。HTML 不符合结构良好的 XML 的简单规则。清单 5 显示了不严格的 HTML 标记的例子。文档没有关联的 DTD。paragraph 标记没有封闭。title 标记用小写,而其他的标记却是大写的。图 1 显示了该文档在 Firefox 浏览器中的显示结果,虽然标记很随便但是仍然能正确显示。

清单 5. 不严格的标记

<HEAD>
<title>XML Tutorial</title>
</HEAD>
<BODY>
<H1>This is a heading</h1>
<P>This is a paragraph.
<P align=center>This is centered</P>
<P><B>This is bold</P></B>
</BODY>

图 1. 浏览器能够正确呈现不严格的 HTML
 

注意,HTML 不是 XML。XML 文档必须是结构良好的。如果 XML 解析器遇到解析错误,则这个文档就根本算不上 XML 文档。除非修正错误,否则这个文档就毫无用处。

XHTML

XHTML 文档是 HTML 的一种变体,符合结构良好的 XML 标记的严格要求。表 1 通过分析 XHTML 说明什么形式的标记是不允许的。

表 1. 不合法的 XHTML 标记

XHTML 非法标记的例子
<P>Uppercase elements</P>
<p>Missing end tags<br>
<body>Missing html root</body>
<p id=id001>Missing quotes on attributes
<p><b>Overlapping extents</p></b>
<input type="checkbox" checked/>

XHTML 文档必须使用 UTF-8 或 UTF-16 编码,或者在前面用 XML 声明明确规定编码格式。必须用 PUBLIC ID DOCTYPE 指定 XHTML DTD。表 2 显示了描述 XHTML 文法变种的五种公共 DTD。

表 2. XHMTL DOCTYPE

文法 DTD DOCTYPE
XHTML 1.0 Strict <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
XHTML 1.0 Transitional <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
XHTML 1.0 Frameset <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
XHTML 1.1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
XHTML 2.0 (2005) <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 2.0//EN" "http://www.w3.org/MarkUp/DTD/xhtml2.dtd">

练习

在这个练习中,需要从 清单 5 中的 HTML 数据创建 XHTML 版本,编辑后得到 图 1 所示的结果。首先打开 XML 编辑器创建一个新文档,选择 XHTML 类型。XHTML 应该如 图 2 所示,如果使用的不是 XMLSpy,看到的结果可能有所不同。文档类型选择 XHTML 1.0 strict。

 图 2. XHTML 样板文件
 

然后编辑样板文件,加入经过整理后的 清单 5 中比较随便的 HTML。如果是结构良好的、有效的 XHTML,就可以通过验证测试,如 图 3 所示。

图 3. 修改后的 XHTML 1.0 标记

注意,必须使用级联样式表(CSS)文本对齐样式来居中显示中间的段落,因为 DTD 不允许 HTML 段落的 center 属性。如果段落使用 center 属性,文档就无法通过有效性测试。

由于浏览器的松散特性,再加上 HTML 继承自复杂的 SGML,相对而言 HTML 更难进行验证。只有专门的复杂工具才能进行 HTML 验证,而任何标准 XML 工具都能验证 XHTML 文档。

为了检查呈现结果,可以保存该 XHTML 文件然后用浏览器打开,或者单击 XMLSpy 中的 Browser 选项卡。图 4 显示了输出结果。注意,这里的结果与 图 1 完全相同。

 图 4. Firefox Web 浏览器中呈现的 XHTML

与松散的 HTML 不同,如果 XHTML 文件无效,浏览器就会拒绝呈现它。单个文法错误将导致整个文档无效。

既然这么麻烦何必使用 XHTML?

浏览器可以通过猜测来呈现 HTML 文件中随意的标记。对于这些猜测没有标准可言,因此 HTML 可能在一个浏览器中呈现不需要的可视化工件,而在另一个浏览器中却没有呈现。

有效的文件是 XML,因此所有解析器都必须解析它并提供合理的可视化呈现。此外要注意,您使用了 XML 工具来创建和编辑文件。XHTML 的真正价值在于可以使用任何 XML 工具或者库来处理 XHTML。

XML 中的数据建模

假设需要用 XML 建模一些出版的书籍。目的是让编写的任何应用程序可以此作为输入,来生成报告、搜索参考资料或者作为图书销售的电子商务编目。

一些权威人士建议在编写示例文档之前先建立 DTD 或者模式。我发现这种方法太抽象了。事实上,我更喜欢借用程序设计中的测试驱动方法:先创建文档,然后建立 DTD 来验证该文档。文档就是测试,DTD 就是应用程序。应该在开发的过程中通过修改两个文档来不断迭代。后面将说明如何放弃 DTD 而创建 XML Schema 来测试文档,因为本教程要介绍如何使用这两种方法来建模和约束 XML 中的数据。

这时候仍然要用到这个测试文档。我们的任务是建模一个已出版图书的编目,但是将来可能需要加入其他出版物。可以把这个编目想像成一个出版物列表。每个 XML 文档都是单根的层次结构,因此可以使用 “publications” 这个词作为根元素。如 清单 6 所示。

清单 6. 开始


<?xml version="1.0" encoding="UTF-8"?>
<publications>
. . .
</publications>

publications 编目包含零本或多本图书。把图书作为 publications 的孩子元素看起来似乎合情合理,如 清单 7 所示。

清单 7. 添加孩子元素

<?xml version="1.0" encoding="UTF-8"?>
<publications>
<book></book>
<book></book>
<book></book>
. . .
</publications>

每本书都有一些共同的属性:标题、作者、版权和 ISBN 编号。这些都是图书的性质,但是要将其建模为真正的 XML 属性还是 XML 元素呢?回想一下属性和元素在功能的上的差异,如 表 3 所示。

表 3. 属性和元素的比较

能力
属性
元素
层次性 无 —— 扁平的
有序 无 —— 未定义
复杂类型 无 —— 只能是字符串
冗长 通常较差 更罗嗦
可读性 较差 一般更容易阅读

标题、作者、版权和 ISBN 似乎都可以作为图书的直接孩子。它们是否还需要自己的孩子呢?现在还不能确定,但是如果没有充分的理由,就不应该禁止这种扩展。从这点上来说应该将其建模为元素。

在图书中这些项可能不需要规定顺序,只要应用程序能够按照名称解析即可,但是有顺序的话看起来会更整齐一些。可以就是否排序分别进行讨论,但是这里不给任何一种情况加分。

有些项似乎是简单的字符串,但版权实际上是一个四位数,这一点可以在将来的出版物编目版本中加以约束。此外,将来可能还要设定 ISBN 的格式化样式。因此这里应该给建模为元素加上一分。

如果 XML 绑定技术,如 Java? Architecture for XML Binding (JAXB),是体系结构中的一部分,还要考虑将元素转化成类,这些性质就变成了类属性。因此,类的个数和元素类型的个数成正比。这可能意味着源代码更长更复杂。不过这些代码是由绑定工具生成的。源文档实际上就是模式。维护人员通常不需要手工修改这些类。因此,XML 绑定可能不会对元素还是属性的决策产生影响。对于 JAXN 或者一般的 XML 绑定来说双方都不加分。

选择元素还是属性目前的比分是二比零 —— 仅对这个问题而言。冗长性和可读性这两个特征可能与个人的喜好有关,也可能是设计需求的一部分。您必须自己评估具体的设计任务,这有时候是个人喜好的问题。

有些模式允许在某个位置使用属性或元素均可。Apache ANT 和 DocBook 文档都允许这种行为。

本教程中对标题、作者、版权和 ISBN 编号都使用元素,ISBN 用小写字母。可以保留为 book 元素增加可选或必需属性的选择,比如使用 image 嵌入可选的书影,使用 id 为图书增加惟一标识符作为应用程序引用的关键字。清单 8 显示了现在的测试文档,它还没有 DTD 或 XML 模式。这是您接下来的任务。本教程将说明如何建立 DTD 和模式。

清单 8. 测试文档 publications1.xml

<?xml version="1.0" encoding="UTF-8"?>
<publications>
<book>
<title>Building J2EE Applications With IBM WebSphere</title>
<author>Nilsson and Mauget</author>
<copyright>2003</copyright>
<publisher>Wiley</publisher>
<isbn>0471281573</isbn>
</book>
<book>
<title>Linux and Windows Interoperability Guide</title>
<author>Bradford and Mauget</author>
<copyright>2001</copyright>
<publisher>Pearson Education</publisher>
<isbn>0130324779</isbn>
</book>
<book>
<title>e - Directories</title>
<author>House,Hahn,Mauget,Daugherty</author>
<copyright>2000</copyright>
<publisher>Pearson Education</publisher>
<isbn>0471281573</isbn>
</book>
</publications>

可以通过用 DTD 的方式描述文档来为这个测试文档增加基于 DTD 的文法,DTD 是一种借自 SGML 的较老的标记语言。首先要明白为何要加上文法?

文法约束了 XML 文档的有效性,在某种程度上类似于关系数据库管理系统(DBMS)模式描述和约束数据库的形式。什么时候应用文法?DBMS 文法防止错误地修改结构或者禁止应用程序在修改表中数据的时候改变关系。DBMS 模式主要是关于写的,很少是关于读的。

XML 文法的侧重点不同。用户可以使用 Microsoft? Notepad 修改 XML 文档,这个应用程序忽略 XML 或者它的文法文档。XML 文法在读取并解析或重新组织 XML 文档的时候应用。因此,XML 文法是关于读取有效信息而不是写入有效信息的。结构良好的文档符合 XML 标记的要求,但根据相关的文法可能是无效的。在解析过程中这是一个赞成或反对的问题。这里缺乏有效性意味着文档毫无用处。没有通融的余地,就像浏览器对于随意的 HTML 那样。

DTD 和 Schemas

定义 DTD

DTD 主要由 <!ELEMENT ... > 和 <!ATTRIBUTE ... > 标记语句组成。

测试文档的第一个元素是 publications。它包括 book 元素,因此将 book 放在括号中表示包含关系。可以在 book 后面加上星号(*)表示 “零个或多个”,或者用加号(+)表示 “一个或多个”,问号(?)表示 “一个或没有”,不使用后缀表示只能有一个 book。这是正则表达式符号的一部分。因此 DTD 的第一行应该是:

<!ELEMENT publications (book*)>

每本书必须依次刚好具有一个 title、author、copyright 和 isbn 元素。(对于本教程而言,多位作者放在一个 author 元素中。创建 DTD 的时候,要记住考虑如何建立 DTD 以允许一个或多个元素。) 因此接下来的标记语句就是:

<!ELEMENT book (title, author, copyright, publisher, isbn)>

其他元素都是包含字符数据的叶子节点元素。跟通常一样使用括号表示包含关系。需要声明字符数据的类型。解析为字符数据的字符串用文字 #PCDATA 表示:

<!ELEMENT title (#PCDATA)>

通过特殊的 XML ID 属性类型可以赋予每本书一个必需的惟一标识符关键字。此外,还可使用可选的 image 属性包含图书封面图像的链接。ATTLIST 标记接受一个元素参数,后面跟着关联到该元素的每个属性的列表。每个元组都由属性名、类型和表明可选或必需的指示符组成。DTD 规范允许 表 4 列出的 10 种属性类型。

表 4. DTD 属性类型

DTD 属性类型
说明
CDATA XML 可接受的任何字符串
NMTOKEN 类似于 XML 名,第一个字符没有严格要求
NMTOKENS 空白分隔的一个或多个 NMTOKEN 记号
Enumeration 列出允许的所有属性值
ENTITY 将名字与类似宏的替代物串联系起来
ENTITIES 空白分隔的 ENTITY 名字列表
ID 在整个文档中惟一的 XML 名
IDREF 引用文档中的 ID 属性
IDREFS 空白分隔的 IDREF 记号列表
NOTATION 将名字与客户使用的信息联系起来

通过在类型之后使用 #REQUIRED 表明该属性是必需的。通过附加 #IMPLIED 可以规定可选属性。book 元素的文法中每种类型的属性都有一个。ATTLIST 语句如下所示:

<!ATTLIST book id ID #REQUIRED image CDATA #IMPLIED>

清单 9 显示了完整的 DTD。

清单 9. Publication DTD

<?xml version="1.0" encoding="UTF-8"?>
<!ELEMENT publications (book*)>
<!ELEMENT book (title, author, copyright, publisher, isbn)>
<!ELEMENT title (#PCDATA)>
<!ELEMENT author (#PCDATA)>
<!ELEMENT copyright (#PCDATA)>
<!ELEMENT publisher (#PCDATA)>
<!ELEMENT isbn (#PCDATA)>
<!ATTLIST book
id ID #REQUIRED
image CDATA #IMPLIED>

如何将 DTD 与它描述的文档联系在一起呢?可以直接在 XML 文档中嵌入 DTD。教科书上建议将两个文档分开。应用程序可明确使用 publications.dtd 文档验证 publications2.xml 文档。因此要将 DTD 隐含链接到 XML 文档:

<!DOCTYPE publications SYSTEM "publications.dtd">

清单 10 显示了链接到新 DTD 的 XML 文档。它假设 DTD 位于当前目录中。

 清单 10. 文档链接到独立的 DTD

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE publications SYSTEM "publications.dtd">
<publications>
<book id="_1001">
<title>Building J2EE Applications With IBM WebSphere</title>
<author>Nilsson and Mauget</author>
<copyright>2003</copyright>
<publisher>Wiley</publisher>
<isbn>0471281573</isbn>
</book>
<book id="_3042">
<title>Linux and Windows Interoperability Guide</title>
<author>Bradford and Mauget</author>
<copyright>2001</copyright>
<publisher>Pearson Education</publisher>
<isbn>0130324779</isbn>
</book>
<book id="_9593" image="edir.jpg">
<title>e - Directories</title>
<author>House,Hahn,Mauget,Daugherty</author>
<copyright>2000</copyright>
<publisher>Pearson Education</publisher>
<isbn>0471281573</isbn>
</book>
</publications>

有没有便于取得的工具可以执行有效性检查?可以使用可免费下载的 Altova XMLSpy Home Edition 来检查文档的有效性。将 publications.dtd 文件和 publications2.xml 文件放在同一个目录中,打开 XML 文档然后按 F8。图 5 显示验证成功的情况。

图 5. 使用链接的 DTD 验证
 

如果文档是无效的会怎么样呢?从第一个元素中删除必需的 id 属性,然后按 F7 检查文档的 XML 语法。状态显示为黄色。这意味着文档是结构良好的。现在按 F8。图 6 显示了结果。状态变成了红色,意味着不是有效的文档。在保存的时候 XMLSpy 将抱怨说文档是无效的。

 图 6. 无效文档 —— 缺少必需的 id 属性



 定义 XML Schema

W3C XML Schema 是一种本身其实是 XML 文档的文法文档。Schematron 和 Relax NG 是另外两种 XML 验证语言。这里只讨论 W3C XML Schema,另外的 XML 验证语言比较简单,也很流行,您可以自己去了解。

与使用 DTD 相比,设计 XML Schema 可以在更大程度上约束文档。比方说,XML Schema 文法可以规定有且只能有四个 apple 元素作为 basket 元素的直接子元素。可以在字符串类型的基础上定义复杂的类型。比如,可以要求 zipcode 元素的值必须具有 "\d\d\d\d\d-\d\d\d\d" 的刻面,所以 "95123-4823" 是有效的,而 "abcde-fghi" 或者 "27703" 都是无效的。

刻面(facet)这个词是什么意思呢?XML Schema 将刻面 看作是一种简单数据类型的可能值的某个方面。表 5 显示了 XML Schema 的刻面。

表 5. XML W3C Schema 刻面

刻面类型
说明
length 规定具体的长度
minLength 规定字符串派生类型的最小长度
maxLength 规定字符串派生类型的最大长度
maxExclusive 必须小于等于适合于该类型的最大值
maxInclusive 必须小于适合于该类型的最大值
minExclusive 必须大于适合于该类型的最小值
minInclusive 必须大于等于适合于该类型的最小值
enumeration 值必须是所定义列表中的一个成员
totalDigits 规定总的数字位数,不算正负号和小数点
fractionDigits 规定小数中的小数位数
whiteSpace 用于保留、替换或压缩文档空白

这里仅仅是告诉您能够在多大粒度上施加控制,但首先要制作一个能够实现 DTD 功能的 XML Schema。后面再讨论如何进一步细化,说明模式的优点。

首先要声明 XML Schema 使用的模式。暂时先不要在文法中使用名称空间。模式本身使用了一个名称空间。按照惯例一般使用前缀 “xs” 来表示。前缀可使用任何字符串,比如 “radish”,但是有什么理由违反惯例呢?

xmlns:xs="http://www.w3.org/2001/XMLSchema"

现在,声明您自己的已声明元素和属性是非限定的:

elementFormDefault="unqualified"
attributeFormDefault="unqualified"

下面是 XML Schema 根元素:

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="unqualified"
attributeFormDefault="unqualified">

接下来可以指定 publications,以及 book、title、author、copyright、publisher 和 isbn 元素声明了。 可以像下面这样指定根元素:

<xs:element name="publications">

publications 根是一个复杂类型,包含其他元素序列,即可选的 book 元素。该元素出现的最多次数没有限制:

<xs:element name="publications">
<xs:complexType>
<xs:sequence>
<xs:element name="book" maxOccurs="unbounded">

book 又是一个复杂类型,包含自己的序列:title、author、copyright、publisher 和 isbn 元素声明。

<xs:element name="publications">
<xs:complexType>
<xs:sequence>
<xs:element name="book" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="title"/>
<xs:element name="author"/>
<xs:element name="copyright"/>
<xs:element name="publisher"/>
<xs:element name="isbn"/>
</xs:sequence>
...

是不是忘记了为 book 元素增加 id 和 image 属性?没有,这些可以放在 book 元素所包围的复杂类型定义的最后完成。

XML Schema 的规则(文法)要求在所属元素包含的复杂类型定义的最后声明属性。属性是形如 <xs:attribute … /> 的模式元素。

因此可以这样添加 id 和 image 属性:

<xs:element name="publications">
<xs:complexType>
<xs:sequence>
<xs:element name="book" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="title"/>
<xs:element name="author"/>
<xs:element name="copyright"/>
<xs:element name="publisher"/>
<xs:element name="isbn"/>
</xs:sequence>
<xs:attribute name="id" type="xs:string" use="required"/>
<xs:attribute name="image" type="xs:string"/>
</xs:complexType>

id 属性是必需的,而 image 属性使用了默认值,即可选的。

注意类型 xs:string。也可以指定基于 string 的复杂类型。这对于 XML 来说仍然是字符串,但是对于 XML Schema 来说则是一种特殊类型的字符串。稍后将进一步讨论用户定义的复杂类型。

现在模式已经基本上完成了。为打开的元素加上结束标记,如 清单 11 所示。

清单 11. 与 DTD 功能匹配的模式

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xs:element name="publications">
<xs:complexType>
<xs:sequence>
<xs:element name="book" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="title"/>
<xs:element name="author"/>
<xs:element name="copyright"/>
<xs:element name="publisher"/>
<xs:element name="isbn"/>
</xs:sequence>
<xs:attribute name="id" type="xs:string" use="required"/>
<xs:attribute name="image" type="xs:string"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>

可以在 XMLSpy 中打开 publication3.xsd,然后按 F8 针对 http://www.w3.org/2001/XMLSchema 对它进行验证,就好像它是 XML 文档一样,因为它就是 XML 文档。

将 XML Schema 关联到文档

只有应用于 XML 文档时模式才有意义。如何将模式关联到文档呢?该应用程序可直接使用 publications3.xsd 验证 publications3.xml 文档。但是我们需要将模式和 XML 文档隐含地关联在一起。

可以修改文档的根元素通过专门的属性链接到模式。到目前为止模式还没有使用名称空间。必须为 publications 根元素增加一个属性,告诉解析器到哪里找到不带名称空间的模式:

<publications xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="publications3.xsd">

仅此而已!请参见 清单 12 中所示的 publications3.xml 文档。文档的内容与 清单 8 相同,不过增加了模式关联。

清单 12. 链接到 W3C XML Schema 的 publication3.xml

<?xml version="1.0" encoding="UTF-8"?>
<publications xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="publications3.xsd">
<book id="_1001">
<title>Building J2EE Applications With IBM WebSphere</title>
<author>Nilsson and Mauget</author>
<copyright>2003</copyright>
<publisher>Wiley</publisher>
<isbn>0471281573</isbn>
</book>
<book id="_3042">
<title>Linux and Windows Interoperability Guide</title>
<author>Bradford and Mauget</author>
<copyright>2001</copyright>
<publisher>Pearson Education</publisher>
<isbn>0130324779</isbn>
</book>
<book id="_9593" image="edir.jpg">
<title>e - Directories</title>
<author>House,Hahn,Mauget,Daugherty</author>
<copyright>2000</copyright>
<publisher>Pearson Education</publisher>
<isbn>0471281573</isbn>
</book>
</publications>

增加名称空间

为什么要在文档中增加名称空间呢?DTD 根本没有名称空间的概念。它忽略名称空间限定元素或属性中的冒号(:)。这意味着任何消歧都是使用冒号前面的前缀来完成的,而不是通过前缀所表示的 URI。两个文档使用相同的前缀关联到不同名称空间是合法的,但是可能会造成命名冲突的问题。

在文档中使用下面的名称空间:

http://rogers60.com/xmltutorial/2

前缀使用 pub,虽然可使用文档和模式中惟一的任何有效字符串。对于 DTD 而言,pub:book 和 pubbook 没有什么区别。

在 XML Schema 名称空间的支持下,概念上相当于 http://rogers60.com/xmltutorial/2book。

DTD 的行为不一定能防止名称空间冲突。另一方面,只要正确地声明,XML Schema 的行为则可以防止名称空间冲突,因为基于 DNS 的 URI 是惟一的。模式演化的下一小步是设置默认名称空间,前缀用 pub:

xmlns:pub="http://rogers60.com/xmltutorial/2"

需要同时用两个属性来指定一个名称空间和使用默认名称空间。一般的惯例是假定不带前缀的元素在默认名称空间中,但不带前缀的属性不属于任何名称空间:

elementFormDefault="qualified"
attributeFormDefault="unqualified"

记得在前面不使用名称空间的文法中声明了 elementFormDefault="unqualified"。对这个具体的模式而言,使用名称空间限定的文法不需要增加什么。参见 清单 13。

清单 13. 使用默认名称空间的模式

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:pub="http://rogers60.com/xmltutorial/2"
targetNamespace="http://rogers60.com/xmltutorial/2"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xs:element name="publications">
<xs:complexType>
<xs:sequence>
<xs:element name="book" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="title"/>
<xs:element name="author"/>
<xs:element name="copyright"/>
<xs:element name="publisher"/>
<xs:element name="isbn"/>
</xs:sequence>
<xs:attribute name="id" type="xs:string" use="required"/>
<xs:attribute name="image" type="xs:string" use="optional"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>

还需要对 XML 文档做一个小手术来将其连接到名称空间。手术很小,因为整个文档都在默认名称空间中。publications 根元素中不必再指定不带名称空间的模式的位置属性,而是改为:

xsi:schemaLocation="http://rogers60.com/xmltutorial/2 publicationsNS4.xsd"

这里要注意。特别是用引号括起来的、用空格分开的值对。第一部分是名称空间 URI,第二部分是模式文件的位置。一般来说应该是一个 URL。此外,还需要增加默认名称空间并为其指定 pub 前缀:

xmlns="http://rogers60.com/xmltutorial/2"
xmlns:pub="http://rogers60.com/xmltutorial/2"

这样,非限定的元素就默认属于 URI http://rogers60.com/xmltutorial/2,明确指定前缀 pub 的元素也属于该名称空间。比如,<book> 和 <pub:book> 声明了相同的元素。增加名称空间后的测试文档如 清单 14 所示。注意,作为一个例子,最后的 book 元素明确限定为 pub:book。该文档对于前述 清单 13 中的模式是有效的。

清单 14. 使用默认名称空间的 XML 文档

<?xml version="1.0" encoding="UTF-8"?>
<publications
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://rogers60.com/xmltutorial/2
publicationsNS4.xsd"
xmlns="http://rogers60.com/xmltutorial/2"
xmlns:pub="http://rogers60.com/xmltutorial/2">
<book id="_1001">
<title>Building J2EE Applications With IBM WebSphere</title>
<author>Nilsson and Mauget</author>
<copyright>2003</copyright>
<publisher>Wiley</publisher>
<isbn>0471281573</isbn>
</book>
<book id="_3042">
<title>Linux and Windows Interoperability Guide</title>
<author>Bradford and Mauget</author>
<copyright>2001</copyright>
<publisher>Pearson Education</publisher>
<isbn>0130324779</isbn>
</book>
<pub:book id="_9593" image="edir.jpg">
<title>e - Directories</title>
<author>House,Hahn,Mauget,Daugherty</author>
<copyright>2000</copyright>
<publisher>Pearson Education</publisher>
<isbn>0471281573</isbn>
</pub:book>
</publications>

内置的简单数据类型

W3C XML Schema 规范规定了一组简单的内置数据类型。表 6 列出了内置的 XML Schema 简单类型及其说明。本教程中的测试用例默认使用字符串类型,只有 book 元素的 image 和 id 属性明确规定了该类型。

表 6. 内置的 W3C Schema 简单数据类型

类型
说明
anyURI 统一资源标识符
base64Binary base64 编码的二进制值
boolean true .. false 或 0 ..1
byte 带符号量,小于等于 128,大于等于 -127
dateTime 绝对日期和时间
ID, IDREF, IDREFS, ENTITY, ENTITIES, NOTATION, NMTOKEN, NMTOKENS 与 表 4 中的定义相同
integer 带符号整数
language XML 1.0 推荐标准中的 “xml:lang” 值
name XML 名
string Unicode 字符串

复杂应用程序中的数据文档模式可能变得很大,难以维护,除非改造成某种规范化的形式。这里模式已经在使用它们的结构点上声明了元素和属性。文档结构以及用于构建该结构的元素和属性的声明混在了一起。这种方法对那些试图理解和维护它的人来说可能降低了模式的清晰度。并且减少了类型重用的可能性,需要修改的时候维护人员很难找到需要的类型。

为什么不把类型声明集中到一起,然后在模式中单独的数据结构部分引用这些类型呢?这样的话,您甚至可以将大型模式分解成不同的文件,分别作为类型部分和结构部分。

我们来试试。修改模式将声明部分从结构中分离出来,在文档的开始部分声明所有的元素,接下来声明属性,最后定义文档结构。结构部分使用 ref 属性和元素或属性名来引用声明的元素或属性。属性引用必须使用名称空间前缀,因为所有的属性都不知道名称空间。

这种规范化的模式布局有时候使大型文档更容易阅读,因为可以看到从不那么冗长的结构中分离出来的声明。此外,也有利于类型的重用。

清单 15 显示了采用这种规范化形式的一个小模式。它实际上比原来的模式长,但是由于声明和结构分开,有些东西只需要声明一次而不是多次,因此更便于维护。

清单 15. 规范化的 W3C Schema

<?xml version="1.0" encoding="UTF-8"?>6
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:pub="http://rogers60.com/xmltutorial/2"
targetNamespace="http://rogers60.com/xmltutorial/2"
elementFormDefault="qualified"
attributeFormDefault="qualified">
<!-- Simple elements -->
<xs:element name="title"/>
<xs:element name="author"/>
<xs:element name="copyright"/>
<xs:element name="publisher"/>
<xs:element name="isbn"/>
<!-- Attributes -->
<xs:attribute name="id" type="xs:string"/>
<xs:attribute name="image" type="xs:string"/>
<!-- Complex elements -->
<xs:element name="publications">
<xs:complexType>
<xs:sequence>
<xs:element name="book" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element ref="pub:title"/>
<xs:element ref="pub:author"/>
<xs:element ref="pub:copyright"/>
<xs:element ref="pub:publisher"/>
<xs:element ref="pub:isbn"/>
</xs:sequence>
<xs:attribute ref="pub:id" use="required"/>
<xs:attribute ref="pub:image" use="optional"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>

将测试文档链接到模式后,该模式仍然与以前一样是有效的,因此不再赘述。现在从 XMLSpy 中打开该模式,然后单击 Schema/WSDL 选项卡显示 图 7 所示的图示。要注意序列的连接器符号、名称空间限定的标签和一堆 pub:book 元素。

图 7. W3C XML Schema 图示

模式显示了一点细微的错误。您希望允许出现空白的 publications 列表,但是注意到这里至少要求有一个 pub:book。空白的 publications 列表是无效的。坚持测试边界条件是一种好习惯。可以通过向该元素增加 minOccurs 属性来解决这个问题:

<xs:element name="book" maxOccurs="unbounded" minOccurs="0">

这样就允许空白的 publications 列表了。

W3C XML Schema 包含内置的简单类型,但是它的吸引力部分原因是能够用更细粒度的用户定义简单类型来约束值。这里可以创建两种简单类型,分别用于 isbn 元素和 copyright 元素。基类型都是 xs:string,但分别应用不同的限制范型。首先来看看版权格式。要保证它必须是一个四位数字。虽然这种简单的限制有多种方法实现(比如指定长度的实数),但是这里采用形如 "dddd" 的范型,其中每个 "d" 表示一个数字:

<xs:simpleType name="year">
<xs:restriction base="xs:string">
<xs:pattern value="\d\d\d\d"/>
</xs:restriction>
</xs:simpleType>

类似地,将 ISBN 编号限制为 “d-dddd-dddd-d” 的形式,其中每个 “d” 也是一个数字。要知道,这并不是真正的权威格式。由于面临着数字耗尽的问题,ISBN 最近从 10 位数改成了 13 位数。但是对本教程来说,这种形式适合测试文档中的 ISBN 编号。

<xs:simpleType name="isbn">
<xs:restriction base="xs:string">
<xs:pattern value="\d-\d\d\d\d-\d\d\d\d-\d"/>
</xs:restriction>
</xs:simpleType>

将这两小节 XML 插入到规范化模式中元素和属性声明的前面。然后就可以在需要的地方引用这些新类型了,只要使用 ref 属性指向这些新类型即可:

<xs:attribute ref="pub:id" use="required"/>
<xs:attribute ref="pub:image" use="optional"/>

ref 的值中使用了名称空间前缀,因为属性引用默认是没有名称空间的,正如该模式中 attributeFormDefault="unqualified" 所规定的那样。清单 16 显示了使用简单类型的最新 XML Schema 版本。这里不再列出 XML 文档,因为除了模式名称以外没有变化。

清单 16. 声明自定义类型的模式

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:pub="http://rogers60.com/xmltutorial/2"
targetNamespace="http://rogers60.com/xmltutorial/2"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<!-- Simple types -->
<xs:simpleType name="isbn">
<xs:restriction base="xs:string">
<xs:pattern value="\d-\d\d\d\d-\d\d\d\d-\d"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="year">
<xs:restriction base="xs:string">
<xs:pattern value="\d\d\d\d"/>
</xs:restriction>
</xs:simpleType>
<!-- Simple elements -->
<xs:element name="title" type="xs:string"/>
<xs:element name="author" type="xs:string"/>
<xs:element name="copyright" type="pub:year"/>
<xs:element name="publisher" type="xs:string"/>
<xs:element name="isbn" type="pub:isbn"/>
<!-- Attributes -->
<xs:attribute name="id" type="xs:string"/>
<xs:attribute name="image" type="xs:string"/>
<!-- Complex elements -->
<xs:element name="publications">
<xs:complexType>
<xs:sequence>
<xs:element name="book" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element ref="pub:title"/>
<xs:element ref="pub:author"/>
<xs:element ref="pub:copyright"/>
<xs:element ref="pub:publisher"/>
<xs:element ref="pub:isbn"/>
</xs:sequence>
<xs:attribute ref="pub:id" use="required"/>
<xs:attribute ref="pub:image" use="optional"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>

使用 XMLSpy 测试时文档仍然是有效的。现在去掉第一个 isbn 元素中的第一个横线再验证。这样将导致验证失败。图 8 显示了结果。

图 8. 自定义类型验证失败
 

可以采用分而治之的办法使模式更容易理解和维护。借用编程技术将模式分解成位于独立文件中的声明。首先,创建一个模式仅包含前述重构模式中的简单类型、元素和属性声明。如 清单 17 所示。

清单 17. 单独模式文件中的声明

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:pub="http://rogers60.com/xmltutorial/2"
targetNamespace="http://rogers60.com/xmltutorial/2"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<!-- Simple types -->
<xs:simpleType name="isbn">
<xs:restriction base="xs:string">
<xs:pattern value="\d-\d\d\d\d-\d\d\d\d-\d"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="year">
<xs:restriction base="xs:string">
<xs:pattern value="\d\d\d\d"/>
</xs:restriction>
</xs:simpleType>
<!-- Simple elements -->
<xs:element name="title" type="xs:string"/>
<xs:element name="author" type="xs:string"/>
<xs:element name="copyright" type="pub:year"/>
<xs:element name="publisher" type="xs:string"/>
<xs:element name="isbn" type="pub:isbn"/>
<!-- Attributes -->
<xs:attribute name="id" type="xs:string"/>
<xs:attribute name="image" type="xs:string"/>
</xs:schema>

然后将这些内容从原始模式的副本中删除,替换为下列标记:

<xs:include schemaLocation="publicationsRedefine8.xsd"/>

最终得到的结构模式如 清单 18 所示。该 ref 属性引用要包含的模式。可以看到两个文件更容易读了。一个包含元素、属性和类型声明。另一个则将其组织到文档结构中。如果将测试 XML 文件中的链接改为指向 publications8.xsd,在 XMLSpy 中该文件仍然是有效的。

清单 18. 结构性 W3C Schema

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:pub="http://rogers60.com/xmltutorial/2"
targetNamespace="http://rogers60.com/xmltutorial/2"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xs:include schemaLocation="publicationsRedefine8.xsd"/>
<!-- Complex elements -->
<xs:element name="publications">
<xs:complexType>
<xs:sequence>
<xs:element name="book" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element ref="pub:title"/>
<xs:element ref="pub:author"/>
<xs:element ref="pub:copyright"/>
<xs:element ref="pub:publisher"/>
<xs:element ref="pub:isbn"/>
</xs:sequence>
<xs:attribute ref="pub:id" use="required"/>
<xs:attribute ref="pub:image" use="optional"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>

在 XML Schema 中,可以使用 xs:include、xs:redefine 或 xs:import 来引入外部文件,通过 xs:extension 或 xs:restriction 元素派生新的类型。这些内容足以单独用一篇教程来讨论。请参阅 参考资料 中列出的 “W3C XML Schema Part 0: Primer Second Edition”。

决定使用 DTD 还是 W3C XML Schema

本教程介绍了 DTD 和 W3C XML Schema。现在对两者加以比较。

表 7 列出了 DTD 的基本验证特性。对于控制元素值和属性值的格式和类型只有很少的粒度可供选择。这对于叙述风格的文档一般已经足够了。事实上,对于各种面向工业的叙述性的交换文档存在数量惊人的标准 DTD。

表 7. DTD 验证

DTD 有效性约束
元素嵌套
元素频次
允许的元素数据
属性类型和默认值

类数据记录的文档构成了 XML 应用的另一个主要分支。对象和 XML 之间的双向转换需要精确的内容规范。这正是 W3C XML Schema 大显身手的地方。表 8 简要描述了模式的约束特性。要注意 XML Schema 和 DTD 有一些重叠,但是 XML Schema 能够为文法组成新的数据类型。重叠的特性容易造成误解。XML Schema 支持更精确的控制,比如元素的频次。比方说可以要求 publication 列表包含而且只能包含 10 本书。在 DTD 中这是不可能的。

表 8. W3C Schema 特性

W3C XML Schema 特性
元素频次约束
名称空间限定的元素和属性声明
简单数据类型和复杂数据类型
类型派生和继承

对于定义面向数据的文法,XML Schema 似乎无论哪个方面都比 DTD 强,不过 DTD 有一件事做的比 XML Schema 好。还记得实体么?这些类似宏的声明可以置换文档中的命名项。在 DTD 中很容易定义实体。但是这种功能很难在 XML Schema 在中再现出来。一般实体常见于叙述性文法,在这个领域 DTD 的地位仍然很稳固。

XML Schema 是 XML 的另一种应用。事实上,它也受到自己的 XML Schema 的约束。DTD 不是 XML,而是一种独立的标记语言。有人认为这是 DTD 的缺点。其他人则认为 XML Schema 太罗嗦,很难阅读。您已经看到将模式规范化成不同的部分可以在一定程度上减轻这种感觉。与 DTD 相比,模式更难从头编写。现代化工具在输入的时候能够提供提示性的帮助,在一程度上缓解了这个问题。

“一定要使用模式” 这种说法是不对的。最终必须根据具体的应用来作出选择,这里给出两方面的一些意见供您参考。

结束语

总结

本系列的第 1 部分讨论了 XML 体系结构。这是第二篇教程,讨论了数据文档和叙述性文档的特点。然后对一个简单的案例研究用 XML 建模,说明了文法的迭代过程。第 3 部分将介绍如何在应用程序中处理 XML。第 4 部分的重点是如何将 XML 文档转换成新文档,第 5 部分介绍测试和优化 XML 以及常见的相关技术。

 

组织简介 | 联系我们 |   Copyright 2002 ®  UML软件工程组织 京ICP备10020922号

京公海网安备110108001071号