我們知道校驗(yàn)XML文件的方式有XSD?和XDR兩種方式,這兩種方式是利用XML?Schema來實(shí)現(xiàn)的,現(xiàn)在我們介紹另外的一種校驗(yàn):DTD。?DTD(Document?type?definition)?校驗(yàn)是使用W3C中有效性約束來實(shí)現(xiàn)的。DTD使用一種正式的文法來描述相適應(yīng)的XML文檔的結(jié)構(gòu)和語法。完成一個(gè)DTD校驗(yàn),是利用XmlValidatingReader類。XmlValidatingReader類將DTD定義在DOCTYPE中。這個(gè)DOCTYPE定義的可以包括在線的DTD或是指向一個(gè)DTD文件。
在什么情況下使用DTD校驗(yàn)?zāi)兀渴褂肈TD校驗(yàn),可以使一個(gè)用戶組中獨(dú)立的不同的用戶使用共同的DTD來交換數(shù)據(jù)。比如,你可以使用共同的DTD校驗(yàn)?zāi)闼邮艿降腦ML數(shù)據(jù)是不是有效,你也可以用DTD來校驗(yàn)?zāi)阕约旱臄?shù)據(jù)。
下面我們用一個(gè)例子來說明DTD是如何使用的。
首先寫一個(gè)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后面還有一個(gè)是屬性Last
<!ATTLIST?Name?Relation?(self?|?spouse?|?child)?"self">
<!ENTITY?MyFirst?"Jeff">
<!ENTITY?MyLast?"Smith">
具體的DTD規(guī)范可以參考W3C規(guī)范。
還有一個(gè)待校驗(yàn)的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>
現(xiàn)在我們寫一個(gè)cs文件來實(shí)現(xiàn)校驗(yàn):
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);
}
}
運(yùn)行這個(gè)程序,可以發(fā)現(xiàn)校驗(yàn)正確。
但如果我們把它改下呢?
我們將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">
既將第一個(gè)元素改成HeadCount1,再運(yùn)行這個(gè)程序,結(jié)果如下:
***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ā)現(xiàn)HeadCount.xml文件中第一個(gè)元素不是所期待的HeadCount1,所以報(bào)錯(cuò)。
恢復(fù)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,再運(yùn)行這個(gè)程序,結(jié)果如下:
***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
通過報(bào)出來的錯(cuò)誤信息,我們可以得知,F(xiàn)irst1和Last1屬性在DTD文件中沒有定義,所以出錯(cuò)。
通過以上的測(cè)試,可以得知DTD校驗(yàn)的一些簡(jiǎn)單的規(guī)律,它同Xml?Schema校驗(yàn)相輔相成,共同構(gòu)成了XML?校驗(yàn),保證XML數(shù)據(jù)的正確性,是XML成為數(shù)據(jù)承載和傳輸?shù)挠行Ф煽康妮d體。
XML?問題?#9??SQL?查詢中的?DTD?和?XML?文檔
前一“XML?話題”專欄討論了各種數(shù)據(jù)模型的一些基本理論和它們的優(yōu)勢(shì)。得出的一個(gè)結(jié)論就是?RDBMS?已被普遍接受(有充分的理由),并且在這種環(huán)境中,最好將?XML?看作是在各種?DBMS?之間傳送數(shù)據(jù)的一種方法,而不是替代?DBMS?的事物。雖然?XPath?和?XSLT?可用于某些特定的“數(shù)據(jù)查詢”,但它們的應(yīng)用遠(yuǎn)不及?RDBMS?和(尤其是)SQL?的應(yīng)用廣泛和普遍。不過,由于篇幅限制,我將在以后的專欄中討論?XPath?和?XSLT?的具體性能(和限制)。
一些近期的?RDBMS(至少包括?DB2、Oracle,可能還有其它一些?RDBMS)都附帶了用于導(dǎo)出?XML?的內(nèi)置(或者至少可選的)工具。不過,在本專欄中討論的工具都具有普遍性;特別是,由這些工具生成的?DTD?對(duì)于對(duì)不同?RDBMS?執(zhí)行的相同查詢都是完全相同的。我希望這對(duì)實(shí)現(xiàn)數(shù)據(jù)透明性的目標(biāo)會(huì)有所幫助。
過于簡(jiǎn)化
對(duì)于將關(guān)系數(shù)據(jù)庫(kù)數(shù)據(jù)轉(zhuǎn)換成?XML,您能想像到的最明顯的方式通常也不是什么好的想法。即,它簡(jiǎn)單到?--?無論從概念上還是從實(shí)踐上?--?將?RDBMS?的所有內(nèi)容逐個(gè)表地轉(zhuǎn)儲(chǔ)到相應(yīng)的?XML?文檔中。例如,LIBRARY?數(shù)據(jù)庫(kù)(繼續(xù)上一專欄中那個(gè)簡(jiǎn)單示例)有一個(gè)稱作?BOOKPRICE?的表,其內(nèi)容如下:
SELECT?*?FROM?BOOKPRICE;
+------------+-------+
|?ISBN?|?Price?|
+------------+-------+
|?2994927282?|?34.99?|
|?3920202049?|?47.50?|
+------------+-------+
?
我可以直接將它轉(zhuǎn)換成如下文檔?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>
?
為數(shù)據(jù)庫(kù)中的每個(gè)表創(chuàng)建了類似的?XML?文檔后,您就擁有了整個(gè)數(shù)據(jù)庫(kù)的快照。
適當(dāng)簡(jiǎn)化
上面勾畫的方法有兩個(gè)基本問題。第一個(gè)問題是?XML?轉(zhuǎn)儲(chǔ)的效率非常低。您已經(jīng)知道,XML?是一種非常冗長(zhǎng)的格式,大型數(shù)據(jù)庫(kù)的?XML?轉(zhuǎn)儲(chǔ)會(huì)生成更大的?XML?文檔集合。而且,一些主要?DBMS?已經(jīng)提供了簡(jiǎn)潔而有效的文本文件或壓縮記錄樣式的轉(zhuǎn)儲(chǔ)。上面所勾畫的?XML?對(duì)這些簡(jiǎn)單格式不但沒有提供任何額外功能,反而顯著地增加了傳送文件的大小。
第二個(gè)問題比第一個(gè)更有趣。在某種意義上,“DASD?很廉價(jià)”,帶寬也變得越來越廉價(jià)("DASD"?是?IBM?對(duì)“硬盤”的舊稱;這個(gè)措詞在設(shè)計(jì)上很陳腐)。因此單單效率不一定就是最重要的因素。更為重要的是,您幾乎不會(huì)想要將數(shù)據(jù)庫(kù)的全部?jī)?nèi)容與伙伴/部門/用戶等進(jìn)行交流。有時(shí)候,一些內(nèi)容是專用的;而幾乎任何時(shí)候,大多數(shù)內(nèi)容與特定用戶并沒有任何關(guān)系。
將有用的?SQL?查詢結(jié)果轉(zhuǎn)儲(chǔ)到?XML?中比將原始表轉(zhuǎn)儲(chǔ)到?XML?更有意義。而為了將?XML?事務(wù)或饋送所需的確切語法分析和處理的預(yù)期規(guī)律化,將?DTD?與這些有用的查詢進(jìn)行關(guān)聯(lián)仍然會(huì)比較好。這兩種有趣的操作恰好是公眾域?Python?實(shí)用程序?sql2dtd?和?sql2xml?能夠?qū)崿F(xiàn)的。
假設(shè)?A?和?B?各自有其自身的內(nèi)部數(shù)據(jù)存儲(chǔ)器策略(例如,在不同的?RDBMS?中)。各自維護(hù)所有種類的相關(guān)信息,這些信息與?A?和?B?之間的交互沒有關(guān)系,但它們又都具有一些希望共享的信息。假設(shè),遵循這樣的路線,A?需要與?B?循環(huán)地就特定種類的數(shù)據(jù)集進(jìn)行通信。A?和?B?可以做的一件事是同意?A?將定期向?B?發(fā)送一組?XML?文檔,每個(gè)文檔都符合事先一致認(rèn)可的?DTD。一個(gè)傳輸中的特定數(shù)據(jù)將隨時(shí)間而有所不同,但有效性規(guī)則已事先指定。只要?A?和?B?知道它們之間的協(xié)議,就可以執(zhí)行它們的編程。
生成?DTD
開發(fā)?A?和?B?之間的這種通信的一個(gè)方法是開發(fā)與?A?和?B?特定需要相匹配的?DTD(或模式)。然后,A?需要開發(fā)定制代碼來將數(shù)據(jù)從?A?的當(dāng)前?RDBMS?導(dǎo)出到符合一致的?DTD?中;B?需要開發(fā)定制代碼來導(dǎo)入相同的數(shù)據(jù)(到結(jié)構(gòu)不同的數(shù)據(jù)庫(kù)中)。最后,通信通道被打開。
不過通常存在一種更快速的方法?--?往往利用現(xiàn)有導(dǎo)出/導(dǎo)入過程的一種方法。“標(biāo)準(zhǔn)查詢語言?(SQL)”是一種能夠確切表達(dá)?RDBMS?數(shù)據(jù)庫(kù)中您所感興趣的數(shù)據(jù)的極其簡(jiǎn)潔的方法。嘗試將例如?XPath?或?XSLT?這樣的?XML?原始技術(shù)合并到關(guān)系模型上可能不太自然,盡管它們必定可以在?XML?的基本分層模型中表達(dá)查詢功能。
許多組織已經(jīng)開發(fā)出為實(shí)現(xiàn)已知任務(wù)的、經(jīng)過了徹底測(cè)試的幾組?SQL?語句。事實(shí)上,RDBMS?往往提供了用于優(yōu)化已存儲(chǔ)查詢的方法。雖然肯定有一些為數(shù)據(jù)交換而設(shè)計(jì)的豐富的?DTD?比較有意義的情況,但在許多或者大多數(shù)情況下,在?SQL?查詢中隱式地使用結(jié)構(gòu)信息作為?SQL?數(shù)據(jù)傳輸?shù)模ㄗ詣?dòng))基礎(chǔ)是種很好的解決方案。
雖然?SQL?查詢可以以復(fù)雜的方法來組合表數(shù)據(jù),但所有?SQL?查詢的結(jié)果都是相當(dāng)簡(jiǎn)單的“行與列”的排列。在查詢輸出中,列的數(shù)量是固定的,每行填入了每個(gè)固定列的值。(即,除了數(shù)量上沒有改變以外,無論是值的類型還是列名在?SQL?結(jié)果中均無改變?--?即使它們可能在?XML?文檔中有所改變)。XML?表示元素復(fù)雜嵌套模式的潛力在表示?SQL?結(jié)果中并沒有得到深層的體現(xiàn)。雖然如此,SQL?查詢的一些重要方面可以而且也應(yīng)該在簡(jiǎn)單的行/列位置以外以?XML?DTD?表示。
要表示?SQL?查詢中的哪些內(nèi)容?
我認(rèn)為在?A?和?B?之間預(yù)期的數(shù)據(jù)交換中,應(yīng)該區(qū)分出兩方面。一方面,是?A?數(shù)據(jù)的內(nèi)部組織?--?例如,其標(biāo)準(zhǔn)化和取消標(biāo)準(zhǔn)化優(yōu)化。B?沒有,也不需要考慮?A?的內(nèi)在方面。另一方面,存在描述實(shí)際發(fā)送內(nèi)容的元數(shù)據(jù)。當(dāng)然,區(qū)分這些方面不一定很容易。
在編寫?sql2dtd(和幫助規(guī)劃?Scott?Hathaway?的?sql2xml)時(shí),我做了一些決定,確定了哪些屬于數(shù)據(jù)傳輸?shù)牟糠郑男┪挥诎l(fā)送方設(shè)置的內(nèi)部(不需要以?DTD?表示)。清單?2?所顯示的樣本?XML(帶有作為內(nèi)部集的?DTD)輸出有助于說明這些決定(該輸出完全從作為屬性包含的?SQL?查詢生成;當(dāng)然,是在對(duì)合適的數(shù)據(jù)庫(kù)運(yùn)行時(shí)):
清單?2.?帶有作為內(nèi)部集的?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>
?
這個(gè)簡(jiǎn)單的?XML?文檔實(shí)際上可以包含比人們最初時(shí)所注意到的更多的元數(shù)據(jù)。當(dāng)然,通過將?SQL?本身作為根節(jié)點(diǎn)的一個(gè)屬性包含在內(nèi),人們可以重新構(gòu)造?SQL?中固有的任何事物。但這樣做需要對(duì)?SQL?重新進(jìn)行語法分析,sql2dtd?已在文檔中表示過它,因此通常這沒有必要。
CALC?屬性的規(guī)范包含這樣一個(gè)事實(shí),即?XML?包含了計(jì)算過的元素。因?yàn)橛?jì)算過的表達(dá)式可能很長(zhǎng),并且可能包含對(duì)于?XML?標(biāo)記來說非法的字符,所以計(jì)算過的列僅由它們的位置命名。不過,加入元素內(nèi)容的特定計(jì)算是作為標(biāo)記的一個(gè)屬性包含在內(nèi)的。為了避免在?XML?主體中重復(fù)屬性,它在?DTD?中指定為?#FIXED。
如果使用計(jì)算過的列,計(jì)算往往反映使用?"GROUP?BY"?修飾符對(duì)列所進(jìn)行的分組。所有這樣的分組都在根元素的?GROUP_BY?屬性中列出。
而且,如果使用了?"ORDER?BY"?子句,每個(gè)?<row>?標(biāo)記就會(huì)帶有指定輸出數(shù)據(jù)序列的?num?屬性。不過,如果結(jié)果集是無序的,則不使用?num?屬性。
讓我們考慮一下在?DTD?中有什么東西沒有表示,就會(huì)發(fā)現(xiàn)它實(shí)際上確實(shí)屬于?A?的內(nèi)部數(shù)據(jù)表示,而不屬于發(fā)送的消息。
除了嵌入的原始?SQL?查詢以外,沒有保留用于查詢數(shù)據(jù)的一個(gè)或多個(gè)表的表示("FROM"?子句)。特殊的表組織只不過不是?B?需要感興趣的一些事物。事實(shí)上,A?可以在有適當(dāng)傳輸協(xié)議之后徹底地修改其數(shù)據(jù)庫(kù)設(shè)計(jì);但只要通過一些手段抽取相同的字段(列),B?就不需要擔(dān)心這一點(diǎn)。尤其是,因?yàn)?"AS"?子句覆蓋了實(shí)際的表的列名,所以有可能繼續(xù)發(fā)送在?A?數(shù)據(jù)庫(kù)中不再擁有任何直接文字意義的?XML?元素。
在?sql2dtd?設(shè)計(jì)中最重要的一點(diǎn)是忽略了?"WHERE"?和?"HAVING"?子句(以及?JOIN、DISTINCT?和?ALL?修飾符)。和表名一樣,將數(shù)據(jù)從?A?的那些表中取出所必需的特定聯(lián)接和過濾器不是?B?應(yīng)該擔(dān)心的問題。如果?A?碰巧需要將一些表聯(lián)接在一起以獲得一些數(shù)據(jù),那只是?A?的一個(gè)標(biāo)準(zhǔn)化策略。B?可以使用也可以不使用任何類似的策略(對(duì)于不同的數(shù)據(jù)子集),兩種方法都不用擔(dān)心?A?的操作。出于相關(guān)但稍微不同的理由忽略過濾器(主要使用?"WHERE"?子句或?"DISTINCT"?修飾符)。不管出于何種商業(yè)原因,如果?A?只需要告之?B?那些其?whatzit?大于?25?的?woozles,從?B?的角度來說,它只是屬于?woozles?的性質(zhì)。即,A?可能對(duì)?B?不關(guān)心的?woozles?的一個(gè)子類感興趣;但是?A?需要通過使用過濾器來獲得感興趣的內(nèi)容(與不擁有它們,或?qū)⑺鼈兎旁诹硪粋€(gè)表中相反)這一特定事實(shí)并不是?B?需要擔(dān)心的。從這一方面來說,sub-select?只是另一種過濾器。
結(jié)束
我還沒有介紹?sql2dtd?和?sql2xml?的任何特定用法。這里不需要做太多解釋,因?yàn)樗鼈冏约阂言趦?nèi)部詳細(xì)說明了。一般情況下,?sql2dtd?可以從?SQL?查詢生成?DTD,但它本身并不查詢?nèi)魏螖?shù)據(jù)庫(kù)。sql2xml?通過?ODBC?執(zhí)行查詢,并可以利用?sql2dtd?來獲得?DTD(它也可以生成?DTD?較少的?XML文檔)。
這些工具只對(duì)?A?和?B?之間預(yù)期大約一半過程有所幫助。A?和?B?可以快速地使用這些工具達(dá)到?DTD,A?可以同樣快速地生成符合這些?DTD?的輸出?XML?文檔。但?B?最終仍需要執(zhí)行語法分析、存儲(chǔ)和處理這些接收到的文檔所涉及的所有工作。以后的專欄將更詳細(xì)地討論?B?的任務(wù)。
簡(jiǎn)單的DTD例子描述和分析
下面舉一個(gè)帶有內(nèi)部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?
數(shù)量?CDATA?#REQUIRED?
說明?CDATA?#IMPLIED?
>?
]>??
<家庭>?
<人>?
<名字>郭大路?
<性別>男?
<年齡>25?
?
<人>?
<名字>李尋歡?
<性別>男?
<年齡>38?
<愛好>作個(gè)教育家和偉人?
?
<家電>?
<名稱>彩電?
<數(shù)量>3?
?
?
這個(gè)文檔從第二行開始進(jìn)行文檔類型聲明,包含了文檔元素(家庭)的名稱。根據(jù)定義,我們發(fā)現(xiàn)該元素可以包含一個(gè)或者多個(gè)人(由這個(gè)+號(hào)決定的),可以包含零個(gè)或多個(gè)家電(由這個(gè)*符號(hào)決定),然后定義了人這個(gè)元素的需要的屬性,其中名字、性別和年齡是必須的,而愛好可以填有也可以不填。家電的名字和數(shù)量屬性必須有,但說明可以寫也可以不寫。?
如果采用外部DTD的話,就需要有兩個(gè)文檔,第一個(gè)文檔就是關(guān)于DTD的文檔,第二個(gè)文檔就是遵守DTD格式的內(nèi)容文檔。實(shí)際上我們可以建立無窮多個(gè)遵守該DTD格式的文檔。舉一個(gè)例子來說,我們?cè)跇?gòu)造關(guān)系數(shù)據(jù)庫(kù)中的表的時(shí)候,我們需要定義好表的結(jié)構(gòu)(也就是表包含的字段集合),然后我們就可以往這個(gè)表中放入記錄,記錄的個(gè)數(shù)從理論上講可以是無窮多個(gè)的。這里關(guān)于表的結(jié)構(gòu)就類似于DTD文檔。記錄類似于遵守DTD格式的內(nèi)容文檔。外部DTD的好處是:它可以方便高效地被多個(gè)XML文件所共享。你只要寫一個(gè)DTD文件,就可以被多個(gè)XML文件所引用。事實(shí)上,當(dāng)許多組織需要統(tǒng)一它們的數(shù)據(jù)交換格式時(shí),它們就是通過外部DTD來完成的。這樣做不僅簡(jiǎn)化了輸入工作,還保證當(dāng)你需要對(duì)DTD做出改動(dòng)時(shí),不用一一去改每個(gè)引用了它的XML文件,只要改一個(gè)公用的DTD文件就足夠了。不過需要注意,如果DTD的改動(dòng)不是"向后兼容"的,這時(shí)原先根據(jù)該DTD編寫的那些XML文件可能就會(huì)出問題了。?
現(xiàn)在我們就嘗試建立一個(gè)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?
數(shù)量?CDATA?#REQUIRED?
說明?CDATA?#IMPLIED?
>?
然后,我們可以建立一個(gè)遵守該DTD格式的內(nèi)容文檔,不妨設(shè)為HomeInstance.xml。其代碼如下:?
<?xml?version="1.0"?encoding="GB2312"??>?
<!DOCTYPE?家庭?SYSTEM?"Home.dtd">?
<家庭>?
<人>?
<名字>郭大路?
<性別>男?
<年齡>25?
?
<人>?
<名字>李尋歡?
<性別>男?
<年齡>38?
<愛好>作個(gè)教育家和偉人?
?
<家電>?
<名稱>彩電?
<數(shù)量>3?
?
?
把這兩個(gè)文檔放到同一個(gè)目錄下,然后可以用XML瀏覽器對(duì)HomeInstance.xml進(jìn)行瀏覽,結(jié)果應(yīng)該和使用內(nèi)部DTD的結(jié)果一樣。?
眾所周知,在設(shè)計(jì)MIS應(yīng)用程序的時(shí)候,重要的是要進(jìn)行E-R圖設(shè)計(jì),然后建立關(guān)系數(shù)據(jù)庫(kù),建立數(shù)據(jù)庫(kù)的關(guān)鍵就是要定義好表的格式,并使它的范式盡可能的高。對(duì)應(yīng)的,建立基于XML應(yīng)用的關(guān)鍵就是要定義好DTD,然后所有的內(nèi)容就按照DTD格式進(jìn)行編寫。DTD實(shí)際上表現(xiàn)了一個(gè)層次的關(guān)系,你也可以把它理解成一棵樹的結(jié)構(gòu)。樹中的節(jié)點(diǎn)實(shí)際上就是一個(gè)個(gè)元素(ELEMENT),一個(gè)元素可以包含其他的元素。比如上面的例子中家庭這個(gè)元素包含了人和家電這兩個(gè)元素。一個(gè)元素可以包含屬性(ATTLIST)也可以沒有任何屬性。比如上面的例子中,家庭這個(gè)元素就沒有任何屬性,而人和家電都有自己的屬性。?
際上如果大家學(xué)過編譯系統(tǒng)的話,都知道對(duì)編程語言進(jìn)行語法定義的工具:巴科斯-諾爾范式。它是用來對(duì)語言的語法進(jìn)行定義的工具。實(shí)際上DTD就是起到了類似的作用。