3.1 Introduction
The Report tool aims to make generation of XML documents from OCaml applications easier.
The main idea is to separate the structure of the document from the information
computed by the application and placed in the document.
For example, in the following document :
<h1>Crepes</h1>
<list>
<item>flour</item>
<item>eggs</item>
<item>sugar</item>
</list>
the document structure is
<h1> </h1>
<list>
<item> </item>
<item> </item>
<item> </item>
</list>
while computed information is ``Crepes'', ``flour'', ``eggs'' and ``sugar''.
To build our XML document, we must therefore describe its structure as well as
the way to fill it with information. Then, at runtime, the application will use
this description to generate the final XML document.
In practice, Report allows to graphically describe the document
(structure + information), and then to generate OCaml code which uses the Report
library. In particular, this library contains a function which computes a document
description to produce the final XML document.
An important point is that the way to compute the information needed in the document
is given in the form of OCaml code.
The Report library as well as the generated code are detailed in 3.2.
The description of document and the use of the report tool are explained in
3.3.
3.2 The Report library
The Report module defines the types used to describe a 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 (unit -> 'a report)
(** A tag. *)
and 'a tag = {
tag : string ;
atts : (string * (unit -> string)) list ;
tag_subs : 'a report_ele list
}
(** A list of substructures. *)
and 'a liste =
{ list_subs : 'a report_ele list ;
f : (unit -> 'a list) ;
mutable current : 'a ;
}
(** Conditional *)
and 'a cond =
{ cond : unit -> bool ;
subs_then : 'a report_ele list ;
subs_else : 'a report_ele list ;
}
(** A report description is a list of report elements. *)
and 'a report = {
rep_eles : 'a report_ele list ;
}
The type report_ele is the type of the tree nodes :
-
Leaf is used for the leafs of the tree and needs the code of a function
taking () and returning a string. While computing the document description,
() will be applied to this function in order to obtain the string to insert
into the final XML document.
This way of hiding OCaml code ``under'' fun () -> allows not to
compute the result until the computation of the leaf, thus allowing to use
values computed ``above'' the leaf.
- Tag is used to define an XML node of the final XML document. It needs the
tag name, eventually a list of attributes and values, as well as subtrees of the
node. Values of attributes are OCaml code too, as for the leafs.
- List allows to insert, while computing the final document, a list of subtrees
for each element in a list. The field f contains the OCaml function returning
the list of elements to iterate on.
The field current is used to store the current element while walking through
the subtrees. This way, the value of the field current can be used in the
functions which appear in the subtrees of the node.
- Cond allows to use one list of subtrees or another, depending on the boolean
value returned by the given function.
- Sub is used to insert another document in the current one.
The given function returns a document description which is computed too.
The document example of the introduction could be described by :
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 "Crepes") ;
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)
The call to Report.coerce is necessary to force the type but type constraints
are already satisfied (notably the use of the current field) at this point.
As we can see, this structure can quickly become a pain to define and read.
To solve this problem, the report tool allows to graphically define
the document description and generate the OCaml code of this structure.
Moreover, the report value could have had parameters ; this is a
way to parametrize the final XML document.
The Report module contains the following functions :
(** 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
The coerce function is used to insert a 'a report_ele into
the node of a 'b report_ele, when 'a cannot be used as 'b.
This function must only be used for this purpose, as in the example above.
The compute function takes a formatter and a document
description and computes this description to generate the final XML document in
the given formatter. The html optional parameter allows not to
close some tags (like br) to generate HTML compliant documents.
The compute_file function acts like compute but writes in
the given file instead of a formatter.
3.3 The report and report.gui tools
The report.gui tool allows to describe a document (structure + code to fill it
with information), as well as the parameters of this document (these parameters
become parameters of the report value in the code example in 3.2).
The document is describe through a graphical user interface with a tree oriented view.
The report tool generates the OCaml code of the document description,
using the types defined in the Report library (cf. 3.2).
Here is the report command usage :
report [options] description_file
The following options are supported :
-
-gen
-
Generate the OCaml code for the document described in the given file.
The code is generated in a file whose name is function of description_file.
If description_file has an extension, the generated file as the same
name with the extension replaced by .ml. If description_file
has no extension, then the generated file has the same name, with the
.ml extension appended.
- -o file
-
Generate the OCaml code in file file, instead of the default file,
when the -gen option is given.