用 SAXEcho 驗證
現在來看看驗證。注釋掉 XML 文檔中 Life as a House dvd 的 price,再看看結果如何,分別使用 DTD 和 XSD 文件進行驗證。清單 6 顯示了輸出結果。
清單那 6. 執行 SAXEcho 的輸出
=== Parsing catalogDTD.XML ===
<catalog><dvd><title>Terminator 2</title><description>
A shape-shifting cyborg is sent back from the future to kill the leader of the resistance.
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
</description><price>19.95</price><year>1991</year></dvd><dvd><title>The Matrix</title><price>10.95</price><year>1999</year></dvd><dvd><title>Life as a House</title><description>
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
When a man is diagnosed with terminal cancer,
he takes custody of his misanthropic teenage son.
</description><year>2001</year>
*** Failed validation ***
* The content of element type "dvd" must match "(title,description?,price,year)".
*************************
</dvd><dvd><title>Raiders of the Lost Ark</title><price>14.95</price><year>1981</year></dvd></catalog>
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
=== Parsing catalogXSD.XML ===
<catalog>
<dvd>
<title>Terminator 2</title>
<description>
A shape-shifting cyborg is sent back from the future to kill the leader of the resistance.
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
</description>
<price>19.95</price>
<year>1991</year>
</dvd>
<dvd>
<title>The Matrix</title>
<price>10.95</price>
<year>1999</year>
</dvd>
<dvd>
<title>Life as a House</title>
<description>
When a man is diagnosed with terminal cancer,
he takes custody of his misanthropic teenage son.
</description>
*** Failed validation ***
* cvc-complex-type.2.4.a: Invalid content was found starting with element 'year'. One of '{"":price}' is expected.
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
*************************
<year>2001</year>
</dvd>
<dvd>
<title>Raiders of the Lost Ark</title>
<price>14.95</price>
<year>1981</year>
</dvd>
</catalog>
使用 XML 模式驗證
您也許會奇怪,既然可以用 DTD 保證文檔結構和內容的有效性,為何需要驗證文檔的其他 方式呢?下面給出幾個理由:
對元素和屬性值的控制粒度:XML Schema 允許指定格式、長度和數據類型。
復雜數據類型:XML Schema 支持從已有類型創建新的數據類型和規范。
元素頻次:使用 XML Schema,可以在更細的粒度上控制元素。
名稱空間:XML Schema 使用名稱空間,名稱空間對於和其他組織打交道的組織來說越來越重要。
XML Schema 語言比 DTD 語言更強大,因此也更復雜。好的方面是 XML Schemas 用 XML 編寫,而 DTD 不是。
我們來驗證一下 DTD 驗證所用的同一個 XML 實例文檔(如 清單 1 所示)。清單 7 顯示了 XML Schema:
清單 7. Catalog XML Schema
<?XML version="1.0" encoding="UTF-8"?>
<xs:schema elementFormDefault="qualifIEd" XML:lang="EN"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- Our DVD catalog contains four or more DVDs -->
<xs:element name="catalog">
<xs:complexType>
<xs:sequence minOccurs="4" maxOccurs="unbounded">
<xs:element ref="dvd"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<!-- DVDs have a title, an optional description, a price, and a release year -->
<xs:element name="dvd">
<xs:complexType>
<xs:sequence>
<xs:element name="title" type="xs:string"/>
<xs:element name="description" type="descriptionString"
minOccurs="0"/>
<xs:element name="price" type="priceValue"/>
<xs:element name="year" type="yearString"/>
</xs:sequence>
<xs:attribute name="code" type="xs:ID"/> <!-- requires a unique ID -->
<xs:attribute name="genre"> <!-- default = optional -->
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="Drama"/>
<xs:enumeration value="Comedy"/>
<xs:enumeration value="SciFi"/>
<xs:enumeration value="Action"/>
<xs:enumeration value="Romance"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
<!-- Descriptions must be between 10 and 120 characters long -->
<xs:simpleType name="descriptionString">
<xs:restriction base="xs:string">
<xs:minLength value="10"/>
<xs:maxLength value="120"/>
</xs:restriction>
</xs:simpleType>
<!-- Price must be < 100.00 -->
<xs:simpleType name="priceValue">
<xs:restriction base="xs:decimal">
<xs:totalDigits value="4"/>
<xs:fractionDigits value="2"/>
<xs:maxExclusive value="100.00"/>
</xs:restriction>
</xs:simpleType>
<!-- Year must be 4 digits, between 1900 and 2099 -->
<xs:simpleType name="yearString">
<xs:restriction base="xs:string">
<xs:pattern value="(19|20)dd"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>
要注意,XML Schema 與對應的 DTD 相比要長得多。事實上,即便去掉注釋和空白,這個模式仍然超過了 50 行,而 DTD 模式只有九行。(當然,這個模式檢查的細節要比 DTD 多。)因此,控制的粒度越精細,代碼也越復雜,而且復雜得多。這意味著,如果驗證不需要 XML Schema,則使用 DTD。
除了和前面 DTD 中所用的可比較的約束以外,看看 XML Schemas 增加了什麼以及為 DVD catalog 文檔帶來了哪些好處:
對元素和屬性值控制的粒度大小:和允許任何字符值的 DTD 不同,XSD 約束了 descriptions(20 到 120 個字符)、prices(0.00 到 100.00)和 years(1900 到 2999)的值。
復雜數據類型: 創建了將來可重用、可擴展的新數據類型:dvd、descriptionString、priceValue 和 yearString。
元素出現頻次:因為本教程使用的例子文檔很小,我把 DVD 的數量設置為四個或更多以便使該文檔有效。實際上,最小值可能是一個很大的數,不過通過這個例子可以看到這種約束是可能的。
名稱空間:僅對 XML Schema 類型使用了名稱空間,但是由於 XML Schemas 是名稱空間感知的,因而可以增加更多名稱空間來控制名稱沖突。
我們更詳細地討論一下 XML Schema 以便理解其內容:
xs:complexType 和 xs:simpleType。complexType 元素可以包含其他元素或屬性:
<xs:element name="dvd">
<xs:complexType>
<xs:sequence>
<xs:element name="title" type="xs:string"/>
...
simpleType 元素只能包含文本和它自己的屬性值:
<xs:simpleType name="yearString">
<xs:restriction base="xs:string">
<xs:pattern value="(19|20)dd"/>
</xs:restriction>
</xs:simpleType>
這個具體的例子中定義了一種新類型 yearString,它必須包含四位數字,並且最高兩位是 “19” 或 “20”。使用 xs:restriction 元素從已有的(基)類型派生新的、受限制的類型。使用 xs:pattern 刻面元素來比較值,檢查是否和指定的表達式匹配(請參閱 刻面)。
xs:sequence. 孩子元素必須按照所列順序出現(雖然 minOccurs 可以把元素變成可選的,如前所述):
<xs:sequence>
<xs:element name="title" type="xs:string"/>
<xs:element name="description" type="descriptionString" minOccurs="0"/>
<xs:element name="price" type="priceValue"/>
<xs:element name="year" type="yearString"/>
</xs:sequence>
sequence 聲明,有效文檔中的 dvd 必須有一個 title,後面可以跟 10 到 120 個字符長的 description,後跟小於 US$100 形如 “nn.nn” 的 price,最後是 year。
注意:XML Schemas 驗證需要 XMLBuddy Pro。
現在做一些修改看看約束是否生效了。 為 Adventure 增加 genre,輸入長度超過 120 字符的 description、設置重復的 dvd code(如 圖 9 所示)。
圖 9. XSD 錯誤
可以發現 genre、惟一的 ID 和 description 長度都是強制性的。
XML Schema 還能做更多。下面強調幾點:
xs:choice:必須出現其中的一個孩子。
xs:all:列出的每個孩子都必須出現一次,但是對順序沒有要求。
xs:group:可以定義和引用一組元素的組名(通過 ref=groupName)。
xs:attributeGroup:和用於元素的 xs:group 一樣,這個相對應的指示符用於屬性。
xs:date:這是 ISO 8601 定義的格裡高利歷法日期,格式為 YYYY-MM-DD。
xs:time:hh:mm:ss 形式的時間,用 "Z" 表示 UTC 相對時間。
xs:duration:一定數量的年、月、日、時、分。
可以看到,編寫 XML Schema 時可以利用很多內建的強大功能。如果找不到需要的類型,可以創建新的類型。
數據類型
XML Schema 的一個強大特性是能夠創建新的數據類型。在 catalog.xsd 文件中可以看到使用了大量新建數據類型,包括 yearString 和 priceValue 類型。在該例中,這些類型只在 dvd 類型中使用,但是可以文檔中出現 year 或 price 的任何地方使用。
這些類型擴展原有的小數和字符串類型:
<!-- Price must be < 100.00 -->
<xs:simpleType name="priceValue">
<xs:restriction base="xs:decimal">
<xs:totalDigits value="4"/>
<xs:fractionDigits value="2"/>
<xs:maxExclusive value="100.00"/>
</xs:restriction>
</xs:simpleType>
<!-- Year must be 4 digits, between 1900 and 2099 -->
<xs:simpleType name="yearString">
<xs:restriction base="xs:string">
<xs:pattern value="(19|20)dd"/>
</xs:restriction>
</xs:simpleType>
如前所述,可以結合使用 restriction 元素和一種或更多刻面來實現已有類型的特化。如果有多個刻面,可結合起來確定哪些值有效,哪些值無效。
范式匹配
pattern 刻面元素支持一種類似 Perl 的豐富的表達式語法。前面在 yearString 中用到了它,范式 “ (19|20)dd” 應讀作 “字符串必須以 19 或 20 開始並且後跟兩位數字”。表 1 列出了幾個范式。
表 1. XML Schema 范式匹配表達式
范式 匹配 (A|B) 匹配 A 或 B 的字符串 A? 和 A 匹配的字符串出現零次或一次 A* 和 A 匹配的字符串出現零次或多次 A+ 和 A 匹配的字符串出現一次或多次 [abcd] 和指定字符之一匹配的單個字符 [^abc] 和指定字符之外的字符匹配的單個字符 t 制表符 反斜槓 c XML 名稱字符 s 空格、制表符、回車換行或者新行字符 . 回車換行或新行字符外的任何字符
更多表達式請參閱 XML in a Nutshell, Third Edition 的第 427-429 頁或者閱讀 XML Bible, Second Edition 在線版(請參閱 參考資料)第 24 章的表 24-5。
XSD 異常處理
為了處理 XML Schema 操作異常,必須啟用驗證。對於 Xerces 只要將模式驗證特性設置為 true:
parser.setFeature(
"http://apache.org/XML/features/validation/schema",
true );
可以通過 apache Software Foundation 網站(請參閱 參考資料)了解 Xcerse 解析器的各種特性。
前面討論過由於操縱問題可能造成的 DOMException。DOMException 的 code 表明發生了什麼類型的問題。
回顧 DOMEcho
改變 DOMEcho.Java 邏輯引發一個 DOMException。新的邏輯如下:
//---- find parent Node
Element indianaJones = document.getElementById("_7755522");
//---- insert a description before the price
// (anywhere else would be invalid)
NodeList years = indianaJones.getElementsByTagName("price");
Node desc = document.createTextNode(
"Indiana Jones is hired to find the Ark of the Covenant");
// This change will now fail validation.
indianaJones.insertBefore( desc, indianaJones );
從而執行下列代碼:
short code = e.code;
...
} else if( code == DOMException.NOT_FOUND_ERR ) {
//take action when element or attribute not found
echo( "*** Element not found" );
System.exit(code);
}
關於 XML Schemas 驗證的更多信息,請閱讀 XML in a Nutshell, Third Edition 的第 17 章、W3Schools 或者 “Interactive XML tutorials”(請參閱 參考資料)。
使用 XQuery
XML Query(XQuery)是一種用於編寫表達式的語言,表達式從 XML 數據(通常是數據庫)中返回匹配的結果。其功能類似於操作非 XML 內容的 SQL:
“與 SQL 相似,XQuery 包括從多個數據集中提取、匯總、聚合和連接數據的功能。”
—— “Java theory and practice: Screen-scraping with XQuery”,Brian Goetz(請參閱 參考資料)
XQuery 擴展了 XPath 表達式,後者將在本系列教程的第四部分 XML 轉換 中詳細討論。XPath 表達式也是有效的 XQuery 表達式。那麼為什麼要用 XQuery 呢?XQuery 的價值在於它在表達式中增加的子句,從而能夠實現和 SQL 中的 SELECT 語句功能類似的更復雜的表達式。
XQuery 子句
XQuery 包含多個子句,用縮寫詞 FLWOR 表示:for、let、where、order by、return。表 2 說明了各個部分。
表 2. FLWOR 子句
子句 說明 for 使用這種循環結構將值賦給其他子句中使用的變量。變量使用美元符號聲明,比如 $name,然後從搜索結果中獲得賦給它們的值。 let 使用 let 將值賦給 for 以外的變量。 where 和 SQL 相似,使用 where 子句根據某種條件對結果進行篩選。 order by 使用該子句確定如何對結果集進行排序(ascending 或 descending)。 return 使用 return 子句確定查詢要輸出的內容。內容可以包括文字、XML 文檔內容、Html 標記或者其他任何東西。
XQuery 包含由結果為 true 或 false 的條件組成 FLWOR 子句中的檢索條件。下面看一些例子。可用 清單 8 中所示的 dvd.xml 作為 XML 實例文檔。
清單 8. dvd.XML
<?XML version="1.0"?>
<!-- DVD inventory -->
<catalog>
<dvd code="1234567">
<title>Terminator 2</title>
<price>19.95</price>
<year>1991</year>
</dvd>
<dvd code="7654321">
<title>The Matrix</title>
<price>12.95</price>
<year>1999</year>
</dvd>
<dvd code="2255577">
<title>Life as a House</title>
<price>15.95</price>
<year>2001</year>
</dvd>
<dvd code="7755522">
<title>Raiders of the Lost Ark</title>
<price>14.95</price>
<year>1981</year>
</dvd>
</catalog>
為了試驗,我使用了 Saxon XQuery 工具。所有文件都放到解壓 Saxon 的目錄中。為了使用 XQuery 創建一個 Html 頁面按升序列出所有 DVD 的標題,我使用 清單 9 中所示的 dvdTitles.xq 文件,其中也顯示了輸出結果。執行該查詢使用了下面的命令:
Java -cp saxon8.jar net.sf.saxon.Query -t dvdTitles.xq > dvdTitles.Html
清單 9. 按升序列出 DVD 標題的 XQuery
dvdTitles.xq:
<Html>
<body>
Available DVDs:
<br/>
<ol>
{
for $title in doc("dvd.XML")/catalog/dvd/title
order by $title
return <li>{data($title)}</li>
}
</ol>
</body>
</Html>
dvdTitles.Html:
<?XML version="1.0" encoding="UTF-8"?>
<Html>
<body>
Available DVDs:
<br/>
<ol>
<li>Life as a House</li>
<li>Raiders of the Lost Ark</li>
<li>Terminator 2</li>
<li>The Matrix</li>
</ol>
</body>
</Html>
仔細觀察 清單 9 中的 XQuery 邏輯。首先,查詢必須用花括號(“{}”)包圍起來。可以看到,該例中使用了三個子句(for、order by 和 return)。使用 doc() 函數打開一個 XML 文檔。$title 是一個變量,在循環中設置為每個搜索結果。具體到該例中,它表示 /catalog/dvd/title 表達式的結果,即 DVD 的標題。返回子句中的 data() 函數輸出 XML 中的值而不包含標記。如果僅僅使用 $title,就會得到 “<title>value</title>”,這不是希望出現在 HTML 輸出中的結果。要注意 XQuery 包圍在完成該網頁所需要的全部 Html 代碼中。
現在,假設需要按降序輸出超過 15 美元的 DVD 的價格。清單 10 顯示了 XQuery 及輸出。
清單 10. 按降序輸出超過 15 美元的 DVD 價格
dvdPriceThreshold.hq
<Html>
<body>
DVDs prices below $15.00:
<br/>
<ol>
{
for $price in doc("dvd.XML")/catalog/dvd/price
where $price < 15.00
order by $price descending
return <li>{data($price)}</li>
}
</ol>
</body>
</Html>
dvdPrices.Html
<?XML version="1.0" encoding="UTF-8"?>
<Html>
<body>
DVDs prices below $15.00:
<br/>
<ol>
<li>14.95</li>
<li>12.95</li>
</ol>
</body>
</Html>
該查詢中主要的區別是指定了 where 子句。僅僅為了好玩一點兒而改變了排列順序。
顯然,還可以做很多實驗來學習 XQuery 的強大功能,不過我已經介紹得夠多了。如果想進一步了解,請參閱 “XQuery” 和 “Five Practical XQuery Applications”(請參閱 參考資料)。
結束語
XML 的核心是解析和驗證。了解如何充分利用這些功能對於在項目中能否成功地引入 XML 至關重要。
結束語
本教程介紹了 XML 處理,主要涉及:
使用 SAX2 和 DOM2 解析器解析 XML 文檔
用 DTD 和 XML Schemas 驗證 XML 文檔
使用 XQuery 從數據庫中訪問 XML 內容