亚洲欧美aⅴ在线资源,中文字幕亚洲专区,国产大陆亚洲精品国产http://m.tkk7.com/jackybu/category/837.html<a ><b><font color=red>共有<script src=http://fastonlineusers.com/online.php?d=jackybu.blogjava.net></script>人在同時閱讀此Blog</font></b></a>zh-cnWed, 28 Feb 2007 04:12:08 GMTWed, 28 Feb 2007 04:12:08 GMT60XML查詢語言http://m.tkk7.com/jackybu/articles/6156.htmlWed, 15 Jun 2005 05:26:00 GMThttp://m.tkk7.com/jackybu/articles/6156.htmlhttp://m.tkk7.com/jackybu/comments/6156.htmlhttp://m.tkk7.com/jackybu/articles/6156.html#Feedback0http://m.tkk7.com/jackybu/comments/commentRss/6156.htmlhttp://m.tkk7.com/jackybu/services/trackbacks/6156.html作者: Jonathan Robie等 譯者: 王緒勝 寫作日期:2000-4-1
Jonathan Robie, Texcel, Inc.
Joe Lapp, webMethods, Inc.
David Schach, Microsoft Corporation
合作:
Michael Hyman, Microsoft Corporation
Jonathan Marsh, Microsoft Corporation

摘要:
XML查詢語言(XQL) 是用于定位和過濾XML文檔中元素和文本的符號。它是XSL模式語法的自然擴展,為指向特定的元素或查找具有指定特征的節點提供了簡明的可以理解的符號。該建議于1998年9月提交給XSL工作小組(http://www.w3.org/Style/XSL/Group/1998/09/XQL-proposal.html),在考慮XSL模式語法的擴展時作為基礎輸入。相關文檔查詢和轉換XML說明了將本建議中的XML查詢和轉換語言基于XSL轉換語言和模式語言擴展的優點。
------------------------------------------------------------------------
 
目錄:
1. 導言
2. XML模式
2.1. 上下文
2.2. 返回結果
2.3. 集合
2.4. 查詢子女及后代
2.5. 搜集元素子女
2.6. 查找屬性
2.7. 分組
2.8. 過濾
2.9. 布爾表達式
2.9.1. 布爾和與布爾或
2.9.2. 布爾非
2.10. 等值
2.10.1. 比較和向量
2.10.2. 比較和文字
2.10.3. 比較過程中文字轉化
2.11. 方法
2.11.1. 信息方法
2.11.1.1. text()
2.11.2. 集合索引函數
2.11.3. 簡化
2.12. 集合中索引
2.12.1. 查找集合中最末元素
3. XQL擴展
3.1. 命名空間
3.1.1. 命名空間方法
3.2. 查找屬性集合
3.3. 比較
3.3.1. 數據類型
3.3.2. 類型轉換函數
3.4. 任一(any)和所有(all)語義
3.5. 并和交
3.6. 集合方法
3.6.1. 文檔根部的DOM節點
3.7. 聚集函數
3.8. 附加方法
3.9. 祖先
3.10. 范圍操作符
Appendix A. XQL巴克斯范式
A.1. 符號說明
A.2. 術語
A.2.1. 值
A.2.2. 節點
A.2.3. 源節點
A.2.4. 文檔順序
A.2.5. 索引
A.2.6. 查找上下文
A.2.7. 參考節點
A.2.8. 參考節點集合
A.2.9. 查找上下文集合
A.2.10. 模式匹配
A.3. 查詢條件
A.3.1. 條件產生規則
A.3.2. 數字
A.3.3. 文本
A.3.4. 根
A.3.5. 點
A.3.6. 元素名稱
A.3.7. 屬性名稱
A.3.8. 調用
A.3.8.1. 參數
A.3.8.2. 函數
A.3.8.2.1. ancestor()
A.3.8.2.2. attribute()
A.3.8.2.3. comment()
A.3.8.2.4. date()
A.3.8.2.5. element()
A.3.8.2.6. id()
A.3.8.2.7. node()
A.3.8.2.8. pi()
A.3.8.2.9. textNode()
A.3.8.2.10. true(), false()
A.3.8.3. 方法
A.3.8.3.1. baseName()
A.3.8.3.2. count()
A.3.8.3.3. end()
A.3.8.3.4. index()
A.3.8.3.5. namespace()
A.3.8.3.6. nodeName()
A.3.8.3.7. nodeType()
A.3.8.3.8. nodeTypeString()
A.3.8.3.9. prefix()
A.3.8.3.10. text()
A.3.8.3.11. rawText()
A.3.8.3.12. value()
A.4. 查詢操作符
A.4.1. 操作符優先級
A.4.2. 操作符產生規則
A.4.3. 分離
A.4.4. 關聯
A.4.5. 否定
A.4.6. 交
A.4.7. 并
A.4.8. 比較
A.4.8.1. 操作數值-左手接收右手值
A.4.8.2. 關系
A.4.8.3. 類型轉換
A.4.8.4. 排序順序
A.4.8.5. 比較操作的值
A.4.9. 路徑
A.4.9.1. 一元子女操作符
A.4.9.2. 一元后代操作符
A.4.9.3. 二元子女操作符
A.4.9.4. 二元后代操作符
A.4.9.5. 路徑操作的值
A.4.10. Bang操作符
A.4.11. 子界
A.4.12. 過濾
A.4.13. 分組
A.5. 查詢
A.6. 語法限制
Appendix B. 示例數據
------------------------------------------------------------------------


1. 導言

XSL 模式語言( http://www.w3.org/TR/WD-xsl, section 2.6) 提供了易于理解的方式描述待處理節點。它是說明性而不是過程性語言,只需使用類目錄結構的簡單模式描述需要查找節點的類型。例如,book/author表示查找包含在book元素中的author元素。
XQL(XML查詢語言)提供對XSL模式語言的自然擴展。在XSL表示類型節點的基礎上增加了布爾邏輯,過濾,節點集合索引等。
XQL是特定為XML文檔設計,它是一種通用查詢語言,提供用于查詢,定位和模式的簡單語法。XQL精煉,簡單,具有強大的功能。
請注意術語XQL是本建議中對查詢語言的工作術語,并不意味著我們準備永久使用該術語。

對于論證XQL及其理論背景的優秀論文,請參照Jonathan Robie的論文The Design of XQL。

以下是XQL的設計目標:
XQL字符串必須緊湊;
XQL應該易于打字和閱讀;
XQL語法應簡單,以便用于簡單和通常情況;
XQL應該用易于嵌入程序,腳本和XML或HTML屬性的字符串表述;
XQL應易于分析;
XQL應該用嵌入URL的字符串表述;
XQL應能夠指定XML文檔中可能的任何路徑和指定任何條件集合以查找路徑上的節點;
XQL應能唯一指定XML文檔中的所有節點;
XQL查詢可以返回任何數量的結果,包括0;
XQL查詢是說明性,而非過程性。即:說明查找什么,而不是如何查找。這一點尤為重要,因為查詢優化器必須能夠自由選擇索引和其他結構以便高效地找到結果。
XQL查詢可以在文檔的任何級別開始計算,而不必從文檔根元素開始導航;
XQL查詢按照文檔順序,無重復地返回結果。
盡管XQL是XSL模式語言的超集,XQL設計用于許多情況,可以用于提供節點的鏈接,查找信息和許多其他應用。
XQL是從文檔中查詢信息的符號,這些信息可能是一個節點集合、節點關系信息或導出值。本說明書沒有指定輸出的格式,查詢結果可能是單個節點、節點列表、XML文檔、其他結構序列。即:XQL并不指定返回的二進制格式,而是邏輯結果。
在一些實現中,查詢結果可能是一個XML文檔或者是傳回XQL的樹。在其他情況下,結果可能是其他類型的結構,例如:指向節點指針的集合。因此,封閉性并不能保證,如果實現方案返回XML文檔,則該文檔必須格式良好,從而保證了封閉性。


2. XML模式
本節描述XQL核心符號。所有的XQL實現必須滿足這些特征,而且在不同技術使用中作為基本功能。
XQL基本語法模仿URI目錄導航語法,但不是通過物理文件結構指定導航,而是通過XML樹的元素。
例如,下例URI表示在bar目錄中查找foo.jpg文件:
bar/foo.jpg
類似地,在XQL中,下例表示查找在baz元素中查找fuz元素集合:
baz/fuz
在整篇文章中,涉及許多示例,它們都參照附錄Sample Data中的數據。

2.1. 上下文
“上下文”是當前查詢操作的節點集合。該術語在附錄A中正式定義。要理解上下文的概念,考慮包含多個節點的樹。從根節點開始查找所有命名為“X”的節點將返回結果集合,從樹枝開始查找所有命名為“X”的節點將返回不同的結果集合。因此,查詢結果取決于執行時的上下文。有許多不同方法可以用于指定查詢的輸入上下文。
XQL允許查詢在當前上下文和“根上下文”選擇作為輸入上下文。“根上下文”是一個包含文檔中最根部元素的上下文。缺省地,查詢使用當前上下文,以“/”(前傾斜)作為前綴的查詢使用根上下文。查詢可以隨意使用“./”(點,前傾斜)作為前綴顯式說明使用當前上下文。這兩個符號都是文件系統導航符號的模擬。“./”前綴只在一種情況下必須存在。使用“//”操作符表示遞歸后代。當該操作符出現在查詢開頭時,初始的“/”引起相對于文檔或存儲根部的遞歸后代執行。前綴
“.//
”允許查詢當前上下文的遞歸后代。
示例:
在當前上下文中查找所有author元素。由于句點不單獨使用,該例向前應用了其他特征

./author
請注意,該例等價于:
author
查找文檔中的根元素(bookstore):
/bookstore
在當前文檔中查找所有出現在任何地方的author元素:
//author
在文檔根部查找所有book元素的style屬性值與bookstore元素specialty屬性值相等的所有book元素。
book[/bookstore/@specialty = @style]

2.2. 返回結果
XQL表達式返回的集合在定義范圍內保持文檔順序,層次結構和標識。即:元素集合總是無重復地以文檔順序返回,屬性集合將無重復地返回,但由于屬性無順序地定義,返回集合并不意味著順序。

2.3. 集合

帶有特定標記名稱的所有元素集合用標記名稱自身表示。元素查詢可以被限定在當前上下文“./”,但當前上下文是認定的,通常無需顯式指定。
示例:
查找所有first-name元素。下列例子是等價的:
./first-name
first-name
查找所有未限定的book元素:
book
查找所有first.name元素:
first.name


2.4. 查詢子女和后代

某些類型的元素集合可以用路徑操作符(“/”或“//”)指定。路徑操作符將左部作為查詢來源的參數集合,右部表示需要查詢的元素。子女操作符(“/”)查詢左部元素集合的直接子女集合,后代操作符(“//”)查詢左部元素集合的任意后代集合。事實上,“//”可以被視為一個或者多個層次結構的替代方案。注意路徑操作符在查詢過程中改變上下文。通過將其串在一起,用戶可以“深鉆”進入文檔內部。
示例:
在author元素中查找所有first-name元素。首先查找當前環境的author子女,然后查找相對于author元素環境的first-name子女:
author/first-name
在bookstore元素一級或多級以下(任意后代),查找所有title元素:
bookstore//title
注意,該例與下述查找所有bookstore元素的孫子title元素的查詢并不相同:
bookstore/*/title
查找在book/excerpt元素中任意位置的emph元素,而且book/excerpt元素在
bookstore元素中任意位置:
bookstore//book/excerpt//emph
在當前環境一級或多級以下查找所有title元素。注意該情況本來是要求句點符號的唯一情況:
.//title


2.5. 搜集元素子女

可以不用標記名稱而用“*”集合引用一個元素。“*”集合不管子女元素的標記名稱,返回當前環境的所有子女元素。
示例:
查找author元素的所有子女元素:
author/*
查找book元素的所有last-name孫子元素:
book/*/last-name
查找當前環境的所有孫子元素:
*/*
查找具有指定屬性的所有元素。注意該例使用了在過濾一節涉及的子查詢和在查找屬性一節討論的屬性:
*[@specialty]


2.6. 查找屬性

屬性名稱用“@”符號開頭。XQL設計公平對待屬性和子元素,在可能的情況下,兩種類型的能力是等價的。
注意:屬性不能包含子元素。因此,在查詢中屬性不能使用路徑操作符。否則將導致語法錯誤。同樣地,屬性天生沒有順序,索引不能用于屬性。
示例:
查找當前環境的style屬性:
@style
在當前環境中查找price元素的exchange屬性:
price/@exchange
下例不是有效的查詢:
price/@exchange/total
查找所有包含style 屬性的book元素。注意該例使用了在過濾一節涉及的子查詢:

book[@style]
查找所有book元素的style屬性:
book/@style


2.7. 分組

為使操作清晰清晰或者常用的優先級不足以表示某個操作時,圓括號可以用于將集合操作符分組。


2.8. 過濾

通過對集合增加過濾子句“[ ]”,可以對任何集合進行限制和分枝。過濾是SQL中包含ANY語義的WHERE子句的模擬。過濾子句中包含了一個查詢,稱為子查詢。子查詢計算出布爾值,對集合中的每一個元素進行測試。集合中未通過子查詢測試的元素將從結果集合中省略。
為方便起見,如果集合放于過濾之中,如果集合中包含元素,則產生布爾值TRUE;如果集合為空,則產生FALSE。本質上,如author/degree的表達式表示集合到布爾值的轉換函數,象如下虛構的“存在一個”方法:
author[.there-exists-a(degree)]
注意,在表達式的給定級別上可以出現任何數目的過濾,但空過濾是不允許的。
示例:
查找至少包含一個excerpt元素的book元素:
book[excerpt]
查找至少包含一個excerpt元素的book元素的所有title:
book[excerpt]/title
查找至少包含一個excerpt元素的book元素的author,而且author元素至少包含一
個deg
ree元素:
book[excerpt]/author[degree]
查找包含author元素的book元素,而且author元素至少包含一個degree元素:
查找包含excerpt和title的所有book元素:


2.9. 布爾表達式

布爾表達式可以在子查詢中使用。例如,可以使用布爾表達式查找特定值的節點集合,或者特定范圍的節點集合。布爾表達式采取${op}$的形式,而{op}可以是任何{b|a}形式的表達式-即:操作符接收左值和右值參數,返回布爾結果。應用可以根據需要提供附加的布爾操作符,但實現結果令人氣餒。注意XQL表達式一節定義了附加的布爾操作符。操作符是大小寫敏感的。

2.9.1. 布爾與和布爾或

$and$和$or$用于執行布爾與和布爾或。
布爾操作符,和分組括號結合起來,可以用于構成非常復雜的邏輯表達式。
注意空格是無意義的,可以被省略,或者如下所示包含空格,用于增加可讀性。
示例:
查找至少包含一個degree元素和一個award元素的author元素:
author[degree $and$ award]
查找至少包含一個degree元素或者award元素,而且包含至少一個publication元素的author元素:
author[(degree $or$ award) $and$ publication]

2.9.2. 布爾非

$not$是用于子查詢中表達式值求反的布爾操作符。
示例:
查找至少包含一個degree元素,而且不包含publication元素的author元素:
author[degree $and$ $not$ publication]
查找包含publication元素,而且不包含degree元素和award元素的author元素:
author[$not$ (degree $or$ award) $and$ publication]

2.10. 等值

“=” 符號用于相等判斷,“!=”用于不相等判斷。作為選擇,$eq$和$ne$ 也可用于相等和不相等。單引號或雙引號可以用于在表達式中分隔字符串,使得在腳本語言中創建或傳遞XQL變得更為容易。
對于比較元素值而言,暗含了value()方法。也就是說,last-name < ‘foo’實際
上表示last-name!value() < foo 。
請注意,過濾總是和上下文相關的。即:表達式book[author]表示對每一個找到的book元素,查看其是否包含author子元素。同樣地,book[author = Bob ] 表示對每一個找到的book元素,查看其是否包含命名為author,且值為’Bob’的子元素。你也可以通過使用“.”(句點) 查看當前上下文的值。例如,book[. = Trenton ]表示對每一個找到的book元素,查看其值是否等于’ Trenton’。
示例:
查找last name等于Bob的所有author:
author[last-name = Bob ]
author[last-name $eq$ Bob ]
查找from屬性不等于 Harvard 的所有author:
degree[@from != Harvard ]
degree[@from $ne$ Harvard ]
查找last-name 與/guest/last-name 相同的所有author:
author[last-name = /guest/last-name]
查找文本為’ Matthew Bob’ 的所有author:
author[. = Matthew Bob ]

2.10.1. 比較與向量

比較的左值可以是一個向量或數量。而比較的右值必須是一個數量或者一個可以在運行時轉換為數量的值。
如果比較的左值是一個集合,那么any(exists)語義用于比較操作符。也就是說,如果集合中的任何元素滿足條件,則比較結果為真。

2.10.2. 比較與文字常量

表達式的左值不能是文字常量。也就是說,’1’=a是不允許的。

2.10.3. 比較過程的文字常量轉換

所有元素和屬性都是字符串,但是許多時候要求數字比較。
如果右值是一個屬性,text(lvalue)與text(rvalue)進行比較。
如果右值是一個文字常量,則運用下列規則:
常量類型 比較 示例
字符串
text(lvalue) op text(rvalue)
a < foo
整數
(long) lvalue op (long) rvalue
a < 3
實數
(double) lvalue op (double) rvalue
a < 3.1
不支持指數符號。
當元素包含類型時,參看數據類型 關于類型轉換的討論。


2.11. Methods

XQL為高級集合操作提供方法。這些方法提供節點的特定集合(查看集合方法),同時提供關于集合和節點的信息。
方法的形式為{方法}(參數表)。
考慮查詢book[author]。它將發現包含author元素的所有book。一般來講,我們稱對應于特定author的book元素為該author的參考節點。也就是說,每一個所檢查的author元素都是其中一個book元素的author(關于參考節點和其他術語的更為徹底的定義,請查看
Annotated XQL BNF附錄)。方法應用于參考節點。
例如:text()方法返回節點內包含的文字,不包括所有結構(也就是說,是包含在元素及其后代的所有文字節點的連接)。下列表達式將返回所有名為’Bob’的author:
author[text() = Bob ]
下例將返回包含first-name子節點,且該子節點的文字為’Bob’的所有author:
author[first-name!text() = Bob ]
下例將返回包含名為’Bob’的子節點的所有author:
author[*!text() = Bob ]
方法名稱是大小寫敏感的。


2.11.1. 信息方法

下列方法提供關于集合內節點的信息。這些方法返回字符串或數字,而且可以用于連接子查詢內的比較操作符。
text()
包含在元素內的文字。該方法連接所有后代節點的文字,不包括標記名稱或屬性值,注釋等。而且將如text()中討論一樣整理字符串。(返回字符串)
value()
返回一個元素值的類型轉換版本(參看Datatypes)。如果數據類型不支持或者該數據類型不提供,返回結果與text()相同。
nodeType()
返回一個指示節點類型的數字:
元素 1
屬性 2
文字 3
處理指令 7
注釋 8
文檔 9
nodeName()
節點的標記名稱,包括命名空間前綴(返回字符串)。

2.11.1.1. text()

text()方法連接所有節點后代的文字,根據節點屬性可選地規范化空白空間。如果節點或者最近的祖先節點的xml:space屬性設為’preserve’,空白空間將被保留。當空白空間規范化時,將在整個字符串范圍內進行。空格用于分隔節點間的文字。當實體引用在文檔中使用時,擴展時空格不出現在實體引用的周圍。
示例:
查找last name為’Bob’的author:
author[last-name!text() = Bob ]
該查詢等價于:
author[last-name = Bob ]
查找值為 Matthew Bob 的author:
author[text() = Matthew Bob ]
author[. = Matthew Bob ]

2.11.2. 集合索引函數

index() 返回父節點內該節點的索引號。索引號是從0開始的,所以0是第一個元素(返回一個數字) 。
查找前三個degree:
degree[index() $lt$ 3]
注意索引函數與父節點相關。考慮下列數據:
<x>
<y/>
<y/>
</x>
<x>
<y/>
<y/>
</x>
下列表達式在每一個x返回第一個y:
x/y[index() = 0]

2.11.3. 簡寫

對于比較的目的而言,如果省略了方法名,value()方法是暗含的。換句話說,當兩個項目進行比較時,比較在兩個項目的值之間進行。請記住在沒有類型信息時,value()返回文字。
下列例子是等價的:
author[last-name!value() = Bob $and$ first-name!value() = Joe ]
author[last-name = Bob $and$ first-name = Joe ]
price[@intl!value() = canada ]
price[@intl = canada ]


2.12. 集合內索引

XQL很容易在節點集合中發現一個特定的節點。簡單地將索引號放在方括號(‘[’和‘]’)里即可。序號是從0開始的。例如,下例查找第一個author元素:
author[0]
下例查找含有first-name子元素的第三個author元素:
author[first-name][2]
注意索引是相對于父節點。換句話說,考慮下列數據:
<x>
<y/>
<y/>
</x>
<x>
<y/>
<y/>
</x>
下列表達式將從每一個x中返回第一個y:
x/y[0]
下例將返回x內所有y組成的集合中的第一個y:
(x/y)[0]
下例將返回第一個x的第一個y:
x[0]/y[0]

2.12.1. 在集合中返回最后的元素

end()方法對集合的最后一個元素返回真。注意end()是相對父節點的。
示例:
查找最后的book:
book[end()]
查找每一個book元素的最后的author:
book/author[end()]
在book的所有author構成的集合中,查找最后的author:
(book/author)[end()]

3. XQL擴展

XQL功能提供在XQL客戶端所需的最小功能集合。XQL擴展描述了擴展XQL能力的額外的功能。


3.1. 命名空間

在查詢中建立命名空間是需要的。由于命名空間是局部范圍,這一點尤其重要。因
此,
XQL需要一個可以為查詢的全局范圍或者查詢內的局部范圍建立命名空間的機制。

盡管我們不得不建立明確的命名空間語法,該語法必須滿足下列要求:
該語法必須能夠設定一個缺省的命名空間用于查詢內部;
該語法必須能夠設定一個命名空間用于任何指定的范圍;
該語法必須能夠設定一個命名空間用于任何級別的查詢。換句話說,人們應該能夠
在查詢開始設定命名空間,即使它們并沒有用于查詢內部較深的元素;
該語法必須能夠建立一個匹配命名空間的長名稱的前綴。
如果沒有在查詢中指定命名空間,前綴則用于匹配。
如果命名空間定義在session上,而不是在每一個查詢基礎上,XQL處理器可以更有
效的地執行。本規范沒有為指定用于整個session的命名空間的集合描述API或XML格式。這是由應用定義的。

3.1.1. 命名空間方法

下列方法可以用于節點,返回命名空間信息。
baseName()
返回節點的名稱部分,不包括前綴。
Namespace()
返回節點命名空間的URI。
Prefix()
返回節點的前綴。
示例:
查找所有未加限定的book元素。注意該查詢不返回my:book元素:
book
查找所有包含前綴’my’的book元素。注意該查詢不返回未加限定的book元素:
my:book
查找所有包含前綴’my’而且含author子元素的book元素:
my:book[author]
查找所有包含前綴’my’的book元素,其中包含author子元素而且author子元素也
包含
前綴’my’:
my:book[my:author]
查找所有包含前綴’my’的所有元素:
my:*
在所有命名空間下查找所有的book元素:
*:book
在所有命名空間下查找所有元素:
*
在book元素中查找含前綴’my’的style屬性:
book/@my:style

3.2. 查找屬性集合

用@*可以返回元素的所有屬性,這一點對那些將屬性視為記錄域的應用程序具有潛在的作用。
示例:
查找所有當前元素上下文的所有屬性:
@*
查找所有命名空間下的所有style屬性:
@*:style
查找命名空間’my’下的所有屬性,包括命名空間’my’下元素的未加限定的屬性

@my:*

3.3. 比較

二進制比較操作符集合可以用于比較數字和字符串,結果返回布爾值。$lt$、 $le$、$gt$、 $ge$分別用于小于、小于或等于、大于、大于或等于。這些操作符也可用于大小寫不敏感的形式:$ieq$, $ine$, $ilt$, $ile$, $igt$, $ige$。
單引號或雙引號可以用于在表達式中分隔字符串,使得在腳本語言中創建或傳遞XQL變得更為容易。
所有元素和屬性都是字符串,但是許多時候要求數字比較。關于類型轉換信息,查看比較過程中文字常量轉換和數據類型。
<、<=、>和>=是$lt$, $le$, $gt$和$ge$的簡寫方式。
示例:
查找last name是’Bob’而且price>50的所有author元素:
author[last-name = Bob $and$ price $gt$ 50]
查找from屬性值不等于’Harvard’的所有author:
author[@from != Harvard ]
查找last name開始于’M’或更大字母的所有author:
author[last-name $ge$ M ]
查找last name開始于’M’、’m’或更大字母的所有author:
author[last-name $ige$ M ]
查找前兩個book元素:
book[index() $le$ 2]
查找publications超過10的所有author:
author[publications!count() $gt$ 10]

3.3.1. 數據類型

如果提供了數據類型,value()函數使用該類型決定元素的類型。對于比較目的而言,左值總是轉換為右值的類型,因此保證了類型在比較過程中不發生變化。任何不能強制轉換的左值將從結果集中省略。

3.3.2.類型轉換函數

XQL提供函數用于值的類型轉換。類型轉換函數可以轉換文字常量或集合。當前,只提供了date函數。它將值轉換為日期,該值必須是具有日期格式的XML數據類型。
示例:
查找所有在1995年1月5日前出版的book:
books[pub_date < date( 1995-01-01 )]
查找出版日期(pub_date)早于屬性first中存儲的值的所有book:
books[pub_date < date(@first)]

3.4. 任一(any)和所有(all)語義

作者可以通過$any$和$all$關鍵字顯式指示是否使用任一(any)和所有(all)語義。

$any$表示如果集合中的任一元素滿足條件,則條件為真。$all$表示如果集合中的所有元素滿足條件,則條件為真。
$any$和$all$關鍵字可以出現在任何表達式前。
示例:
查找其中一個last name是’Bob’的所有author元素:
author[last-name = Bob ]
author[$any$ last-name = Bob ]
查找所有last name都不是’Bob’的所有author元素:
author[$all$ last-name != Bob ]
查找第一個last name是’Bob’的所有author元素:
author[last-name[0] = Bob ]

3.5. 并(Union)和交(intersection)

$union$操作符返回多個元素,返回元素沒有重新排序,沒有重復元素,雙重指定是暗含的。選擇列表中的所有元素必須是當前選擇上下文的后代。注意:由于這是并操作,返回集合每一個類型列表中可能包含0或更多的元素。為限定返回集合至少包含列表中每一個元素,使用在過濾一節討論的過濾。是$union$的簡寫。
$intersect$操作符返回在兩個集合之間的公共元素。元素無需重新排序。
示例:
查找所有的first-name和last-name:
first-name $union$ last-name
從bookstore中查找所有book和magazine:
bookstore/(book | magazine)
查找所有book和所有author:
book $union$ book/author
查找所有的first-name、last-name、來自于book或magazine內author的degree:

(book $union$ magazine)/author/(first-name $union$ last-name $union$ degree)

查找author/first-name等于’Bob’的所有book和price小于10的所有magazine:

book[author/first-name = Bob ] $union$ magazine[price $lt$ 10]

3.6. 集合方法

集合方法為文檔中不同類型節點提供訪問方式。這些集合可以被約束和索引。集合返回滿足特定條件的參考節點的子節點集合。
textNode()
文字節點的集合。
Comment()
注釋節點的集合。
Pi()
處理指示節點的集合。
Element([ name ])
所有元素節點的集合。如果提供了可選的文字參數,將只返回匹配特定名稱的元素子節
點。The collection of all element nodes. If the optional text
parameter is p
rovided, it only returns element children matching that particular
name.
Attribute([ name ])
所有屬性節點的集合。如果提供了可選的文字參數,將只返回匹配特定名稱的屬性節點。
Node()
返回所有非屬性節點。
示例:
查找當前上下文中每一個p元素中的第二個文本節點:
p/textNode()[1]
查找文檔中第二個注釋節點。關于文檔根部上下文設置和細節,請查看上下文:
//comment()[1]

3.6.1. 文檔根部的DOM節點

請注意,在DOM中文檔對象包含注釋、處理指令、聲明、以及術語所稱的“根元素”。XQL使用術語“文檔實體”表示DOM樹的根―文檔對象-而不是“根元素”。 在用于定位的文檔實體層次允許注釋等。
示例:
查找文檔實體層次的所有注釋
/comment()


3.7. 聚合方法

下列方法基于一個集合產生聚合結果。
count() 返回集合中的節點數目。


3.8. 其他方法

下述方法返回指示節點類型的字符串。
nodeTypeString() 返回用于指示節點類型的下列字符串之一:
document
element
attribute
processing_instruction
comment
text


3.9. 祖先(ancestor)

Ancestor查找滿足查詢的最近祖先。結果返回一個元素或空集。
ancestor(query) 滿足提供查詢的最近祖先。.
示例:
查找當前元素的最近的book祖先:
ancestor(book)
查找包含在book元素最近的author祖先:
ancestor(book/author)


3.10. 腳標操作符

可以返回元素的排列。為實現該功能,在腳標操作符(方括號)內指定一個表達式而不是單個值。該表達式可以是逗號分隔的由下列條目組成的列表。
n
返回第n個元素
-n
返回從最后的元素倒數第n個元素。如:-1表示最后一個元素,-2表示倒數第2個元素。

M $to$ n
返回從m到n的元素,其中m,n是包含的。
示例:
查找第一個和第四個author元素:
author[0,3]
查找第一個到第四個author元素:
author[0 $to$ 3]
查找第一個,第三個到第五個和最后一個author元素:
author[0,2 $to$ 4, -1]
查找最后一個author元素:
author[-1]



2005-06-15 13:26 發表評論
]]>
SAX之Java實現學習筆記http://m.tkk7.com/jackybu/articles/2559.htmlMon, 28 Mar 2005 08:15:00 GMThttp://m.tkk7.com/jackybu/articles/2559.htmlhttp://m.tkk7.com/jackybu/comments/2559.htmlhttp://m.tkk7.com/jackybu/articles/2559.html#Feedback0http://m.tkk7.com/jackybu/comments/commentRss/2559.htmlhttp://m.tkk7.com/jackybu/services/trackbacks/2559.html閱讀全文

2005-03-28 16:15 發表評論
]]>
使用SAX處理XML文檔http://m.tkk7.com/jackybu/articles/2558.htmlMon, 28 Mar 2005 08:09:00 GMThttp://m.tkk7.com/jackybu/articles/2558.htmlhttp://m.tkk7.com/jackybu/comments/2558.htmlhttp://m.tkk7.com/jackybu/articles/2558.html#Feedback0http://m.tkk7.com/jackybu/comments/commentRss/2558.htmlhttp://m.tkk7.com/jackybu/services/trackbacks/2558.html
內容:
SAX的基本情況
理解并使用SAX
一個實例
使用DefaultHandler
使用過濾器
一些值得注意的問題
SAX與DOM的比較
附錄SAX的版權聲明
關于作者

王曉強 (forff@sina.com)
2001 年 11 月

這里我將向大家介紹處理XML文檔的另一個重要接口SAX(Simple API for XML)。其中包括它的基本情況,它的API,一個開發實例,實際開發中一些需注意的問題,以及它與DOM的對比。

SAX的基本情況
SAX同DOM一樣也是一個訪問XML文檔的接口。SAX是Simple API for XML的縮寫。它不像DOM那樣是W3C的推薦標準。它是由XML-DEV郵件列表的成員開發維護,由David Megginson領導(david@megginson.com)的一個Public Domain軟件。SAX是一個徹底的自由軟件,它的作者放棄了對它的所有權利,并且它也被許可用于任何目的(在文章最后附錄了它的版權聲明)。

到現在為止SAX的版本已經發展到2.0。在這個最新版本中增加了對名稱空間(Namespaces)的支持,而且可以通過對features以及properties的設置來對解析器做全面的配置,這其中包括設置解析器是否對文檔進行有效性驗證,以及怎樣來處理帶有名稱空間的元素名稱等。SAX1中的接口已經不再使用了,這里只會討論有關SAX2的開發。在本文中提到SAX只是指SAX 2。另外,本文的所有例子都是用java編寫,SAX解析器也使用的是JAVA版本。

像DOM一樣,SAX并不是一個實際可以使用的XML文檔解析器,而是其他兼容SAX的解析器要實現的接口和幫助類的集合。如果你想使用SAX的話,你必須滿足下面的要求:

  1. 系統中包含Java 1.1 或者更高版本。
  2. 在Java classpath中包含進你的SAX類庫。
  3. 在Java classpath中包含進你要使用的兼容SAX的XML解析器類庫。

實現了SAX的解析器有很多,比如Apache的Xerces,Oracle的XML Parser等等。在本文中的例子程序使用的都是Xerces解析器,你可以從 http://xml.apache.org 得到它。讓我們下載得到xerces.jar文件然后將其加入到classpath中去,這樣我們就已經建立好環境(在xerces.jar中已經包含了SAX接口,所以不必特意再去尋找SAX類庫)。

在SAX API中有兩個包,org.xml.sax和org.xml.sax.helper。其中org.xml.sax中主要定義了SAX的一些基礎接口,如XMLReader、ContentHandler、ErrorHandler、DTDHandler、EntityResolver等。而在org.xml.sax.helper中則是一些方便開發人員使用的幫助類,如缺省實現所有處理器接口的幫助類DefaultHandler、方便開發人員創建XMLReader的XMLReaderFactory類等等。在這兩個包中還有一些應用于SAX1的接口,同時還有幾個類它們只是為了便于將在SAX1上開發的應用移植到SAX2上,在這篇文章中就不涉及了。下面是我們要關注的接口和類:

Package org.xml.sax

介紹

Interfaces 接口
Attributes 定義了一個屬性列表接口,供訪問元素的屬性列表而用。
ContentHandler 處理解析文檔內容時產生的事件。
DTDHandler 處理解析DTD時的相應事件。
EntityResolver 處理外部實體。
ErrorHandler 處理解析過程中所遇到的文檔錯誤事件。
Locator 為了定位解析中產生的內容事件在文檔中的位置而準備的一個定位器接口。
XMLFilter 提供了一個方便應用開發的過濾器接口。
XMLReader 任何兼容SAX2的解析器都要實現這個接口,這個接口讓應用程序可以設置或查找features和properties,注冊各種事件處理器,以及開始解析文檔。
Classes
InputSource 為XML實體準備的輸入源。
Exceptions
SAXException 包裝了一般的SAX錯誤和警告。
SAXNotRecognizedException 為識別不出某些標識而拋出的異常。
SAXNotSupportedException 為不支持某個操作而拋出的異常。
SAXParseException 包裝了一個關于XML解析的錯誤或者警告。

Package org.xml.sax.helpers

幫助類所在的包

Classes
AttributesImpl 對Attributes接口的缺省實現
NamespaceSupport 提供名稱空間支持。
DefaultHandler 缺省實現了四個處理器接口,方便用戶開發,在開發過程中會經常用到。
LocatorImpl 提供了一個對Locator接口的實現
XMLFilterImpl 對過濾器接口的實現,使用過濾器進行應用程序開發時,繼承這個類很方便。
XMLReaderFactory 為方便創建不同的XMLReader而提供。也會經常用到。

理解并使用SAX
SAX的設計實現與DOM是完全不同的!DOM處理XML文檔是基于將XML文檔解析成樹狀模型,放入內存進行處理。而SAX則是采用基于事件驅動的處理模式,它將XML文檔轉化成一系列的事件,由單獨的事件處理器來決定如何處理。為了了解如何使用SAX API處理XML文檔,這里先介紹一下SAX所使用的基于事件驅動的處理模式。

這種基于事件的處理模式是一種通用的程序設計模式,被廣泛應用于GUI設計。在JAVA的AWT,SWING以及JAVA BEANS中就有它的身影。而SAX的基于事件驅動的處理模式就與上面三者中的非常相像。

基于事件的處理模式主要是圍繞著事件源以及事件處理器(或者叫監聽器)來工作的。一個可以產生事件的對象被稱為事件源,而可以針對事件產生響應的對象就被叫做事件處理器。事件源和事件處理器是通過在事件源中的事件處理器注冊方法連接的。這樣當事件源產生事件后,調用事件處理器相應的處理方法,一個事件就獲得了處理。當然在事件源調用事件處理器中特定方法的時候,會傳遞給事件處理器相應事件的狀態信息,這樣事件處理器才能夠根據事件信息來決定自己的行為。

在SAX接口中,事件源是org.xml.sax包中的XMLReader,它通過parse()方法來開始解析XML文檔并根據文檔內容產生事件。而事件處理器則是org.xml.sax包中的ContentHandler,DTDHandler,ErrorHandler,以及EntityResolver這四個接口。它們分別處理事件源在解析過程中產生的不同種類的事件(其中DTDHandler是為解析文檔DTD時而用)。而事件源XMLReader和這四個事件處理器的連接是通過在XMLReader中的相應的事件處理器注冊方法set***()來完成的。詳細介紹請見下表:
處理器名稱 所處理事件 注冊方法
org.xml.sax.ContentHandler 跟文檔內容有關的所有事件:
  1. 文檔的開始和結束
  2. XML元素的開始和結束
  3. 可忽略的實體
  4. 名稱空間前綴映射開始和結束
  5. 處理指令
  6. 字符數據和可忽略的空格
XMLReader中的setContentHandler(ContentHandler handler)方法
org.xml.sax.ErrorHandler 處理XML文檔解析時產生的錯誤。如果一個應用程序沒有注冊一個錯誤處理器類,會發生不可預料的解析器行為。 setErrorHandler(ErrorHandler handler)
org.xml.sax.DTDHandler 處理對文檔DTD進行解析時產生的相應事件 setDTDHandler(DTDHandler handler)
org.xml.sax.EntityResolver 處理外部實體 setEntityResolver(EntityResolver resolver)

在這四個處理器接口中,對我們最重要的是ContentHandler接口。下面讓我們看一下對其中方法的說明:
方法名稱 方法說明
public void setDocumentLocator(Locator locator) 設置一個可以定位文檔內容事件發生位置的定位器對象
public void startDocument() throws SAXException 用于處理文檔解析開始事件
public void endDocument() throws SAXException 用于處理文檔解析結束事件
public void startPrefixMapping(java.lang.String prefix, java.lang.String uri) throws SAXException 用于處理前綴映射開始事件,從參數中可以得到前綴名稱以及所指向的uri
public void endPrefixMapping(java.lang.String prefix) throws SAXException 用于處理前綴映射結束事件,從參數中可以得到前綴名稱
public void startElement(java.lang.String namespaceURI,java.lang.String localName,java.lang.String qName,Attributes atts) throws SAXException 處理元素開始事件,從參數中可以獲得元素所在名稱空間的uri,元素名稱,屬性列表等信息
public void endElement(java.lang.String namespaceURI, java.lang.String localName, java.lang.String qName) throws SAXException 處理元素結束事件,從參數中可以獲得元素所在名稱空間的uri,元素名稱等信息
public void characters(char[] ch, int start, int length) throws SAXException 處理元素的字符內容,從參數中可以獲得內容
public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException 處理元素的可忽略空格
public void processingInstruction(java.lang.String target, java.lang.String data) throws SAXException 處理解析中產生的處理指令事件

這里再介紹一下org.xml.sax.XMLReader中的方法,然后讓我們看一個具體的例子。XMLReader是所有兼容SAX2的解析器都要實現的接口,由它的方法開始解析文檔,并且調用它的注冊方法來注冊各種事件處理器。請看下表:
方法名稱 方法介紹
public Boolean getFeature(java.lang.String name)throws SAXNotRecognizedException,SAXNotSupportedException 得到某個feature的值
public void setFeature(java.lang.String name,boolean value) throws SAXNotRecognizedException,SAXNotSupportedException 設置某個feature的值,例如,如果需要解析器支持對文檔進行驗證那么就這么調用本方法。myReader.setFeature(http://xml.org/sax/features/validation,true);其中myReader是XMLReader的實例。
public java.lang.Object getProperty(java.lang.String name)throws SAXNotRecognizedException,SAXNotSupportedException 返回一個property的值
public void setProperty(java.lang.String name,java.lang.Object value)throws SAXNotRecognizedException,SAXNotSupportedException 設置一個property的值
public void setEntityResolver(EntityResolver resolver) 注冊處理外部實體的EntityResolver
public EntityResolver getEntityResolver() 得到系統中注冊的EntityResolver
public void setDTDHandler(DTDHandler handler) 注冊處理DTD解析事件的DTDHandler
public DTDHandler getDTDHandler() 得到系統中注冊的DTDHandler
public void setContentHandler(ContentHandler handler) 注冊處理XML文檔內容解析事件的ContentHandler
public ContentHandler getContentHandler() 得到系統中注冊的ContentHandler
public void setErrorHandler(ErrorHandler handler) 注冊處理文檔解析錯誤事件的ErrorHandler
public ErrorHandler getErrorHandler() 得到系統中注冊的ErrorHandler
public void parse(InputSource input)throws java.io.IOException,SAXException 開始解析一個XML文檔。
public void parse(java.lang.String systemId)throws java.io.IOException,SAXException 開始解析一個使用系統標識符標識的XML文檔。這個方法只是上面方法的一個快捷方式它等同于:parse(new InputSource(systemId));

一個實例
讓我們通過例子來看一下使用SAX解析XML文檔的應用程序是如何建立的。下面是在應用程序中被處理的XML文檔。為了說明SAX對名稱空間的支持,我在這里特意加了一個有名稱空間的元素,在這里會產生相應的前綴映射開始和結束事件。


<?xml version="1.0" encoding="GB2312"?>
<我的書架 >
    <技術書籍>
        <圖書>
            <書名>JAVA 2編程詳解</書名>
            <價格 貨幣單位="人民幣">150</價格>
            <購買日期>2000,1,24</購買日期>
        </圖書>      
    </技術書籍>
    <book:文學書籍 xmlns:book="http://javausr.com"/>
    <歷史書籍/>
</我的書架>

這里的例子程序只是簡單地將遇到的事件信息打印出來。我們首先實現ContentHandler接口來處理在XML文檔解析過程中產生的和文檔內容相關的事件,代碼如下所示MyContentHandler.java:
package com.javausr.saxexample;

import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;

public class MyContentHandler implements ContentHandler {

    private StringBuffer buf;

    public void setDocumentLocator( Locator locator ) {
    }

    public void startDocument() throws SAXException {
        buf=new StringBuffer();
        System.out.println("*******開始解析文檔*******");
    }

    public void endDocument() throws SAXException {
        System.out.println("*******解析文檔結束*******");
    }

    public void processingInstruction( String target, String instruction )
        throws SAXException {
    }

    public void startPrefixMapping( String prefix, String uri ) {
          System.out.println("\n前綴映射: " + prefix +" 開始!"+ "  它的URI是:" + uri);
    }

    public void endPrefixMapping( String prefix ) {
          System.out.println("\n前綴映射: "+prefix+" 結束!");
    }

    public void startElement( String namespaceURI, String localName,
                                  String fullName, Attributes attributes )
                          throws SAXException {
        System.out.println("\n 元素: " + "["+fullName+"]" +" 開始解析!");
        // 打印出屬性信息
        for ( int i = 0; i < attributes.getLength(); i++ ) {
            System.out.println("\t屬性名稱:" + attributes.getLocalName(i)
                + " 屬性值:" + attributes.getValue(i));
        }
    }

    public void endElement( String namespaceURI, String localName,
                                                      String fullName )
                          throws SAXException {
        //打印出非空的元素內容并將StringBuffer清空                  
      String nullStr="";
        if (!buf.toString().trim().equals(nullStr)){
           System.out.println("\t內容是: " + buf.toString().trim());
        }
        buf.setLength(0);
        //打印元素解析結束信息
        System.out.println("元素: "+"["+fullName+"]"+" 解析結束!");              
    }

    public void characters( char[] chars, int start, int length )
                                throws SAXException {
          //將元素內容累加到StringBuffer中                
          buf.append(chars,start,length);
    }

    public void ignorableWhitespace( char[] chars, int start, int length )
                                  throws SAXException {
    }

    public void skippedEntity( String name ) throws SAXException {
    }
}

下面讓我們創建一個調入了xerces解析器來實現XMLReader接口、并使用剛才創建的MyContentHandler來處理相應解析事件的MySAXApp.java類:
package com.javausr.saxexample;

import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import java.io.IOException;

public class MySAXApp {

  public static void main( String[] args ) {
    
    if ( args.length != 1 ) {
      System.out.println("輸入: java MySAXApp ");
      System.exit(0);
    }

    try {
        // 初始化reader
        XMLReader reader = XMLReaderFactory.createXMLReader
                          ("org.apache.xerces.parsers.SAXParser") ;

        // 創建ContentHandler的實例
        ContentHandler contentHandler = new MyContentHandler();

        // 在reader中注冊實例化的ContentHandler
        reader.setContentHandler( contentHandler );

        // 開始解析文檔
        reader.parse(args[0]);

    } catch ( IOException e ) {
        System.out.println("讀入文檔時錯: " + e.getMessage());
    } catch ( SAXException e ) {
        System.out.println("解析文檔時錯: " + e.getMessage());
    }
  }
}

下面讓我們來看一下執行結果:

D:\sax\classes>java com.javausr.saxexample.MySAXApp d:\book.xml
*******開始解析文檔*******

元素: [我的書架] 開始解析!

元素: [技術書籍] 開始解析!

元素: [圖書] 開始解析!

元素: [書名] 開始解析!
        內容是: JAVA 2編程詳解
元素: [書名] 解析結束!

元素: [價格] 開始解析!
        屬性名稱:貨幣單位 屬性值:人民幣
        內容是: 150
元素: [價格] 解析結束!

元素: [購買日期] 開始解析!
        內容是: 2000,1,24
元素: [購買日期] 解析結束!
元素: [圖書] 解析結束!
元素: [技術書籍] 解析結束!

前綴映射: book 開始!  它的URI是:http://javausr.com

元素: [book:文學書籍] 開始解析!
元素: [book:文學書籍] 解析結束!

前綴映射: book 結束!

元素: [歷史書籍] 開始解析!
元素: [歷史書籍] 解析結束!
元素: [我的書架] 解析結束!
*******解析文檔結束*******

上面就是使用SAX解析一個XML文檔的基本過程,但是MyContentHandler只是處理了解析過程中和文檔內容相關的事件,如果在解析過程中出現了錯誤那我們需要實現ErrorHandler接口來處理。如果不注冊一個錯誤處理器來處理的話,那么錯誤事件將不會被報告,而且解析器會出現不可預知的行為。在解析過程中產生的錯誤被分成了3類,它們分別是warning,error,以及fatalerror,也就是說在ErrorHandler中有這么三個相應的方法來處理這些錯誤事件。下面是對這三個錯誤處理方法的介紹:
方法名稱 方法介紹
warning() SAX解析器將用這個方法來報告在XML1.0規范中定義的非錯誤(error)或者致命錯誤(fatal error)的錯誤狀態。對這個錯誤缺省的行為是什么也不做。SAX解析器必須在調用這個方法后繼續提供正常的解析事件:應用程序應該能繼續處理完文檔。
error() 這個方法對應在W3C XML 1.0規范的1.2部分中定義的"error"概念。例如,一個帶有有效性驗證的解析器會使用這個方法來報告違反有效性驗證的情況。一個帶有有效性驗證的解析器會使用這個方法來報告違背有些性約束的情況。缺省的行為是什么也不做。SAX解析器必須在調用這個方法后繼續提供正常的解析事件:應用程序應該能繼續處理完文檔。如果應用程序做不到這樣,則解析器即使在XML1.0規范沒有要求的情況下也要報告一個致命錯誤。
fatalError() 這個方法對應在W3C XML1.0規范的1.2部分定義的"fatal error"概念。例如,一個解析器會使用這個方法來報告違反格式良好約束的情況。在解析器調用這個方法后應用程序必須表明這個文檔是不可使用的,而且應該只是為了收集錯誤信息而繼續進行處理(如果需要的話):實際上,一旦在這個方法被調用后SAX解析器可以停止報告任何事件。

下面是實現了ErrorHandler接口的MyErrorHandler.java類:
package com.javausr.saxexample;

import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXParseException;
import org.xml.sax.SAXException;

public class MyErrorHandler implements ErrorHandler {

    public void warning( SAXParseException exception ) {
        System.out.println("*******WARNING******");
        System.out.println("\t行:\t" + exception.getLineNumber());
        System.out.println("\t列:\t" + exception.getColumnNumber());
        System.out.println("\t錯誤信息:\t" + exception.getMessage());
        System.out.println("********************");
    }

    public void error( SAXParseException exception ) throws SAXException{
        System.out.println("******* ERROR ******");
        System.out.println("\t行:\t" + exception.getLineNumber());
        System.out.println("\t列:\t" + exception.getColumnNumber());
        System.out.println("\t錯誤信息:\t" + exception.getMessage());
        System.out.println("********************");
    }

    public void fatalError( SAXParseException exception ) throws SAXException {
        System.out.println("******** FATAL ERROR ********");
        System.out.println("\t行:\t" + exception.getLineNumber());
        System.out.println("\t列:\t" + exception.getColumnNumber());
        System.out.println("\t錯誤信息:\t" + exception.getMessage());
        System.out.println("*****************************");
    }
}

我們也要對MySAXApp.java類做一些修改(在源代碼中藍色標出的部分)使它使用MyErrorHandler.java:
package com.javausr.saxexample;

import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;
import org.xml.sax.ContentHandler;
//引入ErrorHandler
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import java.io.IOException;

public class MySAXApp {
    
        public static void main( String[] args ) {
        
          if ( args.length != 1 ) {
            System.out.println("輸入: java MySAXApp ");
            System.exit(0);
        }

        try {
            // 初始化reader
            XMLReader reader = XMLReaderFactory.createXMLReader
                               ("org.apache.xerces.parsers.SAXParser") ;

            // 創建ContentHandler的實例
            ContentHandler contentHandler = new MyContentHandler();

            // 在reader中注冊實例化的ContentHandler
            reader.setContentHandler( contentHandler );

            // 創建ErrorHandler的實例
            ErrorHandler errorHandler = new MyErrorHandler();

            // 在reader中注冊實例化的ErrorHandler
            reader.setErrorHandler( errorHandler );

            // 開始解析文檔
            reader.parse(args[0]);

    } catch ( IOException e ) {
        System.out.println("讀入文檔時錯: " + e.getMessage());
    } catch ( SAXException e ) {
        System.out.println("解析文檔時錯: " + e.getMessage());
    }
  }

讓我們人為制造些錯誤來檢查一下我們的錯誤處理器工作情況。刪除元素<購買日期>的閉合標記,這樣會產生一個fatal error,下面是執行結果:
D:\sax\classes>java com.javausr.saxexample.MySAXApp d:\book.xml

*******開始解析文檔*******

元素: [我的書架] 開始解析!

元素: [技術書籍] 開始解析!

元素: [圖書] 開始解析!

元素: [書名] 開始解析!

元素: [書名] 開始解析!
        內容是: JAVA 2編程詳解
元素: [書名] 解析結束!

元素: [價格] 開始解析!
        屬性名稱:貨幣單位 屬性值:人民幣
        內容是: 150
元素: [價格] 解析結束!

元素: [購買日期] 開始解析!
******** FATAL ERROR ********
        行:     8
        列:     7
        錯誤信息:       The element type "購買日期" must be terminated by the matching end-tag "</購買日期>".
*****************************
解析文檔時錯: Stopping after fatal error: The element type "購買日期" must be terminated by the matching end-tag "</購買日期>".

現在總結一下如何書寫基于SAX的應用程序。一般步驟如下:

  1. 實現一個或多個處理器接口(ContentHandler, ErrorHandler, DTDHandler ,or EntityResover)。
  2. 創建一個XMLReader類的實例。
  3. 在新的XMLReader實例中通過大量的set*****() 方法注冊一個事件處理器的實例
  4. 調用XMLReader的parse()方法來處理文檔。

使用DefaultHandler
現在的程序是比較完整了,但還有許多可以改進的地方。首先在我們實現的MyContentHandler.java中,你會發現有很多方法實際上什么也沒有做,但為了實現ContentHandler接口,不得不把它們寫出來,這樣很是麻煩。SAX API已經考慮到這個問題,在它的org.xml.sax.helper包中為我們提供了一個方便實現各種處理器接口的幫助類DefaultHandler。這個類缺省實現了上面提到的4個處理器接口。這樣我們只需繼承這個類,然后覆蓋我們想要實現的事件處理方法即可。下面我們來新建一個繼承了DefaultHandler的MyDefaultHandler.java類,然后把在MyContentHandler.java和MyErrorHandler.java中實現的事件處理方法照搬到MyDefaultHandler.java類中,那些沒有使用的方法就不必重復了。這里是MyDefaultHandler.java:
package com.javausr.saxexample;

import org.xml.sax.*;
import org.xml.sax.helpers.*;
import java.io.*;

public class MyDefaultHandler extends DefaultHandler {

    private StringBuffer buf;

    public void startDocument() throws SAXException {
        buf=new StringBuffer();
        System.out.println("*******開始解析文檔*******");
    }

    public void endDocument() throws SAXException {
        System.out.println("*******解析文檔結束*******");
    }

    public void startPrefixMapping( String prefix, String uri ) {
System.out.println("\n前綴映射: " + prefix +" 開始!"+ "  它的URI是:"+uri);
    }

    public void endPrefixMapping( String prefix ) {
       System.out.println("\n前綴映射: "+prefix+" 結束!");
    }

    public void startElement( String namespaceURI, String localName,
                                  String fullName, Attributes attributes )
                          throws SAXException {

        System.out.println("\n元素: " + "["+fullName+"]" +" 開始解析!");

        // 打印出屬性信息
        for ( int i = 0; i < attributes.getLength(); i++ ) {
            System.out.println("\t屬性名稱:" + attributes.getLocalName(i)
                + " 屬性值:" + attributes.getValue(i));
        }
    }

    public void endElement( String namespaceURI, String localName,
                                                      String fullName )
                          throws SAXException {
       //打印出非空的元素內容并將StringBuffer清空
       String nullStr="";
       if (!buf.toString().trim().equals(nullStr)){
          System.out.println("\t內容是: " + buf.toString().trim());
       }
       buf.setLength(0);
       //打印元素解析結束信息
        System.out.println("元素: "+"["+fullName+"]"+" 解析結束!");
    }

    public void characters( char[] chars, int start, int length )
                                throws SAXException {
       //將元素內容累加到StringBuffer中
       buf.append(chars,start,length);
    }

    public void warning( SAXParseException exception ) {
        System.out.println("*******WARNING******");
        System.out.println("\t行:\t" + exception.getLineNumber());
        System.out.println("\t列:\t" + exception.getColumnNumber());
        System.out.println("\t錯誤信息:\t" + exception.getMessage());
        System.out.println("********************");
    }

    public void error( SAXParseException exception ) throws SAXException{
        System.out.println("******* ERROR ******");
        System.out.println("\t行:\t" + exception.getLineNumber());
        System.out.println("\t列:\t" + exception.getColumnNumber());
        System.out.println("\t錯誤信息:\t" + exception.getMessage());
        System.out.println("********************");
    }

    public void fatalError( SAXParseException exception ) throws SAXException {
        System.out.println("******** FATAL ERROR ********");
        System.out.println("\t行:\t" + exception.getLineNumber());
        System.out.println("\t列:\t" + exception.getColumnNumber());
        System.out.println("\t錯誤信息:\t" + exception.getMessage());
        System.out.println("*****************************");
    }
}

我們也要對MySAXApp.java做相應的修改,修改已在源代碼中標出:
package com.javausr.saxexample;

import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;
//引入DefaultHandler
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.SAXException;
import java.io.IOException;

public class MySAXApp {

  public static void main( String[] args ) {
        
      if ( args.length != 1 ) {
        System.out.println("輸入: java MySAXApp ");
        System.exit(0);
      }

    try {
        // 初始化reader
        XMLReader reader = XMLReaderFactory.createXMLReader
                         ("org.apache.xerces.parsers.SAXParser") ;

        // 創建DefaultHandler的實例
        DefaultHandler defaultHandler=new MyDefaultHandler();

        //在reader中將defaultHandler注冊為ContentHandler
        reader.setContentHandler(defaultHandler);

        //在reader中將defaultHandler注冊為ErrorHandler
        reader.setErrorHandler(defaultHandler);

        // 開始解析文檔
        reader.parse(args[0]);

    } catch ( IOException e ) {
        System.out.println("讀入文檔時錯: " + e.getMessage());
    } catch ( SAXException e ) {
        System.out.println("解析文檔時錯: " + e.getMessage());
    }
  }
}

使用過濾器
在SAX API中還提供了一個過濾器接口org.xml.sax.XMLFilter,以及對它的缺省實現org.xml.sax.helper.XMLFilterImpl。使用它們可以很容易的開發出復雜的SAX應用。這里要先介紹一下過濾器設計模式。這個設計模式很好理解,就像一個凈化水的過程。自然界中的水流過一個個的過濾器得到最后的飲用水。這些過濾器,有的是清除水中的泥沙,有的是殺滅水中的細菌,總之不同的過濾器完成不同的任務。在應用開發中,我們讓被改造的對象(這里是事件流)通過這些過濾器對象從而得到改造后符合要求的對象。這樣,在過濾器的幫助之下,我們可以非常方便的在每個過濾器中實現一個特定功能,從而創建結構復雜的應用程序。在應用程序中你可以構造任意多個過濾器,將它們串接起來完成任務。

在SAX API中org.xml.sax.XMLFilter接口繼承了org.xml.sax.XMLReader接口。它與XMLReader不同的是它不像XMLReader那樣通過解析文檔來獲取事件,而是從其他XMLReader中獲取事件,當然這也包括從其他的XMLFilter中獲取事件。在org.xml.sax.XMLFilter中有兩個方法:
方法名稱 方法描述
Public void setParent(XMLReader parent) 設置父XMLReader。這個方法讓應用程序將這個過濾器連接到它的父XMLReader (也可能是另一個過濾器)。
Public XMLReader getParent() 獲取父XMLReader。這個方法讓應用程序可以查詢父XMLReader(也可能是另一個過濾器)。最好不要在父XMLReader中直接進行任何操作:讓所有的事件通過這個過濾器來處理。

我們不需要自己實現org.xml.sax.XMLFilter接口,在SAX API 中提供了一個org.xml.sax.helper.XMLFilterImpl類,它不僅實現了org.xml.sax.XMLFilter接口而且還實現了其他四個核心處理器接口,我們只需要繼承它即可完成我們的過濾器。剛開始使用XMLFilterImpl比較容易讓人迷惑,你只需要記住:

  1. 在你繼承的XMLFilterImpl類中用set****()方法這冊的事件處理器是給過濾后的事件流而用的。
  2. 在你繼承的XMLFilterImpl類中實現的那些事件處理方法,比如startDocument()、startElement()、characters()等才是這個過濾器實現它自身功能的地方。而通過繼承XMLFilterImpl而實現的這個類會被造型成各種處理器(它本身實現了四個處理器接口)用在它的父XMLReader中。這個步驟會在你調用自己創建的過濾器的parse()方法開始解析文檔時被自動執行(請參見SAX源代碼)。
  3. 如果不是使用帶參數的構造器創建XMLFilter對象,務必使用setParent(XMLReader parent)方法連接它的父XMLReader。
  4. 如果使用多個過濾器的話,執行順序是從父親到最后的過濾器。但是開始解析卻要調用最后一個過濾器的parse()方法。

下面讓我們結合已有的例子來演示過濾器org.xml.sax.XMLFilter的作用。我們在這個過濾器中要過濾掉<技術書籍>這個元素,最后得到的事件流還是由上邊實現的MyDefaultHandler來處理。源代碼如下MyFilter.java:
package com.javausr.saxexample;

import org.xml.sax.*;
import org.xml.sax.helpers.*;
import java.io.*;

public class MyFilter extends XMLFilterImpl {

   private String currentElement;

   public MyFilter( XMLReader parent ) {
      super(parent);
   }

   /**
    * 過濾掉元素<技術書籍>的開始事件
    **/
   public void startElement( String namespaceURI, String localName,
                             String fullName, Attributes attributes )
      throws SAXException {

         currentElement = localName;
         if ( !localName.equals("技術書籍") ) {
           super.startElement(namespaceURI, localName, fullName, attributes);
         }
      }

   /**
    * 過濾掉元素<技術書籍>的結束事件
    **/
   public void endElement(String namespaceURI, String localName, String
                          fullName)
      throws SAXException {

         if ( !localName.equals("技術書籍") ) {
            super.endElement(namespaceURI, localName, fullName);
         }
    }

   /**
    * 過濾掉元素<技術書籍>中的內容
    **/
    public void characters(char[] buffer, int start, int length) 
throws SAXException {
        if ( !currentElement.equals("技術書籍") ) {
          super.characters( buffer,start,length );
        }
    }
}

同樣我們還要修改MySAXApp.java,修改后的代碼如下所示:
package com.javausr.saxexample;

import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;
import org.xml.sax.helpers.DefaultHandler;
//引入XMLFilter
import org.xml.sax.XMLFilter;
import org.xml.sax.SAXException;
import java.io.IOException;

public class MySAXApp {

  public static void main( String[] args ) {

    
    if ( args.length != 1 ) {
      System.out.println("輸入: java MySAXApp ");
      System.exit(0);
    }

    try {
            // 初始化reader
        XMLReader reader = XMLReaderFactory.createXMLReader
                           ("org.apache.xerces.parsers.SAXParser") ;

        //初始化過濾器
        XMLFilter myFilter=new MyFilter(reader);

        // 創建DefaultHandler的實例
        DefaultHandler defaultHandler=new MyDefaultHandler();

        //為過濾后的事件流設置ContentHandler
        myFilter.setContentHandler(defaultHandler);

        //為過濾后的事件流設置ErrorHandler
        myFilter.setErrorHandler(defaultHandler);


            // 開始解析文檔,注意是使用myFilter中的解析方法
        myFilter.parse(args[0]);

      } catch ( IOException e ) {
            System.out.println("讀入文檔時錯: " + e.getMessage());
      } catch ( SAXException e ) {
            System.out.println("解析文檔時錯: " + e.getMessage());
    }
  }
}

這里是最后的執行結果,我們可以發現有關<技術書籍>的全部事件已經被過濾掉了。認真看一下結果,你一定覺得奇怪,為什么<技術書籍>元素的孩子元素仍然存在。請記住SAX是把XML文檔解析成事件流,所有沒有被過濾的事件都會保留下來。這就是SAX和DOM的最大不同。在DOM中文檔被解析成了樹狀模型,如果你刪除一個元素,那么這個元素以及它的孩子元素就都會被刪除,這符合樹狀模型的特點。

D:\sax\classes>java com.javausr.saxexample.MySAXApp d:\book.xml

*******開始解析文檔*******

元素: [我的書架] 開始解析!

元素: [圖書] 開始解析!

元素: [書名] 開始解析!
        內容是: JAVA 2編程詳解
元素: [書名] 解析結束!

元素: [價格] 開始解析!
        屬性名稱:貨幣單位 屬性值:人民幣
        內容是: 150
元素: [價格] 解析結束!

元素: [購買日期] 開始解析!
        內容是: 2000,1,24
元素: [購買日期] 解析結束!
元素: [圖書] 解析結束!

前綴映射: book 開始!  它的URI是:http://javausr.com

元素: [book:文學書籍] 開始解析!
元素: [book:文學書籍] 解析結束!

前綴映射: book 結束!

元素: [歷史書籍] 開始解析!
元素: [歷史書籍] 解析結束!
元素: [我的書架] 解析結束!
*******解析文檔結束*******

一些值得注意的問題
首先是有關元素內容的問題,在SAX API定義中元素內容可以在一次事件(由characters()方法處理)中返回,也可以在多次事件中返回,這樣我們就應該考慮不能一次得到所有內容數據的情況。一般的解決辦法是定義一個StringBuffer由它來保存內容數據,在元素結束或者新元素開始的時候清空這個StringBuffer從而可以保存新的內容數據。請參考上面的相應的源代碼。

還有在SAX API中特意提到從 characters(char[] ch,int start,int length)方法中提取數據時一定不要從返回的字符數組范圍之外讀取,這一點我們也要切記。

另一個值得注意的問題是,在 startElement()方法中返回的Attributes屬性列表中的屬性順序并沒有被特意規定,在不同的SAX實現中也各不相同。所以我們在編寫程序時不要把屬性順序想成一定的。

SAX與DOM的比較
通過上面的介紹我想大家對SAX已經有了一個基本的了解。每一個進行XML開發的編程人員都知道DOM,那為什么在有了DOM這個功能強大的文檔對象模型之后,我們還需要SAX?這就要從它們根本不同的實現方法上來分析。DOM解析器是通過將XML文檔解析成樹狀模型并將其放入內存來完成解析工作的,而后對文檔的操作都是在這個樹狀模型上完成的。這個在內存中的文檔樹將是文檔實際大小的幾倍。這樣做的好處是結構清除、操作方便,而帶來的麻煩就是極其耗費系統資源。而SAX正好克服了DOM的缺點。SAX解析器的處理過程是通讀整個文檔,根據文檔內容產生事件,而把對這些事件的處理交由事件處理器處理。SAX不需要在內存中保存整個文檔,它對系統資源的節省是顯而易見的。這樣在一些需要處理大型XML文檔和性能要求比較高的場合就要用SAX了。

下面的表格列出了SAX和DOM在一些方面的對照:
SAX DOM
順序讀入文檔并產生相應事件,可以處理任何大小的XML文檔 在內存中創建文檔樹,不適于處理大型XML文檔。
只能對文檔按順序解析一遍,不支持對文檔的隨意訪問。 可以隨意訪問文檔樹的任何部分,沒有次數限制。
只能讀取XML文檔內容,而不能修改 可以隨意修改文檔樹,從而修改XML文檔。
開發上比較復雜,需要自己來實現事件處理器。 易于理解,易于開發。
對開發人員而言更靈活,可以用SAX創建自己的XML對象模型。 已經在DOM基礎之上創建好了文檔樹。

通過對SAX和DOM的分析,它們各有自己的不同應用領域:

  1. SAX適于處理下面的問題:
  2. 對大型文檔進行處理。
  3. 只需要文檔的部分內容,或者只需要從文檔中得到特定信息。
  4. 想創建自己的對象模型的時候。

DOM適于處理下面的問題:

  1. 需要對文檔進行修改
  2. 需要隨機對文檔進行訪問,例如XSLT解析器。

對SAX的介紹到這里就告一段落了,希望能對大家有所幫助:),本文的絕大部分參考資料都來源于http://www.megginson.com/SAX/ 以及SAX API(雖然說SAX有了自己新的網站http://sax.sourceforge.net/ 但我從來沒有成功訪問過!) ,感謝David Megginson和其他SAX開發人員給我們提供了這么一個好東東。本文如有錯誤和不妥的地方還請大家指正。

附錄: SAX的版權聲明
SAX2 is Free!

I hereby abandon any property rights to SAX 2.0 (the Simple API for XML), and release all of the SAX 2.0 source code, compiled code, and documentation contained in this distribution into the Public Domain. SAX comes with NO WARRANTY or guarantee of fitness for any purpose.

David Megginson, david@megginson.com

2000-05-05

作者簡介
王曉強,萬千程序開發者中的一員,并樂在其中。熱愛java和linux,一直利用java和xml相關技術進行應用開發,并在這些方面積累了豐富經驗。可通過 grekiller@yeah.net 與他聯系。


2005-03-28 16:09 發表評論
]]>
應該使用SAX還是DOM?http://m.tkk7.com/jackybu/articles/2557.htmlMon, 28 Mar 2005 08:04:00 GMThttp://m.tkk7.com/jackybu/articles/2557.htmlhttp://m.tkk7.com/jackybu/comments/2557.htmlhttp://m.tkk7.com/jackybu/articles/2557.html#Feedback0http://m.tkk7.com/jackybu/comments/commentRss/2557.htmlhttp://m.tkk7.com/jackybu/services/trackbacks/2557.html創建于 : May 22, 1999
修改于 : May 23, 1999 5:57 pm

內容列表

--------------------------------------------------------------------------------

為什么他們同時存在

什么是DOM?

什么是SAX?

什么時候使用DOM

什么時候使用SAX

結論

為什么他們同時存在

--------------------------------------------------------------------------------

SAX (Simple API for XML) 和 DOM (Document Object Model) 都是為了讓程序員不用寫一個解析器就可以訪問他們的資料信息。通過利用XML 1.0格式保存信息,以及使用SAX或者DOM APIs你的程序可以使用任何解析器。這是因為使用他們所喜愛的語言開發解析器的開發者必須實現SAX和DOM APIs。 SAX和DOM APIs 對多種語言中都可以實現(Java, C++, Perl, Python, 其它...)。

所以SAX 和 DOM都是為了同樣的目的而存在,這就是使用戶可以利用任何編程語言訪問存入XML文檔中的信息(要有一個那種編程語言的解析器)。雖然他們在提供給你訪問信息的方法上大不相同。




什么是DOM?

--------------------------------------------------------------------------------

DOM 可以讓你以分層次對象模型來訪問儲存在XML文檔中的信息。DOM生成一棵節點樹(以XML文檔的結構和信息為基礎)你可以通過這棵樹來訪問你的信息。在XML文檔中的文本信息轉變成一組樹的節點。請看下圖:


不管你的XML文檔中的信息的類型 (不管是表格數據,或是一列items,或者只是文檔), DOM在你創建一個XML文檔的文檔對象時創建一棵節點樹。 DOM強迫你使用樹狀模型(就像 Swing TreeModel)去訪問你的XML文檔中的信息。這種模式確實不錯因為XML原本就是分層次的。這也是DOM為什么可以把你的信息放到一棵樹中的原因(即使信息是表格式的或者簡單的列表????這里不知道該怎么翻原文是:even if the information is actually tabular or a simple list??????)。

上圖是過分簡單的,因為在DOM中,每一個元素節點實際上都有一系列的其他節點作為它的孩子。這些孩子節點可以包含文本值或者是其他元素節點。乍看起來,通過遍歷訪問一個元素的所有孩子節點來訪問這個節點的值是沒有必要的(舉例來說:節點 "<name> Nazmul </name>", Nazmul是值)。如果每個元素只有值的話,這確實是沒有必要的。但是,元素可能含有文本數據或者其他元素;這是你要在DOM中做額外的工作來獲取元素節點值的原因。 通常當你的文檔中只有純數據時,把所有的數據壓成一個“塊“放到字串中并讓DOM把那個字串當成某個特定元素節點的值返回是適當的。這種方式并不適合如果在你的XML文檔中的數據是個文檔(比如像WORD文檔或者FRAMEMAKER文檔) 在文檔中,元素的順序是非常重要的。對于純數據(像一個數據庫表)元素的順序是不要緊的。 之所以DOM保持從XML文檔中讀出的元素的順序,因為它把所有的事物都當成文檔來處理。 文檔對像模型的叫法由此而來。

如果你計劃用DOM做為JAVA對象模型用于你存儲在XML文檔中的信息,那么你不需要考慮SAX。可是如果你發現DOM不是一個可以用于處理XML文檔信息的好的對象模式,那么你可能想看看SAX了。在一些必須使用自定義對象模型的案例中使用SAX是非常普遍的。說一句讓事情看來有些糊涂的話,你也可以在DOM的基礎之上創建自己的對象模式。面向對象真是個好東東。




什么是SAX?

--------------------------------------------------------------------------------

SAX讓你訪問儲存在XML文檔中的信息,不是通過節點樹,而是一系列的事件。你會問,這有什么益處?回答是,SAX選擇不在XML文檔上創建JAVA對象模型(像DOM做的那樣)。 這樣使得SAX更快, 同時使下面所述成為必要:

創立你自己的自定義對像模型
創建一個監聽SAX事件的類同時創建你自己的對象模型
注意這些步驟對DOM而言是不必要的,因為DOM已經為你創建了一個對象模型(將你的信息用一棵節點樹表示)。

在使用DOM的情況下,解析器做了絕大多數事情, 讀入XML文檔, 在這基礎之上創建JAVA對象模型,然后給你一個對這個對象的引用(一個 Document對象),因而你可以操作使用它。SAX被叫做Simple API for XML不是沒有原因的, 她真的很簡單。 SAX沒有期待解析器去做這么多工作,所有SAX 要求的是解析器應該讀入XML文檔,同時根據所遇到的XML文檔的標簽發出一系列事件。你要自己寫一個XML文檔處理器類(XML document handler class)來處理這些事件,這意味著使所有標簽事件有意義還有用你自己的對象模型創建對象。所以你要完成:

控制所有XML文檔信息的自定義對象模型(或者源文檔在這里的寫法從來沒有見過,或者懷疑源文檔在這里有排版錯誤,先這么翻了)
一個監聽SAX事件(事件由SAX解析器讀取你的XML文檔時產生)的文檔處理器,還有解釋這些事件創建你自定義對象模型中的對象
如果你的對象模型簡單的話那么SAX在運行時會非常快。在這種情況下,它會比DOM快,因為它忽略了為你的信息創建一個樹形對象模型的過程。從另一方面來說,你必須寫一個SAX 文檔處理器來解釋所有的SAX事件(這會是一件很繁重的工作)。

什么類型的SAX事件被SAX解析器拋出了哪? 這些事件實際上是非常簡單的。SAX會對每一個開始標簽拋出事件,對每一個結束標簽也是如此。它對#PCDATA和 CDATA 部分同樣拋出事件。你的文檔處理器 (對這些事件的監聽器)要解釋這些事件同時還要在他們基礎之上創建你自定義的對象模型。 你的文檔處理器必須對這些事件做出解釋,同時這些事件發生的順序是非常重要的。SAX同時也對processing instructions, DTDs, comments, 拋出事件. 但是它們在概念上是一樣的, 你的解析器要解釋這些事件(還有這些事件的發生順序)以及使他們有意義。





什么時候使用DOM

--------------------------------------------------------------------------------

如果你的XML文檔包含文檔數據(例如, Framemaker documents stored in XML format), 那么DOM就是你的解決方案的最自然選擇。如果你要創建一些類似于文檔信息管理的系統,那么你不得不處理大量的文檔數據。Datachannel RIO 產品就是這么一個例子,它可以索引和組織各種類型文檔資源中的信息(例如Word和Excel 文件)。在這種情況下,DOM是非常合適程序去訪問存貯在這些文檔中的信息的。

然而,如果你主要處理的是結構化的數據(在XML中的序列化的JAVA對象the equivalent of serialized Java objects in XML),DOM不是最好的選擇。那就是SAX會比較合適的地方。






什么時候使用SAX

--------------------------------------------------------------------------------

如果在你XML文檔中的信息是機器易讀的(和機器生成的)數據,那么SAX是讓你可以訪問這些信息的合適的API。機器易讀和生成的數據類型包含像下面這些東東:

存成XML格式的Java對象屬性
用一些以文本為基礎的查詢語句(SQL, XQL, OQL)表示的查詢
由查詢生成的結果集(這也許包含關系型數據庫表中的數據編碼成XML).
這么看來機器生成的數據是你一般要在java中生成數據結構和類的信息。一個簡單的例子是包含個人信息的地址簿,在上圖所示。這個地址簿xml文件不像字處理器文檔,它是一個包含已經被編碼成文本的純數據的XML文檔。

當你的數據是這種樣式,你要創建你自己的數據結構和類(對象模型)來管理操作以及持續保存這些數據。SAX容許你快速創建一個可以生成你的對象模型實例的處理器類。一個實例是:一個SAX文檔處理器。它完成的工作有讀入包含我的地址薄信息的XML文檔,創建一個可以訪問到這些信息的AddressBook類。SAX指南告訴你該怎么做到這些。這個地址薄XML文檔包含person元素,person元素中有name和email元素。我的AddressBook對象模型包括下面的類:

AddressBook 類,Person對象的容器
Person 類,String 型的name和email的容器
這樣我的“SAX 地址簿文檔處理器”可以把person元素轉變成Person對象了,然后把它們都存入AddressBook對象。這個文檔處理器將name和email元素轉變為String對象。




結論

--------------------------------------------------------------------------------

你寫的SAX文檔處理器(SAX document handler)做了將元素映射為對象的工作。如果你的信息被結構化成可以容易創建這樣的映射,你應該使用SAX API。從另一方面來說,如果你的數據更適宜用樹來表示那么你應該使用DOM。



2005-03-28 16:04 發表評論
]]>
JDOM使XML在Java中的操作比以往任何時候都更加容易。 http://m.tkk7.com/jackybu/articles/2445.htmlFri, 25 Mar 2005 09:11:00 GMThttp://m.tkk7.com/jackybu/articles/2445.htmlhttp://m.tkk7.com/jackybu/comments/2445.htmlhttp://m.tkk7.com/jackybu/articles/2445.html#Feedback0http://m.tkk7.com/jackybu/comments/commentRss/2445.htmlhttp://m.tkk7.com/jackybu/services/trackbacks/2445.html碰巧,你在過去可能已經用過眾多Java庫中的某一個以操作XML數據結構。那么JDOM(Java文檔對象模型,Java Document Object Model)的用途是什么?開發者又為什么需要它呢?

JDOM是用于Java優化的XML數據操作的源代碼開放的Java庫。盡管它與萬維網協會(W3C,World Wide Web Consortium)的DOM類似,但它是一種可供選擇的文檔對象模型,它不是在DOM基礎上構建的,也不以DOM為模型。它們的主要區別是:DOM是與語言無關的,最初是用于HTML頁的JavaScript操作,而JDOM專用于Java,因此它利用Java的特性,其中包括方法過載(method overloading)、集合(collection)、映像(reflection)和人們熟悉的編程風格等。對Java編程人員來說,JDOM顯得更自然、更"適用"。就像Java優化的RMI(remote method invocation,遠程方法調用)庫要比與語言無關的CORBA(Common Object Request Broker Architecture,通用對象申請中介體系結構)更自然一樣。

你可以根據源代碼開放的Apache類型(商業友好)的許可,在jdom.org找到JDOM。它是合作設計開發的,并擁有超過3000個訂閱者的郵件列表。此庫已經被Sun公司的Java社區進程(JCP,Java Community Process)接受作為Java規范申請(JSR-102),并很快成為正式Java規范。

本系列文章將對JDOM進行技術介紹。本文提供一些關于重要類的信息。下一篇文章將介紹如何在自己的Java程序中應用JDOM。

ORACLE XML工具

XML Developer Kit(XDK)是Oracle為開發人員提供的免費XML工具庫。它包括可以與JDOM一起使用的XML分析器和XSLT轉換引擎。您可以在Oracle XML主頁oracle.com/xml上找到關于這些工具的很多信息。

要下載這個分析器,請查找名字為"XDK for Java"的XML Developer Kit。點擊下載鏈接左欄中的"Software"。將該軟件解壓縮后,文件xalparserv2.jar中即包括該分析器。

為了配置JDOM和其他軟件,以便以缺省方式使用Oracle分析器的軟件,需要將JAXP的javax.xml.parsers.SAXParserFactory系統屬性設置為oracle.xml.jax. JXSAXParserFactory。它告訴JAXP你選擇使用Oracle分析器。最簡單的設置方法是采用命令行:

java -Djavax.xml.parsers.SAXParserFactory=
oracle.xml.jaxp.JXSAXParserFactory

也可以通過編程來設置:

System.setProperty("jaxax.xml.parsers
.SAXParserFactory",

"oracle.xml.jaxp.JXSAXParserFactory");

除了XDK之外,Oracle還在Oracle9i數據庫版本2中提供了一個本地XML知識庫。Oracle9i XML數據庫(XDB)是一個高性能、本地XML存儲和檢索的技術。它將W3C數據模型全面應用到Oracle9i數據庫中,并為定位和查詢XML提供了新的標準訪問方法。采用XDB可以充分利用關系型數據庫技術和XML技術的優點。

JDOM軟件包結構

JDOM庫包括六個軟件包。第一個是org.jdom包,它包括表示XML文檔以及其組件的類,這些組件有:Attribute、CDATA、Comment、DocType、Document、Element、EntityRef、Namespace、Processing Instruction和Text等。如果你對XML很熟悉,通過類的名字就可以知道它的用途了。

下一個是org.jdom.input包,它包括用于構建XML文檔的類。其中最主要和最重要的類是SAXBuilder。SAXBuilder通過監聽輸入的XML簡單API(SAX,Simple API for XML)事件建立相應的文檔。如果需要由一個文件或其他流(stream)建立文檔,那就可以采用SAXBuilder。它采用SAX分析器讀取流,并根據SAX分析器的"回調"建立文檔。這一設計的一個好處就是SAX分析器越快,SAXBuilder也會越快。另一個主要輸入類是DOMBuilder。DOMBuilder由DOM樹構建。如果預先已經有了一個DOM樹,并希望用JDOM版本的樹來代替,那么采用DOMBuilder是非常方便的。

對于這些可能的生成器來說,不存在什么限制。例如,由于Xerces有Xerces本地接口(XNI,Xerces Native Interface),可用于在比SAX較低的層次上進行操作,所以編寫一個XNIBuilder以支持某些分析器知識,而不采用SAX,就可能很有意義。一個構成JDOM項目的很流行的生成器是ResultSetBuilder。它利用JDBC結果集合,生成一個XML文檔,用以表示SQL結果,根據是哪種元素和哪種屬性,它有多種不同的編排。

org.jdom.output包中包括輸出XML文檔的一些類。其中最重要的類是XMLOutputter。它將文檔轉換為一個字節流,用于輸出到文件、流和接口程序(sockets)中。XMLOutputter具有很多支持原始輸出(raw output)、完美輸出(pretty output)、壓縮輸出或其他輸出的特殊配置選項。它是一個相當復雜的類。這可能是為什么DOM level2中仍然沒有這種能力的原因。

其他輸出器(outoutter)包括SAXOutputter,它可以產生基于文檔內容的SAX事件。盡管它看起來有些神秘,但事實證明這個類在XSLT變換中極其有用,這是因為相對于將文檔數據傳遞到一個引擎的字節來說,SAX事件是一種更為有效的方法。還有一個DOMOutputter輸出器,它用于生成表示文檔的DOM樹。一個有趣的輸出器是JTreeOutputter,它只有幾十行代碼,它可以建立表示文檔的JTree。將它與ResultSetBuilder聯合起來后,僅用幾行代碼就可以通過SQL查詢得到結果的樹視圖。

注意,與在DOM中不同,文檔并不與它們的生成器捆綁在一起。這就形成了一個出色的模型,在這種模型中,你可以用一些類保持數據,另一些類構造數據,其他一些類來定制數據。可以隨意進行混合和匹配。

org.jdom.transform和org.jdom.xpath包中還有支持內置的XSLT轉換和XPath查找的類。

最后,org.jdom.adapters包中還包括在DOM交互中對庫提供幫助的類。該庫的使用者永遠不需要調用這一軟件包中的類。這些類是隨時可用的,因為每個DOM實現都為特定的引導任務生成不同的方法名稱,所以適配器類將標準調用轉換成分析器專用的調用。Java API for XML Processing(JAXP)為解決此問題提供了另一種方法,事實上降低了對這些類的需要,但是這些類仍然保留了下來,這是因為由于許可證的原因,并不是所有的分析器都支持JAXP,也不是任何地方都安裝有JAXP。

生成文檔

文檔由org.jdom.Documentclass表示,你可以從頭建立一個文檔:

// This builds: <root/>

Document doc = new Document(new Element("root"));

也可以由文件、流、系統ID或URL建立一個文檔:

// This builds a document of whatever's in the given resource
SAXBuilder builder = new SAXBuilder();
Document doc = builder.build(url);

將幾個調用結合在一起,可以很容易地在JDOM中建立一個簡單的文檔:

// This builds: <root>This is the root</root>
Document doc = new Document();

Element e = new Element("root");
e.setText("This is the root");
doc.addContent(e);

如果你是一位非常有才能的用戶,你可能更愿意使用"方法鏈",在這種方式中,多種方法是依次調用的。這種方式之所以有效是因為集合方法返回它們所作用的對象,下面是一個例子:

Document doc = new Document(
  new Element("root").setText("This is the root"));

為了進行對比,下面給出如何用JAXP/DOM創建相同的文檔:

// JAXP/DOM
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.newDocument();
Element root = doc.createElement("root");
Text text = doc.createText("This is the root");
root.appendChild(text);
doc.appendChild(root);

用SAXBuilder生成文檔

如前所示,SAXBuilder提供了一種由任意面向字節的數據源來創建文檔的簡單機制。缺省的無參數SAXBuilder()構造器在后臺利用JAXP來選擇一個SAX分析器。如果你希望改變分析器,則可以設置javax.xml.parsers.SAXParserFactory系統屬性,以指向你的分析器提供的SAXParser Factory實現。對于Oracle9i版本2 XML分析器,應當這樣做:

java -Djavax.xml.parsers.SAXParserFactory=
oracle.xml.jaxp.JXSAXParserFactory YourApp

對于Xerces分析器,應當這樣做:

java -Djavax.xml.parsers.SAXParserFactory=org.apache.xerces.jaxp
.SAXParserFactoryImpl YourApp

如果沒有安裝JAXP,SAXBuilder缺省地指向Apache Xerces。一旦你創建了SAXBuilder實例,你就可以在生成器中設置幾個屬性,其中包括:

setValidation(boolean validate)

相關鏈接

開放的源JDOM庫
jdom.org

Java Servlet編程(第二版),作者:Jason Hunter(O'Reilly及其聯合出版機構出版,2001)
www.oreilly.com

這一方法告訴分析器在生成過程中是否根據Document Type Definition(DTD,文檔類型定義)進行驗證。缺省設置為"否"(off)。所用的DTD是文檔中DocType引用的一個。根據任何其他DTD進行驗證是不可能的,因為還沒有分析器支持這一功能。

setIgnoringElementContentWhitespace(boolean ignoring)

這一方法告訴分析器是否忽略元素內容中所謂的"可忽略空格(whitespace)"。按照XML1.0規范,元素內容中的空格必須由分析器保留,但當根據DTD進行驗證時,分析器可能知道文檔的特定部分不會支持空格,所以這一區域的任何空格都是"可忽略的"。其缺省設置為"否"(off)。如果你不希望使一個文檔"往返旅行",將輸入內容以原樣輸出的話,那么將這一開關打開通常會使性能略微有些提高。注意,這一標志只有在驗證已完成時才有效。而進行驗證會導致性能下降,所以這一技巧僅在已應用驗證時才有用。

setFeature(String name, String value)

這一方法設置基礎SAX分析器的一個特性。這是一個原始的"傳遞(pass-through)"調用,所以在應用這一方法時應非常小心,因為對特性的錯誤設置(如弄錯名稱空間)可能會中斷JDOM行為。而且,依靠任何特定分析器特性都會限制可移植性。這一調用對于啟用模式驗證最為有用。

setProperty(String name, Object value)

這一方法設置基礎SAX分析器的一個屬性。它也是一個原始的"傳遞"調用,它具有同樣的風險,而且對于有才能的用戶同樣有用,特別是對于模式驗證。下面的代碼結合這些方法,利用JAXP選擇的分析器讀取本地文件,驗證功能有效,可忽略的空格都被忽略。

SAXBuilder builder = new SAXBuilder();
builder.setValidation(true);
builder.setIgnoringElementContentWhitespace(true);
Document doc = builder.build(new File("/tmp/foo.xml"));

用XMLOutputter輸出

文檔可以以多種不同的格式輸出,但最常見的輸出格式還是字節流。在JDOM中,XMLOutputter類提供這一能力。它的缺省無參數生成器試圖忠實地輸出一個與內存中貯存的完全一樣的文檔。下面的代碼向一個文件生成一個文檔的原始表示。

// Raw output
XMLOutputter outp = new XMLOutputter();
outp.output(doc, fileStream);

如果你不關心空格,那么你可以對文本塊進行整理,以節省一點帶寬:

// Compressed output
outp.setTextTrim(true);
outp.output(doc, socketStream);

如果希望文件打印得很漂亮,使其展示給人們看,則可以增加一些縮進空格,并增加一些新行:

outp.setTextTrim(true);

outp.setIndent("  ");
outp.setNewlines(true);
outp.output(doc, System.out);

對于已有格式化空格的文檔,要打印得很漂亮,就一定要進行整理。否則,就會在已經格式的文檔上添加新的格式,使打印的文檔顯得很難看。

定位元素樹

JDOM使得元素樹的定位非常容易。為了取得根元素,可調用:

Element root = doc.getRootElement();

要得到它所有子元素的列表:

List allChildren = root.getChildren();

僅想得到具有給定名稱的元素:

List namedChildren = root.getChildren("name");

僅想得到具有給定名稱的第一個元素:

Element child = root.getChild("name");

getChildren()調用返回的列表是java.util.List,它是所有Java編程人員都知道的列表(List)接口的一個實現。這一列表令人感興趣的地方在于它是活的。該列表的任何變化都會在支持的文檔中立即反應出來。

// Remove the fourth child
allChildren.remove(3);
// Remove children named "jack"
allChildren.removeAll(root.getChildren("jack"));
// Add a new child, at the tail or at the head
allChildren.add(new Element("jane"));
allChildren.add(0, new Element("jill"));

利用該List隱喻(List metaphor)可以不必增加過多的方法而進行許多元素操作。但是為了方便起見,在結尾增加元素或者刪去給定名字的元素之類的常用任務都有涉及到元素(Element)自身的方法,而不需要首先得到該List:

root.removeChildren("jill");
root.addContent(new Element("jenny"));

采用JDOM的一個好處就是它可以很容易地在一個文檔內或在文檔之間移動元素。兩種情況的代碼相同,如下所示:

Element movable = new Element("movable");
parent1.addContent(movable);    // place
parent1.removeContent(movable); // remove
parent2.addContent(movable);    // add

采用DOM時,移動元素就沒有這么容易,因為在DOM中,元素是與它的生成工具緊緊聯系在一起的。所以在文檔之間移動DOM元素時,必須將其"導入"。

而采用JDOM時,你需要記住的唯一一件事件就是在將一個元素增加到其他位置之前,需要先先將它刪除,這樣你就不會在樹中形成循環。detach()方法可以用一行代碼完成分離/增加:

parent3.addContent(movable.detach());

如果在將一個元素增加到另一個父元素之前忘了將它分離,則該庫會產生一個異常(帶有一個真正準確而有用的錯誤信息)。該庫還會檢查元素的名字和內容,以確保它們不包括空格之類的不恰當字符。它還會驗證其他一些規則,如只有一個根元素、名稱空間的聲明是一致的、在注釋和CDATA部分沒有禁止使用的字符串等等。這一特性能夠盡可能早地在該進程中進行"格式正確性"(well-formedness)錯誤檢查。

處理元素的屬性

元素屬性的格式如下所示:

<table width="100%" border="0"> ... </table>

利用對元素的引用,可以要求元素的任意給定屬性值:

String val = table.getAttributeValue("width");

也可以將屬性看作一個對象,用于進行一些特殊的操作,如類型變換等:

Attribute border = table.getAttribute("border");
int size = border.getIntValue();

要設置或改變一個屬性,可以采用setAttribute():

table.setAttribute("vspace", "0");

要刪除一個屬性,可以采用removeAttribute():

table.removeAttribute("vspace");

處理元素文本內容

一個文本內容類似于:

<description>
  A cool demo
</description>

在JDOM中,這一文本可以通過調用直接獲得:

String desc = description.getText();

要記住,因為XML 1.0規范要求保留空格,所以它會返回"\n A cool demo\n"。當然,有經驗的編程人員常常不希望得到格式化的空格。有一個很方便的方法可以檢索文本而同時忽略其中的空格:

String betterDesc = description.getTextTrim();

如果你真地希望去掉空格,那么有一個getTextNormalize()方法可以將內部空白成為一個標準空格。這一方法對于類似于下面這樣的文本是非常方便的:

<description>
  Sometimes you have text content with formatting
  space within the string.
</description>

要改變文本內容,可以應用setText()方法:

description.setText("A new description");

這一文本內的任何特殊字符都可以正確地被解釋為一個字符,并根據需要在輸出時刪除,使以保持文本的語法正確。比如說你進行了這樣一個調用:

element.setText("<xml/> content");

在內部存儲中,仍然將這些字符串看作字符。不會對其內容進行隱式分析。在輸出中,你將看到:

&lt;xml/&gt; content<elt>

這一操作保留了前面setText()調用的語義。如果你希望在一個元素中保留XML內容,則必須增加相應的JDOM子元素對象。

處理CDATA節也可能在JDOM內進行。一個CDATA節指出不應被分析的文本塊。它實質上是一個"語法糖塊(syntactic sugar)",它允許很容易地包含HTML或XML內容,而不需要很多&lt;和&gt;換碼字符。要建立一個CDATA節,只需要在CDATA對象內封裝字符串即可:

element.addContent(new CDATA("<xml/> content"));

JDOM最了不起的地方是getText()調用返回字符串時,不會麻煩調用程序去判斷它是否由CDATA節表示。

處理混合內容

一些元素包括有很多元素,如空格、注釋、文本、子元素,以及其他元素:

<table>
  <!-- Some comment -->
  Some text
  <tr>Some child element</tr>
</table>

如果一個元素中同時包含文本和子元素,就說它包含有"混合內容" 。處理混合內容可能是非常困難的,但JDOM使它變得非常容易。標準使用情況--返回文本內容和定位子元素--都非常簡單:

String text = table.getTextTrim();  // "Some text"

Element tr = table.getChild("tr");  // A straight reference

對于需要注釋、空格塊、處理指令和實體引用這樣一些更復雜的應用來說,原始混合內容可以作為一個List(列表)來提供:

List mixedCo = table.getContent();
Iterator itr = mixedCo.iterator();
while (itr.hasNext()) {
  Object o = i.next();
  if (o instanceof Comment) {
    ...
  }
  // Types include Comment, Element, CDATA, DocType,
  // ProcessingInstruction, EntityRef, and Text
}

就像子元素列表一樣,對原始內容列表的改變會影響到支持的文檔:

// Remove the Comment.  It's "1" because "0" is a whitespace block.
mixedCo.remove(1);

如果你的觀察力很強,則你會注意到這里有一個Text類。JDOM在內部采用Text類存儲串內容,從而允許串具有"父輩(parentage)"關系,并更容易支持XPath訪問。作為一個編程人員,在檢索或設置文本時,不需要擔心這種類,而只需要在訪問原始內容列表時關心它就行了。.

對于DocType、處理指令和EntityRef類的詳細內容,請參見jdom.org上的API文檔。

關于第2部分

在本文中,開始研究了如何在應用程序中應用JDOM。在下一篇文章中,我將研究XML Namespace、ResultSetBuilder、XSLT和XPath。你現在可以在otn.oracle.com/oraclemagazine中找到這一系列文章的第二部分。

Jason Hunter (jasonhunter@servlets.com) 是一位顧問、Servlets.com的發行人,Apache軟件基金會的副總裁。他還是JCP執行委員會的委員



2005-03-25 17:11 發表評論
]]>
從 XML 到 Java 代碼的數據綁定,系列之一(共四部分) http://m.tkk7.com/jackybu/articles/2392.htmlWed, 23 Mar 2005 22:10:00 GMThttp://m.tkk7.com/jackybu/articles/2392.htmlhttp://m.tkk7.com/jackybu/comments/2392.htmlhttp://m.tkk7.com/jackybu/articles/2392.html#Feedback0http://m.tkk7.com/jackybu/comments/commentRss/2392.htmlhttp://m.tkk7.com/jackybu/services/trackbacks/2392.htmlBrett McLaughlin
Enhydra 戰略家,Lutris 科技公司
2000 年 7 月

內容:
 分析各種選擇
 約束數據
 利用約束
 來自 XML Java
 總結
 參考資料
 作者簡介

在這個由四部分組成的系列文章的第一部分,我們將弄清什么是數據綁定,與在 Java 應用程序中處理 XML 數據的其它方法相比它有什么優勢,以及如何開始使用它。這一部分將考查為什么使用數據綁定,以及如何為各種約束建立模型,使 XML 文檔能轉換成 Java 對象。同時還涵蓋用于生成數據綁定類的輸入和輸出。

您希望在您的 Java 應用程序中使用 XML 嗎?那么好,同成千上萬的其他人一起上這條船吧。當您深入了解 XML 以后,也許您會發現 DOM 和 SAX API(請參閱參考資料)不過是唬人的東西。您可能認為肯定存在某種簡單方法可以取得 XML 文檔,并通過 Java 應用程序訪問它,對嗎?不必通過回調或復雜的樹狀結構,而是使用像 setOwner(Stringowner)int getNumOrders() 這樣的方法,對嗎?如果您曾經沿著這一思路考慮問題,那么數據綁定就是您要尋找的解決方案。

分析各種選擇
當今各種 XML 和 XML 主義正泛濫成災(XSL、RDF、命名空間、RSS、XML Schema、XSLT...),您可能認為現在會有很多方法去訪問 Java 應用程序中的 XML 數據。令人驚訝的是,如果您尋根究底,實際只存在三種訪問 XML 數據的方法。沒錯 -- 只有三種方法,其中的一種還是最近隨一種新的 Java API 才出現的。

應該這樣來看待這一問題:選擇范圍小使您更易于選出適合于您的方法。

回調
回調是作為一種事件驅動模型工作的。當分析 XML 文檔時,某些事件 -- 如文檔的起始和某個元素中的字符數據的起始 -- 將觸發回調方法。通過使用執行邏輯所需的數據,您可以實現這些事件的 Java 代碼。要弄清這種方法不能全靠直覺;開發人員通常要花費一段時間來理解和掌握回調模型的使用。SAX,用于 XML 的一種簡單 API,是這種 XML 使用方法的事實上的標準。


更常見、更流行的是這種 API,它們取得一個 XML 文檔,然后創建數據的樹狀結構。XML 文檔成為樹首,充當一種容器。它有若干子級,如根元素。根元素又有其附加的子級,依此類推,直到(在某種意義上)獲得 XML 數據的一幅圖為止。因為幾乎每個大學生在某個階段肯定都處理過樹狀結構,所以這就可用作表示 XML 數據的一種非常直觀的方法。

用于 XML 文檔樹狀表示的最流行的 API 就是 W3C 的推薦標準,即文檔對象模型 (DOM)。一種更新的 API,JDOM (這不是首字母縮寫詞)最近也正一直在推廣并流行開來。(雖然這個方案是我和 Jason Hunter 建立的,但我還得說實話。)另外,DOM 和 JDOM 都是 Spinnaker 方案設計的基本要求,Spinnaker 是一種新的 XML 分析器,它作為 Apache XML 方案的一部分正在開發之中。

雖然樹狀 API 看起來比事件驅動的 SAX 更易于使用,但它們并不總是合適的。非常大的文檔可能需要大量的內存(尤其是使用 DOM 時);當對樹結構執行轉換 (XSLT) 時,系統可能停止運轉甚至徹底崩潰。雖然更新的 API(如 JDOM)能處理這些問題,但如果您必須處理極大量的數據,它們仍將是一個問題。并且,有時開發人員寧愿將 XML 文檔中的數據建模為一個簡單的帶有值的讀寫方法的 Java 對象,而不用樹狀模型工作。例如,開發人員會寧愿不去訪問名為 skuNumber 的子節點并設置該節點的文本值,而只想調用 setSkuNumber("mySKU") 并繼續進行。

數據綁定
用 Java 代碼訪問 XML 數據的最新方法要依賴于一套新的 Java 方法和相關的 API,這些 API 仍在開發之中。數據綁定是由 Sun 構建的一種“Java 規范要求”(JSR-031,見
參考資料),它設計用于使 Java 對象綁定到 XML 文檔更加方便,這樣就使一種格式能夠容易地轉換為另一種格式,反之亦然。綁定引用一個具有讀寫方法的 Java 對象,讀寫方法都會影響到底層的 XML 文檔,并且也都直接映射為 XML 文檔中的元素及特征的名稱。當您進入到本系列文章下一部分中的某些細節時,這一說明會更有意義,但在目前,只說一點就夠了:這樣做使 XML 文檔特征 name 能夠通過一個稱為 setName() 的方法,來更改它的值,就像我上面暗示的那樣。

這種訪問方式正在得到普及,并且當在 XML 文檔中存儲配置信息時特別有用。許多開發人員發現,它非常便于直接訪問所需的參數,而無須使用更復雜的樹狀結構。雖然這種訪問對于文檔轉換或消息傳送沒有什么用處,但它對于簡單數據處理是極其方便的。它是我們在本文及本系列文章中關注的第三種使用 XML 的方法。

(當然,任何方法隨后都會引出一系列新的術語,所以請查看術語解釋以了解這些新的行話。)

是否任何 XML 文檔都可以轉換為 Java 對象?還是僅有某些類型的 XML 文檔才可以?問得好!您很可能只希望將滿足一組約束條件的文檔轉換為 Java 對象。這與定義 Java 接口的方法類似:您確保只實例化和使用適應該接口的對象,允許就如何操作該對象作出假定。同樣,您只允許將滿足一組約束條件的 XML 對象轉換成 Java 對象;這使您能夠按希望的方式來使用所創建的對象。

約束數據
在研究代碼之前,您需要回答幾個有關如何表示 XML 數據的問題。這是數據綁定的最具挑戰性的方面之一。是為每個文檔創建一個新類,還是創建某個現有類的一個實例?您要使用哪個現有類?并且最重要的是,您正在處理的文檔是否適宜轉換為 Java 對象?

那是一大堆問題,但您將在這里找到全部答案。將這些問題看作一系列決策點,一系列選擇。首先,您必須確定您能否從該 XML 文檔創建 Java 對象(如前面所討論的那樣)。如果能,您就要決定轉換應該以新 Java 類的形式出現,還是僅以現有類的一個實例的形式出現。最后,如果選擇了現有類,那么使用哪個類呢?結果就是各種各樣的決策樹。

如果我們考察清單 1 中所示的一個示例 XML 文檔,然后再來處理這些問題,則決策樹的意義就更加清楚了。此示例文檔表示 Enhydra Application Server 中某個服務(具體說就是一個 web 容器)的配置。

清單 1. 一個用于配置 Enhydra 中的 web 容器的 XML 文檔
<?xml version="1.0"?>

<webServiceConfiguration 
    version="1.0"
    name="My Web Container"
>

  <port number="80"
        protocol="http"
        protected="false"
  />

  <document root="/usr/local/enhydra/html"
            index="*.html,*.xml"
            error="error.html"
  /> 

</webServiceConfiguration>       

此配置文檔包含有關服務本身的版本和名稱的信息,以及幾個嵌套的項目,每個項目都表示有關該 web 容器服務的一些附加信息。它給出了有關端口的詳細信息(包括端口號、協議和安全性),也給出了文檔服務信息(包括文檔根、用于索引頁的默認擴展名以及錯誤頁)。所有這些合在一起,就是配置一個新的 web 容器服務所需的全部信息。

記住這個示例,您就可以開始回答數據表示的各個問題了。

是否適合轉換?
絕對適合!只要看一看
清單 1 中的 XML 文檔就會發現,它表示一個對象(總體配置對象),具有若干特征或變量。其中某些變量又是另外的對象(端口和文檔),這些對象又具有它們自己的特征。實際上,這是適合轉換為 Java 對象的 XML 文檔的一個極好例子。為了進一步保證此對象是可用的,稍后我將向您展示一種方法來約束文檔中的數據。但是,還是先讓我們繼續沿著決策樹往下走。

轉換成類還是實例?
解決適宜性問題以后,現在就可以作出決定,是將每個 XML 配置文檔都變為一個全新的 Java 類呢,還是簡單地將其變為某個現有類的一個新實例。換句話說,就是到底應該生成新代碼,還是利用現有的代碼。照這樣來看,這就變成了一個簡單的可重用性問題。更容易且更明智的做法是,為每個 XML 文檔生成現有類的新實例。如果您一定要嘗試一下從每個文檔創建一個新的 Java 類,則得到的各個類之間可能沒有兼容性 -- 即兩個完全相同的文檔可能導致兩個不同的 Java 類!

不用這個可能引起混亂的方法,您可以采用一組 XML 約束條件(由一個 DTD 或 XML 方案表示,將在下面講述),并根據這些約束條件來生成一個 Java 類(或多個類,根據需要)。這個生成的類將表示符合這些約束條件的任何 XML 文檔;這些 XML 文檔中的每一個都將被解包到生成的類的一個實例中。在這種情況下,就可以為表示 web 服務配置的文檔定義約束條件。這些約束條件將被映射為一個 Java 類,我們將稱之為 WebServiceConfiguration。然后您就可以獲得任何一種表示特定 web 服務配置的 XML 文檔,并假定此文檔符合我們的約束條件,由它而創建出前面生成的類的一個實例。這將允許應用程序將不同的 XML 文檔用作相同類型的對象,只要這些文檔中的數據對于該對象設計時要達到目的來說是有效的即可。

新類還是現有的類?
現在您也已經有條件回答下一個問題了:您希望創建一個現有類即 WebServiceConfiguration 類的一個實例。剩下需要弄清的全部事情是,這個類是如何預先生成的。所以,現在請將您的注意力集中在這樣一個問題上:如何獲得一組約束條件,用 XML 實現它們,并保證文檔符合這些約束?再一個問題就是,您如何再從這些約束條件生成一個可重用的 Java 類?

利用文檔約束條件
既然您知道此文檔將轉換成一個 Java 實例,這就產生了另一個問題:要考慮到必須以某種方式保證可將此文檔正確地解包到一個選定的 Java 類中。缺少變量或數據類型不正確都可能導致在解包過程中出錯 -- 或者甚至在客戶機訪問配置錯誤的容器時出現運行時異常。

最好的情況是,在實際的解包過程開始之前,文檔的作者能夠保證,配置文檔對于他們選擇用來表示數據的類是“合法的”。閱讀到這一方案的 XML 人士說不定就會轉動他們的眼睛并嘀咕說,“好吧,當然您將使用 XML 文檔約束條件。”確認數據對選定類的合法性可以通過引用 DTD (文檔類型定義)或 XML 方案來完成。

通過使用一組用外部 DTD 或方案文件表示的約束條件,文檔作者就可以在這些數據的“接口”上測試配置數據。換句話說,您可以這樣來建立應用程序,使之能夠對照所需的數據來檢查包含在 XML 實例文檔中的數據,而所需數據則是在文檔約束條件的外部文件中指定的。這樣,您就可以為數據創建一個接口。

在使用 DTD 方案還是使用 XML 方案之間作出決策是一種相當簡單的選擇:因為 Java 語言是高度類型化的,所以我們希望能在 XML 文檔中支持類型化。例如,數據接口應該能夠驗證,為 web 容器的端口號提供的是整數,而不是字符串,后者在服務啟動時將引起錯誤。DTD 不支持類型檢查,所以我們無疑應該選擇 XML 方案。雖然 XML 方案在規范的領域在有一點不確定性,但它在很大程度上已趨于穩定,并可望在年內定型。我們可以在一個 XML 方案中編寫數據約束條件,然后用這個方案驗證可能的實例文檔,以確保解包能在這些實例文檔上進行。下面的 XML 方案表示我們的 web 容器服務的數據接口。

清單 2. 表示一個 web 容器配置文檔的數據接口的 XML 方案
<?xml version="1.0"?>

<schema targetNamespace="http://www.enhydra.org"
        xmlns="http://www.w3.org/1999/xmlSchema"
        xmlns:enhydra="http://www.enhydra.org"
>

  <complexType name="ServiceConfiguration">
    <attribute name="name" type="string" />
    <attribute name="version" type="float" />
  </complexType>


  <element name="serviceConfiguration" type="ServiceConfiguration" />

  <complexType name="WebServiceConfiguration" 
               baseType="ServiceConfiguration"
               derivedBy="extension">
    <element name="port">
      <complexType>
        <attribute name="protocol" type="string" />
        <attribute name="number" type="integer" />
        <attribute name="protected" type="string" />
      </complexType>
    </element>

    <element name="document">
      <complexType>
        <attribute name="root" type="string" />
        <attribute name="index" type="string" />
        <attribute name="error" type="string" />
      </complexType>
    </element>
  </complexType>

  <element name="webServiceConfiguration" type="WebServiceConfiguration" />

</schema>

清單 2 中的 XML 方案定義了幾個不同的數據對象,它們合起來表示一個 web 服務的配置對象。首先,定義了一個核心服務配置(serviceConfiguration),它包含版本和名稱。這可用作所有服務(如負載均衡服務、EJB 容器,當然還有我們的 web 服務)的基本對象。然后,作為此基本服務的擴展,又定義了 web 服務配置(webServiceConfiguration)。請注意,Java 成型之后,方案就已經為數據接口建立了模型。我們將附加的 web 服務屬性 portdocument 添加到 versionname 基本屬性中。這些屬性本身都是對象,具有它們自己的屬性(protocolrooterror 等)。

在此方案的定義方式中,特征代表簡單的 Java 類型,通常是原始 (primitive) 類型。這樣,nameversion 就分別成為類型 Stringfloat 的 Java 原始類型。portdocument 這樣的元素成為 Java 對象,它們可以有自己的屬性,還是用特征來表示。這樣就出現了遞歸現象:元素變成新對象,并對它的每個屬性進行檢查。如果屬性是一個特征,就為此對象創建一個簡單的 Java 原始成員變量;如果屬性是元素,則創建一個新的對象,并作為一個成員變量將其添加,然后在這個新對象上又開始同樣的過程,直到全部類都已創建為止。

從蘿卜 ... 嗯 ... XML 獲得 Java
一旦創建了 XML 方案,您就需要從這個方案中提取出必需的信息,來確定應該創建哪些 Java 類。這個過程的第一步是查看 XML 方案,并嚴格確定輸出應該是什么。對于簡單的 serviceConfiguration 對象,定義了兩個 Java 原始屬性:nameversion。對于這樣一個簡單的對象,確定所需的接口并不難。只需將被定義類型的名稱的首字母大寫,并將這些 Java 屬性添加到接口中即可,如清單 3 所示。

清單 3. 為 ServiceConfiguration 接口而從 XML 方案生成的 Java 代碼
public interface ServiceConfiguration {
    public void setVersion(float version);
    public float getVersion();
    public void setName(String name);
    public String getName();
}

這是相當明白易懂的;清單 3 中的接口為 XML 方案中定義的屬性提供讀方法和寫方法。另外,您將需要生成一個實現類來定義此接口的各個成員變量,并實現此接口中的每個方法。這種使接口從實現中分離出來的方法使我們能夠為特定的需要提中供多種實現。 例如,某個特定的服務可能需要執行計算,而不只是接受從寫方法中收到的值。現在考慮那種更復雜的情況還有點為時尚早,但我將在后續文章中重新提到它。然而,一般說來,您仍可以確定實現類應該像什么樣子,如清單 4 所示。

清單 4. 為 ServiceConfiguration 實現而從 XML 方案生成的 Java 代碼
public class ServiceConfigurationImpl implements ServiceConfiguration {
    private String name;
    private float version;

    public void setVersion(float version) {
        this.version = version;
    }

    public float getVersion() {
        return version;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

相同的原則也適用于 XML 方案中定義的其它對象。您可以在下面查看到其它 Java 類(因為它們都是應該生成的):

總結
到目前為止,您應該對數據綁定的各個方面都比較熟悉了。我已初步介紹了您應該使用數據綁定的原因,尤其是在配置信息的范圍內,并概述了為實現此方法您所需要了解的一些基本概念。

此系列文章的下一篇將繼續考察數據綁定的過程。您將有機會去檢查 org.enhydra.xml.binding.SchemaMapper 類,它將接受這第一部分中創建的 XML 方案作為數據接口,并從它創建出一個 Java 接口和實現類。本系列文章的第二部分將詳細說明這一過程的每個步驟,并說明如何確保方案被準確表示,以便 XML 文檔能接著被轉換為生成的類的實例。

梳理一下您學到的 XML 方案和 JDOM (我將在示例代碼中使用它們),下個月再見!

參考資料

  • 下載包含本文中的示例代碼的 zip 文件,包括所有 Java 類XML 文件
  • 從 David Megginson 的 SAX 頁下載 The Simple API for XML (SAX) 的 2000 年 5 月版,這是一個基于事件的 API,用于在 Java 應用程序中讀取 XML。
  • 從 W3C DOM 工作小組的正式網頁上不斷了解 DOM 的最新狀態,DOM 即文檔對象模型 (DOM),該模型是一種基于樹的 API,用于在 Java 應用程序中讀寫 XML。
  • 下載 JDOM,這是一種使 XML 在 Java 應用程序中的使用更加簡單和直觀的 API,并了解有關 JDOM 方案的內容。
  • 研讀 JSR-031:數據綁定,這是 Sun 的數據綁定規范要求。
  • 下載 Enhydra,一種開放源代碼的 Java 應用程序服務器。
  • 從 W3C 的網站閱讀由兩部分組成的 XML 方案規范(目前為工作初稿)的最新細節:結構數據類型
  • Brett McLaughlin 有能力寫一本關于 Java 應用程序和 XML 這一主題的書 -- 他也這樣做了:訂購《Java and XML》,由 O'Reilly 出版。
  • 需要關于 XML 更基本的導論嗎?試一試 developerWorks 的 XML 導論教程和其它教學性內容,其中包括了大多數基本主題。

作者簡介
Brett McLaughlin 是 Lutris 科技公司的 Enhydra 戰略家,其專長是分布式系統的體系結構。他是《
Java and XML》(O'Reilly) 一書的作者。Brett 涉足多種技術,如 Java servlets、Enterprise JavaBeans 技術、XML 和企業對企業的應用程序等。最近他與 Jason Hunter 一起建立了 JDOM 方案,該方案提供簡單的 API 來在 Java 應用程序中操作 XML。他還是 Apache Cocoon 項目和 EJBoss EJB 服務器的積極開發人員,并且是 Apache Turbine 項目的共同創始人之一。可以通過 brett@newInstance.com 與 Brett 聯系。



2005-03-24 06:10 發表評論
]]>
JAXP: Coding for Parser & Transformer Independencehttp://m.tkk7.com/jackybu/articles/2376.htmlWed, 23 Mar 2005 08:09:00 GMThttp://m.tkk7.com/jackybu/articles/2376.htmlhttp://m.tkk7.com/jackybu/comments/2376.htmlhttp://m.tkk7.com/jackybu/articles/2376.html#Feedback0http://m.tkk7.com/jackybu/comments/commentRss/2376.htmlhttp://m.tkk7.com/jackybu/services/trackbacks/2376.html介紹
由于可擴展標記語言(XML)的易用性和輕攜性,其近年來獲得了極大的流行。它與Java結合起來,非常適用于可移植的數據和代碼。每一個與XML文檔打交道的Java程序員,無論是讀數據,還是進行數據轉換,都必須對Java API’s for XML Processing (JAXP)有一個很深的理解。編寫XML解析器無關的代碼有許多好處,JAXP API 是用于XML的,就像JDBC API是用于關系型數據庫的。這篇介紹性的文章幫助開發者學習JAXP API,并讓開發者對可插入層(pluggability layer)有一個很深的理解,這樣,開發者就可以在他們的應用程序中自如的更換解析器。
JAXPack
   SUN推出了一個用于XML的Java API 和架構,稱其為Java XML Pack (JAXPack - http://java.sun.com/xml/javaxmlpack.html)。下載包中包括了現在行業中一些重要的標準。這篇文章中,我們將注意力放在JAXP(the API for XML Processing)上,Sybase的Easerver從版本3.6.1開始支持JAXP。
   首先,我們看一下JAXP提供的解析能力,解析XML文檔有兩種最基本的方法, 基于事件的SAX和遍歷樹的DOM。開發者可以選擇最適合他們需要的方法。讓我們鉆進去,深入的看一下這些API。
這篇文章中,我們用圖1中的XML文檔來闡述我們的例子。


 
SAX
Simple API for XML Parsing (SAX)是事件驅動的,它從頭到尾遍歷整個文檔, 當它遇到一個語法結構時,它會通知運行它的程序,這些是通過事件處理接口ContentHandler, ErrorHandler, DTDHandler, 和 EntityResolver中的回調方法實現的。這些回調方法可以被開發者自定義實現來滿足一些特殊的要求。圖2描繪了SAX解析器解析文檔是各種組件之間的關系。
 

我們將遍歷圖1中XML文檔,并且給出SAX解析器一行一行的解析是調用回調方法的細節,在這個例子中,我們不包括對ignorableWhiteSpace方法調用。


現在你已經對SAX如何工作有一個總體的了解,接下來,讓我們看一看用真真的Java代碼實現的例子,我們實現的程序的完整代碼可以在http://www.sybase.com/developer.上找到,出于這篇文章的目的,我將只用一些代碼相關部分的片斷。

  1. public class SAXExample extends DefaultHandler {
  2. SAXParserFactory factory = SAXParserFactory.newInstance();
  3. SAXParser saxParser = factory.newSAXParser();
  4. DefaultHandler handler = new SAXExample();
  5. saxParser.parse( new File(argv[0]), handler)

 
  注意我們繼承了DefaultHandler Class, 這個類用一些空方法實現了ContentHandler, ErrorHandle, DTDHandler,和 EntityResolver接口,這樣,程序員就可以只覆蓋一些他們需要的方法。
在我們解析之前,我們首先需要通過調用newInstance方法,實例化一個SAXParserFactory,這個方法用某個特定的查找順序來決定使用哪一個SAXParserFactory的實現,這就意味著,解析器更改時,代碼無需重新編譯。
一旦我們實例化了一個SAXParserFactory,我們可以設置三個選項,這些決定了隨后如何產生SAXParser的對象。
SAXParserFactory  使namespace可用
SetValidating     打開驗證
SetFeature         設定底層實現的特征
SAXParserFactory配置好后,我們調用newSAXParser方法來實例化一個JAXP SAXParser對象,這個對象包裝了一個底層的SAX解析器,并且允許我們以廠商中立的方式與其交互,現在,我們就可以解析了。在這個例子中,我們用File對象作為輸入,它還可以接受其他的輸入源,如InputSource對象,InputStream 對象,或者Uniform Resource Identifier (URI)。
注意程序是如何使自己成為解析器的處理者(handler)的,這意味著解析器將調用SAXExampl中的回調方法的,當解析方法一行一行的解析XML文件時,我們的處理類中的回調事件就發生了。

DOM
Document Object Model (DOM)是將XML文檔解析成樹狀對象的一套接口,每一個對象,或結點(node)都有一個用org.w3c.dom包中的接口表示的類型(type).如Element, Attribute,
Comment, 和Text。可以像操作其他任何樹狀數據結構一樣來操作DOM樹狀對象,它允許隨機訪問XML文檔中特定部分的數據,并且修改它,這些是SAX解析器做不到的。
     這種方法的缺點是它非常占用內存和CPU資源,因為構建DOM樹時需要將整個XML文檔讀入并保持在內存中。
 

讓我們看一個例子:
  1.     public class DOMExample {
  2. DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
  3. DocumentBuilder domParser = factory.newDocumentBuilder();
  4. Document document = domParser.parse( new File(argv[0]) );

.
與SAX類似,我們首先用newInstance方法實例化一個DocumentBuilderFactory對象,同樣類似SAXParserFactory,工廠可以配置用來處理namespace(命名空間)和validation(驗證)。另外,還有一些其他可選的設置,但這已經超出了本文的范圍。工廠對象準備好后,我們就可以創造一個DocumentBuilder對象,它可用來解析xml文件和創造Document對象,同樣類似SAXParser的parse方法,Document對象可以接受InputSource對象,InputStream對象,或者URI。

  1. Node thePhonebook = document.getDocumentElement();
  2. NodeList personList = thePhonebook.getChildNodes();
  3. Node currPerson = personList.item(0);
  4. Node fullName = currPerson.getChildNodes().item(0);
  5. Node firstName = fullName.getChildNodes().item(0);
  6. Node lastName = fullName.getChildNodes().item(1);
  7. Text firstNameText = (Text)firstName.getFirstChild();
  8. Text lastNameText = (Text)lastName.getFirstChild();
  9. Node phone = currPerson.getChildNodes().item(1);
  10. Node workPhone = phone.getChildNodes().item(0);
  11. Node homePhone = phone.getChildNodes().item(1);
  12. Text workPhoneText = (Text)workPhone.getFirstChild();
  13. Text homePhoneText = (Text)homePhone.getFirstChild();

一旦我們擁有了Document DOM對象,我們就可以像操作其他樹一樣操作它。getDocumentElement方法返回根元素,從根元素可以得到子節點的NodeList,并且可以處理它們。在DOM樹結構的葉結點,我們可以找到Text對象,它繼承了Node。調用getData方法可以返回字符串的值。如你所見,使用者在操作數據時必須對文檔數據的結構有一個了解,而在SAX中,解析器僅僅對它遇到的數據反應。
但是,DOM最大的優點是它可以對數據結構進行修改,例如:

  1. if (firstNameText.getData().equals("Richard") &&
  2. lastNameText.getData().equals("Mullins")) {
  3.     homePhoneText.setNodeValue("(510)333-3333");
  4. }

用setNodeValue方法可以改變DOM樹中的數據,隨后,我們將看XSLT如何將一個新樹寫入一個數據文件。
XSLT
XSL轉換(XSLT)是將XML文檔轉換為其他XML文檔或其他格式的文檔(如HTML)的一組API,  XML樣式語言(XSL)在轉換中作用巨大,用其定義的樣式表包含了格式規則,指定了文檔如何顯示。

 
這里有一個例子,將DOM對象轉換為XML文檔:
  1. //create a new DOMSource using the root node of an existing DOM tree
  2. DOMSource source = new DOMSource(thePhonebook);
  3. StreamResult result = new StreamResult(System.out);
  4. TransformerFactory tFactory = TransformerFactory.newInstance();
  5. Transformer transformer = tFactory.newTransformer();
  6. transformer.transform(source, result);

我們首先用newInstance方法實例化一個TransformerFactory對象,它用特定的查找順序來決定使用哪一個轉換器實現。和SAX和DOM工廠一樣,可以對TransformerFactory對象進行一些配置,來影響Transformer對象的創建。用newTransformer方法創建好Transformer對象后,就可以調用transform方法,它將一個Source對象(DOMSource,SAXSource, StreamSource)轉換為Result對象(DOMResult, SAXResult, StreamResult)。
抽象層
前面已經提到過,用某種特定的查找順序來決定使用哪個SAX, DOM 和XSLT的實現,下面的API定義了查找順序:
 .使用javax.xml.parsers.SAXParserFactory(或javax.xml.parsers.DocumentBuilderFactory)系統屬性(system property)。
 .使用JRE目錄中“lib/jaxp.properties”屬性文件定義的javax.xml.parsers.SAXParserFactory屬性的值,這個值必須包括了實現類的全名。
 .使用Service API,它可以在運行系統中使用的JAR文件中包括的META-INF/services/javax.xml.parsers.SAXParserFactory文件中查找類名。
 .使用平臺默認的SAXParserFactory實例。
使用DOM時,你只需將javax.xml.parsers.SAXParserFactory替換為javax.xml.parsers.DocumentBuilderFactory,類似,使用XSLT時,你用javax.xml.transform.TransformerFactory替換。
總結
如你所見,你寫的代碼只需與抽象層打交道。這保證了廠商的無關性,你可以快速和輕松的替換你的解析器的實現。解析XML文檔時,Java開發者可以根據他們的需要設置兩個選項。SAX是利用回調過程的事件驅動模型,而DOM是一種游歷樹的模型,它在操作數據前必須將XML文檔解析成樹,XSLT則可以將XML文檔轉換為另一種XML文檔或者其他格式,如HTML。總之,JAXP非常的強大,靈活,它提供的簡單可用的工具可以滿足大多數Java開發者處理XML文檔時的需要。


2005-03-23 16:09 發表評論
]]>
用于XML的簡單APIhttp://m.tkk7.com/jackybu/articles/2371.htmlWed, 23 Mar 2005 07:19:00 GMThttp://m.tkk7.com/jackybu/articles/2371.htmlhttp://m.tkk7.com/jackybu/comments/2371.htmlhttp://m.tkk7.com/jackybu/articles/2371.html#Feedback0http://m.tkk7.com/jackybu/comments/commentRss/2371.htmlhttp://m.tkk7.com/jackybu/services/trackbacks/2371.html閱讀全文

2005-03-23 15:19 發表評論
]]>
文檔對象模型http://m.tkk7.com/jackybu/articles/2370.htmlWed, 23 Mar 2005 07:18:00 GMThttp://m.tkk7.com/jackybu/articles/2370.htmlhttp://m.tkk7.com/jackybu/comments/2370.htmlhttp://m.tkk7.com/jackybu/articles/2370.html#Feedback0http://m.tkk7.com/jackybu/comments/commentRss/2370.htmlhttp://m.tkk7.com/jackybu/services/trackbacks/2370.html閱讀全文

2005-03-23 15:18 發表評論
]]>
用dom4j建立,修改XML文檔,并解決格式化輸出和中文問題 http://m.tkk7.com/jackybu/articles/2360.htmlWed, 23 Mar 2005 05:14:00 GMThttp://m.tkk7.com/jackybu/articles/2360.htmlhttp://m.tkk7.com/jackybu/comments/2360.htmlhttp://m.tkk7.com/jackybu/articles/2360.html#Feedback0http://m.tkk7.com/jackybu/comments/commentRss/2360.htmlhttp://m.tkk7.com/jackybu/services/trackbacks/2360.html閱讀全文

2005-03-23 13:14 發表評論
]]>
DOM4J 使用簡介 http://m.tkk7.com/jackybu/articles/2359.htmlWed, 23 Mar 2005 05:12:00 GMThttp://m.tkk7.com/jackybu/articles/2359.htmlhttp://m.tkk7.com/jackybu/comments/2359.htmlhttp://m.tkk7.com/jackybu/articles/2359.html#Feedback0http://m.tkk7.com/jackybu/comments/commentRss/2359.htmlhttp://m.tkk7.com/jackybu/services/trackbacks/2359.htmlDom4j 使用簡介

作者:冰云 icecloud(AT)sina.com

時間:2003.12.15



版權聲明:

本文由冰云完成,首發于CSDN,未經許可,不得使用于任何商業用途。

文中代碼部分引用自DOM4J文檔。

歡迎轉載,但請保持文章及版權聲明完整。

如需聯絡請發郵件:icecloud(AT)sina.com




DOM4J是dom4j.org出品的一個開源XML解析包,它的網站中這樣定義:

Dom4j is an easy to use, open source library for working with XML, XPath and XSLT on the Java platform using the Java Collections Framework and with full support for DOM, SAX and JAXP.

Dom4j是一個易用的、開源的庫,用于XML,XPath和XSLT。它應用于Java平臺,采用了Java集合框架并完全支持DOM,SAX和JAXP。

DOM4J使用起來非常簡單。只要你了解基本的XML-DOM模型,就能使用。然而他自己帶的指南只有短短一頁(html),不過說的到挺全。國內的中文資料很少。因而俺寫這個短小的教程方便大家使用,這篇文章僅談及基本的用法,如需深入的使用,請……自己摸索或查找別的資料。

之前看過IBM developer社區的文章(參見附錄),提到一些XML解析包的性能比較,其中DOM4J的性能非常出色,在多項測試中名列前茅。(事實上DOM4J的官方文檔中也引用了這個比較)所以這次的項目中我采用了DOM4J作為XML解析工具。

在國內比較流行的是使用JDOM作為解析器,兩者各擅其長,但DOM4J最大的特色是使用大量的接口,這也是它被認為比JDOM靈活的主要原因。大師不是說過么,“面向接口編程”。目前使用DOM4J的已經越來越多。如果你善于使用JDOM,不妨繼續用下去,只看看本篇文章作為了解與比較,如果你正要采用一種解析器,不如就用DOM4J吧。

它的主要接口都在org.dom4j這個包里定義:

Attribute
Attribute定義了XML的屬性

Branch
Branch為能夠包含子節點的節點如XML元素(Element)和文檔(Docuemnts)定義了一個公共的行為,

CDATA
CDATA 定義了XML CDATA 區域

CharacterData
CharacterData是一個標識借口,標識基于字符的節點。如CDATA,Comment, Text.

Comment
Comment 定義了XML注釋的行為

Document
定義了XML文檔

DocumentType
DocumentType 定義XML DOCTYPE聲明

Element
Element定義XML 元素

ElementHandler
ElementHandler定義了 Element 對象的處理器

ElementPath
被 ElementHandler 使用,用于取得當前正在處理的路徑層次信息

Entity
Entity定義 XML entity

Node
Node為所有的dom4j中XML節點定義了多態行為

NodeFilter
NodeFilter 定義了在dom4j節點中產生的一個濾鏡或謂詞的行為(predicate)

ProcessingInstruction
ProcessingInstruction 定義 XML 處理指令.

Text
Text 定義XML 文本節點.

Visitor
Visitor 用于實現Visitor模式.

XPath
XPath 在分析一個字符串后會提供一個XPath 表達式


看名字大致就知道它們的涵義如何了。

要想弄懂這套接口,關鍵的是要明白接口的繼承關系:

interface java.lang.Cloneable
interface org.dom4j.Node
interface org.dom4j.Attribute
interface org.dom4j.Branch
interface org.dom4j.Document
interface org.dom4j.Element
interface org.dom4j.CharacterData
interface org.dom4j.CDATA
interface org.dom4j.Comment
interface org.dom4j.Text
interface org.dom4j.DocumentType
interface org.dom4j.Entity
interface org.dom4j.ProcessingInstruction
一目了然,很多事情都清楚了。大部分都是由Node繼承來的。知道這些關系,將來寫程序就不會出現ClassCastException了。

下面給出一些例子(部分摘自DOM4J自帶的文檔),簡單說一下如何使用。

1. 讀取并解析XML文檔:

讀寫XML文檔主要依賴于org.dom4j.io包,其中提供DOMReader和SAXReader兩類不同方式,而調用方式是一樣的。這就是依靠接口的好處。


// 從文件讀取XML,輸入文件名,返回XML文檔

public Document read(String fileName) throws MalformedURLException, DocumentException {

SAXReader reader = new SAXReader();

Document document = reader.read(new File(fileName));

return document;

}




其中,reader的read方法是重載的,可以從InputStream, File, Url等多種不同的源來讀取。得到的Document對象就帶表了整個XML。

根據本人自己的經驗,讀取的字符編碼是按照XML文件頭定義的編碼來轉換。如果遇到亂碼問題,注意要把各處的編碼名稱保持一致即可。

2. 取得Root節點

讀取后的第二步,就是得到Root節點。熟悉XML的人都知道,一切XML分析都是從Root元素開始的。


  public Element getRootElement(Document doc){

return doc.getRootElement();

}




3. 遍歷XML樹

DOM4J提供至少3種遍歷節點的方法:

1) 枚舉(Iterator)


// 枚舉所有子節點

for ( Iterator i = root.elementIterator(); i.hasNext(); ) {

Element element = (Element) i.next();

// do something

}

// 枚舉名稱為foo的節點

for ( Iterator i = root.elementIterator(foo); i.hasNext();) {

Element foo = (Element) i.next();

// do something

}

// 枚舉屬性

for ( Iterator i = root.attributeIterator(); i.hasNext(); ) {

Attribute attribute = (Attribute) i.next();

// do something

}



2)遞歸

遞歸也可以采用Iterator作為枚舉手段,但文檔中提供了另外的做法


public void treeWalk() {

treeWalk(getRootElement());

}

public void treeWalk(Element element) {

for (int i = 0, size = element.nodeCount(); i < size; i++) {

Node node = element.node(i);

if (node instanceof Element) {

treeWalk((Element) node);

} else { // do something....

}

}

}




3) Visitor模式

最令人興奮的是DOM4J對Visitor的支持,這樣可以大大縮減代碼量,并且清楚易懂。了解設計模式的人都知道,Visitor是GOF設計模式之一。其主要原理就是兩種類互相保有對方的引用,并且一種作為Visitor去訪問許多Visitable。我們來看DOM4J中的Visitor模式(快速文檔中沒有提供)

只需要自定一個類實現Visitor接口即可。


  public class MyVisitor extends VisitorSupport {

public void visit(Element element){

System.out.println(element.getName());

}

public void visit(Attribute attr){

System.out.println(attr.getName());

}

}

調用: root.accept(new MyVisitor())



Visitor接口提供多種Visit()的重載,根據XML不同的對象,將采用不同的方式來訪問。上面是給出的Element和Attribute的簡單實現,一般比較常用的就是這兩個。VisitorSupport是DOM4J提供的默認適配器,Visitor接口的Default Adapter模式,這個模式給出了各種visit(*)的空實現,以便簡化代碼。

注意,這個Visitor是自動遍歷所有子節點的。如果是root.accept(MyVisitor),將遍歷子節點。我第一次用的時候,認為是需要自己遍歷,便在遞歸中調用Visitor,結果可想而知。

4. XPath支持

DOM4J對XPath有良好的支持,如訪問一個節點,可直接用XPath選擇。



public void bar(Document document) {

List list = document.selectNodes( //foo/bar );

Node node = document.selectSingleNode(//foo/bar/author);

String name = node.valueOf( @name );

}




例如,如果你想查找XHTML文檔中所有的超鏈接,下面的代碼可以實現:



public void findLinks(Document document) throws DocumentException {

List list = document.selectNodes( //a/@href );

for (Iterator iter = list.iterator(); iter.hasNext(); ) {

Attribute attribute = (Attribute) iter.next();

String url = attribute.getValue();

}

}




5. 字符串與XML的轉換

有時候經常要用到字符串轉換為XML或反之,


// XML轉字符串
  Document document = ...;

String text = document.asXML();

// 字符串轉XML

String text = <person> <name>James</name> </person>;

Document document = DocumentHelper.parseText(text);




6 用XSLT轉換XML



public Document styleDocument(

Document document,

String stylesheet

) throws Exception {

// load the transformer using JAXP

TransformerFactory factory = TransformerFactory.newInstance();

Transformer transformer = factory.newTransformer(

new StreamSource( stylesheet )

);

// now lets style the given document

DocumentSource source = new DocumentSource( document );

DocumentResult result = new DocumentResult();

transformer.transform( source, result );

// return the transformed document

Document transformedDoc = result.getDocument();

return transformedDoc;

}




7. 創建XML

一般創建XML是寫文件前的工作,這就像StringBuffer一樣容易。



public Document createDocument() {

Document document = DocumentHelper.createDocument();

Element root = document.addElement(root);

Element author1 =

root

.addElement(author)

.addAttribute(name, James)

.addAttribute(location, UK)

.addText(James Strachan);

Element author2 =

root

.addElement(author)

.addAttribute(name, Bob)

.addAttribute(location, US)

.addText(Bob McWhirter);

return document;

}




8. 文件輸出

一個簡單的輸出方法是將一個Document或任何的Node通過write方法輸出


FileWriter out = new FileWriter( foo.xml );

document.write(out);




如果你想改變輸出的格式,比如美化輸出或縮減格式,可以用XMLWriter類

public void write(Document document) throws IOException {

// 指定文件

XMLWriter writer = new XMLWriter(

new FileWriter( output.xml )

);

writer.write( document );

writer.close();

// 美化格式

OutputFormat format = OutputFormat.createPrettyPrint();

writer = new XMLWriter( System.out, format );

writer.write( document );

// 縮減格式

format = OutputFormat.createCompactFormat();

writer = new XMLWriter( System.out, format );

writer.write( document );

}




如何,DOM4J夠簡單吧,當然,還有一些復雜的應用沒有提到,如ElementHandler等。如果你動心了,那就一起來用DOM4J.

DOM4J官方網站:(我老連不上)

http://www.dom4j.org

DOM4J下載(SourceForge),最新版本為1.4

http://sourceforge.net/projects/dom4j

參考資料:

DOM4J文檔

Java 中的 XML:文檔模型,第一部分:性能

http://www-900.ibm.com/developerWorks/cn/xml/x-injava/index.shtml

Java 中的 XML:Java 文檔模型的用法

http://www-900.ibm.com/developerWorks/cn/xml/x-injava2/index.shtml

Java XML API 漫談 by robbin

http://www.hibernate.org.cn:8000/137.html

Dom4j 使用簡介

作者:冰云 icecloud(AT)sina.com

時間:2003.12.15



版權聲明:

本文由冰云完成,首發于CSDN,未經許可,不得使用于任何商業用途。

文中代碼部分引用自DOM4J文檔。

歡迎轉載,但請保持文章及版權聲明完整。

如需聯絡請發郵件:icecloud(AT)sina.com




DOM4J是dom4j.org出品的一個開源XML解析包,它的網站中這樣定義:

Dom4j is an easy to use, open source library for working with XML, XPath and XSLT on the Java platform using the Java Collections Framework and with full support for DOM, SAX and JAXP.

Dom4j是一個易用的、開源的庫,用于XML,XPath和XSLT。它應用于Java平臺,采用了Java集合框架并完全支持DOM,SAX和JAXP。

DOM4J使用起來非常簡單。只要你了解基本的XML-DOM模型,就能使用。然而他自己帶的指南只有短短一頁(html),不過說的到挺全。國內的中文資料很少。因而俺寫這個短小的教程方便大家使用,這篇文章僅談及基本的用法,如需深入的使用,請……自己摸索或查找別的資料。

之前看過IBM developer社區的文章(參見附錄),提到一些XML解析包的性能比較,其中DOM4J的性能非常出色,在多項測試中名列前茅。(事實上DOM4J的官方文檔中也引用了這個比較)所以這次的項目中我采用了DOM4J作為XML解析工具。

在國內比較流行的是使用JDOM作為解析器,兩者各擅其長,但DOM4J最大的特色是使用大量的接口,這也是它被認為比JDOM靈活的主要原因。大師不是說過么,“面向接口編程”。目前使用DOM4J的已經越來越多。如果你善于使用JDOM,不妨繼續用下去,只看看本篇文章作為了解與比較,如果你正要采用一種解析器,不如就用DOM4J吧。

它的主要接口都在org.dom4j這個包里定義:

Attribute
Attribute定義了XML的屬性

Branch
Branch為能夠包含子節點的節點如XML元素(Element)和文檔(Docuemnts)定義了一個公共的行為,

CDATA
CDATA 定義了XML CDATA 區域

CharacterData
CharacterData是一個標識借口,標識基于字符的節點。如CDATA,Comment, Text.

Comment
Comment 定義了XML注釋的行為

Document
定義了XML文檔

DocumentType
DocumentType 定義XML DOCTYPE聲明

Element
Element定義XML 元素

ElementHandler
ElementHandler定義了 Element 對象的處理器

ElementPath
被 ElementHandler 使用,用于取得當前正在處理的路徑層次信息

Entity
Entity定義 XML entity

Node
Node為所有的dom4j中XML節點定義了多態行為

NodeFilter
NodeFilter 定義了在dom4j節點中產生的一個濾鏡或謂詞的行為(predicate)

ProcessingInstruction
ProcessingInstruction 定義 XML 處理指令.

Text
Text 定義XML 文本節點.

Visitor
Visitor 用于實現Visitor模式.

XPath
XPath 在分析一個字符串后會提供一個XPath 表達式


看名字大致就知道它們的涵義如何了。

要想弄懂這套接口,關鍵的是要明白接口的繼承關系:

interface java.lang.Cloneable
interface org.dom4j.Node
interface org.dom4j.Attribute
interface org.dom4j.Branch
interface org.dom4j.Document
interface org.dom4j.Element
interface org.dom4j.CharacterData
interface org.dom4j.CDATA
interface org.dom4j.Comment
interface org.dom4j.Text
interface org.dom4j.DocumentType
interface org.dom4j.Entity
interface org.dom4j.ProcessingInstruction
一目了然,很多事情都清楚了。大部分都是由Node繼承來的。知道這些關系,將來寫程序就不會出現ClassCastException了。

下面給出一些例子(部分摘自DOM4J自帶的文檔),簡單說一下如何使用。

1. 讀取并解析XML文檔:

讀寫XML文檔主要依賴于org.dom4j.io包,其中提供DOMReader和SAXReader兩類不同方式,而調用方式是一樣的。這就是依靠接口的好處。


// 從文件讀取XML,輸入文件名,返回XML文檔

public Document read(String fileName) throws MalformedURLException, DocumentException {

SAXReader reader = new SAXReader();

Document document = reader.read(new File(fileName));

return document;

}




其中,reader的read方法是重載的,可以從InputStream, File, Url等多種不同的源來讀取。得到的Document對象就帶表了整個XML。

根據本人自己的經驗,讀取的字符編碼是按照XML文件頭定義的編碼來轉換。如果遇到亂碼問題,注意要把各處的編碼名稱保持一致即可。

2. 取得Root節點

讀取后的第二步,就是得到Root節點。熟悉XML的人都知道,一切XML分析都是從Root元素開始的。


  public Element getRootElement(Document doc){

return doc.getRootElement();

}




3. 遍歷XML樹

DOM4J提供至少3種遍歷節點的方法:

1) 枚舉(Iterator)


// 枚舉所有子節點

for ( Iterator i = root.elementIterator(); i.hasNext(); ) {

Element element = (Element) i.next();

// do something

}

// 枚舉名稱為foo的節點

for ( Iterator i = root.elementIterator(foo); i.hasNext();) {

Element foo = (Element) i.next();

// do something

}

// 枚舉屬性

for ( Iterator i = root.attributeIterator(); i.hasNext(); ) {

Attribute attribute = (Attribute) i.next();

// do something

}



2)遞歸

遞歸也可以采用Iterator作為枚舉手段,但文檔中提供了另外的做法


public void treeWalk() {

treeWalk(getRootElement());

}

public void treeWalk(Element element) {

for (int i = 0, size = element.nodeCount(); i < size; i++) {

Node node = element.node(i);

if (node instanceof Element) {

treeWalk((Element) node);

} else { // do something....

}

}

}




3) Visitor模式

最令人興奮的是DOM4J對Visitor的支持,這樣可以大大縮減代碼量,并且清楚易懂。了解設計模式的人都知道,Visitor是GOF設計模式之一。其主要原理就是兩種類互相保有對方的引用,并且一種作為Visitor去訪問許多Visitable。我們來看DOM4J中的Visitor模式(快速文檔中沒有提供)

只需要自定一個類實現Visitor接口即可。


  public class MyVisitor extends VisitorSupport {

public void visit(Element element){

System.out.println(element.getName());

}

public void visit(Attribute attr){

System.out.println(attr.getName());

}

}

調用: root.accept(new MyVisitor())



Visitor接口提供多種Visit()的重載,根據XML不同的對象,將采用不同的方式來訪問。上面是給出的Element和Attribute的簡單實現,一般比較常用的就是這兩個。VisitorSupport是DOM4J提供的默認適配器,Visitor接口的Default Adapter模式,這個模式給出了各種visit(*)的空實現,以便簡化代碼。

注意,這個Visitor是自動遍歷所有子節點的。如果是root.accept(MyVisitor),將遍歷子節點。我第一次用的時候,認為是需要自己遍歷,便在遞歸中調用Visitor,結果可想而知。

4. XPath支持

DOM4J對XPath有良好的支持,如訪問一個節點,可直接用XPath選擇。



public void bar(Document document) {

List list = document.selectNodes( //foo/bar );

Node node = document.selectSingleNode(//foo/bar/author);

String name = node.valueOf( @name );

}




例如,如果你想查找XHTML文檔中所有的超鏈接,下面的代碼可以實現:



public void findLinks(Document document) throws DocumentException {

List list = document.selectNodes( //a/@href );

for (Iterator iter = list.iterator(); iter.hasNext(); ) {

Attribute attribute = (Attribute) iter.next();

String url = attribute.getValue();

}

}




5. 字符串與XML的轉換

有時候經常要用到字符串轉換為XML或反之,


// XML轉字符串
  Document document = ...;

String text = document.asXML();

// 字符串轉XML

String text = <person> <name>James</name> </person>;

Document document = DocumentHelper.parseText(text);




6 用XSLT轉換XML



public Document styleDocument(

Document document,

String stylesheet

) throws Exception {

// load the transformer using JAXP

TransformerFactory factory = TransformerFactory.newInstance();

Transformer transformer = factory.newTransformer(

new StreamSource( stylesheet )

);

// now lets style the given document

DocumentSource source = new DocumentSource( document );

DocumentResult result = new DocumentResult();

transformer.transform( source, result );

// return the transformed document

Document transformedDoc = result.getDocument();

return transformedDoc;

}




7. 創建XML

一般創建XML是寫文件前的工作,這就像StringBuffer一樣容易。



public Document createDocument() {

Document document = DocumentHelper.createDocument();

Element root = document.addElement(root);

Element author1 =

root

.addElement(author)

.addAttribute(name, James)

.addAttribute(location, UK)

.addText(James Strachan);

Element author2 =

root

.addElement(author)

.addAttribute(name, Bob)

.addAttribute(location, US)

.addText(Bob McWhirter);

return document;

}




8. 文件輸出

一個簡單的輸出方法是將一個Document或任何的Node通過write方法輸出


FileWriter out = new FileWriter( foo.xml );

document.write(out);




如果你想改變輸出的格式,比如美化輸出或縮減格式,可以用XMLWriter類

public void write(Document document) throws IOException {

// 指定文件

XMLWriter writer = new XMLWriter(

new FileWriter( output.xml )

);

writer.write( document );

writer.close();

// 美化格式

OutputFormat format = OutputFormat.createPrettyPrint();

writer = new XMLWriter( System.out, format );

writer.write( document );

// 縮減格式

format = OutputFormat.createCompactFormat();

writer = new XMLWriter( System.out, format );

writer.write( document );

}




如何,DOM4J夠簡單吧,當然,還有一些復雜的應用沒有提到,如ElementHandler等。如果你動心了,那就一起來用DOM4J.

DOM4J官方網站:(我老連不上)

http://www.dom4j.org

DOM4J下載(SourceForge),最新版本為1.4

http://sourceforge.net/projects/dom4j

參考資料:

DOM4J文檔

Java 中的 XML:文檔模型,第一部分:性能

http://www-900.ibm.com/developerWorks/cn/xml/x-injava/index.shtml

Java 中的 XML:Java 文檔模型的用法

http://www-900.ibm.com/developerWorks/cn/xml/x-injava2/index.shtml

Java XML API 漫談 by robbin

http://www.hibernate.org.cn:8000/137.html

 



2005-03-23 13:12 發表評論
]]>
Java 中的 XML:Java 文檔模型的用法http://m.tkk7.com/jackybu/articles/2358.htmlWed, 23 Mar 2005 05:11:00 GMThttp://m.tkk7.com/jackybu/articles/2358.htmlhttp://m.tkk7.com/jackybu/comments/2358.htmlhttp://m.tkk7.com/jackybu/articles/2358.html#Feedback0http://m.tkk7.com/jackybu/comments/commentRss/2358.htmlhttp://m.tkk7.com/jackybu/services/trackbacks/2358.html關于作者
作者照片 — Dennis Sosnosky Dennis Sosnoski(dms@sosnoski.com)是西雅圖地區 Java 咨詢公司 Sosnoski Software Solutions, Inc. 的共同創始人和首席顧問。他已經有 30 多年專業軟件開發經歷,最近幾年他一直關注服務器端的 Java 技術,包括 servlet、Enterprise JavaBean 和 XML。他已經發表了 Java 性能問題和常規服務器端 Java 技術的許多文章,而且擔任 Seattle Java-XML SIG 的總裁。

2005-03-23 13:11 發表評論
]]>
在 Java 中使用 DOM 和 XPath 進行有效的 XML 處理http://m.tkk7.com/jackybu/articles/2357.htmlWed, 23 Mar 2005 05:10:00 GMThttp://m.tkk7.com/jackybu/articles/2357.htmlhttp://m.tkk7.com/jackybu/comments/2357.htmlhttp://m.tkk7.com/jackybu/articles/2357.html#Feedback0http://m.tkk7.com/jackybu/comments/commentRss/2357.htmlhttp://m.tkk7.com/jackybu/services/trackbacks/2357.html2001 年 12 月
在對幾個大型 XML 項目分析的基礎上,本文探討了在 Java 中如何有效和高效的使用 DOM。DOM 為創建、處理和操縱 XML 文檔提供了靈活和有效的方法,但使用起來可能比較困難并且可能會導致不穩定和錯誤的代碼。作者 Parand Tony Daruger 提供了一套 Java 用法模式和函數庫,使 DOM 變得健壯且易于使用。

文檔對象模型(Document Object Model,DOM)是公認的 W3C 標準,它被用于與平臺及語言無關的 XML 文檔內容、結構和樣式的動態訪問和更新。它為表示文檔定義了一套標準的接口集,也為訪問和操縱文檔定義了一套標準的方法。DOM 得到廣泛的支持和普及,并且它以各種不同的語言實現,包括 Java、Perl、C、C++、VB、Tcl 和 Python。

正如我將在本文所演示的,當基于流的模型(例如 SAX)不能滿足 XML 處理要求時,DOM 是一個極佳的選擇。不幸的是,規范的幾個方面,例如其語言無關性接口和“一切都是節點(everything-is-a-node)”抽象概念的使用,使其難以使用且易于生成脆弱代碼。這在最近的幾個大型 DOM 項目的研究中尤其明顯,這些項目是由許多開發人員過去一年所創建的。下面討論了常見的問題及其補救措施。

文檔對象模型
DOM 規范被設計成可與任何編程語言一起使用。因此,它嘗試使用在所有語言中都可用的一組通用的、核心的功能部件。DOM 規范同樣嘗試保持其接口定義方面的無關性。這就允許 Java 程序員在使用 Visual Basic 或 Perl 時應用他們的 DOM 知識,反之亦然。

該規范同樣將文檔的每個部分看成由類型和值組成的節點。這為處理文檔的所有方面提供了完美的概念性框架。例如,下面的 XML 片段


the Italicized portion.



就是通過以下的 DOM 結構表示的:

圖 1:XML 文檔的 DOM 表示
DOM 表示

樹的每個 DocumentElementTextAttr 部分都是 DOM Node

完美的抽象確實付出了代價。考慮 XML 片段:Value。您或許會認為文本的值可以通過普通的 Java String 對象來表示,并且通過簡單的 getValue 調用可訪問。實際上,文本被當成 tagname 節點下的一個或多個子 Node。因此,為了獲取文本值,您需要遍歷 tagname 的子節點,將每個值整理成一個字符串。這樣做有充分的理由:tagname 可能包含其它嵌入的 XML 元素,在這種情況下獲取其文本值沒有多大意義。然而,在現實世界中,我們看到由于缺乏便利的函數導致頻繁的編碼錯誤占了 80% 的情況,這樣做的確有意義。

設計問題
DOM 語言無關性的缺點是通常在每個編程語言中使用的一整套工作方法和模式不能被使用。例如,不能使用熟悉的 Java new 構造創建新的 Element,開發者必須使用工廠構造器方法。Node 的集合被表示成 NodeList,而不是通常的 ListIterator 對象。這些微小的不便意味著不同尋常的編碼實踐和增多的代碼行,并且它們迫使程序員學習 DOM 的行事方法而不是用直覺的方法。

DOM 使用“一切都是節點”的抽象。這就意味著幾乎 XML 文檔的每個部分,例如:DocumentElementAttr,全都繼承(extendNode 接口。這不僅是概念上完美,而且還允許每個 DOM 的不同實現通過標準接口使其自身的類可見,并且沒有通過中間包裝類所導致的性能損失。

由于存在的節點類型數量及其訪問方法缺乏一致性,“一切都是節點”的抽象喪失了一些意義。例如, insertData 方法被用來設置 CharacterData 節點的值,而通過使用 setValue 方法來設置 Attr(屬性)節點的值。由于對于不同的節點存在不同的接口,模型的一致性和完美性降低了,而學習曲線增加了。

JDOM
JDOM 是使 DOM API 適應 Java 的研究計劃,從而提供了更自然和易用的接口。由于認識到語言無關 DOM 構造的棘手本質,JDOM 目標在于使用內嵌的 Java 表示和對象,并且為常用任務提供便利的函數。

例如,JDOM 直接處理“一切都是節點”和 DOM 特定構造的使用(如 NodeList)。JDOM 將不同的節點類型(如 DocumentElementAttribute)定義為不同的 Java 類,這意味著開發者可以使用 new 構造它們,避免頻繁類型轉換的需要。JDOM 將字符串表示成 Java String,并且通過普通的 ListIterator 類來表示節點的集合。(JDOM 用其本身類替代 DOM 類。)

JDOM 為提供更完善的接口做了相當有益的工作。它已經被接受成為 JSR(正式的 Java Specification Request),而且它將來很可能會被包含到核心的 Java 平臺中。但是,因其還不是核心 Java API 的一部分,一些人對于使用它還心存猶豫。這兒還有關于與 Iterator 和 Java 對象頻繁創建相關的性能問題的報告。(請參閱參考資料)。

如果您對 JDOM 的接受性和可用性已經滿足,并且如果您也沒有將 Java 代碼和程序員轉移到其它語言的直接需求,JDOM 是個值得探索的好選擇。JDOM 還不能滿足本文探討的項目所在的公司需要,因而他們使用了非常普遍的 DOM。本文也是這樣做的。

常見編碼問題
幾個大型 XML 項目分析揭示了使用 DOM 中的一些常見問題。下面對其中的幾個進行介紹。

代碼臃腫
在我們研究中查看的所有項目,本身都出現一個突出的問題:花費許多行代碼行來做簡單的事情。在某個示例中,使用 16 行代碼檢查一個屬性的值。而同樣的任務,帶有改進的健壯性和出錯處理,可以使用 3 行代碼實現。DOM API 的低級本質、方法和編程模式的不正確應用以及缺乏完整 API 的知識,都會致使代碼行數增加。下面的摘要介紹了關于這些問題的特定實例。

遍歷 DOM
在我們探討的代碼中,最常見的任務是遍歷或搜索 DOM。清單 1 演示了需要在文檔的 config 節里查找一個稱為“header”節點的濃縮版本代碼:

清單 1 中,從根開始通過檢索頂端元素遍歷文檔,獲取其第一個子節點(configNode),并且最終單獨檢查 configNode 的子節點。不幸的是,這種方法不僅冗長,而且還伴隨著脆弱性和潛在的錯誤。

例如,第二行代碼通過使用 getFirstChild 方法獲取中間的 config 節點。已經存在許多潛在的問題。根節點的第一個子節點實際上可能并不是用戶正在搜索的節點。由于盲目地跟隨第一個子節點,我忽視了標記的實際名稱并且可能搜索不正確的文檔部分。當源 XML 文檔的根節點后包含空格或回車時,這種情況中發生一個頻繁的錯誤;根節點的第一個子節點實際是 Node.TEXT_NODE 節點,而不是所希望的元素節點。您可以自己試驗一下,從參考資料下載樣本代碼并且編輯 sample.xml 文件 — 在 sampleconfig 標記之間放置一個回車。代碼立即異常而終止。要正確瀏覽所希望的節點,需要檢查每個 root 的子節點,直到找到非 Text 的節點,并且那個節點有我正在查找的名稱為止。

清單 1 還忽視了文檔結構可能與我們期望有所不同的可能性。例如,如果 root 沒有任何子節點,configNode 將會被設置為 null,并且示例的第三行將產生一個錯誤。因此,要正確瀏覽文檔,不僅要單獨檢查每個子節點以及核對相應的名稱,而且每步都得檢查以確保每個方法調用返回的是一個有效值。編寫能夠處理任意輸入的健壯、無錯的代碼,不僅需要非常關注細節,而且需要編寫很多行代碼。

最終,如果最初的開發者了解它的話,清單 1 中示例的所有功能應該可以通過利用對 getElementsByTagName 函數的簡單調用實現。這是下面要討論的。

檢索元素中的文本值
在所分析的項目中,DOM 遍歷以后,第二項最常進行的任務是檢索在元素中包含的文本值。考慮 XML 片段 The Value。如果已經導航到 sometag 節點,如何獲取其文本值(The Value)呢?一個直觀的實現可能是:


sometagElement.getData();



正如您所猜測到的,上面的代碼并不會執行我們希望的動作。由于實際的文本被存儲為一個或多個子節點,因此不能對 sometag 元素調用 getData 或類似的函數。更好的方法可能是:


sometag.getFirstChild().getData();



第二種嘗試的問題在于值實際上可能并不包含在第一個子節點中;在 sometag 內可能會發現處理指令或其它嵌入的節點,或是文本值包含在幾個子節點而不是單單一個子節點中。考慮到空格經常作為文本節點表示,因此對 sometag.getFirstChild() 的調用可能僅讓您得到標記和值之間的回車。實際上,您需要遍歷所有子節點,以核對 Node.TEXT_NODE 類型的節點,并且整理它們的值直到有完整的值為止。

注意,JDOM 已經利用便利的函數 getText 為我們解決了這個問題。DOM 級別 3 也將有一個使用規劃的 getTextContent 方法的解答。教訓:盡可能使用較高級的 API 是不會錯的。

getElementsByTagName
DOM 級別 2 接口包含一個查找給定名稱的子節點的方法。例如,調用:


NodeList names = someElement.getElementsByTagName("name");







將返回一個包含在 someElement 節點中稱為 names 的節點 NodeList。這無疑比我所討論的遍歷方法更方便。這也是一組常見錯誤的原因。

問題在于 getElementsByTagName 遞歸地遍歷文檔,從而返回所有匹配的節點。假定您有一個包含客戶信息、公司信息和產品信息的文檔。所有這三個項中都可能含有 name 標記。如果調用 getElementsByTagName 搜索客戶名稱,您的程序極有可能行為失常,除了檢索出客戶名稱,還會檢索出產品和公司名稱。在文檔的子樹上調用該函數可能會降低風險,但由于 XML 的靈活本質,使確保您所操作的子樹包含您期望的結構,且沒有您正在搜索的名稱的虛假子節點就變得十分困難。

DOM 的有效使用
考慮到由 DOM 設計強加的限制,如何才能有效和高效的使用該規范呢?下面是使用 DOM 的幾條基本原則和方針,以及使工作更方便的函數庫。

基本原則
如果您遵循幾條基本原則,您使用 DOM 的經驗將會顯著提高:

  • 不要使用 DOM 遍歷文檔。
  • 盡可能使用 XPath 來查找節點或遍歷文檔。
  • 使用較高級的函數庫來更方便地使用 DOM。


這些原則直接從對常見問題的研究中得到。正如上面所討論的,DOM 遍歷是出錯的主要原因。但它也是最常需要的功能之一。如何通過不使用 DOM 而遍歷文檔呢?

XPath
XPath 是尋址、搜索和匹配文檔的各個部分的語言。它是 W3C 推薦標準(Recommendation),并且在大多數語言和 XML 包中實現。您的 DOM 包可能直接支持 XPath 或通過加載件(add-on)支持。本文的樣本代碼對于 XPath 支持使用 Xalan 包。

XPath 使用路徑標記法來指定和匹配文檔的各個部分,該標記法與文件系統和 URL 中使用的類似。例如,XPath:/x/y/z 搜索文檔的根節點 x,其下存在節點 y,其下存在節點 z。該語句返回與指定路徑結構匹配的所有節點。

更為復雜的匹配可能同時在包含文檔的結構方面以及在節點及其屬性的值中。語句 /x/y/* 返回父節點為 xy 節點下的任何節點。/x/y[@name='a'] 匹配所有父節點為 xy 節點,其屬性稱為 name,屬性值為 a。請注意,XPath 處理篩選空格文本節點以獲得實際的元素節點 — 它只返回元素節點。

詳細探討 XPath 及其用法超出了本文的范圍。請參閱參考資料獲得一些優秀教程的鏈接。花點時間學習 XPath,您將會更方便的處理 XML 文檔。

函數庫
當研究 DOM 項目時令我們驚奇的一個發現,是存在的拷貝和粘貼代碼的數量。為什么有經驗的開發者沒有使用良好的編程習慣,卻使用拷貝和粘貼方法而不是創建助手(helper)庫呢?我們相信這是由于 DOM 的復雜性加深了學習的難度,并使開發者要理解能完成他們所需要的第一段代碼。開發產生構成助手庫規范的函數所需的專門技術需要花費大量的時間。

要節省一些走彎路的時間,這里是一些將使您自己的庫可以運轉起來的基本助手函數。

findValue
使用 XML 文檔時,最常執行的操作是查找給定節點的值。正如上所討論的,在遍歷文檔以查找期望的值和檢索節點的值中都出現難度。可以通過使用 XPath 來簡化遍歷,而值的檢索可以一次編碼然后重用。在兩個較低級函數的幫助下,我們實現了 getValue 函數,這兩個低級函數是:由 Xalan 包提供的 XPathAPI.selectSingleNode(用來查找和返回與給定的 XPath 表達式匹配的第一個節點);以及 getTextContents,它非遞歸地返回包含在節點中的連續文本值。請注意,JDOM 的 getText 函數,或將出現在 DOM 級別 3 中規劃的 getTextContent 方法,都可用來代替 getTextContents清單 2 包含了一個簡化的清單;您可以通過下載樣本代碼來訪問所有函數(請參閱參考資料)。

通過同時傳入要開始搜索的節點和指定要搜索節點的 XPath 語句來調用 findValue。函數查找第一個與給定 XPath 匹配的節點,并且抽取其文本值。

setValue
另一項常用的操作是將節點的值設置為希望的值,如清單 3 所示。該函數獲取一個起始節點和一條 XPath 語句 — 就象 findValue — 以及一個用來設置匹配的節點值的字符串。它查找希望的節點,除去其所有子節點(因此除去包含在其中的任何文本和其它元素),并將其文本內容設置為傳入的(passed-in)字符串。

appendNode
雖然某些程序查找和修改包含在 XML 文檔中的值,而另一些則通過添加和除去節點來修改文檔本身的結構。這個助手函數簡化了文檔節點的添加,如清單 4 所示。

該函數的參數有:要將新節點添加到其下的節點,要添加的新節點名稱,以及指定要將節點添加到其下位置的 XPath 語句(也就是,新節點的父節點應當是哪個)。新節點被添加到文檔的指定位置。

最終分析
DOM 的語言無關性設計為其帶來了非常廣泛的可應用性并使其在大量的系統和平臺上得以實現。這樣做的代價是:使 DOM 比為每個語言專門設計的 API 更困難且更缺乏直觀性。

DOM 奠定了一個非常有效的基礎,遵循一些簡單的原則就可其上構建易于使用的系統。凝結了一大群用戶智慧和經驗的 DOM 未來版本正在設計之中,而且極有可能為這里討論的問題提供解決方案。如 JDOM 這樣的項目正在修改該 API 以獲得更自然 Java 感覺,而且如本文中所述的技術可以幫助您使 XML 的操縱更方便、更簡潔并且不易出錯。利用這些項目且遵循這些用法模式以允許 DOM 成為基于 XML 項目的出色平臺。

參考資料



關于作者
作者照片:Parand Tony DarugarParand Tony Darugar 是 VelociGen Inc.的聯合創始人和首席軟件架構設計師,VelociGen Inc.是一個 Web 服務的軟件平臺供應商。他的興趣包括用于電子商務集成的高性能系統、智能分布式體系結構、神經網絡和人工智能。可以通過
tdarugar@velocigen.com


2005-03-23 13:10 發表評論
]]>
Java 中的 XML:文檔模型,第一部分:性能http://m.tkk7.com/jackybu/articles/2356.htmlWed, 23 Mar 2005 05:09:00 GMThttp://m.tkk7.com/jackybu/articles/2356.htmlhttp://m.tkk7.com/jackybu/comments/2356.htmlhttp://m.tkk7.com/jackybu/articles/2356.html#Feedback0http://m.tkk7.com/jackybu/comments/commentRss/2356.htmlhttp://m.tkk7.com/jackybu/services/trackbacks/2356.html在本文中,Java 顧問 Dennis Sosnoski 比較幾個 Java 文檔模型的性能和功能。當選擇模型時,無法做到每次都權衡得很清楚,如果以后改變主意,則需要大量編碼來進行切換。作者將性能結果放入特性集合的上下文中并遵循標準,對所要求的正確選擇給出了一些建議。本文包含用于這組測試的幾張圖表和源代碼。

使用內存中 XML 文檔的 Java 開發者可以選擇使用標準 DOM 表示或幾個 Java 特定模型中的任何一個。該靈活性已經幫助將 Java 建立成 XML 工作的出色平臺。但是,由于不同模型數量的增加,已經更加難以確定如何比較模型的功能、性能和易用性。

關于使用“Java 中的 XML”系列中的第一篇文章研究了 Java 中一些領先的 XML 文檔模型的特性和性能。它包括一組性能測試的結果(帶有可下載的測試代碼,請參閱參考資料)。在系列中的第二篇文章將通過比較為實現同樣任務所使用的不同模型的樣本代碼來研究易用性問題。

文檔模型
Java 中的可用文檔模型數一直在增加。對于本文,我已經涵蓋了最常用的模型和幾項選擇,這演示了那些可能還未被廣泛了解或使用的特別令人感興趣的特性。隨著“XML 名稱空間”的重要性增加,我已經包含了僅支持該功能的模型。下面列出了帶有簡要介紹和版本信息的模型。

僅為說明本文中所使用的術語:

  • 解析器是指解釋 XML 文本文檔結構的程序
  • 文檔表示是指程序用于內存中文檔的數據結構
  • 文檔模型是指支持使用文檔表示的庫和 API


某些 XML 應用程序根本不需要使用文檔模型。如果應用程序可以通過文檔的一次遍歷搜集它需要的信息,則可能直接使用解析器。該方法可能需要增加一些工作量,但是它的性能總是優于在內存中構建文檔表示。

DOM
DOM(“文檔對象模型”)是用與平臺和語言無關的方式表示 XML 文檔的官方 W3C 標準。對于任何 Java 特定的模型,它是很好的對照。為了值得與 DOM 標準分開,Java 特定模型應該提供比 Java DOM 實現更優越的性能和/或易用性的優勢。

DOM 定義充分利用了 XML 文檔不同組件的接口和繼承性。這為開發者帶來了將公共接口用于幾個不同類型組件的優勢,但是同時增加了 API 的復雜性。因為 DOM 是與語言無關的,所以接口不需要利用公共 Java 組件,例如,Collections 類。

本文涉及兩個 DOM 實現:Crimson 和 Xerces Java。Crimson 是基于 Sun Project X 解析器的 Apache 項目。它合并一個包含 DTD 支持的完整驗證解析器。可以通過 SAX2 接口訪問該解析器,并且 DOM 實現可以與其它 SAX2 解析器一起工作。Crimson 是在 Apache 許可證下發布的開放源碼。用于性能比較的版本是 Crimson 1.1.1(jar 文件大小是 0.2MB),它包含有用于從文本文件的 DOM 構建的 SAX2 解析器。

另一個測試的 DOM 實現,即 Xerces Java 是另一個 Apache 項目。初始時,Xerces 基于 IBM Java 解析器(通常稱為 XML4J)。(當前還處于早期 beta 測試版的重新開發的 Xerces Java 2 將最終繼承它。當前版本有時稱為 Xerces Java 1。)如同使用 Crimson 一樣,可以通過 SAX2 接口和 DOM 來訪問 Xerces 解析器。然而,Xerces 不提供將 Xerces DOM 與不同的 SAX2 解析器一起使用的任何方法。Xerces Java 包含對 DTD 和 XML Schema 的驗證支持(僅帶有對 Schema 支持的最小限制)。

Xerces Java 還支持 DOM 的延遲節點擴展方式(請參考本文中的延遲 XercesXerces def.),其中文檔組件初始時是以壓縮格式表示的,僅當使用時才將它擴展成完整的 DOM 表示。這種方式的用意是允許更快的解析并降低內存的使用,尤其對于那些可能僅使用部分輸入文檔的應用程序。與 Crimson 類似,Xerces 是在 Apache 許可證下發布的開放源碼。用于性能比較的版本是 Xerces 1.4.2(jar 文件大小是 1.8MB)。

JDOM
JDOM 的目的是成為 Java 特定文檔模型,它簡化與 XML 的交互并且比使用 DOM 實現更快。由于是第一個 Java 特定模型,JDOM 一直得到大力推廣和促進。正在考慮通過“Java 規范請求 JSR-102”將它最終用作“Java 標準擴展”。雖然實際將采用的格式仍在開發中,還是對兩個 beta 測試版的 JDOM API 做了很大的更改,。從 2000 年初就已經開始了 JDOM 開發。

JDOM 與 DOM 主要有兩方面不同。首先,JDOM 僅使用具體類而不使用接口。這在某些方面簡化了 API,但是也限制了靈活性。第二,API 大量使用了 Collections 類,簡化了那些已經熟悉這些類的 Java 開發者的使用。

JDOM 文檔聲明其目的是“使用 20%(或更少)的精力解決 80%(或更多)Java/XML 問題”(根據學習曲線假定為 20%)。JDOM 對于大多數 Java/XML 應用程序來說當然是有用的,并且大多數開發者發現 API 比 DOM 容易理解得多。JDOM 還包括對程序行為的相當廣泛檢查以防止用戶做任何在 XML 中無意義的事。然而,它仍需要您充分理解 XML 以便做一些超出基本的工作(或者甚至理解某些情況下的錯誤)。這也許是比學習 DOM 或 JDOM 接口都更有意義的工作。

JDOM 自身不包含解析器。它通常使用 SAX2 解析器來解析和驗證輸入 XML 文檔(盡管它還可以將以前構造的 DOM 表示作為輸入)。它包含一些轉換器以將 JDOM 表示輸出成 SAX2 事件流、DOM 模型或 XML 文本文檔。JDOM 是在 Apache 許可證變體下發布的開放源碼。用于性能比較的版本是 JDOM Beta 0.7(jar 文件大小是 0.1MB)它帶有用于從文本文件構建 JDOM 表示的 Crimson SAX2 解析器。

dom4j
雖然 dom4j 代表了完全獨立的開發結果,但最初,它是 JDOM 的一種智能分支。它合并了許多超出基本 XML 文檔表示的功能,包括集成的 XPath 支持、XML Schema 支持(當前為 alpha 格式)以及用于大文檔或流化文檔的基于事件的處理。它還提供了構建文檔表示的選項,它通過 dom4j API 和標準 DOM 接口具有并行訪問功能。從 2000 下半年開始,它就一直處于開發之中,保留了最近發行版之間的現有 API。

為支持所有這些功能,dom4j 使用接口和抽象基本類方法。dom4j 大量使用了 API 中的 Collections 類,但是在許多情況下,它還提供一些替代方法以允許更好的性能或更直接的編碼方法。直接好處是,雖然 dom4j 付出了更復雜的 API 的代價,但是它提供了比 JDOM 大得多的靈活性。

在添加靈活性、XPath 集成和對大文檔處理的目標時,dom4j 的目標與 JDOM 是一樣的:針對 Java 開發者的易用性和直觀操作。它還致力于成為比 JDOM 更完整的解決方案,實現在本質上處理所有 Java/XML 問題的目標。在完成該目標時,它比 JDOM 更少強調防止不正確的應用程序行為。

dom4j 使用相同方法作為 JDOM 輸出,這依靠 SAX2 解析器輸入處理,依靠轉換器將輸出處理成 SAX2 事件流、DOM 模型或 XML 文本文檔。dom4j 是在 BSD 樣式許可證下發布的開放源碼,該許可證本質上等價于 Apache 樣式許可證。用于性能比較的版本是 dom4j 0.9(jar 文件大小是 0.4MB),帶有用于從文本文件構建表示的受綁定 AElfred SAX2 解析器(由于 SAX2 選項設置,測試文件之一無法由 dom4j 使用用于 JDOM 測試的同一 Crimson SAX2 解析器來處理)。

Electric XML
Electric XML(EXML)是支持分布式計算的商業項目的附屬產物。它與目前為止討論的其它模型的不同之處在于,它只能適當地支持 XML 文檔的子集,它沒有為驗證提供任何支持并且有更嚴格的許可證。然而,EXML 的優勢是大小很小并提供了對 XPath 子集的直接支持,因為在最近幾篇文章中已經將它提升其它模型的替代模型,所以通過該比較使它成為一個引人注意的候選。

雖然 EXML 通過使用抽象的基本類方法取得了某些相同效果,但它在避免使用接口方面使用與 JDOM 類似的方法(主要區別是接口為擴展實現提供了更大的靈活性)。它與 JDOM 的不同之處還在于避免使用 Collections 類。該組合為其提供了非常簡單的 API,在許多方面類似于帶有附加 XPath 操作的 DOM API 簡化版本。

當空白與非空白文本內容鄰近時,EXML 才在文檔中保留空白,這就將 EXML 限制成 XML 文檔的一個子集。標準 XML 需要在讀取文檔時保留該空白,除非對文檔 DTD 可以確認有無空白無關緊要。對于事先已經知道空白無關緊要的許多 XML 應用程序來說,EXML 方法工作得很好,但是它防止對于期望保留空白的文檔(例如,生成由瀏覽器顯示或查看的文檔的應用程序)使用 EXML。(有關作者對于該主題的謙虛建議,請參閱副欄使用空白的目的。)

這種空白的刪除會對性能比較產生誤導效果 — 許多類型的測試范圍與文檔中的組件個數成比例,并且由 EXML 刪除的每個空白序列都是其它模型中的組件。EXML 包含在本文顯示的結果中,但是解釋性能差異時請記住這種影響。

EXML 使用集成的解析器依據文本文檔構建文檔表示。除了通過文本方式外,它不提供從 DOM(或 SAX2)轉換或轉換成 SAX2(或 DOM)事件流的任何方式。EXML 是由 Mind Electric 在禁止將它嵌入某些類型的應用程序或庫的受限許可證下發布的開放源碼。用于性能比較的版本是 Electric XML 2.2(jar 文件大小是 0.05MB)。

XML Pull Parser
XML Pull Parser (XPP)是最近開發的,它演示了 XML 解析的不同方法。與 EXML 一樣,XPP 只能適當支持 XML 文檔的子集并且不提供驗證的任何支持。它同樣具有尺寸小的優勢。這種優勢再與拉回解析器方法結合,使它成為該比較中的良好替換項。

XPP 幾乎獨占地使用接口,但是它僅使用所有類中的一小部分。和 EXML 一樣,XPP 避免使用 API 中的 Collections 類。總的來說,它是本文中最簡單的文檔模型 API。

將 XPP 限制成 XML 文檔子集的局限性是它不支持文檔中的實體、注釋或處理指示信息。XPP 創建僅包含元素、屬性(包括“名稱空間”)和內容文本的文檔結構。這對于某些類型的應用程序來說是一種非常嚴格的限制。但是通常它對性能的影響比 EXML 空白處理對性能的影響小。在本文中我僅使用了一個與 XPP 不兼容的測試文件,并且在帶有注釋的圖表中顯示了 XPP 結果,該注釋不包含該文件。

XPP 中的拉回解析器支持(本文中稱為 XPP 拉回)通過將解析實際上推遲到訪問文檔的一個組件時才進行,然后按照構造那個組件的需要對文檔進行解析。該技術想實現允許非常快速的文檔顯示或分類應用,尤其在需要轉發或除去(而不是對文檔進行完全解析和處理)文檔時。該方法的使用是可選的,如果以非拉回型方式使用 XPP,它對整個文檔進行解析并且同時地構建完整的表示。

與 EXML 一樣,XPP 使用依據文本文檔構建文檔表示的集成語法解析器,并且除了通過文本方式外,它不提供從 DOM(或 SAX2)轉換或轉換成 SAX2(或 DOM)事件流的任何方式。XPP 是具有 Apache 樣式許可證的開放源代碼。用于性能比較的版本是 PullParser 2.0.1 Beta 8(jar 文件大小是 0.04MB)。

測試詳細信息
所顯示的計時結果是來自使用 Sun Microsystems Java version 1.3.1、Java HotSpot Client VM 1.3.1-b24 測試,這些軟件是運行在帶有 256MB RAM 的 Athlon 1GHz 系統上的 Redhat Linux 7.1 下。將這些測試的初始 JVM 和最大內存大小都設置成 128MB,我想將它表示為服務器類型執行環境。

在使用初始缺省 JVM 內存設置為 2MB 和最大內存為 64MB 運行的測試中,帶有較大 jar 文件大小(DOM、JDOM 和 dom4j)的模型的結果非常差,尤其在運行測試的平均時間中。這可能是由于內存受限執行的 HotSpot JVM 的無效操作引起的。

文檔模型中的兩種(XPP 和 EXML)支持直接將文檔輸入成“字符串”或字符數組。該類型直接輸入不能代表實際應用程序,因此我在這些測試中避免使用它。對于輸入和輸出,我使用 Java 流封裝字節以消除 I/O 對性能的影響,而保留了用于 XML 文檔輸入和輸出的應用程序在典型情況下使用的語言接口。

性能比較
本文中使用的性能比較基于對一組選中的 XML 文檔進行的解析和使用,這些文檔試圖代表較大范圍的應用程序:

  • much_ado.xml,標記成 XML 的莎士比亞戲劇。沒有屬性并且是相當簡單的結構(202K 字節)。
  • periodic.xml, XML 中的元素的周期表。一些屬性,也是相當簡單的(117K 字節)。
  • soap1.xml,取自規范的樣本 SOAP 文檔。大量名稱空間和屬性(0.4K 字節,每次測試需要重復 49 次)。
  • soap2.xml,SOAP 文檔格式中的值列表。大量名稱空間和屬性(134K 字節)。
  • nt.xml,標記為 XML 的“新約”。沒有屬性并且非常簡單的結構,大量文本內容(1047K 字節)。
  • xml.xml,XML 規范,不帶 DTD 引用,在內部定義所有實體。帶有大量混合內容的文本樣式標記,一些屬性(160K 字節)。

關于測試平臺的更多信息,請參閱副欄測試詳細信息并查看參考資料以獲取用于測試的源代碼的鏈接。

除了非常小的 soap1.xml 文檔之外,所有評測時間都是指文檔的每次特定測試所經歷的時間。在 soap1.xml 的情況下,評測的時間是 49 個連續的文檔測試(總數為 20K 字節文本的足夠副本數)。

測試框架在一個文檔上運行一個特定的測試多次(這里顯示運行了 10 次),依此跟蹤該測試的最短時間和平均時間,然后繼續同一文檔上的下一個測試。完成對一個文檔的全部測試序列后,它對下一個文檔重復該過程。為防止文檔模型之間的交互,在執行每個測試框架時僅測試一個模型。

HotSpot 以及類似于動態優化 JVM 的計時基準程序是出了名的棘手的;測試序列中的小變化經常導致計時結果發生很大變化。我已經發現對于執行特定代碼段的平均時間時,確實如此;最短時間比較一致,正是我在這些結果中列出的值。可以參閱第一次測試(文檔構建時間)的平均和最短時間的比較。

文檔構建時間
文檔構建時間測試檢查解析文本文檔和構造文檔表示所需的時間。出于比較目的,已經在圖表中包含了使用 Crimson 和 Xerces SAX2 解析的 SAX2 解析時間,因為大多數文檔模型(除了 EXML 和 XPP 外的所有文檔)使用 SAX2 解析事件流作為文檔構建過程的輸入。圖 1 描述了測試結果。

圖 1. 文檔構建時間
文檔構建時間圖

對于大多數測試文檔來說,XPP 拉回的構建時間太短以至于難以計算(因為在這種情況下,實際上沒有對該文檔進行解析),只顯示為非常短的 soap1.xml。對于該文件,拉回解析器內存大小和相關的創建開銷使 XPP 顯得相對比較緩慢。這是因為測試程序為正在進行解析的文檔的每個副本創建一個新的拉回解析器副本。在 soap1.xml 情況下,每次評測時間使用 49 個副本。分配與初始化這些解析器實例的開銷大于重復解析文本并構建文檔表示的大多數其它方法所需的時間。

XPP 的作者在一個電子郵件的討論中指出,在實際應用程序中,可以合用拉回解析器實例以重新使用。如果這樣做的話,soap1.xml 文件的開銷將明顯降到忽略不計程度。對于更大的文件,甚至不需要合用,拉回解析器創建開銷也可以變得忽略不計。

在本測試中,XPP(帶有完整解析),帶有延遲節點創建的 Xerces 和 dom4j 都顯示整體上的同等性能。延遲的 Xerces 對于較大的文檔尤其出色,但是對于較小的文檔的開銷較高 — 甚至比常規 Xerces DOM 高很多。在第一次使用文檔的一部分時,延遲節點創建方法的開銷也較高,這會降低快速解析的優勢。

對于較小的 soap1.xml 文件,所有格式(SAX2 解析、常規 DOM 和延遲 DOM)的 Xerces 的顯得開銷較高。對于該文件 XPP(完全解析)尤其出色,對于 soap1.xml,EXML 甚至超過基于 SAX2 的模型。雖然 EXML 具有廢棄單獨的空白內容的優勢,但是總體上,它是本測試中最差的。

文檔遍歷時間
文檔遍歷時間測試檢查遍歷構造的文檔表示所需的時間,按文檔順序遍歷每個元素、屬性和文本內容段。它試圖表示文檔模型接口的性能,這對于從進行過解析的文檔中重復訪問信息的應用程序來說可能很重要。總體上,遍歷時間比解析時間快得多。對于只對解析過的文檔單次遍歷的應用程序,解析時間將比遍歷時間更重要。圖 2 顯示了結果。

圖 2. 文檔遍歷時間
文檔遍歷時間表

在本測試中,XPP 的性能大大超過了其余的測試對象。Xerces DOM 所花費的時間大約是 XPP 的兩倍。雖然 EXML 具有廢棄文檔中單獨的空白內容的優勢,但是,EXML 花費的時間幾乎是 XPP 的三倍。dom4j 在這張圖中處于中間位置。

使用 XPP 拉回時,直到訪問文檔表示時才真正發生對文檔文本的解析。這導致第一次遍歷文檔表示時的開銷非常大(表中未顯示)。如果以后訪問整個文檔表示,則當使用拉回解析方法時,XPP 顯示性能的凈損失。對于拉回解析器來說,前兩個測試所需的總時間比使用 XPP 的常規解析長(長 20% 到 100%,這取決于文檔)。但是,當還未完全訪問正在進行解析的文檔時,拉回解析器方法仍然具有可觀的性能優勢。

帶有延遲節點創建的 Xerces 顯示了相似的行為,第一次訪問文檔表示時導致性能下降(圖中未顯示)。但是,在 Xerces 情況下,節點創建開銷大約與解析期間常規 DOM 創建的性能差值相同。對于較大的文檔來說,用 Xerces 延遲的前兩次測試所需的總時間大致與使用帶常規 DOM 構建的 Xerces 所用的時間相同。如果在非常大的文檔(可能 10KB 或更大)上使用 Xerces,則延遲的節點創建似乎是一個好選擇。

文檔修改時間
這個測試檢查系統地修改構造文檔表示所需的時間,其結果在圖 3 中顯示。它遍歷表示,刪除所有單獨的空白內容并且用新添加的元素封裝每個非空白內容字符串。它還向包含非空白內容的原始文檔的每個元素中添加一個屬性。該測試試圖表示經過一定范圍文檔修改后文檔模型的性能。如遍歷時間一樣,修改時間比解析時間短很多。因此,對于僅單次遍歷每個解析過的文檔的應用程序來說,解析時間將更重要。

圖 3. 文檔修改時間
文檔修改時間表

這次測試中 EXML 處于領先地位,但是由于在解析期間它總是廢棄單獨的空白內容,它才比其它模型具有性能上的優勢。這意味著在測試期間沒有要從 EXML 表示中進行刪除的內容。

在修改性能方面,XPP 僅次于 EXML,并且與 EXML 不同,XPP 測試包含刪除。Xerces DOM 和 dom4j 接近地處于中間位置,JDOM 和 Crimson DOM 模型的性能仍是最差。

文本生成時間
這個測試檢查將文檔表示輸出成文本 XML 文檔所需的時間;結果顯示在圖 4 中。對于不專門使用 XML 文檔的任何應用程序,該步驟似乎是整體性能的一個重要部分,特別是因為將文檔輸出為文本所需的時間總體接近于對文檔輸入進行解析所需的時間。為使這些時間具有直接的可比性,該測試使用原始文檔,而沒有使用由前面的測試所生成的已修改文檔。

圖 4. 文本生成時間
文本生成時間表

文本生成時間測試表明各模型之間的差別小于前面測試中各項的差別,Xerces DOM 性能最好,但領先不多,JDOM 性能最差。EXML 的性能優于 JDOM,但是這同樣是由于 EXML 廢棄空白內容。

許多模型提供了控制文本輸出格式的選項,并且一些選項似乎影響文本生成時間。這個測試只使用每個模型的最基本的輸出格式,因此結果只顯示缺省性能而不顯示最好的可能性能。

文檔內存大小
這個測試檢查用于文檔表示的內存空間。這對于使用大文檔或同時使用多個較小文檔的開發者來說,意義尤為重要。圖 5 顯示這個測試的結果。

圖 5. 文檔內存大小
文檔內存大小表

內存大小結果與計時測試不同,因為小的 soap1.xml 文件顯示的值表示文件的單個副本而不表示在計時評測中使用的 49 個副本。在大多數模型中,用于簡要文檔的內存太小以至于無法在圖的刻度上顯示。

除了 XPP 拉回(直到訪問它時才真正構建文檔表示)之外,與一些計時測試中顯示的差別相比,內存大小測試中模型之間的差別相對較小。延遲的 Xerces 具有最緊湊的表示(當第一次訪問表示時將它擴展成基本 Xerces 大小),緊接著是 dom4j。雖然 EXML 廢棄包含在其它模型中的空白內容,但是它仍具有最不緊湊的表示。

因為即使最緊湊的模型也要占用大約原始文檔文本大小(以字節計)四倍的空間,所以對于大文檔來說,所有模型似乎都需要太多的內存。通過提供使用部分文檔表示的方法,XPP 拉回和 dom4j 為非常大的文檔提供了最好的支持。XPP 拉回通過僅構建實際被訪問的表示部分完成該任務,而 dom4j 包含對基于事件處理的支持,使得一次只構建或處理文檔的一部分。

Java 序列化
這些測試評測文檔表示的 Java 序列化的時間和輸出大小。這主要涉及那些使用 Java RMI(“遠程方法調用”)在 Java 程序之間傳送表示的應用程序(包括 EJB (Enterprise JavaBean) 應用程序)起作用。在這些測試中,僅包含了那些支持 Java 序列化的模型。下列三張圖顯示了該測試的結果。

圖 6. 序列化輸出時間
序列化輸出時間表

圖 7. 序列化輸入時間
序列化輸入時間表

圖 8. 序列化文檔大小
序列化文檔大小表

dom4j 顯示了輸出(生成序列化的格式)和輸入(從序列化的格式重新構建文檔)的最好的序列化性能,而 Xerces DOM 顯示了最差的性能。EXML 所花費的時間接近 dom4j,但是 EXML 還是具有在表示中使用較少數量對象的優勢,因為它廢棄空白內容。

如果將文檔輸出成文本然后進行解析以重新構建文檔,而不是使用 Java 序列化,則所有性能 — 時間和大小 — 都會好得多。這里的問題是作為大量唯一的小對象的 XML 文檔表示的結構。Java 序列化無法有效處理這種類型的結構,這導致時間和輸出大小的開銷都很高。

可以設計比文本表示小且比文本輸入和輸出快的文檔序列化格式,但是只能通過繞過 Java 序列化來完成。(我有一個項目實現 XML 文檔的這種定制的序列化,在我公司的 Web 站點上可以找到其開放源碼,請參閱參考資料。)

結束語
不同的 Java XML 文檔模型各有所長,但是從性能觀點來看,有些模型具有明顯的優勢。

在大多數方面,XPP 性能處于領先地位。盡管 XPP 是一種新模型,但是對于不需要驗證、實體、處理指示信息或注釋的中間件類型應用程序來說,它是非常好的選擇。它尤其適用于作為瀏覽器小應用程序或在內存受限的環境下運行的應用程序。

雖然 dom4j 沒有與 XPP 同等的速度,但是,它確實提供了具備更標準化的優越性能和功能更全的實現,包括對 SAX2、DOM 甚至 XPath 的內置支持。雖然 Xerces DOM(帶有延遲的節點創建)對于小文件和 Java 序列化性能不佳,但是在大多數評測時仍然出色。對于常規 XML 處理,dom4j 和 Xerces DOM 都是很好的選擇,對它們的選擇取決于您認為是特定于 Java 的特性更重要還是跨語言的兼容性更重要。

JDOM 和 Crimson DOM 在性能測試時一直表現不佳。在小文檔情況下還值得考慮使用 Crimson DOM,而 Xerces 表現很差。雖然 JDOM 的開發者已經說明他們期望在正式發行版前專注性能問題,但是從性能觀點來看,它確實沒有值得推薦之處。然而,如果不進行 API 的重新構建,JDOM 可能難以達到與其它模型匹配的性能。

使用空白的目的
XML 規范通常需要保留空白,但是許多 XML 應用程序使用僅為可讀性而保留空白的格式。對于這些應用程序,EXML 廢棄隔離空白的方法起了作用。

這些性能中使用的大多數文檔屬于“為可讀性而保留的空白”類別。這些文檔被格式化成便于人們查看的形式,一行最多一個元素。結果,無關的空白內容字符串數實際上超過了文檔中的元素數量。這大大增加了每一步處理的不必要開銷。

支持修剪輸入上這種類型空白的選項將有助于提高帶有可忽略的空白的應用程序的所有文檔模型的性能(除了 EXML 之外)。只要修剪是一個選項,它就不會影響需要完全保留空白的應用程序。解析器級別的支持將更好,因為解析器必須逐一處理輸入字符。總之,這種類型的選項將非常有助于許多 XML 應用程序。

EXML 非常小(以 jar 文件大小為單位)并且在一些性能測試中表現良好。雖然 EXML 具有刪除單獨空白內容的優勢,但是在性能方面不如 XPP。除非您需要 EXML 支持而 XPP 缺少的一種特性,否則在內存受限的環境下,XPP 可能是更好的選擇。

雖然 dom4j 性能最好,但是,當前沒有一種模型能為 Java 序列化提供良好性能。如果需要在程序之間傳遞文檔,通常的最佳選擇是將文檔寫成文本然后進行解析以重新構建表示。將來,定制序列化格式可能提供一個更好的選擇。


我已經涵蓋了一些文檔模型的基本特性,并且顯示了幾種類型文檔操作的性能評測。請記住,雖然,性能只是選擇文檔模型的一個因素。對于大多數開發者,可用性至少與性能一樣重要,并且這些模型使用不同的 API 都可能有喜歡這個而不喜歡那個的理由。

在后續文章中將集中研究可用性,其中我將比較用于完成這些不同模型中相同操作的樣本代碼。請檢查本比較的第二部分。當您等待時,可以通過下面的論壇中的鏈接提出您對本文的評論和問題與大家共享。

參考資料



關于作者
作者照片:Dennis M. SosnoskiDennis Sosnoski(
dms@sosnoski.com)是西雅圖地區 Java 咨詢公司 Sosnoski Software Solutions, Inc. 的創建者和首席顧問。他具有 30 多年的專業軟件開發經驗,最近幾年,他集中研究服務器方 Java 技術,包括 servlet、Enterprise JavaBeans 和 XML。他已經多次演示了 Java 性能問題和常規服務器端的 Java 技術,他還是 Seattle Java-XML SIG 的主席。



2005-03-23 13:09 發表評論
]]>
用JDOM處理XML文檔http://m.tkk7.com/jackybu/articles/2355.htmlWed, 23 Mar 2005 05:08:00 GMThttp://m.tkk7.com/jackybu/articles/2355.htmlhttp://m.tkk7.com/jackybu/comments/2355.htmlhttp://m.tkk7.com/jackybu/articles/2355.html#Feedback0http://m.tkk7.com/jackybu/comments/commentRss/2355.htmlhttp://m.tkk7.com/jackybu/services/trackbacks/2355.html

用JDOM處理XML文檔

發布日期:2002-04-09 作者:AYellow 閱讀人次:4736   用JDOM處理XML文檔

關鍵詞:Java、JDOM、XML、JAXB
歡迎與我討論(boyofjava@sina.com)

(一)JDOM的介紹以及與JAXB的比較

Java + XML = JDOM !
這就是JDOM設計者的目標。如果你曾經使用過煩人的SAX或是DOM來處理XML,你就會知道為什么要有JDOM或者是JAXB。在今年(2002)的JavaOne會議上JDOM的主要創始人Jason Hunter有一篇精彩的演講介紹了JDOM技術,題目就是JDOM Makes XML Easy。
在那篇文檔里,JDOM被拿來與DOM比較,而我更愿意拿它同JAXB比較。因為JAXB和JDOM都是為了在Java中提供比DOM和SAX更為方便的XML處理接口而開發的,并且通過完全不同的途徑來解決這個問題。JDOM的處理方式是與DOM類似的樹操作。而JAXB通過DTD和綁定模式來生成訪問XML文檔的Java代碼,將XML映射成了Java對象來操作。你可以根據項目的需要和個人喜好來決定采用哪一個。
JDOM與JAXB的比較,從本身的特點來看:
1)    JDOM比JAXB更容易上手。使用JAXB首先要會編寫DTD,然后還要會編寫綁定模式。JDOM沒有這樣的要求,如果你會Java和XML,甚至可以說光是看JDOM的javadoc文檔就能夠使用JDOM。
2)    JAXB編寫好DTD和綁定模式以后,XML文檔被映射成了Java對象,其數據就是Java對象的屬性,連數據類型都做好了轉換,因此,訪問XML文檔比JDOM要簡便,可以說是一勞永逸。
3)    JAXB由某個DTD和綁定模式生成的代碼只能訪問該DTD所約束的文檔。如果想要訪問其他XML文檔,需要再編寫DTD和綁定模式。JDOM可以處理任何XML文檔,包括受約束的和不受約束的。

目前JDOM和JAXB都沒有正式版本。JDOM的最新版本是beta8,JAXB是1.0 early access,其規范版本是0.21。相對而言,JDOM更成熟一些。例如JAXB不支持名字空間、不能向XML文檔寫入處理指令,有時我們需要保留的換行符和首尾空格在JAXB中自動過濾掉了,就連放在<![CDATA[ 和 ]]>里面也不能幸免。JDOM就沒有這些限制。如果說以上的3點比較是JDOM和JAXB本身的特點所決定的,幾乎不可能改變,那么這里表明,JAXB還需要更多的工作。

(二)獲得并安裝JDOM
在http://jdom.org可以下載JDOM的最新版本。以JDOM beta8的2進制版本為例。下載后解壓縮,JDOM的jar文件就是build目錄下的文件jdom.jar,將之加入類路徑。另外JDOM還需要lib目錄下那些jar文件如xerces.jar的支持。如果在使用中出現以下錯誤:
java.lang.NoSuchMethodError
或 
java.lang.NoClassDefFoundError: org/xml/sax/SAXNotRecognizedException
你需要保證xerces.jar文件在CLASSPATH中位于其他XML類,如JAXP或Crimson之前,這些類文件,包括以前老版本的xerces,可能不支持SAX2.0或DOM Level 2。于是導致了上面的錯誤。

(三)一個簡單的例子
JDOM的處理方式有些類似于DOM,但它主要是用SAX實現的,你不必擔心處理速度和內存的問題。另外,JDOM中幾乎沒有接口,的類全部是實實在在的類,沒有類工廠類的。其最重要的一個包org.jdom中主要有以下類:
? Attribute
? CDATA
? Comment
? DocType
? Document
? Element
? EntityRef
? Namespace
? ProcessingInstruction
?    Text
數據輸入要用到XML文檔要通過org.jdom.input包,反過來需要org.jdom.output。如前面所說,關是看API文檔就能夠使用。
我們的例子讀入XML文件exampleA.xml,加入一條處理指令,修改第一本書的價格和作者,并添加一條屬性,然后寫入文件exampleB.xml:
//exampleA.xml
<?xml version="1.0" encoding="GBK"?>
<bookList>
    <book>
        <name>Java編程入門</name>
        <author>張三</author>
        <publishDate>2002-6-6</publishDate>
        <price>35.0</price>
    </book>
    <book>
        <name>XML在Java中的應用</name>
        <author>李四</author>
        <publishDate>2002-9-16</publishDate>
        <price>92.0</price>
    </book>
</bookList>

//testJDOM.java
import org.jdom.*;
import org.jdom.output.*;
import org.jdom.input.*;
import java.io.*;
public class TestJDOM{
    public static void main(String args[])throws Exception{
        
        SAXBuilder sb = new SAXBuilder();

        //從文件構造一個Document,因為XML文件中已經指定了編碼,所以這里不必了
        Document doc = sb.build(new FileInputStream("exampleA.xml"));
        
        //加入一條處理指令\
        ProcessingInstruction pi = new ProcessingInstruction
            ("xml-stylesheet","href=\"bookList.html.xsl\" type=\"text/xsl\"");
        doc.addContent(pi);


        Element root = doc.getRootElement(); //得到根元素
        java.util.List books = root.getChildren(); //得到根元素所有子元素的集合
        Element book = (Element)books.get(0); //得到第一個book元素
        //為第一本書添加一條屬性
        Attribute a = new Attribute("hot","true");  
        book.setAttribute(a);
        Element author = book.getChild("author"); //得到指定的字元素
        author.setText("王五\\"); //將作者改為王五
        //或 Text t = new Text("王五\\");book.addContent(t);
        Element price = book.getChild("price"); //得到指定的字元素
        //修改價格,比較郁悶的是我們必須自己轉換數據類型,而這正是JAXB的優勢
        author.setText(Float.toString(50.0f)); 
        


        String indent = "    ";
        boolean newLines = true;
        XMLOutputter outp = new XMLOutputter(indent,newLines,"GBK");
        outp.output(doc, new FileOutputStream("exampleB.xml"));

    }
};

執行結果exampleB.xml:
<?xml version="1.0" encoding="GBK"?>
<bookList>
    <book hot=”true”>
        <name>Java編程入門</name>
        <author>50.0</author>
        <publishDate>2002-6-6</publishDate>
        <price>35.0</price>
    </book>
    <book>
        <name>XML在Java中的應用</name>
        <author>李四</author>
        <publishDate>2002-9-16</publishDate>
        <price>92.0</price>
    </book>
</bookList>
<?xml-stylesheet href="bookList.html.xsl" type="text/xsl"?>

在默認情況下,JDOM的Element類的getText()這類的方法不會過濾空白字符,如果你需要過濾,用setTextTrim() 。


(四)參考文檔
1)    JDOM Makes XML Easy (http://www.servlets.com/speaking/jdom-javaone.pdf)
2)    The Java &#8482; Architecture for XML Binding User’s Guide (http://java.sun.com/xml/jaxb/jaxb-docs.pdf)
3)    Web Services Made Easier. The Java TM APIs and Architectures for XML, A Technical White Paper (http://java.sun.com/xml/webservices.pdf )





  整理發布:umbrella

2005-03-23 13:08 發表評論
]]>
Java XML API 漫談 http://m.tkk7.com/jackybu/articles/1752.htmlSat, 05 Mar 2005 14:10:00 GMThttp://m.tkk7.com/jackybu/articles/1752.htmlhttp://m.tkk7.com/jackybu/comments/1752.htmlhttp://m.tkk7.com/jackybu/articles/1752.html#Feedback0http://m.tkk7.com/jackybu/comments/commentRss/1752.htmlhttp://m.tkk7.com/jackybu/services/trackbacks/1752.html在IBM的developerWorks上有幾篇非常優秀的關于Java XML API的評測文章,它們是:

http://www-900.ibm.com/developerWorks/cn/xml/x-injava/index.shtml

http://www-900.ibm.com/developerWorks/cn/xml/x-injava2/index.shtml

http://www-900.ibm.com/developerWorks/cn/xml/x-databdopt/part2/index.shtml

http://www-900.ibm.com/developerWorks/cn/xml/x-databdopt/part1/index.shtml

對這幾篇文章我想說的就是“吐血推薦”

Java的XML API這幾篇文章該講的都講到了,我只想補充幾點:

一、Crimson和Xerces恩仇錄

Crimson來自于Sun捐贈給Apache的ProjectX項目,Xerces來自IBM捐贈給Apache的XML4J項目,結果Xerces勝出,成了Apache XML小組全力開發的XML API,而Crimon已經早就不做了,如今Xerces名滿天下,到處都是在用Xerces DOM和SAX解析器,只有Sun不服氣,非要在JDK1.4里面使用過時的Crimson,讓人感覺像是在賭氣一樣,真是讓人可憐又可氣!不過IBM發行JDK用的XML 解析器自然是Xerces。

由于JDK的Class Loader的優先級關系,當你采用JAXP編寫XML程序的時候,即使把Xerces包引入CLASSPATH,JDK還是會頑固的使用Crimson,這一點通過打開JVM的verbose參數可以觀察到。不過JDK也允許你采用其它的解析器,因此我們可以通過在JRE\lib\目錄下建一個jaxp.properties的文件,來替換解析器,jaxp.properties內容如下:

引用:
javax.xml.parsers.DocumentBuilderFactory=org.apache.xerces.jaxp.DocumentBuilderFactoryImpl
javax.xml.parsers.SAXParserFactory=org.apache.xerces.jaxp.SAXParserFactoryImpl

這樣就可以使用Xerces,當然你必須還是要把Xerces包放到CLASSPATH下。

二、JAXP的姍姍來遲

Sun在XML領域總是后知后覺,等到Sun重視XML的時候,XML的API早就滿天 飛了,尤其是IBM具有非常大的領先優勢。不過Sun是規范的制訂者,于是參考W3C的標準制訂了JAXP規范。JAXP不像Xerces和Crimon那樣,它只是一個spec,本身是不做任何事情的,它的作用就是提出一個統一的接口,讓其它的XML API都來遵循JAXP編程,那么用JAXP寫出來的程序,底層的API可以任意切換。

具體來說JAXP包括了幾個工廠類,這就是JDK1.4里面的javax.xml.parsers 包,用來尋找符合DOM標準的XML API實現類的位置;此外JAXP還包括一整套interface,這就是JDK1.4里面的org.w3c.dom那幾個包。工廠類負責加載DOM的實現類。那么加載的規則是什么呢?

我是通過閱讀JAXP的源代碼知道的,工廠類首先會根據java命令行傳入的參數進行尋找,然后在根據JRE\lib\jaxp.properties中定義的實現類尋找,最后什么都找不到的話,就用Crimson。注意Crimons是由Bootstrap Class Loader來load的,如果你不通過上面兩個方法來改變工廠的尋找順序,那么鐵定用Crimson了 Sad

三、 DOM解析器和DOM API

當你嚴格采用JAXP編程的時候,是遵循W3C的DOm標準的,那么在JAXP底層你實際上可以任意切換不同的DOM實現,例如Xerces,或者Crimon,再或者其它,切換方法就是配置jaxp.properties。因此JAXP就是一些標準接口而已。

而Xerces和Crimon也不單單是一個DOM實現那么簡單,他們本身實際上也包含SAX解析器和DOM解析器。所以一個JAXP程序下面有如下層次:

引用:
JAXP應用程序 -> JAXP接口 -> Xerces DOM實現 -> Xerces DOM/SAX 解析器


只要你用JAXP編程,那么你就可以切換到Crimson上來

引用:
JAXP應用程序 -> JAXP接口 -> Crimson DOM實現 -> Crimson DOM/SAX 解析器


另外你也可以這樣來做:

引用:
JAXP應用程序 -> JAXP接口 -> Crimson DOM實現 -> Xerces DOM/SAX 解析器


不過如果你的程序不安裝JAXP來寫,那么就沒有辦法切換不同的DOM實現了。

四、不是標準的dom4j和jdom

W3C的DOM標準API難用的讓人想撞墻,于是有一幫人開發Java專用的XML API目的是為了便于使用,這就是jdom的由來,開發到一半的時候,另一部分人又分了出來,他們有自己的想法,于是他們就去開發dom4j,形成了今天這樣兩個API,至于他們之間的性能,功能之比較看看上面我推薦的文章就知道了,jdom全面慘敗。

jdom 相當于上面的 JAXP接口 + Xerces DOM實現部分,它本身沒有解析器,它可以使用Xerces或者Crimson的解析器,就是這樣:

引用:
jdom應用程序 -> jdom API -> Xerces/Crimson解析器


dom4j 和jdom類似,不過他自己綁定了一個叫做Alfred2的解析器,功能不是很全,但是速度很快,當沒有其它的解析器的時候,dom4j將使用Alfred2解析器,如下:

引用:
dom4j應用程序 -> dom4j API -> Xerces/Crimson解析器


或者

引用:
dom4j應用程序 -> dom4j API -> Alfred2解析器


你在SF上下載的dom4j.jar是不含 Alfred2解析器的,而dom4j-full.jar包含了 Alfred2解析器,在這種情況下,實際上你什么也不需要,光是一個dom4j-full.jar就全部都包括了。

因此可以看出采用dom4j/jdom編寫的應用程序,已經不具備可移植性了。

五、小插曲

Sun是JAXP標準的制訂者,甚至很執著的在JDK1.4里面綁定Crimson DOM實現和解析器,然后可笑的是,Sun自己的JAXM RI竟然不是用JAXP寫出來的,而是dom4j,制訂標準讓大家遵守,自己卻監守自盜,這未免太說不過去了吧!

BTW: Hibernate也用的是dom4j來讀取XML配置文件,如今已經越來越多的程序紛紛采用dom4j,如果你不是那么在乎可移植性,我強烈建議你采用dom4j。


2005-03-05 22:10 發表評論
]]>
XML在B/S架構開發中的應用 http://m.tkk7.com/jackybu/articles/1275.htmlThu, 17 Feb 2005 07:41:00 GMThttp://m.tkk7.com/jackybu/articles/1275.htmlhttp://m.tkk7.com/jackybu/comments/1275.htmlhttp://m.tkk7.com/jackybu/articles/1275.html#Feedback0http://m.tkk7.com/jackybu/comments/commentRss/1275.htmlhttp://m.tkk7.com/jackybu/services/trackbacks/1275.html
  動機:

  1. JSP構造頁面太痛苦. 代碼嵌入頁面導致頁面臃腫, 邏輯復雜, 不易編輯.
  2. 程序員和美工的接口必須精確定義. 復雜的嵌套輸出必須構造復雜的顯示類作為接口.
  3. 調試JSP必須等到前端程序結束才能看到效果.
  4. 調試JSP效率極低, 一點點小的修改都會引起jsp的重新編譯.

  新的XML的解決方案:

  1. 一個servlet做統一管理. 接受url請求, 映射到對應的程序bean.
  2. 程序bean只產生XML, 由servlet統一用對應的XSL轉換為html.

  好處:

  1. JDK1.4已經自帶XML API和XSLT, 并且支持XSLT 1.0標準. IE6也支持同樣標準.
  2. 開發前先制作接口用的demo XML, 程序員的輸出必須符合此格式. 美工根據此格式制作XSL. 接口可視化, 明確定義.
  3. 只要定義好接口XML, 兩邊幾乎不需要再聯合調試. 美工利用接口XML和IE6就可以直接看到效果.
  4. 程序員只要保證輸出的XML的是否符合接口, 直觀的做文字性對比.
  5. 大量頁面可重用的部分能方便的抽象出來, 在XSL文件中include/import進來. 高度重用, 標準化.
  6. 寫XSL可大量采用template, 結構清晰, 修改方便, 寫頁面變成搭積木, 不再有jsp的層層嵌套.
  7. 對于不同的終端設備, 可以定制不同的XSL, 很方便的就能夠支持手機, pda...
  8. 在程序中利用支持XML的工具, 比如sql2000, 可以直接生成XML結果, 無需復雜編程.

  壞處:

  1. 因為要進行XSLT轉換, 服務器工作量稍微增大.

  以下是幾個關鍵環節的簡單示例:

==============servlet中的XML轉換==========
/**
* XML是String
* XSL從文件中讀取
* 直接寫入response
*/
//import javax.xml.transform.*;
//import javax.xml.transform.stream.*;

StreamSource xml = new StreamSource(new StringReader(xmlSource));
StreamSource xsl = new StreamSource(xslFileName);

response.setContentType("text/html; charset=UTF-8");
StreamResult result = new StreamResult(response.getWriter());

Transformer trans = TransformerFactory.newInstance().newTransformer(xsl);
trans.setOutputProperty("encoding","UTF-8");
trans.transform(xml, result);

==================接口XML文件樣本=============
<?xml version="1.0" encoding="UTF-8"?>
<?xml:stylesheet type="text/xsl" href="xxx.xsl"?>
<PAGE>
<OUTPUT>
<INFO>推廣UNICODE, 解決多語言問題.</INFO>
</OUTPUT>
</PAGE>

==================XSL文件樣本=============
<?xml version="1.0" encoding="UTF-8"?>
<?xml:namespace prefix = xsl /><xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">

<P align=center>
<xsl:value-of select="page/output/info"></xsl:value-of>

</xsl:template>
</xsl:stylesheet>

  考慮:

  1. 可以把XSLT轉換放在IE進行, IE6以下版本要安裝MSXML3, 對客戶端限制太多.
  2. 在客戶端的XSLT轉換必須完全讀入XML+XSL, 在網速不高或者內容很多的時候反應相當遲鈍.
  3. 手機, pda等設備不能進行XSLT轉換, 所以XSLT轉換還是放在服務器上比較合適.
  4. 因為一般提交內容都比較簡單, 所以沒有采用XML處理, 在程序中也盡量少用XML, 避免影響速度.
  5. 可以在servlet判斷特定參數, 比如發現有xml-view參數時, 不做XSLT轉換, 直接輸出XML到瀏覽器, 供程序調試用.

  說明:

  1. 這里只是利用XSLT的功能來解決網站建設中最頭痛的界面和邏輯分開問題, 不是基于XML的內容處理.
  2. 如果你想趕時髦, 用XML來全副武裝, 可以參考cocoon2, http://xml.apache.org/cocoon/. 但是請記住, cocoon

  還不完善, 你將碰到亂碼, 速度, 文檔不足的問題, 但是它的思想的確值得學習.
  3. 在tomcat, resin中有用filter實現的xslt轉換,可參考。



2005-02-17 15:41 發表評論
]]>
Java與XML(一)基礎 http://m.tkk7.com/jackybu/articles/1208.htmlTue, 15 Feb 2005 03:28:00 GMThttp://m.tkk7.com/jackybu/articles/1208.htmlhttp://m.tkk7.com/jackybu/comments/1208.htmlhttp://m.tkk7.com/jackybu/articles/1208.html#Feedback0http://m.tkk7.com/jackybu/comments/commentRss/1208.htmlhttp://m.tkk7.com/jackybu/services/trackbacks/1208.htmlJAXP API--嵌入不同的解釋器

SAX解釋器+DOM解釋器+XSL轉換器

javax.xml.parsers中加載XML文檔的類:
DocumentBuilder
DocumentBuildrFactory
SAXParser
SAXParserFactory
=====================================
SAX API

SAX的XML解釋器:Apache的Xerces或Crimson

處理XML文檔的接口:
ContentHandler
EntityResolver
ErroHandler
DTDHandler

DeclHandler
LexicalHandler
======================================
DOM API

兩個DOM標準:DOM Level1 DOM Level 2 Core

節點
Node-節點類型接口層次結構的根。
Document-樹結構的根
Element-XML元素
Text-元素內的文本
Attr-元素的特性
CDATA Sectionn-CDATA
NodeList-子節點的集合
ProcessingInstruction-指令
Comment-包含注釋的信息
DocumentFragment-Document的消減版,用于在樹中移動節點
DocumentType-文檔類型定義的子集。
Entity-DTD中的實體標記
EntityReference-XML文檔中的實體引用
Notation-DTD中的符號標記


從程序中讀取X M L文檔基本上有三種方式:
1把X M L只當做一個文件讀取,然后自己挑選出其中的標簽。這是黑客們的方法,我們不推薦這種方式。
你很快會發現處理所有的特殊情況(包括不同的字符編碼,例外約定,內部和外部實體,缺省屬性等)比想象的困難得多;
你可能不能夠正確地處理所有的特殊情況,這樣你的程序會接收到一個非常規范的X M L文檔,卻不能正確地處理它。
要避免這種想法:XML解析器似乎并不昂貴(大多數是免費的)。
2可以用解析器分析文檔并在內存里創建對文檔內容樹狀的表達方式:解析器將輸出傳遞給文檔對象模型,即DOM。
這樣程序可以從樹的頂部開始遍歷,按照從一個樹單元到另一個單元的引用,從而找到需要的信息。
3也可以用解析器讀取文檔,當解析器發現標簽時告知程序它發現的標簽。
例如它會告知它何時發現了一個開始標簽,何時發現了一些特征數據,以及何時發現了一個結束標簽。
這叫做事件驅動接口,因為解析器告知應用程序它遇到的有含義的事件。
如果這正是你需要的那種接口,可以使用SAX。

SAX是只讀的
DOM可以從XML原文件中讀取文檔,也可以創建和修改內存中的文檔。相比較而言,SAX是用來讀取XML文檔而不是書寫文檔。

可擴展樣式語言(XSL,eXtensible Sytlesheet Language)是一種基于XML的語言,
它被設計用來轉換XML文檔到另一種XML文檔或轉換XML文檔為可翻譯對象。
原始的XSL語言已經被分割成三種不同的語言:
1轉換工具(XSLT)是一種轉換XML文檔到其他XML文檔的語言
2翻譯工具(XSLF—可以包括X S LT的使用)
3XML分級命令處理工具(XPath)
XSL有它自已的根,不管是在層疊樣式表(CSS)中還是在一種叫DSSSL(文檔樣式語義和規格語言—讀為'deessel')的語言中。
隨著它的發展,XSL的樣式表現變得更接近于CSS和遠離DSSSL

================================================================================

Java與XML(二)用java編寫xml的讀寫程序

這是讀取xml文件的java程序,我調試好的。采用的是dom方式讀取xml文件到Vector中。
package src;
import java.io.*;
import java.util.Vector;
import javax.xml.parsers.*;
import org.w3c.dom.*;
public class readxml {
 static Document document;
 private boolean validating;
 public readxml() {
 }
 public Vector toRead(String filename) {
  Vector title=new Vector();
  Vector content=new Vector();
  String myStr=new String();
  try {
   DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
   factory.setValidating(validating);
   DocumentBuilder builder = factory.newDocumentBuilder();
   document = builder.parse(new File(filename));
   document.getDocumentElement().normalize();
   Node node = document.getFirstChild();
   NodeList list = node.getChildNodes();
   for (int i = 0; i < list.getLength(); i++) {
    Node nodeitm = list.item(i);
    if (nodeitm.getNodeName().equals("Title")) {
     myStr=nodeitm.getFirstChild().getNodeValue();
     title.addElement(myStr);//getFirstChild()
    }
    if (nodeitm.getNodeName().equals("Content")) {
     myStr=nodeitm.getFirstChild().getNodeValue();
     content.addElement(myStr);
    }
   }
  } catch (Exception exp) {
   exp.printStackTrace();
   return null;
  }
  Vector all=new Vector();
  all.add(title);
  all.add(content);  
  return all;
 }

 public static void main(String[] args) {
  Vector A;
  readxml my = new readxml();
  A = my.toRead("f:\\tomcat5\\webapps\\myxml\\xmldata\\9.xml");
  for (int i = 0; i < A.size(); i++) {
   System.out.println(A.elementAt(i));
  }
 }
}
這是將xml寫入文件。其中,transformer.setOutputProperty(OutputKeys.ENCODING,"GB2312")關系到編碼問題,非常重要。
import org.w3c.dom.*;
import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
public class writexml {
 private Document document;
 private String filename;
 
 public writexml(String name) throws ParserConfigurationException{
  filename=name;
  DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance();
  DocumentBuilder builder=factory.newDocumentBuilder();
  document=builder.newDocument();
 }
 public void toWrite(String mytitle,String mycontent){
      Element root=document.createElement("WorkShop");
  document.appendChild(root);
  Element title=document.createElement("Title");
  title.appendChild(document.createTextNode(mytitle));
  root.appendChild(title);
  Element content=document.createElement("Content");
  content.appendChild(document.createTextNode(mycontent));
  root.appendChild(content);
  }
 public void toSave(){
  try{
   TransformerFactory tf=TransformerFactory.newInstance();
   Transformer transformer=tf.newTransformer();
   DOMSource source=new DOMSource(document);
   transformer.setOutputProperty(OutputKeys.ENCODING,"GB2312");
   transformer.setOutputProperty(OutputKeys.INDENT,"yes");
   PrintWriter pw=new PrintWriter(new FileOutputStream(filename));
   StreamResult result=new StreamResult(pw);
   transformer.transform(source,result);
  }
  catch(TransformerException mye){
   mye.printStackTrace();
  }
  catch(IOException exp){
   exp.printStackTrace();
  }
 }
 public static void main(String args[]){
  try{
  writexml myxml=new writexml("f:\\tomcat5\\webapps\\myxml\\xmldata\\9.xml");
  myxml.toWrite("中文題目","中文內容");
  myxml.toSave();
  System.out.print("Your writing is successful.");
  }
  catch(ParserConfigurationException exp){
   exp.printStackTrace();
   System.out.print("Your writing is failed.");
  }  
 }
}

=================================================================================

利用(二)中我寫的兩個函數(放在package src中),這次實現web頁面的操作。
index.html:
<%@ page language="java" pageEncoding="GB2312"%>
<body>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<table width="60%" border="1" align="center">
  <tr>
    <td>
      <p align="left"><font size="4" color="#003399">管理測試程序</font></p>
     
      <form name="readform" method=post action="load.jsp">
        <p align="left"><font color="#FF0000">讀取xml文件: </font></p>
        <p align="left"> <font color="#FF0000">
          <input type="text" name="mypath" size="50" value="c:\\eclipse\\workspace\\myxml\\xmldata\\1.xml">
          </font></p>
        <p align="left">
        <a href="#" onClick=submit()>讀取</a>
        </p>
        <p align="left">&nbsp;</p>
      </form>
     
      <form name="writeform" method=post action="create.jsp">
        <p align="left"><font color="#FF0000">寫入xml文件:</font> </p>
        <p align="left">請填寫路徑:
          <input type="text" name="mypath" width="100" size="50"
          value="c:\\eclipse\\workspace\\myxml\\xmldata\\11.xml">
        </p>
        <p align="left">請填寫題目:
          <input type="text" name="mytitle" width="100" size="50">
        </p>
       
        <p align="left">請填寫內容:
          <textarea name="mycontent" cols="100"></textarea>
        </p>
        <p align="left">
        <a href="#" onClick=submit()>寫入</a>
        </p>
      </form>
</td>
  </tr>
</table>
</body>

寫入的頁面create.jsp
<%@ page language="java" pageEncoding="GB2312"%>
<%@ page import="src.*" %>
<%@ page import="org.w3c.dom.*"%>
<%@ page import="javax.xml.parsers.*"%>
<%@ page import="javax.xml.transform.*"%>
<%@ page import="javax.xml.transform.dom.DOMSource"%>
<%@ page import="javax.xml.transform.stream.StreamResult"%>
<%@ page import="java.io.*"%>
<html>
<body>
<%
String mypath=(String)request.getParameter("mypath");
String mytitle=(String)request.getParameter("mytitle");
String mycontent=(String)request.getParameter("mycontent");

mypath=new String(mypath.getBytes("ISO-8859-1"),"GB2312");
mytitle=new String(mytitle.getBytes("ISO-8859-1"),"GB2312");
mycontent=new String(mycontent.getBytes("ISO-8859-1"),"GB2312");
try{
writexml myxml=new writexml(mypath);
myxml.toWrite(mytitle,mycontent);
myxml.toSave();
out.print("Your writing is successful.");
}
catch(ParserConfigurationException exp){
 exp.printStackTrace();
 out.print("Your writing is failed.");
}
%>
</body>
</html>


讀取xml的頁面load.jsp:
<%@ page language="java" pageEncoding="GB2312"%>
<%@ page import="src.*" %>
<%@ page import="java.io.*" %>
<%@ page import="java.util.Vector" %>
<%@ page import="javax.xml.parsers.*" %>
<%@ page import="org.w3c.dom.*" %>
<html>
<body>
<%
Vector A=new Vector();
String mypath=(String)request.getParameter("mypath"); 
out.println(mypath);%>
<p>
<%
readxml my = new readxml();
A = my.toRead(mypath);
for (int i = 0; i < A.size(); i++) {
out.println(A.elementAt(i));
%>
<p>
<%
}
%>
</body>
</html>
這個寫程序還有一個缺陷,它只是創建xml格式和內容,而不是改寫已有文件。
如果您寫出了改寫文件的程序望能交流



2005-02-15 11:28 發表評論
]]>
主站蜘蛛池模板: 亚洲国产第一页www| 国产一区二区视频免费| 久久综合给合久久国产免费| 97国免费在线视频| 福利免费在线观看| 女同免费毛片在线播放| 二区久久国产乱子伦免费精品| 中文字幕久无码免费久久| 国产精品免费大片一区二区| 国产精品免费久久| 怡红院免费全部视频在线视频| 一级成人a毛片免费播放| 亚洲成人在线免费观看| 在线视频观看免费视频18| 91免费精品国自产拍在线不卡| 毛片免费在线观看网站| 日本a级片免费看| 亚洲人成网站18禁止一区| 亚洲精品午夜无码电影网| 久久亚洲私人国产精品vA| 亚洲一区二区三区亚瑟| 亚洲AV成人精品日韩一区| 深夜福利在线视频免费| 你懂得的在线观看免费视频| 四虎影视成人永久免费观看视频| 日本免费一区二区在线观看| 国产精品美女午夜爽爽爽免费| 国产一级特黄高清免费大片| 亚洲人成人网站在线观看| 亚洲AV成人无码久久精品老人| 亚洲妓女综合网99| 精品国产日韩亚洲一区91| 国产免费区在线观看十分钟| 国产免费一区二区三区在线观看| ww4545四虎永久免费地址| 国产国产人免费人成免费视频| 久久亚洲精品无码观看不卡| 久久精品国产亚洲av水果派| 亚洲日韩精品无码AV海量| 久久性生大片免费观看性| 日本妇人成熟免费中文字幕 |