Class Helper, LINQ et fichiers CSV

C# 3.0 a apporté des nouveautés syntaxiques qui ne se bornent pas à être seulement des petits suppléments qu'on peut ignorer, au contraire il s'agit de modifications de premier plan qui impactent en profondeur la façon d'écrire du code si on sait tirer partie de ces nouveautés. J'en ai souvent parlé, au travers de billets de ce blog ou d'articles comme celui décrivant justement les nouveautés syntaxiques de C#3.0. Je ne vais donc pas faire de redites, je suppose que vous connaissez maintenant tous ces ajouts au langage. Ce que je veux vous montrer c'est qu'en utilisant correctement C# 3.0 on peut écrire un code incroyable concis et clair pour résoudre les problèmes qui se posent au quotidien au développeur.

Le problème à résoudre

Vous recevez un fichier CSV, disons le résultat d'une exportation depuis un soft XY des ventes à l'export de votre société. Vous devez rapidement ajouter la possibilité d'importer ces données dans l'une de vos applications mais après les avoir interprétées, triées, voire filtrées.

La méthode la plus simple

Rappel : un fichier CSV est formé de lignes comportant plusieurs champs séparés par des virgules. Les champs peuvent être délimités par des double quotes.

A partir de cette description voyons comment avec une requête LINQ nous pouvons lire les données, les filtrer, les trier et les mettre en forme. Le but ici sera de générer en sortie une chaîne par enregistrement, chaîne contenant des champs "paddés" par des espaces pour créer un colonnage fixe. On tiendra compte des lignes débutant par le symbole dièse qui sont considérées comme des commentaires.

   1:  string[] lines = File.ReadAllLines("Export Sales Info-demo.csv");
   2:  var t1 = lines
   3:      .Where(l => !l.StartsWith("#"))
   4:      .Select(l => l.Split(','))
   5:      .OrderBy(items=>items[0])
   6:      .Select(items => String.Format("{0}{1}{2}",
   7:          items[1].PadRight(15),
   8:          items[2].PadRight(30),
   9:          items[3].PadRight(10)));
  10:  var t2 = t1
  11:      .Select(l => l.ToUpper());
  12:  foreach (var t in t2.Take(5))
  13:      Console.WriteLine(t);

La sortie (avec le fichier exemple fourni) sera :

SAN JOSE       CITY CENTER LODGE             CA
SAN FRANCISCO  KWIK-E-MART                   CA
SEATTLE        LITTLE CORNER SWEETS          WA
SEATTLE        LITTLE CORNER SWEETS          WA
IRVINE         PENNY TREE FOODS CORPORATION  CA
 

Cette méthode, très simple, ne réclame rien d'autre que le code ci-dessus. La solution est applicable à tout moment et s'adapte facilement à toute sorte de fichiers sources. Elle possède malgré tout quelques faiblesses. Par exemple les champs contenant des doubles quotes ou les champs mal formés risquent de faire échouer la séquence. Dans certains cas cela sera inacceptable, dans d'autres on pourra parfaitement protéger la séquence dans un bloc try/catch et rejeter les fichiers mal formés. Une fois encore il ne s'agit pas ici de montrer un code parfaitement adapté à un problème précis, mais bien de montrer l'esprit, la façon d'utiliser C# 3.0 pour résoudre des problèmes courants. 

Expliquons un peu ce code :

la ligne 1 charge la totalité du fichier CSV dans un tableau de strings. La méthode peut sembler "brutale" mais en réalité elle est souvent très acceptable car de tels fichiers dépassent rarement les quelques dizaines ou centaines de Ko et la RAM de nos machines modernes n'impose en rien une lecture bufferisée, tout peut tenir d'un bloc en mémoire sans le moindre souci. Cela nous arrange ici il faut l'avouer mais l'utilisation d'un flux bufferisé resterait parfaitement possible.

Nous disposons maintenant d'un tableau de chaînes, chacune étant formatée en CSV. La première requête LINQ (variable "t1" en ligne 2) fait l'essentiel du travail :

  • gestion des commentaires (ligne 3)
  • décomposition des champs (ligne 4)
  • tri sur l'un des champs (ligne 5)
  • génération d'une sortie formatée (lignes 6 à 9)

Ce qui est merveilleux ici c'est que nous avons en quelques lignes un concentré de ce qu'est une application informatique : acquisition de données, traitement de ces données, production de sorties exploitables par un être humain ou une autre application. En si peu de lignes nous avons réalisé ce qui aurait nécessité une application entière avec les langages de la génération précédente comme C++ ou Delphi. C'est bien ce bond en avant que représente C# 3.0 qui est ici le vrai sujet du billet.

Une méthode plus complète

La séquence que nous avons vu plus haut répond au problème posé. Elle possède quelques lacunes. Celles liées à sa trop grande simplicité (certains cas du parsing CSV ne sont pas correctement pris en compte) et celles liées à sa forme : c'est un bout de code qu'il faudra copier/coller pour le réutiliser et qui viendra "polluer" nos requêtes LINQ les rendant plus difficiles à lire.

Si ces lacunes sont acceptables dans certains cas (règlement ponctuel d'un problème ponctuel) dans d'autres cas on préfèrera une approche plus facilement réutilisable. Notamment si nous sommes amenés à traiter plus ou moins souvent des fichiers CSV nous avons intérêt à encapsuler le plus possible le parsing et à le rendre plus facilement reexploitable.

L'une des voies serait de créer une classe totalement dédiée à cette tâche. C'est une solution envisageable mais elle est assez lourde et amène son lots de difficultés.

Nous allons choisir ici une autre approche, celle de la création d'un class helper. C'est à dire que nous souhaitons non pas créer une classe qui traite un fichier CSV comme un tout, mais nous voulons pouvoir parser n'importe quelle chaîne de caractères formatée en CSV. L'approche est très différente. Dans le premier cas il nous faudra complexifier de plus en plus notre classe, voire créer une hiérarchie de classes pour traiter les fichiers CSV mais aussi les flux mémoire CSV, et puis encore les services Web retournant du CSV, etc, etc.

Dans le second cas nous allons plutôt ajouter la capacité à la classe string de parser une chaîne donnée. La source de la chaîne ne nous importe pas. Il pourra s'agir d'un élément d'un tableau de chaîne comme dans le premier exemple autant que d'une chaîne lue depuis un flux mémoire, une section data d'un fichier XML, etc. D'un certain sens nous acceptons d'être moins "complet" que l'option "classe dédiée CSV", mais nous gagnons en agilité et en "réutilisabilité" en faisant abstraction de la provenance de la chaîne à parser. Bien entendu nous pouvons nous offrir le luxe d'une telle approche car nous savons que nous pouvons nous reposer sur C# 3.0, ses class helpers et LINQ...

Le projet qui accompagne ce billet contient tout le code nécessaire et même un fichier CSV d'exemple, nous n'entrerons pas dans les détails de l'implémentation du class helper lui-même qui étend la classe string, parser du CSV n'est qu'un prétexte sans plus d'intérêt dans ce billet. Le code utilisé pour l'exemple provient d'ailleurs d'un billet de  Eric White dont vous pouvez visiter le blog si vous lisez l'anglais.

la façon d'écrire un class helper est décrite dans mon article sur C# 3.0, regardons juste sa déclaration :

public static string[] CsvSplit(this String source)

Cette méthode est déclarée au sein d'une classe statique de type "boîte à outils" pouvant centraliser d'autres méthodes utilitaires. La déclaration nous montre que le class helper s'applique à la classe String uniquement (this String source) et qu'elle retourne un tableau de chaîne (string[]).

Une fois le class helper défini (code complet dans le projet accompagnant l'article) il nous est possible d'écrire des requêtes LINQ du même type que celle du premier exemple. Mais cette fois-ci le parsing CSV est réalisé par le class helper ce qui permet à la fois de le rendre plus sophistiqué et surtout de ne plus avoir à le copier/coller dans les requêtes LINQ...

Voici un exemple d'utilisation du class helper sur le même fichier CSV. Ici nous parsons la source, nous la trions, nous éliminons les lignes de commentaire mais aussi nous créons en réponse une classe anonyme contenant le nom du contact, sa ville et le nom de sa société. Il est dès lors possible de traiter la liste d'objets résultat comme n'importe quelle liste : affichage, traitement, génération d'état, etc.

   1:  var data = File.ReadAllLines("Export Sales Info-demo.csv").Where(s=>!s.StartsWith("#"))
   2:      .Select(l =>
   3:  {
   4:  var split = l.CsvSplit();
   5:  return new
   6:             {
   7:                 Contact = split[0],
   8:                 City = split[1],
   9:                 Company = split[2]
  10:             };
  11:  }  ).OrderBy(x=>x.Contact);
  12:   
  13:  foreach (var d in data.Take(5))
  14:      Console.WriteLine(d);

Ce qui, avec le même fichier source, affichera à la console :

{ Contact = Allen James, City = San Jose, Company = City Center Lodge }
{ Contact = Bart H. Perryman, City = San Francisco, Company = Kwik-e-mart }
{ Contact = Beth Munin, City = Seattle, Company = Little Corner Sweets }
{ Contact = Beth Munin, City = Seattle, Company = Little Corner Sweets }
{ Contact = Bruce Calaway, City = Irvine, Company = Penny Tree Foods Corporation } 

Conclusion

La bonne utilisation de C# 3.0 permet de réduire significativement la taille du code donc de réduire dans les mêmes proportions les bugs et d'augmenter la productivité. Apprendre à utiliser cette nouvelle approche c'est gagner sur tous les plans...

 

Le projet exemple : LinqToCsv.zip (9,46 kb) [Downloads: 73]

Utiliser des clés composées dans les dictionnaires

Les dictionnaires sont des listes spécialisées permettant de relier deux objets, le premier étant considéré comme la clé, le second comme la valeur. Une fois clé et valeur associées au sein d'une entrée du dictionnaire ce dernier est capable de retourner rapidement la valeur de toute clé. Les dictionnaires peuvent être utilisés en de nombreuses circonstances, comme la conception de caches de données par exemple.

Un dictionnaire se créée à partir de la classe générique Dictionnary<Key,Value>. Comme on le remarque si la clé peut être de tout type elle reste monolithique, pas de clés composées donc, et encore moins de classes telles Dictionnary<Key1,Key2,Value> ou Dictionnary<Key1,Key2,Key3,Value> etc...

Or, il est assez fréquent qu'une clé soit composée (multi-part key ou composed key). Comment utiliser les dictionnaires génériques dans un tel cas ?

La réponse est simple : ne confondons pas une seule clé et un seul objet objet clé ! En effet, si le dictionnaire n'accèpte qu'un seul objet pour la partie clé, rien n'interdit que cet objet soit aussi complexe qu'on le désire... Il peut donc s'agir d'instances d'une classe créée pour l'occasion, classe capable de maintenir une clé composée.

Vous allez me dire que ce n'est pas bien compliqué, et vous n'aurez qu'à moitié raison...

Créer une classe qui contient 2 propriétés n'est effectivement pas vraiment ardu. Prenons un exemple simple d'un dictionnaire associant des ressources à des utilisateurs. Imaginons que l'utilisateur soit repéré grâce à deux informations, son nom et une clé numérique (le hash d'un password par ex) et imaginons, pour simplifier, que la ressource associée soit une simple chaîne de caractères.

La classe qui jouera le rôle de clé du dictionnaire peut ainsi s'écrire en une poignée de lignes :

   1:  public class LaClé
   2:  {
   3:    public string Name { get; set; }
   4:    public int PassKey {get; set; }
   5:  }

Oui, c'est vraiment simple. Mais il y a un hic !

En effet, cette classe ne gère pas l'égalité, elle n'est pas "comparable". De base, écrite comme ci-dessus, elle ne peut pas servir de clé à un dictionnaire...

Pour être utilisable dans un tel contexte il faut ajouter du code afin de gérer la comparaison entre deux instances. Il existe plusieurs façons de faire, l'une de celle que je préfère est l'implémentation de l'interface générique IEquatable<T>. On pourrait par exemple choisir une autre voie en implémentant dans la classe clé une autre classe implémentant IEqualityComparer<T>. Toutefois dans un tel cas il faudrait préciser au dictionnaire lors de sa création qu'il lui faut utiliser ce comparateur là bien précis, cela est très pratique si on veut changer de comparateur à la volée, mais c'est un cas assez rare. En revanche si demain l'implémentation changeait dans notre logiciel et qu'une autre structure soit substituée au dictionnaire il y aurait de gros risque que l'application ne marche plus: les objets clés ne seraient toujours pas comparables deux à deux "automatiquement".

L'implémentation d'une classe utilisant IEqualityComparer<T> est ainsi une solution partielle en ce sens qu'elle réclame une action volontaire pour être active. De plus cette solution se limite aux cas où un comparateur de valeur peut être indiqué.

C'est pour cela que je vous conseille fortement d'implémenter directement dans la classe "clé" l'interface IEquatable<T>. Quelles que soient les utilisations de la classe dans votre application l'égalité fonctionnera toujours sans avoir à vous soucier de quoi que ce soit, et encore moins, et surtout, des éventuelles évolutions du code.

Comme par enchantement l'excellent Resharper (add-in pour VS totalement indispensable) sait générer automatiquement tout le code nécessaire, je n'ai donc pas eu grand chose à saisir pour le code final... Ceux qui ne disposent pas de cet outil merveilleux pourront bien entendu s'inspirer de l'implémentation proposée pour leur propre code.

Le code de notre classe "clé" se transforme ainsi en quelque chose d'un peu plus volumineux mais de totalement fonctionnel :

   1:  public class ComposedKey : IEquatable<ComposedKey>
   2:          {
   3:              private string name;
   4:              public string Name
   5:              {
   6:                  get { return name; }
   7:                  set { name = value; }
   8:              }
   9:   
  10:              private int passKey;
  11:              public int PassKey
  12:              {
  13:                  get { return passKey; }
  14:                  set { passKey = value; }
  15:              }
  16:   
  17:              public ComposedKey(string name, int passKey)
  18:              {
  19:                  this.name = name;
  20:                  this.passKey = passKey;
  21:              }
  22:   
  23:              public override string ToString()
  24:              {
  25:                  return name + " " + passKey;
  26:              }
  27:   
  28:              public bool Equals(ComposedKey obj)
  29:              {
  30:                  if (ReferenceEquals(null, obj)) return false;
  31:                  if (ReferenceEquals(this, obj)) return true;
  32:                  return Equals(obj.name, name) && obj.passKey == passKey;
  33:              }
  34:   
  35:              public override bool Equals(object obj)
  36:              {
  37:                  if (ReferenceEquals(null, obj)) return false;
  38:                  if (ReferenceEquals(this, obj)) return true;
  39:                  if (obj.GetType() != typeof (ComposedKey)) return false;
  40:                  return Equals((ComposedKey) obj);
  41:              }
  42:   
  43:              public override int GetHashCode()
  44:              {
  45:                  unchecked
  46:                  {
  47:                      return ((name != null ? name.GetHashCode() : 0)*397) ^ passKey;
  48:                  }
  49:              }
  50:   
  51:              public static bool operator ==(ComposedKey left, ComposedKey right)
  52:              {
  53:                  return Equals(left, right);
  54:              }
  55:   
  56:              public static bool operator !=(ComposedKey left, ComposedKey right)
  57:              {
  58:                  return !Equals(left, right);
  59:              }
  60:          }

Désormais il devient possible d'utiliser des instances de la classe ComposedKey comme clé d'un dictionnaire générique.

Dans un premier temps testons le comportement de l'égalité :

   1:  // Test of IEquatable in ComposedKey
   2:  var k1 = new ComposedKey("Olivier", 589);
   3:  var k2 = new ComposedKey("Bill", 9744);
   4:  var k3 = new ComposedKey("Olivier", 589);
   5:   
   6:  Console.WriteLine("{0} =? {1} : {2}",k1,k2,(k1==k2));
   7:  Console.WriteLine("{0} =? {1} : {2}",k1,k3,(k1==k3));
   8:  Console.WriteLine("{0} =? {1} : {2}",k2,k1,(k2==k1));
   9:  Console.WriteLine("{0} =? {1} : {2}",k2,k2,(k2==k2));
  10:  Console.WriteLine("{0} =? {1} : {2}",k2,k3,(k2==k3));

Ce code produira le résultat suivant à la console :

Olivier 589 =? Bill 9744 : False
Olivier 589 =? Olivier 589 : True
Bill 9744 =? Olivier 589 : False
Bill 9744 =? Bill 9744 : True
Bill 9744 =? Olivier 589 : False

Ces résultats sont conformes à notre attente. Nous pouvons dès lors utiliser la classe au sein d'un dictionnaire comme le montre le code suivant :

   1:  // Build a dictionnary using the composed key
   2:  var dict = new Dictionary<ComposedKey, string>()
   3:                 {
   4:                     {new ComposedKey("Olivier",145), "resource A"},
   5:                     {new ComposedKey("Yoda", 854), "resource B"},
   6:                     {new ComposedKey("Valérie", 9845), "resource C"},
   7:                     {new ComposedKey("Obiwan", 326), "resource D"},
   8:                 };
   9:   
  10:  // Find associated resources by key
  11:   
  12:  var fk1 = new ComposedKey("Yoda", 854);
  13:  var s = dict.ContainsKey(fk1) ? dict[fk1] : "No Resource Found";
  14:  // must return 'resource B'
  15:  Console.WriteLine("Key '{0}' is associated with resource '{1}'",fk1,s);
  16:   
  17:  var fk2 = new ComposedKey("Yoda", 999);
  18:  var s2 = dict.ContainsKey(fk2) ? dict[fk2] : "No Resource Found";
  19:  // must return 'No Resource Found'
  20:  Console.WriteLine("Key '{0}' is associated with resource '{1}'", fk2, s2);

Code qui produira la sortie suivante :

Key 'Yoda 854' is associated with resource 'resource B'
Key 'Yoda 999' is associated with resource 'No Resource Found'

Et voilà ...

Rien de tout cela est compliqué mais comme on peut le voir il y a toujours une distance de la coupe aux lèvres, et couvrir cette distance c'est justement tout le savoir-faire du développeur !

Le projet VS 2008 : MultiKey.zip (5,84 kb) [Downloads: 52]

.. Stay Tuned !

Donnez des couleurs à Blend et Design (intégration de Adobe Kuler).

Comme une coïncidence je parlais de Adobe dans mon dernier billet... Et si je n'en faisais pas forcément l'éloge pour la facette programmation de leurs outils, en revanche je reconnaissais bien volontier leur talent en ce qui concerne les outils de graphisme. En voici une illustration, et un moyen de récupérer ce savoir-faire dans les produits de design de la gamme Microsoft Expression !

Adobe a mis en ligne un site qui s'appelle Adobe Kuler, son utilité est de permettre facilement, et visuellement, de créer des nuanciers de couleurs. Les graphistes utilisent fréquemment les nuanciers car une fois ajoutés à un projet ils sont le garant du respect de la charte décidée pour celui-ci.

Bien entendu les outils tels que Expression Design ou Expression Blend savent gérés les nuanciers, chacun à sa façon. Design le gère de façon similaire à Adobe Illustrator, Blend le fait sous la forme de ressources de brosses. Mais qu'importe la technique exacte cela revient au même. Surtout, les contraintes qu'un graphiste doit prendre en compte restent les mêmes, qu'il travaille sous Illustrator ou Design ou même Blend : le respect de la charte est essentiel.

Je ne pourrais pas dire, faute de le connaître, si Jonas Follesoe est type bien ou sympa, en revanche je peux vous affirmer que c'est un bon développeur WPF qui aime Blend et Design ! Et il a créé un add-in pour ces deux produits qui permet de disposer d'une palette supplémentaire se connectant à Adobe Kuler pour aller y chercher des nuanciers !

Recherche des favoris, des plus populaires, etc, voire recherche sur un mot (tous les nuanciers "ocean" par exemple), tout cela est possible. On récupère les nuanciers par simple copie dans le logiciel (ou même par drag'n drop). C'est vraiment génial. D'autant qu'on peut aussi accéder au site Adobe Kuler pour modifier un nuancier ou en créer un totalement nouveau. Le site propose des modes intelligents comme la création de palettes à partir d'algorithmes de type couleurs complémentaires, nuancier monochromatique, etc.

Toutes les instructions pour télécharger le plugin Blend/Design ainsi que l'application WPF stand-alone sur son le blog de Jonas.

Le site Adobe Kuler vaut lui aussi une visite si vous ne connaissez pas.

Enfin, le projet de Jonas est sur CodePlex, dont open source, toujours bon pour repiquer les bonnes idées de développement et progresser dans son apprentissage !

Et comme c'est joli toutes ces couleurs, voici une petite capture écran du plugin en action sur mon Blend 2 SP1 :

 

Silverlight 2.0 RC disponible (avec les MAJ de VS 2008 et Blend)

L'avènement de Silverlight approche... Si la version 1.0 a surtout été l'occasion "d'occuper le marché", la version 2.0 marquera très certainement la véritable entrée de cette technologie dans la réalité des développements Web.

Silverlight 1.x c'était du XAML et du Javascript, vous allez me dire ce n'est pas pire que Flash ou Flex avec leur ActionScript (que je n'ai jamais aimé). Mais cela n'était bien évidemment pas une finalité en soi. Faire aussi bien (ou mal) que Flash, c'est une ambition assez limitée, avouons-le.

Silverlight 2.0 c'est toute la couche graphique (avec des améliorations) de la V 1 plus la programmation en langage .NET (C# ou VB généralement) pour le "code behind" selon un principe identique à celui des pages ASP.NET par exemple.

Dit comme ça, cela ne semble pas si révolutionnaire... Et pourtant  !

Silverlight est un plugin pour Web browser, pas un ActiveX dédiée à IE. Le plugin existe pour Windows et IE mais aussi pour Linux et Mac OSX ! Du coup Silverlight 2.0 c'est "la" réponse au développement multi plateforme : la possibilité de développer des applications riches qui font tourner du .NET et qui fonctionnent sur un PC qu'il soit équipé de OSX (un Mac donc), de Windows XP/Vista ou de Linux !

Imaginez ce que vous pouvez faire avec un tel outil ? Tout en conservant votre savoir-faire .NET et C# ou VB.Net vous pouvez concevoir des applis Internet ou Intraweb pour des environnements hétérogènes. La révolution est bien là. Silverlight est la première technologie de ce type.

Certains dirons, un peu chagrins, que Adobe et son Flash ça existe depuis des lustres, pas de quoi en faire une tartine donc.

Si je comprends cette première impression, je leur demanderais de bien vouloir examiner la différence qui peut exister entre un Flash développé par une société spécialiste du graphisme mais sans aucun savoir-faire en terme d'environnement de développement ou d'OS, et un Silverlight qui lui repose sur le framework .NET avec des EDI comme VS 2008 ou Blend, sans aucun équivalent chez Adobe (ou autre d'ailleurs). La nuance est là et elle est de taille. Pour un développeur qui veut faire de vraies applications en tout cas, et pas seulement des bandeaux de pub...

Le mieux c'est de vous rendre compte par vous-mêmes hein...

La version 2.0 RC de Silverlight est désormais disponible, les téléchargements intègrent le plugin lui-même ainsi que le SDK et le Service Pack 1 de Blend 2 qui remplace Blend 2.5 .

Une petite chose qu'il faut savoir : la RC 2.0 est une preview mise à disposition en US comme toutes les previews ou presque. De fait le SP1 de Blend 2 ne fonctionne que sur la version US, pas sur la version française. Pour profiter d'un Blend 2 capable de travailler avec Silverlight 2 il est donc nécessaire de désinstaller Blend 2 français pour installer la version US. Il existe une version trial chez MS, si vous aviez un Blend 2 avec licence, normalement la version US trial se tansformera en version réelle. Dans tous les cas ce "montage" est temporaire : dans quelque temps nous passerons de la RC à la release finale et le SP1 de Blend 2 sera fourni en FR aussi, bien entendu (il sera alors possible de réinstaller votre Blend 2 FR pour lui appliquer la SP1 FR définitive).

Silverlight c'est beau, c'est cool, et c'est du .NET multi plateforme. N'hésitez pas plus longtemps !

Open XML SDK et PowerTools pour OOXML - Manipuler les documents OOXML

Le format Open XML utilisé par Microsoft pour sa suite Office est une grande avancée en ce sens que le stockage n'est plus propriétaire et que cela permet d'envisager de nombreux traitements automatiques des documents qu'il s'agisse de recherche documentaire, de modification ou même de création de nouveaux documents.

Le format en lui même est aujourd'hui normalisé ISO/IEC et même si on peut regretter la guerre des formats entre Open XML de Microsoft et OpenDocument, il va falloir apprendre à jongler avec ces deux normes ouvertes, aucune n'étant plus "sympathique" ou "meilleure", et chacune affichant ses petites différences. Quoi qu'il en soit, dans les faits la masse de documents produite par la suite Office dans le monde est tellement gigantesque qu'au final le développpeur aura forcément plus souvent affaire à de l'Open XML qu'à du OpenDocument.

Il faut donc saluer Microsoft d'avoir opté pour un nouveau format lisible, public et normalisé. Pour nous, informaticiens, c'est la seule chose qui compte. Laissons les querelles de format à ceux qui ont du temps à perdre...

Open XML SDK 

Même si OOXML est une norme claire, les documents la décrivant sont, comme toute norme, un peu imbuvables, avouons-le... Mais bien heureusement il existe un SDK gratuit "Open XML SDK" qui se compose d'aide et d'une librairie de classes .NET facilitant la manipulation des fichiers de type Word, Excel, PowerPoint...

Vous pouvez télécharger le OOXML SDK en cliquant sur le lien.

Une fois cet assemblage référencé, vos applications peuvent facilement traiter tout document Office.

A titre d'exemple vous trouverez un petit projet console qui permet de lister le contenu d'un fichier OOXML (Word, Excel, PowerPoint). J'ai supprimé du projet les fichiers de test, il vous suffira de modifier le code de la méthode Main pour y placer le chemin d'un fichier OOXML se trouvant sur votre machine (ou une copie si vous être méfiant ! ).

Voici un exemple de sortie sur un fichier Excel très simple :

URI                        Content Type
===                        ============
/xl/workbook.xml           application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml
/xl/worksheets/sheet.xml   application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml
 

Ce n'est qu'une sorte de "hello world", le SDK permettant de vraiment traiter les documents et de faire des choses bien plus sophistiquées, bien entendu !

PowerTools pour OOXML

Les PowerTools pour OOXML sont eux une autre couche s'utilisant d'ailleurs avec PowerShell dont je vous parlais dernièrement. C'est un projet open source sur CodePlex dont le but est d'autoriser le traitement de fichiers OOXML côté serveur. Par exemple produire directement un document Word ou Excel sur un serveur Web pour l'envoyer à l'utilisateur, faire des recherches dans une base documentaire, etc...

Lien : PowerTools OOXML sur CodePlex

On notera ce billet Julien Chable sur l'installation des PowerTools (en français, pour une fois...), ainsi qu'une vidéo de présentation (US) sur MSN Video

L'avantage de la combinaison des PowerTools OOXML au PowerShell et ses scripts est de former un ensemble permettant de traiter automatiquement des documents Offices en dehors de toute application. C'est donc une autre façon de traiter les documents Office que celle proposée par le SDK présenté plus haut.

Un exemple concret d'utilisation parmi des milliers d'autres : ajouter un filigrane avec le nom de la société ou une mention "ne pas diffuser" (ou autre) automatiquement à tous les documents d'un répertoire.  On peut même concevoir un service Windows surveillant un ou plusieurs répertoires et ajoutant systématiquement une mention, un logo, un filigrane, etc, à tout nouveau document qui est déposé. On peut envisager par le même biais d'extraire les propriétés de tout document déposé dans un répertoire et de l'envoyer par mail à un administrateur. Les idées ne manquent pas, à vous de jouer !

Conclusion

Disposer de tels outils ouvre la voie à l'intégration de fonction de GED de haut niveau dans toutes les applications, Web ou Desktop. C'est aussi un moyen simple de produire des documents OOXML loin des bricolages DDE, COM/DCOM, ActiveX ou autres pour faire de l'Automation. De plus les fichiers peuvent être manipulés sans que les applications équivalentes du pak Office ne soient installées, c'est un énorme avantage.

le projet (il faut installer le SDK OOXML of course) : PowerOPX.zip (2,91 kb) [Downloads: 65]