作者:孫東風 2009-12-01(轉載請註明出處)
在筆者的上一篇文章《玩轉iPhone網路通訊之BSD Socket篇》中,筆者試圖在iPhone平台上利用BSD Socket搭建了一個同時相容TCP/IP和HTTP協議進行通訊的架構,而在接下來的幾篇文章裡,筆者將進一步完善這個網路通訊的引擎並利用這個引擎寫一個簡易的wap瀏覽器。
在iPhone的safari瀏覽器上並不支援WML的解析,儘管筆者也認為WML這種抱殘守舊的技術被淘汰是遲早的事,但WML作為XML結構的一個“變種”進行學習還是不錯的。
最近瀏覽器技術很熱,熱得筆者都摸不著頭腦,前段時間金山的雷軍同志也投資UCWeb,儘管筆者並不覺得瀏覽器技術有什麼高深的技術含量抑或可進行投資的價值,其實瀏覽器充其量是個用戶端,但是既然人家大牛看好,那筆者研究研究也不無益處,或許看完本文讀者也可以拿著自己的產品去找雷軍同志投資一把了:)
閑話少話,言歸正傳。
上面說了,WML是XML結構的一個“變種”或者說特例,既然是特例那麼就可以把它當成XML來進行解析。那麼做一個瀏覽器的任務流程就清晰了,如下:
² 封裝BSD Socket進行HTTP請求。
² 將請求到的WML頁面解析成XML資料結構。
² 渲染需要在介面上顯示的WML標籤(英文名tag)。
² 將渲染後的WML標籤顯示在介面上(UIView)。
其中第一條在筆者的前一篇文中《玩轉iPhone網路通訊之BSD Socket篇》已經進行了初步的編寫,當然筆者還會在下面的文章中進一步完善。
這篇文章中著重講解WML的解析,因為WML是XML資料的特例,解析WML也就意味這解析XML。
說到解析XML,iPhone為程式員提供了很多工具比如NSXMLParser,這個類的介面定義如下:
@interface NSXMLParser : NSObject {
@private
void * _parser;
id _delegate;
id _reserved1;
id _reserved2;
id _reserved3;
}
- (id)initWithContentsOfURL:(NSURL *)url; // initializes the parser with the specified URL.
- (id)initWithData:(NSData *)data; // create the parser from data
// delegate management. The delegate is not retained.
- (id)delegate;
- (void)setDelegate:(id)delegate;
- (void)setShouldProcessNamespaces:(BOOL)shouldProcessNamespaces;
- (void)setShouldReportNamespacePrefixes:(BOOL)shouldReportNamespacePrefixes;
- (void)setShouldResolveExternalEntities:(BOOL)shouldResolveExternalEntities;
- (BOOL)shouldProcessNamespaces;
- (BOOL)shouldReportNamespacePrefixes;
- (BOOL)shouldResolveExternalEntities;
- (BOOL)parse; // called to start the event-driven parse. Returns YES in the event of a successful parse, and NO in case of error.
- (void)abortParsing; // called by the delegate to stop the parse. The delegate will get an error message sent to it.
- (NSError *)parserError; // can be called after a parse is over to determine parser state.
@end
從介面的定義中大致可以知道,這個類解析XML是採用SAX模式(Simple API for XML),而SAX是基於事件驅動的,其基本工作流程是分析XML檔案流資料,每當發現一個新的元素時,就會產生一個對應的事件,並調用相應的使用者處理函數。在iPhone上蘋果公司採用了delegate模式,每發現一個新的元素時,就會調用相應的委託介面進行XML標籤的處理。
利用SAX模式解析XML佔用記憶體少、速度快,但使用者需要把解析到的XML標籤自己組合成一個樹狀結構,從而使程式處理比較複雜。
而對WML瀏覽器來說,儘管其tag並不是特別多,但是如果想完整的支援WML的tag也是一件比較枯燥的事情。所以,筆者這裡採用DOM(Document Object Model)模式來解析XML檔案。DOM模式在分析XML檔案時,一次性的將整個XML檔案流進行分析,並在記憶體中形成對應的樹結構,同時,向使用者提供一系列的介面來訪問和編輯該樹結構。這種方式佔用記憶體大,速度往往慢於SAX模式,但可以給程式員提供一個物件導向的提供者,較為方便。
XML語言的全稱是可擴充標識語言(eXtensible Markup Language),具體含義顧名思義就知道了。所謂“可擴充”,那是因為HTML等語言的不可擴充,在XML裡的標籤都是可以自訂的,比如WML利用XML語言自訂了一套tag,於是就有了無線wap規範。
XML的可擴充性是指在相應的規範和標準上的擴充。首先格式要符合XML的基本要求,比如第一行要有聲明,標籤的嵌套層次必須前後一致等等,符合這些要求的檔案,就算是一個合格的XML檔案,稱為Well-formatted。其次,XML文檔因其內容的不同還必須在語義上符合相應的標準,這些標準由相應的“DTD檔案”或者“Schema檔案”來了定義,符合了這些定義要求的XML檔案,稱作Valid。
筆者在本文中採用了開源的TinyXML解析器,這個解析器不會用相應的DTD檔案對XML檔案進行校正,但它的體積很小,只包含兩個*.h檔案和四個*.cpp檔案。
TinyXML是個開源的項目,更多詳細的資訊可以參考http://www.grinninglizard.com/tinyxml/index.html。
下載檔案包後,把相應的檔案匯入到項目工程中,如:
圖1
其中tinyxml.h檔案包含了全部的聲明,在項目中只需要包含這個檔案即可。
Tinyxml.h中定義了很多結構,如下
class TiXmlNode : public TiXmlBase
{
friend class TiXmlDocument;
friend class TiXmlElement;
…
}
這些類對應XML中的樹狀結構,拿下面的XML文檔為例:
<?xml version="1.0" encoding="utf-8" ?>
<!-example-->
<food>
<name>bread</name>
<price unit=”$”>1.5</price>
<description>made in China</description>
</ food >
其中整個XML文檔用類TiXmlDocument表示,<food>、<name>、<price>、<description>等各自對應一個類TiXmlElement,XML文檔的第一行對應類TiXmlDeclaration,第二行對應類TiXmlComment,文本“example”對應類TiXmlText,unit則是元素price的一個TiXmlAttribute屬性。
把TinyXML包匯入到項目後,建立一個XMLParserEx.h檔案和一個XMLParserEx.cpp檔案來封裝XML的處理,標頭檔定義如下:
#ifndef _CC_XMLPARSEREX_H_
#define _CC_XMLPARSEREX_H_
#include <stdio.h>
#include "tinyxml.h"
#define INVALID_ID -1
class XMLParserEx
{
public:
static XMLParserEx* GetInstance();
static void Destroy();
void RemoveAll();
void parsexml(const char* buffer);