Report
Qu'est-ce que Report ?

L'outil Report vise à rendre la génération de documents XML en OCaml plus facile. L'idée principale est de séparer la structure du document des informations calculées par l'application et placées dans le document. Par exemple, dans le document suivant:

<h1>Crêpes</h1>
<list>
  <item>flour</item>
  <item>eggs</item>
  <item>sugar</item>
</list>
la structure du document est
<h1> </h1>
<list>
  <item> </item>
  <item> </item>
  <item> </item>
</list>
tandis que l'information calculées est "Crêpes", "flour", "eggs" and "sugar".

Pour construire notre document XML, nous devons donc décrire sa structure et la façon de la remplir d'information. Ensuite, à l'exécution, l'application utilisera la description pour générer le document XML final.

En pratique, Report permet d'diter graphiquement le document (structure + information), et de générer le code OCaml qui utilise la bibliothèque Report. En particulier, cette bibliothèque contient une fonction qui "évalue" une description de document pour produire le document XML final. Un point important est que l'information à mettre dans le document est donnée sous forme de code OCaml.

La bibliothèque
Types

Le module Report définit les types utilisés pour décrire un document :

(** A report element. *)
type 'a report_ele =
  | Leaf of (unit -> string)
  | Tag of 'a tag
  | List of 'a liste
  | Cond of 'a cond
  | Sub of 'a sub

(** A tag. *)
and 'a tag = {
    mutable tag : string ;
    mutable atts : (string * (unit -> string)) list ;
    mutable tag_subs : 'a report_ele list
  }

(** A list of substructures. *)
and 'a liste =
    { mutable list_subs : ('a -> 'a report_ele list) ;
      mutable f : (unit -> 'a list) ;
    }

(** Conditional *)
and 'a cond =
    { mutable cond : unit -> bool ;
      mutable subs_then : 'a report_ele list ;
      mutable subs_else : 'a report_ele list ;
    }

(** Subreport *)
and 'a sub =
    {
      mutable sub_rep : unit -> 'a report ;
    }

(** A report description is a list of report elements. *)
and 'a report = {
    mutable rep_eles : 'a report_ele list ;
  } 
Le type report_ele représente les noeud de l'arbre:
  • Leaf est utilisé pour les feuilles de l'arbre et requiert le code OCaml d'un fonction prenant en paramètre () et retournant une chaine. Pendant l'évaluation de la description du document, () sera passé à cette fonction afin d'obtenir la chaine à insérer dans le document XML final. Cette façon de cacher le code OCaml "sous" fun () -> permet de ne pas calculer le résultat avant l'évaluation de la feuille, ce qui permet d'utiliser des valeurs calculées "au-dessus" de la feuille.
  • Tag est utilisé pour définir un noeud XML du document final. Il faut indiquer un nom de tag, éventuellement une list d'attributs et de valeurs, ainsi que les sous-arbres du noeud. Les valeurs des attributs sont, comme pour les feuilles, du code OCaml.
  • List permet d'insérer, pendant l'évaluation du document, une list de sous-arbres pour chaque élément de la liste. Le champs f contient la fonction OCaml retournant la liste des éléments sur lesquels itérer. Le champs current est utilisé pour stocker l'élément courant pendant la descente dans les sous-arbres. De cette façon, la valeur du champs current peut-être utilisée dans les fonctions qui apparaissent dans les sous-arbres du noeud.
  • Cond permet d'utiliser une liste de sous-arbres ou une autre, selon la valeur booléenne renvoyée par la fonction donnée.
  • Sub est utilisé pour insérer un autre document dans le document courant. La fonction donnée retourne une description de document qui est évaluée à son tour.

Le document en exemple de l'introduction peut-être décrit comme dans la section suivante.

Exemple de code
let rec report  =
  ({
   rep_eles = [
     Tag { tag = "h1" ; atts = [] ;
           tag_subs = [
             Leaf (fun () -> "Crepes");
           ] } ;
     Tag { tag = "list" ; atts = [] ;
           tag_subs = [
             ( let rec ing =
                 { f = (fun () -> ing_of_recipe "Crêpes") ;
                   current = (Report.coerce 0) ;
                   list_subs = [
                     Tag { tag = "item" ; atts = [] ;
                           tag_subs = [ Leaf (fun () -> ing.current.ing_name) ] }
                   ] }
                in
                List (Report.coerce ing))
           ]
          }
    ]
   } : int report)

L'appel à Report.coerce est nécessaire pour forcer le type mais les contraintes de type sont déjà satisfaites (notamment l'utilisation du champs current ) à cet endroit.

Comme on peut le voir, cette structure peut rapidement devenir difficile à écrire et à lire. Pour pallier à ce problème, l'outil report_gui permet de définir graphiquement la description de document tandis que l'outil report permet de générer le code OCaml correspondant au document décrit.

De plus, la valeur report pourrait avoir des paramètres; c'est une façon de paramétrer le document XML final.

Fonctions

Le module Report contient les fonctions suivantes :

(** Coerce report elements. *)
val coerce : 'a -> 'b

(** Compute a report and print it to the given formatter. *)
val compute : ?html: bool -> Format.formatter -> 'a report -> unit

(** Compute a report and print it in a file. *)
val compute_file : ?html: bool -> string -> 'a report -> unit

La fonction coerce est utilisée pour insérer un 'a report_ele dans le noeud d'un 'b report_ele, quand 'a ne peut être utilisé comme 'b. Cette fonction ne doit être utilisée qu'à cet effet, comme dans l'exemple ci-dessus.

La fonction compute prend en paramètre un formatter et une description de document et évalue cette description pour générer le document XML final dans le formatter donné. Le paramètre optionnel html permet de ne pas fermer certains tags (comme br ) pour générer du HTML correct.

La fonction compute_file agit comme la fonction compute mais écrit dans le fichier donné au lieu d'un formatter.

Lancement de l'éditeur graphique

L'outil report.gui permet de décrire un document (structure + code pour le remplir d'information), ainsi que les paramètres de ce document (ces paramètres deviennent des paramètres de la valeur report dans l'exemple de code). Le contenu du document est édité dans une vue arborescente représentant la structure du document.

L'éditeur graphique est lancé par la commande suivante:

report.gui.{x,byte} [options] file [file2 [file3 ...]]
Lancement du générateur de code

L'outil report génère le code OCaml de la description de document, en utilisant les types définis dans la bibliothèque Report.

Le générateur de code est lancé par la commande suivante:

report.{x,byte} [options] file

Les options suivantes sont supportées:

  • -gen génère le code dans le fichier (chop_extension <file>).ml
  • -o file génère le code OCaml dans le fichier file, au lieu du fichier par défaut, quand l'option -gen est donnée.