简易跨平台XML解析器:XmlInspector

在编程世界中,处理XML文件是一项常见的任务。然而,找到一个既简单又符合要求的XML解析器并非易事。曾寻找一个满足以下条件的解析器:简单易用、跨平台、体积小、遵循XML标准。遗憾的是,没有找到完全符合这些条件的解析器,因此决定自己编写一个——这就是XmlInspector项目的由来。

XmlInspector的设计目标是简单、轻量、跨平台,并且严格遵循XML标准。它不依赖于任何第三方库,仅使用C++标准库。这意味着,无论项目大小如何,都可以直接将XmlInspector集成进去,而无需担心配置解析库的复杂性。

XmlInspector遵循XML推荐标准和XML命名空间推荐标准,但不处理DOCTYPE元素。如果XML文档中包含DOCTYPE元素,XmlInspector将忽略这些信息,并且不会解析任何DTD。此外,XmlInspector利用了C++11标准中的一些特性,如char16_t、char32_t、std::u16string、std::u32string、std::uint_least64_t、std::unique_ptr和强类型枚举。它在Linux的GCC 4.7.2和Clang 3.0-6.2、Windows的Visual C++ 2012和MinGW 4.7.2上进行了测试。

在XML解析器中,有两种流行的API:DOM和SAX。DOM是一种基于树的解析器,它会将整个文档读入树结构中。SAX是一种基于事件的解析器,它按顺序处理文档的每一部分。XmlInspector采用了与SAX相似的理念,但不需要注册回调函数和等待事件,可以随时读取XML文档中的下一个节点。认为这是一种更方便的解析XML文档的方式。

如何使用XmlInspector

首先,需要将XmlInspector.hpp、CharactersReader.hpp和CharactersWriter.hpp这三个头文件添加到项目中。然后,可以像下面这样读取元素:

#include "XmlInspector.hpp" #include <iostream> #include <cstdlib> int main() { Xml::Inspector<Xml::Encoding::Utf8Writer> inspector("test.xml"); while (inspector.Inspect()) { switch (inspector.GetInspected()) { case Xml::Inspected::StartTag: std::cout << "StartTag name: " << inspector.GetName() << "\n"; break; case Xml::Inspected::EndTag: std::cout << "EndTag name: " << inspector.GetName() << "\n"; break; case Xml::Inspected::EmptyElementTag: std::cout << "EmptyElementTag name: " << inspector.GetName() << "\n"; break; case Xml::Inspected::Text: std::cout << "Text value: " << inspector.GetValue() << "\n"; break; case Xml::Inspected::Comment: std::cout << "Comment value: " << inspector.GetValue() << "\n"; break; default: // Ignore the rest of elements. break; } } if (inspector.GetErrorCode() != Xml::ErrorCode::None) { std::cout << "Error: " << inspector.GetErrorMessage() << "At row: " << inspector.GetRow() << ", column: " << inspector.GetColumn() <<< ".\n"; } return EXIT_SUCCESS; }

运行上述代码,将得到以下结果:

StartTag name: shelter Comment value: Only 3 pets at this moment. It's the new shelter. StartTag name: pets StartTag name: dog Text value: Very aggressive dog, be careful. EndTag name: dog StartTag name: dog Text value: 2 years old dog with a short tail. EndTag name: dog StartTag name: cat Text value: Very lazy cat. EndTag name: cat EndTag name: pets Comment value: No pokemons yet. EmptyElementTag name: pokemons EndTag name: shelter

XmlInspector是主要的解析器类,Xml::Encoding::Utf8Writer是选择用来以UTF-8编码写入字符的类。这意味着将接收到std::string的引用。如果更喜欢例如UTF-16编码和std::u16string,可以这样写:

Xml::Inspector<Xml::Encoding::Utf16Writer> inspector("test.xml"); // ... const std::u16string& referenceToElementName = inspector.GetName();

Xml::Inspector类有更多的构造函数,例如可能想要解析存储在内存中的XML文档:

std::string doc = "abc"; Xml::Inspector<Xml::Encoding::Utf16Writer> inspector(doc.begin(), doc.end());

也可以使用单个Inspector对象解析更多的XML文档——有Xml::Inspector::Reset方法,参数与构造函数相同:

Xml::Inspector<Xml::Encoding::Utf16Writer> inspector; inspector.Reset("test.xml"); // ... std::string doc = "abc"; inspector.Reset(doc.begin(), doc.end());

每次调用Xml::Inspector::Inspect方法都试图从文档中读取一个元素。当检查的元素例如是StartTag,那么Xml::Inspector::GetName方法将提供这个元素的名称。如果检查的元素是ProcessingInstruction,那么Xml::Inspector::GetName方法将提供这个处理指令的目标名称。

现在让只打印狗的名字:

#include "XmlInspector.hpp" #include <iostream> #include <cstdlib> int main() { Xml::Inspector<Xml::Encoding::Utf8Writer> inspector("test.xml"); while (inspector.Inspect()) { if ((inspector.GetInspected() == Xml::Inspected::StartTag || inspector.GetInspected() == Xml::Inspected::EmptyElementTag) && inspector.GetName() == "dog") { Xml::Inspector<Xml::Encoding::Utf8Writer>::SizeType i; for (i = 0; i < inspector.GetAttributesCount(); ++i) { if (inspector.GetAttributeAt(i).Name == "name") { std::cout << inspector.GetAttributeAt(i).Value << "\n"; break; } } } } if (inspector.GetErrorCode() != Xml::ErrorCode::None) { std::cout << "Error: " << inspector.GetErrorMessage() << "At row: " << inspector.GetRow() << ", column: " << inspector.GetColumn() <<< ".\n"; } return EXIT_SUCCESS; }

运行上述代码,将得到以下结果:

Rambo Pinky

正如看到的,访问属性名称与Xml::Inspector方法略有不同。属性是一个简单的结构,包含一些字符串,没有方法。

XmlInspector的工作原理

XmlInspector不会将整个XML文档加载到内存中。它根据需要分配尽可能少的内存来遍历元素,因此文件大小并不重要。即使计算机内存比XML文档大小还要小,也可以处理大型文件。

如果某个元素需要更多的字符串分配,那么下一个元素不会删除它,即使这个字符串现在不需要。考虑这个文件:

<root name1="value1" name2="value2" name3="value3"> <aaa attr="abc" /> <bbb name1="value1" name2="value2" /> </root>

根元素需要为三个属性分配空间。元素aaa只有一个属性,所以另外两个属性现在不需要。如果元素aaa删除了这些字符串,那么下一个元素bbb将需要再次为属性分配空间。XmlInspector试图减少后续元素和文档的分配数量。如果不再需要Xml::Inspector对象,可以调用Xml::Inspector::Clear方法,或者等待析构函数释放一些空间。

支持的编码

XmlInspector支持的编码集包括:UTF-8、UTF-16、UTF-16BE、UTF-16LE、UTF-32、UTF-32BE、UTF-32LE;ISO-8859-1、ISO-8859-2、ISO-8859-3、ISO-8859-4、ISO-8859-5、ISO-8859-6、ISO-8859-7、ISO-8859-8、ISO-8859-9、ISO-8859-10、ISO-8859-13、ISO-8859-14、ISO-8859-15、ISO-8859-16、TIS-620;windows-874、windows-1250、windows-1251、windows-1252、windows-1253、windows-1254、windows-1255、windows-1256、windows-1257、windows-1258。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485