dom是解析xml檔案的官方标準,它與平台和語言無關。dom解析将整個xml檔案載入并組裝成一棵dom節點樹,然後通過周遊、查找節點以讀取xml檔案中定義的資料。由于dom解析中把所有節點都載入到記憶體中,因而它比較耗資源,而且它需要把整棵節點樹建構完成後開始讀取資料,因而它相對性能也不好;不過由于它在記憶體中儲存了dom節點樹,因而它可以多次讀取,并且它的節點樹定義比較容易了解,因而操作起來比較簡單。關于性能,有人對一些常用的解析方法做了比較:
100kb
1mb
10mb
dom
0.146s
0.469s
5.876s
sax
0.110s
0.328s
3.547s
jdom
0.172s
0.756s
45.447s
dom4j
0.161s
0.422s
5.103s
stax stream
0.093s
0.334s
3.553s
stax event
0.131s
0.359s
3.641s
dom樹中,所有節點都是一個node,對不同節點有不同類型,w3c對不同類型的節點定義如下:
節點類型
描述
子元素
document
表示整個文檔(dom 樹的根節點)
element (max. one)
processinginstruction
comment
documenttype
documentfragment
表示輕量級的 document 對象,其中容納了一部分文檔。
text
cdatasection
entityreference
向為文檔定義的實體提供接口。
none
表示處理指令。
表示實體引用元素。
element
表示 element(元素)元素
attr
表示屬性。
表示元素或屬性中的文本内容。
表示文檔中的 cdata 區段(文本不會被解析器解析)
表示注釋。
entity
表示實體。
notation
表示在 dtd 中聲明的符号。
在java中dom節點之間的繼承關系如下:
在dom中,所有節點類型都繼承自node類,它代表在dom樹中的一個節點。node接口定義了一些用于處理子節點的方法,但是并不是所有的節點有子節點,比如text節點并沒有子節點,因而向text節點添加子節點(appendchild)會抛出domexception。
nodename、nodevalue、attributes屬性
node接口提供了getnodename()、getnodevalue()、getattributes()三個方法,以友善應用程式擷取這些資訊,而不需要每次都轉換成不同子類以擷取這些資訊。但是并不是所有的節點類型都有nodename、nodevalue、attributes資訊,因而對那些不存在這些資訊的節點可以傳回null。所有節點類型對這三個方法的傳回值如下表:
getnodename()
getnodevalue()
getattributes()
“#document”
null
“#document-fragment”
documenttype.name
實體引用名稱
element.tagname(qname)
namednodemap
屬性名(attr.name)
屬性值(attr.value)
processinginstruction.target
processinginstruction.data
“#comment”
注釋文本(characterdata.data)
“#text”
節點内容(characterdata.data)
“#cdata-section”
實體名稱
符号名稱
對nodevalue,node接口還提供了setnodevalue(string nodevalue)方法,對那些getnodevalue()為null的節點類型來說,調用該方法不會有任何影響,而對非null的nodevalue,如果它是隻讀的節點,調用該方法将會抛出domexception。隻有element節點類型才有屬性資訊,因而隻有element節點的getattributes()方法能傳回namednodemap,node接口還提供hasattributes()方法以判斷目前節點是否存在屬性資訊,也隻有element類型節點才會傳回true。
namednodemap接口實作了name=>node的一個map操作,對namednodemap執行個體操作會影響其所在的element節點中屬性的資訊:
public interface namednodemap {
public node getnameditem(string name);
public node setnameditem(node arg);
public node removenameditem(string name);
public node item(int index);
public int getlength();
public node getnameditemns(string namespaceuri, string localname);
public node setnameditemns(node arg);
public node removenameditemns(string namespaceuri, string localname);
}
textcontent屬性(set、get)
node接口還提供了textcontent屬性,它以字元串的形式表示目前節點和它的所有子節點。對設定該屬性(非空非null值),它會移除目前節點的所有子節點,并用一個text節點替代。讀取該屬性的值不會包含任何标簽字元,它也不會做任何解析,因而傳回的文本會包含所有空格、換行等符号資訊。對不同節點類型該屬性的内容如下:
textcontent
element、attr、entity、entityreference、documentfragment
将所有子節點的textcontent屬性連接配接在一起組成的字元串(不包含comment、processinginstruction節點),如果目前節點沒有子節點,該值為空
text、cdatasection、comment、processinginstruction
nodevalue
document、documenttype、notation
nodetype屬性
dom為每種節點類型定義了一個short值:
nodetype
named constant
node value
element_node
1
attribute_node
2
text_node
3
cdata_section_node
4
entity_reference_node
5
entity_node
6
processing_instruction_node
7
comment_node
8
document_node
9
document_type_node
10
document_fragment_node
11
notation_node
12
在節點樹中周遊、查找方法
getparentnode():傳回目前節點的父節點。attr、document、documentfragment、entity、notation這些類型的節點沒有父節點。其他類型都有可能有父節點。
getfirstchild():傳回目前節點的第一個子節點,如果沒有,傳回null
getlastchild():傳回目前節點的最後一個子節點,如果沒有,傳回null
getnextsibling():傳回目前節點的下一個兄弟節點,如果沒有,傳回null
getprevioussibling():傳回目前節點的上一個兄弟節點,如果沒有,傳回null
getownerdocument():傳回和目前節點關聯的document節點,一般對dom節點樹,document是其根節點,因而所有子節點可以通過該方法直接擷取根節點。對document、documenttype節點,該方法傳回null。
haschildnodes():判斷目前節點是否存在子節點。
修改子節點方法
appendchild(node newchild):向該節點添加子節點(所有已存在的子節點之後),如果該新的節點已經在節點樹中了,該節點會先被移除。如果新節點是documentfragment類型,則新節點内部所有的節點都會添加到子節點清單中。由于如果新添加的節點已存在在節點樹中,該節點會先被移除,因而新節點不可以是目前節點的祖先節點或該節點本身;對不同類型的節點,其子節點的類型也是固定的,因而不可以添加了目前節點不支援的子節點;另外,對document節點,它隻能存在一個element節點和一個documenttype節點。
removechild(node oldchild):移除目前節點中的oldchild子節點,并傳回該節點。
replacechild(node newchild, node oldchild):将oldchild子節點替換成newchild子節點,如果newchild節點類型是documentfragment,則所有documentfragment内部的節點都會插入到oldchild節點所在的位置,最後傳回oldchild子節點。如果oldchild節點已存在節點樹中,則該節點會先被移除。
insertbefore(node newchild, node refchild):向已存在的refchild子節點之前插入新子節點newchild。如果refchild為null,則該方法如appendchild(),即向子節點最後插入新子節點newchild。如果newchild節點為documentfragment,則插入的節點為documentfragment中的所有節點。如果newchild節點存在節點樹中,該節點會先被移除。
命名空間支援
dom從level2開始提供對命名空間的支援。在xml中,隻有element節點和attr節點存在命名空間的定義,而且屬性節點(attr)的命名空間并不預設繼承自element節點,而它需要自己顯示的定義所在的命名空間,否則預設沒有定義命名空間。
getnamespaceuri():擷取目前節點所在的命名空間,如果沒有定義傳回null。它是通過在目前作用域中查找到的值。出了element、attr,其他節點類型沒有命名空間定義。
getprefix()/setprefix(string prefix):命名空間字首屬性,對非element、attr的節點,他們永遠傳回null,對其設值不會有任何影響。
getlocalname():傳回目前節點的本地名稱,即不包含命名空間的名字。
getbaseuri():不認識
lookupprefix(string namespaceuri):通過namespaceuri查找命名空間字首(prefix),從目前節點開始查找,忽略預設命名空間uri。
lookupnamespaceuri(string prefix):通過prefix查找命名空間uri,從目前節點開始查找。若prefix為null,傳回預設命名空間uri。
isdefaultnamespace(string namespaceuri):判斷是否是預設的命名空間uri。
其他
issupported(string feature, string version):傳回dom實作是否支援給定的feature和version。
getfeature(string feature, string version):傳回實作該feature和version的對象,有點難了解的方法,參考其中一個簡單實作(nodeimpl):
public object getfeature(string feature, string version) {
return issupported(feature, version) ? this : null;
setuserdata(string key, object data, userdatahandler handler)/getuserdata(string key):向該node中添加使用者自定義的資料,應用程式可以在接下來的邏輯中從該node使用getuserdata(string key)方法重新擷取該資料。其中userdatahandler執行個體會在該node每次被複制(node.clonenode())、導入(document.importnode())、重命名(document.renamenode())、從其他document中引入(document.adoptnode())、删除(删除在java中的實作不可靠)時被調用:
public interface userdatahandler {
public static final short node_cloned = 1;
public static final short node_imported = 2;
public static final short node_deleted = 3;
public static final short node_renamed = 4;
public static final short node_adopted = 5;
public void handle(short operation, string key, object data, node src, node dst);
clonenode(boolean deep):拷貝目前node,但是不會拷貝userdata屬性和parentnode屬性。拷貝element節點是,如果deep為false,不會拷貝所有位元組點,但會拷貝所有屬性節點以及定義的具有預設值的屬性。而拷貝attr節點,不管deep為false還是true,都會拷貝attr的屬性值和其所有子節點。拷貝entityreference節點時,不管deep為false還是true,都會拷貝相應的entity。對所有其他節點類型的拷貝都是指傳回自身引用。
normalize():在程式設計建構dom樹時,可以建構出一棵不标準的dom樹,比如存在兩個相鄰的text節點,空節點之類的,normalize方法可以合并兩個相鄰的text節點、移除空節點等。
comparedocumentposition(node other):比較兩個節點的相對位置,傳回的short值隻是一些簡單的資訊,可以有如下值:
public static final short document_position_disconnected = 0x01;
public static final short document_position_preceding = 0x02;
public static final short document_position_following = 0x04;
public static final short document_position_contains = 0x08;
public static final short document_position_contained_by = 0x10;
public static final short document_position_implementation_specific = 0x20;
issamenode(node other):判斷兩個節點是否相同,比較引用,包括代理引用。
isequalnode(node other):判斷兩個節點是否相同,比較内容。normalize操作會影響該方法的結果,因而一般先調用normalize方法後,比較。parentnode、owneddocument、userdata等屬性不會影響該方法的比較,具體參考api文檔。
document是dom樹的根節點,由于其他節點類型都要基于document而存在,因而document接口還提供了建立其他節點的工廠方法。
document級别操作
document子節點隻能包含一個documenttype和element節點,因而document提供了兩個方法直接傳回這兩個節點,而對其他子節點(processinginstruction和comment)則需要通過node接口提供的操作讀取:
public documenttype getdoctype();
public element getdocumentelement();
public domimplementation getimplementation();
對domimplementation接口,它提供了一些和dom節點無關的操作:
public interface domimplementation {
public boolean hasfeature(string feature, string version);
public documenttype createdocumenttype(string qualifiedname,
string publicid, string systemid);
public document createdocument(string namespaceuri, string qualifiedname,
documenttype doctype);
public object getfeature(string feature, string version);
每個xml檔案都可以指定編碼類型、standalone屬性、xml版本、是否執行嚴格的文法檢查、document的位置:
public string getinputencoding();
public string getxmlencoding();
public boolean getxmlstandalone();
public void setxmlstandalone(boolean xmlstandalone);
public string getxmlversion();
public void setxmlversion(string xmlversion);
public boolean getstricterrorchecking();
public void setstricterrorchecking(boolean stricterrorchecking);
public string getdocumenturi();
public void setdocumenturi(string documenturi);
工廠方法
document提供了建立其他所有節點類型的工廠方法,并且支援帶命名空間的element、attr的建立:
public element createelement(string tagname);
public element createelementns(string namespaceuri, string qualifiedname);
public attr createattribute(string name);
public attr createattributens(string namespaceuri, string qualifiedname);
public documentfragment createdocumentfragment();
public text createtextnode(string data);
public comment createcomment(string data);
public cdatasection createcdatasection(string data);
public processinginstruction createprocessinginstruction(string target, string data);
public entityreference createentityreference(string name);
查找element節點
1. 通過id屬性查找:public element getelementbyid(string elementid);
2. 使用标簽名查找:public nodelist getelementsbytagname(string tagname);
3. 使用命名空間uri和本地标簽名:
public nodelist getelementsbytagnamens(string namespaceuri, string localname);
其中nodelist接口提供了周遊node的操作:
public interface nodelist {
配置與規格化
如node.normalize(),document也定義自己的normalizedocument(),它根據目前的配置規格化dom樹(替換entityreference成entity,合并相鄰的text節點等),如果配置驗證資訊,在操作同時還會驗證dom樹的合法性。document可以通過domconfiguration接口執行個體配置:
public domconfiguration getdomconfig();
public interface domconfiguration {
public void setparameter(string name, object value);
public object getparameter(string name);
public boolean cansetparameter(string name, object value);
public domstringlist getparameternames();
如設定validate屬性:
domconfiguration docconfig = mydocument.getdomconfig();
docconfig.setparameter("validate", boolean.true);
其中domstringlist提供了周遊string list的操作,類似nodelist:
public interface domstringlist {
public string item(int index);
public boolean contains(string str);
public node importnode(node importednode, boolean deep);
向目前document中導入存在于另一個document中的節點而不改變另一個document中dom樹的結構。
public node adoptnode(node source);
向目前document中加入存在于另一個document中的節點,并将該節點從另一個document中移除。
public node renamenode(node n, string namespaceuri, string qualifiedname);
重命名一個element或attr節點
element節點表示xml檔案中的一個标簽,因而它最常用。由于在所有節點類型中,隻有element節點可以包含書,因而所有和屬性具體相關的操作都定義在element接口中:
public string getattribute(string name);
public void setattribute(string name, string value);
public void removeattribute(string name);
public attr getattributenode(string name);
public attr setattributenode(attr newattr)
public attr removeattributenode(attr oldattr);
public string getattributens(string namespaceuri, string localname);
public void setattributens(string namespaceuri, string qualifiedname, string value);
public void removeattributens(string namespaceuri, string localname);
public attr getattributenodens(string namespaceuri, string localname);
public attr setattributenodens(attr newattr);
public boolean hasattribute(string name);
public boolean hasattributens(string namespaceuri, string localname);
public void setidattribute(string name, boolean isid);
public void setidattributens(string namespaceuri, string localname, boolean isid);
public void setidattributenode(attr idattr, boolean isid);
element還提供了兩個使用标簽名查找子element方法以及傳回目前element的标簽名方法,該标簽名為包含命名空間字首的全名:
public nodelist getelementsbytagname(string name);
public string gettagname();
attr節點表示element節點中的一個屬性,一般一個element中存在什麼樣的屬性會在相應的schema或dtd中定義。雖然attr繼承自node接口,但是它并不屬于element節點的子節點,因而它不屬于dom樹中的節點,它隻存在于element節點中,是以它的parentnode、previoussibling、nextsibling的值都為null。然而attr可以存在子節點,它的子節點可以是text節點或entityreference節點。
對在schema中有預設值定義的attr,移除和初始化時會新建立一個attr,它的specified屬性為false,值為schema中定義的預設值。在調用document.normalizedocument()方法時,所有specified屬性為false的attr值都會重新計算,如果它在schema中沒有預設值定義,則該attr會被移除。
屬性是具有name和value的值對,其中建立一個attr執行個體後,name不可以改變,要改變則需要建立新的attr執行個體,而value值可以改變。specified屬性表明該屬性的值是使用者設定的(true)還是schema中預設提供的。id屬性表明該attr是不是其所屬element的id屬性,一個id屬性的值可以在一個document中唯一的辨別一個element。由于所有attr都是基于element的,因而可以擷取其所屬的element。
public interface attr extends node {
public string getname();
public boolean getspecified();
public string getvalue();
public void setvalue(string value);
public element getownerelement();
public typeinfo getschematypeinfo();
public boolean isid();
每個document都有doctype屬性,它包含了dtd檔案資訊(位置、檔案名等),同時它還提供了讀取dtd檔案中定義的entity、notation的集合,即它是對xml檔案中以下語句的封裝:
<!doctype beans public "-//spring//dtd bean//en" "http://www.springframework.org/dtd/spring-beans.dtd">
其中:
name為beans
publicid為-//spring//dtd bean//en
systemid為http://www.springframework.org/dtd/spring-beans.dtd
entities、notations為該dtd檔案中定義的entity和notation的集合
public interface documenttype extends node {
public namednodemap getentities();
public namednodemap getnotations();
public string getpublicid();
public string getsystemid();
public string getinternalsubset();
在xml檔案中還可以定義一些指令以供一些解析器使用該資訊對該檔案做正确的處理,在dom中使用processinginstruction接口對該定義進行抽象:
<?xml-stylesheet href="show.css" type="text/css" ?>
processinginstruction額外定義了兩個屬性:target和data:
target為xml-stylesheet
data為href="show.css" type="text/css"
public interface processinginstruction extends node {
public string gettarget();
public string getdata();
public void setdata(string data);
characterdata接口繼承自node接口,它是所有字元相關節點的父接口,定義了所有字元相關的操作:定義屬性、添加、插入、删除、替換、取子串等。
public interface characterdata extends node {
public string substringdata(int offset, int count);
public void appenddata(string arg);
public void insertdata(int offset, string arg);
public void deletedata(int offset, int count);
public void replacedata(int offset, int count, string arg);
text接口繼承自characterdata接口,它表示文本節點,一般作為element、attr的子節點,而它本身沒有子節點。text定義了一個文本節點,如element的文本content或attr的值。若文本裡面包含特殊字元(如’<’, ‘>’等)需要轉義。在操作dom樹時,使用者可以插入多個text節點,在node.normalize()處理時會合并兩個各相鄰的text節點。
text節點提供了除對字元資料操作的其他額外操作:
splittext():text節點分割成兩個相鄰的text節點,即新分割出的text節點為之前text節點的兄弟節點
iselementcontentwhitespace():判斷目前text節點是否存在element content whitespace,沒讀懂。
getwholetext():當存在多個相鄰的text節點時,該屬性會傳回所有相鄰text節點的值。
replacewholetext():替換所有相鄰text節點為新設定的節點(可能是目前節點本身)。如果其中有一個節點無法移除(如包含entityreference的節點),則會抛出domexception。
public interface text extends characterdata {
public text splittext(int offset);
public boolean iselementcontentwhitespace();
public string getwholetext();
public text replacewholetext(string content);
cdatasection接口繼承自text接口,它類似于text節點,所不同的是所有的cdatasection節點都包含在<![cdata[“content need not to be escaped”]]>中,并且如果其内容包含特殊字元不需要轉義。不同于text節點,在node.normalize()階段,相鄰的兩個cdatasection節點不會被合并。
public interface cdatasection extends text {
comment接口繼承自characterdata接口,它是對xml檔案中注釋語句的抽象,它隻包含注釋的字元串資訊,沒有額外定義的行為:
public interface comment extends characterdata {
entity接口是對一個實體定義的抽象,即它是對dtd檔案中以下定義的抽象:
<!entity jenn system "http://images.about.com/sites/guidepics/html.gif" ndata gif>
entity接口定義了systemid、publicid、notationname等資訊,而對其他資訊則在其子節點中顯示,如entity可能指向另一個外部檔案,或者直接定義entity的值(對這個,貌似我始終傳回null,按網上的說法,這個是xerces的bug),如以下定義:
<!entity name "cnblog">
<!entity copyright system "copyright.desc">
另外entity還定義了一些編碼和xml版本的資訊:
public interface entity extends node {
public string getnotationname();
public string getinputencoding();
public string getxmlencoding();
public string getxmlversion();
entityreference節點表示對一個entity的引用。
public interface entityreference extends node {
notation接口是對dtd中notation定義的抽象:
<!notation gif system "image/gif">
一個notation包含name(nodename)、systemid、publicid資訊:
public interface notation extends node {
documentfragment是對dom樹片段的抽象,進而可以将部分dom樹節點作為一個集合來處理,如插入到一個document中的某個節點中,實際插入的是documentfragment中所有的子節點。把dom樹的部分節點作為一個整體來看待部分可以通過document來實作,然而在部分實作中,document是一個重量級的對象,而documentfragment則可以保證它是一個輕量級的對象,因為它沒有document存在的那麼限制,這也是documentfragment存在的原因。documentfragment的定義如下:
public interface documentfragment extends node {