[.NET] Génération de document Word 2007 avec le SDK OpenXML 2.0
La génération de documents est une problématique importante lors de la réalisation d’un projet. En effet, il est courant d’avoir à sortir des informations d’un système (liste de clients, liste d’achats, factures, commandes etc…).
Dans ce billet, je vais tâcher de vous expliquer les bases du SDK Open XML (2.0 April CTP) afin de générer des documents Word à partir de vos données métier.
Commencez par télécharger le SDK sur cette page.
Retour sur le format DocX
Les documents Word 2007 (docx) sont basés sur la norme OpenXML. Un docx n’est rien d’autre qu’un package que vous pouvez extraire pour explorer le contenu. Afin de comprendre la composition de cette archive, je vous invite à créer un document très simple sous Word :
Enregistrez le fichier et extrayez en le contenu :
Comme vous pouvez le voir, plusieurs fichiers XML composent votre document. Parmi ceux-ci, il est utile d’insister sur :
- document.xml : c’est lui qui contient la structure et le contenu de votre document. Par exemple ici :
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document><!-- xmlns tronqués pour lisibilité -->
<w:body>
<w:p w:rsidR="00AA5D53" w:rsidRDefault="00DF5AC3">
<w:r>
<w:t>Paragraphe</w:t>
</w:r>
</w:p>
<w:sectPr w:rsidR="00AA5D53" w:rsidSect="00AA5D53">
<w:pgSz w:w="12240" w:h="15840"/>
<w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440"
w:header="720" w:footer="720" w:gutter="0"/>
<w:cols w:space="720"/>
<w:docGrid w:linePitch="360"/>
</w:sectPr>
</w:body>
</w:document>
Toutes les balises que vous pouvez voir dans le code ci-dessus, ou presque, possèdent une classe équivalente définie dans la librairie “DocumentFormat.OpenXml.dll” installée avec le SDK et plus particulièrement dans l’espace de noms “DocumentFormat.OpenXml.Wordprocessing” :
- <w:document> : représentée par la classe “Document”, correspond à la balise racine de votre document word OpenXML
- <w:body> : représentée par la classe “Body”, elle permet de définir le corps du document.
- <w:p> : représentée par la classe “Paragraph”, elle représente un paragraphe dans le document.
- <w:r> : représentée par la classe “Run”, elle permet d’ajouter du contenu texte, lien hypertexte etc…
- <w:t> : représentée par la classe “Text”, elle représente un élément texte simple.
Les balises suivantes dans l’extrait ci-dessus permettent de configurer la mise en page du document, notamment les marges.
Ce qu’il faut retenir
Jusque là, vous devez retenir l’ordre dans lequel les balises sont imbriquées les unes dans les autres. Le format OpenXML est assez intuitif comme tous les langages hiérarchiques dérivés du XML (HTML, SOAP etc..).
- styles.xml : c’est le fichier qui va définir l’apparence de votre document Word. C’est en quelque sorte la feuille de styles CSS d’un document OpenXML. En effet, c’est dans ce fichier que seront définis tous les styles utilisés dans le document. Ces styles sont ensuite référencés par leurs noms dans le fichier “document.xml”. Nous aurons l’occasion d’y revenir dans la suite.
- _rels : ce dossier contient un fichier xml (document.xml.rels) chargé de maintenir toutes les liens (image, hypertexte, etc…) utilisé par le fichier document.xml. Par exemple, si vous rajoutez une image dans votre document, vous retrouverez la ligne suivante dans le fichier de relations :
<Relationship Id="rId4"
Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"
Target="media/image1.jpeg"/>
Un dossier media aura été crée dans l’archive docx. Le fichier “image1.jpeg” peut maintenant être utilisé à partir de son id “rId4” dans document.xml :
<a:blip r:embed="rId4" cstate="print"/>
Je ne vais pas vous décrire toute la norme OpenXml, premièrement parce que ce n’est pas le but de cet article et deuxièmement parce que je n’ai pas la prétention de la connaître :p. Sachez tout de même que les spécifications sont disponibles ici.
Utilisation du SDK OpenXml 2.0
Afin de travailler sur un cas concret, je vous propose de créer une application console qui générera un document Word 2007 contenant un export de tous les clients et achats depuis la base de données Northwind.
Voilà un aperçu du DataContext LINQ que j’utiliserai dans la suite :
Nous allons avoir besoin d’ajouter des références à notre projet :
- WindowsBase.dll : elle contient la définition du namespace System.IO.Packaging sur lequel repose n’importe quelle représentation de document OpenXml en .NET
- DocumentFormat.OpenXml.dll : contient la définition des classes qui vont permettre la génération du document Word.
La première classe que nous allons utiliser est la classe “WordprocessingDocument” définie dans l’espace de noms DocumentFormat.OpenXml.Packaging, c’est elle qui représente le package “docx” contenant tous les fichiers relatifs au document.
Pour créer un document, vous devez utiliser la méthode statique “Create” qui prend en paramètre le chemin du fichier à créer ainsi que le type de document à créer :
WordprocessingDocument document = WordprocessingDocument.Create(
@"C:\CustomersHistory.docx",
WordprocessingDocumentType.Document
);
Une fois le package créé, vous devez lui ajouter le fichier “document.xml” étudié ci-dessus.
Pour cela, vous ferez appel à la méthode “AddMainDocumentPart” du WordprocessingDocument :
MainDocumentPart mainDocumentPart = document.AddMainDocumentPart();
Vous pouvez maintenant créer le noeud racine “<w:document>” :
mainDocumentPart.Document = new Document();
Puis le noeud “<w:body>”. Vous ajouterez ce dernier au document :
Body documentBody = new Body();
mainDocumentPart.Document.Append(documentBody);
La structure du document est maintenant en place. Il ne nous reste plus qu’à récupérer les données en base pour pouvoir écrire le contenu à proprement parlé :
List<Customer> customers = null;
using (NorthwindDataContext db = new NorthwindDataContext())
{
DataLoadOptions options = new DataLoadOptions();
options.LoadWith<Customer>(c => c.Orders);
db.LoadOptions = options;
customers = db.Customers.ToList();
}
Nous allons commencer par donner un titre à notre document. Pour cela, il suffit de créer un paragraphe, d’y ajouter un “run”, d’y ajouter du texte (souvenez vous de la structure vue plus haut) :
Paragraph titleParagraphe = new Paragraph();
Run titleRun = new Run();
Text titleText = new Text("Northwind's Customers History");
titleRun.Append(titleText);
titleParagraphe.Append(titleRun);
Vous pouvez alors ajouter le paragraphe au corps du document :
documentBody.Append(titleParagraphe);
Nous pouvons itérer sur la liste de client et générer, pour chaqu’un d’eux, un paragraphe avec l’id et le nom du client, suivi d’un tableau listant ses achats :
foreach (var customer in customers)
{
//paragraphe pour le nom du client
Paragraph customerNameParagraph = new Paragraph();
Run customerNameRun = new Run();
Text customerNameText = new Text(
String.Format("#{0} : {1}",
customer.CustomerID,
customer.ContactName
)
);
customerNameRun.Append(customerNameText);
customerNameParagraph.Append(customerNameRun);
//ajout du paragraphe au body
documentBody.Append(customerNameParagraph);
//création du tableau :
Table ordersTable = new Table();
//boucle sur les achats
foreach (var order in customer.Orders)
{
//création d'une ligne
TableRow orderRow = new TableRow();
//création d'une cellule pour l'id
//notez que l'on retrouve l'imbriquation para/run/texte
TableCell orderIDCell = new TableCell();
orderIDCell.Append(
new Paragraph(
new Run(
new Text(order.OrderID.ToString())
)
)
);
//création d'une cellule pour la date
TableCell orderDateCell = new TableCell();
orderDateCell.Append(
new Paragraph(
new Run(
new Text(order.OrderDate.Value.ToShortDateString())
)
)
);
//ajout des cellules à la ligne
orderRow.Append(orderIDCell, orderDateCell);
//ajout de la ligne au tableau
ordersTable.Append(orderRow);
}
//ajout du tableau au document
documentBody.Append(ordersTable);
}
La dernière étape consite à sauvegarder le document et à disposer l’instance de WordprocessingDocument :
document.MainDocumentPart.Document.Save();
document.Dispose();
Voilà le document que vous obtenez si vous exécutez ce code :
Nous allons maintenant faire en sorte d’ajouter des styles à notre document.
Gestion des styles
La première solution consiste à récupérer une feuille de style existante. Pour cela, rien de plus simple, vous avez vu plus haut que tout document généré avec Word embarque un fichier styles.xml contenant la stylesheet associée au document. Nous allons donc récupérer ce fichier et l’ajouter au projet afin de pouvoir utiliser les styles de base de Office 2007 :
De la même manière que nous avions créer le fichier “document.xml” dans le package, nous allons devoir créer ce fichier styles.css.
Pour cela, nous allons faire appel à la méthode générique AddNewPart<T> où T sera la classe représentant la partie que nous souhaitons ajouter. Dans le cas du fichier styles.xml, cette classe est “StyleDefinitionPart” :
//création du fichier de styles associés au docx
StyleDefinitionsPart styleDefinitionsPart =
mainDocumentPart.AddNewPart<StyleDefinitionsPart>();
Nous allons ensuite charger le fichier de style depuis notre projet et le passer sous forme d’un FileStream via la méthode FeedData de StyleDefinitionPart :
//récupération du template de style
FileStream stylesTemplate =
new FileStream("styles.xml", FileMode.Open, FileAccess.Read);
styleDefinitionsPart.FeedData(stylesTemplate);
styleDefinitionsPart.Styles.Save();
Vous pouvez alors appliquer tous les styles Office 2007 à votre document. Pour appliquer un style à un paragraphe, rien de plus simple : vous devez passer par une instance de ParagraphProperties et lui spécifier l’id du style à utiliser. Vous n’avez alors qu’à ajouter les propriétés à votre paragraphe.
Ainsi, pour le titre du document, nous ajoutons le code suivant :
//création de propriétés pour le paragraphe
ParagraphProperties titleProperties = new ParagraphProperties();
//on utilise le style Title de word 2007 pour ce paragraphe
titleProperties.ParagraphStyleId = new ParagraphStyleId { Val = "Title" };
//on ajoute les propriétés au paragraphe
titleParagraphe.Append(titleProperties);
Pour le nom des clients, nous ajoutons ce code :
//on applique le style "Heading1" de word2007 au paragraphe
ParagraphProperties customerNameProperties = new ParagraphProperties();
customerNameProperties.ParagraphStyleId =
new ParagraphStyleId { Val = "Heading1" };
customerNameParagraph.Append(customerNameProperties);
Et voilà le résultat :
La seconde solution consiste à créer vous même vos propres styles. Voici les étapes nécéssaires :
- Créer le fichier “styles.xml” dans le package, comme vu ci-dessus
- Instancier la collection de style associée au fichier :
stylePart.Styles = new Styles();
//création d'un style Title
Style dateStyle = new Style();
dateStyle.Append(new Name { Val = "DateFormat" });
//Font à utiliser
RunFonts font = new RunFonts();
font.Ascii = "Calibri";//police
//Propriété d'un bloc <w:r>
RunProperties runProperties = new RunProperties();
runProperties.Append(font);//font
runProperties.Append(new Color() { Val = "006600" });//couleur
runProperties.Append(new Italic());//italic
runProperties.Append(new FontSize { Val = 32 });//taille 32
//ajout des propriétés au style
dateStyle.Append(runProperties);
//ajout du style au StyleDefinitionPart
stylePart.Styles.Append(dateStyle);
Comme précédemment, on doit utiliser un ParagraphProperties :
ParagraphProperties dateProperties = new ParagraphProperties();
dateProperties.ParagraphStyleId = new ParagraphStyleId
{
Val = "DateFormat"
};
Et l’ajouter au paragraphe auquel on souhaite appliquer le style :
TableCell orderDateCell = new TableCell();
orderDateCell.Append(
new Paragraph(
dateProperties,
new Run(
new Text(order.OrderDate.Value.ToShortDateString())
)
)
);
Le tour est joué :
J’espère que cet article vous aura donné envie de vous essayer au SDK OpenXML 2.0. En tout cas, je trouve ça très pratique et vraiment intuitif pour créer rapidement un document word. Tout ça reste à explorer, car les fonctionnalités sembles très nombreuses.
Au passage, je n’ai utilisé ici que la partie permettant de travailler avec Word, mais les autres types de documents type powerpoint, excel etc… sont aussi disponibles !
A bientôt 
Sources : ArticleSDKOpenXML.zip (71.15 kb)