【问题标题】:MSXML2.DOMDocument.6.0 "MultipleErrorMessages" Property Name is InvalidMSXML2.DOMDocument.6.0“MultipleErrorMessages”属性名称无效
【发布时间】:2020-01-09 22:55:33
【问题描述】:

我正在尝试使用 OLE 变体在 Embarcadero C++Builder 10.1 Berlin 中进行 XML 验证。我的最终目标是显示所有验证错误,而不仅仅是第一个错误(在this MSDN example 之后)。我的课在下面。当我运行以下行时,出现“属性名称无效”的异常。

FXmlDomDoc.Exec( XmlFuncSetProperty );

如果我将此行注释掉,一切都会正常运行。

这使得“MultipleErrorMessages”似乎不是 MSXML2.DOMDocument.6.0 上的 setProperty() 的有效参数。但是,当我查看 the list of Second-Level DOM Properties 时,似乎这是 6.0 XML DOM 对象的有效二级属性。

我尝试过的:

  1. 将 XmlFuncSetProperty 定义为过程而不是函数;同样的错误。

  2. 将 ValidateOnLoad / ValidateOnParse 设置为 false,以防万一它们以某种方式影响了这一点;同样的错误。

  3. 使用 _di_IXMLDOMDocument3、_di_IXMLDOMSchemaCollection2、_di_IXMLDOMParseError 重写类。我在这些类中找不到对多个错误的任何支持。我确实在其他几个类中找到了我需要的函数,但它们是纯虚拟的。

问题:

  • 我在这里缺少什么?为什么会出现这个错误?

  • 在 C++ Builder 中是否有更好的方法来执行此操作?

.cpp 文件:

//------------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
//------------------------------------------------------------------------------
#include "XmlValidatorU.h"
#include <System.Win.ComObj.hpp>
//------------------------------------------------------------------------------
#pragma package(smart_init)
//------------------------------------------------------------------------------
// Validates XML against Schema
//------------------------------------------------------------------------------
// This class uses OLE objects from MSXML2 to validate XML from an XSD file.
// Generally, use the following steps to deal with OLE objects:
//  1. Define a Variant variable for your OLE Object; assign using CreateOleObject().
//  2. Define your TAutoCmd objects that will be used in Variant.Exec()
//  3. Set TAutoCmd args using << to add settings
//  4. Once everything is set up, call Exec() on your OLE Object variant
// More documentation on OLE objects / TAutoCmd at:
//  http://docwiki.embarcadero.com/CodeExamples/Rio/en/AutoCmd_(C%2B%2B)
//------------------------------------------------------------------------------
// This macro clarifies that we're registering OLE Function names to our defined TAutoCmd variables.
//
#define RegisterAutoCmd( _AutoCmd, _OleFunc ) _AutoCmd( _OleFunc )
//------------------------------------------------------------------------------
// These macros clear AutoCmdArgs before setting them.
// I made these because setting an arg multiple times just stacks them up, changing the function signature.
// Then, OLE throws a "Member Not Found" error because it can't find a function with that signature.
//
#define AutoCmdArg( _AutoCmd, _Arg ) _AutoCmd.ClearArgs(); _AutoCmd << _Arg
#define AutoCmdArgs( _AutoCmd, _Arg1, _Arg2 ) AutoCmdArg( _AutoCmd, _Arg1 ); _AutoCmd << _Arg2
//------------------------------------------------------------------------------
__fastcall TXmlValidator::TXmlValidator( String _SchemaLocation )
    :
    RegisterAutoCmd( CacheProcAdd,              "add"               ),
    RegisterAutoCmd( CacheSetValidateOnLoad,    "validateOnLoad"    ),
    RegisterAutoCmd( XmlProcLoadXml,            "loadXML"           ),
    RegisterAutoCmd( XmlFuncSetProperty,        "setProperty"       ),
    RegisterAutoCmd( XmlSetValidateOnParse,     "validateOnParse"   ),
    RegisterAutoCmd( XmlSetResolveExternals,    "resolveExternals"  ),
    RegisterAutoCmd( XmlSetSchemas,             "schemas"           ),
    RegisterAutoCmd( XmlGetParseError,          "parseError"        ),
    RegisterAutoCmd( ParseErrorGetReason,       "reason"            )
{
    if ( _SchemaLocation.IsEmpty() )
    {
        FInvalidMsg = "No Schema Location Specified";
    }
    else if ( ! FileExists( _SchemaLocation ) )
    {
        FInvalidMsg = "Schema File Does Not Exist: " + _SchemaLocation;
    }
    else
    {
        FInvalidMsg = "";
    }

    if ( FInvalidMsg.Length() > 0 )
    {
        return;
    }

    // Instantiate the OLE objects
    FSchemaCache    = CreateOleObject( "MSXML2.XMLSchemaCache.6.0"  );
    FXmlDomDoc      = CreateOleObject( "MSXML2.DOMDocument.6.0"     );

    // Set static args that shouldn't change
    AutoCmdArg( CacheSetValidateOnLoad, true );
    AutoCmdArg( XmlSetValidateOnParse,  true );
    AutoCmdArg( XmlSetResolveExternals, true );

    AutoCmdArgs( XmlFuncSetProperty, "MultipleErrorMessages", true );

    const AnsiString NoNameSpace = "";
    AutoCmdArgs( CacheProcAdd, NoNameSpace, AnsiString( _SchemaLocation ) );

    // Load Cache
    FSchemaCache.Exec( CacheSetValidateOnLoad   );  // Validate on Load
    FSchemaCache.Exec( CacheProcAdd             );  // Add Schema file location to the cache

    // Now that the cache is loaded, set cached schema as arg to XML
    AutoCmdArg( XmlSetSchemas, FSchemaCache );

    // Set up Xml Dom doc as much as we can...
    FXmlDomDoc.Exec( XmlSetValidateOnParse  );
    FXmlDomDoc.Exec( XmlSetResolveExternals );
    FXmlDomDoc.Exec( XmlSetSchemas          );
    FXmlDomDoc.Exec( XmlFuncSetProperty     );
}
//------------------------------------------------------------------------------
String __fastcall TXmlValidator::ValidationError( String _Xml )
{
    if ( FInvalidMsg.Length() > 0 )
    {
        return FInvalidMsg;
    }

    AutoCmdArg( XmlProcLoadXml, AnsiString( _Xml ) );       // update the XML for re-parsing

    FXmlDomDoc.Exec( XmlProcLoadXml );                      // Load the doc from the XML

    Variant ParseErr = FXmlDomDoc.Exec( XmlGetParseError ); // Get the parseError object

    return ParseErr.Exec( ParseErrorGetReason );            // Extract the reason
}
//------------------------------------------------------------------------------

.h 文件:

//------------------------------------------------------------------------------
#ifndef XmlValidatorUH
#define XmlValidatorUH
//------------------------------------------------------------------------------
class PACKAGE TXmlValidator
{
private:
    String  FInvalidMsg;

    // OLE Variant Variables
    Variant FSchemaCache;
    Variant FXmlDomDoc;

    // TAutoCmd Variables
    Procedure   CacheProcAdd;
    PropertySet CacheSetValidateOnLoad;
    Procedure   XmlProcLoadXml;
    Function    XmlFuncSetProperty;
    PropertySet XmlSetValidateOnParse;
    PropertySet XmlSetResolveExternals;
    PropertySet XmlSetSchemas;
    PropertyGet XmlGetParseError;
    PropertyGet ParseErrorGetReason;

public:
    __fastcall TXmlValidator( String _SchemaLocation );

    String __fastcall ValidationError( String _Xml );

};
//------------------------------------------------------------------------------
#endif

【问题讨论】:

  • 我不知道为什么它说MultipleErrorMessages 不是有效属性。这意味着您将 MultipleErrorMessages 设置为 OLE 对象本身的属性,而不是调用 setProperty() 并将 "MultipleErrorMessages" 作为参数传递。您的代码建议后者。更糟糕的情况是,您可能需要使用变体的OleProcedure() 方法来调用setProperty,例如:FXmlDomDoc.OleProcedure(_D("setProperty"), WideString(L"MultipleErrorMessages"), true); 此外,您应该避免在OLE 代码中使用char* 字符串,它不能很好地工作。请改用wchar_t*WideString
  • 非常感谢,@RemyLebeau!再一次,你救了我的培根。我真的很感谢你的辛勤工作和慷慨。你有 PayPal 或 Patreon 什么的吗? :-)

标签: c++builder domdocument vcl ole


【解决方案1】:

感谢 Remy 的出色建议,我在调试模式下完成了这项工作。但是,我发现 AutoCmd 在使用此代码的发布版本中导致访问冲突。因此,为了完整起见,我在下面包含了我的最终答案(适用于调试和发布)。它为 OLEVariant 方法放弃 AutoCmd。

.cpp 文件:

//------------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
//------------------------------------------------------------------------------
#include "XmlValidatorU.h"
#include <System.Win.ComObj.hpp>
//------------------------------------------------------------------------------
#pragma package(smart_init)
//------------------------------------------------------------------------------
// This class uses OLE objects from MSXML2 to validate XML from an XSD file.
// Generally, use the following steps to deal with OLE objects:
//  1. Define a Variant variable for your OLE Object; assign using CreateOleObject()
//  2. Use Variant.OlePropertySet(), OlePropertyGet(), OlePropertySet(), OleProcedure(),
//      and OleFunction() to do actions on your OLE objects
//  3. Previously, I had this working with TAutoCmd arguments, which worked great
//      in Debug, but then yielded a mysterious Access Violation in Release build.
//
// Even though it poops its pants in Release, here's documentation of Embarcadero's AutoCmd solution:
//  http://docwiki.embarcadero.com/CodeExamples/Rio/en/AutoCmd_(C%2B%2B)
//
// See MSDN tutorial for allErrors here: https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ms754649%28v%3dvs.85%29
//
//------------------------------------------------------------------------------
// This macro casts char* strings to WideString
#define wstr( _CharPtr ) WideString( L##_CharPtr )
//------------------------------------------------------------------------------
__fastcall TXmlValidator::TXmlValidator( String _SchemaLocation )
{
    if ( _SchemaLocation.IsEmpty() )
    {
        FInvalidMsg = "No Schema Location Specified";
    }
    else if ( ! FileExists( _SchemaLocation ) )
    {
        FInvalidMsg = "Schema File Does Not Exist: " + _SchemaLocation;
    }
    else
    {
        FInvalidMsg = "";
    }

    if ( FInvalidMsg.Length() > 0 )
    {
        return;
    }

    // First, set up the MSXML2.XMLSchemaCache.6.0 OLE object
    FSchemaCache = CreateOleObject( wstr( "MSXML2.XMLSchemaCache.6.0" ) );
    FSchemaCache.OlePropertySet( _D( "validateOnLoad" ), true );
    FSchemaCache.OleProcedure( _D( "add" ), wstr( "" ), WideString( _SchemaLocation ) );

    // Now set up the MSXML2.DOMDocument.6.0 OLE object
    FXmlDomDoc = CreateOleObject( wstr( "MSXML2.DOMDocument.6.0" ) );
    FXmlDomDoc.OlePropertySet( _D( "validateOnParse"  ),  true          );
    FXmlDomDoc.OlePropertySet( _D( "resolveExternals" ),  true          );
    FXmlDomDoc.OlePropertySet( _D( "schemas"          ),  FSchemaCache  );  // set schemas to the cache we created above
    FXmlDomDoc.OleProcedure( _D( "setProperty" ), wstr( "MultipleErrorMessages" ), true ); // secondary properties must call setProperty()
}
//------------------------------------------------------------------------------
// This function checks _ParseError for a non-zero error code.
// If found, add Reason and Location to _Result. If no Reason found, at least add Error Code.
void __fastcall TXmlValidator::AddErrorDesc( String& _Result, Variant _ParseError )
{
    long TmpErrCode = _ParseError.OlePropertyGet( _D( "errorCode" ) );

    if ( 0 == TmpErrCode )
    {
        return;
    }

    String TmpReason = _ParseError.OlePropertyGet( _D( "reason" ) );
    String TmpXPath  = _ParseError.OlePropertyGet( _D( "errorXPath" ) );

    TmpReason = TmpReason.Trim();
    TmpXPath  = TmpXPath.Trim();

    if ( TmpReason.IsEmpty() )
    {
        TmpReason = "Error Code: " + String( TmpErrCode );
    }
    else
    {
        TmpReason = "Reason: " + TmpReason;
    }

    if ( TmpXPath.Length() > 0 )
    {
        TmpXPath = StringReplace( TmpXPath, "[1]", "", TReplaceFlags() << rfReplaceAll );
        TmpXPath = "Location: " + TmpXPath;
    }

    _Result += TmpReason + "\r\n\r\n";
    _Result += TmpXPath + "\r\n";
}
//------------------------------------------------------------------------------
String __fastcall TXmlValidator::ValidationError( String _Xml )
{
    String Result = FInvalidMsg;

    if ( Result.Length() > 0 )
    {
        return Result;
    }

    FXmlDomDoc.OleProcedure( _D( "loadXML" ), WideString( _Xml ) );

    // No need to call AddErrorDesc for TopParseErr; it duplicates the first item in allErrors
    Variant TopParseErr = FXmlDomDoc.OlePropertyGet( _D( "parseError" ) );
    Variant AllErrors   = TopParseErr.OlePropertyGet( _D( "allErrors" ) );
    int TmpAllErrLength = AllErrors.OlePropertyGet( _D( "length" ) );

    for ( int ix = 0; ix < TmpAllErrLength; ix++ ) // Iterate through allErrors
    {
        Variant TmpErrItem = AllErrors.OlePropertyGet( _D( "item" ), ix );

        AddErrorDesc( Result, TmpErrItem ); // Add error desc
    }

    return Result;
}
//------------------------------------------------------------------------------

.h 文件:

//------------------------------------------------------------------------------
#ifndef XmlValidatorUH
#define XmlValidatorUH
//------------------------------------------------------------------------------
class PACKAGE TXmlValidator
{
private:
    String  FInvalidMsg;
    Variant FSchemaCache;
    Variant FXmlDomDoc;

    void __fastcall AddErrorDesc( String& _Result, Variant _ParseError );

public:
    __fastcall TXmlValidator( String _SchemaLocation );

    String __fastcall ValidationError( String _Xml );

};
//------------------------------------------------------------------------------
#endif

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-14
    • 2017-04-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多