Introduction
Kylix 2 et Delphi 6 introduisent un nouveau composant
TXMLDocument permettant de manipuler facilement les
documents au format XML. La particularité de ce composant
est sa capacité à utiliser différents parseurs XML. Sous
Windows avec Delphi 6 c'est le parseur de Microsoft (MSXML)
qui est utilisé par défaut, mais il est possible de
changer pour celui d'IBM, d'OpenXML (entièrement écrit en
Delphi), etc. Avec Kylix 2 c'est ce dernier qui est utilisé
par défaut. Malheureusement, contrairement à celui de
Microsoft, OpenXML n'implémente pas de transformation XSL
ni de XPath. Mais avec l'architecture du composant
TXMLDocument il est possible d'écrire une interface pour
d'autre parseur tel que GDOM/libXSL (Gnome) ou Sablotron
fournissant un processeur XSL et une gestion de XPath.
L'objectif de cet article est de décrire l'architecture
du composant TXMLDocument et de montrer comment implémenter
une interface pour un parseur tiers.
Remarque: Ceci est aussi valable pour Delphi 6,
mais MSXML suffit largement pour les manipulations XML/XSL,
alors qu'OpenXML fournit avec Kylix 2 n'implémente que le
DOM.
Architecture de TXMLDocument
Modèle objet
Le composant TXMLDocument a la capacité de changer
dynamiquement de parseur. Ceci est possible grâce à la
définition dans l'architecture du composant d'une couche
d'abstraction représentant les méthodes de manipulation du
DOM. Cette couche d'abstraction (dans le fichier xmldom.pas)
est entièrement faite à base d'interfaces(IInterface
ou IUnknown). Pour rappel,
les interfaces peuvent être comparées à des classes
abstraites pures, c'est-à-dire que seules les méthodes
sont déclarées et aucune implémentation n'est donnée. A
partir de ces interfaces, on peut coder un comportement
différent dans des classes les implémentant pour chacunes
des méthodes définies.

Chaque unité de parseur (msxmldom.pas, oxmldom.pas)
implémente donc les interfaces du DOM et fait référence
aux objets interne du parseur:

Pour MSXML, le TXMLNode a une référence
sur un IDOMNode (interface du DOM) qui est implémenté par
TMSDOMNode (msxmldom.pas). Cette classe garde un référence
su l'interface IXMLDOMNode qui est la représentation
mémoire du noeud parsé par msxml3.dll.

pour OpenXML, c'est la classe TOXDOMNode (oxmldom.pas)
qui implémente le IDOMNode (interface du DOM). Cette classe
a une référence du un TdomNode (XDom.pas) qui est la classe
encapsulant le noeud pour le parseur OpenXML.
Remarque: En tout, on a 3 objets en mémoire pour
chaque noeud. Il faut donc garder à l'esprit que pour 1000
noeuds XML, on a 3000 objets en mémoire...
Choix du parseur
Le changement dynamique du parseur s'effectue via la
propriété DOMVendor de type TDOMVendor. cette classe
abstraite est déclarée de la façon suivante:
TDOMVendor =
class
public
function Description: string; virtual;
abstract;
function DOMImplementation: IDOMImplementation;
virtual; abstract;
end; |
Dans la section initialization de chaque unité
implémentant l'interfaçage pour chaque parseur, l'appel à
la méthode RegisterDOMVendor permet d'enregistrer le
nouveau parseur dans une liste interne globale (xmldom.pas)
de tous les Vendors disponibles.
initialization
MSXML_DOM := TMSDOMImplementationFactory.Create;
RegisterDOMVendor(MSXML_DOM); |
initialization
OpenXMLFactory := TOXDOMImplementationFactory.Create;
RegisterDOMVendor(OpenXMLFactory); |
Dans les exemples précédents, TMSDOMImplementationFactory
et TOXDOMImplementationFactory
dérivent de TDOMVendor et redéfinissent les méthodes
Description et DOMImplementation qui vont être appelées par
le composant TXMLDocument. La méthode Description
renvoie une chaîne de caractères indiquant le nom du parseur (MSXML, OpenXML, etc.). La méthode DOMImplementation
renvoie une référence sur une classe implémentant
l'interface IDOMImplementation fournissant entre autre la
méthode CreateDocument, point d'entré du document XML.
IDOMImplementation =
interface
['{2BF4C0E0-096E-11D4-83DA-00C04F60B2DD}']
function hasFeature(const feature, version: DOMString): WordBool;
function createDocumentType(const qualifiedName, publicId, { DOM Level 2 }
systemId: DOMString): IDOMDocumentType;
safecall;
function createDocument(const namespaceURI, qualifiedName: DOMString;{ DOM Level 2 }
doctype: IDOMDocumentType): IDOMDocument;
safecall;
end; |
Ajout d'un parseur tiers
Implémentation des interfaces
L'ajout d'un nouveau parseur au composant TXMLDocument
passe par l'implémentation des interfaces décrites
dans xmldom.pas. les plus importantes sont
-
IDOMImplementation
(Point d'entré du document XML, création du document)
-
IDOMNode
(Manipulation de base des noeuds)
-
IDOMNodeEx
(Extensions: Text, XML, TransformNode (XSL))
-
IDOMNodeSelect
(SelectNode, SelectNodes (XPath))
-
IDOMNodeList
(Manipulation des listes de noeuds)
-
IDOMNamedNodeMap
(Manipulation des listes d'attributs)
-
IDOMAttr
(Manipulation spécifique d'un attribut)
-
IDOMElement
(Manipulation spécifique d'éléments)
-
IDOMDocument
(Manipulation spécifique de documents)
On va donc créer une unité
semblable à msxmldom.pas ou oxmldom.pas. Pour Sablotron je
l'ai appelé sablotxmldom.pas. Il s'agit donc de créer des
classes implémentant spécifiquement le comportement de l'appel aux méthodes du
parseur sous jacent. Par exemple si
l'on crée la classe TSablotDOMImplementation
implémentant IDOMImplementation:
TSablotDOMImplementation =
class(TSablotDOMInterface, IDOMImplementation)
private
FSablotSituation: TSablotSituation;
protected
{ IDOMImplementation }
function hasFeature(const
feature, version: DOMString): WordBool;
function createDocumentType(const
qualifiedName, publicId,
systemId: DOMString):
IDOMDocumentType; safecall;
function createDocument(const namespaceURI, qualifiedName: DOMString;
doctype: IDOMDocumentType): IDOMDocument;
safecall;
public
constructor Create;
end; |
on aura le code de createDocument:
function
TSablotDOMImplementation.createDocument(const namespaceURI,
qualifiedName: DOMString; doctype: IDOMDocumentType): IDOMDocument;
var
Doc: TSDOM_Document;
begin
Sablot.SablotCreateDocument(FSablotSituation, @Doc);
Result := TSablotDOMDocument.Create(FSablotSituation, Doc);
end; |
La donnée membre FSablotSituation
est spécifique à l'implémentation de Sablotron. C'est une
sorte de contexte permettant d'identifier sur quel document
on est en train de travailler. Il est nécessaire pour tous
les appels aux fonctions du parseur. La fonction SablotCreateDocument
est une fonction de la bibliothèque Sablotron. Cette
dernière est interfacée dans l'unité Sablot.pas où l'on
retrouve les prototypes des fonctions publiés par le .so
(ou la dll). TSablotDOMDocument
est une classe qui implémente IDOMDocument.
Un problème se pose: comment adapter un objet
TSDOM_Node qui est un noeud Sablotron en une référence sur
l'interface IDOMNode ?
On va écrire des fonctions utilitaires permettant de
générer les noeuds dans la représentation du DOM (Réference
sur les interfaces du xmldom.pas).
On a par exemple la fonction MakeNode qui à partir d'un
node dans la représentation du parseur crée une référence
sur IDOMNode. De même on fera un MakeNodeList pour adapter
un TSDOM_NodeList en IDOMNodeList.
function
MakeNode(SablotSituation: TSablotSituation; Node:
TSDOM_Node): IDOMNode;
const
NodeClasses: array[ELEMENT_NODE..NOTATION_NODE]
of TSablotDOMNodeClass =
(TSablotDOMElement,
TSablotDOMAttr, TSablotDOMText,
TSablotDOMCDataSection,
TSablotDOMEntityReference,
TSablotDOMEntity, TSablotDOMProcessingInstruction,
TSablotDOMComment,
TSablotDOMDocument, TSablotDOMDocumentType,
TSablotDOMDocumentFragment,
TSablotDOMNotation);
var
NodeType: Integer;
begin
if Node <> nil then
begin
Sablot.SDOM_getNodeType(SablotSituation,
Node, @NodeType);
Result := NodeClasses[NodeType].Create(SablotSituation,
Node);
end
else
Result := nil;
end; |
La fonction déclare un tableau de toutes les classes
implémentant un descendant de IDOMNode. Après avoir
récupéré le type du noeud passé en paramètre, on crée
une instance grâce à la référence de classe
correspondante.
On va pouvoir ensuite l'utiliser de la manière suivante:
function
TSablotDOMDocument.createElement(const
tagName: DOMString): IDOMElement;
var
Node: TSDOM_Node;
SName: string;
begin
SName := tagName;
Sablot.SDOM_createElement(FSablotSituation,
SablotDocument, @Node, PChar(SName));
Result := MakeNode(FSablotSituation, Node) as
IDOMElement;
end; |
la fonction du parseur SDOM_CreateElement
crée un nouveau noeud Sablotron (TSDOM_Node)
puis on crée à partir de celui-ci un TSablotDOMElement
implémentant l'interface IDOMElement
du DOM.
Enregistrement et utilisation du nouveau parseur
Pour rendre disponible ce nouveau parseur au niveau de
TXMLDocument, il suffit de rajouter dans la clause initialization
une RegisterDOMVendor avec l'instance de classe dérivante
de TDOMVendor est redéfinissant les méthodes Description
et DOMImplementation.
initialization
Sablot_DOM :=
TSablotDOMImplementationFactory.Create;
RegisterDOMVendor(Sablot_DOM);
finalization
UnRegisterDOMVendor(Sablot_DOM);
Sablot_DOM.Free; |
Dans l'exemple ci-dessus, TSablotDOMImplementationFactory
dérive de TDOMVendor.
la méthode DOMImplementation doit
renvoyer une référence sur IDOMImplemention.
il suffit donc d'instancier la classe l'implémentant:
function
TSablotDOMImplementationFactory.DOMImplementation:
IDOMImplementation;
begin
Result := TSablotDOMImplementation.Create;
end; |
Maintenant nous sommes prêt à utiliser le nouveau
parseur. Pour rendre disponible Sablotron dans la liste des
DOMVendors, il suffit d'ajouter les 2 fichiers Sablot.aps
et salbotxmldom.pas dans un
paquet (par exemple user.dpk) de conception et de
l'installer.
On pourra aussi l'indiquer à l'exécution. Pour que
l'enregistrement se fasse, il est impératif d'inclure notre
unité dans la clause uses de
l'unité utilisant le TXMLDocument.
var
XMLDocumentCompo: TXMLDocument;
XMLDocument: IXMLDocument;
begin
XMLDocumentCompo := TXMLDocument.Create(nil);
XMLDocumentCompo.DOMVendor := Sablot_DOM;
XMLDocumentCompo.XML.Text := '<document></document>';
XMLDocument := XMLDocumentCompo;
XMLDocument.Active := True;
end; |
la variable Sablot_DOM est l'instance du TDOMVendor
enregistrée dans l'initialization de l'unité. Une fois que
l'on a fixé le DOMVendor, on utilisera l'interface
IXMLDocument pour manipuler le document XML.
Conclusion
Le composant TXMLDocument permet, en plus de manipuler
facilement des documents XML, de changer de parseur
permettant de gérer le XSL ou le XPath ce qui n'est
actuellement pas possible avec le parseur OpenXML ou IBM
livrés avec Kylix 2. Autre grand avantage de Sablotron,
sa portabilité: On va pouvoir utiliser notre interfaçage
aussi bien sous Linux que sous Windows. En plus de l'interfaçage en Pascal des
bibliothèques des parseurs, il est nécessaire
d'implémenter de façon spécifique, pour chaque parseur, les
comportements pour les interfaces du DOM définies dans Kylix
2. Ce travail reste assez fastidieux compte tenu du nombre
importants de méthodes.
Voici l'interface en pascal ainsi que l'unité implémentation
les interfaces du DOM pour utiliser le parseur Sablotron
dans Kylix2. L'implémentation que j'en ai faite reste
basique mais suffisante pour la plupart des utilisations.
Sablotron: http://www.gingerall.com/charlie/ga/xml/p_sab.xml
par Jean-Philippe
BEMPEL
|