MSXML2.DOMDocument.6.0 Недопустимое имя свойства «MultipleErrorMessages» - PullRequest
0 голосов
/ 10 января 2020

Я пытаюсь использовать варианты OLE для проверки XML в Embarcadero C ++ Builder 10.1 Berlin. Моя конечная цель - показать все ошибки валидации вместо первой (следуя этому примеру MSDN ). Мой класс ниже. Когда я запускаю следующую строку, я получаю исключение «Имя свойства недопустимо».

FXmlDomDoc.Exec( XmlFuncSetProperty );

Если я закомментирую эту строку, все будет нормально.

Это создает впечатление, что «MultipleErrorMessages» не является допустимым аргументом для setProperty () в MSXML2.DOMDocument.6.0. Однако, когда я смотрю на список свойств DOM второго уровня , кажется, что это допустимое свойство второго уровня для объекта DOM 6.0 XML.

Что я пробовал:

  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

1 Ответ

0 голосов
/ 29 января 2020

Благодаря превосходному совету Реми, я получил это в режиме отладки. Однако я обнаружил, что AutoCmd вызвал нарушение прав доступа в сборке выпуска с этим кодом. Поэтому для полноты я включил свой окончательный ответ ниже (который работает как в Debug, так и в Release). Он отказывается от AutoCmd для методов OLEVariant.

. cpp file:

//------------------------------------------------------------------------------
#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 file:

//------------------------------------------------------------------------------
#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
...