Utilisation de Visual Basic .NET depuis VBA pour sérialiser des documents Word en XML

Michael Corning
Microsoft Corporation

S'applique à :
   Microsoft® Word 2002
   Microsoft Visual Studio® .NET

Résumé : Cet article décrit la procédure à suivre pour sérialiser rapidement des documents Word volumineux au format XML en utilisant du code .NET à partir d'un programme Microsoft Office Visual Basic pour Applications (VBA).

Téléchargez ou consultez setup.msi sur MSDN Downloads ( leave-msdn france Site en anglais). (Les commentaires des programmeurs sont en anglais dans les exemples de fichiers téléchargés, mais pour plus de clarté ils ont été traduits dans l'article.) Cet article contient des captures d'écran en anglais.

Sommaire

Introduction
L'exemple WordXml.Net
Déploiement de WordXml.Net
Documentation supplémentaire
Conclusion
Annexe

Introduction

Cet article décrit les techniques et les technologies Microsoft® .NET qui ont permis, à partir d'un programme Microsoft Office Visual Basic® pour Applications (VBA), de sérialiser un document Microsoft Word 2002 de 100 pages au format XML en 33 secondes (avec le code VBA initial, l'opération a pris dix minutes).

En l'écrivant, je me suis fixé trois objectifs consistant à montrer : premièrement, comment utiliser les classes System.Xml dans .NET Framework afin de contourner l'objet XML DOMDocument le plus coûteux et sérialiser directement le XML dans le système de fichiers ; deuxièmement, comment créer et déboguer une bibliothèque de classes Microsoft Visual Basic .NET permettant l'interopérabilité avec la technologie COM (Component Object Model) afin que VBA ait accès au composant XML géré ; troisièmement, comment créer un package d'installation Microsoft Windows® pour installer l'application Visual Basic .NET, y compris les symboles de débogage, sur la machine de l'utilisateur.

Bien sûr, la communauté des développeurs Office s'interroge sur le fait de savoir si Microsoft va incorporer .NET Framework dans Office et de quelle manière. Pour tout dire, je ne suis pas inquiet. Jusqu'à ce jour, faire confiance à COM Interop pour tirer le meilleur parti des deux mondes m'a donné entière satisfaction. Mon code VBA existant reste en place, mais j'ai remplacé une fonction VBA qui utilisait la DLL native de Microsoft XML Core Services (msxml4.dll) par un appel à mon composant Visual Basic .NET, lequel utilise du XML géré. Les baisses de performances imputables à COM Interop sont éclipsées par les gains de performances que j'ai obtenus en sérialisant un contenu Word sans mettre en cache au préalable le XML dans un objet DOMDocument.

Mon histoire commence vers la fin du cycle de développement. J'avais achevé un programme VBA destiné à convertir les spécifications de test de logiciels du format Word en XML. Tout allait bien jusqu'à ce que l'un de nos testeurs me donne ses propres spécifications, bien réelles. Il a fallu à mon programme VBA plus de dix minutes pour obtenir l'objet DOMDocument contenant les tests. J'ai compris alors qu'il n'y avait plus de temps à perdre : il fallait en passer par les classes gérées de l'espace de noms .NET System.Xml.

Lorsque j'eus terminé, je disposais d'une solution extrêmement performante pour remplacer la mise en cache du XML dans VBA, et j'ai pris un véritable plaisir à la mettre en place avec .NET ; mais dans le même temps, j'ai constaté qu'il existait dans ce domaine quelques articles et exemples précieux traitant des besoins des développeurs Office et XML tels que moi. J'ai donc décidé de raconter mon histoire, en espérant faire économiser des milliers d'heures de développement aux autres clients Microsoft. Je signale au passage que j'ai trouvé des articles très utiles après avoir fini mon travail de développement et j'en fournis la liste à la fin du présent document.

J'ai simplifié le système de création des spécifications de test que j'avais écrit pour nos testeurs et j'ai inclus la solution Visual Basic .NET et le modèle Word dans les éléments téléchargeables avec cet article. Le programme simplifié s'appelle WordXml.Net et je vais en expliquer le code source Visual Basic .NET dans les pages qui suivent. Au cas où il vous semblerait utile de lire le code source WordXml.Net pour comprendre à quoi ressemblait le système d'origine, j'ai ajouté en annexe les spécifications fonctionnelles et du programme de notre système, appelé Socrates. Le modèle téléchargeable WordXml.dot ne peut sérialiser qu'un contenu Word ; j'ai supprimé tout le code source de Socrates servant à créer des spécifications de test.

L'exemple WordXml.Net

Lorsque vous allez créer un document à partir du fichier WordXml.dot, vous aurez devant vous un exemple de spécifications de test Socrates. Le processus de sérialisation comporte trois étapes qui génèrent chacune au moins un fichier XML. Dans Socrates, le fichier XML final est utilisé par la fonction d'automatisation des tests du logiciel pour exécuter le test en question. Pour plus de détails, reportez-vous à l'annexe. En tant que développeur Word, le fichier XML qui vous intéressera le plus est le premier, c'est-à-dire celui qui porte le même nom que les spécifications de test (il est simplement suivi de l'extension .xml). Ce premier fichier XML correspond à la sérialisation réelle du document Word.

L'exemple WordXml.Net inclut le reste du pipeline Socrates, car j'ai tenu à souligner l'importance d'utiliser la technologie XML la plus appropriée en fonction des problèmes de programmation. Ainsi, l'utilisation des techniques de streaming (flux) XML dans l'espace de noms System.Xml s'avère parfaitement adaptée lorsqu'il n'est pas nécessaire de mettre le XML dans un cache ; en revanche, pour d'autres tâches, il est préférable d'utiliser XSLT et, dans cas, vous devez avoir recours à un cache XML. Enfin, XSLT ayant ses propres limites, j'expliquerai comment utiliser les classes System.Xml qui optimisent les caches XML.

Encore une remarque avant de commencer. Dans le système Socrates, le premier fichier XML généré est un fichier appelé IML, ce qui correspond à Intermediate Markup Language. Le second fichier XML généré est un fichier XIML et il correspond à un fichier IML étendu. Le troisième et dernier fichier XML est appelé varmap car il est incorporé à la structure d'automatisation des tests. Là encore, cette terminologie, ainsi qu'un schéma montrant les relations qui lient entre eux tous les composants de sérialisation, sont expliqués plus en détail dans l'annexe.

Les composants Visual Basic .NET

Cette section concerne le code source Visual Basic .NET. Il y a deux programmes Visual Basic .NET que les utilisateurs de WordXml.Net peuvent appeler. La bibliothèque de classes, qui constitue le premier programme, contient deux classes et trois fonctions ; Word 2002 appelle deux de ces fonctions et un fichier EXE Visual Basic .NET appelle la troisième.

La classe XmlProvider de l'assembly WordXml.Net.dll contient la fonction serialize qui peut générer un fichier IML à partir des spécifications de test créées au format Word avec le modèle WordXml.dot. Cette fonctionnalité est exposée uniquement par l'intermédiaire de Word 2002 car l'appel à Word depuis un fichier EXE .NET est extrêmement coûteux en raison du marshalling inter-processus entre Word et l'EXE .NET (ou un service Web XML).

Word 2002 appelle également la fonction compileXimlFromWord de la classe XimlCompiler pour convertir les données IML des spécifications en XIML, puis générer un ou plusieurs varmaps à partir des données XIML. Le processus .NET indique la progression de l'opération dans la barre d'état de Word.

La classe XimlCompiler fournit également la fonction compileXimlFromExe afin que le second programme Visual Basic .NET, WordXmlHost.exe, puisse afficher la progression de la compilation sur la console. Les deux fonctions compileXimlFrom... appellent la fonction privée compile qui accomplit tout le travail.

Bibliothèque de classes

La bibliothèque de classes (le composant) WordXml.Net.dll fournit des objets aussi bien pour le script VBA dans Word que pour l'EXE Visual Basic .NET du système de fichiers. Le composant a deux rôles à remplir : il utilise les classes XML non gérées pour sérialiser le contenu binaire Word 2002 en IML de type texte, et il convertit l'IML en XIML puis en autant de fichiers XML varmap qu'il existe de nœuds varmap dans les données XIML.

La principale raison d'utiliser Visual Basic .NET est que nous avons accès aux classes XML très performantes de Microsoft .NET Framework, qui peut traiter des documents de plusieurs méga-octets en quelques secondes seulement. Comme je l'ai dit plus haut, il a fallu plus de dix minutes pour sérialiser un document de spécifications de test d'une centaine de pages, contre 33 secondes pour le convertir à l'aide du composant Visual Basic .NET.

L'autre inconvénient, aujourd'hui disparu grâce au composant Visual Basic .NET, est que le XSLT (qui convertit l'IML en XIML) ne peut écrire que dans un seul fichier. Toutefois, une même spécification peut, selon son type, générer plusieurs fichiers varmap différents (mais reliés entre eux). Le composant WordXml.Net peut envoyer du XIML à autant de fichiers varmap qu'il est nécessaire. Les classes XML gérées sont si efficaces qu'il est même possible d'enregistrer sur le disque des fichiers XIML de presque trois méga-octets en à peine plus qu'une seconde.

Code source

Examinons tout d'abord la structure du composant. Pour commencer à développer le composant .NET, cliquez sur la commande Ajouter une référence dans le menu contextuel Références du projet de bibliothèque de classes Visual Basic .NET. Lorsque vous cliquez sur l'onglet COM de la boîte de dialogue Ajouter une référence, vous avez accès au dossier contenant WINWORD.EXE (pour Word 2002). Sélectionnez le fichier EXE afin d'ajouter le PIA (Primary Interop Assembly) pour Word 2002 et Microsoft Office XP dans les références du projet (voir la figure 1).

Boîte de dialogue Ajouter une référence

Figure 1. Boîte de dialogue Ajouter une référence

L'étape suivante a pour but de faciliter la saisie des références de classe dans le code source Visual Basic .NET en ajoutant les instructions Imports suivantes dans le fichier du code source WordXml.Net.vb du composant.

Imports Microsoft.Office
Imports System.Text.RegularExpressions
Imports System.Xml
Imports System.Xml.Xsl
Imports System.Xml.XPath
Imports Word

Listing 1. Instructions Imports dans la bibliothèque de classes

Il faut ensuite organiser les classes, les fonctions et les signatures de fonction qui implémenteront nos compilateurs WordXml.Net. Je parlerai plus loin du constructeur vide, en décrivant en détail le processus d'activation de COM Interop. À partir de cette présentation globale, je vais pour chaque fonction parcourir le pseudocode. Pour le cas où le code source vous semblerait complexe, je vais décrire les principaux éléments et laisser le soin au lecteur d'explorer le reste du code et les commentaires téléchargés.

Public Class XmlProvider

 Public Sub New()
 ' constructeur public nécessaire à COM Interop
 End Sub

 Public Function Serialize (ByVal rngTestAreas As Range) _
 As Boolean
 End Function

End Class

Public Class XimlCompiler

 Public Sub New()
 ' constructeur public nécessaire à COM Interop
 End Sub

 Private Function Compile(ByVal imlPath As String, _
 ByVal imlFileName As String, _
 ByVal reader As XmlNodeReader, _
 ByRef result As String)
 End Function

 Public Function CompileXimlFromWord (ByVal app As Application, _
 ByVal xsltPath As String, _
 ByVal imlPath As String, _
 ByVal imlFileName As String) _
 As String
 End Function

 Public Function CompileXimlFromExe(ByVal xsltPath As String, _
 ByVal imlPath As String, _
 ByVal imlFileName As String) _
 As String
 End Function

 Private Function IncludeXml(ByVal dataNodes As XmlNodeList,
 ByRef writer As XmlTextWriter)
 End Function

End Class

Listing 2. Présentation des classes

La classe XmlProvider

La fonction Serialize exécute l'essentiel du travail de tous les compilateurs car elle doit parcourir un objet document Word complexe (toutes les fonctions de la classe XimlCompiler se contentent de traiter du XML bien formaté). Le parcours du document Word est du type sans état, de sorte que nous bénéficions de l'amélioration des performances due au XML géré. Pour réduire le marshalling (ordonnancement) interprocessus et puisque Word ne supporte pas directement le Common Language Runtime, il convient d'adopter le langage VBA. Dès que nous abordons le traitement du texte XML dans WordXmlHost.exe, nous basculons vers Visual Basic .NET qui sert à la fois de langage client et serveur.

Public Function Serialize (ByVal rngTestAreas As Range) _
 As Boolean
 Try
 ' entrer l'objet Range depuis Word XP
 ' sérialiser les sections Introduction, Projects et Contexts
 ' parcourir toutes les sections TestAreas à partir de la première balise Set
 ' sérialiser la balise Set et ses paragraphes au style setText
 ' sérialiser la balise Level et son titre
 ' parcourir tous les paragraphes de la balise Level
 ' sérialiser les balises Var
 ' sérialiser la table varText
 ' si elle existe, sérialiser la table de test Declared
 ' si elle existe, sérialiser la table de test Defined
 ' si elle existe, sérialiser la balise Level frère (même numéro de niveau
 ' mais catégorie différente)
 ' répéter la sérialisation des balises Var

 Catch
 ' exception afin de pouvoir interrompre le composant au
 ' moment de l'exécution et du débogage.

 Finally
 ' fermer le programme d'écriture pour stocker le contenu sur disque (fermer
 ' même s'il y a une exception)

End Function

Listing 3. Pseudocode XmlProvider Serialize

Serialize est beaucoup plus rapide que la fonction serializeTestAreasWithDOM du module WordXml.dot WordXmlDotNet car la méthode Serialize se contente de mapper l'infoset (jeu d'informations) Word sur l'infoset Socrates (pour une description plus détaillée de l'infoset Socrates, reportez-vous à l'annexe). La méthode Serialize ne crée pas d'objet XML élaboré pour chaque nœud ; elle utilise simplement l'espace de noms System.Xml.XmlTextWriter pour écrire les chaînes de syntaxe XML dans le système de fichiers. L'inconvénient de XmlTextWriter est qu'il transmet le XML dans un flux (streaming) et ne permet pas un accès direct aux données XML. Plus loin dans cet article, je vous expliquerai comment j'ai utilisé à la fois XSLT et des caches XML gérés optimisés pour implémenter des solutions exigeant un accès direct au XML.

Liaison des documents Word à Visual Basic .NET

En termes de code source, le premier point intéressant à propos de la méthode Serialize est la façon dont nous relions l'objet Range de Word à la fonction de Visual Basic .NET. La première étape consiste à passer l'objet Range (contenant les zones de test appartenant à la spécification modifiée) comme argument pour appeler Serialize. Une fois dans la fonction, nous instancions un objet Word.Document (specDoc) avec la propriété Parent de l'objet Range. L'objet specDoc nous donne ensuite accès au nom du document Word (que nous utiliserons avec le suffixe .xml comme argument destiné au constructeur XmlTextWriter) ainsi que plusieurs autres valeurs nécessaires à la fonction Serialize.

Dim specDoc As Document
Dim writer As XmlTextWriter
imlFilePathName = specDoc.Path & "\" & _
 specDocConvertedName.Replace(specDoc.Name, ".xml")
writer = New XmlTextWriter(imlFilePathName, Nothing)

Listing 4. Liaison du document Word au programme Visual Basic .NET

Sérialisation de tables hiérarchiques

Tout en vous montrant comment j'utilise du XML géré pour multiplier presque par 60 les performances de mon application XML, il me semble judicieux de vous présenter certaines techniques visant à réellement sérialiser le contenu Word.

Je vais donc vous expliquer comment j'ai utilisé des tableaux Word pour simuler une hiérarchie imbriquée et comment j'ai sérialisé un tableau en XML. Si vous examinez l'exemple de spécifications de test WordXml.Net (fourni avec les éléments téléchargés), vous constatez que le Set 1 utilise deux classes pour implémenter les procédures de configuration des tests et de nettoyage. La seconde ligne du tableau cContext des spécifications est en retrait. Le code source suivant destiné à sérialiser un tableau Context recherche ces retraits pour imbriquer des éléments XML :

writer.WriteStartElement("contextSection", NSURI_IML)
Do While True
 rng = rng.Next(WdUnits.wdParagraph)
 If rng.Tables.Count = 0 Then
 If Len(rngTextString) > 0 Then
 writer.WriteElementString("p", rngTextString)
 End If
 Else
 thisTable = rng.Tables.Item(FLD_CONTEXT_CLS)
 writer.WriteStartElement("grp")
 For intCt = 3 To thisTable.Rows.Count
 ' obtenir le nom actuel de la classe Group et la liste des Sets
 cellName = _
 thisTable.Rows.Item(intCt).Cells.Item(FLD_CONTEXT_CLS)
 cellSets = _
 thisTable.Rows.Item(intCt).Cells.Item(FLD_CONTEXT_SETS)
 If thisTable.Rows.Item(intCt).IsLast Then
 nextLeftIndent = 0
 Else
 nextLeftIndent = _
 thisTable.Rows.Item(intCt + _
 1).Cells.Item(FLD_CONTEXT_CLS). _
 Range.Paragraphs.LeftIndent
 End If
 ' Cette ligne est de type enfant si intLIndent > 0
 leftIndent = cellName.Range.Paragraphs.LeftIndent

 writer.WriteStartElement("grp", NSURI_CONTEXT)
 writer.WriteAttributeString( _
 "cls", _
 cleanCell.Match(cellName.Range.Text).Value)
 ' utiliser l'espace comme délimiteur des valeurs de Set
 setList = Split(cleanCell.Match(cellSets.Range.Text).Value)
 ' ajouter une varref enfant pour chaque Set cité
 For Each setRef In setList
 If "" <> setRef Then
 writer.WriteStartElement("varref", NSURI_CONTEXT)
 writer.WriteAttributeString("set", Trim(setRef))
 writer.WriteEndElement()
 End If
 Next setRef
 If nextLeftIndent = leftIndent Then
 ' fermer la balise grp actuelle
 writer.WriteEndElement()
 ElseIf nextLeftIndent < leftIndent Then
 ' fermer la balise grp actuelle
 writer.WriteEndElement()
 ' fermer la balise grp parent
 writer.WriteEndElement()
 End If
 Next intCt
 ' fermer la balise conteneur grp
 writer.WriteEndElement()
 Exit Do
 End If ' rng.Tables.Count = 0
Loop
' fermer la balise contextSection
writer.WriteEndElement()

Listing 5. sérialisation du tableau Contexts dans NewTestSpec.doc

Je dois faire une ou deux remarques sur ce code. Tout d'abord, la méthode Next de l'objet Range est très pratique et très rapide pour parcourir un document paragraphe après paragraphe. Le code ci-dessus sérialise des paragraphes de texte lorsque la plage (Range) n'inclut pas de tableaux, et sérialise des objets Table lorsqu'il en existe dans un paragraphe. Chaque ligne du tableau Contexts figurant dans les spécifications est une balise <grp> ; s'il existe plusieurs lignes et si la ligne suivante est associée à une propriété leftIndent supérieure à celle de la ligne en cours, une balise <grp> enfant sera créée avant que la balise <grp> en cours ne soit fermée. Si la propriété leftIndent de la ligne suivante est inférieure à la propriété leftIndent de la ligne en cours, il est obligatoire de fermer la balise <grp> en cours et la balise <grp> parent avant que la balise <grp> de la ligne actuelle ne soit créée.

Après sérialisation du fichier NewTestSpec.doc (fourni avec les éléments téléchargés), voici comment se présente le tableau Context dans un des fichiers XML varmap de spécifications :

<grp>
 <grp cls="CSetup">
 <grp cls="CSpecial">
 <rec key="arg1">Premier argument</rec>
 <rec key="arg2">Second argument</rec>
 <varref set="1" />
 </grp>
 </grp>
 <grp cls="CExtraSpecial">
 <rec key="arg1">Autre argument</rec>
 <varref set="2" />
 </grp>
</grp>

Listing 6. Tableau Contexts sérialisé comme nœud grp

Les nœuds de balise enfants rec de la balise grp ont été ajoutés par fusion des fichiers de données XML externes avec le fichier XML des spécifications de test dans la méthode Compile(), dont je vais dire un mot. C'est ce besoin de fusionner des nœuds XML spécifiques dans un fichier XML parent (ainsi que la nécessité de fractionner le fichier XIML en plusieurs fichiers XML) qui a déterminé le passage d'un flux XML (avec XmlTextWriter) à du XML mis en cache.

Maintien de la hiérarchie dans XmlTextWriter

Étant donné que la propriété Top de la classe XmlTextWriter est privée et qu'il n'existe aucune autre propriété publique indiquant jusqu'à quel niveau de la hiérarchie parvient le flux XML, l'utilisation de la classe System.Collections.Stack s'est avérée réellement appréciable pendant le développement du code Visual Basic .NET afin de sérialiser un document XML hiérarchique à partir d'un document Word plat. Il est intéressant de noter que, une fois que le code fonctionne correctement, la pile ne me paraît pas nécessaire, mais elle reste utile si vous utilisez des instructions Assert dans votre programme pour garantir la conformité des documents Word entrants avec le schéma XML à sérialiser.

En d'autres termes, à moins que votre mécanisme de sérialisation suive le niveau de la dernière section (et s'y limite), un document Word est en général plat. Pire encore : lorsque vous utilisez XmlTextWriter pour enregistrer le XML dans un système de stockage quelconque (le système de fichiers par exemple), vous enregistrez un flux de texte, mais XmlTextWriter ne vous propose aucune propriété publique qui vous permettrait d'effectuer un suivi au moment de fermer une balise ouverte. Par conséquent, vous pouvez facilement arriver à un point où vous tentez de fermer une balise alors qu'aucune n'est ouverte. Et le comble de l'ironie est que XmlTextWriter a une propriété top privée, mais qui ne sert qu'au moment du débogage. En revanche, si vous oubliez de fermer des balises ouvertes, XmlTextWriter le fait à votre place, bien que le résultat ne soit pas nécessairement celui escompté. En réalité, ce comportement par défaut me paraît plutôt utile pendant le développement. Ainsi, lorsque mes clauses Catch ferment automatiquement l'objet Writer, je vois jusqu'où va le sérialiseur et où il est bloqué, en examinant le fichier XML (partiel) résultant.

L'autre aspect que j'ai découvert en utilisant cette technique basée sur la pile est qu'il ne faut pas chercher à optimiser l'algorithme. La méthode Serialize a été mon tout premier laboratoire d'expérimentation de ces techniques. La dernière méthode sur laquelle j'ai travaillé, à savoir la méthode Compile de la classe XimlCompiler (voir plus bas), est plus primitive mais plus facile à suivre (du point de vue hiérarchique). Dans cette section, je vais présenter un extrait de code représentatif de Serialize. Cet extrait vous montrera comment j'ai toujours essayé de savoir à quel niveau de la hiérarchie je me trouvais et quel était le processus suivant dans la sérialisation à mesure que je décidais de désempiler la pile ou non. Ensuite, vous verrez que j'ai utilisé une autre stratégie dans la méthode Compile qui désempile toujours la pile.

Dim testTree As New System.Collections.Stack()
If testTree.Count > 1 Then ' Ceci n'est pas le premier Set
 Try
 If testTree.Count = 3 Then
 ' fermer Var
 testTree.Pop()
 writer.WriteEndElement()
 End If
 Debug.Assert(testTree.Count = 2)
 If testTree.Count = 2 Then
 ' fermer Level
 testTree.Pop()
 writer.WriteEndElement()
 End If
 Debug.Assert(testTree.Count = 1)
 If testTree.Count = 1 Then
 ' fermer Set
 ' mais sans le retirer de la pile
 writer.WriteEndElement()
 End If
 Catch
 MsgBox("Attempting to close unopened element.", _
 MsgBoxStyle.Critical, "SerializeTestAreas -- New Set")
 End Try
Else
 ' place ce Set dans la pile
 testTree.Push("set")
End If

writer.WriteStartElement("set", NSURI_IML)

Listing 7. Utilisation sécurisée de WriteEndElement

Ma stratégie consiste à alimenter la pile juste avant de commencer un nouvel élément. L'extrait de code précédent s'exécute lorsque le paragraphe en cours utilise un style de document Set (pour plus de clarté, reportez-vous au fichier NewTestSpec.doc téléchargé avec les autres éléments). Mais avant de créer une nouvelle balise <set>, je dois m'assurer que j'ai fermé tous les éléments enfants. La clause If suppose que l'imbrication maximale que je peux atteindre est de trois niveaux (sinon, la première instruction Assert intervient). Ainsi, si je viens de traiter le dernier paragraphe Var du Set précédent, je supprime le troisième niveau de la pile. Si le code vient de traiter un paragraphe Level, je ne suis qu'à deux niveaux d'imbrication et je supprime le second niveau hors de la pile en insérant la balise de fin </level>. Le test testTree.Count = 1 garantit que je ne provoque pas l'erreur qui survient en cas de fermeture d'une balise inexistante ; à vrai dire, le test n'est pas nécessaire.

Une fois que la fonction Serialize s'est exécutée, un fichier IML réside dans le même dossier que le document Word sérialisé par l'IML (voir la figure 16).

La classe XimlCompiler

Comme indiqué plus haut, les deux fonctions de la classe XimlCompiler, compileXimlFromWord et compileXimlFromExe, appellent la fonction Compile privée. Dans chaque fonction appelante, le code ouvre un fichier IML (ou traite proprement l'erreur si le fichier IML manque) et exécute l'IML via une transformation XSLT, en enregistrant le XIML résultant sur le disque. Ce XIML est à nouveau chargé dans un objet XmlDocument qui instancie un objet XmlNodeReader et qui est transmis à la fonction Compile dans l'argument reader. La fonction Compile utilise reader pour parcourir le fichier XIML, en insérant des données XML à partir d'un fichier de données externe et en enregistrant les fichiers varmap sur le disque. Le pseudocode relatif à ce processus est décrit dans le Listing 8 ci-après.

Private Function compile(ByVal imlPath As String, _
 ByVal imlFileName As String, _
 ByVal reader As XmlNodeReader, _
 ByRef result As String, _
 ByVal templatePath As String) as Integer

 Try
 ' Une fois que le programme appelant a converti l'IML en XIML :
 ' lire chaque nœud du XIML
 ' si nodeName="varmap", créer un nouveau XmlTextWriter avec
 ' un nom de fichier basé sur le propriétaire et la structure
 ' si nodeName="var"
 ' ajouter un élément var, sauf son attribut "nr" dans le résultat
 ' si nodeName="rec" créer une balise de début pour <rec>
 ' si nodeName="grp" enregistrer l'élément grp
 ' si nodeName="varref" enregistrer la balise varref avec les attributs
 ' si nodeType is un commentaire et si nodeName="rec",
 ' enregistrer le nœud comment
 ' si nodeType est un texte, enregistrer le nœud de texte
 ' si nodeType est varmap endElement, enregistrer l'élément de fin et
 ' fermer le writer pour stocker le nœud varmap dans un fichier
 ' (rouvert par des nœuds varmap suivants)
 Catch
 ' renvoyer une exception à la routine appelante avec un message disant
 ' qu'il existe un xml partiel qu'il est possible d'examiner
 Finally
 ' fermer le reader et le writer (même si une exception se produit)
End Function

Listing 8. Pseudocode de la fonction Compile de la classe XimlCompiler

Le code source complet pour la fonction Compile est proposé ci-dessous, mais je n'en commenterai que les parties intéressantes. Tout d'abord, je vais vous expliquer comment Compile implémente une stratégie plus simple pour sérialiser en toute sécurité le contenu plat d'un document Word. Je vous montrerai aussi comment utiliser XPathNavigator pour inclure des données XML externes dans votre document XML principal (dans mon cas, j'ai besoin d'inclure les données de runtime relatives aux classes Context de mon exécutable de test).

Pour optimiser un algorithme de sérialisation, une solution consiste à ne pas désempiler la pile lorsque le nœud suivant de l'infoset entrant est le même que le nœud en cours. En d'autres termes, aussi longtemps que vous traitez des éléments frères, vous n'avez pas à désempiler la pile. Mais lorsque l'arborescence atteint trois niveaux d'imbrication (voir le Listing 11), cette méthode n'est pas efficace. Puisque la balise <grp> accepte un nombre arbitraire de niveaux d'imbrication des balises <grp> et <varref>, j'ai choisi simplement de désempiler la pile chaque fois que je rencontre une balise de fermeture dans l'infoset entrant.

Private Function Compile(ByVal imlPath As String, _
 ByVal imlFileName As String, ByVal reader As XmlNodeReader, _
 ByRef result As String, ByVal templatePath As String) _
 As Integer

 Dim dataInc() As String
 Dim dataNodeIterator As XPathNodeIterator
 Dim doc As XmlDocument = New XmlDocument()
 Dim includeDataClass As Boolean
 Dim nt As New NameTable()
 Dim nav As XPathNavigator
 Dim nsuri As String = _
 "http://wordXml.net/schemas/mcf/2002/01/varmap"
 Dim varCount As Integer
 Dim varmapCount As Integer = -1
 Dim varmapFileName As String
 Dim varmapTree As New Stack()
 Dim writer As XmlTextWriter

 nt = reader.NameTable

 reader.Read()
 Try
 While reader.Read()
 Select Case reader.NodeType
 Case XmlNodeType.Element
 Select Case reader.Name
 Case nt.Get("varmap")
 If reader.GetAttribute("framework") <> _
 "Manual" Then
 varmapTree.Push(reader.Name)
 varmapCount = varmapCount + 1
 varmapFileName = imlPath & imlFileName & _
 IIf("" <> reader.GetAttribute("owner"), _
 reader.GetAttribute("owner"), _
 CStr(varmapCount))
 If "" = reader.GetAttribute("framework") Then
 varmapFileName = varmapFileName & _
 ".varmap.xml"
 Else
 varmapFileName = varmapFileName & "." &_
 reader.GetAttribute("framework") & _
 ".varmap.xml"
 End If
 result = result & vbTab & varmapFileName & vbCr
 writer = New XmlTextWriter _
 (varmapFileName, Nothing)
 writer.Formatting = Formatting.Indented
 writer.Indentation = 2
 writer.WriteStartElement(reader.Name, nsuri)
 ' ignorer les attributs wordxml.net
 Do While reader.MoveToNextAttribute()
 If InStr("revision.framework", reader.Name) = _
 0 Then
 writer.WriteAttributeString(reader.Name, _
 reader.Value)
 End If
 Loop
 Else
 ' ignorer le reste de ce test manuel...
 Do Until reader.Name = "varmap" And _
 reader.NodeType = XmlNodeType.EndElement
 reader.Read()
 Loop
 End If

 Case nt.Get("var")
 ' garantir qu'il n'y a pas de balises var imbriquées
 If varmapTree.Count = 1 Then _
 varmapTree.Push(reader.Name)
 varCount = varCount + 1
 writer.WriteStartElement(reader.Name)
 Do While reader.MoveToNextAttribute()
 If reader.Name <> nt.Get("nr") Then
 writer.WriteAttributeString(reader.Name, _
 reader.Value)
 End If
 Loop
 reader.MoveToElement()
 If reader.IsEmptyElement Then
 writer.WriteEndElement()
 varmapTree.Pop()
 End If

 Case nt.Get("rec")
 varmapTree.Push(reader.Name)
 writer.WriteStartElement(reader.Name)
 writer.WriteAttributes(reader, False)

 Case nt.Get("grp")
 varmapTree.Push(reader.Name)
 writer.WriteStartElement(reader.Name)
 Do While reader.MoveToNextAttribute()
 If reader.Name = nt.Get("dataCls") Then
 dataInc = Split(reader.Value, "#")
 includeDataClass = True
 Exit Do
 Else
 includeDataClass = False
 writer.WriteAttributeString(reader.Name, _
 reader.Value)
 End If
 Loop
 reader.MoveToElement()
 If Not dataInc Is Nothing Then
 If dataInc(0) <> "" Then
 ' charger le fichier dataCls
 doc = New XmlDocument()
 doc.Load(imlPath & dataInc(0))
 nav = doc.CreateNavigator()
 If dataInc.Length = 2 Then
 dataNodeIterator = nav.Select(dataInc(1))
 Else
 dataNodeIterator = _
 nav.Select("//*[@xlink='" & _
 reader.GetAttribute("cls") & "']")
 End If
 If Not dataNodeIterator Is Nothing Then
 IncludeXml(dataNodeIterator, writer)
 dataInc = Nothing
 dataNodeIterator= Nothing
 End If
 End If
 End If

 Case nt.Get("varref")
 varmapTree.Push(reader.Name)
 writer.WriteStartElement(reader.Name)
 writer.WriteAttributes(reader, False)
 If reader.IsEmptyElement Then
 writer.WriteEndElement()
 varmapTree.Pop()
 End If
 End Select

 Case XmlNodeType.Comment
 If nt.Get(varmapTree.Peek()) = nt.Get("rec") Then
 writer.WriteComment(reader.Value)
 End If

 Case XmlNodeType.Text
 writer.WriteString(reader.Value)

 Case XmlNodeType.EndElement
 If nt.Get("ximl") <> reader.Name And _
 varmapTree.Count > 0 Then
 writer.WriteEndElement()
 varmapTree.Pop()
 If nt.Get("varmap") = reader.Name Then
 writer.Close()
 End If
 End If
 End Select
 End While

 Catch e As Exception
 MsgBox(e.Message)
 Throw New Exception("Could not complete compilation of ximl. "
 & _
 "Please consult the file " & varmapFileName & _
 " for the point at " & _
 "which the failure occurred.")

 Finally
 writer.Close()
 reader.Close()
 End Try

 Return varCount
End Function

Listing 9. Code source de la fonction Compile de la classe XimlCompiler

L'instruction Case qui gère le traitement des balises IML <grp> recherche un attribut dataCls indiquant que le testeur a besoin d'une partie de cet exécutable de test pour extraire certaines données XML au moment de l'exécution. Si la valeur de l'attribut dataCls inclut le caractère #, la fonction Compile utilise l'expression XPath suivante (et l'attribut spécial xlink figurant dans le fichier de données externe) pour isoler le nœud correct dans le fichier XML externe. Si, à la place, la fonction Compile() se contente de déréférencer un nom de fichier, le nom de l'attribut cls qui figure dans le fichier IML du tableau Context devient la valeur qu'il convient de trouver pour l'attribut xlink du fichier de données externe. Compile gère les deux techniques pour lier les données de runtime aux classes d'exécution et elle transmet les données XML sélectionnées à IncludeXml (voir le Listing 10), où la dernière fonction fusionne les données externes dans le fichier varmap résultant en vue de l'exécution.

Private Function IncludeXml(ByVal dataNodeIterator As
 XPathNodeIterator, ByRef writer As XmlTextWriter)
 Dim nav As XPathNavigator
 While (dataNodeIterator.MoveNext())
 nav = dataNodes.Current.Clone()
 writer.WriteStartElement(nav.Name)

 nav.MoveToFirstAttribute()
 If nav.Name <> "xlink" Then
 writer.WriteAttributeString(nav.Name, nav.Value)
 End If
 While (nav.MoveToNextAttribute)
 If nav.Name <> "xlink" Then
 writer.WriteAttributeString(nav.Name, nav2.Value)
 End If
 End While
 nav.MoveToParent()

 writer.WriteString(nav.Value)
 writer.WriteEndElement()

 End While
End Function

Listing 10. Code source de la fonction IncludeXml de la classe XimlCompiler

Le code des Listing 9 et Listing 10 est fusionné avec le XML des Listing 11 et Listing 12 de façon à produire le XML du Listing 6.

<grp>
 <grp cls="CSetup" >
 <grp cls="CSpecial"
 dataCls="grpClass.xml#data/grp[@xlink=&quot;CX&quot;]/rec"
 >
 <varref set="1" />
 </grp>
 </grp>
 <grp cls="CExtraSpecial" dataCls="grpClass.xml">
 <varref set="2" />
 </grp>
</grp>

Listing 11. Tableau Context sérialisé du fichier IML

<data>
 <grp xlink="CX">
 <rec key="arg1" xlink="CSpecial">Premier arg</rec>
 <rec key="arg2" xlink="CSpecial">Deuxième arg</rec>
 </grp>
 <rec key="arg1" xlink="CExtraSpecial">Autre arg</rec>
</data>

Listing 12. Fichier de données XML externe traité par IncludeXml

Le client EXE Visual Basic .NET

Dans Socrates, le fichier EXE est nécessaire pour les testeurs qui génèrent des données IML à partir d'une source autre que Word 2002. Les sources les plus courantes sont des données de Microsoft SQL Server™ ou des spécifications internes écrites dans un schéma XML différent. Le client EXE Visual Basic .NET ne compile que du XIML et des varmaps à partir des données IML. Il n'interagit en aucune façon avec Word 2002 (notez l'absence de toute référence à Word dans le nœud WordXmlHost, dans la figure 2). Pour obtenir la référence entourée d'un cercle dans la bibliothèque de classes, j'ai cliqué sur le bouton Parcourir dans le menu contextuel Références et j'ai accédé au dossier contenant la DLL de mon nouveau composant .NET.

Références pour l'EXE et la bibliothèque de classes

Figure 2. Références pour l'EXE et la bibliothèque de classes

Il est possible d'appeler l'EXE depuis la ligne de commande ou de faire glisser le fichier IML sur un raccourci pointant sur l'EXE. L'EXE instancie la bibliothèque de classes (voir le Listing 14 ci-après) et passe trois arguments, puis affiche un message renvoyé par le composant :

Dim a() As String
Dim x As String
...
result = XimlCompiler.compileXimlFromExe(xsltPath, imlPath, _
 imlFileName)

a = Split(result, vbCr)
For Each x In a
 Console.WriteLine(x)
Next x

If promptUser Then
 Console.WriteLine()
 Console.WriteLine("Enter any key to finish")
 Console.ReadLine()
End If

Listing 13. Compilation du XIML à partir de l'EXE

Le reste du code source EXE détermine le nombre d'arguments qui ont été transmis et, à partir de cette information, génère les trois arguments nécessaires au composant.

Compilation du code Visual Basic .NET

Cette section présente les boîtes de dialogue de Visual Studio .NET qui contiennent des informations utilisées par le composant COM et par l'EXE Visual Basic .NET.

Les deux éléments importants dans la figure 3 sont les zones Nom de l'assembly et Espace de noms racine.

Spécification de l'assembly et de l'espace de noms

Figure 3. Spécification de l'assembly et de l'espace de noms (cliquez sur l'image pour l'agrandir)

L'EXE Visual Basic .NET importe le composant avec une instruction qui énumère la quasi totalité de l'espace de noms racine du composant dans l'instruction Imports et utilise le dernier niveau de l'espace de noms plus le nom de la classe pour instancier l'objet :

Imports WordXml.Net
Dim XimlCompiler As New Authoring.XimlCompiler()

Listing 14. Instanciation du composant dans l'EXE

Le client VBA utilise la chaîne complète de l'espace de noms (cette chaîne est en réalité extraite du fichier AssemblyInfo.vb que Visual Studio .NET génère pour le composant, comme le montre le Listing 10) afin d'ajouter une référence au composant, et il utilise le nom d'assembly plus le nom de la classe pour instancier l'objet XimlCompiler (voir le Listing 15).

Ajout d'une référence aux compilateurs de WordXml.Net

Figure 4. Ajout d'une référence aux compilateurs de WordXml.Net

Dim XimlCompiler As New WordXml_Net.XimlCompiler

Listing 15. Instanciation de XimlCompiler dans VBA

Débogage de la bibliothèque de classes Visual Basic .NET

Pour déboguer un composant .NET, vous devez passer par un EXE .NET. Pour cela, j'utilise le même EXE .NET décrit plus haut (utilisé essentiellement pour générer les fichiers XIML et varmap à partir des fichiers IML). J'ai trois scénarios de débogage. Le premier s'applique à la fonction Serialize appelée par Word. Le second est le débogage de la classe XimlCompiler (soit à partir de Word, soit depuis l'EXE .NET). Le troisième concerne le débogage du code source dans l'EXE .NET uniquement.

La figure 5 montre comment configurer la feuille de propriétés Debugging de l'EXE .NET pour exécuter WINWORD.EXE et ouvrir un document Word. Dans l'onglet Débogage de la page Propriétés de configuration, choisissez la zone Démarrer le programme externe comme option de Action de démarrage et entrez le chemin du fichier pour accéder à l'hôte (dans notre cas, il s'agit de Word 2002). Dans Options de démarrage, entrez le nom du chemin d'accès au document Word que vous voulez ouvrir. Lorsque vous appuyez sur F5, l'EXE .NET démarre Word (et non lui-même) et ouvre votre document. Quand votre code VBA dans Word appelle le composant .NET, vous pouvez définir des points d'arrêt dans Visual Studio .NET afin d'arrêter le traitement lors d'un appel provenant de Word.

Configuration du débogage à partir de Word

Figure 5. Configuration du débogage à partir de Word (cliquez sur l'image pour l'agrandir)

Dans le second scénario de débogage, j'ai besoin d'exécuter l'EXE .NET en déboguant les fonctions du composant .NET. Pour changer de scénario, je sélectionne la zone Démarrer le projet et je modifie la ligne de commande de façon à ouvrir un fichier IML. Je peux ensuite définir des points d'arrêt dans la classe XimlCompiler et déboguer ce code.

Débogage d'un fichier XML

Figure 6. Débogage d'un fichier XML (cliquez sur l'image pour l'agrandir)

Pour le troisième scénario, je garde les mêmes propriétés Debugging que dans la figure 6, mais je définis des points d'arrêt dans le code source EXE .NET.

Activation de COM Interop

Il est bien sûr indispensable de garantir l'interopérabilité entre le composant .NET et Word 2002 et, pour des raisons de performances, cette interopérabilité doit être essentiellement axée sur .NET hébergé par COM (et non l'inverse). Plus précisément, toutes les classes de composant et toutes les fonctions sauf une interagissent un minimum avec Word (pour mettre à jour la barre d'état), mais lorsque le document Word est parcouru, le composant .NET s'exécute sous l'espace d'adresses de Word. Le fait de parcourir un document dans un EXE .NET entraîne une baisse importante des performances due au marshalling inter-processus.

Pour activer COM Interop pour un composant .NET, il faut définir une option dans la feuille de propriétés du composant et ajouter certaines instructions dans son code source, comme nous allons le voir. Il est également nécessaire de définir une option dans la section Dépendances détectées du projet de déploiement de la page Propriétés du projet (comme indiqué ci-dessous).

L'option à définir est accessible depuis l'onglet Générer de la page Propriétés de configuration (voir la figure 7). Si la case Inscrire pour COM Interop est cochée, Visual Studio .NET va entourer d'un CCW (COM callable wrapper) le composant qui permet à COM d'interagir avec lui, comme s'il avait été écrit avec des constructeurs COM. Cette case permet également à Visual Studio .NET de fournir les entrées de registre Microsoft Windows® nécessaires (en appelant RegAsm.exe) et d'exporter la bibliothèque de types du composant (en appelant TlbExp.exe). Si le projet est reconstruit, la sélection de cette case entraîne la suppression des entrées de registre existantes et des fichiers de bibliothèques de types, avant leur recréation avec le code source mis à jour.

Activation de COM Interop

Figure 7. Activation de COM Interop (cliquez sur l'image pour l'agrandir)

La seconde étape pour activer COM Interop consiste à ajouter l'attribut adéquat dans le fichier AssemblyInfo.vb de notre composant. Voici la ligne que nous avons ajoutée dans le fichier qui a été automatiquement généré par Visual Studio .NET au moment de la création du projet :

<Assembly: ClassInterfaceAttribute(ClassInterfaceType.AutoDual)>

Nous aurions pu ajouter cet attribut (sans le préfixe Assembly:) aux deux classes du composant, mais il est plus simple de ne l'ajouter qu'à un endroit. Si, pour une raison quelconque, nous ajoutons une classe qui doit rester invisible pour le composant COM, nous devrons supprimer ClassInterfaceAttribute du fichier AssemblyInfo.vb et l'incorporer dans le fichier de classes du composant, de façon à marquer uniquement les classes à exposer.

La dernière étape pour permettre au composant COM d'instancier un objet basé sur notre composant .NET consiste à ajouter un constructeur vide pour chaque classe (voir le Listing 2).

Le client VBA

Cette section explique comment le client VBA utilise le fichier WordXml.Net.dll. Word 2002 dispose d'une référence à la DLL (un CCW, COM callable wrapper, généré par Visual Studio .NET après compilation du code source Visual Basic .NET). Les deux sections suivantes décrivent l'interopérabilité entre le code VBA et le CCW. Vous pouvez constater que la référence au CCW est établie selon le même processus de référencement d'un composant COM traditionnel dans VBA (à partir de la commande Références du menu Outils de l'éditeur Visual Basic, comme le montre la figure 4).

Compilation des zones de test en IML

La fonction serializeTestAreas et la procédure compileIml s'exécutent à partir du module WordXmlDotNet dans le modèle WordXml.dot. La procédure compileSpec appelle la fonction serializeTestAreas quand l'utilisateur sélectionne l'option Compile Test Areas to IML dans le menu Outils de WordXml.dot.

Function serializeTestAreas()
 Dim XmlProvider As New WordXml_Net.XmlProvider
 Dim result As Boolean
 Dim datestart As Date

 datestart = Now()
 Application.ScreenUpdating = False
 On Error GoTo handler
 result = XmlProvider.serializeTestAreas(rngTestAreas)
 On Error GoTo 0
 Application.ScreenUpdating = True

 Application.StatusBar = "Serialized " & _
 rngTestAreas.Paragraphs.Count & " nodes in " & _
 DateDiff("s", datestart, Now()) & " seconds."
 serializeTestAreas = True

Exit Function

handler:
 MsgBox ("Error serializing Test Areas:" & vbCr & _
 Err.Description)
 serializeTestAreas = False

End Function

Listing 16. Fonction serializeTestAreas de VBA

Compilation de l'IML en fichiers varmap

Après la compilation des données de spécifications en IML, Socrates compile l'IML en XIML et en fichiers varmap. L'exécutable de test utilise les fichiers varmap directement au moment de l'exécution. En fait, si le varmap est indisponible ou non valide, la compilation n'a pas lieu.

Sub compileIml()
 Dim XimlCompiler As New WordXml_Net.XimlCompiler
 Dim xsltPath As String
 Dim imlPath As String
 Dim imlFileName As String

 xsltPath = ActiveDocument.AttachedTemplate.Path & "\"
 imlPath = ActiveDocument.Path & "\"
 imlFileName = Replace(ActiveDocument.name, ".doc", ".xml")

 MsgBox XimlCompiler.compileXiml(Application, xsltPath, imlPath,
 imlFileName)

End Sub

Listing 17. Procédure compileIml

Là encore, vous pouvez constater l'utilisation du nom de l'assembly .NET dans le ProgID de XimlCompiler. En outre, une référence à l'objet Application en cours d'exécution est envoyée au composant .NET afin que la fonction compileXiml puisse mettre à jour la barre d'état de Word à mesure que le processus se déroule.

Déploiement de WordXml.Net

Cette dernière section explique comment utiliser Visual Studio .NET pour construire un fichier MSI de déploiement.

À l'exception de quelques points de détail à ne pas oublier et d'un cas spécial à éviter, la création de fichiers MSI dans Visual Studio .NET est très simple. Je traiterai du cas spécial dans l'annexe du présent document. La résolution du problème posé par la création d'un fichier MSI contenant une DLL utilisée à la fois par un modèle Word et par un EXE .NET représente la partie la plus pénible de mon aventure dans l'univers .NET et je tiens vraiment à vous éviter de souffrir autant que moi pour générer un projet de déploiement qui fonctionne parfaitement !

Pour commencer, une fois le nœud de ma solution sélectionné, j'ai choisi l'option Nouveau projet dans le menu Ajouter. Dans la liste des types, j'ai sélectionné Projets de configuration et de déploiement, j'ai entré dans la boîte de dialogue un nom de projet puis j'ai cliqué sur le bouton OK.

Les étapes suivantes sont importantes pour parvenir à un résultat correct. Je devais ajouter dans mon projet de déploiement les trois éléments suivants : l'EXE .NET (et les symboles), le modèle Word et tous les fichiers XML associés.

Pour ajouter l'EXE .NET et les symboles, j'ai cliqué avec le bouton droit de la souris sur le nœud du projet de configuration dans la fenêtre de l'Explorateur de solutions, j'ai pointé sur Ajouter, puis j'ai cliqué sur la commande Sortie du projet. J'ai sélectionné l'option WordXmlHost dans la liste des Projets ainsi que les éléments Sortie principale et Symboles de débogage dans la liste des groupes de fichiers (voir la figure 8).

Ajout de l'EXE .NET et des symboles

Figure 8. Ajout de l'EXE .NET et des symboles

Pour ajouter les symboles concernant le composant .NET, j'ai recommencé la procédure, en laissant simplement de côté le groupe Sortie principale.

À ce stade, je dois vous avouer que, lors de mes premières tentatives de construction d'un projet de développement pour l'application Socrates, j'ai inclus le groupe Sortie principale du composant .NET. Par conséquent, j'ai eu deux entrées sous le nœud Dépendances détectées (voir l'entrée entourée en rouge SqrtsDotNetAuthoring.dll dans la figure 9).

Au tout début, lorsque le projet de configuration affichait deux instances de SqrtsDotNetAuthoring.dll dans le nœud Dépendances détectées, il arrivait souvent que le code VBA ne parvienne pas à instancier la DLL .NET. Dès que j'ai supprimé la référence explicite à la DLL .NET dans le projet de déploiement (en ne gardant que l'entrée qui y a été placée parce que SqrtsDotNetAuthoring.dll est déjà une dépendance de sqrts.exe), cette erreur a disparu (mais il m'a fallu un nombre d'heures de débogage considérable avant de découvrir que cette double référence était à l'origine du problème d'activation d'un objet dans VBA). Aussi, ne faites pas comme moi : n'ajoutez pas votre composant .NET dans le projet de configuration, laissez l'EXE .NET le faire à votre place. Vous me remercierez plus tard.

Mais revenons à la procédure que j'ai suivie pour créer le projet de configuration WordXml.Net : j'ai ajouté le modèle Word et l'exemple de spécifications de test ainsi que les fichiers XSL et XML dans le projet de configuration, en pointant sur Ajouter puis en cliquant sur Fichier dans le menu contextuel à partir de la fenêtre Explorateurs de solutions. J'ai appuyé sur CTRL et cliqué sur tous les fichiers (sauf les fichiers .NET) dont j'avais besoin dans le fichier MSI. (Lors de la construction du projet de configuration Socrates, j'ai inclus d'autres fichiers, tels qu'un fichier de configuration XML et plusieurs fichiers de définition de schémas XML ; ces fichiers sont utiles pour modifier les spécifications de test mais inutiles pour l'application simplifiée WordXml.Net).

Enfin, j'étais prêt pour les dernières étapes du processus de configuration, à savoir :

  • l'exclusion de certaines dépendances détectées ;
  • la configuration du composant .NET à inscrire dans le registre Windows au moment de l'installation ;
  • l'ajout de l'option de création WordXml.Net dans le menu Programmes de Windows.

Chacune des dépendances détectées est signalée par un cercle barré dans l'angle inférieur gauche de l'icône (par exemple, MSWord.OLB dans la figure 9). La dépendance dotnetfxredist_x86_enu.msm est exclue par défaut et, pour exclure les trois autres, j'ai appuyé sur CTRL et cliqué sur chaque icône ; une fois les trois dépendances sélectionnées, j'ai cliqué avec le bouton droit de la souris sur l'une d'elles et j'ai choisi Exclure dans le menu contextuel.

Fichiers de configuration

Figure 9. Fichiers de configuration (cliquez sur l'image pour l'agrandir)

Je n'étais plus loin du but. Il suffisait de définir la propriété Register du nœud WordXml.Net.dll dans les dépendances détectées. Après avoir sélectionné le nœud, j'ai cliqué sur l'onglet Propriétés (dans ma configuration de Visual Studio .NET, cet onglet se trouve à droite de l'onglet Explorateur de solutions) pour + afficher l'écran représenté dans la figure 10. Une fois définie la propriété Register à vsdraCOM, je n'avais plus qu'à créer pour les utilisateurs une option de menu accessible depuis le menu Programmes.

Figure 10. Inscription du composant COM pendant l'installation (cliquez sur l'image pour l'agrandir)

Pour finir, je devais créer un raccourci pour accéder au modèle Word et le rendre disponible à partir du menu Programmes de Windows. Cette opération se déroule dans l'ordre suivant :

  1. Création d'un menu accessible à partir du menu Programmes de Windows.
  2. Création d'un raccourci vers le modèle Word.
  3. Déplacement du raccourci vers le nouveau menu.

Vous devez exécuter ces trois étapes à partir de la vue Système de fichiers du projet de configuration. Pour ouvrir la vue Système de fichiers, cliquez avec le bouton droit de la souris sur le nœud du projet de configuration dans la fenêtre Explorateur de solutions, pointez sur Afficher, puis cliquez sur Système de fichiers dans le menu contextuel. Pour créer une option de menu Programmes, sélectionnez l'option Menu Programmes de l'utilisateur, cliquez avec le bouton droit, pointez sur Ajouter et cliquez sur Dossier. Donnez un nom à ce dossier, comme je l'ai fait figure 11.

Création d'une option Programmes

Figure 11. Création d'une option Programmes

Il convient ensuite de créer un raccourci vers le modèle Word. Pour ce faire, j'ai sélectionné l'option wordXml.dot dans la liste de fichiers exposés lors de la sélection de l'option Dossier d'application à gauche de l'écran, puis le fichier wordXml.dot dans le volet droit (voir la figure 12). Il faut ensuite cliquer avec le bouton droit sur wordXml.dot, puis sur Créer un raccourci afin d'insérer un raccourci à la fin des fichiers répertoriés.

Création d'un raccourci vers le modèle Word

Figure 12. Création d'un raccourci vers le modèle Word

Pour que le nouveau raccourci figure dans le menu Programmes, il a suffi de le faire glisser vers le nœud WordXml.Net Authoring de l'option Menu Programmes de l'utilisateur (voir la figure 13). Notez le trait d'union dans le nom du raccourci. J'ai essayé d'utiliser les deux points, mais le compilateur n'en a pas voulu et j'ai dû adopter le trait d'union.

Ajout du raccourci dans le menu Programmes

Figure 13. Ajout du raccourci dans le menu Programmes

Désormais, lorsque l'utilisateur voudra sélectionner l'option WordXml.Net Authoring dans son menu Programmes, la commande pour démarrer le modèle Word s'affichera. Il lui suffira de cliquer sur la commande pour démarrer Word et ouvrir un nouveau document de spécifications de test.

Une fois le projet de configuration ajouté à la solution WordXml, Visual Studio .NET l'a ignoré et a renvoyé le message suivant : « La configuration du projet a été ignorée, car elle n'est pas sélectionnée dans cette configuration de solution ». Je voyais ce message pour la première fois et je ne trouvais rien dans le système d'aide de VS .NET qui puisse le justifier. J'ai donc sélectionné le nœud Solution dans l'Explorateur de solutions, puis activé la feuille de propriétés de ce nœud (voir la figure 14). Dans le volet Propriétés de configuration, j'ai remarqué que la case sous la colonne Générer n'était pas cochée pour le projet de configuration. Une fois cette case cochée, j'ai pu reconstruire la solution.

20030113_UsingDotNetFromVBA_14_th.gif

Figure 14. Feuille de propriétés de la Solution

Je me suis également souvenu d'un autre détail que je n'ai pas mentionné, à savoir que dans la feuille de propriétés du projet Configuration, j'avais affecté la valeur True à « RemovePreviousVersions ». Si je développe un jour une nouvelle version, le programme de configuration commencera par désinstaller WordXml, en supposant que j'incrémente la propriété Version de mes versions ultérieures (ce qui déclenchera une fonction UpgradeCode différente que je confirmerai lorsque le projet de Configuration m'y invitera, une fois la propriété Version modifiée).

Documentation supplémentaire

Cet article n'aurait pas été écrit sans l'aide généreuse des auteurs et développeurs Microsoft. Je remercie Siew-Moi Khor, Misha Shneerson, Ralf Westphal, Paul Cornell, David Guyer et Kenny Jones. Je recommande la lecture des articles suivants, et j'en oublie certainement d'autres.

Paul Cornell a écrit un excellent article, Introducing .NET to Office Developers ( leave-msdn france Site en anglais), qui présente les différentes technologies .NET utilisables avec Microsoft Office. Paul traite également d'un sujet proche du mien. La différence est que l'article de Paul, Creating Office Managed COM Add-Ins with Visual Studio .NET ( leave-msdn france Site en anglais), explique comment écrire des compléments pour Word en utilisant .NET, alors que mon article est plus rudimentaire puisqu'il décrit l'utilisation de VBA pour tirer parti de l'efficacité de .NET Framework.

Siew-Moi Khor et Misha Shneerson se sont associés pour écrire une « trilogie » sur l'utilisation du code géré dans des hôtes non gérés tels que VBA. Ces articles, comme ceux de Paul, sont d'un niveau très élevé concernant l'utilisation de compléments COM dans Word. Voici la liste de ces articles :

Je vous renverrai ultérieurement à ces articles lorsque je remanierai le code VBA de Socrates sous la forme d'un complément COM utilisant des balises actives.

Ralf Westphal a écrit un très bon article qui décrit une approche plus élaborée que celle que j'ai adoptée dans Socrates pour utiliser la classe XmlTextReader. Dans Implementing XmlReader Classes for Non-XML Data Structures and Formats ( leave-msdn france Site en anglais), Ralf décrit un objet XmlTextReader générique dérivé de la classe de base abstraite System.Xml, à savoir XmlReader. Bien que ce dernier ne contienne pas d'exemple de classe XmlWordReader, j'ai l'intention d'utiliser les idées de Ralf autant que possible lorsque je réécrirai la fonction SerializeTestAreas dans ma classe XmlProvider. Ma démarche sera différente de celle Ralf sur un point :, lui utilise XSD pour définir la conception de son objet XmlReader personnalisé, mais il ne s'en sert pas au moment de l'exécution. Dans mes expérimentations, j'ai inclus le XSD au moment de l'exécution pour obtenir un objet XmlWordReader de validation. Mais ceci est une autre histoire.

Pour plus de détails sur les projets de déploiement et de configuration, consultez les articles de Kenny Jones. Ceux-ci constituent la meilleure source d'information pour tout ce qui concerne les fichiers MSI et Visual Studio .NET. Dans mon article, j'ai juste fait mention de MSI. Pour en savoir plus, lisez les articles de Kenny.

Enfin, je remercie David Guyer dont la patience et le dévouement m'ont permis de découvrir un monde jusqu'alors inconnu. David m'a tiré des nombreux guêpiers dans lesquels je m'étais engouffré du fait que j'expérimentais à mesure que j'apprenais. Avec son aide, j'espère avoir écrit un article qui démystifie le processus MSI, en le rendant plus simple à implémenter pour les développeurs qui souhaitent optimiser leurs codes VBA avec des composants .NET.

Conclusion

Ce document concerne la multitude de programmes qui, à partir de Word 2002, permettent d'obtenir des fichiers XML utilisables pour l'automatisation des tests de logiciel. Ces programmes sont conçus de façon à optimiser les performances et à accroître leur simplicité d'emploi. Un composant Visual Basic .NET utilise du XML géré pour tirer parti des techniques de flux XML des classes System.Xml.XmlTextReader et System.Xml.XmlTextWriter en vue de traiter des documents Word volumineux. Il suffit de définir quelques options dans Visual Studio .NET avant de compiler le composant, et d'ajouter quelques lignes de code dans les fichiers source pour obtenir un composant .NET utilisable dans Word 2002.

Il me semble judicieux de terminer cet article en proposant un tableau récapitulant les règles que j'ai appliquées pour prendre des décisions et harmoniser au mieux les techniques de traitement XML existantes avec mes besoins de programmation.

Nécessité d'un cache XML ? Parcours basé sur l'état ? J'ai utilisé. . .  Dans. . . 
Non Non XmlTextWriter

XmlNodeReader

XmlProvider.Serialize

XimlCompiler.Compile

Oui Oui XPathNavigator et XSLT XimlCompiler.CompileXiml

Annexe

AssemblyInfo.Vb

Les attributs de l'assembly qui sont en gras contiennent :

  • Le texte utilisé par VBA pour ajouter une référence à cet assembly (voir la figure 4).
  • L'attribut qui permet à l'objet COM de voir l'interface du composant.

L'instruction Imports en gras est nécessaire pour la référence non qualifiée à l'attribut ClassInterfaceAttribute.

Imports System.Reflection

Imports System.Runtime.InteropServices
' Les informations générales sur un assembly sont contrôlées via
' l'ensemble suivant d'attributs. Il faut changer les valeurs de ces attributs pour
' modifier les informations associées à un assembly.

' Révision des valeurs des attributs de l'assembly

<Assembly: AssemblyTitle("WordXml.Net")>
' l'attribut suivant est un nom convivial lorsque l'assembly est
' ajouté dans des références COM
<
Assembly: AssemblyDescription("WordXml.Net.Authoring")
 >
<Assembly: AssemblyCompany("Microsoft Corporation")>
<Assembly: AssemblyProduct("WordXml.Net Authoring Template")>
<Assembly: AssemblyCopyright("2002")>
<Assembly: AssemblyTrademark("Microsoft Corporation")>
<Assembly: CLSCompliant(True)>

<Assembly: ClassInterfaceAttribute(ClassInterfaceType.AutoDual)>


' Le GUID suivant correspond à l'ID de la bibliothèque de types
'si ce projet est exposé au composant COM
<Assembly: Guid("88A80136-9318-4798-B0A4-5FE3121A0D96")>

' Les informations de version d'un assembly contiennent les quatre
 valeurs suivantes :
'
' Version principale
' Version secondaire
' Numéro de version
' Révision
'
' Vous pouvez spécifier toutes les valeurs ou accepter les numéros de
 'version et de révision par défaut en utilisant '*' comme ci-dessous :

<Assembly: AssemblyVersion("1.0.*")>

Listing 18. AssemblyInfo.vb

Précautions

Faites très attention lorsque vous choisissez des noms d'assembly car ils risquent d'entrer en conflit avec les noms de classes et de modules VBA.

Selon la séquence des événements, vous pouvez rencontrer l'erreur suivante lors de la compilation de la solution VS.Net :

The file 'SqrtsDotNetAuthoring.dll' cannot be copied to the run
 directory. (Le fichier 'SqrtsDotNetAuthoring.dll' ne peut pas
 être copié dans le répertoire d'exécution.)The process cannot
 access the file because it is being used by another
 process. (Le processus ne peut pas accéder au fichier
 car il est déjà utilisé par un autre processus.)

Cette erreur vient de ce qu'une instance au moins de WINWORD.EXE contient une référence au composant. Pour vous assurer que tous les processus WINWORD.EXE sont fermés, ouvrez le Gestionnaire des tâches et choisissez le tri par Nom de l'image. Vérifiez que vous avez fermé Word, puis sélectionnez chaque image restante de WINWORD.EXE et cliquez sur le bouton Terminer le processus.

Au début de mon travail de développement (avant que j'aie compris qu'il fallait éviter d'ajouter une référence Sortie du projet explicite dans mon composant .NET), la boîte de dialogue suivante apparaissait régulièrement après chaque reconstruction du fichier sqrts.dll.

Boîte de dialogue signalant une erreur d'automatisation

Figure 15. Boîte de dialogue signalant une erreur d'automatisation

Cette anomalie est liée à celle évoquée plus haut, lorsque VBA refusait de créer un objet sur la base des classes .NET. Je rencontre également ce type d'erreur lorsque je déplace mes projets Visual Studio .NET (par exemple, de mon ordinateur de développement sur mon portable), puis que je les recompile immédiatement et que j'utilise un fichier de spécifications associé à une autre instance de SqrtsDotNetAuthoring.dll.

La solution qui fonctionne en général (et fonctionne toujours dans le scénario d'utilisation d'un ancien document Word avec une nouvelle instance de DLL .NET) est la suivante :

  1. Ouvrez l'éditeur VBA pour le document Word qui pose problème.
  2. Sélectionnez le modèle Sqrts (dans mon cas).
  3. Désactivez la référence Smx.Test.Infra.Sqrts.Net.Authoring (toujours dans mon cas) (voir la figure 4).
  4. Cliquez sur OK.
  5. Recommencez le processus uniquement en resélectionnant le composant .NET.

Ce processus lie à nouveau la DLL correcte au fichier de spécifications lorsque le modèle sqrts.dot et les documents basés sur celui-ci sont déplacés.

Ainsi, si vous êtes prudent lorsque vous déplacez un code de développement, votre modèle VBA basé sur .NET fonctionnera correctement. Je dois ajouter qu'aucune de ces remarques ne s'adressent à vos utilisateurs, elles ne sont destinées qu'aux développeurs. Les utilisateurs ont de leur côté l'assurance que le programme d'installation Windows va installer tous les fichiers et les inscrire correctement, de sorte que leur première impression ne peut être que bonne.

Spécification fonctionnelle Sqrts.Net

Cette section décrit les opérations qu'exécute le système nommé Socrates (ou Sqrts). La section suivante, « Spécification de programme », décrit le fonctionnement de Socrates.

Public visé

Socrates est conçu pour les testeurs de logiciel et leurs responsables. Il aide les testeurs à écrire des spécifications de test de manière à la fois structurée et souple. L'efficacité de Socrate vient de ce qu'il tire parti de la puissance de traitement du XML. Ainsi, une fois que le contenu d'un document Word est sérialisé en XML, la spécification devient exécutable. Plus précisément, elle représente une base pour les programmes de test axés sur des données et elle fournit en même temps des données d'exécution. Du fait que les données XML incluent les noms de classes du code de test implémenté, les spécifications ne sont pas un document inactif mais, au contraire, actif, qui est toujours synchrone avec le code des tests. En d'autres termes, si les spécifications et l'exécutable du test ne sont pas synchronisés, l'exécutable ne fonctionne pas.

Puisque les spécifications de test ne sont pas uniquement en XML et qu'elles sont étroitement liées aux exécutables, un gestionnaire des échecs liés au XML peut, en cas d'échec d'un test, afficher uniquement les données susceptibles d'être en rapport avec l'échec du test. Ceci permet de réduire le temps nécessaire au débogage et permet aux testeurs qui ne sont pas les auteurs des tests de les exécuter, d'analyser les défaillances et de créer des bogues à confronter au code produit.

Les gestionnaires y trouvent leur compte car les rapports des tests élaborés, implémentés et exécutés sont gratuits. Concrètement, les testeurs peuvent se consacrer à concevoir des tests réellement opérationnels au lieu de passer leur temps à mettre à jour une feuille de calcul Microsoft Excel indiquant dans le détail combien de tests positifs, négatifs, de sécurité ou généraux ont été écrits, ou encore quel pourcentage représentent les tests de base, fonctionnels, d'intégration, de contrainte ou d'acceptation. Les rapports sont le résultat d'une simple conversion XSL des données XML qui contrôlent les tests.

En effet, Microsoft Word devient alors un éditeur XML.

Objectifs de conception
  • Permettre aux testeurs d'écrire des spécifications de test claires et bien structurées dans un délai le plus court possible.
  • Garantir que toutes les spécifications de test sont écrites et s'affichent dans le même format, ce qui raccourcit ensuite le temps de révision.
  • Permettre aux testeurs d'écrire des tests basés sur des données afin d'élargir le domaine d'application des tests sans accroître le temps nécessaire pour écrire et gérer les exécutables de ces tests. En d'autres termes, les exécutables des tests sont plus efficaces car les données d'exécution ne sont pas compilées de façon statique.
  • Permettre aux testeurs d'utiliser des structures d'automatisation des tests, ce qui réduit le temps nécessaire pour écrire les tests et améliore la qualité et la cohérence de tous les tests écrits par les testeurs de l'équipe.
  • Automatiser les tests manuels en affichant les étapes manuelles dans un formulaire Web et en stockant les résultats de ces processus manuels dans des fichiers XML ou des tables de base de données qui facilitent la génération des rapports et leur analyse.
Fonctionnalités
  • Interface utilisateur qui utilise de manière cohérente des objets Word pour capturer les données des tests en vue de simplifier la sérialisation en XML.
  • Deux compilateurs XML : un qui sérialise le contenu Word en XML et un second qui retraite le premier XML pour le convertir en formats XML utilisables pour automatiser le code de test et afficher des formulaires Web (pour des tests automatisés et manuels, respectivement).
  • Une version HTML des spécifications (quasiment identique en apparence au document Word original) qui transforme le XML des spécifications en HTML.
  • Des formats de type navigateur, formulaire Windows et Service Web XML pour la restitution des tests manuels et l'enregistrement des résultats de ces tests.
  • Intégration avec un logiciel de gestion des échecs de test afin de permettre aux techniciens de voir les spécifications pour chaque variante de test défaillant (automatisé ou manuel).
Workflow

Les testeurs commencent leur vérification en décrivant les tests en termes de Sets, Levels et Vars. Les spécifications de test contiennent aussi un tableau qui indique les ensembles (Sets) appartenant à tel ou tel exécutable, ainsi qu'un tableau indiquant les ensembles utilisés par les différentes classes pour exécuter les opérations standard de configuration et de nettoyage. Les testeurs définissent les données d'exécution soit en répertoriant les arguments que transmet une variante d'un test, ainsi que les valeurs possibles de chaque argument, soit en indiquant la valeur de chaque argument pour chacune des variantes du test. Dans le premier cas, Socrates génère un nombre de cas de test qui correspond au produit vectoriel du nombre d'arguments par leurs valeurs possibles. Dans le deuxième cas, le testeur spécifie explicitement les données d'entrée de chaque version du test.

Une fois les spécifications de test élaborées et approuvées par le système de gestion des tests, le testeur utilise les options de menu de l'interface Socrates pour générer une copie HTML du document Word et un fichier XML représentant toutes les données figurant dans les spécifications. Le vocabulaire XML est le langage IML (Intermediate Markup Language ; il joue, pour l'exécutable de test, le même rôle le langage MSIL (Microsoft Intermediate Language) joue pour le CLR (Common Language Runtime) de Microsoft.

Une fois que le testeur a fini de modifier l'IML, il doit générer les données pour le cas de test complet. Il peut alors obtenir un nombre de test nettement supérieur à celui défini dans les spécifications. Chaque test réel inclut les données d'exécution définies dans la phase de conception. Le fichier complet IML est appelé XIML. Automatiquement, Socrates traite le XIML une fois de plus et génère des fichiers XML séparés, appelés varmap.

Une fois que le testeur a écrit le code d'implémentation du test, il modifie les spécifications avec les noms de classes qui implémentent chaque variante de test. En outre, il peut modifier les spécifications qui, une dernière fois, ajoutent ou modifient des attributs au niveau des nœuds XML qui activent et désactivent les tests (en fonction de l'état des bogues précédemment découverts dans le logiciel testé). Enfin, le testeur peut assigner des propriétaires différents à des exécutables de test différents.

Il faut ensuite procéder aux tests en exécutant les fichiers varmap (générés par les compilateurs XML de Socrates) dans le contexte d'automatisation des tests. Chaque variante spécifie la classe à exécuter et les données à utiliser. Ainsi, seules les classes spécifiées s'exécutent, et c'est ce qui permet d'assurer la synchronisation entre les spécifications et l'exécutable du test.

Socrates restitue les résultats des tests manuels dans des formulaires Web contenant des cases d'option pour marquer la réussite ou l'échec de chaque variante. Les étapes détaillées dans les spécifications de test apparaissent dans le formulaire Web de sorte que le testeur peut exécuter le test manuellement. Chaque clic sur la case d'option du formulaire Web met à jour le fichier varmap du test manuel. Une fois implémentés comme service Web XML, ces fichiers varmap peuvent s'adresser à de nombreux utilisateurs, de sorte que plusieurs testeurs peuvent exécuter les tests manuels. Chaque fois qu'un testeur met à jour le fichier varmap, le formulaire Web est actualisé en conséquence, ce qui permet à tous les testeurs de voir les modifications effectuées par d'autres testeurs.

Spécifications de programme

Cette section décrit les classes XML et XSL gérées utilisées pour implémenter le mécanisme de spécifications de test exécutable. Ces classes XML sont appelées depuis VBA dans Word, à partir de la ligne de commande avec un EXE .NET ou à partir d'un formulaire Web.

Architecture

L'objectif principal du système Sqrts.NET est de sérialiser le contenu binaire provenant de documents Microsoft Word 2002 dans un format XML utilisable par les moteurs d'exécution de test tels que le moteur Managed Code Framework (voir l'étape 3 dans la figure 16).

Compilateurs Sqrts.NET

Figure 16. Compilateurs Sqrts.NET

Comme vous le constatez, le composant principal dans cette architecture est le fichier SqrtsDotNetAuthoring.dll. Ce composant est appelé à la fois par Word 2002 (plus particulièrement, par le modèle sqrts.dot) et (facultativement) par un EXE .NET (sqrts.net.varmap.compiler.exe). SqrtsDotNetAuthoring.dll convertit d'abord le contenu binaire de Word en IML (étape 1 dans la figure 16). IML est un schéma XML. L'IML peut contenir des nœuds XML qui représentent plusieurs cas de test pour une variante de test spécifique, de sorte que le composant .NET doit ensuite traiter l'IML en développant tous les cas implicites en éléments Var explicites (étape 2 dans la figure 16).

Cet IML étendu (XIML) peut ne pas encore convenir aux frameworks de code de test ; par exemple, le fichier XIML peut contenir plusieurs nœuds varmap (un ou plusieurs pour des tests automatisés ou pour des tests manuels). Par conséquent, une étape supplémentaire, la dernière, est nécessaire. Au cours de cette étape, le composant .NET utilise du XML géré pour enregistrer chaque nœud varmap de test automatisé dans un fichier distinct, en laissant les nœuds varmap de tests manuels dans le fichier XIML (étape 3 dans la figure 16).

Pour résumer, le contenu Word passe par un pipeline XML qui est, au fil des traitements, changé en une série de fichiers XSLT et XML gérés. Le processus renvoie trois fichiers XML, chacun ayant sa propre finalité. Le premier fichier XML est une sérialisation directe du contenu Word ; le second développe certains éléments du premier XML de façon à fournir plus de données de test que ce qui a été entré dans Word ; le troisième se présente dans un format adapté qui permet son exécution par le mécanisme d'automatisation des tests logiciels. Notre objectif était d'utiliser une technologie souple pour lier étroitement nos spécifications de test à nos exécutables de test. Notre devise est « à nouvelles spécifications, nouveau code ».

Dernière mise à jour le lundi 13 janvier 2003

Pour en savoir plus