简介:
这个号称是最快的DOM模型XML分析器,在使用它之前我都是用TinyXML的,因为它小巧和容易上手,但真正在项目中使用时才发现如果分析一个比较大的XML时TinyXML还是表现一般,所以我们决定使用RapidXML作为替换。当然是为了获取更好的性能,经过我们的初步试验后发现确实比TinyXML好,但看到网上关于rapidxml的资料零散,而且也缺乏一份较为权威的说明文档,找来找去还是得看官方的英文手册。所以我又下单了,翻译官方提供的手册,希望给各位朋友提供一些绵薄之力。
1. RapidXml 是什么?
RapidXML是一个试图创建最快的XML DOM分析器,当然同时也保留它的可用性、移植性和适当的W3C兼容性。它是一个用C++编写的即用分析器,在相同的数据上它的分析速度接近于strlen()函数。
整个分析器就包含在单独的一个头文件中,所以不需要任何的构建和链接。当你使用的时候只需将 rapidxml.hpp 文件复制到一个合适的地方(例如你的工程目录下),然后在任何需要它的地方包含一下即可。或许你也会用到 rapidxml_print.hpp 头文件里的打印函数。
1.1 Dependencies And Compatibility(依赖和兼容)
RadpidXML除了使用到标准C++库(<cassert>, <cstdlib>, <new> 和 <exception>, 除非异常被关闭了)的很小一个子集外没有关联其它东西了。它能被任何标准编译器所编译,并已在Visual C++ 2003, Visual C++ 2005, Visual C++ 2008, gcc 3, gcc 4, 和 Comeau 4.3.3. 中测试通过。也就是说在这些编译器上没出现任何的警告信息,即使在最高警告级别上。
1.2 Character Types And Encodings(字符类型和编码)
RadpidXML并不知道字符集的类型(译注:因为它是基于模板,可以自定义字符类型),所以在窄字符和宽字符下都能工作。当前版本并不完全支持UTF-16和UTF-32,所以使用宽字符稍微有点不适合。不管怎样,如果数据的字节顺序与当前系统匹配,它能成功分析包含UTF-16或UTF-32 的wchar_t 字符串。UTF-8是完全支持的,包括所有的数值字符引用,这些字符会被扩展到适当的UTF-8字节顺序(除非你打开parse_no_utf8 flag 标记)。
要注意RapidXml不会对 name() 和 value() 函数返回的字符串进行解码,因为函数会根据源代码文件本身的编码模式来编码(译注:也就是说如果你的源代码文件编码格式为ANSI,则name()函数返回的字符串编码也是ANSI,如果源代码文件的编码格式为UTF-8,字符串编码也是UTF-8)。Rapidxml能解释和扩展下列的字符应用:' & " < > &#...;其它字符引用不会被展开。
1.3 Error Handling (错误处理)
默认情况下,RapidXml使用C++异常来报告错误,如果你不喜欢这个行为,可以使用RAPIDXML_NO_EXCEPTIONS定义来制止异常代码,请参考 parse_error 类和 parse_error_handler() (http://rapidxml.sourceforge.net/manual.html#namespacerapidxml_ff5d67f74437199d316d2b2660653ae1_1ff5d67f74437199d316d2b2660653ae1) 函数。
1.4 Memory Allocation(内存分配)
RadpidXml使用一个特别的内存池对象来分配所有的节点和属性,因为直接使用 new 操作符来分配太慢了,池中的内存分配可用 memory_pool::set_allocator() (http://rapidxml.sourceforge.net/manual.html#classrapidxml_1_1memory__pool_c0a55a6ef0837dca67572e357100d78a_1c0a55a6ef0837dca67572e357100d78a) 函数来自定义,详细信息请看 memory_pool (http://rapidxml.sourceforge.net/manual.html#classrapidxml_1_1memory__pool) 类。
1.5 W3C Compliance(W3C兼容)
RapidXml并不是一个与W3C完全一致的分析器,主要原因是忽略了 DOCTYPE 声明。还有一些其它原因,如兼容性不是很好,但是,它能成功分析和生成与W3C文件组(有关XML处理器的规格设计超过1000个文件)定义的完整XML树。即使在不良的情况下它也能对空白符进行正常化处理和对一部分字符集实体进行替换。
1.6 API Design(API设计)
为了尽可能的减少代码量,RadpidXML API已经很简约了,并且为了能在它嵌入的环境中更容易使用,一些附加的功能被分割在不同的头文件中:rapidxml_utils.hpp 和rapidxml_print.hpp (http://rapidxml.sourceforge.net/manual.html#rapidxml__print_8hpp)。这两个头文件并不是必须的,并且当前也没有相关文档(但是代码中有注释)。
1.7 Reliability(可靠性)
RadpidXML很健壮,因为它通过了一大堆的单元测试。已经特别小心地确保分析器的稳定性,不管你扔什么文本给它。其中的一项单元测试用了随机产生100,000个XML文档错误,RapidXml也能创建它(还未更正)。当这些错误被引入后RapidXml还是能通过这项测试,并且不会被破坏和进入死循环。
其它的单元测试也和RapidXMl进行肉搏,稳定的XML分析器,和其它为了符合一大推文档和功能的测试。
另外RapidXml进行了1000多个W3C兼容性文件测试,并且都能取得正确的结果。其它的测试包括对API函数的测试,并且测试各种各样的分析模式。
1.8 Acknowledgements(致谢)
I would like to thank Arseny Kapoulkine for his work on pugixml (http://code.google.com/p/pugixml), which was an inspiration for this project. Additional thanks go to Kristen Wegner for creating pugxml (http://www.codeproject.com/soap/pugxml.asp), from which pugixml was derived. Janusz Wohlfeil kindly ran RapidXml speed tests on hardware that I did not have access to, allowing me to expand performance comparison table.
2. Two Minute Tutorial(两分钟教程)
2.1 Parsing(分析)
下面的代码示范了RapidXml分析一个以0结尾的字符串text:
using namespace rapidxml;
xml_document<> doc; // 字符类型默认为char
doc.parse<0>(text); // 0表示默认分析标记
doc对象现在是一个DOM树,代表已分析的XML。因为所有的RadpidXml接口都包含在命名空间 rapidxml中,用户要么加入此命令空间,要么使用完整的命名(译注:即要使用doc必须使用 rapidxml::xml_docment<> doc),类xml_document代表一个DOM层次的根节点。除了是公共的继承点,它还是一个xml_node 和memory_pool。xml_document::parse()函数的模板参数是用来指定分析标记的,它可以用来对分析器进行微调。但要注意,标记必须是一个编译器的常数。
2.2 Accessing The DOM Tree(访问DOM树)
要访问DOM树,使用xml_node和xml_attribute类中的方法即可:
cout << "Name of my first node is: " << doc.first_node()->name() << "
";
xml_node<> *node = doc.first_node("foobar");
cout << "Node foobar has value " << node->value() << "
";
for (xml_attribute<> *attr = node->first_attribute();
attr; attr = attr->next_attribute())
{
cout << "Node foobar has attribute " << attr->name() << " ";
cout << "with value " << attr->value() << "
";
}
2.3 Modifying The DOM Tree(修改DOM树)
分析器生成的DOM树完全可以修改。可以增加和删除节点和属性,还有他们的内容。下面的例子创建了一个HTML文档,并有个单独的链接到google.com网站:
xml_document<> doc;
xml_node<> *node = doc.allocate_node(node_element, "a", "Google");
doc.append_node(node);
xml_attribute<> *attr = doc.allocate_attribute("href", "google.com");
node->append_attribute(attr);
其中一个特点是节点和属性都不拥有它们的名称和值。这是因为通常它们都只保存指向源文本的指针。所以,当关联一个新的名称或值给节点时,要特别小心字符串的生命周期。最简单的方法是通过xml_document内存池来分配字符串。在上面的例子中其实是不需要的,因为我们只关联了字符常量。但是下面的例子就使用memory_pool::allocate_string() (http://rapidxml.sourceforge.net/manual.html#classrapidxml_1_1memory__pool_69729185bc59b0875192d667c47b8859_169729185bc59b0875192d667c47b8859)函数来分配节点名称(拥有与文档一样的生命周期),并将它关联到一个新的节点:
xml_document<> doc;
char *node_name = doc.allocate_string(name); // 分配字符串并将名称复制进去
xml_node<> *node = doc.allocate_node(node_element, node_name); // 为 node_name设置节点名称
查看 参考 小节可看到这个接口的描述。
2.4 Printing XML(打印XML)
你可以将xml_document和xml_node对象输出为一个XML字符串。使用print()函数或操作符 operator<<,在 rapidxml_print.hpp (http://rapidxml.sourceforge.net/manual.html#rapidxml__print_8hpp) 头文件中可找到这些定义。
using namespace rapidxml;
xml_document<> doc; // 字符类型默认为 char
// ... 一些填充文档的代码
// 使用标准字符流operator <<
std::cout << doc;
// 用print函数来输出流,指定了打印参数
print(std::cout, doc, 0); // 0 表示默认
// 通过输出迭代器来打印字符串
std::string s;
print(std::back_inserter(s), doc, 0);
// 通过输出迭代器来输出到内存缓存里
char buffer[4096]; // 你必须保证缓存足够大!
char *end = print(buffer, doc, 0); // end包含了指向最后一个字符的指针
*end = 0; // 在XML后面添加字符终止符
3. Differences From Regular XML Parsers(与常规XML分析器的不同之处)
RapidXml是一个原位分析器,能提供非常快的分析速度。原位的意思是分析器并不做字符串的拷贝,而是在DOM层次中放置指向源文本的指针。
3.1 Lifetime Of Source Text(源文本的生命周期)
原位分析需要源文本的生命周期与XML文档对象一样长。如果源文本销毁了,DOM树里的节点名称和值也会跟着销毁,而且,空白符处理,字符集转换,和0结尾的字符串都需要在分析期间被修改(但不是破坏性的)。这就使得Rapidxml进行下一步分析时字符串不再有效了。
但是在大多数情况下,这些都不是严重的问题。
3.2 Ownership Of Strings(字符串的所有权)
Rapidxml生成的节点和属性都不拥有它们自己的名称和值字符串。它们只是保留了指向名称和值的指针。也就是说,当你用xml_base::name(const Ch *) (http://rapidxml.sourceforge.net/manual.html#classrapidxml_1_1xml__base_e099c291e104a0d277307fe71f5e0f9e_1e099c291e104a0d277307fe71f5e0f9e) 或 xml_base::value(const Ch *) (http://rapidxml.sourceforge.net/manual.html#classrapidxml_1_1xml__base_18c7469acdca771de9b4f3054053029c_118c7469acdca771de9b4f3054053029c) 函数时必须非常小心的设置这些值。要小心保证传递进来的字符串拥有与节点和属性一样长的生命周期。也可以简单地通过使用文档的内存池来分配这些字符串。使用memory_pool::allocate_string() (http://rapidxml.sourceforge.net/manual.html#classrapidxml_1_1memory__pool_69729185bc59b0875192d667c47b8859_169729185bc59b0875192d667c47b8859)函数可达到此目的。
3.3 Destructive Vs Non-Destructive Mode(破坏模式和非破坏模式)
默认情况下,当分析器进行分析处理时会修改源文本。这时因为需要做字符集的转换、空白符常规化、字符串0终止。
可能在某些情况下这个行为不好,例如源文本在一个只读的内存中,或者是从文件中直接映射过来的。通过使用适当的分析标记(parse_non_destructive),源文本修改模式可以被关闭。但是,因为Rapidxml做了原位分析,该标记明显有下列的副作用:
· 不作空白符的正常化处理
· 不作实体引用转换
· 名称和值都是非0终止的,你必须使用xml_base::name_size() (http://rapidxml.sourceforge.net/manual.html#classrapidxml_1_1xml__base_0dae694c8f7e4d89f1003e2f3a15a43c_10dae694c8f7e4d89f1003e2f3a15a43c) 和 xml_base::value_size() (http://rapidxml.sourceforge.net/manual.html#classrapidxml_1_1xml__base_aed5ae791b7164c1ee5e649198cbb3db_1aed5ae791b7164c1ee5e649198cbb3db)函数来告诉他们结束。