我们知道校验XML文件的方式有XSD 和XDR两种方式,这两种方式是利用XML Schema来实现的,现在我们介绍另外的一种校验:DTD。 DTD(Document type definition) 校验是使用W3C中有效性约束来实现的。DTD使用一种正式的文法来描述相适应的XML文档的结构和语法。完成一个DTD校验,是利用XmlValidatingReader类。XmlValidatingReader类将DTD定义在DOCTYPE中。这个DOCTYPE定义的可以包括在线的DTD或是指向一个DTD文件。
在什么情况下使用DTD校验呢?使用DTD校验,可以使一个用户组中独立的不同的用户使用共同的DTD来交换数据。比如,你可以使用共同的DTD校验你所接受到的XML数据是不是有效,你也可以用DTD来校验你自己的数据。
下面我们用一个例子来说明DTD是如何使用的。
首先写一个HeadCount.dtd:
<!ELEMENT HeadCount (Name)*>//ELEMENT说明元素是HeadCount
<!ELEMENT Name (Name)*> //ELEMENT说明元素是Name
<!ATTLIST Name First CDATA #REQUIRED>//ATTLIST说明元素Name后面是属性First
<!ATTLIST Name Last CDATA #REQUIRED>//ATTLIST说明元素Name后面还有一个是属性Last
<!ATTLIST Name Relation (self | spouse | child) "self">
<!ENTITY MyFirst "Jeff">
<!ENTITY MyLast "Smith">
具体的DTD规范可以参考W3C规范。
还有一个待校验的XML文件HeadCount.xml:
<!DOCTYPE HeadCount SYSTEM "HeadCount.dtd">
<HeadCount>
<Name First="Waldo" Last="Pepper">
<Name First="Salt" Last="Pepper" Relation="spouse"/>
<Name First="Red" Last="Pepper" Relation="child"/>
</Name>
<Name First="&MyFirst;" Last="&MyLast;">
<Name First="Sharon" Last="&MyLast;" Relation="spouse"/>
<Name First="Morgan" Last="&MyLast;" Relation="child"/>
<Name First="Shelby" Last="&MyLast;" Relation="child"/>
</Name>
</HeadCount>
现在我们写一个cs文件来实现校验:
testDtd.cs
using System;
using System.IO;
using System.Xml;
using System.Xml.Schema;
class testDtd
{
public static void Main()
{
XmlTextReader tr = new XmlTextReader("HeadCount.xml");
XmlValidatingReader vr = new XmlValidatingReader(tr);
vr.ValidationType = ValidationType.DTD;
vr.ValidationEventHandler += new ValidationEventHandler (ValidationHandler);
while(vr.Read());
Console.WriteLine("Validation finished");
}
public static void ValidationHandler(object sender, ValidationEventArgs args)
{
Console.WriteLine("***Validation error");
Console.WriteLine("\tSeverity:{0}", args.Severity);
Console.WriteLine("\tMessage :{0}", args.Message);
}
}
运行这个程序,可以发现校验正确。
但如果我们把它改下呢?
我们将HeadCount.dtd改成:
<!ELEMENT HeadCount1 (Name)*>
<!ELEMENT Name (Name)*>
<!ATTLIST Name First CDATA #REQUIRED>
<!ATTLIST Name Last CDATA #REQUIRED>
<!ATTLIST Name Relation (self | spouse | child) "self">
<!ENTITY MyFirst "Jeff">
<!ENTITY MyLast "Smith">
既将第一个元素改成HeadCount1,再运行这个程序,结果如下:
***Validation error
Severity:Error
Message :The 'HeadCount' element is not declared. An error occurred at
file:///F:/writer/validReader/testDtd/HeadCount.xml, (2, 2).
Validation finished
可以发现HeadCount.xml文件中第一个元素不是所期待的HeadCount1,所以报错。
恢复HeadCount.dtd文件,将HeadCount.xml改一下:
<!DOCTYPE HeadCount SYSTEM "HeadCount.dtd">
<HeadCount>
<Name First1="Waldo" Last1="Pepper">
<Name First="Salt" Last="Pepper" Relation="spouse"/>
<Name First="Red" Last="Pepper" Relation="child"/>
</Name>
<Name First1="&MyFirst;" Last1="&MyLast;">
<Name First="Sharon" Last="&MyLast;" Relation="spouse"/>
<Name First="Morgan" Last="&MyLast;" Relation="child"/>
<Name First="Shelby" Last="&MyLast;" Relation="child"/>
</Name>
</HeadCount>
既将First改成First1,Last改成Last1,再运行这个程序,结果如下:
***Validation error
Severity:Error
Message :The 'HeadCount' element is not declared. An error occurred at
file:///F:/writer/validReader/testDtd/HeadCount.xml, (2, 2).
Validation finished
F:\writer\validReader\testDtd>testDtd
***Validation error
Severity:Error
Message :The 'First1' attribute is not declared. An error occurred at f
ile:///F:/writer/validReader/testDtd/HeadCount.xml, (3, 9).
***Validation error
Severity:Error
Message :The 'Last1' attribute is not declared. An error occurred at fi
le:///F:/writer/validReader/testDtd/HeadCount.xml, (3, 24).
***Validation error
Severity:Error
Message :The required attribute 'Last' is missing. An error occurred at
file:///F:/writer/validReader/testDtd/HeadCount.xml, (3, 4).
***Validation error
Severity:Error
Message :The 'First1' attribute is not declared. An error occurred at f
ile:///F:/writer/validReader/testDtd/HeadCount.xml, (7, 9).
***Validation error
Severity:Error
Message :The 'Last1' attribute is not declared. An error occurred at fi
le:///F:/writer/validReader/testDtd/HeadCount.xml, (7, 28).
***Validation error
Severity:Error
Message :The required attribute 'Last' is missing. An error occurred at
file:///F:/writer/validReader/testDtd/HeadCount.xml, (7, 4).
Validation finished
通过报出来的错误信息,我们可以得知,First1和Last1属性在DTD文件中没有定义,所以出错。
通过以上的测试,可以得知DTD校验的一些简单的规律,它同Xml Schema校验相辅相成,共同构成了XML 校验,保证XML数据的正确性,是XML成为数据承载和传输的有效而可靠的载体。
XML 问题 #9 SQL 查询中的 DTD 和 XML 文档
本专栏讨论了可以不依赖 RDBMS 生成可移植 XML 结果集的公众域 sql2dtd 和 sql2xml 实用程序。从关系数据库中抽取数据的 SQL 查询可以提供非常实用且特殊的文档类型信息,用于以 XML 表示查询结果。
前一“XML 话题”专栏讨论了各种数据模型的一些基本理论和它们的优势。得出的一个结论就是 RDBMS 已被普遍接受(有充分的理由),并且在这种环境中,最好将 XML 看作是在各种 DBMS 之间传送数据的一种方法,而不是替代 DBMS 的事物。虽然 XPath 和 XSLT 可用于某些特定的“数据查询”,但它们的应用远不及 RDBMS 和(尤其是)SQL 的应用广泛和普遍。不过,由于篇幅限制,我将在以后的专栏中讨论 XPath 和 XSLT 的具体性能(和限制)。
一些近期的 RDBMS(至少包括 DB2、Oracle,可能还有其它一些 RDBMS)都附带了用于导出 XML 的内置(或者至少可选的)工具。不过,在本专栏中讨论的工具都具有普遍性;特别是,由这些工具生成的 DTD 对于对不同 RDBMS 执行的相同查询都是完全相同的。我希望这对实现数据透明性的目标会有所帮助。
过于简化
对于将关系数据库数据转换成 XML,您能想像到的最明显的方式通常也不是什么好的想法。即,它简单到 -- 无论从概念上还是从实践上 -- 将 RDBMS 的所有内容逐个表地转储到相应的 XML 文档中。例如,LIBRARY 数据库(继续上一专栏中那个简单示例)有一个称作 BOOKPRICE 的表,其内容如下:
SELECT * FROM BOOKPRICE;
+------------+-------+
| ISBN | Price |
+------------+-------+
| 2994927282 | 34.99 |
| 3920202049 | 47.50 |
+------------+-------+
我可以直接将它转换成如下文档 BOOKPRICE.xml:
清单 1. BOOKPRICE.xml 文档
<?xml version="1.0"?>
<SQL>
<row>
<ISBN>2994927282</ISBN>
<Price>34.99</Price>
</row>
<row>
<ISBN>3920202049</ISBN>
<Price>47.50</Price>
</row>
</SQL>
为数据库中的每个表创建了类似的 XML 文档后,您就拥有了整个数据库的快照。
适当简化
上面勾画的方法有两个基本问题。第一个问题是 XML 转储的效率非常低。您已经知道,XML 是一种非常冗长的格式,大型数据库的 XML 转储会生成更大的 XML 文档集合。而且,一些主要 DBMS 已经提供了简洁而有效的文本文件或压缩记录样式的转储。上面所勾画的 XML 对这些简单格式不但没有提供任何额外功能,反而显著地增加了传送文件的大小。
第二个问题比第一个更有趣。在某种意义上,“DASD 很廉价”,带宽也变得越来越廉价("DASD" 是 IBM 对“硬盘”的旧称;这个措词在设计上很陈腐)。因此单单效率不一定就是最重要的因素。更为重要的是,您几乎不会想要将数据库的全部内容与伙伴/部门/用户等进行交流。有时候,一些内容是专用的;而几乎任何时候,大多数内容与特定用户并没有任何关系。
将有用的 SQL 查询结果转储到 XML 中比将原始表转储到 XML 更有意义。而为了将 XML 事务或馈送所需的确切语法分析和处理的预期规律化,将 DTD 与这些有用的查询进行关联仍然会比较好。这两种有趣的操作恰好是公众域 Python 实用程序 sql2dtd 和 sql2xml 能够实现的。
假设 A 和 B 各自有其自身的内部数据存储器策略(例如,在不同的 RDBMS 中)。各自维护所有种类的相关信息,这些信息与 A 和 B 之间的交互没有关系,但它们又都具有一些希望共享的信息。假设,遵循这样的路线,A 需要与 B 循环地就特定种类的数据集进行通信。A 和 B 可以做的一件事是同意 A 将定期向 B 发送一组 XML 文档,每个文档都符合事先一致认可的 DTD。一个传输中的特定数据将随时间而有所不同,但有效性规则已事先指定。只要 A 和 B 知道它们之间的协议,就可以执行它们的编程。
生成 DTD
开发 A 和 B 之间的这种通信的一个方法是开发与 A 和 B 特定需要相匹配的 DTD(或模式)。然后,A 需要开发定制代码来将数据从 A 的当前 RDBMS 导出到符合一致的 DTD 中;B 需要开发定制代码来导入相同的数据(到结构不同的数据库中)。最后,通信通道被打开。
不过通常存在一种更快速的方法 -- 往往利用现有导出/导入过程的一种方法。“标准查询语言 (SQL)”是一种能够确切表达 RDBMS 数据库中您所感兴趣的数据的极其简洁的方法。尝试将例如 XPath 或 XSLT 这样的 XML 原始技术合并到关系模型上可能不太自然,尽管它们必定可以在 XML 的基本分层模型中表达查询功能。
许多组织已经开发出为实现已知任务的、经过了彻底测试的几组 SQL 语句。事实上,RDBMS 往往提供了用于优化已存储查询的方法。虽然肯定有一些为数据交换而设计的丰富的 DTD 比较有意义的情况,但在许多或者大多数情况下,在 SQL 查询中隐式地使用结构信息作为 SQL 数据传输的(自动)基础是种很好的解决方案。
虽然 SQL 查询可以以复杂的方法来组合表数据,但所有 SQL 查询的结果都是相当简单的“行与列”的排列。在查询输出中,列的数量是固定的,每行填入了每个固定列的值。(即,除了数量上没有改变以外,无论是值的类型还是列名在 SQL 结果中均无改变 -- 即使它们可能在 XML 文档中有所改变)。XML 表示元素复杂嵌套模式的潜力在表示 SQL 结果中并没有得到深层的体现。虽然如此,SQL 查询的一些重要方面可以而且也应该在简单的行/列位置以外以 XML DTD 表示。
要表示 SQL 查询中的哪些内容?
我认为在 A 和 B 之间预期的数据交换中,应该区分出两方面。一方面,是 A 数据的内部组织 -- 例如,其标准化和取消标准化优化。B 没有,也不需要考虑 A 的内在方面。另一方面,存在描述实际发送内容的元数据。当然,区分这些方面不一定很容易。
在编写 sql2dtd(和帮助规划 Scott Hathaway 的 sql2xml)时,我做了一些决定,确定了哪些属于数据传输的部分,哪些位于发送方设置的内部(不需要以 DTD 表示)。清单 2 所显示的样本 XML(带有作为内部集的 DTD)输出有助于说明这些决定(该输出完全从作为属性包含的 SQL 查询生成;当然,是在对合适的数据库运行时):
清单 2. 带有作为内部集的 DTD 的样本 XML 输出
<?xml version="1.0"?>
<!DOCTYPE SQL [
<!ELEMENT SQL (row)*>
<!ATTLIST SQL
GROUP_BY NMTOKEN #FIXED "AuthID"
query CDATA #FIXED "SELECT AuthID AS SSN,COUNT(GroupID)
FROM AUTHGROUP GROUP BY AuthID
ORDER BY AuthID"
>
<!ELEMENT row (SSN, column2)>
<!ATTLIST row num ID #IMPLIED>
<!ELEMENT SSN (#PCDATA)>
<!ELEMENT column2 (#PCDATA)>
<!ATTLIST column2 CALC CDATA #FIXED "COUNT(GroupID)">
]>
<SQL>
<row num="1">
<SSN>111-22-3333</SSN>
<column2>1</column2>
</row>
<row num="2">
<SSN>333-22-4444</SSN>
<column2>2</column2>
</row>
<row num="3">
<SSN>666-44-5555</SSN>
<column2>1</column2>
</row>
</SQL>
这个简单的 XML 文档实际上可以包含比人们最初时所注意到的更多的元数据。当然,通过将 SQL 本身作为根节点的一个属性包含在内,人们可以重新构造 SQL 中固有的任何事物。但这样做需要对 SQL 重新进行语法分析,sql2dtd 已在文档中表示过它,因此通常这没有必要。
CALC 属性的规范包含这样一个事实,即 XML 包含了计算过的元素。因为计算过的表达式可能很长,并且可能包含对于 XML 标记来说非法的字符,所以计算过的列仅由它们的位置命名。不过,加入元素内容的特定计算是作为标记的一个属性包含在内的。为了避免在 XML 主体中重复属性,它在 DTD 中指定为 #FIXED。
如果使用计算过的列,计算往往反映使用 "GROUP BY" 修饰符对列所进行的分组。所有这样的分组都在根元素的 GROUP_BY 属性中列出。
而且,如果使用了 "ORDER BY" 子句,每个 <row> 标记就会带有指定输出数据序列的 num 属性。不过,如果结果集是无序的,则不使用 num 属性。
让我们考虑一下在 DTD 中有什么东西没有表示,就会发现它实际上确实属于 A 的内部数据表示,而不属于发送的消息。
除了嵌入的原始 SQL 查询以外,没有保留用于查询数据的一个或多个表的表示("FROM" 子句)。特殊的表组织只不过不是 B 需要感兴趣的一些事物。事实上,A 可以在有适当传输协议之后彻底地修改其数据库设计;但只要通过一些手段抽取相同的字段(列),B 就不需要担心这一点。尤其是,因为 "AS" 子句覆盖了实际的表的列名,所以有可能继续发送在 A 数据库中不再拥有任何直接文字意义的 XML 元素。
在 sql2dtd 设计中最重要的一点是忽略了 "WHERE" 和 "HAVING" 子句(以及 JOIN、DISTINCT 和 ALL 修饰符)。和表名一样,将数据从 A 的那些表中取出所必需的特定联接和过滤器不是 B 应该担心的问题。如果 A 碰巧需要将一些表联接在一起以获得一些数据,那只是 A 的一个标准化策略。B 可以使用也可以不使用任何类似的策略(对于不同的数据子集),两种方法都不用担心 A 的操作。出于相关但稍微不同的理由忽略过滤器(主要使用 "WHERE" 子句或 "DISTINCT" 修饰符)。不管出于何种商业原因,如果 A 只需要告之 B 那些其 whatzit 大于 25 的 woozles,从 B 的角度来说,它只是属于 woozles 的性质。即,A 可能对 B 不关心的 woozles 的一个子类感兴趣;但是 A 需要通过使用过滤器来获得感兴趣的内容(与不拥有它们,或将它们放在另一个表中相反)这一特定事实并不是 B 需要担心的。从这一方面来说,sub-select 只是另一种过滤器。
结束
我还没有介绍 sql2dtd 和 sql2xml 的任何特定用法。这里不需要做太多解释,因为它们自己已在内部详细说明了。一般情况下, sql2dtd 可以从 SQL 查询生成 DTD,但它本身并不查询任何数据库。sql2xml 通过 ODBC 执行查询,并可以利用 sql2dtd 来获得 DTD(它也可以生成 DTD 较少的 XML文档)。
这些工具只对 A 和 B 之间预期大约一半过程有所帮助。A 和 B 可以快速地使用这些工具达到 DTD,A 可以同样快速地生成符合这些 DTD 的输出 XML 文档。但 B 最终仍需要执行语法分析、存储和处理这些接收到的文档所涉及的所有工作。以后的专栏将更详细地讨论 B 的任务。
简单的DTD例子描述和分析
下面举一个带有内部DTD的XML文档的例子:
<?xml version="1.0" encoding="GB2312" ?>
<!DOCTYPE 家庭 [
<!ELEMENT 家庭 (人+,家电*)>
<!ELEMENT人 EMPTY>
<!ELEMENT 家电 EMPTY>
<!ATTLIST 人
名字 CDATA #REQUIRED
性别 (男|女) #REQUIRED
年龄 CDATA #REQUIRED
爱好 CDATA #IMPLIED
>
<!ATTLIST 家电
名称 CDATA #REQUIRED
数量 CDATA #REQUIRED
说明 CDATA #IMPLIED
>
]>
<家庭>
<人>
<名字>郭大路
<性别>男
<年龄>25
<人>
<名字>李寻欢
<性别>男
<年龄>38
<爱好>作个教育家和伟人
<家电>
<名称>彩电
<数量>3
这个文档从第二行开始进行文档类型声明,包含了文档元素(家庭)的名称。根据定义,我们发现该元素可以包含一个或者多个人(由这个+号决定的),可以包含零个或多个家电(由这个*符号决定),然后定义了人这个元素的需要的属性,其中名字、性别和年龄是必须的,而爱好可以填有也可以不填。家电的名字和数量属性必须有,但说明可以写也可以不写。
如果采用外部DTD的话,就需要有两个文档,第一个文档就是关于DTD的文档,第二个文档就是遵守DTD格式的内容文档。实际上我们可以建立无穷多个遵守该DTD格式的文档。举一个例子来说,我们在构造关系数据库中的表的时候,我们需要定义好表的结构(也就是表包含的字段集合),然后我们就可以往这个表中放入记录,记录的个数从理论上讲可以是无穷多个的。这里关于表的结构就类似于DTD文档。记录类似于遵守DTD格式的内容文档。外部DTD的好处是:它可以方便高效地被多个XML文件所共享。你只要写一个DTD文件,就可以被多个XML文件所引用。事实上,当许多组织需要统一它们的数据交换格式时,它们就是通过外部DTD来完成的。这样做不仅简化了输入工作,还保证当你需要对DTD做出改动时,不用一一去改每个引用了它的XML文件,只要改一个公用的DTD文件就足够了。不过需要注意,如果DTD的改动不是"向后兼容"的,这时原先根据该DTD编写的那些XML文件可能就会出问题了。
现在我们就尝试建立一个DTD文档,不妨命名为Home.dtd。其代码如下:
<?xml version="1.0" encoding="GB2312" ?>
<!ELEMENT 家庭 (人+,家电*)>
<!ELEMENT人 EMPTY>
<!ELEMENT 家电 EMPTY>
<!ATTLIST 人
名字 CDATA #REQUIRED
性别 (男|女) #REQUIRED
年龄 CDATA #REQUIRED
爱好 CDATA #IMPLIED
>
<!ATTLIST 家电
名称 CDATA #REQUIRED
数量 CDATA #REQUIRED
说明 CDATA #IMPLIED
>
然后,我们可以建立一个遵守该DTD格式的内容文档,不妨设为HomeInstance.xml。其代码如下:
<?xml version="1.0" encoding="GB2312" ?>
<!DOCTYPE 家庭 SYSTEM "Home.dtd">
<家庭>
<人>
<名字>郭大路
<性别>男
<年龄>25
<人>
<名字>李寻欢
<性别>男
<年龄>38
<爱好>作个教育家和伟人
<家电>
<名称>彩电
<数量>3
把这两个文档放到同一个目录下,然后可以用XML浏览器对HomeInstance.xml进行浏览,结果应该和使用内部DTD的结果一样。
众所周知,在设计MIS应用程序的时候,重要的是要进行E-R图设计,然后建立关系数据库,建立数据库的关键就是要定义好表的格式,并使它的范式尽可能的高。对应的,建立基于XML应用的关键就是要定义好DTD,然后所有的内容就按照DTD格式进行编写。DTD实际上表现了一个层次的关系,你也可以把它理解成一棵树的结构。树中的节点实际上就是一个个元素(ELEMENT),一个元素可以包含其他的元素。比如上面的例子中家庭这个元素包含了人和家电这两个元素。一个元素可以包含属性(ATTLIST)也可以没有任何属性。比如上面的例子中,家庭这个元素就没有任何属性,而人和家电都有自己的属性。
际上如果大家学过编译系统的话,都知道对编程语言进行语法定义的工具:巴科斯-诺尔范式。它是用来对语言的语法进行定义的工具。实际上DTD就是起到了类似的作用。
posted on 2006-05-24 00:45
船长 阅读(3949)
评论(0) 编辑 收藏 所属分类:
J2EE