Safari浏览器上并不支持WML的解析,尽管笔者也认为WML这种抱残守旧的技术被淘汰是迟早的事,但WML作为XML结构的一个“变种”进行学习还是不错的。
上面说了,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);
void ElementParser(TiXmlNode* aParent);
protected:
XMLParserEx();
~XMLParserEx();
private:
static XMLParserEx* mInstance;
};
#endif
XMLParserEx.cpp文件实现如下:
#include "XMLParserEx.h"
XMLParserEx::XMLParserEx()
{
}
XMLParserEx::~XMLParserEx()
{
RemoveAll();
}
XMLParserEx* XMLParserEx::mInstance = 0;
XMLParserEx* XMLParserEx::GetInstance()
{
if (mInstance == 0)
{
mInstance = new XMLParserEx();
}
return mInstance;
}
void XMLParserEx::Destroy()
{
if (mInstance)
{
delete mInstance;
mInstance = 0;
}
}
void XMLParserEx::RemoveAll()
{
}
void XMLParserEx::ElementParser(TiXmlNode* aParent)
{
if(aParent == NULL)
return;
TiXmlNode* aChild = aParent->FirstChild();
while(aChild)
{
printf("aChild value = %s ",aChild->Value());
int t = aChild->Type();
if( t == TiXmlNode::ELEMENT)
{
TiXmlAttribute* attr = aChild->ToElement()->FirstAttribute();
if(attr)
{
TiXmlNode* node = aChild;
while(node)
{
while(attr)
{
printf("attr name = %s, attr value = %s ",attr->Name(),attr->Value());
attr = attr->Next();
}
node = node->NextSiblingElement();
}
}
ElementParser(aChild);
}
else if( t == TiXmlNode::TEXT)
{
printf("aChild Value = %s ",aChild->Value());
}
aChild = aChild->NextSibling();
}
}
void XMLParserEx::parsexml(const char* buffer)
{
TiXmlDocument* doc = new TiXmlDocument();
printf("xmlBuffer len = %d ",strlen(buffer));
printf("xmlBuffer is = %s ",buffer);
doc->Parse(buffer,0,TIXML_ENCODING_UTF8);
TiXmlElement* root = doc->RootElement();
printf("parse xml succeed ");
ElementParser(root);
}
已经成功解析出来了wml页面中的tag,如果读者仔细的话可能会看到里面中文的tag显示为乱码,这是因为在iPhone上默认的中文编码格式是UTF-8,而通过BSD Socket请求过来的是ASCII码,所以需要通过转换为UTF-8格式,如下:
[[NSString alloc] initWithBytes:aChild->Value() length:strlen(aChild->Value()) encoding:NSUTF8StringEncoding]
经过转换编码之后,在屏幕上显示的打印内容如下:
parse xml succeed
aChild value = STATUS OK
aChild value = card
TiXmlNode::ELEMENT name = title, attr value = 百度一下,你就知道
aChild value = p
aChild value = img
TiXmlNode::ELEMENT name = src, attr value = /r/wise/wapsearchindex/logoindexsmall.gif
TiXmlNode::ELEMENT name = alt, attr value = 百度首页
aChild value = br
aChild value = input
TiXmlNode::ELEMENT name = name, attr value = word
TiXmlNode::ELEMENT name = emptyok, attr value = true
aChild value = br
aChild value = anchor
aChild value = 搜网页
TiXmlNode::TEXT Value = 搜网页
接下来的任务就是渲染这些解析出来的tag并显示在界面上了,本篇里笔者重点讲述如果搭建一个可扩展的、健壮的界面架构。
在所有平台的界面架构中,笔者一直推崇MVC,MVC的着重点在于把界面显示和数据处理分离开来以提供可扩展的界面架构平台。
在Xml模块处理完xml数据并提取出tag后,交给CXmlControl进行处理,CXmlControl在这里充当Control的角色,它负责在处理完tag(标签)后生成相应的消息以显示在界面上。
其中CXmlControl继承自UIView类,它负责显示界面并响应用户的按键消息,而具体的逻辑处理则是在CXmlControlImpl类中进行的,CXmlControlImpl这个类负责管理生成的tag(标签)以及tag(标签)的界面Layout(布局),如下:
@class CXmlControlImpl;
@interface CXmlControl : UIView {
@public
CXmlControlImpl* iImpl;
}
-(void) addElements:(CXmlElementImpl*)iElemntAdded;
-(CXmlElement*) InsertContent:(CXmlElement*)aTarget aPosition:(NSInteger)aPosition aSource:(const NSString*)aSource aFlags:(NSInteger)aFlags;
-(CXmlElement*) AppendContent:(const NSString*)aSource aFlags:(NSInteger)aFlags;
-(void) Refresh;
-(void) RefreshAndDraw;
-(void) ClearContent;
-(void) RemoveElement:(CXmlElement*)aElement;
-(CXmlElement*) Element:(const NSString*)aId aIndex:(NSInteger)aIndex;
-(CXmlElement*) ElementByTag:(const NSString*)aTagName aIndex:(NSInteger)aIndex;
-(CXmlElement*) FocusedElement;
-(void) SetFocusTo:(CXmlElement*)aElement;
-(void) ScrollToView:(CXmlElement*)aElement;
-(CXmlElement*) Body;
-(void) SetEventObserver:(id<MXmlCtlEventObserver>)aObserver;
-(CXmlElementImpl*) Impl;
-(CGContextRef) SystemGc;
-(void) Draw:(const CGRect)aRect;
-(CWritableBitmap*) OffScreenBitmap;
-(void) SetOffScreenBitmap:(CWritableBitmap*)aBitmap;
-(void) DrawOffscreen;
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
-(void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
-(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
@end
Tag(标签)类的基类为CXmlElement,它抽象了标签的基本属性和操作,在每一个派生自此基类的tag(标签)类如CXmlTextElement、CXmlImgElement中都维护一个全局的布局类CHcMeasureStatus,每一个tag(标签)类都负责自己的布局和自己的绘制操作,完成布局操作和绘制操作后为改变CHcMearuseStatus的状态和属性并传递给CXmlControl类。