Paru le 12 mars 2006
Par Pierre Lagarde, Microsoft France
Sur cette page
Introduction
Détail d’un fichier Excel exemple
Création du Template dans Excel
Ajout du document Excel dans le projet Visual Studio en tant que ressource
Extraction du model et manipulation pour générer le document cible Excel
Insérer une formule
Conclusion
Introduction
La création de document Office sur le serveur est maintenant beaucoup plus facile grâce au nouveau format de fichier Open XML que propose Office 2007. Ce format n’est, en effet, qu’un fichier ZIP contenant des fichiers XML, en opposition avec l’ancien format binaire que représentait un fichier .doc, .xls ou .ppt.
Ainsi, il est maintenant possible de créer ou de manipuler des documents Office sur un serveur, sans avoir à y installer Office.
C’est dans ce contexte qu’on va tenter de réaliser une application console à partir d’un document Excel existant et dans lequel viendront s’insérer du texte, des valeurs numériques ou même des formules, tout ça en ne manipulant que du XML.
Cet article va détailler comment on peut créer un document Excel, non pas à partir de rien mais d’un model préalablement créé dans Excel. Ce model va être embarqué dans l’application et sera manipulé en tant que ressource de l’application, pour ne pas avoir à tout générer à chaque fois et donc simplifier sa génération.
Pour simplifier la manipulation de document Open XML, on utilisera l’API du Framework 3.0 « System.IO.Package ».
Pour pouvoir importer ce package dans une solution Visual Studio, il faut installer le Framework 3.0 son SDK et l’Add-on à Visual Studio.
Détail d’un fichier Excel exemple
Un document Open XML Excel contient cette arborescence :
Le répertoire « worksheets » contient un fichier XML par feuille de calcul Excel. Ces fichiers XML respectent le format SpreadSheetML.
Schéma disponible ici : http://www.ecma-international.org/news/TC45_current_work/TC45-2006-50_final_draft.htm
Création du Template dans Excel
La première étape consiste à créer un document Excel qui nous servira de model dans notre application et qu’on ajoutera comme ressource dans le projet Visual Studio.
Ouvrir Excel et créer un document avec un titre qui prend 3 colonnes, par exemple.
PS : Le texte saisi en « [] » est juste là reconnaitre le texte à remplacer.
Ajout du document Excel dans le projet Visual Studio en tant que ressource
Pour ajouter une ressource à un projet, il faut aller dans les propriétés du projet et ajouter la ressource.
Ici, on ajoutera une ressource existante qui sera le document Excel qu’on vient de créer.
Un répertoire « Resources » est créé dans la solution Visual Studio contenant le fichier Excel.
Extraction du model et manipulation pour générer le document cible Excel
Ce qui reste à faire :
-
Extraire le document des ressources.
-
Ouvrir les packages pour éditer le fichier Excel.
-
Manipuler les documents XML pour mettre à jour le model en vue du document cible.
-
Générer le document final sous forme de fichier physique.
Extraire le document des ressources
On va ouvrir le package à partir des ressources de l’application.
using (PackageHelper package =
new PackageHelper(
Properties.Resources.ExcelSample))
{…} Ouvrir les packages pour éditer le fichier Excel
Puis charger le XML (SpreadSheetML) depuis le package.
Uri sheet1Uri =
new Uri(@"/xl/worksheets/sheet1.xml",UriKind.Relative);
XmlDocument sheet1Xml =
package.GetWritablePart(sheet1Uri);
Une fois qu’on a accès au SpeadSheetML, la manipulation n’est pas aussi simple que pour le WordProcessiongML. Il y a plusieurs fichiers à manipuler en même temps.
Manipuler les documents XML
Regardons dans le détail le contenu du Package du Template Excel :
Tout ce qui est numérique est stocké dans les fichiers « sheet*.xml » mais le texte (dans notre Template « [Titre] »), quant à lui, est stocké dans le fichier « sharedStrings.xml ».
Qui a cette forme :
Et donc le fichier « sheet1.xml » y fait référence de cette façon : <v>0</v>
Voilà le document cible :
Il faut donc :
-
Remplacer [Titre] par « Rapport Activité »
-
Ajouter Janvier sur la ligne 2
-
Ajouter 123 et 234 sur la ligne 2, colonnes B et C
Remplacer [Titre] par « Rapport Activité »
On va commencer par lire le fichier « sharedStrings.xml » et charger son contenu dans un Dictionnaire<string,int>.
Uri sharedStringsUri =
new Uri("/xl/sharedStrings.xml", UriKind.Relative);
// Chargement du XML
XmlDocument sharedStringsXml =
package.GetWritablePart(sharedStringsUri);
XmlElement sharedStringsElt =
sharedStringsXml.DocumentElement;
// Parcourt de chaînes de caractère
XmlNodeList shareStrings = sharedStringsElt.SelectNodes("/x:sst/x:si/x:t",
Namespaces.NamespaceManager);
// Ajout dans le dictionnaire
foreach (XmlNode node in shareStrings)
{
m_sharedStrings.Add(node.InnerText, m_sharedStrings.Count);
}
Il faut maintenant remplacer « [Titre] » dans le dictionnaire. Le fichier « shareStrings.xml » sera mis à jour à partir du dictionnaire, juste avant la création du fichier physique.
int i = m_sharedStrings[oldText];
m_sharedStrings.Remove(oldText);
m_sharedStrings.Add(text, i);
Ajouter « Janvier » sur la ligne 2
Il faut modifier le fichier « sheet1.xml » pour lui ajouter ce nœud : (où id est l’item dans le dictionnaire de chaîne de caractères).
<c r="A1" s="1" t="s">
<v>id</v>
</c>
Et donc ajouter « Janvier » au dictionnaire.
// Récupérer l’index s’il existe déjà
if (m_sharedStrings.ContainsKey(text))
return m_sharedStrings[text];
// Sinon ajouter une valeur dans le dictionnaire
int newIndex = m_sharedStrings.Count;
m_sharedStrings.Add(text, newIndex);
return newIndex;
Ajouter 123 et 234 sur la ligne 2, colonne B et C
Pour ajouter des valeurs numériques, c’est plus simple : on peut directement écrire dans le fichier « sheet1.xml ».
// Créer un nœud <x:c r="A1">
writer.WriteStartElement(Prefixes.SpreadsheetML,
"c", Namespaces.SpreadsheetML);
writer.WriteAttributeString("r", column + row.ToString());
// Ecrire la valeur numérique dans un nœud <x:v>732.00</x:v>
writer.WriteElementString(Prefixes.SpreadsheetML, "v",
Namespaces.SpreadsheetML, value.ToString("#0.00"));
// Fermeture </x:c>
writer.WriteEndElement();
Générer le document final sous forme de fichier physique
Il ne reste plus qu’à sauvegarder le Dictionnaire des chaînes de caractères dans le fichier « sharedStrings.xml ».
// Uri sur sharedStrings
Uri sharedStringsUri =
new Uri("/xl/sharedStrings.xml", UriKind.Relative);
// Charger le XML
XmlDocument sharedStringsXml =
package.GetWritablePart(sharedStringsUri);
// Nettoyage
sharedStringsXml.DocumentElement.RemoveAll();
// Sérialiser le contenu de m_sharedString dans le fichier « sharedStrings.xml »
XPathNavigator sharedStringsNav =
sharedStringsXml.DocumentElement.CreateNavigator();
using (XmlWriter writer = sharedStringsNav.AppendChild())
{
// Parcourt du dictionnaire
foreach (KeyValuePair<string, int>
sharedString in m_sharedStrings)
{
// Ecriture du nœud <x:si><x:t>[à remplacer]</x:t></x:si>
writer.WriteStartElement("si", Namespaces.SpreadsheetML);
writer.WriteElementString("t", Namespaces.SpreadsheetML,
sharedString.Key);
writer.WriteEndElement();
}
}
// Sauvegarde de la Part XML vers le package
package.SavePart(sharedStringsUri, sharedStringsXml);
Puis sauvegarder le package dans un fichier physique.
// Flush
m_package.Flush();
m_package.Close();
// Ecriture dans un fichier physique
using (FileStream outputStream = File.Create(filename))
m_packageData.WriteTo(outputStream);
// Fermeture du Package
m_packageData.Close();
Insérer une formule
Après avoir inséré du texte et des valeurs, insérons maintenant une formule. Ici, nous allons insérer, à la cellule D2, la formule de la somme de B2 et C2 : « =SUM(B2:C2) »
Le principe est le suivant : il faut insérer la formule dans le fichier « sheet1.xml ».
<x:c r="D2">
<x:f>SUM(B2:C2)</x:f>
</x:c>
Puis indiquer dans le fichier « calcChain.xml » que la cellule D2 est une cellule à calculer.
<calcChain xmlns="…">
<c r="E2" i="1" />
</calcChain>
Mais contrairement au fichier « sharedStrings.xml » qui existait déjà dans le package, le fichier « calcChain.xml » n’existe pas, il faudra donc le créer.
Insérer la formule
Comme pour les numériques, on va simplement manipuler le XML de la part sheet1.xml.
// Créer le nœud <x:c r="A1">
writer.WriteStartElement(Prefixes.SpreadsheetML, "c",
Namespaces.SpreadsheetML);
writer.WriteAttributeString("r", column + row.ToString());
// insérer la formule <x:f>SUM(B2:C2)</x:f>
writer.WriteElementString(Prefixes.SpreadsheetML, "f",
Namespaces.SpreadsheetML, formula);
// Fermer le nœud </x:c>
writer.WriteEndElement();
// Ajoutez la référence à la cellule dans une liste
// List<Cell> m_cells
m_cells.Add(new Cell(sheetId, cell, childChain));
L’objet Cell :
private struct Cell
{
public int SheetId;
public string CellName;
public bool ChildChain;
public Cell(int sheetId, string cellName, bool childChain)
{
SheetId = sheetId;
CellName = cellName;
ChildChain = childChain;
}
}
Créer la part « calcChain.xml »
Pour créer une Part, il faut partir du Package et d’une URI, puis créer une relation entre « sheet1.xml » et « calcChain.xml ».
Pour simplifier la création du fichier « calcChain.xml », on peut en créer un vide, et l’insérer en tant que ressource dans l’application.
// Les URIs
Uri workbookUri =
new Uri("/xl/workbook.xml", UriKind.Relative);
Uri calcChainUri =
new Uri("/xl/calcChain.xml", UriKind.Relative);
// Création de la nouvelle part à partir du fichier stocké en ressource
package.CreateNewPart(calcChainUri,
"application/vnd.Open XMLformats-" +
"officedocument.spreadsheetml.calcChain+xml",
Properties.Resources.CalcChainTemplate);
// Création de la relation
package.CreateInternalRelationship(
workbookUri, calcChainUri,
"http://schemas.Open XMLformats.org/" +
"officeDocument/2006/relationships/calcChain");
Créer le fichier XML
Avant la création du fichier physique Excel, il faut donc remplir le fichier « calcChain.xml » à partir de la List<Cell> m_cells.
// Création du fichier XML
XmlDocument calcChainXml = package.GetWritablePart(calcChainUri);
// Création du XmlWriter
XPathNavigator calcChainNav =
calcChainXml.DocumentElement.CreateNavigator();
using (XmlWriter writer = calcChainNav.AppendChild())
{
// Pour chaque formule du dictionnaire
foreach (Cell cell in m_cells)
{
// Ecrire le nœud
writer.WriteStartElement("c",
Namespaces.SpreadsheetML);
writer.WriteAttributeString("r", cell.CellName);
writer.WriteAttributeString("i",
cell.SheetId.ToString());
if (cell.ChildChain)
writer.WriteAttributeString("s", "1");
writer.WriteEndElement();
}
}
// Sauvegarde du XML dans le package
package.SavePart(calcChainUri, calcChainXml);
Conclusion
Nous avons donc réussi à créer un document Excel à partir d’un Template. Nous avons vu comment modifier et insérer du texte, des données numériques et des formules dans un fichier Excel Open XML, en manipulant du « SpreatSheetML ».
Ce scénario peut donc être appliqué dans des solutions purement serveur, sans qu’Excel ne soit installé sur le serveur. En définitive, ce n’est que de la manipulation de package (facilitée par le Framework 3.0) et de la manipulation de XML.
Téléchargez
OpenXML-Creation-document-Excel-a-partir-Template-Sample.zip
88 Ko