C++ XML 解析器:tinyxml

jopen 10年前

C++ XML 解析器:tinyxml

1) TinyXML-2

一个简单,轻量,高效的C++ XML 解析器,能够很容易得整合到其他程序。

TinyXML-2相比1,内存占用更少,读取更快,能更好得适应移动设备(Android)。

2) 准备

2.1) 源码

TinyXML-2源码放在了GitHub上,其为zlib license开源协议。

当前最新release版为:tinyxml2-2.1.0.tar.gz

2.2) 编译

tinyxml2-2.1.0/tinyxml2/目录下是工程文件。居然有C::B的cbp,其配置的GCC Compiler,Window上用MinGW即可。

TinyXML-2仅有三个文件:tinyxml2.h,tinyxml2.cpp是其核心代码;xmltest.cpp是其测试代码。

需要注意tinyxml2.h内的宏定义:

ANDROID_NDK  # for Android    _WIN32       # for Win32  TINYXML2_EXPORT  # 动态库导出  TINYXML2_IMPORT  # 动态库导入    _DEBUG | DEBUG  # debug

自行配置的话,若在Windows上生成dll,注意定义宏_WIN32,TINYXML2_EXPORT。链接其的工程最好加个宏TINYXML2_IMPORT,减去不必要的寻址。其对应内容如下:

#ifdef _WIN32  #   ifdef TINYXML2_EXPORT  #       define TINYXML2_LIB __declspec(dllexport)  #   elif defined(TINYXML2_IMPORT)  #       define TINYXML2_LIB __declspec(dllimport)  #   else  #       define TINYXML2_LIB  #   endif  #else  #   define TINYXML2_LIB  #endif

3) 解析

3.1) 解析方式

TinyXML采用的是DOM方式,需要将XML整个加载到内存,将其看作树状结构进行操作。

其他方式还有:

  1. SAX:事件驱动,顺序解析XML,遇到节点内容等时回调函数。相比DOM,占用更少内存。
  2. Android上还提供了Pull:解析类似SAX,触发事件时是个标识符,可用switch区分事件。
  3. 更重量级的有XQuery:直接语法查询,能应用在任何类XML数据上,包括数据库。

总之,虽然TinyXML-2想更好得适应移动设备,但DOM方式本身就不适合呢。Android上推荐Pull方式,但应该只有Java API吧。

3.2) 读写XML

Step 1: 生成XML树,并写入文件,基本步骤如下:

  1. XMLDocument::New*(): 生成各类节点
  2. XMLElement::SetAttribute(): 设置节点属性
  3. XMLNode::Insert*(): 插入生成的节点
  4. XMLDocument::DeleteNode(),XMLNode::DeleteChild(): 删除节点
  5. XMLDocument::SaveFile(): 保存为文件

例如如下代码:

/*  <?xml version="1.0" encoding="UTF-8"?>  <element>    <!--comment-->    <sub attr="1" />    <sub attr="3" >&amp; Text!</sub>  </element>  */  bool CreateXml(const char *filename) {      XMLDocument *doc = new XMLDocument();        // use the standard declaration by default      XMLDeclaration *declaration = doc->NewDeclaration();      doc->InsertFirstChild(declaration);        // insert 'element' node      XMLNode *element = doc->InsertEndChild(doc->NewElement("element"));        // insert 'sub' nodes      XMLElement *subs[3];      for (int i = 0; i < 3; ++i) {          XMLElement *sub = doc->NewElement("sub");          sub->SetAttribute("attr", i+1);          element->InsertEndChild(sub);          subs[i] = sub;      }      // insert text to 3rd 'sub' node      XMLText *text = doc->NewText("& Text!");      // text->SetCData(true);  // <![CDATA[& Text!]]>      subs[2]->InsertFirstChild(text);        // delete 2nd 'sub' node      element->DeleteChild(subs[1]);      // doc->DeleteNode(subs[1]);        // insert 'comment' node      element->InsertFirstChild(doc->NewComment("comment"));        // save xml, true for compact      XMLError error = doc->SaveFile(filename, true);        delete doc;      return error == 0;  }

Step 2: 从文件载入,并打印输出:

  1. XMLDocument::LoadFile(): 从文件载入
  2. XMLDocument::Print(): 直接打印,或用XMLPrinter转为字符串

这样即可:

bool PrintXml(const char *filename) {      XMLDocument doc;      if (!doc.LoadFile(filename)) {          // doc.Print();          XMLPrinter streamer(0, false);  // true for compact          doc.Print(&streamer);          printf("%s", streamer.CStr());      }      return false;  }

以上代码,具体请见附1"src/xmlhandle.cc”。

3.2) 解析XML

例如此段XML:

<content lang="javascript">  <array>    <!-- a script -->    <script>      <key>0</key>      <bundle scope="global">        <x type="n" value="2"/>        <y type="n" value="7"/>        <z/>      </bundle>      <content lang="javascript">        <![CDATA[        (function(x, y) {          if (x < y && x > 0) {            return true;          } else {            return false;          }        }(x, y));        ]]>      </content>    </script>  </array>

Step 1: 直接对字符串进行解析:

static const char *xml =      "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"      // ...      "</array>";    XMLDocument doc;  doc.Parse(xml);  // doc.Print();  if (doc.ErrorID() != 0) {      doc.PrintError();  } else {      // 对doc操作解析  }

Step 2: 准备简单存储结构,以存放解析结果:

namespace script {    struct Value {      Type type;      std::string value;  };  struct Bundle {      std::string scope;      std::map<std::string, Value> values;  };  struct Content {      std::string lang;      std::string text;  };  struct Script {      int key;      Bundle bundle;      Content content;  };    }  // script

Step 3: 用XMLNode::NextSiblingElement()访问邻居,以遍历同级节点。

1) array下同级script:

// root 'array' node  XMLElement *el_root = doc.FirstChildElement("array");  if (el_root) {      // ...      // traverse 'script' node      XMLElement *el_script = el_root->FirstChildElement("script");      while (el_script) {          // ...          // next 'script' node          el_script = el_script->NextSiblingElement("script");      }      // ...  }

2) 不确定名称时,如bundle下可能x,y,z或其他:

XMLElement *el_bundle = el_script->FirstChildElement("bundle");  if (el_bundle) {      // ...      // traverse 'bundle' child values      XMLElement *el_value = el_bundle->FirstChildElement();      while (el_value) {          const char *name = el_value->Name();  // won't Null          // ...          el_value = el_value->NextSiblingElement();      }  }

Step 4: 用XMLElement::Attribute()获取节点属性,GetText()获取文本。

1) 获取content的lang属性及其内容:

XMLElement *el_content = el_script->FirstChildElement("content");  if (el_content) {      // 'content' 'lang' attribute      const char *lang = el_content->Attribute("lang");      if (lang) {          script.content.lang = std::string(lang);      }      // 'content' text      const char *text = el_content->GetText();      if (text) {          script.content.text = std::string(text);      }  }

Step 5: 用类似XMLElement::QueryIntAttribute()QueryIntText(),直接转换类型。

1) 直接将key文本存为int:

XMLElement *el_key = el_script->FirstChildElement("key");  if (!el_key || el_key->QueryIntText(&script.key) != 0) {      // none exists, XML_NO_TEXT_NODE, XML_CAN_NOT_CONVERT_TEXT  }

Step 6: 获取注释试试看:

// the comment  XMLNode *nd_comment = el_root->FirstChild();  if (nd_comment && nd_comment->ToComment()) {      std::stringstream comment;      comment << "<!--" << nd_comment->Value() << "-->";      std::cout << comment.str() << std::endl;  }

以上代码,具体请见附1"src/script.h与xmlparse.cc”。

4) 总结

本文主要介绍了TinyXML-2的使用。其实,DOM操作的API都很类似的。

例如操作HTML DOM:

  1. 原生API的话,见document。可以看到类似的createElement(),getElement*()等。不过查找节点,用querySelector()选择器更方便。

  2. jQuery的话,相应API在分类ManipulationTraversing下。但找一些特定位置的子节点,直接选择器Child Filter更简单。


附1:样例工程tinyxml_start

下载:tinyxml_start.zip

目录树如下:

tinyxml_start/  ├─build/  │  ├─tinyxml_start-gcc.cbp   # for gnu gcc  │  └─tinyxml_start-msvc.cbp  # for msvc 2010  ├─src/  ├─third_party/  │  └─tinyxml2-2.1.0/  └─tools/

tinyxml2下载解压到third_party/目录下,用C::B打开cbp工程文件即可。

tinyxml2在Windows上配置生成的是动态库,Linux下是静态库。