最近做的C++項目中需要使用xml文件保存一些信息,程序啟動時會讀取這些信息。最終經過主程的評測,決定使用ACEXML庫來讀取解析XML文件。
好吧,至於為什么選擇ACEXML庫,我就不說了。既然選擇了它,就要盡快上手並使用它。可是主程說他沒有文檔,如何使用自己看着辦吧
那么我是如何在短時間內掌握ACEXML的使用方法呢,下面來分享一下,我的學習歷程。
第一步肯定是谷歌搜索
對於我來說,第一步肯定不用想,直接上谷歌搜索。
經過幾分鍾的搜索后,我並沒有得到我想要的信息,谷歌前五頁基本上只能找到一個簡單的例子程序使用 ACEXML 來解析一個 xml 文件 和一個簡介ACEXML Programming Guidelines。
花幾分鍾看了一下例子程序,代碼特別長,有很多無關的東西,決定換下一種方法。
第二步查看官方的example
好吧,既然網上沒有詳細的介紹,那么退而求其次。
一般的開源庫都會有許多例子程序來幫助使用者學習庫的使用方式,ACEXML庫也不例外。
找到ACEXML文件夾中有一個examples子文件夾,打開里面的解決方案,看到有一個”SAXPrint”的例子程序,OK,就是它了。
首先編譯程序,然后運行
窗口中輸出的是命令行參數
-s : 使用 SAXPrint_Handler,默認是 Priing_handler。
-l:解析內置的字符串。
-f:當沒有指定-l 時,解析指定的文件。
-z:使用指定的HTTP 文件。
接下來在運行的時候指定命令行
-l -s
根據打印出來的信息與內置的字符串比較,xml格式字符串正確的解析出來。
那么接下來看一下SAXPrint_Handler這個類是如何實現的。
/** * @class ACEXML_SAXPrint_Handler * * @brief ACEXML_SAXPrint_Handler is an example SAX event handler. * * This SAX event handler try to regenerate the XML document it * reads with correct indentation. */ class ACEXML_SAXPrint_Handler : public ACEXML_DefaultHandler { public: /* * Default constructor. */ ACEXML_SAXPrint_Handler (const ACEXML_Char* name); /* * Default destructor. */ virtual ~ACEXML_SAXPrint_Handler (void); // Methods inherit from ACEXML_ContentHandler. /* * Receive notification of character data. */ virtual void characters (const ACEXML_Char *ch, size_t start, size_t length) ; /* * Receive notification of the end of a document. */ virtual void endDocument (void) ; /* * Receive notification of the end of an element. */ virtual void endElement (const ACEXML_Char *namespaceURI, const ACEXML_Char *localName, const ACEXML_Char *qName); /* * End the scope of a prefix-URI mapping. */ virtual void endPrefixMapping (const ACEXML_Char *prefix); /* * Receive notification of ignorable whitespace in element content. */ virtual void ignorableWhitespace (const ACEXML_Char *ch, int start, int length); /* * Receive notification of a processing instruction. */ virtual void processingInstruction (const ACEXML_Char *target, const ACEXML_Char *data); /* * Receive an object for locating the origin of SAX document events. */ virtual void setDocumentLocator (ACEXML_Locator *locator); /* * Receive notification of a skipped entity. */ virtual void skippedEntity (const ACEXML_Char *name); /* * Receive notification of the beginning of a document. */ virtual void startDocument (void); /* * Receive notification of the beginning of an element. */ virtual void startElement (const ACEXML_Char *namespaceURI, const ACEXML_Char *localName, const ACEXML_Char *qName, ACEXML_Attributes *atts); /* * Begin the scope of a prefix-URI Namespace mapping. */ virtual void startPrefixMapping (const ACEXML_Char *prefix, const ACEXML_Char *uri); // *** Methods inherit from ACEXML_DTDHandler. /* * Receive notification of a notation declaration event. */ virtual void notationDecl (const ACEXML_Char *name, const ACEXML_Char *publicId, const ACEXML_Char *systemId); /* * Receive notification of an unparsed entity declaration event. */ virtual void unparsedEntityDecl (const ACEXML_Char *name, const ACEXML_Char *publicId, const ACEXML_Char *systemId, const ACEXML_Char *notationName); // Methods inherit from ACEXML_EnitityResolver. /* * Allow the application to resolve external entities. */ virtual ACEXML_InputSource *resolveEntity (const ACEXML_Char *publicId, const ACEXML_Char *systemId); // Methods inherit from ACEXML_ErrorHandler. /* * Receive notification of a recoverable error. */ virtual void error (ACEXML_SAXParseException &exception); /* * Receive notification of a non-recoverable error. */ virtual void fatalError (ACEXML_SAXParseException &exception); /* * Receive notification of a warning. */ virtual void warning (ACEXML_SAXParseException &exception); void inc_indent (); void dec_indent (); void print_indent (); private: size_t indent_; ACEXML_Char* fileName_; ACEXML_Locator* locator_; };
根據這個類的聲明以及注釋,可以知道ACEXML的解析是基於事件處理的方式。ACEXML_DefaultHandler 為事件處理的基類,然后用於只需要繼承此類,然后實現基類所定義的虛方法即可。
那么ACEXML_DefaultHandler 類的虛方法們又是在什么時候會觸發呢。雖然代碼中每個方法都有注釋,但是這畢竟太抽象了,至少我憑想象是無法知道每個方法是在什么時候調用的。怎么辦,神器——單步調試。
。。。。。
雖然單步調試是神器,但是很明顯,它在這個地方起不到很大的作用。
既然深入機理無法了解原理,那么就縱觀大局,對總體情況有一個明晰。
縱觀大局——實現自己的Handler類
縱觀大局嘛,給每個從ACEXML_DefaultHandler類派生的虛方法加上日志,記錄參數的內容,這樣就可以根據所有的日志了解整體的調用過程。
xml_hanlder代碼如下
#pragma once #include "ACEXML/common/DefaultHandler.h" #include <string> #include <iostream> using namespace std; class xml_handler : public ACEXML_DefaultHandler { public: xml_handler(char* path): filename_(path){} public: virtual void characters( const ACEXML_Char *ch, size_t start, size_t length ) { cout << "filename : " << filename_ << ", characters : " << ch << endl; } virtual void endDocument( void ) { cout << "endDocument---------------------------------" << endl; } virtual void endElement( const ACEXML_Char *namespaceURI, const ACEXML_Char *localName, const ACEXML_Char *qName ) { cout << "endElement namespaceURI:" << namespaceURI << ", localName:" << localName << ", qName" << qName << endl; } virtual void endPrefixMapping( const ACEXML_Char *prefix ) { cout << "endPrefixMapping prefix" << prefix << endl; } virtual void ignorableWhitespace( const ACEXML_Char *ch, int start, int length ) { cout << "ignorableWhitespace ch:" << ch << endl; } virtual void processingInstruction( const ACEXML_Char *target, const ACEXML_Char *data ) { cout << "processingInstruction target:" << target << ", data:" << data << endl; } virtual void setDocumentLocator( ACEXML_Locator *locator ) { locator_ = locator; } virtual void skippedEntity( const ACEXML_Char *name ) { cout << "skippedEntity name:" << name << endl; } virtual void startDocument( void ) { cout << "startDocument------------------------------" << endl; } virtual void startElement( const ACEXML_Char *namespaceURI, const ACEXML_Char *localName, const ACEXML_Char *qName, ACEXML_Attributes *atts ) { cout << "startElement namespaceURI:" << namespaceURI << ", localName:" << localName << ", qName" << qName << endl; } virtual void startPrefixMapping( const ACEXML_Char *prefix, const ACEXML_Char *uri ) { cout << "startPrefixmapping prefix:" << prefix << ", uri" << uri << endl; } virtual void notationDecl( const ACEXML_Char *name, const ACEXML_Char *publicId, const ACEXML_Char *systemId ) { cout << "notationDecl name:" << name << ", publicId:" << publicId << ", systemID:" << systemId << endl; } virtual void unparsedEntityDecl( const ACEXML_Char *name, const ACEXML_Char *publicId, const ACEXML_Char *systemId, const ACEXML_Char *notationName ) { cout << "unparsedEntityDecl name:" << name << ", publicId:" << publicId << ", systemID:" << systemId << ", notationName" << notationName << endl; } virtual ACEXML_InputSource * resolveEntity( const ACEXML_Char *publicId, const ACEXML_Char *systemId ) { cout << "resolveEntity publicId:" << publicId << ", systemId:" << systemId << endl; return 0; } virtual void error( ACEXML_SAXParseException &exception ) { cout << "error" << endl; } virtual void fatalError( ACEXML_SAXParseException &exception ) { cout << "fatalError" << endl; } virtual void warning( ACEXML_SAXParseException &exception ) { cout << "warning" << endl; } private: size_t indent_; string filename_; ACEXML_Locator* locator_; };
代碼編寫完畢,首先編寫一個測試用的xml文件
<?xml version="1.0"?> <root> <file id="1">test1.txt</file> <file id="2">test2.txt</file> <file id="3" name="test3" show="true">test3.txt</file> </root>
然后運行程序看一下效果
根據程序中輸出的信息可以了解整個xml文件解析過程中,handler類中大部分方法的意義以及調用時機。
Handler類詳解
開始解析的時候會調用”startDocument”方法,解析結束后會調用”endElement”方法。
解析到每個節點開始的時候會調用”startElement”方法,然后會調用”characters”方法,通過”characters”方法,可以獲取到節點的文本內容。
如果這個節點還有子節點的話,會依次解析子節點。
節點解析結束后調用”endElement”方法。
以上就是解析XML時,Handler類的主要方法的調用時機。
示例xml文件解析流程圖
根據示例中的xml文件,可以整理如下的調用流程圖
解析file節點的attributes
根據流程圖我們看到,此過程只解析除了file節點的內容,但是沒有獲得file節點的屬性。
而根據官方的示例程序的截圖,看到正確的解析除了所有節點的所有屬性,那么我們為什么不參考一下例子程序呢。
示例程序的startElement方法的代碼
void ACEXML_SAXPrint_Handler::startElement (const ACEXML_Char *, const ACEXML_Char *, const ACEXML_Char *qName, ACEXML_Attributes *alist) { this->print_indent (); ACE_DEBUG ((LM_DEBUG, ACE_TEXT ("<%s"), qName)); if (alist != 0) for (size_t i = 0; i < alist->getLength (); ++i) { ACE_DEBUG ((LM_DEBUG, ACE_TEXT (" %s = \"%s\""), alist->getQName (i), alist->getValue (i))); } ACE_DEBUG ((LM_DEBUG, ACE_TEXT (">"))); this->inc_indent (); }
startElement方法有一個參數alist,其類型為ACEXML_Attributes,這個參數應該就是解析節點時,獲取到的屬性集合。
參照此代碼,來完善xml_handler類中的startElement方法
virtual void startElement( const ACEXML_Char *namespaceURI, const ACEXML_Char *localName, const ACEXML_Char *qName, ACEXML_Attributes *atts ) { cout << "startElement namespaceURI:" << namespaceURI << ", localName:" << localName << ", qName" << qName << endl; if (atts != 0) { for (size_t i = 0; i < atts->getLength (); ++i) { cout << "localName attributes: " << atts->getQName(i) << " = " << atts->getValue(i) << endl; } } }
那么我們再來看一下運行效果
這樣我們就可以獲取xml文件中所有節點的所有屬性和內容,ACEXML解析xml文件的基本方法就掌握了。
經過完善后的流程圖
學習方法總結
其實ACEXML庫是一個非常簡單的庫,掌握它的使用方法並不難。關鍵是當接觸這樣一個完全不了解的庫的時候,如何能夠迅速熟悉並掌握它,才是本文想要描述的重點。
下面是我的經驗總結:
1.通過搜索引擎或者專業博客獲取介紹信息、使用demo,迅速建立起對這個庫的簡單的認識。
2.閱讀官方提供的介紹文檔。
3.查看官方提供的示例程序。
4.參照官方的示例程序,動手實現自己的demo。
5.總結自己實現demo過程中遇到的問題以及學到的東西。