65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (18投票s)

2007年4月11日

7分钟阅读

viewsIcon

63672

downloadIcon

649

一个简单的命令行实用程序,用于验证 XML 文件,使用 VOLE COM/Automation 驱动程序库通过 MSXML 实现

Screenshot - LeadImage.png

引言

本文介绍 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)
{

策略

  1. 检查参数
  2. 初始化 COM 库
  3. 声明要“使用”的 VOLE 组件
  4. 创建 MSXML 文档对象的实例
  5. 加载 XML
  6. 如果成功,则向外部返回成功代码
  7. 如果失败,则提取解析错误详细信息并显示
. . .
}

包含

当然,我们需要确保我们包含了所有必需的 #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 提供的两个主要公共类型是 objectcollectionvole::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_methodvole::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++ 类型(包括 longstd::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 子句。(当然,如果你想为项目做出贡献来推动这个或其他任何剩余问题,我们非常欢迎你 前往此处。)

设置环境

要构建程序,你需要能够访问 VOLESTLSoft 库。两者都是开源的。两者都使用 修改后的 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/includeSTLSOFT/include 包含到你的项目设置中,例如:

Screenshot - xmlValidator-VC71-setting.png

或者在命令行中,例如:

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 文件的响应:

Screenshot - xmlValidator-example-useSMALL.png

敬请期待更多内容……

我计划很快再写一篇关于 VOLE 的文章,重点介绍 vole::collection 类通过 COM Collection 的元素(通过 IEnumXXXX 协议)提供 STL 迭代器的能力。

欢迎您通过 VOLE 项目主页 此处 提供您对 VOLE 的评论/批评/功能请求。

欢迎您通过 STLSoft 新闻组(此处),该新闻组由提供免费高质量 C/C++/D 编译器的 Digital Mars 提供,提供您对 STLSoft 的评论/批评/功能请求。

历史

2007 年 4 月 7 日:第一个版本

© . All rights reserved.