Latest article: Exporter des données depuis Excel

Exporter des données depuis Excel

Si vous utilisez Excel pour créer les données de votre jeu, voici une solution pour exporter automatiquement vos fichiers XLSX vers un format texte ou binaire.

En ce moment je travaille sur un RPG qui bien que modeste demande de manipuler et équilibrer pas mal de nombres.

Pour Reckless Squad nous utilisions simplement des fichiers Lua contenant toutes nos données, un peu à la manière du JSON. Pour moi c’était suffisant, mais c’est parce que je ne m’occupais pas franchement de l’équilibrage. Ce n’est que vers la fin du développement qu’on a ressentis le besoin de mettre les données en relation les unes avec les autres et d’avoir une vue d’ensemble. Franck a donc rassemblé toutes nos données dans un joli tableau Excel, mais on devait toujours reporter les changements aux fichiers Lua. Non seulement il y a un risque d’erreur assez fort (du genre se tromper de ligne) mais en plus il n’a pas fallut longtemps avant que les deux sources cessent d’être synchros.

On a fait avec, mais pour ce projet là je voulais faire les choses différemment. Au tout début j’utilisais Excel pour traiter des fichiers CSV, mais cela posait deux problèmes :

  1. Un fichier CSV ne permet pas d’utiliser tout le potentiel d’Excel.
  2. A chaque enregistrement du fichier il fallait répondre ‘oui’ a une série de messagebox. C’est devenu pénible assez vite.

La solution était de copier/coller le tableau Excel dans un fichier texte. Cela donne quelque chose de similaire au CSV, juste avec des tabulations plutôt que des point-virgules.

Mais ça reste une opération manuelle, rapidement agaçante, et il est facile d’oublier une colonne ou une ligne lors de la sélection.

J’ai donc décidé de retrousser mes manches et de m’attaquer à une vraie solution : écrire un utilitaire pour exporter les données d’un fichier Excel au bon format.

C# est tout naturellement la solution. J’ai créé un projet console et ai ajouté la référence “WindowsBase.dll” qui permet d’utiliser le namespace System.IO.Packaging. Comme le nom l’indique, on va manipuler des archives ZIP. Ou pour être plus précis, des archives XLSX.

Depuis Office 2007, Word et Excel utilisent le format Office Open XML qui est relativement similaire au Open Document. L’idée c’est que le document est constitué de plusieurs fichiers XML contenus dans une archive ZIP. System.IO.Packaging va donc nous permettre d’accéder à ces fichiers XML à l’intérieur de notre XLSX. Les specs du format font plus de 6000 pages, mais la partie qui nous intéresse est simplissime, un rapide coup d’œil aux fichiers XML (après les avoir réindentés) permet de comprendre comment ça marche.

La première chose qui peut surprendre, c’est que les cellules contenant du texte ne stockent pas le texte en question mais un nombre. Il s’agit de l’index vers une table contenant toutes les chaines de caractère du document. Probablement pour des raisons d’optimisation, comme on le fait souvent dans les jeux vidéo d’ailleurs. La première étape est donc de récupérer ces chaines qui sont dans le fichier /xl/sharedStrings.xml :

Le XPath*/*/*” devrait être “sst/si/t” mais pour une raison que je ne m’explique pas, ce dernier ne fonctionne pas. En utilisant les jokers à la place ça fonctionne avec tous les fichiers Excel que j’ai pu tester, donc je vais pas chercher plus loin.

Maintenant que nous avons notre table de chaînes, on va pouvoir s’attaquer à la feuille de calcul en elle même. Un même document Excel peut avoir plusieurs feuilles, et elles sont stockées dans le dossier /xl/worksheets/. Même procédure que plus haut pour charger le fichier XML en mémoire. Le noeud qui nous intéresse est sheetData qui contient une liste de noeuds row, eux même contenant des noeuds c (pour cell).

Simple, pas vrai ?

Bon il y a deux trois trucs auxquels il faut faire attention. Dans mon cas je veux charger ma feuille de style dans un tableau à deux dimensions, il faut donc garder ça en tête :

  • Excel saute les lignes vides. Le numéro réel de la ligne est fournit par l’attribut r.
  • De même, les colonnes de droite vides ne faisant pas parties d’un tableau ne sont pas présentes.

C’est facile à contourner, je me contente de repérer de tels cas et d’ajouter des cellules vides jusqu’à ce que j’ai un beau tableau 2D.

Excel stocke deux informations par cellule : la valeur (<v>) et la formule (<f>). Dans mon cas la formule ne m’intéresse pas, je veux juste le résultat final, je ne récupère donc que la valeur. L’attribut t (type) d’une cellule indique s’il s’agit d’une chaine de caractère ou d’un nombre. De ce que j’ai pu voir, il n’est présent que pour les chaines de caractère (t=”s”). S’il n’y est pas c’est que c’est un nombre.

Comme j’ai besoin de faire la distinction entre int et float pour exporter en binaire, je regarde s’il y a un point dans la chaine de caractère. Si c’est le cas, toute la colonne contient des floats (même si les autres nombres sont entiers).

Dans le cas d’une chaine de caractère, c’est aussi un nombre que vous trouverez. C’est l’index de la chaine dans la table que l’on a créé plus haut.

A ce moment là nous avons déjà un tableau contenant l’intégralité de notre feuille de calcul. J’ai rajouté une fonction pour séparer la première ligne du reste du tableau et la considérer comme un en-tête :

Cependant, mes fichiers Excel contiennent dans une même feuille plusieurs tableaux, par exemple je me sert des stats du joueur pour déterminer celles des monstres. J’ai donc un tableau contenant les dites stats, et je ne souhaite pas l’exporter. J’ai aussi d’autres colonnes dans mon tableau de monstres qui ne sont pas pertinentes et qui servent uniquement pour les formules. Ce que je veux, c’est n’exporter qu’un sous-ensemble de ma feuille de calcul.

Comme nous avons fait en sorte de stocker nos cellules dans un tableau à deux dimensions, extraire un sous-ensemble est trivial :

Le tableau s’indexe comme n’importe quel tableau 2D (y*width+x), et avec ça je peux donc extraire juste la partie qui m’intéresse.

Enfin, l’export. Mon utilitaire exporte au même format que le copier/coller, l’avantage c’est qu’il le fait tout seul comme un grand quand je lance mes scripts de compilation. Je peux donc utiliser cette ligne de commande pour exporter un sous ensemble du tableau :

XLS2BIN.exe txt “Data.xlsx” “Monsters.txt” sheet1 true 0 20 18 22

Cool.

J’ai aussi codé une fonction permettant d’exporter directement en binaire, ce qui peut être intéressant pour les petites tables triviales. Mais il est plus probable que vous ayez besoin de traiter ces données, par exemple dans mon cas le tableau des monstres contient une colonne “animation d’attaque”. L’animation est spécifiée avec une chaine de caractères (ex: “sword”) mais j’utilise un autre script pour remplacer cette chaîne par l’ID de l’animation. Cela a plusieurs avantages :

  • Données plus compactes et temps de chargement plus court, ce qui est important sur un smartphone.
  • Possibilité de vérifier les données et de tout arrêter si l’animation “swrod” (au lieu de “sword”) n’a pas été trouvée.

Cet autre script recrache toutes les données du jeu dans un unique fichier binaire, et c’est la seule chose que le jeu en lui même connaisse. Pas besoin de parser des chaines de caractère, toutes les données sont déjà dans le bon format. Je développe pour Windows Phone 7 et utilise donc C#, ce qui m’oblige à récupérer les données avec un BinaryReader, mais si vous utilisez du bon vieux C ou C++, un simple fread peut suffire pour charger l’ensemble des données dans une structure. Faites juste attention à ne pas avoir des champs de taille variable.

Bon arrivé là je vais quand même vous passer ce petit utilitaire, avec ses sources. Je l’ai fait à l’arrache, c’est assez peu documenté et il est possible que ça ne fonctionne pas avec tous les fichiers Excel. D’ailleurs, j’ai utilisé Excel 2007, je n’ai aucune idée si ça fonctionne avec 2010. Lancez-le sans argument pour voir la liste des paramètres attendus.

Télécharger

Enfin, comme jamais rien n’est parfait en ce bas monde, il reste un problème avec cette méthode. A vrai dire il reste un problème avec toute méthode impliquant Excel : il locke les fichiers tant qu”ils sont ouverts. La fonction File.Open (même en read only) va donc échouer si le fichier est déjà ouvert par Excel. Devoir quitter et relancer Excel à chaque fois qu’on veut tester les nouvelles valeurs est, disons-le franchement, super chiant. Si quelqu’un connait un moyen pour éviter ça, je rêve de l’entendre.

Discussion

Reply