XML的衍生規格
XML在設計當初就有將Web考慮在內,而且提供了SGML所沒有的好處。瀏覽器可以利用XML搭配HTML來做資料的顯示與呈現。而為了支援XML在 Web 上的傳遞,目前還有兩種XML的相關規格被制定出來。
其中一種規格是「延伸連結語言」 (eXtensible Linking Language 簡稱XLL),這種規格描述的是一種超連結架構,用來同時支援傳統的HTML超連結及更進階的延伸超連結,其中後者可以讓單一連結指向多個不同的連結資源。雖然SGML也可以定義超連結機制,不過在SGML的原始規格中並沒有提到。
另一種規格是「延伸樣式語言」 (eXtensible Stylesheet Language 簡稱XSL),這種規格可以提供對XML樣式表架構的支援,這也是SGML規格中沒有涵蓋的部份。樣式表允許設計者建立樣式範本 (例如粗體、斜體等) 或樣式組合,當XML文件顯示時,即可將樣式範本或組合套用到各種XML元件 (Element)上。
簡單來說,XML擁有SGML百分之八十的功能,但其複雜度卻只有SGML的百分之二十。基本上,擁有一種簡單且通用的資料描述方法是十分有用的。舉例來說,由於XML擁有中立的資料交換格式,因此許多不同類型的系統都可以存取其資料。舉例來說,就我們所知,很多老舊的電腦系統是用各種不同的格式來儲存資料的,但是現在已經有一些系統轉換程式,可以讓很多舊系統可以讀取或輸出XML格式的資料。所以,在某個舊系統中用XML製成的資料檔案可以輕易地輸出到其他舊系統上、或發表成 Web 網頁、或輸出到其它支援XML的應用程式中。也就是說,XML可以當作是一個中介的資料格式,讓原本不相容的系統可以互相交換資料。
XML是一種資料
如果HTML是為了「顯示」資訊,那麼,XML就是為了「描述」資訊。XML是一個可以讓資料結構化並描述資料的標準語言,且可以讓不同的應用程式所瞭解。XML最大的威力就是它可以將使用者介面與資料分開。我們來看一個簡單的XML文件,以瞭解 XML 是如何運作的:
This is an XML-based email.
這個例子必須注意的重點是,在文件中並沒有描述如何顯示此份文件。換句話說,就是沒有包含樣式的資訊 (如粗體、斜體字、縮排、字體大小等)。因為XML文件的原始碼只是描述有哪些資料,讓讀者可以輕易地了解這份文件的內容以及結構。
XML 文件也被稱為「自我描述」 (self-describing) 的文件,因為,每一份 XML文件內都會包含一組資料必須遵循的規則。而由於每一組規則都可以重複使用,因此,文件建立者可以很容易地利用相同類別 (class) 文件的新版本。
類別是出自物件導向的程式觀念,在物件導向的程式中,類別用於描述一群有相同特性的物件。基於各文件的內容種類,將文件建立類別,可以提供一種很有用的文件分類方法。
有效的與標準的XML文件
XML 最有用的兩個特性是:提供文件結構化的能力,及提供資料自我描述的能力。要有這兩種能力,文件中必須要有遵循結構化 (structual) 與文法化 (grammatical) 的規則。接下來的兩小節,將簡單介紹兩個XML詞彙:「有效的」 (Valid) 和「標準的」 (Well-formed),這兩者描述了文件如何遵循用來控制XML文件的結構化與文法化規則。
有效的(Valid)文件
XML文件處理器 (XML processor) 可強制執行XML文件執行文件內的結構和內容規則。這些規則包括標準XML規格所要求的部份,以及針對該文件所特別定義的規則。其中,第二種規則是以「文件型態定義」 (Document Type Definiton,簡稱DTD) 存在。DTD 和 XML文件中真正的資料部份是分開的,DTD 可以直接包含在XML文件內部;也可以從XML獨立出來,成為一個獨立的文件。一個有效的XML文件必須嚴格地遵守這兩種規則。本書並不會詳細介紹如何撰寫及使用 DTD文件,因為本書只針對簡單且不需DTD的XML例子來說明。您可以在任何專業的XML參考手冊或網站上找到更多撰寫DTD文件的資訊,例如XML in Action這本書 (by William J. Pardi,Microsoft Press 1999) 和 www.w3.org/xml (W3C的XML網站)。正如您等一下看到的,XML文件不是非得要有DTD文件才行。
標準的 (Well-formed) 文件
標準的 (Well-formed) 文件所要遵循的規則遠不如有效文件嚴格。建立一個標準的XML文件其實是非常簡單且直覺的。以下列表會條列出標準化的XML文件主要規則。本章稍後也會舉一些例子。
如果XML文件不遵循這些規則,瀏覽器將會出現錯誤訊息。在 Internet Explorer瀏覽器中,可以利用Script語言透過物件模型 (Object Model) 來偵測錯誤。本章稍後也會有錯誤處理 (Error handling) 的詳細介紹。
我們來看看一個XML文件的例子:
這個文件包含了一個PLANT文件元件 (稱為Document Element或根元件),而COMMON和BOTANICAL是包含在根元件內部的元件。為了要講述這個觀念,以下一個例子是「非」Well-formed的文件,因為它包含了兩個根元件:
元件的巢狀層級可建立父/子元件的關係。每個子元件 (非根元件的元件) 都會放置在其父元件之內,就像我們在第五章所討論的動態物件模型一樣。這個觀念我們用下面的例子來展示:
子元件絕對不能放在其父元件的其它子元件之內。例如,下面這段原始碼就是一個非well-formed的XML文件:
同樣地,根元件的前後標籤也都不能放在其任一子元件的內部。下面的原始碼也不是一個Well-formed的XML文件:
正如您所看到的,XML文件是非常結構化的文件,而且必須遵循嚴格但簡單的規則,以形成well-formed或valid的文件。這種結構加上特定的層級結構,產生了一個有父子元件關係的家族樹狀結構。
XML和Internet Explorer
截至目前為止,我們只介紹了XML的資料特性,而未介紹XML的顯示方式。接著您一定會有這個邏輯上的疑問,那就是該如何在網頁上顯示XML資料? Internet Explorer包含了幾個可以顯示XML資料的物件,最常被用到的就是DOMDocument 物件。這個物件基本上扮演的是資料本身與 Internet Explorer 資料顯示功能的中介角色。
當這個物件在分析一份文件時,它會在記憶體中建立文件內所有元件的樹狀結構;並提供使用XML文件物件模型 (XML Document Object Model) 的方法,也就是允許存取XML資料的介面。這個物件模型會顯露物件的性質、方法、事件、及包含在資料樹中真正的資料部份。詳細的XML 文件物件模型的性質、方法、與事件,在本章稍後的表格21-1到21-3中可找到詳細的列表。物件模型可以讓設計者檢視從根到分支的所有樹狀結構。
程式列表21-1是一個簡單的網頁,它將XML資料送進網頁中,然後將它顯示在螢幕上。圖21-1 顯示的是網頁的瀏覽結果。
程式21-1
圖 21-1 一個簡單處理XML文件的 HTML網頁
在
中的第一段Script程式碼只是一個很簡單的事件處理器,其作用是在瀏覽器載入網頁後,執行start函式的呼叫。我們等一下會解釋這個函式。注意網頁本身包含了SPAN標籤,這些標籤所定義的區域將會是最後XML資料的顯示空間。我們先前有提到,XML處理器扮演的是XML文件與HTML網頁 (或任何其它類似的顯示媒體) 的中間媒介層。我們必須先將XML處理器載入,成為網頁上的一個物件,如此我們才能利用Script語言與之溝通。
var xmlDoc=new ActiveXObject("Microsoft.XMLDOM")
xmlDoc.async=false
xmlDoc.load("email.xml")
程式碼的第一行載入一個DOMDocument物件,然後將它設定給變數xmlDoc。如此提供使用XML處理器與物件模型的權利。下一行告訴處理器不要「非同步」(Asychronously) 載入文件,這個意思是說網頁會等到文件的資料完全下載後再繼續顯示網頁;設定值若改為TRUE,則會使資料與網頁的其他部分一起下載,如此一來,就得使用一些偵測 (通常會測試ondataavailable或onreadystatechange事件,或readystate性質),來確定資料在被使用之前已載入。第三行則是讓物件載入本章一開始提到的 email.xml 這個 XML 文件。以下是資料樹狀結構一開始被載入的部份:
DOCUMENT
|---XMLDCL
| |---ATTRIBUTE version "1.0"
+---ELEMENT EMAIL
|---ELEMENT TO
| +---PCDATA "[email protected]"
此樹狀結構是以文件物件 (Document Object) 作為開端,它包含了一個XML宣告。接著是根元件EMAIL,EMAIL元件包含另一個元件,也就是上面結構圖中所顯示TO子元件,其值為[email protected],這是一個叫做 PCDATA 的XML資料型態。
一旦網頁被下載,start函式就會被呼叫。這個函式其實十分簡單,其第一行會建立一個變數稱為root,並且設定給XML文件的根節點 (root node),讓我們可以存取XML資料樹。下一行程式碼利用 innerText 性質,將一段文字設定給ID為todata 的SPAN元件,如此可以讓文字被顯示,而它使用的文字是來自XML根元件的第一個子元件。利用root.childNodes.item(0).text這一段程式碼,即可存取根元件的第一個子元件,以取得文字內容。
如果您能瞭解這段程式,您將會知道如何使用 XMLDocument 控制項來載入一份XML文件,且從XML的結構樹中取得資料。這是處理其他XML文件的基礎。
雖然上面講的文件所要花費的功夫,要比簡單且可得到相同效果的HTML文件來得多。但是,其實一些使用XML會有的優點我們可以從中瞭解,最大的優點是網頁可以變成一個範本資料。上面所建立的HTML網頁可以當作其它可以事先定義模組的XML資料的範本。雖然建立一個HTML文件會需要花一點功夫,但它可重複使用於不同的內容。舉例來說,XML文件即使被翻譯成法語、德語…等其它語言,仍然可以被插入同一個HTML文件中。
除了利用 DOMDocument 物件外,我們還可以用其它的方法可以將XML資料顯示成網頁,例如: XML資料來源物件 (Data Source Object)、XSL、和 XSL Pattern。這些技術加入了更多使用XML將資料結構化和儲存資料的好處,您可以在微軟SBN Workshop網站和隨書附贈光碟中找到更多相關資訊。在隨書CD中,要看 XML 的資料,請在XML TOC (XML Table of Contents) 中選擇任何一個主題即可。若要參考線上版本,可連上MSDN線上資源網站。下面的表格分別列出XML 文件物件模型中的各項性質。
性質 | 說明 |
async | 回傳一個布林值,決定是否允許非同步下載。當允許非同步下載時, 傳回值為TRUE;若不允許則傳回值為FALSE 。讀/寫。 |
attributes | 傳回包含目前節點元件之屬性集合物件。若節點不能有屬性值,則傳 回值為null。唯讀。 |
childNodes | 傳回包含目前節點元件之所有子節點元件的集合。若目前節點沒有任 何子節點,則傳回值為null。唯讀。 |
doctype | 傳回一個包含擁有DTD的文件型態節點。若不含DTD,則傳回值為 null。唯讀。 |
documentElement | 傳回一個包含根元件資料的物件。若沒有根元件,則傳回值為null。 唯讀。 |
firstChild | 傳回一個包含目前節點的第一個子節點元件,若目前節點沒有任何第 一子節點,則傳回值為null。唯讀。 |
implementation | 傳回DOM為目前XML文件實作所得的物件。唯讀。 |
lastChild | 傳回一個包含目前節點的最後一個子節點元件,若目前節點沒有任何 最後一個子節點,則傳回值為null。唯讀。 |
nextSibling | 傳回一個包含目前文件節點在子節點元件集合中的下一個兄弟元件, 若目前節點沒有任何兄弟節點,則傳回值為null。唯讀。 |
nodeName | 傳回一個能代表合格的目前節點(元件,屬性,或實體參考)名稱字 串,唯讀。 |
nodetype | 傳回一個數字,它分別代表目前節點的DOM型態 (元件、屬性、文 字)。唯讀。 |
enodeValu | 傳回特定節點的相關純文字資料,這不是元件的資料值,而是未剖析 相關於節點的純文字,就好像是屬性和處理指令(PI),某種資料型態 會回傳null。讀/寫。 |
ondataavaiable | 設定Ondataavaiable事件處理,只能寫。 |
onreadystatechange | 設定Onreadystatechange事件處理,只能寫。 |
ownerDocument | 傳回一個物件,它包含目前節點的文件之根節點。唯讀。 |
parentNode | 傳回包含目前節點元件之所有父節點元件的集合。若目前節點沒有任 何父節點,則傳回值為null。唯讀。 |
parseError | 傳回一個DOM剖析錯誤物件,它描述了最後的剖析錯誤。若沒有錯 誤發生,則傳回0。唯讀。 |
previousSibling | 傳回一個包含目前文件節點在子節點元件集合中的上一個兄弟元件, 若目前節點沒有任何兄弟節點,則傳回值為null。唯讀。 |
readyState | 傳回一個數字表示目前的XML文件的狀態。唯讀。值如下所示:
|
url | 傳回最後一個成功下載文件的URL。若文件只存在記憶體中(非從外 部檔案載入),則傳回null值。唯讀。 |
validateOnParse | 提示給剖析器文件是否被確認。若為True,則當被剖析時文件是被 確認的,若為false,文件並無被確認,且被期望為well-formed。 讀/寫。 |
xml | 傳回特定節點和其子孫的XML呈現。唯讀。 |
方法 | 說明 |
abort() | 若這個方法在非同步下載期間被呼叫,所有的解析動作將被 停止,在記憶體中文件的任何部分也將被拋棄。 |
appendChild(newChild) | 新增一個節點當作特定節點的最後一個節點。 |
cloneNode(deep) | 建立與一個特定節點完全相同的複製節點,deep參數是一 個布林值,若為True,則不只複製節點而且還複製其子孫元 件。若為False,只有特定節點與其屬性被複製。 |
createAttribute(name) | 建立一個特定名稱的屬性。 |
createCDATASection(data) | 建立一個CDATA區段,其中包含特定字串資料。 |
createComments(data) | 建立一個特定字串的註解。 |
createCommentFragment() | 建立一個新的文件片段,不過並不把它加入文件樹中。若要 將它加入樹中,必須利用insert方法,例如 insertBefore、 replaceChild、或appendChild。 |
createElement(tagName) | 建立一個特定標籤名稱(大小寫不同)的元件。 |
createEntinyReference | 建立一個特定名稱的實體參考,不過並不把它加入文件樹(name) 中。若要將它加入樹中,必須利用insert方法,例如 insertBefore、replaceChild、或appendChild。 |
createNode(type,name, namespaceURL) | 設定特定型態、名稱及名稱空間建立一個新的節點。 |
createProcessingInstruction(target,data) | 建立一個包含特定目標和資料的處理指令,不過並不把它加入文件樹中。 若要將它加入樹中,必須利用insert方法, 例如insertBefore、replaceChild、或 appendChild。 |
createTextnode(data) | 建立一個包含特定資料的新文字節點,不過並不把它加入 文件樹中。若要將它加入樹中,必須利用insert方法,例如 insertBefore、replaceChild、或 appendChild。 |
getElementsByTagName | 傳回一個特定名稱的元件集合。使用 "*" 的標籤名稱設定, (tagname) 則會傳回所有在文件能找到的元件。 |
haschildnodes() | 傳回True 若節點含有子元件,false則沒有。 |
InsertBefore | 在參考節點之前插入一個新子節點,newChild 參數是一個包 (newChild,refChild) 含新子節點位置的物件,而refChild是參考節點的位置。若 不包含refChild參數,新的子節點會被插入在子節點集合的 最後位置。 |
load(url) | 從特定的位置中載入指定的文件,傳回True若文件載入成 功,傳回False則為否。 |
loadXML(xml/string) | 載入一個XML文件,或從字串來的文件片段,傳回True若 文件載入成功,傳回False則為否。 nodeformID(idString) 傳回一個符合特定值的節點,若有符合的物件則傳回此物 件,若此操作失敗,傳回null值。 |
Parsed | 若目前節點和此節點的所有的子孫節點,已經被剖析及, 則傳回值為 True,若節點的任何部分都尚未被剖析,則傳 回null。 |
removeChild(child) | 從節點集合中刪除特定的子節點,child參數就是包含要被 刪除的子節點之元件。 |
replaceChild | 用提供的新節點來取代舊節點。若newChild是null,則舊 (newChild,oldChild) 節點不會被取代,而直接刪除。 |
selectNodes(patternString) | 傳回一個節點集合元件,此元件包含符合提供 XSL pattern 的節點。 |
selectSingleMode | 傳回第一節點符合所提供的XSL pattern,若沒有發現任何 (Pattern String) 符合,則傳回null。 |
TransformNode(stylesheet) | 傳回一個字串包含目前節點的處理結果和它的子節點使用 所提供的XSL樣式。 |
事件 | 說明 |
Ondataavailable | 當任何資料可用則會引發此Ondataavailable事件。這個技巧並 不提示在這個文件中有多少資料是可用。 |
Onreadystatechange | 當readystate性質改變,則會引發Onreadystatechange 事件, 但此事件並不會提供ready state的設計值,必須使用readystate 性質來取得目前狀態。 |
基礎之外的其它Scripting 技術
上面介紹的簡單 E-Mail 範例,示範了如何存取XML物件模型,以及使用XML資料。若進一步來觀察這個文件和它的script,我們將會看到簡單所帶來的代價。在我們想盡辦法讓Script簡單一些時,我們加入了一些技巧在其中,以證明script在許多情況下還是不夠理想的。請考慮下面的問題:
我們現在就來看一些script 的寫作技巧,以避免我們剛剛所講的問題。
處理XML文件樹
在上一個例子中,設計者必須知道XML文件中元件的數目和類型,但是如果能使用一些XML物件模型中的其它性質和方法,那麼事情就會簡單一些。
在這些有用的性質當中,其中有一個是childNodes,這個性質會傳回目前節點的子節點集合。ChildNodes的length性質會包含子節點的數目。因此,處理節點的子節點將會非常容易。參考如下程式碼 21-2。
程式 21-2
圖21-2 對話窗包含第一個元素的名稱與內容
現在載入HTML文件後,應該會一連串產生 5 個對話窗,每個對話窗都會包含元件在 XML 文件中的名稱與內容。第一個對話窗如圖21-2所示。處理的程式利用了root.childNodes.length性質來詢問 XML 文件,以得知根元件包含了多少個子元件。接著,script會進入一個迴圈,巡行檢視根元件的所有子元件,並取得每一個子節點的名稱 (利用nodeName性質) 和文字內容,直到最後一個節點為止。
不過,這個程式處理結構樹的第一層而已,若根元件的任何子元件也有子元件,則會被程式21-2所忽略。Inbox.xml檔案是較為複雜的文件,此文件是一個多層的樹結構。注意第一個email甚至包含一個元件(CC),這是在其它email所沒有的。
This is an example of a tree structure.
This is a simple message.
I send too many emails.
這需要更複雜的script才能得到和程式21-1相同型態的結果,也就是顯示在瀏覽器中的email內容。程式21-3從XML文件起頭開始,然後漸漸地由上往下巡行,並且顯示每一個節點的內容。如果有一個節點有子節點,其子節點將會被檢視,並且顯示其內容。圖21-3顯示執行此程式的結果。
程式 21-3
圖21-3 顯示XML文件的內容
我們一起從頭到尾來看一下這個程式。當文件被載入後,script區塊會載入XML檔案,將XML的根元件設定給變數root,接著建立空白newHTML變數,這個變數等一下在script中會用到。當文件結束載入,在BODY標籤中的onload事件處理會呼叫start函式,此函式會輪流呼叫buildtree函式,並傳送XML根元件給它。
這個例子主要運作是由buildTree函式來完成的,這是一個遞回函式,也就是說當一些特殊情況發生時,它會自己呼叫自己。這個函式基本上可以檢視目前節點,及檢查每個節點多少個子節點。若節點含有子節點,這函式會再次呼叫自己一次,傳入第一個子節點給自己。一旦下達一個沒有子節點的節點,它會加入newHTML變數及一些HTML原始碼,來描述目前節點的資料,然後往上一層回到它的父節點。藉由重複這樣的動作,它可以巡行檢視整個資料樹。接著,來深入看一個函式的片段:
var children = passedNode.childNodes.length 函式一開頭就找出目前有多少子節點,然後將值設定給children變數。下一行由一個for迴圈開始,它會針對目前節點的每一個子節點重複執行。 Node=passedNode.childNodes.item(j) "+Node.nodeName+" "+j) } 接著,目前的子節點會被設定給Node變數。由於我們希望資料中的每一個email都可以分開,因此加入了上面的第二行。若節點的名稱為 "EMAIL",則除了這個節點的名稱和號碼之外,還會加入段落標籤 到newHTML中,我們也測試是否Node.parentNode==root,因為只有在root之下的元件才有可能是EMAIL元件。 BuildTree(Node) 您應該可以注意到,使用這些技巧可以讓HTML文件更加彈性且功能更強,雖然這個文件只是一個特定類型的XML文件範本而已。但是這樣的技巧卻讓HTML與XML能完美的互相補強。 錯誤處理 ParseError性質提供了每個可能發生錯誤的程式碼。設計者可以使用這些程式做下列事情: 下面的程式碼說明parseError如何告知使用者發生問題。 Var xmlDoc = new ActiveXObject("Microsoft.xmldom"); 這個技巧可以用於除錯和讓使用者可以避開錯誤。 XML Data Island 另外也有一些負面的意見,端視如何使用data islands: 處理行內XML Data Islands
for (var j=0; j
if (Node.nodeName=="EMAIL"){ newHTML+=("
if (!Node.hasChildNodes()){
newHTML+=( "
"+Node.parentNode.nodeName+":
For迴圈的最後一行再次呼叫buildtree函數,並傳目前節點過去。最後,每個元件都呼叫了此函式。更彈性且有完整功能的XML tree 巡行程式可以在http://www.microsoft.com/gallery/sample/xml/tree_viewer/default.asp找到,請參閱Tools and Samples和XML Tree viewer部分。
XML的物件模型提供了許多方法給設計者來處理 XML 文件所產生的錯誤。其中一個是文件物件的parseError性質,它提供了XML文件發生問題時的資訊,讓設計者可以進行處理。基本上,使用者可能不會需要去處理這樣子的錯誤。
XmlDoc.load("email.xml");
If (xmlDoc.parseError.reason == " ") {
Alert("Document loaded successfully")
}
else {
alert("The Following error occurred:"+xmlDoc.parseError.errorCode)
}
在剛剛的例子,我們將XML 當作一個資料來源,並將HTML當作顯示的方法。當HTML文件被顯示時,XML 與 HTML 會被當作是兩個互相影響的獨立文件。另一個選擇是使用XML data islands。Data islands可以讓設計者在HTML文件直接包括XML片段。不過對於這個方法的效果,目前有正反兩種意見,正面的意見是:
要使用行內XML Data Islands,只要很簡單地直接插入一段XML程式碼到HTML文件就好。XML文件的內容放在
This is an XML-based email.
To:
From:
CC:
Subject:
Body:
程式 21-4
首先注意當行內XML Data包含在
處理連結XML Data Islands
要利用
此執行結果和前面例子完全相同。這個好處在於XML資料仍然與html文件分離。本質上,這個方法處理的方式十分像ActiveX Control方式,除了瀏覽器必須負責處理control及載入文件以外。
VALIDATEONPARSE 屬性指出當它被解析時,文件是否應該被確認。它的預設值為True。下面是一個例子:
ASYNC 屬性意指是否文件要非同步下載。它的預設值為True。下面是一個例子:
XML是否為所有發生在web上問題的解決方案?或者只是個誇大的宣傳?事實上都不是。XML已經獲得不同領域的業界所支援,而不僅僅是在Web 上而已。這表示會有許多承諾,特別是在不同的瀏覽器都能支援XML的情況下。本章只是提供十分簡單的 XML 概略說明,您可以在W3C網站,及一本不錯的XML參考手冊( XML in action by William J. Pardi Microsoft Press) 中找到更多XML的資訊。