我們知道校驗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
船長 閱讀(3962)
評論(0) 編輯 收藏 所屬分類:
J2EE