在编程世界中,处理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.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不会将整个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。