validation de schéma avec msxml dans delphi
Question
J'essaie de valider un fichier XML par rapport aux schémas qu'il référence. (Utilisation de Delphi et MSXML2_TLB.) Le code (la partie pertinente du code) ressemble à ceci:
procedure TfrmMain.ValidateXMLFile;
var
xml: IXMLDOMDocument2;
err: IXMLDOMParseError;
schemas: IXMLDOMSchemaCollection;
begin
xml := ComsDOMDocument.Create;
if xml.load('Data/file.xml') then
begin
schemas := xml.namespaces;
if schemas.length > 0 then
begin
xml.schemas := schemas;
err := xml.validate;
end;
end;
end;
Cela a pour résultat que le cache est chargé ( schemas.length > 0
), mais l'assignation suivante déclenche une exception: "seules les commandes XMLSchemaCache-schemacollections peuvent être utilisées."
Comment dois-je m'y prendre?
La solution
J'ai mis au point une approche qui semble fonctionner. Je commence par charger explicitement le schéma, puis je les ajoute à la collection schem. Ensuite, je charge le fichier XML et assigne la schemacollection à sa propriété schemas. La solution ressemble maintenant à ceci:
uses MSXML2_TLB
That is:
// Type Lib: C:\Windows\system32\msxml4.dll
// LIBID: {F5078F18-C551-11D3-89B9-0000F81FE221}
function TfrmMain.ValidXML(
const xmlFile: String;
out err: IXMLDOMParseError): Boolean;
var
xml, xsd: IXMLDOMDocument2;
cache: IXMLDOMSchemaCollection;
begin
xsd := CoDOMDocument40.Create;
xsd.Async := False;
xsd.load('http://the.uri.com/schemalocation/schema.xsd');
cache := CoXMLSchemaCache40.Create;
cache.add('http://the.uri.com/schemalocation', xsd);
xml := CoDOMDocument40.Create;
xml.async := False;
xml.schemas := cache;
Result := xml.load(xmlFile);
if not Result then
err := xml.parseError
else
err := nil;
end;
Il est important d’utiliser XMLSchemaCache40 ou une version ultérieure. Les versions précédentes ne respectent pas le standard XML Schema du W3C, elles ne valident que par rapport à XDR Schema, une spécification MicroSoft.
L'inconvénient de cette solution est que je dois charger explicitement le schéma. Il me semble qu’il devrait être possible de les récupérer automatiquement.
Autres conseils
Bien que BennyBechDk soit peut-être sur la bonne voie, son code me pose quelques problèmes que je vais corriger ci-dessous:
uses Classes, XMLIntf, xmlDoc, SysUtils;
function IsValidXMLDoc(aXmlDoc: IXMLDocument): boolean;
var
validateDoc: IXMLDocument;
begin
result := false; // eliminate any sense of doubt, it starts false period.
validateDoc := TXMLDocument.Create(nil);
try
validateDoc.ParseOptions := [poResolveExternals, poValidateOnParse];
validateDoc.XML := aXmlDoc.XML;
validateDoc.Active := true;
Result := True;
except
// for this example, I am going to eat the exception, normally this
// exception should be handled and the message saved to display to
// the user.
end;
end;
Si vous souhaitez que le système lève simplement l'exception, il n'y a aucune raison de le faire en premier lieu.
uses Classes, XMLIntf, XMLDoc, SysUtils;
procedure ValidateXMLDoc(aXmlDoc: IXMLDocument);
var
validateDoc: IXMLDocument;
begin
validateDoc := TXMLDocument.Create(nil);
validateDoc.ParseOptions := [poResolveExternals, poValidateOnParse];
validateDoc.XML := aXmlDoc.XML;
validateDoc.Active := true;
end;
Etant donné que validateDoc est une interface, elle sera éliminée correctement à la sortie de la fonction / de la procédure. Il n’est donc pas nécessaire de procéder à la destruction vous-même. Si vous appelez ValidateXmlDoc sans obtenir une exception, celle-ci est valide. Personnellement, j'aime bien le premier appel, IsValidXMLDoc, qui renvoie true si valide ou false sinon (et ne génère pas d'exceptions en dehors de lui-même).
J'ai travaillé sur la solution de Miel pour résoudre le problème. J'ouvre le xml deux fois, une fois pour obtenir les espaces de noms, et l'autre, après avoir créé la collection de schémas, pour valider le fichier. Ça marche pour moi. Il semble que IXMLDOMDocument2, une fois ouvert, n’accepte pas de définir la propriété schemas.
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;
J'ai déjà validé des documents XML à l'aide du code suivant:
Uses
Classes,
XMLIntf,
SysUtils;
Function ValidateXMLDoc(aXmlDoc: IXMLDocument): boolean;
var
validateDoc: IXMLDocument;
begin
validateDoc := TXMLDocument.Create(nil);
validateDoc.ParseOptions := [poResolveExternals, poValidateOnParse];
validateDoc.XML := aXmlDoc.XML;
validateDoc.Active := true;
Result := True;
end;