一个简单的 XML 验证器,使用 VOLE





5.00/5 (18投票s)
2007年4月11日
7分钟阅读

63672

649
一个简单的命令行实用程序,用于验证 XML 文件,使用 VOLE COM/Automation 驱动程序库通过 MSXML 实现
引言
本文介绍 xmlValidator,一个简单的命令行实用程序,它使用 MSXML 验证 XML 文件。它使用 VOLE C++/COM Automation 驱动程序库编写,这使得应用程序非常简洁,并且不受特定编译器(供应商)的限制。
背景
最近在与一位客户合作时,我们需要验证数百个 XML 文件的正确性,以验证企业 Java 系统的配置。自然,我们不想手动完成这项工作,所以我只用了几分钟时间就写出了本文介绍的工具 xmlValidator,然后我们就可以批量运行验证了。
该应用程序功能非常简单:打开并解析作为其唯一命令行参数传入的 XML 文件。如果 XML 文件可以打开且有效,应用程序将遵循“沉默规则”,不做任何操作,并返回 EXIT_SUCCESS
。如果 XML 文件无法打开,或者不是 XML,或者存在错误,那么应用程序将发出错误描述及其位置,并返回 EXIT_FAILURE
。
因为我们使用的是 Windows 系统,所以我们决定使用 Microsoft 的 XML 解析器 MSXML。MSXML 是一个 COM 组件。从 C++ 程序中使用它有很多种方法。如果你想自找麻烦,可以直接编程操作 COM 接口——但这非常耗时。使用一个包装库会容易得多。VOLE 就是这样一个库,它是一个独立的、开源的项目,我今年早些时候发布的。通过使用 VOLE,我们只用了六行代码就完成了所有 COM 操作——创建 MSXML XML 文档对象,让它解析 XML 文件,并从中提取解析错误信息。
object xmlDocument = object::create("Msxml2.DOMDocument");
bool success = xmlDocument.invoke_method<bool>(L"load", argv[1]);
object parseError = xmlDocument.get_property<object>(L"ParseError");
std::string reason = parseError.get_property<std::string>(L"reason");
long line = parseError.get_property<long>(L"line");
long linePos = parseError.get_property<long>(L"linepos");
实现
应用程序结构
应用程序的基本结构如下:
int main(int argc, char** argv)
{
策略
- 检查参数
- 初始化 COM 库
- 声明要“使用”的 VOLE 组件
- 创建 MSXML 文档对象的实例
- 加载 XML
- 如果成功,则向外部返回成功代码
- 如果失败,则提取解析错误详细信息并显示
. . . }
包含
当然,我们需要确保我们包含了所有必需的 #include
。除了包含主 VOLE 头文件 vole/vole.hpp,我们还包含了来自 STLSoft
库的两个组件的头文件,以及必需的标准 C 和 C++ 头文件。
// VOLE Header Files
#include <vole/vole.hpp> // for VOLE
// STLSoft Header Files
#include <comstl/util/initialisers.hpp> // for comstl::com_initialiser;
#include <winstl/error/error_desc.hpp> // for winstl::error_desc
// Standard C++ Header Files
#include <iostream> // for std::cout, std::cerr,
// std::endl;
#include <string> // for std::string
// Standard C Header Files
#include <stdlib.h> // For EXIT_SUCCESS,
EXIT_FAILURE
步骤 1:检查参数
这部分内容非常模板化。
if(2 != argc)
{
std::cerr << "USAGE: xmlValidator <xml-file>" << std::endl;
}
else try
{
. . . // main functioning
}
catch( . . . )
{
. . . // error handling
}
步骤 2:初始化 COM 库
这是通过 STLSoft
组件中的第一个组件 com_initialiser
完成的,它来自 COMSTL 子项目。正如注释中所解释的,它确保 COM 库的初始化和反初始化得到妥善处理。
这是通过创建 comstl::com_initialiser
组件的本地实例来完成的,它内部使用了 RAII 来在构造函数中初始化 COM 库,并在析构函数中反初始化它们(如果成功初始化)。
comstl::com_initialiser coinit; // Initialise COM, via RAII
步骤 3:声明要使用的 VOLE 组件
与大多数 C++ 库一样,VOLE 组件定义在 vole
命名空间内。我们使用“using 声明”来节省我们(和手指)不得不为每个 VOLE 组件的使用添加前缀的眼部(和手指)负担。
VOLE 提供的两个主要公共类型是 object
和 collection
。vole::object
是 COM 服务器的通用包装器。vole::collection
是 COM “集合”的通用包装器,并提供与 STL 兼容的迭代器来枚举集合的元素。在此实用程序中不需要 vole::collection
,但我计划撰写一篇后续文章来演示其用法。如果你等不及,可以随时在此 查看示例。
using vole::object;
using vole::of_type; // Only required by "old" compilers, such as VC6
vole::of_type
是一个辅助函数模板,供有问题的旧编译器(即 VC++ 6)使用,这些编译器无法处理所有现代编译器都可以使用的标准 VOLE 语法。这将在接下来的几节中进行解释。我们将使用预处理器符号 XMLVALIDATOR_USE_OLD_SYNTAX
来区分以下代码中显示的两种形式,该符号定义如下:
#if defined(STLSOFT_COMPILER_IS_MSVC) && \
_MSC_VER == 1200
# define XMLVALIDATOR_USE_OLD_SYNTAX
#endif /* compiler */
步骤 4:创建 MSXML 服务器并进行包装。
这很简单,使用静态方法 vole::object::create()
,如下所示:
创建 MSXML 文档对象的实例。我们使用静态 object::create()
方法,该方法可以接受 CLSID、CLSID 的字符串形式,或者像本例中这样的 ProgId。如果失败,将抛出 vole_exception。
object xmlDocument = object::create("Msxml2.DOMDocument");
此方法有三个重载,允许通过 CLSID、ProgId 或 CLSID 的字符串形式(即 "{F6D90F11-9C73-11D3-B32E-00C04F990BB4}"
)进行创建。每个方法都有两个附加的默认参数,你可以使用它们来指定创建上下文(例如 CLSCTX_ALL
)和“强制转换级别”——即将从 Automation 类型 VARIANT
返回的值强制转换为 C++ 类型的程度。这两者在本文中都不会进一步讨论。
步骤 5:加载 XML
这又是一个非常简单的操作,只需一行代码:bool success =
xmlDocument.invoke_method<bool>(L"load", argv[1]);
不幸的是,对于 Visual C++ 6.0 的用户来说,这种语法会让编译器崩溃。这时 vole::of_type()
函数模板就派上用场了。它仅用于为 vole::object::invoke_method
和 vole::object::get_property
方法模板提供类型提示。因此,步骤 5 的实际代码如下:
#ifdef XMLVALIDATOR_USE_OLD_SYNTAX
bool success = xmlDocument.invoke_method(of_type<bool>(), L"load", argv[1]);
#else /* ? XMLVALIDATOR_USE_OLD_SYNTAX */
bool success = xmlDocument.invoke_method<bool>(L"load", argv[1]);
#endif /* XMLVALIDATOR_USE_OLD_SYNTAX */
步骤 6:解析成功
这非常简单。我们只需返回 EXIT_SUCCESS
。
if(success)
{
return EXIT_SUCCESS;
}
步骤 7:解析失败
这是我们论述的重点。如果失败,我们需要从 XML 文档实例中提取 ParseError 项目(它也是一个自动化对象),然后从中提取错误的详细信息。所有值都通过 vole::object::get_property
方法模板从属性中获取。
else
{
object parseError = xmlDocument.get_property<object>(L"ParseError");
std::string reason = parseError.get_property<std::string>(L"reason");
long line = parseError.get_property<long>(L"line");
long linePos = parseError.get_property<long>(L"linepos");
std::cout << "Parse error at (" << line << ", " << linePos << "): " << reason <<
std::endl;
}
VOLE 提供对返回其他 COM 对象(以 vole::object
形式)和大多数常见 C++ 类型(包括 long
和 std::string
)的支持,因此以上所有代码都可以正常工作。如果你希望获取不支持的类型,可以专门化 vole::com_return_traits
特征类模板;这超出了本文的讨论范围,但将在未来的文章中介绍。
就像我们在方法调用中看到的,上面显示的语法会导致 Visual C++ 出现问题,但有一种替代语法适用于所有编译器。因此,应用程序步骤 6 的实际代码包含以下内容:
else
{
#ifdef XMLVALIDATOR_USE_OLD_SYNTAX
object parseError = xmlDocument.get_property(of_type<object>(), L"ParseError");
std::string reason = parseError.get_property(of_type<std::string>(), L"reason");
long line = parseError.get_property(of_type<long>(), L"line");
long linePos = parseError.get_property(of_type<long>(), L"linepos");
#else /* ? XMLVALIDATOR_USE_OLD_SYNTAX */
object parseError = xmlDocument.get_property<object>(L"ParseError");
std::string reason = parseError.get_property<std::string>(L"reason");
long line = parseError.get_property<long>(L"line");
long linePos = parseError.get_property<long>(L"linepos");
#endif /* XMLVALIDATOR_USE_OLD_SYNTAX */
std::cout << "Parse error at (" << line << ", " << linePos << "): " << reason << std::endl;
}
处理错误
由于 VOLE 从其(方法和属性)函数返回对象和值,因此它通过抛出异常来指示错误,这些异常派生自 vole::vole_exception
。因此,应用程序的最后一部分包含两个 catch 子句,如下所示:
catch(vole::vole_exception &x)
{
std::cerr << "Validation failed: " << x.what() << ": " << winstl::basic_error_desc<char>(x.hr()) << std::endl;
}
catch(std::exception &x)
{
std::cerr << "Validation failed: " << x.what() << std::endl;
}
// All other code paths lead to a failure (non-0) return code
return EXIT_FAILURE;
第二个是通用子句,它将捕获所有标准异常,包括 std::bad_alloc
。第一个更有趣。它捕获 vole::vole_exception
,它派生自 COMSTL 异常类 comstl::com_exception
,该类有一个 hr()
访问器,用于返回与错误关联的 COM 错误代码(HRESULT
)。然后将其与 ANSI/多字节 WinSTL 类模板 basic_error_desc
一起使用,该模板是 Win32 API 函数 FormatString()
的一个帮助器 Facade。例如,如果我们更改 ProgId,例如 Msxml99999.DOMDocument,程序将打印出:
验证失败:无法创建 coclass:800401f3,无效类字符串
这比
验证失败:无法创建 coclass
要有用得多。注意:VOLE 是一个新且仍在开发中的库,我尚未完成丰富的异常层次结构的实际实现。因此,所有 VOLE 异常类型
vole::vole_exception
vole::creation_exception
vole::invocation_exception
vole::type_conversion_exception
目前都只是 comstl::com_exception
的别名,所以暂时不要尝试涉及不同 VOLE 异常类型的多个 catch 子句。(当然,如果你想为项目做出贡献来推动这个或其他任何剩余问题,我们非常欢迎你 前往此处。)
设置环境
要构建程序,你需要能够访问 VOLE 和 STLSoft 库。两者都是开源的。两者都使用 修改后的 BSD 许可证。VOLE 的最新版本是 0.2.2,它需要 STLSoft 版本 1.9.1 beta 47 或更高版本。
由于这两个库都是 100% 只包含头文件,因此设置它们的用途只需要下载并设置相应的环境变量。我建议设置环境变量 VOLE
(例如,VOLE=C:\ThirdPartyLibs\VOLE\vole-0.2.2)和 STLSOFT
(例如,STLSOFT=C:\ThirdPartyLibs\STLSoft\stlsoft-1.9.1-beta47)。然后,你可以将包含路径 VOLE/include
和 STLSOFT/include
包含到你的项目设置中,例如:
或者在命令行中,例如:
C:\ThirdPartyTools\xmlValidator>cl -nologo -EHsc
-I%VOLE%/include -I%STLSOFT%/include -DWIN32 -D_CRT_SECURE_NO_DEPRECATE
..\xmlValidator.cpp ole32.lib oleaut32.lib
使用 xmlValidator 工具
以下两个简单的 XML 文件说明了该工具的使用非常简单。首先是一个格式正确的 XML 文件。
good.xml:
<?xml version="1.0"
encoding="UTF-8"?>
<good>
<no-problem-here />
</good>
现在是一个格式错误的 XML 文件。
bad.xml:
<?xml version="1.0"
encoding="UTF-8"?>
<bad>
<problem-here>
</bad>
下图显示了构建命令以及该工具对这两个 XML 文件的响应:
敬请期待更多内容……
我计划很快再写一篇关于 VOLE 的文章,重点介绍 vole::collection
类通过 COM Collection 的元素(通过 IEnumXXXX 协议)提供 STL 迭代器的能力。
欢迎您通过 VOLE 项目主页 此处 提供您对 VOLE 的评论/批评/功能请求。
欢迎您通过 STLSoft 新闻组(此处),该新闻组由提供免费高质量 C/C++/D 编译器的 Digital Mars 提供,提供您对 STLSoft 的评论/批评/功能请求。
历史
2007 年 4 月 7 日:第一个版本