Ce moteur Java de templates est basé sur les notions suivantes :
Data model
Le modèle de données FreeMarker est arborescent et construit à partir d'une racine prédéfinie nommée ".data_model" (cliquez ici pour plus de détails).
Chaque donnée est un nœud de l'arborescence et est identifiée par un nom (il y a unicité du nom pour les nœuds fils d'un nœud donné, et un nom est sensible à la casse).
Si un nœud est terminal (nœud feuille), la donnée qu'il contient est de type simple.
Les types simples (ou scalaires) dans FreeMarker sont les suivants :
- String (chaîne alphanumérique),
- Number (nombre entier ou décimal),
- Boolean (valeur booléenne vrai ou fausse),
- Date (une date : année, mois, jour),
- Time (une heure : heure, minute, seconde),
- Date-time (un horodatage : date + heure + milliseconde).
Si un nœud est non-terminal, la donnée qu'il contient est de type complexe.
Les types complexes (ou composites) dans FreeMarker sont les suivants :
- Hash (table de hachage ou tableau associatif),
- Sequence (liste ou tableau de valeurs).
La racine ".data_model" est de type Hash. Elle permet d'accéder aux valeurs des nœuds de premier niveau par leur nom.
Chaque nœud est défini par un chemin, et ce chemin permet d'accéder à la valeur du nœud : on part du nœud racine et on sépare chaque nom de nœud par un point ".".
Dans un template FreeMarker, l'accès au modèle de données se fait par la variable ".data_model" et l'accès aux données se fait via les "sous-variables" définies par leur notation "point" dans le modèle de données :
Data model : |
|
Accès aux "sous-variables" : |
|
Expression |
Valeur |
.data_model.person .data_model.person.name .data_model.person.age .data_model.person.isMarried .data_model.birthdate .data_model.hobbies .data_model.hobbies[0] .data_model.hobbies[1] .data_model.hobbies[2] |
Objet Hash |
Comme FreeMarker est implémenté en langage Java, voici un tableau de correspondance entre les types Java et les types FreeMarker associés :
Types simples | |
Type Java |
Type FreeMarker |
String |
String |
Short, Integer, Long, Double, BigDecimal |
Number |
Boolean |
Boolean |
java.sql.Date |
Date |
java.sql.Time |
Time |
java.sql.Timestamp |
Date-time |
Types complexes | |
Map<String, Object> |
Hash |
List< >, <>[] |
Sequence |
Cliquez ici pour plus de détails.
|
↑ Haut de page
Template
Un template est un document textuel qui peut être vu comme un programme écrit en FTL (FreeMarker Template Language).
Celui-ci est un mélange des sections suivantes :
- Texte : ces sections constituées de texte seront affichées dans le document résultat tel que (y compris les caractères de mise en forme tels que les retours chariot, les tabulations, etc.),
- Interpolation : ces sections seront remplacées par une valeur calculée dans la sortie. Les interpolations sont délimitées par "${" et "}",
- Balises FTL : les balises FTL sont un peu similaires aux balises XML, mais elles sont considérées comme des instructions pour FreeMarker et ne seront pas affichées dans la sortie,
- Commentaires : les commentaires sont similaires aux commentaires XML, mais ils sont délimités par <#-- et -->. Les commentaires seront ignorés par FreeMarker et ne seront pas écrits dans la sortie.
Cliquez ici pour plus de détails.
Un template est identifié par son nom.
Lors de sa première exécution, FreeMarker transforme le programme FTL (document textuel) en code Java qui est gardé en mémoire (dans un cache), ce qui a pour conséquence d'améliorer les performances d'exécution (une seule lecture et "compilation" du programme pour toutes les exécutions).
Dans le cadre de l'APE, un template FreeMarker est un fichier texte ayant l'extension ".ftlx". Ce fichier doit être encodé en UTF-8. Il contiendra les sections suivantes :
- Texte : ces sections contiendront les parties statiques du document résultat et seront écrites en langage XSL-FO,
- Interpolation : ces sections serviront à afficher les données du modèle de données dans certaines balises XSL-FO (les parties dynamiques) du document résultat,
- Balises FTL : elles peuvent servir à conditionner l'affichage de données, à parcourir des séquences de données et gérer leur affichage, etc., dans le document résultat,
- Commentaires : comme un template est un programme, il est nécessaire d'insérer des commentaires pour faciliter sa maintenance.
Exemple de template :
Interpolation | Texte | Balises FTL | |||||
Commentaire | Saut de ligne |
↑ Haut de page
Template loader
La recherche d'un template est déléguée à un ou plusieurs chargeurs (ou template loader).
Une fois trouvé, le chargeur renvoie le contenu du template (le document textuel).
Un chargeur de template est défini par son type de stockage (recherche dans un système de fichiers, dans le "classpath" Java et les fichiers .JAR associés, dans la mémoire RAM, dans une base de données, etc.) et par une racine de recherche (un répertoire, un nom de package Java, etc.).
FreeMarker identifie un template en fonction :
- de son nom : dans le cadre de l'APE, il s'agit du nom du fichier du template (extension .ftlx comprise) ;
- d'un espace de noms : il s'agit d'un espace de regroupement de templates. On peut comparer les espaces de noms à des dossiers : le comportement est identique à un dossier, il est possible d'avoir des éléments de même nom dans différents espaces de noms. De plus, un espace de noms peur contenir d'autres espaces de noms ;
- d'une locale (au format IETF BCP 47) : par défaut, FreeMarker recherche s'il existe un template associé à une locale. S'il n'existe pas, le template sans locale est choisi. De plus, cette locale deviendra la locale par défaut utilisée lors du traitement du template : l'affichage de données (de type Number ou Date/Time/Date-time) ou la recherche de chaînes externalisées se fera en fonction de cette locale.
La signification de l'espace de noms dépend du chargeur de templates utilisé :
- Si le chargeur a un type de stockage "système de fichiers", l'espace de noms doit être vu comme un répertoire défini par un sous-chemin relatif à la racine de recherche,
- Si le chargeur a un type de stockage "classpath" Java, l'espace de noms doit être vu comme un package Java défini par un sous-package relatif à la racine de recherche.
Un espace de noms peut être vide.
FreeMarker prend comme emplacement de recherche d'un template la résolution de la racine de recherche et de l'espace de noms.
Exemple 1 : Chargeur de templates "système de fichiers"
Configuration du chargeur de template (freemarker.properties) |
template_loader=com.hardis.adelia.mergedocengine.freemarker.conf.FileTemplateLoader(java.io.File ("c:/myTemplates") |
Espace de noms |
"" |
Emplacement de recherche déduit |
"c:/myTemplates" |
Exemple 2 : Chargeur de templates "système de fichiers"
Configuration du chargeur de template (freemarker.properties) |
template_loader=com.hardis.adelia.mergedocengine.freemarker.conf.FileTemplateLoader(java.io.File ("c:/myTemplates") |
Espace de noms |
"bills" |
Emplacement de recherche déduit |
"c:/myTemplates/bills" |
Exemple 3 : Chargeur de templates "système de fichiers"
Configuration du chargeur de template (freemarker.properties) |
template_loader=com.hardis.adelia.mergedocengine.freemarker.conf.FileTemplateLoader(java.io.File ("c:/myTemplates") |
Espace de noms |
"bills/providers" |
Emplacement de recherche déduit |
"c:/myTemplates/bills/providers" |
Exemple 4 : Chargeur de templates "classpath"
Configuration du chargeur de template (freemarker.properties) |
template_loader=com.hardis.adelia.mergedocengine.freemarker.conf.ClassTemplateLoader(com.hardis.adelia.mergedocengine.ProcessTemplate.CLASS_FOR_OBJECT_BUILDER_EXPRESSION, "/myTemplates") |
Espace de noms |
"" |
Emplacement de recherche déduit |
Package "/myTemplates" à partir du contenu de la variable système Java CLASSPATH |
Exemple 5 : Chargeur de templates "classpath"
Configuration du chargeur de template (freemarker.properties) |
template_loader=com.hardis.adelia.mergedocengine.freemarker.conf.ClassTemplateLoader(com.hardis.adelia.mergedocengine.ProcessTemplate.CLASS_FOR_OBJECT_BUILDER_EXPRESSION, "/myTemplates") |
Espace de noms |
"bills" |
Emplacement de recherche déduit |
Package "/myTemplates/bills" à partir du contenu de la variable système Java CLASSPATH |
Exemple 6 : Chargeur de templates "classpath"
Configuration du chargeur de template (freemarker.properties) |
template_loader=com.hardis.adelia.mergedocengine.freemarker.conf.ClassTemplateLoader(com.hardis.adelia.mergedocengine.ProcessTemplate.CLASS_FOR_OBJECT_BUILDER_EXPRESSION, "/myTemplates") |
Espace de noms |
"bills/providers" |
Emplacement de recherche déduit |
Package "/myTemplates/bills/providers" à partir du contenu de la variable système Java CLASSPATH |
Dans le cadre de l'APE, un chargeur est préconfiguré, ce qui permet d'utiliser les templates du runtime (les bibliothèques FreeMarker Hardis) pour l'exécution des templates utilisateurs.
De plus, l'utilisateur devra configurer son ou ses propres chargeurs en fonction du type de stockage de ses templates (dans un répertoire, dans un fichier .JAR, etc.). ↑ Haut de page
Locale
Elle permet de particulariser le template à rechercher en fonction des informations constituant la locale (le code de la langue, le code du pays ou de la région et la variante). Pour associer un template "foo" à une locale donnée, il faut nommer le fichier de ce template "foo_code langue _code pays ou region _variante.ftlx".
La stratégie et l'ordre de recherche de fichier est la suivante (identique recherche Java de fichier .properties) :
- Recherche du fichier "foo_code langue _code pays ou region _variante.ftlx",
- Recherche du fichier "foo_code langue _code pays ou region.ftlx",
- Recherche du fichier "foo_code langue.ftlx",
- Recherche du fichier "foo.ftlx".
Exemple 1 :
Configuration du chargeur de template (freemarker.properties) |
template_loader=com.hardis.adelia.mergedocengine.freemarker.conf.FileTemplateLoader(java.io.File ("c:/myTemplates") |
Espace de noms |
"bills" |
Nom du template |
"foo_bar.ftlx" |
Locale |
"en-US" |
Fichiers templates recherchés |
"c:/myTemplates/bills/foo_bar_en_US.ftlx" |
Exemple 2 :
Configuration du chargeur de template (freemarker.properties) |
template_loader= com.hardis.adelia.mergedocengine.freemarker.conf.ClassTemplateLoader(com.hardis.adelia.mergedocengine.ProcessTemplate.CLASS_FOR_OBJECT_BUILDER_EXPRESSION, "/myTemplates") |
Espace de noms |
"bills" |
Nom du template |
"foo.ftlx" |
Locale |
"sr-Latn-RS" |
Fichiers templates recherchés |
"/myTemplates/bills/foo_sr_Latn_RS.ftlx" |
↑ Haut de page
De plus, comme décrit dans le paragraphe précédent, la locale fournie au chargeur pour la recherche d'un template deviendra la locale par défaut utilisée lors du traitement de celui-ci : l'affichage de données (de type Number ou Date/Time/Date-time) ou la recherche de chaînes externalisées se fera en fonction de cette locale.
Langage FTL
Le langage FTL permet de manipuler des interpolations et d'appeler des directives.
Interpolation
Le format d'une interpolation est ${expression} où expression peut être tout type d'expression (par exemple ${100 + x}).
Une interpolation est utilisée pour insérer la valeur de l'expression convertie en texte dans le document résultat. Les interpolations ne peuvent être utilisées qu'à deux endroits : dans les sections de texte (par exemple, <fo:block> Hello $ {name}! </fo:block>), et dans les expressions littérales de chaîne (par exemple, <#include "/sub_templates/${section3}.ftlx">).
Le résultat d'une expression doit être de type FreeMarker String, Number, Date, Time ou Date-time car.
Par défaut, seuls ces types sont convertis automatiquement en chaîne alphanumérique. Les valeurs d'autres types (Hash ou Sequence) doivent être converties "manuellement" (c'est-à-dire en parcourant les valeurs contenues dans ces types pour retourner un résultat textuel).
Cliquez ici pour plus d'informations.
Directive
Une directive est une instruction du langage FTL. Elle est décrite sous la forme d'un couple de balises :
- Une balise ouvrante <#directiveName parameters>,
- Une balise fermante </#directiveName>.
Tout ce qui se trouve entre la balise ouvrante et fermante d'une macro est le contenu imbriqué de cette macro.
Lorsqu'une directive n'a pas de balise fermante (donc pas de contenu imbriqué), elle s'écrit seulement avec sa balise ouvrante. Cliquez ici pour plus d'informations .
Il existe deux types distincts de directives :
- Les directives prédéfinies , c'est-à-dire celles du langage FTL,
- Les directives utilisateur , c'est-à-dire celles écrites par l'utilisateur.
Pour appeler les directives définies par l'utilisateur, utilisez "@" au lieu de "#".
Par exemple : <@mydirective parameters> ... </ @ mydirective>.
Une autre différence est que, si la directive n'a pas de contenu imbriqué, vous devez utiliser la notation <@mydirective parameters />.
Les directives utilisateur sont de deux natures différentes :
- Les macros : ces unités de traitement sont définies en utilisant la directive "macro",
- Les fonctions : ces unités de traitement sont définies en utilisant la directive "function".
Une macro est un fragment de template associé à une variable. Vous pouvez utiliser cette variable dans votre template en tant que directive définie par l'utilisateur, ce qui facilite les tâches répétitives.
Par exemple, voici un exemple de création d'une variable macro qui affiche "Hello Joe!":
<#macro greet>
<fo:inline font-size="x-large">Hello Joe!</fo:inline>
</#macro>
La directive "macro" elle-même n'affiche rien ; elle crée simplement la variable macro, donc il y aura une variable nommée "greet".
Les éléments entre <#macro greet> et </#macro> (appelé corps de définition de la macro) seront exécutés uniquement lorsque vous utilisez la variable en tant que directive.
Vous utilisez des directives définies par l'utilisateur en écrivant @ au lieu de # dans la syntaxe de balises FTL.
Utilisez le nom de la variable comme nom de la directive. De plus, la balise de fin pour les directives définies par l'utilisateur est obligatoire.
Voici un exemple d'appel de la macro "greet ":
<@greet></@greet>
Mais comme cette directive n'a pas de contenu imbriqué à l'appel de celle-ci, il est préférable d'écrire :
<@greet/>
Ce qui produira comme sortie dans le document résultat :
<fo:inline font-size="x-large">Hello Joe!</fo:inline>
De plus, il est possible de définir des paramètres à la macro. Ils doivent être définis après le nom de la macro dans la directive "macro" :
<#macro greet person>
<fo:inline font-size="x-large">Hello ${person}!</fo:inline>
</#macro>
Vous pouvez utiliser ensuite cette macro comme-ceci :
<@greet person="Fred"/> and <@greet person="Batman"/>
Ce qui produira comme sortie dans le document résultat :
<fo:inline font-size="x-large">Hello Fred!</fo:inline> and <fo:inline font-size="x-large">Hello Batman!</fo:inline>
Enfin, il est possible d'extraire et de traiter le contenu imbriqué d'une macro, c'est-à-dire le texte contenu entre les balises de début et de fin d'appel de macro :
<@greet>Ceci est le texte imbriqué de l'appel de macro</@greet>
La directive <#nested> exécute le fragment de template entre les balises de début et de fin d'une directive utilisateur (cliquez ici pour plus de détails).
Donc, si on définit une macro comme suit :
<#macro nestedContentInMacro>
This text '<#nested>' was into macro call!
</#macro>
Vous pouvez ensuite utiliser cette macro comme-ceci :
<@nestedContentInMacro>Help, I'm a nested text </@nestedContentInMacro>
Ce qui produira comme sortie dans le document résultat :
This text 'Help, I'm a nested text ' was into macro call!
Une fonction est un cas particulier d'une macro : cette unité de traitement n'a pas de contenu imbriqué et sert à retourner une valeur dans la sortie. Une fonction peut être appelée soit dans une interpolation, soit dans une autre directive.
Par exemple : création d'une fonction qui calcule la moyenne de deux nombres
<#function avg x y>
<#return (x + y) / 2>
</#function>
L'appel à cette fonction peut se faire comme suit :
${avg(10, 20)}
Ou comme ceci :
<#local avgNumber = avg(10, 2)>
Il est possible d'écrire un template .ftlx qui ne contient que des macros et fonctions définies par l'utilisateur.
Ce template peut être vu comme un module ou une bibliothèque : il sert de regroupement logique d'unités de traitement et ne produit aucun document résultat.
Un template module peut être inclus dans un template de document grâce à la directive <#import>.
Celle-ci prend en paramètre :
- le chemin d'accès au template module, relativement à l'emplacement du template document qui l'inclut,
- un espace de noms servant de préfixe lors de l'appel à une macro ou fonction du module dans le template document.
Cliquez ici pour plus d'informations.