使用NSXMLParser解析XML數據

使用NSXMLParser解析XML數據

對xml進行解析的標準有兩種:sax以及dom。

首先這兩種標準並不是針對java的,他們在各種語言環境下都可以實現。dom是真正的國際標準。sax是事實的標準,它不由任何商業組織維 護,而是由一個非商業的組織在運作。就像iso7層模型和tcp/ip一樣,雖然sax不是正式的標準,但是一點不影響其在xml解析領域的地位。

 

dom實現的原理是把整個xml文檔一次性讀出,放在一個樹型結構裏。在需要的時候,查找特定節點,然後對節點進行讀或寫。它的主要優勢是實現簡單,讀寫平衡;缺點是比較佔內存,因爲他要把整個xml文檔都讀入內存,文件越大,這種缺點就越明顯。

 

sax的實現方法和dom不同。

SAX解析XML,是基於事件通知的模式,一邊讀取XML文檔一邊處理,不必等整個文檔加載完之後才採取操作,當在讀取解析過程中遇到需要處理的對象,會發出通知對其進行處理。由於該方法 只在xml文檔中查找特定條件的內容,並且只提取需要的內容。這樣做佔用內存小,靈活,正好滿足我們的需求。

 

在iOS中,可以通過NSXMLParser實現sax方法解析xml文件。

對於NSXMLParser,常用的初始化方法有兩種:通過XML的URL初始化和通過本地的XML文件初始化。

1.使用NSXMLParser解析XML網址數據

首先給出一個XML資源網址: http://rss.sina.com.cn/tech/index.shtml ,也就是新浪新聞頻道列表,可以用來練習解析XML數據。

使用NSXMLParser解析XML數據的關鍵是實現NSXMLParserDelegate中的方法:

#pragma mark -
#pragma mark NSXMLParserDelegate

/* 開始解析xml文件,在開始解析xml節點前,通過該方法可以做一些初始化工作 */
- (void)parserDidStartDocument:(NSXMLParser *)parser
{
    NSLog(@"開始解析xml文件");
}

/* 當解析器對象遇到xml的開始標記時,調用這個方法開始解析該節點 */
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
attributes:(NSDictionary *)attributeDict
{
    NSLog(@"發現節點");
}

/* 當解析器找到開始標記和結束標記之間的字符時,調用這個方法解析當前節點的所有字符 */
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
    NSLog(@"正在解析節點內容");
}

/* 當解析器對象遇到xml的結束標記時,調用這個方法完成解析該節點 */
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
    NSLog(@"解析節點結束");
}

/* 解析xml出錯的處理方法 */
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError
{
    NSLog(@"解析xml出錯:%@", parseError);
}

/* 解析xml文件結束 */
- (void)parserDidEndDocument:(NSXMLParser *)parser
{
    NSLog(@"解析xml文件結束");
}

在這裏我選擇了 view-source:http://rss.sina.com.cn/news/allnews/tech.xml 進行解析。

其中一項內容是:

<item>
      <title>
        <![CDATA[蘋果設計主管艾維:設計最重要的是全心投入]]>
      </title>
      <link>http://go.rss.sina.com.cn/redirect.php?url=http://tech.sina.com.cn/it/2013-10-11/14588806471.shtml</link>
      <author>SINA.com</author>
      <guid>http://go.rss.sina.com.cn/redirect.php?url=http://tech.sina.com.cn/it/2013-10-11/14588806471.shtml</guid>
      <category>
        <![CDATA[科技新聞]]>
      </category>
      <pubDate>Fri, 11 Oct 2013 06:58:40 GMT</pubDate>
      <comments></comments>
      <description>
        <![CDATA[  新浪科技訊 北京時間10月11日下午消息,蘋果首席設計師喬尼・艾維(Jony Ive)接受媒體採訪時表示,設計產品的過程中最重要的是要真心重視這項工作,付出自己最大的努力。而且要加強與各種材料的親身接觸,不能過度依賴電腦建模。

  “在對待人們不會立刻發現的事情時,我....]]>
      </description>
    </item>


在這裏要解析的是新聞的標題title,摘要description,發佈時間pubDate。

首先聲明三個數組來存儲這些新聞內容:

static NSString *xmlURLString = @"http://rss.sina.com.cn/news/allnews/tech.xml"; // 要解析的XML網址

@interface ViewController () <NSXMLParserDelegate>
@property (nonatomic, strong) NSMutableArray *newsTitles;      // 標題
@property (nonatomic, strong) NSMutableArray *newsDescription; // 摘要
@property (nonatomic, strong) NSMutableArray *newsPublicDates; // 發佈時間
@property (nonatomic, strong) NSMutableString *tempString;     // 用於臨時保存解析的字符數據
@property (nonatomic, strong) NSXMLParser *xmlParser; // XML解析器
@end

看看程序的界面:


有一個spinner_view用來指示正在解析xml數據,parse按鈕按下後開始解析xml數據,showxml_textView用於顯示解析後的新聞內容。

在按下parse按鈕後,對解析器進行初始化並啓動解析:

- (IBAction)parseXML:(id)sender {
    xmlParser = [[NSXMLParser alloc] initWithContentsOfURL:[NSURL URLWithString:xmlURLString]];
     xmlParser.delegate = self;
    [xmlParser parse];
}
必須要設置解析器的委託爲自己。 

在parse方法調用後,將觸發NSXMLParser中的方法,開始進行XML解析工作:

#pragma mark -
#pragma mark NSXMLParserDelegate

/* 開始解析xml文件,在開始解析xml節點前,通過該方法可以做一些初始化工作 */
- (void)parserDidStartDocument:(NSXMLParser *)parser
{
    [spinner_view startAnimating];
    newsTitles = nil;
    newsDescription = nil;
    newsPublicDates = nil;
    tempString = nil;
    showxml_textView.text = @"";
    NSLog(@"開始解析xml文件");
}

/* 當解析器對象遇到xml的開始標記時,調用這個方法開始解析該節點 */
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
   attributes:(NSDictionary *)attributeDict
{
    
    NSLog(@"發現節點");
    if([elementName isEqualToString:@"title"])
    {
        if(newsTitles == nil)
            newsTitles = [[NSMutableArray alloc] init];
    }
    else if([elementName isEqualToString:@"description"])
    {
        if(newsDescription == nil)
            newsDescription = [[NSMutableArray alloc] init];
    }
    else if([elementName isEqualToString:@"pubDate"])
    {
        if(newsPublicDates == nil)
            newsPublicDates = [[NSMutableArray alloc] init];
    }
    else {
        
    }
}

/* 當解析器找到開始標記和結束標記之間的字符時,調用這個方法解析當前節點的所有字符 */
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
    NSLog(@"正在解析節點內容");
    if(self.tempString == nil)
        self.tempString = [[NSMutableString alloc] init];
    [self.tempString appendString:string];
}

/* 當解析器對象遇到xml的結束標記時,調用這個方法完成解析該節點 */
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
    NSLog(@"解析節點結束");
    if([elementName isEqualToString:@"title"])
    {
        [newsTitles addObject:self.tempString];
    }
    else if([elementName isEqualToString:@"description"])
    {
        [newsDescription addObject:self.tempString];
    }
    else if([elementName isEqualToString:@"pubDate"])
    {
        [newsPublicDates addObject:self.tempString];
    }
    else {
        
    }
    self.tempString = nil;
}

/* 解析xml出錯的處理方法 */
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError {
    NSLog(@"解析xml出錯:%@", parseError);
}

/* 解析xml文件結束 */
- (void)parserDidEndDocument:(NSXMLParser *)parser
{
    if (!tempString) {
        tempString = [[NSMutableString alloc] init];
    }
    for (int i = 2; i < 10; i++) {
        [tempString appendString:newsTitles[i + 1]];
        [tempString appendString:newsDescription[i]];
        [tempString appendString:newsPublicDates[i]];
        [tempString appendString:@"\n------------------------------------------------"];
    }
    
    showxml_textView.text = tempString;
    NSLog(@"解析xml文件結束");
    [spinner_view stopAnimating];
}

簡單說一說該解析過程:

(1)在parse方法調用後,受委託的類首先調用委託中的
/* 開始解析xml文件,在開始解析xml節點前,通過該方法可以做一些初始化工作 */
- (void)parserDidStartDocument:(NSXMLParser *)parser
方法進行一些初始化工作,比如清空保存新聞內容的數組,對界面內容操作等。

(2)在解析過程中,如果遇到xml開始標記,表明已經遇到了一個xml節點,此時將調用委託中的

/* 當解析器對象遇到xml的開始標記時,調用這個方法開始解析該節點 */
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
   attributes:(NSDictionary *)attributeDict
方法開始解析這個節點。

這個時候,應該對存儲節點的數組進行初始化工作。 
(3)在解析節點時,將調用委託中的

/* 當解析器找到開始標記和結束標記之間的字符時,調用這個方法解析當前節點的所有字符 */
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
方法解析該節點中的所有字符。此時可以用一個tempString變量保存其中的字符內容。 
(4)在遇到一個xml結束標記後,表明解析該xml節點結束,此時可以調用
/* 當解析器對象遇到xml的結束標記時,調用這個方法完成解析該節點 */
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
完成解析後的工作,例如將解析得到的結果存入數組中。

(5)如果解析出錯,將調用

/* 解析xml出錯的處理方法 */
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError {
    NSLog(@"解析xml出錯:%@", parseError);
}
進行出錯處理。

(6)如果解析沒有出錯,也就是在成功解析整個XML文件後,將調用

/* 解析xml文件成功 */
- (void)parserDidEndDocument:(NSXMLParser *)parser
在該方法中可以使用xml解析後的完整數據。 

運行結果:


2.使用NSXMLParser解析本地的XML文件

可以在Xcode中新建一個RTF文件,然後將XML中的文字內容粘貼到該文件中,例如:

<?xml version="1.0" encoding="UTF-8"?>

<Data>
     <Movie>
           <title>good lucky to you</title>
           <box>111</box>
           <summary>This is a story</summary>
     </Movie>

     <Movie>
           <title>hello</title>
           <box>99</box>
           <summary>oh,yes</summary>
    </Movie>
       
    <Movie>
          <title>Cold</title>
          <box>100</box>
          <summary>I love cold weather</summary>
    </Movie>
</Data>

最後將rtf後綴改爲xml就可以了。

如果修改xml文件中的內容,比如去掉一個</summary>,那麼在解析xml文件時將會出錯,例如:
2013-10-11 16:46:27.777 XMLParserDemo[5636:a0b] 解析xml出錯:Error Domain=NSXMLParserErrorDomain Code=76 "The operation couldn’t be completed. (NSXMLParserErrorDomain error 76.)" UserInfo=0x8c40f70 {NSXMLParserErrorLineNumber=20, NSXMLParserErrorColumn=13, NSXMLParserErrorMessage=Opening and ending tag mismatch: summary line 0 and Movie
}
此時,程序將調用出錯處理方法
/* 解析xml出錯的處理方法 */
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError {
    NSLog(@"解析xml出錯:%@", parseError);
}
但最終不會調用
- (void)parserDidEndDocument:(NSXMLParser *)parser
也就是說,只有解析xml成功,纔會有parserDidEndDocument:方法的調用。 

和解析xml的url數據唯一不同的是解析器的初始化方法不同,這裏的是:
- (IBAction)parseXML:(id)sender {
    // xmlParser = [[NSXMLParser alloc] initWithContentsOfURL:[NSURL URLWithString:xmlURLString]];
    NSString *path = [[NSBundle mainBundle] pathForResource:@"xml" ofType:@"xml"]; // 找到文件路徑
    NSFileHandle *file = [NSFileHandle fileHandleForReadingAtPath:path]; // 創建文件處理器
    NSData *data = [file readDataToEndOfFile]; // 讀取文件中的二進制數據
    [file closeFile]; // 關閉文件
    xmlParser = [[NSXMLParser alloc] initWithData:data]; // 通過文件中的二進制數據初始化xml解析器
     xmlParser.delegate = self; // 設置委託
    [xmlParser parse]; // 開始轉換
}

對於解析url的xml數據的解析器初始化方法爲:

xmlParser = [[NSXMLParser alloc] initWithContentsOfURL:[NSURL URLWithString:xmlURLString]];

解析本地xml文件的解析器的初始化方法爲:

NSFileHandle *file = [ NSFileHandle fileHandleForReadingAtPath :path]; // 創建文件處理器

NSData *data = [file readDataToEndOfFile ]; // 讀取文件中的二進制數據

xmlParser = [[ NSXMLParser alloc ] initWithData :data]; // 通過文件中的二進制數據初始化 xml 解析器


其解析過程是一樣的,只是修改一下各個節點的節點名就可以了。

很簡單,代碼就不貼了。

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