Rss Reader实例开发之Xml与RSS解析
我们在《JSON格式解析和libjson使用简介》中介绍了JSON格式的解析,这里将介绍下一个更普遍使用的网络数据交换格式XML,及基于XML语言而扩展出来用于内容聚合的格式规范RSS,并结合实例介绍下XML/RSS的解析与生成。
XML简介:
XML(Extensible Markup Language)即可扩展标记语言,它是由一系列的自定义的标签和数据组成,是被设计用来结构化、存储以及传输信息,在形式上与HTML很类似,最大的区别是XML的标签都是自定义的。XML标签是区分字母大小写的,所有的XML元素都必需有关闭标签,如:〈abc〉〈/abc〉或写成〈abc /〉。
XML 元素指的是从(且包括)开始标签直到(且包括)结束标签的部分。元素可包含其他元素(即子元素)、文本或者两者的混合物。元素也可以拥有属性,属性值须加引号,如attr=”XXX”,元素中每个属性间用空格分隔。
XML 文档由XML序言开始,XML序言是描述该文档的并有一个包含其他所有元素的父元素,该元素称为根元素/文档元素。
- xml version="1.0" encoding="UTF-8" standalone="no"?>
- <root>
- <child1 subNum=”3”>
- <subchild1>1st textsubchild1>
- <subchild2>2nd textsubchild2>
- <subchild3>3rd textsubchild3>
- child1>
- <child2>
- <subchild4>4th textsubchild4>
- child2>
- root>
〈root〉便是这XML文档的根元素,根元素下面有两了元素:〈child1〉与〈child2〉,而〈child1〉元素下面又有三个子元素〈subchild1〉、〈subchild2〉和〈subchild3〉,这三个元素下面不再有子元素了,都是包含了文本内容。〈child1〉还有一个subNum属性,其值是3。
可以看出XML是以一种树结构来组织数据的。每个元素便是一个节点,元素的子元素便是这个节点的子节点,元素的文本内容就是这棵树的叶子。所以上例可以用一棵树状结构来描述,请看下图:
![](http://dev.uphoneapp.com/upload/ckfiles/images/21.PNG)
在RssReader中使用XML:
在对XML有了一个基础认识,就可以在这基础上使用它,接下来就介绍一下如何在代码中解析和生成一份XML文档。网络上有很多优秀的解析/生成XML文档的开源库,如:libxml、TinyXML等,这里我们选择了TinyXML,主要是因为TinyXML对中文的支持比libxml好。TinyXML是一款简单小巧的C++ XML解析器,使用文档对象模型(DOM),不支持文档类型定义(DTDs)和可扩展样式表语言(XLSs)。
TinyXML支持两种方式的字符串处理,一种是使用STL的std::string类型及其流操作,一种是TinyXML自定义的String类型及操作。由于沃Phone平台使用的是android的编译器,而android的编译器是不支持STL的部分功能,如其流操作等,所以我们要在沃Phone平台上正常使用TinyXML,那么就要将tinyxml.h文件中的宏定义(TIXML_USE_STL)给注释掉,这样在编译时就会选择使用TinyXML自定义的String类型及操作。如下代码:
-
- #ifdefTIXML_USE_STL
- #include 〈string〉
- #include 〈iostream〉
- #include 〈sstream〉
- #define TIXML_STRING std::string
- #else
- #include"tinystr.h"
- #defineTIXML_STRING TiXmlString
- #endif
如此修改之后,就可以在交叉编译时顺利通过了。
在TinyXML中有如下所列的类与上图中的各节点概念相对应:
1、TiXmlDocument:对应上图中的XML file节点,代表整份文档;
2、TiXmlDeclaration :对应上图中的XML序言节点,它表示文件的声明部分;
3、TiXmlComment :是一个注释类,它表示文件的注释部分;
4、TiXmlElement :对应上图中除XML序言节点外的所有白色椭圆节点(从root到subchild[n]),它是文件的主要部分,并且支持嵌套结构;
5、TiXmlAttribute/TiXmlAttributeSet :元素属性,它一般嵌套在元素中,用于记录此元素的一些属性,对应上图中的subNum的白色矩形框;
6、TiXmlText :对应上图中的绿色椭圆节点,文本对象,它嵌套在某个元素内部;
下面就介绍一下如何使用TinyXML来解析XML文档:
一切都是从TiXmlDocument对象开始的,也就是说要先new一个TiXmlDocument对象,再调用相应的Load方法将文档内容装载到TiXmlDocument对象中。而这个过程又可分成两种方式:
1、先创建一个空的TiXmlDocument对象,再调用LoadFile(“文档路径”)装载,如:
- TiXmlDocument* pTiXmlDoc =newTiXmlDocument();
- if(pTiXmlDoc)
- {
- pTiXmlDoc->LoadFile (“test.xml”);
- }
2、在创建对象时就在构造函数中传入文档路径,再调用LoadFile()装载,如:
- TiXmlDocument* pTiXmlDoc =newTiXmlDocument(“test.xml”);
- if(pTiXmlDoc)
- {
- pTiXmlDoc->LoadFile ();
- }
第一种方式中,又根据传入的参数类型与数量不同而分为下面3个API:
- bool LoadFile( TiXmlEncodingencoding= TIXML_DEFAULT_ENCODING);
- bool LoadFile(constchar* filename, TiXmlEncodingencoding = TIXML_DEFAULT_ENCODING);
- bool LoadFile( FILE*, TiXmlEncodingencoding= TIXML_DEFAULT_ENCODING);
在RssReader中对TinyXML扩展了三个API:
- bool LoadFileTG3( TiXmlEncodingencoding= TIXML_DEFAULT_ENCODING);
- bool LoadFileTG3( Handlefile, TiXmlEncodingencoding= TIXML_DEFAULT_ENCODING);
- bool LoadFileTG3( constTUChar* _filename, TiXmlEncodingencoding= TIXML_DEFAULT_ENCODING);
在装载数据成功后,我们就可以从这个DOM结构中提取我们关心的数据。首先先获取文档的根元素:
TiXmlElement* pTiXmlRootElem = pTiXmlDoc->RootElement();
TinyXML是基于树状结构的,所以我们需要通过TiXmlElement类的FirstChildElement(key)方法一级一级地获取下一级元素的指针,如:
TiXmlElement* pTiXmlElem = = pTiXmlRootElem->FirstChildElement("child1");
如果要获取child1节点的属性值subNum,则需要调用Attribute(key, value),其中value为[out]型参数,是把属性key对应的值放到value中,如:
Int32 nValue = 0;
pTiXmlElem->Attribute("subNum", &nValue);
而如果某个元素下面全部/部分子元素的key为一样的话(可以理解成数组),我们可以使用TiXmlElement指针及FirstChildElement(key)与NextSiblingElement(key)来遍历该元素的所有子元素,如:
- TiXmlElement* pCur = NULL;
- for (pCur = pTiXmlRootElem->FirstChildElement("item"); pCur; pCur = pCur->NextSiblingElement("item"))
- {
-
- }
而最终获取元素的文本内容时,是使用GetText()方法。下面就举个RssReader中用于解析RSS文档中前面若干条新闻ITEM的一个例子:
- void TMainPageData::LoadDataFromXmlFile(TUChar* pszFilePath)
- {
- TiXmlDocument* pTiXmlDoc = NULL;
- TiXmlElement* pTiXmlRootElem = NULL;
-
-
- pTiXmlDoc = new TiXmlDocument();
- if (pTiXmlDoc)
- {
-
- if (pTiXmlDoc->LoadFileTG3(pszFilePath))
- {
-
- pTiXmlRootElem = pTiXmlDoc->RootElement();
- if (pTiXmlRootElem && (pTiXmlRootElem = pTiXmlRootElem->FirstChildElement("channel")))
- {
- char* pszEncoding = "utf-8";
- TiXmlElement* pTiXmlElem = NULL;
- TiXmlElement* pCur = NULL;
-
-
- for (pCur = pTiXmlRootElem->FirstChildElement("item"); pCur;
- pCur = pCur->NextSiblingElement("item"))
- {
-
- if (m_aHotNews.size() < MAIN_PAGE_SHOW_NEWS_NUM)
- {
- NewsItemData* pNewsItem = new NewsItemData;
-
-
- pTiXmlElem = pCur->FirstChildElement("title");
- if (pTiXmlElem)
- {
-
-
- pNewsItem->pszTitle = MakeNewCopy2Unicode(pTiXmlElem->GetText(), pszEncoding);
- }
- pTiXmlElem = pCur->FirstChildElement("link");
- if (pTiXmlElem)
- {
- pNewsItem->pszLink = MakeNewCopy(pTiXmlElem->GetText());
- }
- pTiXmlElem = pCur->FirstChildElement("pubDate");
- if (pTiXmlElem)
- {
- pNewsItem->pszTime = MakeNewCopy(pTiXmlElem->GetText());
- }
- pTiXmlElem = pCur->FirstChildElement("guid");
- if (pTiXmlElem)
- {
- pNewsItem->pszGuid = MakeNewCopy(pTiXmlElem->GetText());
- }
- pTiXmlElem = pCur->FirstChildElement("description");
- if (pTiXmlElem)
- {
- pNewsItem->pszDesc = MakeNewCopy(pTiXmlElem->GetText());
- }
-
- m_aHotNews.push_back(pNewsItem);
- }
- }
-
- pTiXmlRootElem = NULL;
- }
- }
-
- delete pTiXmlDoc;
- pTiXmlDoc = NULL;
- }
- }
上面这段代码是实现将指定文件路径的XML文档解析并提取其中部分数据,存放到m_aHotNews的vector变量中。
当然TinyXML还提供一种使用句柄提取数据的方式,不过这里就不作介绍,只是取个例子,如:
- TiXmlHandle docHandle( &document );
- TiXmlElement* child2 = docHandle.FirstChild( "Document" ).FirstChild( "Element" ).Child( "Child", 1 ).ToElement();
- if ( child2 )
- {
-
- }
使用TinyXML来生成并保存XML文档:
生成一棵XML树(文档),其中的诸多工作基本可以归纳为可递归的三个步骤:
1、创建/new一个节点对象;
2、设置节点对象的属性值;
3、建立节点与子节点间的连接;
最终将文档保存到本地时,只要调用SaveFileTG3("文档路径")方法。例子代码如下:
- TiXmlDocument* pTiXmlDoc = NULL;
- TiXmlElement* pTiXmlRootElem = NULL;
- TiXmlText* pTiXmlText = NULL;
-
- pTiXmlDoc = new TiXmlDocument();
- if (pTiXmlDoc)
- {
-
- pTiXmlRootElem = new TiXmlElement("root");
- if (pTiXmlRootElem)
- {
-
- pTiXmlDoc->LinkEndChild(pTiXmlRootElem);
-
-
- TiXmlElement* pTiXmlElem = new TiXmlElement("child1");
- if (pTiXmlElem)
- {
-
- pTiXmlRootElem->LinkEndChild(pTiXmlElem);
-
-
- pTiXmlElem->SetAttribute("subNum", 3);
-
-
- pTiXmlText = new TiXmlText(“1st text”);
- if (pTiXmlText)
- {
-
- pTiXmlElem->LinkEndChild(pTiXmlText);
- pTiXmlText = NULL;
- }
- pTiXmlElem = NULL;
- }
-
- pTiXmlRootElem = NULL;
- }
-
-
- pTiXmlDoc->SaveFileTG3(m_szFileFullName);
- delete pTiXmlDoc;
- pTiXmlDoc = NULL;
- }
RSS简介:
RSS为Really Simple Syndication(简易供稿)的缩写,也叫聚合内容,通常在时效性比较强的内容上使用RSS订阅能更快速获取信息,网站提供RSS输出,有利于让用户获取网站内容的最新更新。RSS,原意是把网站内容如标题、链接、部分内文甚至全文转换为可延伸标示语言(XML:eXtensible Markup Language)的格式,以向其它网站供稿。
其实RSS文档就是一份有自身规范的XML文档,通用的RSS文档格式如下:
-
- xml version="1.0"?>
-
-
- <rss version="2.0">
-
-
- <channel>
-
-
- <title>Lift Off Newstitle>
-
-
- <link>http://liftoff.msfc.nasa.gov/link>
-
-
- <description>Liftoff to Space Exploration.description>
-
-
- <language>en-uslanguage>
-
-
- <pubDate>Tue, 10 Jun 2003 04:00:00 GMTpubDate>
-
-
- <lastBuildDate>Tue, 10 Jun 2003 09:41:01 GMTlastBuildDate>
-
- <docs>http://blogs.law.harvard.edu/tech/rssdocs>
-
-
- <generator>Weblog Editor 2.0generator>
- <managingEditor>editor@example.commanagingEditor>
- <webMaster>webmaster@example.comwebMaster>
-
-
- <ttl>5ttl>
-
-
-
-
- <item>
-
- <title>Star Citytitle>
-
-
- <link>http://liftoff.msfc.nasa.gov/news/2003/news-starcity.asplink>
-
-
- <description>How do Americans get ready to work with Russians aboard theInternational Space Station? They take a crash course in culture, languageand protocol at Russia's Star City.description>
-
-
- <pubDate>Tue, 03 Jun 2003 09:39:21 GMTpubDate>
-
-
- <category>ITcategory>
-
-
- <author>bill@xxx.comauthor>
-
-
- <guid>http://liftoff.msfc.nasa.gov/2003/06/03.html#item573guid>
-
-
- <comments>http://news.mtc.sohu.com/news/channel/1/news/13604.html#commentscomments>
-
-
- <source>source>
-
-
- <enclosure>enclosure>
- item>
-
- <item>
- <title>Space Explorationtitle>
- <link>http://liftoff.msfc.nasa.gov/link>
- <description>Sky watchers in Europe, Asia, and parts of Alaska and Canadawill experience a partial eclipse of the Sun on Saturday, May 31st.description>Fri, 30 May 2003 11:06:42 GMTpubDate>
- <guid>http://liftoff.msfc.nasa.gov/2003/05/30.html#item572guid>
- item>
-
- <item>
- <title>The Engine That Does Moretitle>
- <link>http://liftoff.msfc.nasa.gov/news/2003/news-VASIMR.asplink>
- <description>Before man travels to Mars, NASA hopes to design new enginesthat will let us fly through the Solar System more quickly. The proposedVASIMR engine would do that.description>Tue, 27 May 2003 08:37:32 GMTpubDate>
- <guid>http://www.zhanghangfeng.cn/rss.xmlguid>
- item>
-
- <item>
- <title>Astronauts' Dirty Laundrytitle>
- <link>http://liftoff.msfc.nasa.gov/news/2003/news-laundry.asplink>
- <description>Compared to earlier spacecraft, the International SpaceStation has many luxuries, but laundry facilities are not one of them.Instead, astronauts have other options.description>Tue, 20 May 2003 08:56:02 GMTpubDate>
- <guid>http://liftoff.msfc.nasa.gov/2003/05/20.html#item570guid>
- item>
-
- channel>
- rss>
〈channel 〉及其子项〈item〉都有三项是一般拥有的元素:〈title〉、〈link〉和〈 description 〉,其它为可选。这只是一份一般情况下通用的格式,不同版本或不同的用途的RSS源的RSS格式会有所不一样,如:有的〈channel 〉下会有〈image〉、〈TextInput〉等标签,当然,我们也可按着RSS规范的精神来扩展一些功能,如在每个〈item〉中增加一项〈icon〉用来设定item的图标链接。
开源库MRss的使用:
在RSSReader项目中,用于解析/保存RSS格式的工具是mRss,mRss是一款开源库,现在官网上其最新版本是libmrss-0.9,它同时支持解析0.91、0.92、1.0和2.0四个版本的RSS格式。由于网络上下载的libmrss都是.c或.h为后缀的文件,而RSSReader项目中的C文件都是以.cpp为后缀的,所以我们在将libmrss里的C文件添加到项目中之前,要先把.c后缀改成.cpp。另外,我们还需要将源文件中的头部的宏定义给删/注释掉,删除的代码部分如下:
- #ifdef HAVE_CONFIG_H
- #include
- #else
- # error Use configure; make; make install
- #endif
在做完以上两步工作,libmrss就可以在我们的RSSReader项目中编译通过了。不过这时的mrss在解析带中文的RSS格式还是有很多问题的,因为RSS是基于XML格式的,所以MRSS解析RSS文档时的做法是:先将RSS文档解析到DOM中,再从DOM中提取数据放到MRSS自定义的结构体中,MRSS在解析和生成RSS文档的逻辑过程如下图:
![](http://dev.uphoneapp.com/upload/ckfiles/images/22.PNG)
而mrss是使用libxml来解析XML的,而libxml对中文的支持问题很大,特别是GB编码的中文,所以我们需要对mrss中执行解析工作的文件mrss_parser.cpp大刀阔斧地修改一番,全部改成使用TinyXML来解析XML,这样就可以很好的支持中文了。至于具体如何修改,这里就不赘述了,已有修改后的源码(RSSReader/mrss)上传到社区,请自行下载使用。
对应RSS文档的格式,在mrss中声明了如下几个主要的结构体类型来组织和存储RSS中的数据:
- typedef struct mrss_t mrss_t;
- typedef struct mrss_item_t mrss_item_t;
- typedef struct mrss_category_t mrss_category_t;
- typedef struct mrss_hour_t mrss_hour_t;
- typedef struct mrss_day_t mrss_day_t;
mrss_item_t、mrss_category_t、mrss_hour_t和mrss_day_t结构体类型都是链表结构,mrss_t是存放整个RSS文档数据的结构体类型,记录RSS的版本号、编码、标题等信息,以及指向mrss_item_t、mrss_category_t、mrss_hour_t和mrss_day_t结构体类型的链表的指针。其组织结构图如下:
![](http://dev.uphoneapp.com/upload/ckfiles/images/23.PNG)
mrss提供了丰富的API用于解析、保存、释放RSS数据等操作,
1、解析API,根据不同的数据源,分别提供了三种接口:
mrss_error_t mrss_parse_url(char* url, mrss_t ** mrss);//用于解析网络上的RSS源
mrss_error_t mrss_parse_file(char * file, mrss_t ** mrss);//用于解析本地文件
mrss_error_t mrss_parse_buffer(char *buffer, size_t size_buffer, mrss_t ** mrss);//用于解析内存中的RSS数据
2、保存API:
mrss_error_t mrss_write_file (mrss_t * mrss, char * file);//保存到本地文件
mrss_error_t mrss_write_buffer (mrss_t *mrss, char ** buffer);//保存到内存
3、释放API:
mrss_error_t mrss_free (mrss_generic_t element);//释放内存
除了上述API,还有一些其它用途的API,如编辑功能的API等。
解析RSS的例子:
- mrss_t* m_pRss = new mrss_t;
- if(m_pRss)
- {
- MemSet(m_pRss, 0, sizeof(mrss_t));
-
- if (MRSS_OK == mrss_parse_file(pszRssFilePath, & m_pRss))
- {
- mrss_item_t *pRssItem;
- pRssItem = m_pRss->item;
-
-
-
- }
-
-
- mrss_free(m_pRss);
- delete m_pRss;
- m_pRss = NULL;
- }
总结:
与JSON相比,一般情况下,传输相同信息量,使用XML格式比使用JSON格式产生的数据量大些。另外,在解析或生成XML格式文档也会比解析或生成JSON格式文档来的复杂一些。但是,XML格式的首尾标签对齐的形式,使其可读性和可编辑性会比JSON良好,所以XML常会用于需要人工阅读或编辑的情况下的数据存储和交换。