簡單談談XML DOM parser

定義

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的解析過程分爲下面幾步:

  1. 需要使用DOM的應用程序創建DOM parser,parser一般是單例的。
  2. parser讀取XML文檔,以及相應的詞彙表(也就是校驗規則),比如DTD(已經過時),或者Schema。當然,如果不需要校驗,可以只讀取XML文檔。
  3. parser嚮應用程序報告解析過程中遇到的錯誤,包括但不限於:
    1. 格式不良好,也就是不滿足5 + 1規則;
    2. I/O異常;
    3. 解析器自身的異常;
    4. 如果有詞彙表,並且與詞彙表所定義的語義不符;
  4. parser生成DOM樹
  5. 應用程序對DOM樹進行CRUD操作;
  6. 應用程序輸出最終版本的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方法來指定錯誤處理的邏輯。如果大家還有印象的話,這個其實就是命令模式。它還有一個更通俗的叫法,“回調函數”。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章