[iOS] Handling XML Elements and Attributes

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

一般而言,當你解析XML文件時,大部分的處理過程包含了元素和與元素有關的東西,像是屬性和文本(textual)內容。元素掌握了XML文件中大部分的資訊。當NSXMLParser物件通過(traverse)XML文件裡面的一個元素時,它至少會依照下面所述的次序送出三個不同的訊息到它的委任:

  • parser:didStartElement:namespaceURI:qualifiedName:attributes:  
  • parser:foundCharacters:  
  • parser:didEndElement:namespaceURI:qualifiedName:
Parser可能會對一個元素多次送出parser:foundCharacters:訊息,然而,若字符(character)只有空白字符(space, new line, tab和類似的字符)沒有其他東西,parser會送出parser:foundIgnorableWhitespace(而不是parser:foundCharacters:)。

當你解析XML元素時,有一個高等技術,就是你可以在多個委任中切換處理則任,每一個委任都知道怎樣處理元素的一個特定種類。可以參考Using Multiple Delegates。

Design Considerations 

  在物件導向環境,像是Cocoa,一個常見的處理元素的策略是去映照它們—至少在更高的巢狀層級—到物件。根元素和其他頂級(top level)元素常等同於在Cocoa裡面由NSDictionary和NSArray物件表示的收集。其他的元素可能會立即映照到一或多個應用程式的客製模型物件(application’s custom modal object)。

然而,不是所有的元素都最好要表示成物件。有些低階和特別的(leaf)元素更邏輯的被視作它們的父元素的特性(若那元素映照到一個物件)。當然,你可能製作任何元素的實際屬性之對應的物件的一個特性(property)(亦即,一個實例變數)。

儘管有這些建議,但並沒有現成的映照公式,事實上你的應用程式可能不需執行任何元素到物件之間的映照。這些設計決定需要一些想法和對於XML結構的熟悉。

Handling an Element: An Example 

 下面討論中提到的範例程式碼處理一個包含人和地址資訊的XML檔案並且轉換這資訊到通訊錄物件(Address Book object)( ABPerson和ABMultipleValue),這些物件可以加到指定的使用者地址資料庫(address database)。XML的一部分就像下面這樣:

表一:XML的範例

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE addresses SYSTEM "addresses.dtd">
<addresses owner="swilson">
    <person>
        <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>
 
    <!-- more person elements go here -->
 
</addresses>


來看看前三個元素是怎麼處理的吧。當parser首先遇到這些元素,它呼喚委任的parser:didStartElement:namespaceURI:qualifiedName:attributes:方法。對前兩個元素,委任創建一個相等的物件,對第三個元素(lastName),委任設定第二個物件的一個適當的特性。表二顯示了對於前三個元素開始標籤的委任實作。

表二:實作parser:didStartElement:namespaceURI:qualifiedName:attribute:

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
 
    if ( [elementName isEqualToString:@"addresses"]) {
        // addresses is an NSMutableArray instance variable
       if (!addresses)
             addresses = [[NSMutableArray alloc] init];
        return;
    }
 
    if ( [elementName isEqualToString:@"person"] ) {
        // currentPerson is an ABPerson instance variable
        currentPerson = [[ABPerson alloc] init];
        return;
    }
 
    if ( [elementName isEqualToString:@"lastName"] ) {
        [self setCurrentProperty:kABLastNameProperty];
        return;
    }
    // .... continued for remaining elements ....
}


委任識別在(elementName)中傳遞的元素,然後根據以下原則處理它:

  • 若它是一個地址元素(addresses element)(根元素, root element),它創建可變更的陣列去持有ABPerson物件,這個可變陣列做為實例變數來持有。  
  • 若它是一個個人元素(person element),它創建一個ABPerson物件,這物件以名為currentPerson的實例變數來持有。  
  • 若是lastName元素,它設定一個實例變數來持有目前的通訊錄特性(Address Book property),這個值是一個宣告在Address Book framework 的enum常數。      
 這邊一個重要的動作是有一個方法(在這情況是,實例變數)去追蹤目前的元素在parser遍歷(parser’s traversal)它之後。一個重要的理由是parser:foundCharacters:的語義,很可能在下一個委任方法中呼喚。對同一個元素,這個方法可以被呼喚好幾次。在這個方法,委任應該要附加上元素目前累積的傳入(pass in)的字符(characters)。為此目的,NSMutableString的appendString:方法很有用,參考表三。

表三:實作parser:foundCharacters:

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
    if (!currentStringValue) {
        // currentStringValue is an NSMutableString instance variable
        currentStringValue = [[NSMutableString alloc] initWithCapacity:50];
    }
    [currentStringValue appendString:string];
}


程式碼使用了實例變數(currentStringValue)來追蹤並聚集(gather)目前元素的內容。若parser在元素內容中遇到一些空白字符,它會傳送訊息parser:foundIgnorableWhitespace:給委任,讓委任有機會去維持(retain)任何空白字符(像是tab或是換行)。

最後,當parser遇到元素的結尾標籤,它會喚起委任方法parser:didEndElement:namespaceURI:qualifiedName:。表示表示了範例程式碼中的委任採取的方法。

表四:實作parser:didEndElement:namespaceURI:qualifiedName:

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
    // ignore root and empty elements
    if (( [elementName isEqualToString:@"addresses"]) ||
        ( [elementName isEqualToString:@"address"] )) return;
 
    if ( [elementName isEqualToString:@"person"] ) {
        // addresses and currentPerson are instance variables
        [addresses addObject:currentPerson];
        [currentPerson release];
        return;
    }
    NSString *prop = [self currentProperty];
 
    // ... here ABMultiValue objects are dealt with ...
 
    if (( [prop isEqualToString:kABLastNameProperty] ) ||
        ( [prop isEqualToString:kABFirstNameProperty] )) {
        [currentPerson setValue:(id)currentStringValue forProperty:prop];
    }
    // currentStringValue is an instance variable
    [currentStringValue release];
    currentStringValue = nil;
}


若委任決定結尾標籤是person元素的,它加入ABPerson物件到addresses陣列並且釋放ABPerson物件。若結尾物件是lastName元素的(舉例來說),委任使用ABRecord的setValue:forProperty:方法去設定ABPerson物件中適當的特性(ABPerson是ABPerson的超類)。最後,持有累積的元素內容的實例變數(currentStringValue)會被釋放。

Handling an Attribute     

 在表一中的XML範例的addresses元素包含了以下屬性:

<addresses owner="swilson">


在這假設的情況中,屬性允許解析XML的應用程式儲存創建的Address Book information在指定的多重使用者系統內的使用者目錄(directory)。NSXMLParser物件表示到委任的一個元素的屬性parser:didStartElement:namespaceURI:qualifiedName:attributes: 方法內最後一個引數內的字典(亂翻一通) 

表五顯示範例中的委任如何處理owner屬性。

表五:處理元素的屬性

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
 
    if ( [elementName isEqualToString:@"addresses"]) {
        // addresses is an NSMutableArray instance variable
        if (!addresses)
            addresses = [[NSMutableArray alloc] init];
        NSString *thisOwner = [attributeDict objectForKey:@"owner"];
        if (thisOwner)
            [self setOwner:thisOwner forAddresses:addresses];
        return;
    // ... continued ...
}}

委任使用屬性名稱(owner)做為鍵,從attributeDict字典擷取擁有者使用者名稱。然後它喚起一個聯繫owner和imported Address Book data的隱式方法 

留言