[iOS] Constructing XML Tree Structures

此份文件為個人學習事件驅動XML程式的翻譯,內文請參考蘋果官方文件,若有翻譯錯誤請見諒,請勿挪作商業用途,並著明出處。



一般而言,如果你想要加入或修改XML文件的內容,你必須建構一個靜態的樹狀結構, 此結構會完整的表示文件內的元素和其它的建構。若你要驗證一個XML文件是否符合描述文件邏輯結構的DTD(或其他語言schema),那麼樹狀表示就是必要的。當大多數的開發者想要去建構XML文件的DOM-style樹狀表示,他們會使用一個樹形parser,而不是串流parser,像是NSXMLParser實例(然而,樹形解析引擎通常是建立在串流parser的頂端)。然而,這不表示你不能用NSXMLParser實力去創建樹狀結構。雖然這篇文章不會非常詳細介紹使用NSXMLParser去建構XML樹狀結構,但會介紹主要的方法。

你可以以階層樹狀來表示任何的XML文件,樹狀中的“節點”就是元素,可以表示它和其它元素之間父與子的關係。每一個元素可以有一個或多個子元素並且有一個明確的父元素,當然根元素是沒有父元素的,所以根元素不包括在內。樹狀是由根元素來定錨的,根元素是樹狀中唯一沒有父元素的。樹的“葉子”節點是典型的只有包含文本的元素,這些元素可以是混合或是空元素。


舉例來說,來看看以下簡短的XML文件:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE addresses SYSTEM "addresses.dtd">
<addresses>
    <person idnum="0123">
        <lastName>Doe</lastName>
        <firstName>John</firstName>
        <phone location="mobile">(201) 345-6789</phone>
        <email>jdoe@foo.com</email>
        <address>
            <street>100 Main Street</street>
            <city>Somewhere</city>
            <state>New Jersey</state>
            <zip>07670</zip>
        </address>
    </person>
</addresses>

以下元素節點的樹表示了這份文件:
Figure 1  Tree representation of simple XML document
Tree representation of simple XML document

有很多方法可以使用NSXMLParser來建構XML為建的樹形表示。然而這篇文章是使用一種遞歸(recursive)、物件導向形式的做法,這種做法可以動態的在表示文件的元素的物件之間轉換委任的責任(這種轉移NSXMLParser委任的策略在Using Multiple Delegate一文有更深入的討論)。 The programmatic result is doubly-linked lists of objects and arrays of objects; the abstract result is a tree representation of the document.

這種方法來建構樹形的過程需要以下的步驟:
  1. 創建一個類別,它的實例需要表示XML文件裡面的元素。這個類需要定義元素的名稱和它的父(一對一)與子關係,它也應該要封裝和元素相關的屬性。為了快速表示這個步驟,我們把這個類別取名為MyElement。
  2. 從應用程式中的頂層物件,載入一份XML文件,給它創建一個NSParser實例,將頂層物件設為委任,並且開始解析文件(請參考XML Parsing Basics)。
  3. parser會先遇到文件的根元素,並且送parser:didStartElement:namespaceURI:qualifiedName:attributes:方法給它的委任。委任會創建一個MyElement物件來表示這個根元素,並且把它的父元素設為nil。創建和初始化物件的這個方法也會把它設成NSXMLParser實例的新的委任。
  4. parser接著遇到文件中的下一個元素,這是根元素的第一個子元素,並且再次送parser:didStartElement:namespaceURI:qualifiedName:attributes:方法到委任去。現在委任就是才剛創建用來表示根元素的MyElement物件。它會創建另一個MyElement物件用來表示新的元素(在這過程中會把新物件設定成委任,以及把它自己設定為父級別(我認為這是指父子之間的層級關係,翻得有點頭大))並且將這新的物件加到它的子元素列表中。
  5. The new delegate receives the next parser:didStartElement:namespaceURI:qualifiedName:attributes: message, identifying its first child element, and it creates it and adds it to its list of children.
    新的委任會接收到另一個parser:didStartElement:namespaceURI:qualifiedName:attributes:訊息,會識別它的第一個子元素,並且它會創建一個新物件再把它加入到它的子元素列表中。
  6. 這個遞歸下降(resursive descent)會經過樹狀的第一枝幹,當parser遇到包含文本、混合內容或是空元素的"葉子"元素時就會結束。如果有混合內容,下降並不會真的結束,因為parser:didStartElement:namespaceURI:qualifiedName:attributes:方法會送到委任去,就算委任接收到目前元素的parser:foundCharacters:方法。處理過程和元素種類有關:

    • 若他是一個空元素,處理過程會往前跳過到下一個步驟(結束元素的標籤)。
    • 若只有和目前元素節點有關的文本,委任會累積文本來響應parser:foundCharacters:訊息(接著喚起parser:foundCharacters:)。
    • 若有混合內容,即使委任接收通知它內嵌元素的開始元素(start-element)和結束元素(end-element)的標籤的通知訊息,委任也會處理文本。一種處理作法是將文本包(wrap)在一個特殊的文本元素物件並把這些物件(以適當的次序)插入到元素的子列表。
  7. 最後,parser送出parser:didEndElement:namespaceURI:qualifiedName:方法到委任,通知它元素已經完成(解析?)。委任會設定新的委任為它的父層別並且回傳。
  8. 若父層級有許多子元素,parser會再傳一個parser:didStartElement:namespaceURI:qualifiedName:attributes:訊息,父MyElement物件會創建一個MyElement實例作為它的下一個子層級(過程中會設定它為新的委任,並且設定它作為新的MyElement的父層級),並且把新創建的物件加到它的子列表中。然而,若父層級沒有其他的子層級可加到它的列表(亦即,它接收的訊息是parser:didEndElement:namespaceURI:qualifiedName:),那他會設定新的委任為它的父層級並且回傳。
  9. 此程序會以這樣的方法繼續直到整個 XML文件都被處理,並且所有的樹形支幹都被創建。

那些作為樹形的節點的物件(代表著大部分的元素)應該都能夠被印成XML程式碼。你的應用程式也應可以時作演算法要求物件依照適當的文件次序打印他們本身。

留言