ACEXML解析XML文件——我是如何學習並在短時間內掌握一個庫的使用方法的


最近做的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

指定命令行參數-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>

然后運行程序看一下效果

ACEXML解析過程示例

根據程序中輸出的信息可以了解整個xml文件解析過程中,handler類中大部分方法的意義以及調用時機。

Handler類詳解

開始解析的時候會調用”startDocument”方法,解析結束后會調用”endElement”方法。

解析到每個節點開始的時候會調用”startElement”方法,然后會調用”characters”方法,通過”characters”方法,可以獲取到節點的文本內容。

如果這個節點還有子節點的話,會依次解析子節點。

節點解析結束后調用”endElement”方法。

以上就是解析XML時,Handler類的主要方法的調用時機。

示例xml文件解析流程圖

根據示例中的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的attributes

這樣我們就可以獲取xml文件中所有節點的所有屬性和內容,ACEXML解析xml文件的基本方法就掌握了。

經過完善后的流程圖

示例xml文件完善后的解析流程圖

學習方法總結

其實ACEXML庫是一個非常簡單的庫,掌握它的使用方法並不難。關鍵是當接觸這樣一個完全不了解的庫的時候,如何能夠迅速熟悉並掌握它,才是本文想要描述的重點。

下面是我的經驗總結:

1.通過搜索引擎或者專業博客獲取介紹信息、使用demo,迅速建立起對這個庫的簡單的認識。

2.閱讀官方提供的介紹文檔。

3.查看官方提供的示例程序。

4.參照官方的示例程序,動手實現自己的demo。

5.總結自己實現demo過程中遇到的問題以及學到的東西。

系列鏈接

ACEXML解析XML文件——我是如何學習並在短時間內掌握一個庫的使用方法的

ACEXML解析XML文件——簡單示例程序


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM