Validate XML using Windows DOM and TXMLDocument: doesn't work on some computers

后端 未结 2 1713
逝去的感伤
逝去的感伤 2021-01-01 08:01

I have some Delphi code to read and validates XML files based on an XSD document. I am using using Windows DOM (TMXLDocument). This Article explains the underlying logic.<

2条回答
  •  情话喂你
    2021-01-01 08:17

    TXMLDocument does not directly support enabling XSD validation when using MSXML, so it is MSXML's responsibility to manage it. Enabling the poResolveExternals and poValidateOnParse flags is important for that, but there are some other factors to consider. Most importantly, although MSXML does support referencing an XSD from within the XML, it has some limitations on whether the referenced XSD will actually be used while loading the XML:

    Referencing XSD Schemas in Documents

    To reference an XML Schema (XSD) schema from an XML document in MSXML 6.0, you can use any one of the following means to link a schema to an XML document so that MSXML will use the schema to validate the document contents.

    • Reference the XSD schema in the XML document using XML schema instance attributes such as either xsi:schemaLocation or xsi:noNamespaceSchemaLocation.

    • Add the XSD schema file to a schema cache and then connect that cache to the DOM document or SAX reader, prior to loading or parsing the XML document.

    ...

    The xsi:schemaLocation attribute works well in situations where namespace prefixes are explicitly declared and used in the XML document you want to validate.

    The following example shows an XML document that references an external XSD schema, MyData.xsd for us in validating nodes that are in the 'urn:MyData' namespace URI , which is mapped to the "MyData:" namespace prefix.

    
         Presenting XML
         Richard Light
      
    

    In order for the MyData.xsd file to be paired with and used you to validate elements and attribute nodes that start with the "MyData:", the schema needs to use and contain the following schema attributes:

    
    

    These attributes declare the 'urn:MyData' namespace URI and the "MyData:" namespace prefix so that they correspond identically to how these declarations were made in the XML file. If they do not match, the schema at the specified location would never be invoked during validation.

    You have not shown your XSD yet, but the XML you have shown does not conform to the rules mentioned in the above documentation. In particular, you are missing the use of a urn namespace mapping, and prefixes on the XML nodes that you want to validate. Some versions of MSXML might handle this better than others, which could explain why the validation works on some machines and is ignored on other machines, depending on which versions of MSXML are installed.

    That being said, you may have to resort to the second approach mentioned in the documentation:

    • Add the XSD schema file to a schema cache and then connect that cache to the DOM document or SAX reader, prior to loading or parsing the XML document.

    That requires using MSXML directly, you can't do it with TXMLDocument:

    MSXML also provides a means to connect and use a schema cache to store, load and connect a schema to an XML document, such as in the following VBScript code excerpt:

    'Create the schema cache and add the XSD schema to it.
    set oSC = CreateObject("MSXML2.XMLSchemaCache.6.0")
    oSC.Add "urn:MyData", "http://www.example.com/MyData.xsd"
    'Create the DOM document assign the cache to its schemas property.
    set oXD = CreateObject("MSXML2.DOMDocument.6.0")
    oXD.schemas = oSC
    'Set properties, load and validate it in the XML DOM.
    

    The gotcha is that you have to know where the XSD is located in order to hook it up to the parser. So, you would have to load the XML once just to extract the XSD location, then load the XSD into an schema cache, and then re-load the XML with the XSD attached. Here are some Delphi examples of that:

    schema validation with msxml in delphi

    function TForm1.ValidXML2(const xmlFile: String;
      out err: IXMLDOMParseError): Boolean;
    var
      xml, xml2, xsd: IXMLDOMDocument2;
      schemas, cache: IXMLDOMSchemaCollection;
    begin
      xml := CoDOMDocument.Create;
      if xml.load(xmlFile) then
      begin
        schemas := xml.namespaces;
        if schemas.length > 0 then
        begin
          xsd := CoDOMDocument40.Create;
          xsd.Async := False;
          xsd.load(schemas.namespaceURI[0]);
          cache := CoXMLSchemaCache40.Create;
          cache.add(schemas.namespaceURI[1], xsd);
          xml2 := CoDOMDocument40.Create;
          xml2.async := False;
          xml2.schemas := cache;
          Result := xml2.load(xmlFile);
          //err := xml.validate;
          if not Result then
            err := xml2.parseError
          else
            err := nil;
        end;
      end;
    end;
    

    How to validate a IXMLDocument against a XML Schema?

    unit XMLValidate;
    
    // Requirements ----------------------------------------------------------------
    //
    // MSXML 4.0 Service Pack 1
    // http://www.microsoft.com/downloads/release.asp?releaseid=37176
    //
    // -----------------------------------------------------------------------------
    
    interface
    
    uses
      SysUtils, XMLIntf, xmldom, XMLSchema;
    
    type
      EValidateXMLError = class(Exception)
      private
        FErrorCode: Integer;
        FReason: string;
      public
        constructor Create(AErrorCode: Integer; const AReason: string);
        property ErrorCode: Integer read FErrorCode;
        property Reason: string read FReason;
      end;
    
    procedure ValidateXMLDoc(const Doc: IDOMDocument; const SchemaLocation, SchemaNS: WideString); overload;
    procedure ValidateXMLDoc(const Doc: XMLIntf.IXMLDocument; const SchemaLocation, SchemaNS: WideString); overload;
    procedure ValidateXMLDoc(const Doc: IDOMDocument; const Schema: IXMLSchemaDoc); overload;
    procedure ValidateXMLDoc(const Doc: XMLIntf.IXMLDocument; const Schema: IXMLSchemaDoc); overload;
    
    implementation
    
    uses
      Windows, ComObj, msxmldom, MSXML2_TLB;
    
    resourcestring
      RsValidateError = 'Validate XML Error (%.8x), Reason: %s';
    
    { EValidateXMLError }
    
    constructor EValidateXMLError.Create(AErrorCode: Integer; const AReason: string);
    begin
      inherited CreateResFmt(@RsValidateError, [AErrorCode, AReason]);
      FErrorCode := AErrorCode;
      FReason := AReason;
    end;
    
    { Utility routines }
    
    function DOMToMSDom(const Doc: IDOMDocument): IXMLDOMDocument2;
    begin
      Result := ((Doc as IXMLDOMNodeRef).GetXMLDOMNode as IXMLDOMDocument2);
    end;
    
    function LoadMSDom(const FileName: WideString): IXMLDOMDocument2;
    begin
      Result := CoDOMDocument40.Create;
      Result.async := False;
      Result.resolveExternals := True; //False;
      Result.validateOnParse := True;
      Result.load(FileName);
    end;
    
    { Validate }
    
    procedure InternalValidateXMLDoc(const Doc: IDOMDocument; const SchemaDoc: IXMLDOMDocument2; const SchemaNS: WideString);
    var
      MsxmlDoc: IXMLDOMDocument2;
      SchemaCache: IXMLDOMSchemaCollection;
      Error: IXMLDOMParseError;
    begin
      MsxmlDoc := DOMToMSDom(Doc);
      SchemaCache := CoXMLSchemaCache40.Create;
      SchemaCache.add(SchemaNS, SchemaDoc);
      MsxmlDoc.schemas := SchemaCache;
      Error := MsxmlDoc.validate;
      if Error.errorCode <> S_OK then
        raise EValidateXMLError.Create(Error.errorCode, Error.reason);
    end;
    
    procedure ValidateXMLDoc(const Doc: IDOMDocument; const SchemaLocation, SchemaNS: WideString);
    begin
      InternalValidateXMLDoc(Doc, LoadMSDom(SchemaLocation), SchemaNS);
    end;
    
    procedure ValidateXMLDoc(const Doc: XMLIntf.IXMLDocument; const SchemaLocation, SchemaNS: WideString);
    begin
      InternalValidateXMLDoc(Doc.DOMDocument, LoadMSDom(SchemaLocation), SchemaNS);
    end;
    
    procedure ValidateXMLDoc(const Doc: IDOMDocument; const Schema: IXMLSchemaDoc);
    begin
      InternalValidateXMLDoc(Doc, DOMToMSDom(Schema.DOMDocument), '');
    end;
    
    procedure ValidateXMLDoc(const Doc: XMLIntf.IXMLDocument; const Schema: IXMLSchemaDoc);
    begin
      InternalValidateXMLDoc(Doc.DOMDocument, DOMToMSDom(Schema.DOMDocument), '');
    end;
    
    end.
    

    Doc := LoadXMLData(XmlFileEdit.Lines.Text);
    ValidateXMLDoc(Doc, FSchemaFileName, 'http://www.foo.com');
    

    XML Documents, Schemas and Validation

    var
      XML, XSDL: Variant;
    begin
      XSDL := CreateOLEObject('MSXML2.XMLSchemaCache.4.0');
      XSDL.validateOnLoad := True;
      XSDL.add('','MySchema.xsd'); // 1st argument is target namespace
      ShowMessage('Schema Loaded');
      XML := CreateOLEObject('MSXML2.DOMDocument.4.0');
      XML.validateOnParse := True;
      XML.resolveExternals := True;
      XML.schemas := XSDL;
      XML.load('file.xml');
      ShowMessage(XML.parseError.reason);
    end.
    

提交回复
热议问题