最近在项目中发现, RapidXML 虽然使用了模板用于支持 wchar_t 宽字符. 但实际上, 其对于宽字符的处理存在着巨大的瑕疵, 甚至可能导致整个 XML 文件的解析失败. 本文将给出其原因分析及解决办法.
写在文章开头, RapidXML 从 2009 年开始就已经不再更新, 建议如果没有特殊需求不要再使用 RapidXML; 其他常见的、还在保持更新的 XML 库有TinyXml 、PugiXml 等, 个人比较推荐后者, 接口使用非常简单, 对 Unicode 支持良好, 性能也是非常的优秀, 官方给出了如下的性能对比数据:
如果不幸项目中已经使用了 RapidXML 且暂时无法切换到其他库, 需要特别小心地处理 wchar_t 宽字符和中文带来的问题, RapidXML 虽然使用模板且可以传入 wchar_t 来支持宽字符的处理, 但是其程序本身设计相当不完善, 导致会出现解析错误. 例如, 当 XML 属性中出现如“开关”之类的中文词语时, RapidXML 解析时会直接抛出异常 expected ‘ or “.
显然, RapidXml 将“开关”之类的中文字符判定为包含引号的字符串, 导致整个 XML 文件中的引号不能完全配对. 而问题出在下面这段代码上:
// Detect attribute value character
template
struct attribute_value_pred
{
static unsigned char test(Ch ch)
{
if (Quote == Ch('\''))
return internal::lookup_tables<0>::lookup_attribute_data_1[static_cast<unsigned char>(ch)];
if (Quote == Ch('\"'))
return internal::lookup_tables<0>::lookup_attribute_data_2[static_cast<unsigned char>(ch)];
return 0; // Should never be executed, to avoid warnings on Comeau
}
};
其中, lookup_attribute_data_x 是一个大小为 256 的数组, RapidXml 使用空间换时间的方法, 将每个字符是否是 Attribute 结束符的标志构造成了这个数组, 而运行时直接通过下标随机访问来判定 Attribute 是否结束. 但是, 上述程序中“开关”字符作为 wchar_t 类型, 其范围早已不在 256 以内, 而 RapidXml 的处理则是硬把其强制转换到 256 以内, 这种转换没有任何实际上的意义, 最终引入了误判.
修复方法为首先判定字符是否在 256 以内, 如果超出范围则为 Unicode 字符, 不可能是引号(中文引号应作为 Attribute 内容); 否则, 再执行上述的查表逻辑. 如下:
// Detect attribute value character
template
struct attribute_value_pred
{
static unsigned char test(Ch ch)
{
if (ch > 255)
return 0;
if (Quote == Ch('\''))
return internal::lookup_tables<0>::lookup_attribute_data_1[static_cast<unsigned char>(ch)];
if (Quote == Ch('\"'))
return internal::lookup_tables<0>::lookup_attribute_data_2[static_cast<unsigned char>(ch)];
return 0; // Should never be executed, to avoid warnings on Comeau
}
};
不过要注意的是, 上述方法仅仅个性了 Attribute 中中文字符的支持, 但对于其他的如 Node 等对中文支持仍有问题, 程序中需要逐一修改. 由于现在的程序大多是用 Unicode 字符集来编译的, 因此建议大家切换到其他 XML 库.