定義
DOM是什麼?DOM的全稱是Document Object Model,即文檔對象模型。
可能前端同學對這個會比較熟悉;事實上,HTML中的DOM和XML中的DOM是同一個概念。我們來看看W3C對DOM的定義:
“The DOM” is an API for accessing and manipulating documents (in particular, HTML and XML documents)
也就是說,DOM是一個對文檔(尤其是指的是HTML和XML中的文檔)進行訪問和修改的API。
所以,顧名思義,DOM parser就是一個對DOM進行解析的工具,把XML加載到內存中,解析成樹型結構,也就是所謂的DOM樹。並且,樹的每一個結點都是對象,封裝了數據,並且提供了相應的操作方法。
爲什麼是樹
爲什麼是樹,而不是別的結構?因爲,文檔具有層次結構的特點,屬於那種類型相同(都是element),並且層數可變的數據。對於這種數據,使用樹能夠有效降低深度,提供比較高的檢索效率。當然,這種結構也有一個弱點,就是因爲類型相同,約束實際上很弱,只需要滿足很少的一些條件就可以被稱爲是“格式良好”的;因此,我們引入了詞彙表,包括DTD和Schema,來對語義做進一步的校驗,以確保XML是“合法”的。
格式良好的XML
格式良好其實是客觀需求,至少在當時是。因爲,小型設備往往缺乏解釋“糟糕”的標記語言的計算資源/計算能力,所以需要我們滿足一些規範,以減少解析的開銷。
格式良好的XML需要滿足以下幾個條件,也就是所謂的“5 + 1規則”。其中,5條定義語法結構,1條保證一致性:
-
單根元素
- 所有的XML文檔都只能有一個根元素
- 樹結構,而非森林結構
-
元素標籤規則
- 同時有開始標籤<>和結束標籤</>
- 沒有內容的元素可以簡寫</>
- 可以擁有鍵值對形式的屬性a=""
-
元素嵌套規則
- 可以任意嵌套任意多層的子元素
- 子元素關閉前不能提前關閉父元素
-
元素規則
命名
- 首字母必須是大小寫字母或者下劃線
- 後面可以接任意長度的字符、數字、連字符
- 大小寫敏感(與HTML不同,HTML大小寫不敏感)
- 不能有空格
- 不能以XML(以及XML的任意大小寫組合)開頭,這是保留字段
內容
-
XML文檔由使用標籤對錶示的元素、可選的屬性和可選的在開始標籤和結束標籤之間的數據構成
-
混合內容:數據 + 其他內容
-
PCDATA(被解析的字符數據):提取並檢查
- 需要對預定義實體進行轉義,如>
- 默認使用
-
CDATA(字符數據):使用<![CDATA[...]]>進行包裝,不處理
-
HTML、XML、JS代碼、需要大量轉義的文本,等等
-
內部的文本遵循源語言的轉義規則
類似於其他語言中的raw string,我沒有暗示Python的意思。
要不然叫template string吧。
-
-
元素屬性
- 給元素附加信息
- 鍵值對,其中key必須是合法的XML名稱
- 屬性必須有值,用""或’'包裹
- 可以有多個屬性
-
XML聲明(一致性)
- 可選的XML聲明,出現在第一行
- encoding指定編碼
- standalone表達完整性:是否依賴於文檔外的信息?
處理過程
DOM的解析過程分爲下面幾步:
- 需要使用DOM的應用程序創建DOM parser,parser一般是單例的。
- parser讀取XML文檔,以及相應的詞彙表(也就是校驗規則),比如DTD(已經過時),或者Schema。當然,如果不需要校驗,可以只讀取XML文檔。
- parser嚮應用程序報告解析過程中遇到的錯誤,包括但不限於:
- 格式不良好,也就是不滿足5 + 1規則;
- I/O異常;
- 解析器自身的異常;
- 如果有詞彙表,並且與詞彙表所定義的語義不符;
- parser生成DOM樹
- 應用程序對DOM樹進行CRUD操作;
- 應用程序輸出最終版本的XML。當然,如果應用程序只需要讀取XML,這一步就可以沒有。
DOM結構
理想情況下,DOM應該是很簡單的。但是,實際使用時,爲了可讀性(沒有誰會願意讀那種全部放在一行的XML吧),會插入很多換行符和空白符,這些換行符和空白符也會以text結點的形式出現在DOM樹中。同時,結點還會擁有屬性,這就讓整個DOM的模型複雜了很多。
不過,歸根結底,它們都是結點,都擁有共同的操作方法,這給我們的處理過程帶來了便利。
DOM結點
通用接口
在DOM中,所有的內容都是結點,包含這些屬性:
- 結點名稱
- 結點類型,比如element,text,attribute
- 與其他結點的關係,比如parent,siblings,children
包含一些通用的方法:
- 增加子結點
- 替換子結點
- 刪除子結點
結點類型
有四種主要的結點類型:
- Document
- Element
- Attr
- Text
以及一些不那麼常見的結點類型:
- DocumentFragment
- 這個類型比較有趣,因爲它可以不滿足之前所說的單根原則。
- 在HTML中,一般用以實現shadow DOM,注入一些HTML片段。並且,由於它的內容並不是原始DOM的一部分,不會觸發重新渲染。
- CDATASection
- 這個就是之前所說的CDATA的部分,用以實現“raw string”。
- Comment
- Processing Instruction
- Entity / EntityReference
Document結點
比較特殊的是,DOM會有一個document結點,包含全部的DOM元素。至於爲什麼,剛纔已經提到過了,格式良好的文檔應該滿足單根元素規則,也就是隻有一個根結點,並且是樹結構而不是森林結構。可以想象,如果沒有document結點,因爲還會有別的元素出現,整個DOM就會變成森林結構。
從某種程度上來說,document類似於所謂的“啞結點”。
空白符
剛纔提到,爲了可讀性,我們會往XML裏插入很多換行符和空白符,這些換行符和空白符也會以text結點的形式出現在DOM樹中。這個有時候會帶來一些讓人迷惑的現象,比如:
<book ibsn="123-765">
<author>Tom</author>
<title>Foo</title>
<price>$6</price>
</book>
請問book有幾個子節點?答案是7個,3個元素,4個空白符形成的text結點。
屬性結點
比較神祕的是,屬性不“屬於”DOM結構的內容。因爲,屬性結點沒有父結點,也沒有子結點。在HTML中,屬性是以NamedNodeMap的形式出現在父結點的屬性中的。
DOM應用
一個DOM應用,一般來說,會有四個步驟:
- 創建parser
- 處理parser的錯誤
- 遍歷document,不過這個說法聽起來性能很差,也許叫瀏覽會好一點
- 處理document
這些就是一些純粹的實現細節上的問題,就不再贅述了。
值得一提的是,在處理DOM parser的異常時,可能會有兩種處理方式,一種是原生的try-catch,另一種是SAX風格的錯誤處理,通過使用setErrorHandler
方法來指定錯誤處理的邏輯。如果大家還有印象的話,這個其實就是命令模式。它還有一個更通俗的叫法,“回調函數”。