Chapitre 4 : MathML
4.1 Présentation
La mise à disposition sur le web de documents scientifiques contenant des formules et des équations mathématiques représente une gageure en utilisant le langage HTML 3.2. Dernièrement, la création d'un nouveau langage de représentation mathématique allant avec XML et HTML 4.0 permettra de définir et d'afficher de manière beaucoup plus intuitive les formules.
Ce langage permettra de fournir un support aux communications scientifiques. Il a été concu par le groupe W3C [5] pour pouvoir inclure des données mathématiques de manière simple, mais il doit pouvoir aussi rendre toutes les complexités des notations et structures mathématiques, dans un style compréhensible et facilitant le travail des navigateurs internet. Il devrait être alors possible de travailler directement sur la formule, en la sélectionnant, la copiant. Ceci est impossible avec les représentations actuelles en images, ou en tableau comme le fait HEVEA en HTML 3.2.
Ainsi, MathML est plutôt orienté vers un langage mathématique de bas niveau. C'est pour cette raison qu'il est recommandé dans [5] de ne pas l'utiliser directement, mais plutôt à l'aide de traducteurs ou de générateurs automatiques.
Malheureusement, pour l'instant, les navigateurs internet courants (Netscape, Internet Explorer,...) ne reconnaissent pas le langage MathML, ni le langage HTML 4.0. Seul le visualiseur fourni par le groupe W3C, Amaya, permet de tester le langage MathML (ainsi que HTML 4.0 et XML). Pourtant, même Amaya ne reconnait pas le standard MathML en entier. En effet, ce dernier définit un grand nombre de symboles et de commandes qui permettent de couvrir l'ensemble des domaines scientifiques. Seuls sont reconnus pour l'instant les symboles courants. De plus, seule la moitié des commandes MathML a étée implémentée dans Amaya, ce qui limite un peu la production.
MathML utilise deux types de description des formules. On peut utiliser des éléments de présentation ou de contenu. On trouve de plus un élément d'interface.
-
Les éléments de présentation
- donnent la structure de la notation mathé-matique. Chaque élément correspond à une notation, comme une ligne (
mrow
), un indice (msub
) ou une fraction (mfrac
)
- Les éléments de contenu
- décrivent les objets mathématiques et leur concept. On a par exemple les élément
plus
ou vector
.
- L'interface
- est réalisée par un seul élément à part, ``
math
'', qui permet de rentrer en mode MathML ou d'en sortir.
Ensuite, chaque morceau de la formule doit être dans un des éléments de présentation suivant : mi
pour les identificateurs, mo
pour les opérateurs et mn
pour les nombres.
Pour décrire une formule, on a alors le choix entre les deux types de repré-sentation qui seront équivalentes du point de vue visuel. Comme Amaya ne reconnait que les éléments de présentation, j'ai traité uniquement ces éléments là. Il se trouve que ce sont ces éléments qui correspondent le mieux aux concepts LATEX de notation mathématique.
4.2 Implémentation
Pour permettre à HEVEA de produire une sortie MathML au milieu d'un document HTML, il a fallu séparer le module existant en quatre modules distincts :
-
HtmlCommon
- contient le fonctionnement de base du module HTML, avec la gestion des blocs et des modes, ainsi que les fonctions de sortie.
- Html
- est le module appelé par
Scan
. Il contient donc les appels vers les autres modules en redirigeant les fonctions mathématiques vers le module MathML ou celui produisant des tableaux. On y trouve aussi la gestion des tableaux et des listes.
- HtmlMath
- produit des formules, en mode display, par la création de tableaux.
- MathML
- a la même interface que
HtmlMath
, mais donne un code MathML.
L'interface commune aux deux modules de mathématiques et au module de texte a été décrite en section 1.3.3.
4.2.1 Displays
Le modèle crée pour le mode HTML a été adapté. On retrouve les displays. Ceux-ci correspondent en fait à un groupement horizontal d'éléments et ont été naturellement traduits par des éléments ``mrow
''.
Il faut cependant faire attention au groupement logique des expressions. En fait, pour une formule comme x2+4x+4=0, il est préconisé par [5] de rassembler l'opérant de gauche à l'intérieur d'un élément mrow
, ce qui donnerait la traduction suivante en MathML:
<mrow>
<mrow>
<msup>
<mi>x</mi>
<mn>2</mn>
</msup>
<mo>+</mo>
<mrow>
<mn>4</mn>
<mo>⁢</mo>
<mi>x</mi>
</mrow>
<mo>+</mo>
<mn>4</mn>
</mrow>
<mo>=</mo>
<mn>0</mn>
</mrow>
A l'inverse, il ne faut pas entourer un seul élément de marqueurs mrow
.
Donc, lorsqu'on ouvre un display, on ouvre, dans le module HtmlCommon
, un bloc mrow
, puis un autre bloc. On va remplir ce dernier bloc et le vider dans le bloc mrow
dès que l'on a mis un élément dedans. Ceci est determiné lors de la fonction put
et de diverses fonctions plus spécialisée par un appel à la fonction item_display
qui vérifie si le bloc est vide et le transfère dans le bloc mrow
si besoin, en incrémentant le compteur ncols
qui va donner l'indication de remplissage du bloc.
A la fermeture du display, on va agir différemment selon la valeur de ncols
:
-
Si
ncols
=0 et que le bloc n'est pas vide, c'est que l'on a deux displays imbriqués. Il n'est pas nécessaire de faire figurer les deux et alors on recopie directement le contenu du bloc mrow
dans le bloc père, sans mettre les tags <mrow>
et </mrow>
.
if (n = 0 && not flags.blank) then begin
let ps,_,ppout = pop_out out_stack in
try_close_block "mrow";
let old_out = !cur_out in
cur_out := ppout ;
Out.copy old_out.out !cur_out.out ;
flags.empty <- false ; flags.blank <- false ;
free old_out ;
- Si
ncols
=1, le bloc ne contient qu'un seul élément et, de même que dans le cas précédent, on ne va pas faire figurer les indications du bloc mrow
. On a le même code qu'au dessus.
- Si
ncols
³2, alors on a plus d'un élément dans cette ligne, et il faut les inclure dans un élément mrow
. On ferme donc le bloc mrow
normalement, pour qu'il rajoute les tags correspondants.
end else begin
flags.empty <- flags.blank ;
close_flow "mrow";
end ;
Ceci permet de ne pas créer trop d'éléments mrow
. Pour grouper les éléments de manière logique, un système empirique a été retenu. en effet, pour parenthéser correctement une expression mathématique entière, il faut souvent la connaitre entièrement dès le début, ce qui n'est pas compatible avec le fonctionnement d'HEVEA, en une seule passe.
On reconnait alors certains caractères qui imposent des ouvertures ou fermetures de display. Ce sont les parenthésages : `([{<' et `>}])'. On ouvre un display avant la parenthèse ouvrante, et on le ferme après la parenthèse fermante. En cas de mauvais parenthésage, le display est refermé au bon endroit.
On peut donc avoir une certaine organisation des formules mathématiques, même si celle-ci n'est pas interprétée par HEVEA, mais simplement traduite, au `mot par mot'.
4.2.2 Positionnements
Il faut ensuite reconnaitre les changements de présentation, comme le passage d'une partie de la formule en indice ou en exposant, les fractions, et la création de délimiteurs.
Indices et exposants
On trouve essentiellement deux types d'indices et d'exposants. Certains se placent à côté de l'expression : xi2, alors que d'autres sont au même niveau : ba.
Ces deux types se retrouvent en MathML avec six éléments de présentation : msub
, msup
, msubsup
, munder
, mover
, munderover
. Ces éléments prennent deux ou trois arguments selon les cas. Par exemple, pour xi2, il faut écrire :
<msubsup>
<mi> x </mi>
<mi> i </mi>
<mn> 2 </mn>
</msubsup>
On place d'abord la base, puis l'indice et/ou l'exposant. Ceci pose un problème car HEVEA a déjà traité le x
lorsqu'on voit qu'il faut rajouter un indice. Il faut donc insérer le tag <msubsup>
à la bonne position avant le x
. Cela est réalisé en utilisant le bloc anonyme qui est toujours ouvert après le bloc mrow
. En effet, celui-ci est vidé dans l'autre à chaque élément, et il contient donc le dernier élément traduit, qu'il suffit d'intercepter avant qu'il ne soit recopié.
Cela est possible grâce à la fonction Out.to_string
qui rend une chaîne avec le contenu du tampon. En effet, on remplace sur la pile des blocs de sorties le dernier tampon par un nouveau, puis on ferme le bloc anonyme, ce qui a pour effet de recopier celui-ci dans le tampon qui est sur le haut de la pile.
On peut ensuite ouvrir le bloc correspondant à l'indice ou l'exposant voulu, puis un display, et on recopie le contenu du tampon précédent dans ce display.
let insert_sub_sup tag scanner s t =
let ps,pargs,pout = pop_out out_stack in
let new_out = new_status false [] [] in
push_out out_stack (ps,pargs,new_out);
close_block "";
cur_out := pout;
open_block tag ""; open_display "";
do_put ( Out.to_string new_out.out );
free new_out;
close_display ();
put_sub_sup scanner s;
if t<>"" then put_sub_sup scanner t;
close_block tag;
open_block "" "";
;;
Il ne faut pas oublier d'ouvrir des displays autour de chaque élément argument de ces fonctions. Par exemple, pour xi+1, on ne doit pas traduire par xi+1, mais plutôt par :
<msup>
<mi> x </mi>
<mrow>
<mi> i </mi>
<mo> + </mo>
<mn> 1 </mn>
</mrow>
</msup>
Il faut englober x et i+1 dans un bloc mrow
. Ces blocs ne seront pas mis éventuellement, comme on l'a vu à la section 4.2.1.
Il existe un cas plus simple pour les intégrales, les limites, et tous les opérateurs dont on sait qu'ils sont en général suivis d'indices et d'exposants. On commence alors par regarder s'il y a éventuellement de tels indices, puis on passe au module MathML tous les arguments nécessaires. Cette fois la base est connue et il n'y a pas besoin d'insérer de bloc.
Fractions
Les fractions LATEX définies par \over
, comme dans 5/2, sont traduites par l'élément mfrac
qui prend deux arguments : le numérateur et le dénominateur :
<mfrac>
<mn> 5 </mn>
<mn> 2 </mn>
</mfrac>
De même que pour les exposants, la commande \over
n'est interprétée qu'après le numérateur. Il faut donc insérer le bloc mfrac
avant le numérateur, que l'on détermine comme étant le dernier groupement horizontal, qui se trouve donc dans le bloc mrow
ouvert. il faut alors remonter avant le dernier display ouvert.
Par contre, cette fois, on ne connait pas le deuxième argument lorsqu'on insère le bloc mfrac
. Il faut donc continuer à analyser le fichier après avoir inséré ce bloc, ce qui pose un problème car il faut pouvoir le refermer, et cette information n'est pas contenue dans le fichier LATEX. Il faut donc garder en mémoire que l'on a ouvert un bloc, puis un display pour pouvoir refermer ces deux environnements lorsque le display sera refermé.
On peut faire cela en rajoutant sur la pile des blocs ouverts, `out_stack
', un élément de type Freeze
, contenant la fonction à éxécuter pour fermer le bloc. En fait, à chaque fermeture de display, on va commencer par vérifier s'il n'y a pas un élément de type Freeze
en haut de cette pile. Si on en trouve un, on interrompt la fermeture du display pour éxecuter à la place la fonction sauvegardée dans cette pile. On peut ainsi retarder la fermeture d'un bloc pour englober le display suivant dans ce bloc.
let over display lexbuf =
let mods = insert_vdisplay (...) in
close_display () ;
open_display "" ;
freeze (fun () -> close_display () ; close_block "mfrac")
;;
Délimiteurs
Les délimiteurs LATEX, définis dans [6], permettent de parenthéser les expressions mathématiques avec des symboles qui peuvent s'étendre verticalement.
En MathML, si un opérateur considéré comme délimiteur commence ou termine un bloc mrow
, il semble être automatiquement ajusté à la taille du bloc qu'il entoure. Cela fonctionne même si les délimiteurs ne sont pas bien parenthésés.
Il faut donc ouvrir un display juste avant le délimiteur gauche et le fermer juste après le délimiteur droit.
Si les délimiteurs sont bien parenthésés dans le fichier LATEX, il ne devrait y avoir aucun problème : on ouvre un display que l'on ferme un peu plus loin. Pourtant, il arrive de trouver des expressions mal parenthésées. Dans ce cas, il est prévu de donner un avertissement à l'utilisateur et de continuer quand même. Il faut pourtant fermer le bloc qui a été ouvert avec le délimiteur gauche.
On utilise donc la technique du bloc Freeze
qui permet de fermer le display ouvert lorsqu'on ferme le display suivant. De même. si on pose un délimiteur droit sans avoir mis celui de gauche, on ne trouve pas de bloc Freeze
sur la pile out_stack
, et on peut envoyer un avertissement.
4.2.3 Styles
Les styles sont supportés en MathML, mais il faut utiliser une syntaxe XML différente de l'HTML 3.2. On ne peut donc pas réutiliser directement les fonctions de gestion des styles du module HtmlCommon
.
En MathML, on peut définir un style en le passant en argument de l'élément considéré, ou en englobant lélément dans un élément mstyle
qui a pour argument le style voulu. On peut ainsi mettre un x en gras par
<mo font-weight: bold> x </mo>
ou bien par
<mstyle "font-weight: bold">
<mo> x </mo>
</mstyle>
Ces deux formulations sont équivalentes. Dans HEVEA, j'ai utilisé la seconde car elle se rapproche plus du modèle HTML utilisé précedement, et a demandé un minimum de changement du module HtmlCommon, ce qui est toujours à préférer pour limiter les risques d'introduction d'erreur.
Un changement de style, de fonte ou de couleur, défini dans le source LATEX par des commandes du type \mathtt
, va être traduit par HEVEA en une commande interne \@style{TT}
, qui sera reconnue ensuite dans le module HtmlCommon
, qui gère les modes, les ouvertures et fermetures de styles.
Il faut ensuite reconnaitre au niveau de la fonction do_open_mod
, qui affiche les tags de style, si l'on est en mode mathématique et en mode MathML. Une traduction est alors effectuée des styles HTML 3.2 vers les éléments mstyle
de MathML.
4.2.4 Symboles
Le standard MathML [5] procure un très grand nombre de symboles et d'objets mathématiques. On retrouve les symboles utilisés par LATEX parmi ceux-ci. Même si un grand nombre de ces symboles n'est pas encore reconnu par Amaya, j'ai traduit ces symboles par l'entité recomandée.
On trouve la définition de ces symboles dans un des fichiers de définitions des macros d'HEVEA, symb_mathml.hva
, qui fait partie de l'ensemble des fichiers de gestion des symboles. On y redéfinit les symboles utilisés précedemment en mode HTML 3.2, en y ajoutant tous ceux reconnus par LATEX et décrits dans [6].