Utilisation d'OpenXML pour réaliser un mailing en C++ et C

A travers cet article je vais décrire l'interface C++ et C que j'ai créée pour réaliser un mailing en utilisant OPenXML le nouveau format libre de Word 2007

Article lu   fois.

L'auteur

Profil ProSite personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Introduction

La génération de courriers d'après un modèle est un sujet auquel je suis souvent confronté dans mes applications de gestion.
Jusqu'à présent on disposait de l'automation pour réaliser ce genre de tâche.
Cette interface com a évoluée au fil des ans et des versions successives d'office, mais il n'est pas toujours très facile de trouver de la documentation sur le sujet, et surtout :
-On est obligé de disposer de Word sur le poste de traitement.
-Le manque de rapidité de l'interface peut être un frein à la génération d'un volume important de document.

Avec office 2007, Microsoft met en avant une nouvelle structure de document l'openXML.
Ce nouveau format de données n'utilise que des standards ouverts comme : ZIP, XML, XMLSchema, png,…
Je ne vais pas rentrer dans les détails sur le format OpenXML nombre d'articles sur Internet sont disponibles sur le sujet.

L'accès aux différents éléments constituants le fichier est très simple:
Celui-ci étant au format zip il est aisé de le renommer, et d'accéder aux différents éléments le constituant.
Du coup il devient relativement facile de manipuler le fichier XML document.xml contenant les données.
Vous utilisez encore office 2003?
Qu'à cela ne tienne, il existe un pack de compatibilité Microsoft Office pour les formats de fichier Word, Excel et PowerPoint 2007: http://www.microsoft.com/downloads/details.aspx?displaylang=fr&FamilyID=941b3470-3ae9-4aee-8f43-c6bb74cd1466

II. Objectif mailing en C++ ou en C

J'ai annoncé la couleur dans le titre, mon objectif est de réaliser une interface en C++ et C permettant de générer des documents Word au format .docx, mon outil de développement sera Visual C++ 2008, néanmoins la solution sera aussi compilable en Visual 6.0 qui est encore beaucoup utilisé.
Pour cela je vais avoir besoin de deux bibliothèques :
Une pour gérer des fichiers au format ZIP, j'ai choisi ZipArchive.
Une autre pour gérer un fichier au format XML mon choix s'est porté sur TinyXML.

Détails du traitement :
Un fichier modèle servira de base pour générer les documents.
Les zones à remplacer dans mon cas seront délimitées mais le choix reste libre.
Exemple :
Pour un simple mailing je vais devoir remplir automatiquement l'adresse du destinataire que je vais écrire comme suit :

 
Sélectionnez

<NOM> <PRENOM>
<ADRESSE1>
<ADRESSE2>
<CDP> <VILLE>

Le traitement devra donc repérer ces zones dans le fichier XML et les remplacer par les données réelles.
Après avoir mis en place ces éléments je me suis intéressé aux tableaux, le but étant de pouvoir repérer un tableau grâce au texte d'une cellule et de fournir les traitements suivants:
- Connaitre la taille du tableau en ligne et colonne.
- Sélectionner une ligne et/ou une colonne pour récupérer ou mettre à jour le texte d'une cellule.
- Rajouter une ligne d'après une ligne existante.

Une génération de courrier suivra le cycle de traitement suivant :
1) Déclaration du fichier modèle.
2) Décompression du fichier dans un répertoire temporaire portant le nom du fichier.
3) Réalisation d'une copie de sauvegarde du fichier document.xml.
4) Analyse du fichier document.xml avec TinyXML.
5) Spécification des zones types à remplacer avec leurs valeurs respectives par l'utilisateur.
6) Sauvegarde du fichier XML par TinyXML.
7) Reconstruction d'un fichier .docx portant le nom spécifié par l'utilisateur.
8) Restitution du fichier document.xml d'origine d'après la copie.
9) Retour à la phase 4).

III. Programmation de l'interface

Contraintes :
- Je veux disposer d'une classe de traitement en C++ et d'un jeu de fonctions en C contenus dans une DLL autonome.
- Mon interface fonctionnera dans un environnement Unicode ou multi-bytes.
Pour faciliter ce traitement j'utiliserai la classe ATL/MFC CString qui permet de passer facilement entre les deux modes.
Comme je suis conscient que cette interface pourrait être utilisée sans les MFC (cas de la dll en C pour réduire la taille),
je proposerai un mode de compilation proposant les classes CString CStringA et CStringW de substitution dans un environnement non MFC ou les classes CStringA et CStringW avec Visual C++ 6.0.
Ce jeu de classes s'appuiera sur la classe string de TinyXML que j'ai remaniée et complétée en classe patron (Template) pour manipuler le mode Unicode ou multi-bytes.

-Le format OpenXML codifie les données selon la norme UTF-8 (UCS transformation format 8 bits), celle-ci permet de coder de l'Unicode sous une suite de 4 octets maximum.
Les caractères dont le code ASCII est supérieur à 127 sont codés sur plusieurs octets.
Il faudra donc disposer d'une fonction de décodage et d'encodage des données pour modifier le fichier XML.
Après quelques recherches sur Internet je n'ai pas trouvé de solution satisfaisante.
J'ai donc réalisé deux fonctions pour ces traitements d'après la description donnée dans Wikipédia

 
Sélectionnez

//-------------------------------------------------------------------------
CStringA  UTF8EncodeString(const CStringA str)
{
   CStringW input(str);
   CStringA output;

#if _MSC_VER <= 1200
   for(int i=0; i < input.GetLength(); i++)
#else
   for(unsigned int i=0; i < input.GetLength(); i++)
#endif
   {
      // 0xxxxxxx
	  if(input[i] < 0x80)
      {
		output+=input[i];         
      }	   
	  // C0		  80	
      // 110xxxxx 10xxxxxx	  
      else if((input[i] > 0x7F) && (input[i] < 0x800))
      {
	     output+=static_cast<unsigned char>(0xC0 | (input[i] >> 6));
         output+=static_cast<unsigned char>(0x80 | (input[i] & 0x3F));			          
      }
	  // E0       80       80
      // 1110xxxx 10xxxxxx 10xxxxxx
      else if(input[i] < 0x8000)
      {
		output+=static_cast<unsigned char>(0xE0 | (input[i] >> 12));         
		output+=static_cast<unsigned char>(0x80 | (input[i] >> 6 & 0x3F));         
        output+=static_cast<unsigned char>(0x80 | (input[i] & 0x3F));
      }
	  else
	  {  // F0      80       80       80
		 //11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
		output+=static_cast<unsigned char>(0xF0 | (input[i] >> 12)>>6);
		output+=static_cast<unsigned char>(0x80 | (input[i] >> 12 & 0x3F));
		output+=static_cast<unsigned char>(0x80 | (input[i] >> 6 & 0x1F));
        output+=static_cast<unsigned char>(0x80 | (input[i] & 0x3F));
	  }
   }
   return output;
} 
//-------------------------------------------------------------------------
CStringA UTF8DecodeString(const CStringA input)
{   
   CStringW output;
   #if _MSC_VER <= 1200
   for(int i=0; i < input.GetLength();i++)
   #else
   for(unsigned int i=0; i < input.GetLength();i++)
   #endif
   {  
	  // F0      80       80       80
	  //11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
	  if((input[i] & 0xFE) == 0xFE)
	  {
		output+= static_cast<wchar_t>(((input[i] & 7 )<< 18) | ((input[i+1] & 0x3F) << 12) | ((input[i+2] & 0x3F) << 6) | (input[i+3] & 0x3F));
		i += 3;
	  }
	  else
      // E0       80       80
      // 1110xxxx 10xxxxxx 10xxxxxx
      if((input[i] & 0xE0) == 0xE0)
      {
         output+= static_cast<wchar_t>(((input[i] & 0x0F) << 12) | ((input[i+1] & 0x3F) << 6) | (input[i+2] & 0x3F));
         i += 2;
      }
	  // C0       80
      // 110xxxxx 10xxxxxx
      else if((input[i] & 0xC0) == 0xC0)
      {
         output+= static_cast<wchar_t>(((input[i] & 0x1F) << 6) | (input[i+1] & 0x3F));
         i ++;
      }
      // 0xxxxxxx
      else if(input[i] < 0x80)   output+=input[i];           
   }
   return CStringA(output);
}
// tests:
CStringA input,output;
input="é";
output=UTF8EncodeString(input);
TRACE("\n output é:%s",static_cast<const char *>(output));
output=UTF8DecodeString(output);
TRACE("\n output é:%s",static_cast<const char *>(output));
input="";
output=UTF8EncodeString(input);
TRACE("\n output :%s",static_cast<const char *>(output));
output=UTF8DecodeString(output);
TRACE("\n output é:%s",static_cast<const char *>(output));

Notes:
-Une chaine au format UTF8 peut être contenue dans une CString multi bytes.
-j'ai implémenté la codification sur 4 octets mais l'exemple de Wikipédia avec le "?" ne semble pas correct dans ce cas puisque son code ASCII est égal à 63.
-Ce code ne fonctionne pas directement avec Visual 6.0 à cause des CString: On ne dispose pas de CStringA et de CStringW...

TinyXML génère un fichier XML " propre " avec des blancs pour implémenter l'indentation des balises XML, Word 2007 génère un fichier sans blancs et retours chariots pour avoir un fichier compact.
J'ai donc modifié TinyXML pour disposer d'un mode permettant d'ignorer l'indentation et les retours chariots pour gagner en compacité.
Au problème rencontré, TinyXML tronque le dernier blanc d'une donnée avant le caractère d'ouverture de la balise de fin champ.
J'ai dû procéder à une correction pour éviter d'avoir des mots collés dans le document final, ce qui est gênant pour un document Word…

IV. Composants de la solution

Ma solution se composera de quatre composants, un par type de traitement :
-Une classe de décompression d'un fichier en répertoire portant le nom du fichier.
-Une classe de compression d'un répertoire en un fichier.
-Une classe d'analyse du fichier document.xml.
-Une classe de Traitement pour le mail fédérant les autres objets.

IV-a. Compression et décompression du document

Commençons par les classes de compression et décompression présentées dans le diagramme ci-dessous :

Image non disponible

Les classes UnZipFile et ZipFiles héritent de la classe de base BaseZip.
Celle-ci contient quelques fonctions statiques et un mécanisme virtuel pour l'affichage des informations pendant la phase de compression ou de décompression.
Ces informations sont transmisses par l'intermédiaire de la classe ZipCallBack qui possède un pointeur sur la classe de base BaseZip.

La classe UnZipFile :
Son rôle :
Décompresser un fichier dans un répertoire portant le nom du fichier sans l'extension.
Si le répertoire racine de destination n'est pas spécifié le répertoire temporaire de Windows sera utilisé.

Descriptions des méthodes :

 
Sélectionnez

UnzipFile(const TCHAR *szdocument=NULL,const TCHAR *szFolderTemp=NULL)

Constructeur par défaut, si szDocument est défini on procède à la décompression dans le répertoire Racine szFolterTemp, si elle est nulle dans le répertoire temporaire Windows.

 
Sélectionnez

void		SetZipPath(const TCHAR *szPath):

Réglage chemin d'origine du document zip

 
Sélectionnez

CString	GetZipPath(){return m_strZipPath;}

Récupération du chemin d'origine du zip

 
Sélectionnez

void		SetFolderTemp(const TCHAR *szFolderTemp);

Fixe le répertoire de travail pour la décompression, par défaut si szFolderTemp est null Windows\temp

 
Sélectionnez

CString	GetFolderTemp(){return m_strFolderTemp;}

Renvoie le chemin du répertoire de travail

 
Sélectionnez

CString	GetUnZipFolder();

Renvoie le chemin du dossier généré dans le répertoire de travail, c'est à dire le répertoire de travail + le nom du document.

 
Sélectionnez

CStringA	GetLastError(){return m_strError;}

Renvoie sous forme de chaine la dernière erreur récupérée de GetLastError()

 
Sélectionnez

Virtual void OnInitRange(DWORD nSizeFile,const TCHAR *szFileName){}

Fonction virtuelle appelée pour renseigner une interface graphique sur la tâche en cours.
Attention szFileName peut être nulle, pour initialiser un range d'un progressCtrl on écrira SetRange(0,nSizeFile)

 
Sélectionnez

virtual		void Progress(int iProgress){}

Appelée lors de la décompression pour indiquer l'avancement.

 
Sélectionnez

bool Unzip();

Procède à la décompression peut être appelée par le constructeur. Utilisation :

 
Sélectionnez

try
	{
		UnzipFile Unzip(_T("Test.docx"));
	}
	catch(std::exception& e)
	{
		 afxDump << _T("erreur") << e.what() ;
	}

// Ou

UnzipFile Unzip;
Unzip.SetZipPath(_T("Test.docx"));
if(!Unzip.Unzip())
	afxDump << _T("erreur") << Unzip.GetLastError() ;

La classe ZipFile:
Son rôle :
Compresser en un fichier les éléments contenus dans un répertoire.
Le nom du fichier correspondant au nom du répertoire, l'extension du fichier par défaut étant .zip.

Descriptions des méthodes :

 
Sélectionnez

ZipFiles(const TCHAR *szPathdocumentToZip,const TCHAR *szTargetFolder=NULL)

constructeur par défaut ,szPathdocumentToZip indique le chemin du répertoire à compresser, le fichier résultant est stocké à l'emplacement szTargetFolder où dans le répertoire courant si cette variable est nulle.

 
Sélectionnez

void		SetPathDocumentToZip(const TCHAR *szPath)

Fixe le chemin du répertoire à zipper, initialise par défaut le nom du zip au nom du répertoire.

 
Sélectionnez

CString	GetPathDocumentToZip()

Renvoie le chemin du répertoire à zipper.

 
Sélectionnez

void		SetTargetFolder(const TCHAR *szTargetFolder)

Fixe le répertoire de destination du document, si szTargetFolder est nulle : le répertoire courant.

 
Sélectionnez

CString		GetTargetFolder()

Renvoie le répertoire de destination du document

 
Sélectionnez

void		SetZipName(const TCHAR *szZipName)

Fixe le nom du document Zip final, si le chemin est fourni SetTargetFolder est appelée pour mémoriser le chemin.

 
Sélectionnez

CString		GetPathZipName();

Récupération du chemin du document généré : le répertoire de destination + le nom de l'archive fixée par SetZipName .

 
Sélectionnez

virtual		bool OnFilterFile(const TCHAR *szCurrentFile)

Permet de filtrer les fichiers à compresser, pour filtrer le fichier renvoyez false,renvoie true par défaut

 
Sélectionnez

CStringA	GetLastError()

Renvoie la dernière erreur détectée.

 
Sélectionnez

bool		Zip();

Compresse le répertoire désigné par SetPathDocumentToZip.

 
Sélectionnez

bool		AddFileInZip(const TCHAR *szPath);

Rajoute un fichier ou répertoire à l'archive en cours.

 
Sélectionnez

virtual void OnInitRange(DWORD nSizeFile,const TCHAR *szFileName)

Fonction virtuelle appelée pour renseigner une interface graphique sur la tache en cours.
Attention szFileName peut être nulle, pour initialiser une plage d'un progressCtrl on écrira SetRange(0,nSizeFile)

 
Sélectionnez

virtual		void Progress(int iProgress){}

Appelée lors de la décompression pour indiquer l'avancement.

 
Sélectionnez

virtual	CString GetDefaultExtZipFile(){return _T(".zip");}

Fonction virtuelle retournant l'extension par défaut du fichier généré, retourne .zip par défaut.

Utilisation :

 
Sélectionnez

ZipDocx Zip;
Zip.SetPathDocumentToZip(Unzip.GetUnZipFolder());
Zip.SetTargetFolder(NULL);
Zip.SetZipName(_T("essai"));
if(!Zip.Zip())
#ifdef _DEBUG
afxDump << _T("erreur ") << Zip.GetLastError() ;
#else
{
	str=_T("erreur")+Zip.GetLastError();
	AfxMessageBox(str);
}
#endif

IV-b. Analyse XML du document et mailing

Image non disponible
La classe ParseXMLDocument :
Son rôle :
Cette classe s'occupe d'analyser le fichier document.xml.
Chaque élément repéré comme étant du texte est stocké dans une liste :

 
Sélectionnez

std::list<TiXmlText*>	m_lXmlText;	

Celle-ci sera parcourue pour la recherche ou le remplacement de zones.
L'implémentation actuelle permet la recherche et le remplacement de zones existantes, ainsi que la modification de cellules ou l'ajout d'une ligne dans un tableau

Méthode de parcours du fichier document.xml:
En partant de la racine du document on énumère tous les éléments, pour chaque élément on va chercher tous les éléments enfants le constituant, le parcours est donc récursif.

 
Sélectionnez

void ParseNode(TiXmlNode* node)
{
	if(node->ToElement())
	{
		TiXmlNode *elem =node;
		for(elem=elem->FirstChild();elem;elem=elem->NextSiblingElement())
		{			
			if(elem->ToElement())
			{
				TiXmlAttribute* attrib=elem->ToElement()->FirstAttribute();
				for ( ; attrib; attrib = attrib->Next() )
				{
					#ifdef _DEBUG
					TRACE("\n Name:%s: Value:%s",attrib->Name(),attrib->Value());
					#endif
				}			
				ParseNode(elem);
			}
			else
			{
				if(elem->ToText())
				{				
					m_lXmlText.push_back(elem->ToText());
					#ifdef _DEBUG
					TRACE("\n (%s) Texte:%s",elem->Parent()->Value(),elem->Value());
					#endif
				}
			}
		}
	}
}
for (TiXmlNode* node=m_Document.FirstChild(); node; node=node->NextSibling() )
{		
	ParseNode(node);
}

Comme le montre le code ci-dessus le parcours est très simple, quand on détecte un élément de texte on le stocke dans la liste.

Méthode de reconnaissance d'un tableau:
Un tableau est identifié par la balise xml w:tbl, chaque ligne du tableau par la balise xml w:tr, chaque colonne par w:tc. A partir du moment ou l'on peut se positionner sur un emplacement du tableau en recherchant un élément de texte il devient facile de repérer la racine du tableau:
Il suffit de trouver le début de la ligne, et le parent immédiat d'une ligne, c'est la racine du tableau.

 
Sélectionnez

//------------------------------------------------------------
TiXmlNode* ParseXMLDocument::GetGridRowNode(TiXmlNode* pNode) // recherche de la ligne d'apres l'emplacement pNode
{	
	if(!CompareValue(pNode,_T("w:tr")))
	do
	{
		pNode=pNode->Parent();		
	}
	while(!CompareValue(pNode,_T("w:tr")));
	return pNode;
}
<code langage="cpp"><![CDATA[
//------------------------------------------------------------
TiXmlNode* ParseXMLDocument::GetGrid(TiXmlNode* pNode)
{
	if(CompareValue(pNode,_T("w:tbl"))) return pNode;
	TiXmlNode* pGrid=GetGridRowNode(pNode);
	return pGrid->Parent();
}


Note :
Le fichier étant au format UTF-8 les méthodes Replace et Find gèrent l'encodage et le décodage des chaines manipulées.

Descriptions des méthodes :

 
Sélectionnez

ParseXMLDocument(const TCHAR *szdocumentFile=NULL);

Constructeur par défaut, si szdocumentFile est non nulle Parse est appelée.

 
Sélectionnez

void	SetDocumentFile(const TCHAR *szdocumentFile);

Fixe le nom du document à analyser.

 
Sélectionnez

virtual bool	Parse();

Lance l'analyse

 
Sélectionnez

virtual TiXmlText* Find(const TCHAR *szText);

Cherche un texte et renvoie un pointeur sur la branche texte le contenant.

 
Sélectionnez

virtual bool Replace(const TCHAR *szTextSrc,const TCHAR *szTextDest);

Remplace un text szTextSrc par szTextDest dans tout le fichier XML.

 
Sélectionnez

virtual int	Replace(const std::vector<CString> &arTextSrc,const std::vector<CString> &arTextDest);

Remplace une liste de zone arTextSrc par des éléments contenus dans arTextDest

 
Sélectionnez

TiXmlNode*	GetGrid(const TCHAR *szText);

Retrouve la racine du grid d'après le texte d'une des cellules de ce grid. Renvoie null si la recherche échoue

 
Sélectionnez

TiXmlNode*	GetGrid(TiXmlNode* pNode);

Retrouve la racine du grid d'après l'emplacement en cours. Renvoie null si la recherche échoue

 
Sélectionnez

TiXmlNode*	GetGridRowNode(TiXmlNode* pNode);

Retrouve la ligne courante à partir de l'emplacement en cours, (remonte dans l'arborescence) Renvoie null si la recherche échoue

 
Sélectionnez

TiXmlNode*	GetGridRowNode(TiXmlNode* pGrid,int nRow);

Retourne la ligne nRow pour pGrid, pGrid doit être le sommet du tableau (w:tbl) Renvoie null si la recherche échoue

 
Sélectionnez

void	DuplicateGridRow(TiXmlNode* pGrid,int nRow,int nAddRow=1);

Duplique une ligne à partir de la ligne nRow pour le grid désigné par pGrid.

 
Sélectionnez

bool	SetGridTextCol(TiXmlNode* pRowGrid,int nCol,TCHAR *szText);

Fixe le texte pour la ligne en cours pour la cellule nCol,l'indicage commence a 0.

 
Sélectionnez

bool	GetGridTextCol(TiXmlNode* pRowGrid,int nCol,CString &rText);

Renvoie le texte pour la ligne en cours pour la cellule nCol, l'indexage commence à 0.

 
Sélectionnez

int	GetGridColSize(TiXmlNode* pGrid);

Renvoie le nombre de ligne pour le grid désigné par pGrid

 
Sélectionnez

int   GetGridRowSize(TiXmlNode* pGrid);

Renvoie le nombre de colonne pour le grid désigné par pGrid

 
Sélectionnez

TiXmlNode *FindTextNode(TiXmlNode* pNode,TCHAR *szLabel);

Recherche la zone texte szLabel pour le nœud pNode et le label xml szLabel.
Renvoie null si la recherche échoue

 
Sélectionnez

bool	CompareValue(TiXmlNode* pNode,TCHAR *szToValue);

Renvoie true si pNode->Value()==szToValue

 
Sélectionnez

virtual bool WriteXML(const TCHAR *szdocumentFile=NULL,bool bOptimise=true);

Sauvegarde le fichier XML, si szdocumentFile est nulle prend le nom du document d'analyse.
si bOptimise==true enlève les blancs et les cr+lf pour gagner de la place.

Exemple d'utilisation :

 
Sélectionnez

	UnzipFile Unzip;
	Unzip.SetZipPath(_T("RELANCECLIENT.docx"));
	CString str;
	if(!Unzip.Unzip())
#ifdef _DEBUG
		afxDump << _T("erreur") << Unzip.GetLastError() ;
#else
	{
		str=_T("erreur ")+Unzip.GetLastError();
		AfxMessageBox(str);
	}
#endif
	else
	{
		ParseXMLDocument Parse(Unzip.GetUnZipFolder()+_T("\\word\\document.xml"));
        // cherche le tableau ayant une cellule nommée GRID
		TiXmlNode* pGrid=Parse.GetGrid(_T("<GRID>"));
		int nRow=Parse.GetGridRowSize(pGrid);
		int nCol=Parse.GetGridColSize(pGrid);
		// insere 2 lignes a partir de la ligne 1 du tableau.
		Parse.DuplicateGridRow(pGrid,1,2); 
         
        // récupère la ligne 1 du grid.
		TiXmlNode* pRow=Parse.GetGridRowNode(pGrid,1);
		// affecte les colonnes de la ligne pRow.
		Parse.SetGridTextCol(pRow,0,_T("01/08/2008"));
		Parse.SetGridTextCol(pRow,1,_T("31/08/2008"));
		Parse.SetGridTextCol(pRow,2,_T("Libellé"));
		Parse.SetGridTextCol(pRow,3,_T("2008150"));
		Parse.SetGridTextCol(pRow,4,_T("libellé essai"));
		Parse.SetGridTextCol(pRow,5,_T("100.00"));
		Parse.SetGridTextCol(pRow,6,_T("200.00"));
        // écriture finale du document.
		Parse.WriteXML(_T("document.xml"),false);
	}

La classe WordXML:
Son role :
C'est la classe d'interface pour gérer le mailing. Pour cela elle utilise les classes précédemment décrites, les composants de compression, décompression, d'analyse XML , ces objets devenant transparents à l'utilisation.

Elle permet entre autres quelques traitements sur le document final :
sa visualisation dans Word, son impression sur une imprimante spécifiée, et enfin la génération au format PDF en utilisant l'imprimante PDF que j'ai décrit dans un autre article

Descriptions des méthodes :

 
Sélectionnez

			WordXML();

Constructeur par défaut, initialise par défaut les différents composants pour la compression/décompression et l'analyse XML. comme le montre le code ci-dessous.

 
Sélectionnez

WordXML::WordXML():m_pUnzipObj(NULL),m_pZipObj(NULL),m_pParserObj(NULL),m_pCurrentGrid(NULL),m_pCurrentGridRow(NULL),m_nCurrentRow(0)
{
	try
	{
		m_pUnzipObj=new UnzipFile;
		m_pZipObj= new ZipDocx;
		m_pParserObj= new ParseXMLDocument;
		m_strDefaultPrinter=m_strDefaultPrinterOrg=GetDefPrinter();	
	}
	catch(...)
	{
		delete m_pUnzipObj;
		delete m_pZipObj;
		delete m_pParserObj;
		throw;
	}	
}
 
Sélectionnez

bool	SetUnzipObj(UnzipFile *pUnzipObj);

Fixe l'objet de décompression, détruit l'objet précédent.

 
Sélectionnez

UnzipFile *GetUnzipObj()

Renvoie l'objet courant de décompression.

 
Sélectionnez

bool	SetZipFilesObj(ZipFiles *pZipObj);

Fixe l'objet de compression, détruit l'objet précédent.

 
Sélectionnez

ZipFiles *GetZipFilesObj()

Renvoie l'objet courant de compression

 
Sélectionnez

bool	SetParserObj(ParseXMLDocument *pParserObj)

Fixe l'objet d'analyse XML, détruit l'objet précédent.

 
Sélectionnez

ParseXMLDocument *GetParserObj()

renvoie l'objet courant d'analyse XML.

 
Sélectionnez

bool	SetModelDocument(const TCHAR *szDocument);

Fixe le nom du document modèle.

 
Sélectionnez

CString GetModelDocument()

Renvoie le nom du document modèle.

 
Sélectionnez

void		SetDestinationFolder(const TCHAR *szFolder)

Fixe le répertoire de destination pour le stockage du document généré.

 
Sélectionnez

bool	CloseModel()

Fermeture du modèle en cours.

 
Sélectionnez

void	SetDocument(const TCHAR *szDocument)

Fixe le nom du document à générer

 
Sélectionnez

CString GetDocument()

Renvoie le nom du document à générer.

 
Sélectionnez

bool	ViewDocument(const TCHAR *szDocument=NULL)

Lance la visualisation du document szDocument ou du document spécifié par SetDocument.
L'application Windows par défaut est appelée.

 
Sélectionnez

bool	PrintDocument(const TCHAR *szPrinterName=NULL,const TCHAR *szDocument=NULL)

Lance l'impression du document szDocument sur l'imprimante szPrinterName,
si szPrinterName est nulle utilise l'imprimante par défaut windows,
si szDocument est nulle utilise le nom du document spécifié par SetDocument.

 
Sélectionnez

bool	SetDefPrinter(const TCHAR *szPrinterName)

Fixe l'imprimante par défaut Windows, l'ancienne imprimante par défaut est mémorisée.

 
Sélectionnez

CString GetDefPrinter()

Renvoie le nom de l'imprimante par défaut Windows.

 
Sélectionnez

bool	RestoreDefPrinter()

Restitue l'imprimante par défaut Windows initiale.

 
Sélectionnez

bool	FindText(const TCHAR *szText)

Cherche une zone de texte dans le document.

 
Sélectionnez

bool	ReplaceText(const TCHAR *szTextSrc,const TCHAR *szTextDest)

Remplace une zone szTextSrc par un autre szTextDest

 
Sélectionnez

int	ReplaceText(const std::vector<CString> &arTextSrc,const std::vector<CString> &arTextDest)

Remplace une liste de zone texte arTextSrc par les éléments contenus dans arTextDest
Renvoie le nombre d'occurrences trouvées.

 
Sélectionnez

bool	GetGrid(const TCHAR *szText);

Retrouve et mémorise la racine du grid d'après le texte d'une des cellules de ce grid.

 
Sélectionnez

bool	GetGridRow(int nRow);

Fixe la ligne de travail à nRow pour le grid en cours (GetGrid)

 
Sélectionnez

bool	DuplicateGridRow(int nRow,int nAddRow=1);

Duplique une ligne à partir de la ligne en cours (GetGridRow) nRow pour le grid en cours (GetGrid)

 
Sélectionnez

bool	SetGridTextCol(int nCol,TCHAR *szText);

Fixe le texte pour la ligne en cours pour la cellule nCol, l'indexage commence à 0.

 
Sélectionnez

bool	GetGridTextCol(int nCol,CString &rText);

Renvoie le texte pour la ligne en cours pour la cellule nCol, l'indexage commence à 0.

 
Sélectionnez

int	GetGridColSize();

Renvoie le nombre de lignes pour le grid en cours (GetGrid)

 
Sélectionnez

int	GetGridRowSize();

Renvoie le nombre de colonnes pour le grid en cours (GetGrid)

 
Sélectionnez

bool	SaveDocument(const TCHAR *szDocumentName)

Sauvegarde le document avec le nom spécifié par szDocumentName

 
Sélectionnez

CStringA	GetLastError()

Renvoie la dernière erreur détectée.

 
Sélectionnez

void	SetPdfAppRun(bool bEnable=false)

Active/désactive le lancement du programme à la fin du document.

 
Sélectionnez

void	SetPdfFileName(const TCHAR *szFilename)

Fixe le nom pour la génération du fichier.

 
Sélectionnez

CString GetLastPdfFileName()

Renvoie le dernier nom fixé par SetPdfFileName
Utilisation :

 
Sélectionnez

	WordXML WXml;
	if(!WXml.SetModelDocument(_T("RELANCECLIENT.docx")))
	{
		#ifdef _DEBUG
			afxDump << _T("erreur") << WXml.GetLastError() ;
		#else
		{
			CString str;
			str=_T("erreur ")+WXml.GetLastError();
			AfxMessageBox(str);
		}
		#endif
	}
	else
	{
		std::vector<CString> strKeyWord;
		std::vector<CString> strValue;

		strKeyWord.push_back(_T("<NOM>"));			strValue.push_back(_T("Dawkins"));
		strKeyWord.push_back(_T("<PRENOM>"));		strValue.push_back(_T("Richard"));
		strKeyWord.push_back(_T("<ADRESSE1>"));		strValue.push_back(_T("P.O. Box 13604"));
		strKeyWord.push_back(_T("<ADRESSE2>"));		strValue.push_back(_T("Savannah,"));
		strKeyWord.push_back(_T("<CP>"));			strValue.push_back(_T("GA 31416"));
		strKeyWord.push_back(_T("<VILLE>"));		strValue.push_back(_T("USA"));
		strKeyWord.push_back(_T("<COMPTE>"));		strValue.push_back(_T("0000001"));
		strKeyWord.push_back(_T("<DEBIT>"));		strValue.push_back(_T("200.00"));
		strKeyWord.push_back(_T("<CREDIT>"));		strValue.push_back(_T("50.00"));
		strKeyWord.push_back(_T("<SOLDEB>"));		strValue.push_back(_T("150.00"));
		strKeyWord.push_back(_T("<SOLCRE>"));		strValue.push_back(_T(""));
		strKeyWord.push_back(_T("<SOLDE>"));		strValue.push_back(_T("150.00"));

		WXml.ReplaceText(strKeyWord,strValue);

		if(WXml.GetGrid(_T("<GRID>")))
		{
			WXml.DuplicateGridRow(1);
			int nRow=WXml.GetGridRowSize();
			int nCol=WXml.GetGridColSize();
			
			WXml.GetGridRow(1);
			WXml.SetGridTextCol(0,_T("01/08/08"));
			WXml.SetGridTextCol(1,_T("31/08/2008"));
			WXml.SetGridTextCol(2,_T("Libellé"));
			WXml.SetGridTextCol(3,_T("2008150"));
			WXml.SetGridTextCol(4,_T("libellé essai"));
			WXml.SetGridTextCol(5,_T("100.00"));
			WXml.SetGridTextCol(6,_T("24.00"));

			WXml.GetGridRow(2);
			WXml.SetGridTextCol(0,_T("15/08/08"));
			WXml.SetGridTextCol(1,_T("15/09/2008"));
			WXml.SetGridTextCol(2,_T("Libellé 2"));
			WXml.SetGridTextCol(3,_T("2008151"));
			WXml.SetGridTextCol(4,_T("libellé essai2"));
			WXml.SetGridTextCol(5,_T("100.00"));
			WXml.SetGridTextCol(6,_T("26.00"));
		}
		WXml.SaveDocument(_T("cour0001.docx"));
		WXml.ViewDocument();

		// deuxieme document.
		strValue.erase(strValue.begin());
		strValue.insert(strValue.begin(),_T("farscape"));
		WXml.ReplaceText(strKeyWord,strValue);
		if(WXml.GetGrid(_T("<GRID>")))
		{
			WXml.DuplicateGridRow(1);
			int nRow=WXml.GetGridRowSize();
			int nCol=WXml.GetGridColSize();
			
			WXml.GetGridRow(1);
			WXml.SetGridTextCol(0,_T("01/08/08"));
			WXml.SetGridTextCol(1,_T("31/08/2008"));
			WXml.SetGridTextCol(2,_T("Libellé"));
			WXml.SetGridTextCol(3,_T("2008150"));
			WXml.SetGridTextCol(4,_T("libellé essai"));
			WXml.SetGridTextCol(5,_T("100.00"));
			WXml.SetGridTextCol(6,_T("24.00"));

			WXml.GetGridRow(2);
			WXml.SetGridTextCol(0,_T("15/08/08"));
			WXml.SetGridTextCol(1,_T("15/09/2008"));
			WXml.SetGridTextCol(2,_T("Libellé 2"));
			WXml.SetGridTextCol(3,_T("2008151"));
			WXml.SetGridTextCol(4,_T("libellé essai2"));
			WXml.SetGridTextCol(5,_T("100.00"));
			WXml.SetGridTextCol(6,_T("26.00"));
		}
		WXml.SaveDocument(_T("cour0002.docx"));
		WXml.ViewDocument();		
		WXml.CloseModel();
	}

Le code ci-dessus génère deux documents à partir du modèle relanceclient.docx.
Ces classes sont utilisables dans les configurations suivantes:
Avec Visual 6.0 :
- En Unicode et multi-bytes
- Avec ou sans MFC : une classe de substitution est implémentée pour la classe CString avec les versions CStringA et CStringW suivant le contexte.
Avec Visual 2008 :
- En Unicode et multi-bytes
- Avec ou sans MFC, dans la configuration sans MFC : une classe de substitution est implémentée pour la classe CString, CStringA et CStringW.

V. L'interface C

Voici la liste des fonctions disponibles :

 
Sélectionnez

	WXML_EXPORT int				WXmlSetModelDocument(const XML_CHAR *szDocument);
	WXML_EXPORT const XML_CHAR *WXmlGetModelDocument();

	WXML_EXPORT void			WXmlSetDestinationFolder(const XML_CHAR *szFolder);

	WXML_EXPORT void			WXmlSetDocument(const XML_CHAR* szDocument);
	WXML_EXPORT const XML_CHAR *WXmlGetDocument();

	WXML_EXPORT void			WXmlListClearReplace();
	WXML_EXPORT void			WXmlListAddReplace(const XML_CHAR *szKeyWord,const XML_CHAR *szValue);
	WXML_EXPORT int				WXmlListReplace();
	WXML_EXPORT int				WXmlReplaceText(const XML_CHAR *szKeyWord,const XML_CHAR *szValue);
	WXML_EXPORT int				WXmlFindText(const XML_CHAR *szText);

	WXML_EXPORT int				WXmlGetGrid(const XML_CHAR *szText);		
	WXML_EXPORT int				WXmlGetGridRow(int nRow);		
	WXML_EXPORT int				WXmlDuplicateGridRow(int nRow,int nAddRow);		
	WXML_EXPORT int				WXmlSetGridTextCol(int nCol,XML_CHAR *szText);		
	WXML_EXPORT int				WXmlGetGridTextCol(int nCol,XML_CHAR *szText,int nBufSizeMax);
	WXML_EXPORT int				WXmlGetGridColSize();		
	WXML_EXPORT int				WXmlGetGridRowSize();

	WXML_EXPORT int				WXmlSaveDocument(const XML_CHAR *szFileName);
	WXML_EXPORT int				WXmlPrintDocument(const XML_CHAR *szPrinterName);
	WXML_EXPORT int				WXmlViewDocument(const XML_CHAR *szDocument);
	WXML_EXPORT int				WXmlCloseModel();

	WXML_EXPORT int				WXmlSetDefaultPrinter(const XML_CHAR *szPrinterName);
	WXML_EXPORT const XML_CHAR *WXmlGetDefaultPrinter();
	WXML_EXPORT int				WXmlRestoreDefaultPrinter();

	WXML_EXPORT const char*		WXmlGetLastError();
	WXML_EXPORT int				WXmlEmptyDirectory(const XML_CHAR *szDir,int bRemoveDir);

	WXML_EXPORT	int				WXmlGetSizeJobForPrinter(const XML_CHAR *szPrinterName);
	WXML_EXPORT void			WXmlSetPdfFileName(const XML_CHAR *szFilename);
	WXML_EXPORT const XML_CHAR* WXmlGetLastPdfFileName();
	WXML_EXPORT void			WXmlSetPdfAppRun(int bEnable);

Utilisation dans un programme console en C :

 
Sélectionnez

void test()
{
	struct ZONEWORD
	{
		const TCHAR *szLabel;
		TCHAR *pszItem;	
	};
	struct ZONEWORD zoneWord[]=
	{
		_T("<PREFIXE>")		,_T("Monsieur "),
		_T("<NOM>")			,_T("Dawkins"),
		_T("<PRENOM>")		,_T("Richard  "),
		_T("<ADRESSE1>")	,_T("P.O. Box 13604"),
		_T("<ADRESSE2>")	,_T("Savannah,"),
		_T("<CP>")			,_T("GA 31416"),
		_T("<VILLE>")		,_T("USA"),
		_T("<COMPTE>")		,_T("0000001"),
		_T("<DEBIT>")		,_T("200.00"),
		_T("<CREDIT>")		,_T("50.00"),
		_T("<SOLDEB>")		,_T("150.00"),
		_T("<SOLCRE>")		,_T(""),
		_T("<SOLDE>")		,_T("150.00"),
	};
	
	int i,nRow,nCol;
	// fixe le nom du modèle
	WXmlSetModelDocument(_T("RELANCECLIENT.docx"));
	// Le nom du document à générer
	WXmlSetDocument(_T("cour0001.docx"));
	// vide la liste interne de remplacement de zones.
	WXmlListClearReplace();
	// prepare la liste des remplacements a effectuer.
	for(i=0;i<sizeof(zoneWord)/sizeof(struct ZONEWORD);i++)
	{		
		WXmlListAddReplace(zoneWord[i].szLabel,zoneWord[i].pszItem);
	}
	// affecte le grid.
	if(WXmlGetGrid(_T("<GRID>")))
	{
		WXmlDuplicateGridRow(1,1);
		nRow=WXmlGetGridRowSize();
		nCol=WXmlGetGridColSize();
		
		WXmlGetGridRow(1);
		WXmlSetGridTextCol(0,_T("01/08/08"));
		WXmlSetGridTextCol(1,_T("31/08/2008"));
		WXmlSetGridTextCol(2,_T("Libellé"));
		WXmlSetGridTextCol(3,_T("2008150"));
		WXmlSetGridTextCol(4,_T("libellé essai"));
		WXmlSetGridTextCol(5,_T("100.00"));
		WXmlSetGridTextCol(6,_T("24.00"));

		WXmlGetGridRow(2);
		WXmlSetGridTextCol(0,_T("15/08/08"));
		WXmlSetGridTextCol(1,_T("15/09/2008"));
		WXmlSetGridTextCol(2,_T("Libellé 2"));
		WXmlSetGridTextCol(3,_T("2008151"));
		WXmlSetGridTextCol(4,_T("libellé essai2"));
		WXmlSetGridTextCol(5,_T("100.00"));
		WXmlSetGridTextCol(6,_T("26.00"));
	}
	// désactive le lancement de l'application pdf associée.
	WXmlSetPdfAppRun(0);
	// remplace les zones
	WXmlListReplace();
	// sauvegarde le document
	WXmlSaveDocument(NULL);
	// fixe le nom du document pdf à générer
	WXmlSetPdfFileName(_T("letter0001"));
	// imprime sur l'imprimante pdf.
	WXmlPrintDocument(_T("GhostScript PDF Writer"));

	// le nouveau nom du document à générer
	WXmlSetDocument(_T("cour0002.docx"));

	WXmlListClearReplace();
	for(i=0;i<sizeof(zoneWord)/sizeof(struct ZONEWORD);i++)
	{		
		WXmlListAddReplace(zoneWord[i].szLabel,zoneWord[i].pszItem);
	}
	WXmlListReplace();
	if(WXmlGetGrid(_T("<GRID>")))
	{
		WXmlDuplicateGridRow(1,1);
		nRow=WXmlGetGridRowSize();
		nCol=WXmlGetGridColSize();

		WXmlGetGridRow(1);
		WXmlSetGridTextCol(0,_T("01/08/08"));
		WXmlSetGridTextCol(1,_T("31/08/2008"));
		WXmlSetGridTextCol(2,_T("Libellé"));
		WXmlSetGridTextCol(3,_T("2008150"));
		WXmlSetGridTextCol(4,_T("libellé essai"));
		WXmlSetGridTextCol(5,_T("100.00"));
		WXmlSetGridTextCol(6,_T("24.00"));

		WXmlGetGridRow(2);
		WXmlSetGridTextCol(0,_T("15/08/08"));
		WXmlSetGridTextCol(1,_T("15/09/2008"));
		WXmlSetGridTextCol(2,_T("Libellé 2"));
		WXmlSetGridTextCol(3,_T("2008151"));
		WXmlSetGridTextCol(4,_T("libellé essai2"));
		WXmlSetGridTextCol(5,_T("100.00"));
		WXmlSetGridTextCol(6,_T("26.00"));
	}
	// fixe le nouveau nom du document pdf à générer
	WXmlSetPdfFileName(_T("letter0002"));
	WXmlSaveDocument(NULL);	
	WXmlPrintDocument(_T("GhostScript PDF Writer"));
	// attente de la fin d'impression
	while(WXmlGetSizeJobForPrinter(_T("GhostScript PDF Writer")));
	
	// active le lancement de l'application par defaut associée à l'imprimante pdf.
	WXmlSetPdfAppRun(1);
}

L'exemple ci-dessus est issu du programme de test TestDLLC disponible dans le projet sous Visual 2008.
Il illustre la génération avec les commandes C de deux documents docx à partir d'un modèle existant.
Une fois générés les documents sont transformés en fichier PDF grâce à l'imprimante PDF que j'ai développée dans un autre tutoriel (voir plus bas pour les liens).
L'exemple fonctionne en mode multi-bytes et Unicode.
la DLL produite n'utilise pas les classes CString, CStringW, CStringA des MFC, mais une classe patron de substitution que j'ai implémentée d'après la classe string de tinyXML.
Cette classe pourra être utilisée avec le compilateur VC 6.0 pour disposer de CStringA et CStringW.
Définition de la classe:

 
Sélectionnez

#ifdef WXML_VC6_STRING
#define WXML_MFC_STRING
#endif

#ifdef WXML_MFC_STRING
#pragma once

#include <assert.h>
#include <string.h>
#include <wchar.h>
#include <mbstring.h>
#include <stdio.h> 
#include <stdarg.h>

/*
   CStringT est une emulation de la classe CStringT MFC, seules les fonctions courantes sont implémentées,
   on disposera des classes CString,CStringW,CStringA
*/
template<typename TYPE> 
class CStringT
{
  public :
	// The size type used
  	typedef size_t size_type;

	// Error value for find primitive
	const size_type npos; // = -1;

	
	// CStringT empty constructor
	CStringT () : npos(static_cast<size_type >(-1))
	{
		nullrep_.size=0;
		nullrep_.capacity=0;
		nullrep_.str[0]='\0';			
		rep_=&nullrep_;
	}

	// CStringT copy constructor
	CStringT ( const CStringT & copy) : rep_(0),npos(static_cast<size_type >(-1))
	{
		nullrep_.size=0;
		nullrep_.capacity=0;
		nullrep_.str[0]='\0';					
		init(copy.length());
		StringMemcpy(start(),copy.data(), length());		
	}

	// CStringT constructor, based on a string
	CStringT ( const char * copy) : rep_(0),npos(static_cast<size_type >(-1))
	{
		nullrep_.size=0;
		nullrep_.capacity=0;
		nullrep_.str[0]='\0';
		int nlen=strlen(copy);
		init( static_cast<size_type>( nlen ));
		if(sizeof(TYPE)==sizeof(char))	memcpy(reinterpret_cast<void *>(start()),static_cast<const char*>(copy), length());
		else
		{			
			wchar_t *wsz= new wchar_t[nlen+1];
			MultiByteToWideChar( CP_ACP, 0, copy,nlen,wsz,nlen);
			wmemcpy(reinterpret_cast<wchar_t *>(start()),reinterpret_cast<const wchar_t*>(wsz), length());
			delete [] wsz;
		}
	}

	// CStringT constructor, based on a string
	CStringT ( const wchar_t * copy) : rep_(0),npos(static_cast<size_type >(-1))
	{
		nullrep_.size=0;
		nullrep_.capacity=0;
		nullrep_.str[0]='\0';
		int nlen=wcslen(copy);
		init( static_cast<size_type>( nlen ));
		if(sizeof(TYPE)!=sizeof(char))	wmemcpy(reinterpret_cast<wchar_t *>(start()),copy, length());
		else
		{			
			char *sz= new char[nlen+1];
			WideCharToMultiByte( CP_ACP, 0, copy, -1, sz, nlen+1, NULL, NULL );
			memcpy(reinterpret_cast<void *>(start()),sz, length());
			delete []sz;
		}
	}
	
	// CStringT constructor, based on a string
	explicit CStringT ( const TYPE * str, size_type len) : rep_(0),npos(static_cast<size_type >(-1))
	{
		nullrep_.size=0;
		nullrep_.capacity=0;
		nullrep_.str[0]='\0';			
		init(len);
		StringMemcpy(start(),str, len);
	}

	// CStringT destructor
	~CStringT ()
	{
		quit();
	}

	// = operator
	CStringT& operator = (const wchar_t * copy)
	{	
		return assign(copy, (size_type)StringLen(copy));		
	}
	// = operator
	CStringT& operator = (const char * copy)
	{	
		return assign(copy, (size_type)StringLen(copy));
		
	}

	// = operator
	CStringT& operator = (const CStringT & copy)
	{
		return assign(copy.start(), copy.length());
	}

	//Extracts a substring of length nCount characters from this CStringT object, starting at position iFirst (zero-based).
	CStringT Mid( size_type iFirst, int nCount) const
	{
		CStringT <TYPE>tmp;
		if(iFirst >=size()) return tmp;
		if(iFirst+nCount >=size()) nCount-=((iFirst+nCount)-size());
		tmp.init(nCount);
		memmove(tmp.start(), start()+iFirst, nCount*sizeof(TYPE));
		return tmp;
	}
	//Extracts a substring from this CStringT object, starting at position iFirst (zero-based).
	CStringT Mid(  size_type iFirst ) const
	{
		CStringT <TYPE>tmp;
		if(iFirst >=size()) return tmp;
		int nCount=size()-iFirst;		
		tmp.init(nCount);
		memmove(tmp.start(), start()+iFirst, nCount*sizeof(TYPE));
		return tmp;
	}
	//Extracts the leftmost nCount characters from this CStringT object and returns a copy of the extracted substring.
	CStringT Left(  size_type nCount ) const
	{
		CStringT <TYPE>tmp;
		if(nCount >=size()) nCount=size();
		tmp.init(nCount);
		memmove(tmp.start(), start(), nCount*sizeof(TYPE));
		return tmp;
	}

	//Extracts the last (that is, rightmost) nCount characters from this CStringT object and returns a copy of the extracted substring.
	CStringT Right(  size_type nCount ) const
	{
		CStringT <TYPE>tmp;
		if(nCount >=size()) nCount=size();
		tmp.init(nCount);
		int nOfs=size()-nCount;
		memmove(tmp.start(), start()+nOfs, nCount*sizeof(TYPE));
		return tmp;
	}
	// += operator. Maps to append
	CStringT& operator += (const wchar_t * suffix)
	{			
		return append(suffix, static_cast<size_type>( StringLen(suffix) ));
		
	}

	// += operator. Maps to append
	CStringT& operator += (const char * suffix)
	{		
		return append(suffix, static_cast<size_type>( StringLen(suffix) ));		
	}

	// += operator. Maps to append
	CStringT& operator += (TYPE single)
	{
		return append(&single, 1);
	}

	// += operator. Maps to append
	CStringT& operator += (const CStringT & suffix)
	{
		return append(suffix.data(), suffix.length());
	}
	// Convert a CStringT into a null-terminated char *
	operator const TYPE*() const{ return rep_->str; }

	// Convert a CStringT into a null-terminated char *
	const TYPE * c_str () const { return rep_->str; }

	// Convert a CStringT into a char * (need not be null terminated).
	const TYPE * data () const { return rep_->str; }

	// Return the length of a CStringT
	size_type length () const { return rep_->size; }

	// Return the length of a CStringT, Alias for length()
	int GetLength() const { return rep_->size; }

	bool IsEmpty() const {return !size();}

	// Alias for length()
	size_type size () const { return rep_->size; }

	// Checks if a CStringT is empty
	bool empty () const { return rep_->size == 0; }

	// Return capacity of string
	size_type capacity () const { return rep_->capacity; }


	// single char extraction
	const TYPE  at (size_type index) const
	{
		assert( index < length() );
		return rep_->str[ index ];
	}

	// [] operator
	TYPE operator [] (int index) const
	{
		assert(static_cast<unsigned int>( index) < length() );
		return rep_->str[ index ];
	}
	
	// find a char in a string from an offset. Return CStringT::npos if not found
	size_type Find (TYPE tofind, size_type offset=0) const
	{
		if (offset >= length()) return npos;

		for (const TYPE * p = c_str() + offset; *p != '\0'; ++p)
		{
		   if (*p == tofind) return static_cast< size_type >( p - c_str() );
		}
		return npos;
	}
	// find a string in a string from an offset. Return CStringT::npos if not found
	size_type Find (const TYPE* pszSub, size_type offset=0) 
	{
		if (offset >= length() || pszSub == NULL) return npos;
	
		// find first matching substring
		const TYPE *psz = StringFindString( c_str()+offset, pszSub );

		// return -1 for not found, distance from beginning otherwise
		return( (psz == NULL) ? npos : int( psz-c_str() ) );
	}
	// release buffer, setting length to nNewLength (or to first nul if -1)
	void ReleaseBuffer(int nNewLength=-1)
	{
		if (nNewLength == -1)	nNewLength = StringLen(c_str()); 

		assert(static_cast<unsigned int>(nNewLength) <= size());
		CStringT <TYPE>tmp;			
		tmp.init(nNewLength);
		memmove(tmp.start(),start(),nNewLength*sizeof(TYPE));
		swap(tmp);
	}
	
	TYPE* GetBuffer(int nMinBufLength)
	{
		assert(nMinBufLength >= 0);		

		int nOldLen = size();
		if (nMinBufLength < nOldLen) nMinBufLength = nOldLen;
		CStringT <TYPE>tmp;
		tmp.init(nMinBufLength);		
		memcpy(tmp.start(), start(), (nOldLen+1)*sizeof(TYPE));
		swap(tmp);
		set_size(nOldLen);
		return start();
	}
	
	// get pointer to modifiable buffer exactly as long as nNewLength
	TYPE *GetBufferSetLength(int nNewLength)
	{
		assert(static_cast<unsigned int>(nNewLength) >= 0);

		CStringT <TYPE>tmp;
		tmp.init(nNewLength);
		unsigned int nSize=nNewLength;
		if(nSize>size()) nSize=size()+1;
		memmove(tmp.start(),start(),nSize*sizeof(TYPE));
		swap(tmp);
		return const_cast<TYPE *>(c_str());
	}

	// Replace all occurrences of string 'pszOld' with string 'pszNew'
	int Replace( const TYPE *pszOld,const TYPE* pszNew )
	{
		// can't have empty or NULL lpszOld
		// nSourceLen is in TYPE
		int nSourceLen = StringLen( pszOld );
		if( nSourceLen == 0 )			return( 0 );
		// nReplacementLen is in TYPE
		int nReplacementLen = StringLen( pszNew );

		// loop once to figure out the size of the result string
		int nCount = 0;
		{
			TYPE * pszStart =const_cast<TYPE*>(c_str());
			TYPE * pszEnd = pszStart+size();
			while( pszStart < pszEnd )
			{
				TYPE* pszTarget;
				while( (pszTarget =const_cast<TYPE*>( StringFindString( pszStart, pszOld ) )) != NULL)
				{
					nCount++;
					pszStart = pszTarget+nSourceLen;
				}
				pszStart += StringLen( pszStart )+1;
			}
		}

		// if any changes were made, make them
		if( nCount > 0 )
		{
			// if the buffer is too small, just
			//   allocate a new buffer (slow but sure)
			int nOldLength = size();
			int nNewLength = nOldLength+(nReplacementLen-nSourceLen)*nCount;

			CStringT <TYPE>tmp;			
			tmp.init(__max( nNewLength, nOldLength ) );
			memmove(tmp.start(),start(),size()*sizeof(TYPE));

			TYPE * pszBuffer =const_cast<TYPE*>( tmp.c_str());

			TYPE * pszStart = pszBuffer;
			TYPE * pszEnd = pszStart+nOldLength;

			// loop again to actually do the work
			while( pszStart < pszEnd )
			{
				TYPE * pszTarget;
				while( (pszTarget = const_cast<TYPE*>(StringFindString( pszStart, pszOld ) )) != NULL )
				{
					int nBalance = nOldLength-int(pszTarget-pszBuffer+nSourceLen);
					memmove( pszTarget+nReplacementLen,pszTarget+nSourceLen, nBalance*sizeof( TYPE ) );
					memcpy( pszTarget,pszNew, nReplacementLen*sizeof( TYPE ) );
					pszStart = pszTarget+nReplacementLen;
					pszTarget[nReplacementLen+nBalance] = 0;
					nOldLength += (nReplacementLen-nSourceLen);
				}
				pszStart += StringLen( pszStart )+1;
			}
			assert( pszBuffer[nNewLength] == 0 );
			swap(tmp);			
		}
		return( nCount );
	}
	// Converts all the characters in this string to uppercase characters.
	CStringT& MakeUpper()
	{
		StringMemcpy(start(),Strupper(const_cast<TYPE *>(data())), length());
		return *this;
	}

	//Trims trailing whitespace characters from the string.
	void TrimRight()
	{
		
		TYPE * lpsz = start();
		TYPE * lpszLast = NULL;

		while (*lpsz != '\0')
		{
			if (isSpace(*lpsz))
			{
				if (lpszLast == NULL) lpszLast = lpsz;
			}
			else lpszLast = NULL;
			lpsz++ ;
		}

		if (lpszLast != NULL)
		{			
			*lpszLast = '\0';
			rep_->size = (lpszLast - start())/sizeof(TYPE);
		}
	}

	// Formats the string as sprintf does.
	void Format(const char *szFormat,... )
	{
		assert(sizeof(TYPE)==sizeof(char));
		va_list arg_ptr;
		va_start(arg_ptr,szFormat);	
	
		CStringT <TYPE>tmp;				
		int nSize=_vscprintf(szFormat,arg_ptr);			
		tmp.init(nSize);
		vsprintf(tmp.start(),szFormat,arg_ptr);
		swap(tmp);
		va_end(arg_ptr);
	}
	void Format(const wchar_t *szFormat,... )
	{
		assert(sizeof(TYPE)==sizeof(wchar_t));
		va_list arg_ptr;
		va_start(arg_ptr,szFormat);	
		
		CStringT <TYPE>tmp;			

		int nSize=_vscwprintf(szFormat,arg_ptr);			
		tmp.init(nSize);
		vswprintf(tmp.start(),szFormat,arg_ptr);
		va_end(arg_ptr);
		swap(tmp);
	}

	// alias of clear
	void Empty(){clear();}

	void clear ()
	{
		//Lee:
		//The original was just too strange, though correct:
		//	CStringT().swap(*this);
		//Instead use the quit & re-init:
		quit();
		init(0,0);
	}

	/*	Function to reserve a big amount of data when we know we'll need it. Be aware that this
		function DOES NOT clear the content of the CStringT if any exists.
	*/
	void reserve (size_type cap)
	{
		if (cap > capacity())
		{
			CStringT<TYPE>tmp;
			tmp.init(length(), cap);
			StringMemcpy(tmp.start(),data(), length());					
			swap(tmp);
		}
	}

	CStringT& assign (const wchar_t * str, size_type len)
	{
		void *pzon=reinterpret_cast<void *>(const_cast <wchar_t *>(str));
		char *sz=NULL;
		if(sizeof(TYPE)==sizeof(char))
		{			
			sz= new char[len];
			WideCharToMultiByte( CP_ACP, 0, str, -1, sz, len, NULL, NULL );
			pzon=sz;
		}
		size_type cap = capacity();
		if (len > cap || cap > 3*(len + 8))
		{
			CStringT <TYPE>tmp;
			tmp.init(len);
			memcpy(reinterpret_cast<void *>(tmp.start()),pzon,len*sizeof(TYPE));			
			swap(tmp);
		}
		else
		{
			memmove(start(), pzon, len*sizeof(TYPE));
			set_size(len);
		}
		delete []sz;
		return *this;
	}
	CStringT& assign (const char * str, size_type len)
	{
		void *pzon=reinterpret_cast<void *>(const_cast <char *>(str));
		wchar_t *wsz=NULL;
		if(sizeof(TYPE)==sizeof(wchar_t))
		{						
			wsz= new wchar_t[len];
			MultiByteToWideChar( CP_ACP, 0, str,len,wsz,len);
			pzon=wsz;
		}
		size_type cap = capacity();
		if (len > cap || cap > 3*(len + 8))
		{
			CStringT <TYPE>tmp;
			tmp.init(len);
			memcpy(reinterpret_cast<void *>(tmp.start()),pzon,len*sizeof(TYPE));			
			swap(tmp);
			
		}
		else
		{
			memmove(start(), pzon, len*sizeof(TYPE));
			set_size(len);
		}
		delete []wsz;
		return *this;
	}

	CStringT& append (const char* str, size_type len)
	{
		void *pzon=reinterpret_cast<void *>(const_cast <char *>(str));
		wchar_t *wsz=NULL;
		if(sizeof(TYPE)==sizeof(wchar_t))
		{						
			wsz= new wchar_t[len];
			MultiByteToWideChar( CP_ACP, 0, str,len,wsz,len);
			pzon=wsz;
		}
		size_type newsize = length() + len;
		if (newsize > capacity())
		{
			reserve (newsize + capacity());
		}
		memmove(finish(), pzon, len*sizeof(TYPE));
		set_size(newsize);
		delete []wsz;
		return *this;
	}
	CStringT& append (const wchar_t* str, size_type len)
	{
		void *pzon=reinterpret_cast<void *>(const_cast <wchar_t *>(str));
		char *sz=NULL;
		if(sizeof(TYPE)==sizeof(char))
		{			
			sz= new char[len];
			WideCharToMultiByte( CP_ACP, 0, str, -1, sz, len, NULL, NULL );
			pzon=sz;
		}
		size_type newsize = length() + len;
		if (newsize > capacity())
		{
			reserve (newsize + capacity());
		}
		memmove(finish(), pzon, len*sizeof(TYPE));
		set_size(newsize);
		delete []sz;
		return *this;
	}

	void swap (CStringT& other)
	{
		Rep* r = rep_;
		// switch avec un chaine null.
		bool bNull=(rep_ == &nullrep_);
		rep_ = other.rep_;
		// improbable ...
		if(other.rep_==&other.nullrep_)	rep_=&nullrep_;
		other.rep_ = r;
		if(bNull) other.rep_=&other.nullrep_;
	}
	static int Compare(const char *sz1,const char *sz2)
	{
		return strcmp(sz1,sz2);
	}
	static int Compare(const wchar_t *sz1,const wchar_t *sz2)
	{
		return wcscmp(sz1,sz2);
	}
	static int StringLen(const char *psz) {return strlen(psz);}
	static int StringLen(const wchar_t *pwsz){return wcslen(pwsz);}

	void init(size_type sz) { init(sz, sz); }
	TYPE* start() const { return rep_->str; }
	TYPE* finish() const { return rep_->str + rep_->size; }

  private:	

	int isSpace(const char t){return isspace(t);}
	int isSpace(const wchar_t t){return iswspace(t);}

	char *Strupper(char *sz)
	{
		return _strupr(sz);
	}
	wchar_t *Strupper(wchar_t *sz)
	{
		return _wcsupr(sz);
	}
	const char *StringFindString( const char *pszBlock, const char *pszMatch )
	{
		return reinterpret_cast< const char* >( _mbsstr( reinterpret_cast< const unsigned char* >( pszBlock ),
			reinterpret_cast< const unsigned char* >( pszMatch ) ) );
	}
	const wchar_t *StringFindString( const wchar_t *pszBlock, const wchar_t *pszMatch )
	{
		return reinterpret_cast< const wchar_t* >( wcsstr(  pszBlock , pszMatch  ) );
	}
	void StringMemcpy(char *dest,const char *src,size_t nlen)
	{
		memcpy(reinterpret_cast<void *>(dest),src,nlen);
	}
	void StringMemcpy(wchar_t *dest,const wchar_t *src,size_t nlen)
	{
		wmemcpy(dest,src,nlen);
	}
	
	
	void set_size(size_type sz) { rep_->str[ rep_->size = sz ] = '\0'; }
	

	struct Rep
	{
		size_type size, capacity;
		TYPE str[1];
	};

	void init(size_type sz, size_type cap)
	{
		if (cap)
		{
			// Lee: the original form:
			//	rep_ = static_cast<Rep*>(operator new(sizeof(Rep) + cap));
			// doesn't work in some cases of new being overloaded. Switching
			// to the normal allocation, although use an 'int' for systems
			// that are overly picky about structure alignment.			
			const size_type bytesNeeded = sizeof(Rep) + (cap *sizeof(TYPE));
			const size_type intsNeeded = ( bytesNeeded + sizeof(int) - 1 ) / sizeof( int ); 
			rep_ = reinterpret_cast<Rep*>( new int[ intsNeeded ] );

			rep_->str[ rep_->size = sz ] = '\0';
			rep_->capacity = cap;
		}
		else
		{
			rep_ = &nullrep_;
		}
	}

	void quit()
	{
		if (rep_ != &nullrep_)
		{
			// The rep_ is really an array of ints. (see the allocator, above).
			// Cast it back before delete, so the compiler won't incorrectly call destructors.
			delete [] ( reinterpret_cast<int*>( rep_ ) );
		}
	}

	Rep * rep_;
	Rep nullrep_;

} ;

template<typename TYPE> 
inline bool operator == (const CStringT<TYPE> & a, const CStringT<TYPE> & b)
{
	return CStringT<TYPE>::Compare(a.c_str(),b.c_str())==0;
}
template<typename TYPE> 
inline bool operator == (const CStringT<TYPE> & a, const TYPE* b)
{
	return CStringT<TYPE>::Compare(a.c_str(),b)==0;
}
template<typename TYPE> 
inline bool operator == (const TYPE* a, const CStringT<TYPE> & b) { return b == a; }


template<typename TYPE> 
inline bool operator < (const CStringT<TYPE> & a, const CStringT<TYPE> & b)
{
	return CStringT<TYPE>::Compare(a.c_str(),b.c_str())<0;	
}
template<typename TYPE> 
inline bool operator < (const CStringT<TYPE> & a, const TYPE* b)
{
	return CStringT<TYPE>::Compare(a.c_str(),b)<0;	
}
template<typename TYPE> 
inline bool operator < (const TYPE * a, const CStringT<TYPE> & b)
{
	return CStringT<TYPE>::Compare(a,b.c_str())<0;		
}

template<typename TYPE> 
inline bool operator != (const CStringT<TYPE> & a, const CStringT<TYPE> & b) { return !(a == b); }
template<typename TYPE> 
inline bool operator != (const CStringT<TYPE> & a, const TYPE* b) { return !(a == b); }
template<typename TYPE> 
inline bool operator != (const TYPE* a, const CStringT<TYPE> & b) { return !(b == a); }

template<typename TYPE> 
inline bool operator >  (const CStringT<TYPE> & a, const CStringT<TYPE> & b) { return b < a; }
template<typename TYPE> 
inline bool operator >  (const TYPE* a, const CStringT<TYPE> & b) { return b < a; }
template<typename TYPE> 
inline bool operator >  (const CStringT<TYPE> & a, const TYPE* b) { return b < a; }

template<typename TYPE> 
inline bool operator <= (const CStringT<TYPE> & a, const CStringT<TYPE> & b) { return !(b < a); }
template<typename TYPE> 
inline bool operator <= (const TYPE* a, const CStringT<TYPE> & b) { return !(b < a); }
template<typename TYPE> 
inline bool operator <= (const CStringT<TYPE> & a, const TYPE* b) { return !(b < a); }

template<typename TYPE> 
inline bool operator >= (const CStringT<TYPE> & a, const CStringT<TYPE> & b) { return !(a < b); }
template<typename TYPE> 
inline bool operator >= (const TYPE* a, const CStringT<TYPE> & b) { return !(a < b); }
template<typename TYPE> 
inline bool operator >= (const CStringT<TYPE> & a, const TYPE* b) { return !(a < b); }

template<typename TYPE> 
CStringT<TYPE>operator + (const CStringT<TYPE> & a, const CStringT<TYPE> & b)
{
	CStringT<TYPE>tmp;
	tmp.reserve(a.length() + b.length());
	tmp += a;
	tmp += b;
	return tmp;
}
template<typename TYPE> 
CStringT<TYPE> operator + (const CStringT<TYPE> & a, const TYPE* b)
{
	CStringT <TYPE>tmp;
	unsigned int b_len = CStringT<TYPE>::StringLen(b);
	tmp.reserve(a.length() + b_len);
	tmp += a;
	tmp.append(b, b_len);
	return tmp;
}
template<typename TYPE> 
CStringT<TYPE> operator + (const TYPE* a, const CStringT<TYPE> & b)
{
	CStringT <TYPE>tmp;
	unsigned int a_len = CStringT<TYPE>::StringLen(a);
	tmp.reserve(a_len + b.length());
	tmp.append(a, a_len);
	tmp += b;
	return tmp;
}

#ifdef _UNICODE
	#ifndef WXML_VC6_STRING // en mode MFC et visual 6.0 pas besoin de CString...
		typedef CStringT<wchar_t> CString;
	#endif
#else
	#ifndef WXML_VC6_STRING	// en mode MFC et visual 6.0 pas besoin de CString...
		typedef CStringT<char>    CString;
	#endif
#endif

typedef CStringT<char>    CStringA;
typedef CStringT<wchar_t> CStringW;

#endif	// WXML_MFC_STRING

La directive WXML_VC6_STRING permet dans un projet MFC avec VC6.0 de disposer de CStringA et CStringW.
la directive WXML_MFC_STRING seule, permet de disposer des classes CString, CStringA et CStringW.

Cette DLL sera disponible dans les configurations suivantes:
Avec Visual 6.0 :
- En Unicode et multi-bytes
- Avec ou sans MFC : dépendra de la version de la DLL utilisée.

Avec Visual 2008 :
- En Unicode et multi-bytes
- Avec ou sans MFC : dépendra de la version de la DLL utilisée.

VI. Les différents modules de la solution

VI-a. L'interface pour OpenXml

Le projet TinyXML modifié en version visual6.0 et Visual 2008 :./tinyxml.zip
Le projet ZipArchive modifié en version visual6.0 et Visual 2008 :./ziparchive.zip
Le projet sous Visual 2008 contenant:./LibWordXML.zip

le projet contient les sous projets suivants:
- la DLL en C :LibWordXml
- le test de la DLL en C :TestDLLC :
- le test en MFC :TestLibWordXML, fonctionne dans les configurations: Release/debug/ReleaseUnicod/DebugUnicod
- le test en C++ mode console :TestClasse
La DLL pour l'interface C en version visual6.0 et Visual 2008 : il faudra ouvrir avec Visual 6 le projet LibWordXML.dsw contenu dans le répertoire principal
Le projet GhostPrinterPdf pour l'impression PDF :http://farscape.developpez.com/tutoriels/PrinterPDF/
Dans le projet Visual 2008 vous trouverez le modèle relanceClient.docx utilisé pour les tests.

VI-b. L'envoi des fichiers par Emails

Pour envoyer les fichiers par email j'ai utilisé une contribution disponible sur le site de CodeProjects : http://www.codeproject.com/KB/IP/csmtpconn.aspx
J'ai modifié le source qui date de 2000, pour qu'il soit compilable sous Visual 2008 dans un environnement Unicode ou Multi-Bytes.
Envoyer un mail est très simple et ce résume à ces quelques lignes :

 
Sélectionnez

//......
	WXml.SaveDocument(_T("cour0001.docx"));
		// send email.
		{
			CSMTPConnection smtp;
			smtp.Connect(_T("smtp.neuf.fr"));
			CSMTPMessage m;
			m.AddRecipient(CSMTPAddress("yoyonovitch@neuf.fr"));
			m.m_From = CSMTPAddress("edgard@neuf.fr");
			m.m_sSubject = "votre lettre de relance";

			CSMTPAttachment a;
			a.Attach(WXml.GetDocument());
			m.AddAttachment(&a);
			smtp.SendMessage(m);
			smtp.Disconnect();
		}
		WXml.ViewDocument();

J'ai mis en place l'envoi de l'email après la sauvegarde du document.
Vous trouverez ces lignes dans l'exemple TestLibWordXML, pensez à changer les adresses Emails …

VII. Conclusion

Cette implémentation est un premier pas, elle permet le remplacement de zones textes existantes dans un document Word, et le remplissage d'un tableau, ce qui est amplement suffisant dans mon cas pour gérer un mailing simple.
Cette solution combinée à l'impression du document au format PDF permettra l'envoi de documents par mail dans un format connu de tous et protégé contre les modifications.

VIII. Remerciements

Je remercie toute l'équipe C++, pour leur relecture attentive du document, notamment PetitPapaNoël Merci à jbarreau-mainson pour la correction finale de cet article

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2008 Farscape. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts. Droits de diffusion permanents accordés à Developpez LLC.