Retrouvez cet article dans : Linux Magazine 104
Asseyez-vous et prenez vos cahiers. Aujourd’hui, nous ouvrons un nouveau chapitre : " le traitement de texte, je le fais avec mon éditeur favori parce que saymieux (tm) ". Silence au fond, les récriminations du genre " ouais, LaTeX, c’est trop dur " sont tout à fait hors sujet. Loutres nous sommes, loutres nous resterons. Le LaTeX (et aussi le XHTML, etc.), on va le faire écrire par un programme. Plus précisément, l’ambition de ces quelques pages est de vous montrer comment jouer avec la suite d’outils appelée " Docutils ", et la syntaxe qui lui est le plus souvent associée, reStructuredText. Et on écrira reST dans la suite, quand on vous dit que loutre un jour, loutre toujours.
Le verbe " jouer " est choisi à dessein : le but n’est pas de paraphraser un tutoriel quelconque sur la syntaxe de reST, mais plutôt de lever le capot pour avoir une vision d’ensemble du système, et pouvoir " faire des trucs " avec, et si possible des trucs amusants, utiles, poilus... bien sûr.
On supposera dans la suite que Docutils est installé. Chez moi, c’est un petit coup de :
# aptitude install python-docutils
Chez vous, je ne sais pas, mais, en tout état de cause, la procédure d’installation [1] est parfaitement documentée.
Plantons le décor
Un peu d’histoire
Le traditionnel fichier README.txt [2] de Docutils précise que " l’objet du projet Docutils est de créer un ensemble d’outils pour générer de la documentation dans des formats utiles tels que HTML, XML, LaTeX à partir de texte simple (plaintext) ".
Le projet Docutils est maintenu par David Goodger et la version 0.1 remonte à 2002. Toutefois, son origine est bien plus ancienne : la première version d’une syntaxe du type de celle utilisée dans reStructuredText est celle de Setext [3] (structure enhanced text), reprise et étendue dans le cadre du projet Zope en StructuredText [4], puis dans un premier projet reStructuredText [5].
Ce projet a ensuite intégré le projet Docutils, qui est plus ambitieux dans la mesure où il ne postule pas de syntaxe a priori : d’autres parsers sont possibles, et peuvent prendre place facilement dans l’architecture, comme on va le voir.
Comprendre la structure de Docutils
L’ensemble Docutils est donc une chaîne d’outils qui va prendre une source (le plus souvent un fichier texte) en amont et produire en aval une sortie formatée dans la forme choisie, LaTeX ou HTML par exemple.
Chaque type d’outil a sa responsabilité (on retrouve un peu la philosophie " faire une chose et la faire bien ") dans l’ensemble. On distingue :
reader
Il doit lire la source et la passer au parser. Deux readers sont distribués actuellement, le standalone qui lit un fichier texte standard – c’est celui qui est le plus utilisé – et le PEP reader, qui, comme son nom l’indique, lit les Python Enhancement Proposals.
parser
Responsable d’une première transformation : la syntaxe du document est analysée pour produire un arbre (node tree) le représentant. Cet arbre ressemble en gros à un document XML, mais sans balises de fermeture : c’est l’indentation qui joue ce rôle (vous avez dit " Python " ?). Évidemment, on peut concevoir des parsers différents en fonction de la syntaxe utilisée dans le document source. C’est donc ici que reST s’intègre à Docutils, puisque le seul parser distribué pour le moment lit cette syntaxe.
transform
C’est en quelque sorte une " deuxième passe ", qui prend un node tree en entrée et le restitue, mais enrichi de ce qui ne pouvait pas être calculé avant : références croisées, table des matières, etc.
writer
Enfin, le writer prend en entrée le node tree définitif et retourne la sortie formatée dans le " langage " choisi. La distribution standard propose des writers pour XML, HTML, LaTeX (deux versions en fait pour ce dernier), et S5 (le système de présentation proposé par Eric Meyer), mais tout est possible, et la sandbox du dépôt subversion contient différents essais plus ou moins finalisés.
Cette structure est parfaitement visible dans le répertoire d’installation de Docutils (le répertoire de l’installation est ici celui d’une Debian) :
$ cd /usr/share/pycentral/python-docutils/site-packages/docutils $ ls –l total 260 -rw-r--r-- 1 root root 26741 2007-11-18 11:59 core.py -rw-r--r-- 1 root root 3858 2005-06-27 13:19 examples.py -rw-r--r-- 1 root root 32086 2005-12-12 05:12 frontend.py -rw-r--r-- 1 root root 6711 2007-11-18 11:59 __init__.py -rw-r--r-- 1 root root 11570 2005-07-03 17:02 io.py drwxr-xr-x 2 root root 4096 2007-11-19 08:23 languages -rw-r--r-- 1 root root 58983 2007-11-18 11:59 nodes.py drwxr-xr-x 3 root root 4096 2007-11-19 08:23 parsers drwxr-xr-x 3 root root 4096 2007-11-19 08:23 readers -rw-r--r-- 1 root root 55377 2005-12-08 00:46 statemachine.py drwxr-xr-x 2 root root 4096 2007-11-19 08:23 transforms -rw-r--r-- 1 root root 6321 2005-12-30 04:00 urischemes.py -rw-r--r-- 1 root root 20952 2007-11-18 11:59 utils.py drwxr-xr-x 7 root root 4096 2007-11-19 08:23 writers
On note bien la présence des répertoires readers, parsers, transforms et writers.
Concrètement, ces outils se présentent sous la forme de classes Python, et l’écriture d’une nouvelle version de l’un d’entre eux se fait en dérivant la classe de base. Ainsi, une nouvelle classe de parser devra dériver de la classe docutils.parsers.Parser que l’on trouvera dans docutils/parsers/__init__.py.
On notera également l’existence d’une classe spéciale, le Publisher, qui permet de simplifier les opérations en prenant la source en entrée et en proposant directement la sortie. Plusieurs publishers existent, en fonction de la sortie désirée, mais aussi de la forme de celle-ci : chaîne de caractères, dictionnaire Python etc. Voir ci-dessous " Utiliser les publishers dans un shell ou un programme ".
Enfin, pour cacher toute cette mécanique à votre petite sœur, il existe des scripts Front-End dont les noms laissent deviner l’utilisation : rst2html.py par exemple... Tous ces scripts répondent fort poliment à l’option -- help. Ils sont très simples, puisque, dans la plupart des cas, il suffit d’appeler un publisher avec les bons paramètres. On les trouve en principe dans le répertoire docutils/tools, mais Debian les place dans /usr/bin en supprimant le suffixe .py. Les goûts et les couleurs...
Les notions importantes de reStructuredText
Encore une fois, on ne va pas expliquer toute la syntaxe de reST, mais plutôt en montrer le principe, et surtout voir les deux briques les plus importantes de l’édifice, le texte interprété, et les directives.
Un document reST est constitué de blocs (paragraphe, liste, table...). La syntaxe permet de définir le type de bloc, de titrer les sections, mais également de marquer certaines parties de texte à l’intérieur des blocs (emphase, hyperliens, appels de notes...). On parlera alors de marquage en ligne.
Le texte interprété fait partie du marquage en ligne, les directives sont (ou marquent) des blocs.
Le texte interprété
La documentation de référence [6] de reST précise qu’il s’agit d’une partie de texte qui est destinée à être indexée, liée, résumée ou tout autre type de traitement, mais en ne touchant généralement pas le texte lui-même. Ainsi, une entrée d’index simple ne sera pas " marquée " dans le corps du texte et apparaîtra telle quelle : elle fera en revanche l’objet d’un traitement de façon à apparaître dans l’index du document.
La marque du texte interprété est le backtick (accent grave), précédé ou suivi du rôle que devra jouer le texte en question, une référence de RFC par exemple. Le rôle est un identifiant précédé et suivi des deux points. Par exemple :
Les détails sont dans le :RFC:`2822`.
Cette notation permettra de créer automatiquement un lien vers le RFC correspondant.
Le rôle est facultatif : un rôle par défaut sera appliqué dans ce cas. La valeur par défaut du rôle par défaut (bah oui, y’a deux fois défaut) est :title-reference:, qui correspond au titre d’un ouvrage ou d’un article et sera typiquement marqué par la balise <cite> dans un rendu HTML. Il est possible de spécifier la valeur par défaut du rôle par défaut (encore...) à l’aide de la directive default-role (voir, ci-dessous, " Les directives ").
Notre but étant de bricoler avec Docutils, voyons tout de suite comment créer de nouveaux rôles. En principe, il faut définir le nouveau rôle à l’aide d’une fonction (voir " Création de nouveaux rôles pour le texte interprété ", ci-dessous), mais ce n’est pas nécessaire dans le cas le plus simple. On supposera simplement que l’on souhaite produire un document HTML et que l’on veuille obtenir un effet " surligné " pour certains mots de celui-ci. La directive role nous permet de définir notre rôle :
.. role:: surligne
Et on peut ensuite l’utiliser où on le souhaite dans notre document :
Essai de :surligne:`texte surligné`.
Cette ligne produira le résultat suivant dans la sortie HTML :
Essai de <span class="surligne">texte surligné<span>.
Il ne reste qu’à personnaliser les feuilles de style et le tour sera joué.
Les directives
Les directives font partie des blocs au marquage explicite, comme les cibles d’hyperliens, les définitions des substitutions ou les commentaires, par exemple. Les directives sont conçues dès le départ comme un mécanisme d’extension de la syntaxe reST, qui permet de mettre en place de nouveaux éléments sans créer de nouveaux marqueurs syntaxiques (donc sans toucher au parser). Il existe de nombreuses directives standards, décrites dans reStructuredText Directives [7]. Comme un petit dessin vaut mieux qu’un long discours, voici la directive utilisée pour créer une image cliquable :
..image:: foo.png :target: http://libristes-immatures.fr/
Une directive est marquée par les deux points consécutifs en début de ligne, suivis d’une espace, du nom de la directive, de deux fois deux points (oui alors non, ça ne fait pas quatre points) et d’une espace. Tout ce qui suit sur la même ligne est considéré comme un argument de la directive (toutes n’en prennent pas forcément).
Les lignes suivant la directive forment le reste du bloc de celle-ci : elles doivent être indentées. Les lignes qui débutent par un nom de champ entre : sont les options de la directive, puis suit le contenu de celle-ci. Options et contenus ne sont pas obligatoires, tout dépend de la directive.
La directive image a un seul argument, obligatoire, l’URI du fichier source de l’image. Elle peut prendre de nombreuses options, classiques dans le cas des images : height, width, align, scale, etc. Dans l’exemple ci-dessus, on utilise l’option target qui permet de rendre l’image cliquable et de spécifier l’URI de sa cible.
Une classe de directives un peu spéciale est celle des admonitions. Elle représente tous les paragraphes mis en valeur comme catégories particulières, comme " Note ", " Important ", " Danger ", etc. Cette classe admet une directive générique qui permet de créer de nouvelles catégories en dehors de celles qui sont explicitement prévues. Ainsi, il n’y a pas d’admonition " exemple ". Mais, il est tout à fait possible d’en créer une de cette manière :
.. admonition:: exemple Ceci est un excellent exemple.
Le rendu HTML serait alors :
<div class="admonition-exemple admonition"> <p class="first admonition-title">exemple</p> <p class=”last”>Ceci est un excellent exemple</p> </div>
On notera la présence des class= qui permettent de styler indépendamment le bloc, son titre et son contenu. Bien entendu, on ne se contentera pas de si peu, et on verra un peu plus loin comment se créer des directives maison aux petits oignons.
Obtenir le résultat souhaité
Personnaliser les feuilles de style
Naturellement, la feuille de style évoque le rendu HTML et sa personnalisation par le biais d’une CSS. Docutils permet bien sûr de le faire, et le HTML produit par défaut utilise l’attribut class de façon systématique. Les classes sont bien conçues et permettent de styler avec beaucoup de précision.
En reprenant l’exemple ci-dessus (les directives) :
<div class="admonition-exemple admonition"> <p class="first admonition-title">exemple</p> <p class=”last”>Ceci est un excellent exemple</p> </div>
On constate la présence de nombreuses classes :
admonition
Cette classe est commune à tous les blocs générés par les directives admonition. On peut l’utiliser pour encadrer ceux-ci, par exemple.
admonition-exemple
Ici aussi, la classe concerne tout le bloc, mais elle est propre aux blocs d’exemple. On pourrait donc les distinguer des autres blocs par une couleur de fond spéciale.
first
Le premier paragraphe de tous les blocs d’admonition est marqué par cette classe.
admonition-title
Le titre de chaque bloc d’admonition possède cette classe.
last
Marque le dernier paragraphe de chaque bloc d’admonition.
On peut créer une feuille de style from scratch, éventuellement en s’inspirant de la feuille par défaut de la distribution (docutils/writers/html4css1/). Mais si vous voulez redistribuer votre création, il est recommandé de copier la feuille standard dans votre répertoire de travail, puis de l’importer dans votre feuille :
/* :Author: :Contact: :Copyright: This stylesheet has been placed in the public domain. Stylesheet for use with Docutils. [Optionally place a more detailed description here.] */ @import url(html4css1.css);
La feuille de style peut être incluse dans le HTML final ou simplement liée à celui-ci. Quatre options de rst2html.py permettent de contrôler ce comportement :

On trouvera quelques détails supplémentaires dans la documentation des feuilles de style [8]
Toutefois, l’utilisation de feuilles de style dans Docutils ne se limite pas au rendu HTML. Il est possible de passer des paramètres dans le rendu LaTeX au travers de ce qui est également appelé une feuille de style. Au sens strict, ce n’en est pas une, mais simplement un fichier dont le contenu sera inclus dans l’en-tête du fichier de sortie LaTeX (avant la ligne \begin{document} donc).
La documentation du rendu LaTeX [9] précise la liste des paramètres qui sont configurables de cette façon. J’utilise ainsi systématiquement :
\setlength{\parindent}{0pt}
\setlength{\parskip}{2ex plus 0.2ex minus 0.2ex}
Ici, les premières lignes des paragraphes ne sont pas indentées, et la valeur de l’espacement vertical entre les paragraphes est un peu supérieure à la valeur par défaut.
Deux options de ligne de commande contrôlent l’utilisation de la " feuille de style " LaTex :

Le fichier de configuration
Comme on vient d’en avoir une illustration ci-dessus, la plupart des options de ligne de commande des outils Docutils sont des options POSIX longues. Pour éviter de devoir les préciser à chaque invocation, et ainsi faire honneur à notre animal favori, la loutre, on peut renseigner un (ou des) fichier(s) de configuration avec les options choisies.
Ces fichiers de configuration adoptent une syntaxe tout à fait standard et sont composés de sections autour des principales briques de Docutils : readers, parsers, writers et applications. Dans chaque section, on peut trouver des sous-sections pour chaque instance de la classe d’outil concernée. Tout ceci est d’un classicisme de bon aloi, et bien documenté [10] : je me demande pourquoi j’en parle, tiens.
Trois emplacements de fichiers de configuration sont lus par défaut, respectivement /etc/docutils.conf pour le système, ./docutils.conf pour le projet et ~/.docutils pour l’utilisateur. On peut de plus préciser un fichier sur la ligne de commande par l’option --config. Comme d’habitude, en cas de conflit, c’est la dernière valeur lue qui l’emporte ou la valeur passée sur la ligne de commande si elle est présente.
Allez, un petit exemple et on passe à plus amusant :
$ cat .docutils [html4css1 writer] embed_stylesheet: no stylesheet_path: /home/user/templates/docutils.css
Les mains dans le cambouis
Création de nouveaux rôles pour le texte interprété
On a vu dans la description du texte interprété comment créer un rôle tout simple pour faire du <span class="truc">blah</span> dans le rendu html. Mais, ce n’était bien sûr qu’une mise en bouche.
La documentation fournit quelques informations sur la création des rôles [11], mais surtout un conseil bien utile : " Use the Source, Luke ! "... La source en question est le module parsers/rst/roles.py. On va s’en inspirer pour créer un rôle qui permettra de faire des hyperliens vers le del.icio.us d’un groupe de jeunes barbus de ma connaissance [12]. En effet, celui-ci fourmille d’informations pertinentes et introuvables, et non, non, je ne suis pas influencé. L’objectif est donc de pouvoir écrire :
Pour plus d’informations, voir le tag :gcu:`python`.
Pour obtenir en HTML :
<p>Pour plus d’informations, voir le tag <a href="http://del.icio.us/gcu_squad/python>python</a>.</p>
Et un hyperlien équivalent dans un source LaTeX qui sera compilé pour obtenir un document PDF.
Le module docutils/parsers/rst/roles.py contient la source du rôle :RFC: déjà évoqué. On s’en servira de modèle :
1 def rfc_reference_role(role, rawtext, text, lineno, inliner,
2 options={}, content=[]):
3 try:
4 rfcnum = int(text)
5 if rfcnum <= 0:
6 raise ValueError
7 except ValueError:
8 msg = inliner.reporter.error(
9 ‘RFC number must be a number greater than or equal to 1; ‘
10 ‘”%s” is invalid.’ % text, line=lineno)
11 prb = inliner.problematic(rawtext, rawtext, msg)
12 return [prb], [msg]
13 # Base URL mainly used by inliner.rfc_reference, so this is correct:
14 ref = inliner.document.settings.rfc_base_url + inliner.rfc_url % rfcnum
15 set_classes(options)
16 node = nodes.reference(rawtext, ‘RFC ‘ + utils.unescape(text), refuri=ref,
17 **options)
18 return [node], []
Les rôles sont implémentés par la fonction truc_role qui recevra automatiquement les arguments suivants :
rolele nom du rôle tel qu’il est utilisé dans le texte source.
rawtextune chaîne qui contient la totalité du texte interprété, y compris le marquage en ligne. Cette variable est utilisée en particulier pour renvoyer des messages d’erreurs, comme on peut le voir dans le source ci-dessus, ligne 11.
textle texte à interpréter, c’est-à-dire ce qui est entre les `.
linenole numéro de ligne du texte source où commence le texte interprété.
inlinerl’objet
Inliner (décrit dans docutils/parsers/rst/states.py) qui a appelé la fonction, et qui contient des informations utiles pour le traitement des erreurs et l’accès à l’arbre du document. On le voit ici utilisé aux lignes 8 et 11 pour le traitement de l’erreur, et à la ligne 14 pour le traitement de l’URL.
optionsun dictionnaire contenant les options éventuellement passées à la directive de personnalisation du rôle.
content
une liste de chaînes de contenu éventuellement passées à la directive de personnalisation du rôle.
Cette structure sera donc la base du rôle :gcu:. On notera que dans la source ci-dessus, un contrôle est fait : il s’agit du traitement d’une référence de RFC. Celle-ci doit donc être numérique et positive. Si ce n’est pas le cas, une exception est levée et traitée avec les outils de Docutils, qui génèreront le message d’erreur adéquat. Notre code sera bien plus simple pour deux raisons : un tag peut être à peu près n’importe quoi, et, surtout, il s’agit simplement de montrer en quelques lignes le principe de la création de rôle, pas de produire un code abouti.
Le rôle une fois décrit par une fonction, il doit être enregistré pour être reconnu par le parser de Docutils. Cet enregistrement peut se faire de façon globale dans Docutils, si vous avez l’intention de toujours utiliser votre rôle ou de façon locale à une application. Dans notre cas, un enregistrement local suffira. Application ? Qu’est-ce que c’est que cette " application " ? En fait, on parle ici du script qui prendra en charge le traitement à l’aide d’un publisher, comme expliqué à la section consacrée à la structure de Docutils. Copiez la source de rst2html.py dans un répertoire de test :
$ cp /usr/bin/rst2html myrst2html.py
Adaptez selon votre distribution, bien sûr. Les applications se trouvent en principe dans docutils/tools, mais on se rappelle que Debian les installe dans usr/bin et supprime leur suffixe.
Éditez maintenant myrst2html.py (qui ne fait que quelques lignes) pour y ajouter la fonction de définition du rôle :gcu: :
1 #!/usr/bin/python
2
3 „““
4 A minimal front end to the Docutils Publisher, producing HTML.
5 „““
6
7 try:
8 import locale
9 locale.setlocale(locale.LC_ALL, ‚‘)
10 except:
11 pass
12
13 from docutils import nodes, utils
14 from docutils.core import publish_cmdline, default_description
15 from docutils.parsers.rst import roles
16
17 def gcu_reference_role(role, rawtext, text, lineno, inliner,
18 options={}, content=[]):
19 ref = „http://del.icio.us/gcu_squad/%s“ % text
20 roles.set_classes(options)
21 node = nodes.reference(rawtext, utils.unescape(text), refuri=ref,
22 **options)
23 return [node], []
24
25 roles.register_local_role(‚gcu‘, gcu_reference_role)
26
27
28
29 description = (‚Generates (X)HTML documents from standalone reStructuredText ‚
30 ‚sources. ‚ + default_description)
31
32 publish_cmdline(writer_name=‘html‘, description=description)
Les lignes 1 à 11, 14 et 29 à 32 viennent du rst2html.py d’origine. Les ajouts sont les lignes 13 et 15, importation des modules nécessaires, lignes 17 à 23, définition de la fonction de rôle, et, enfin, ligne 25, enregistrement de celle-ci.
Il reste à créer un fichier source minimal :
$ cat truc.txt Essai de texte interprété Voir le lien vers :gcu:`python` pour d’autres informations.
Et enfin à le traiter, et à admirer le résultat :
$ myrst2html.py truc.txt truc.html $ tail -n 5 truc.html <p>Essai de texte interprété</p> <p>Voir le lien vers <a class="reference" href="http://del.icio.us/gcu_squad/python">python</a> pour d’autres informations.</p> </div> </body> </html>
C’est pas beau ?
Création de nouvelles directives
Important
L’API de création de directives a changé après la dernière version " stable " de Docutils, qui est la 0.4 et est celle qui est distribuée sous forme de packages. Ce qui suit concerne la version 0.4 et donc ce qu’on pourrait appeler l’ancienne API, parce qu’il y a de fortes chances que ce soit celle qui est installée sur votre machine.
En tout état de cause, la compatibilité avec l’ancienne API est conservée : si vous avez le dernier snapshot, ce qui suit fonctionnera aussi – mais vous pouvez essayer la nouvelle API dans laquelle les directives sont définies par classe, non par fonction.
La création de nouvelles directives va souvent de pair avec l’enrichissement de l’arbre créé par Docutils pour représenter le document après le passage par le parser. En effet, si un nouvel élément est créé, il faut pouvoir le représenter dans l’arbre. Il sera ensuite nécessaire d’indiquer au(x) writers comment traiter cet élément.
Pour illustrer tout ceci, on va s’intéresser à la représentation des formules mathématiques dans une source reST. La FAQ Docutils [13] indique qu’il n’existe pas de solution intégrée élégante pour le moment. Toutefois, différents contributeurs proposent des solutions plus ou moins abouties.
Jens Jørgen Mortensen a mis en place un rôle et une directive pour utiliser la syntaxe des mathématiques de LaTeX directement dans la source reST. Le rôle permet d’entrer une formule " en ligne ", donc à l’intérieur d’un paragraphe, et la directive de créer un bloc de formule qui se comportera comme un paragraphe à part entière. Jens crée à cet effet un nouvel élément (node) dans l’arbre représentant le document, et enrichit les writers LaTeX et HTML de façon à pouvoir traiter cet élément. Il utilise le même principe que celui qu’on vient d’appliquer pour créer un rôle " maison " : partir des scripts standards comme rst2html.py et les enrichir de ses créations. On va commenter sa version de rst2latex.py, dont la longueur est acceptable, et qui contient néanmoins tout ce qu’il faut pour saisir le principe. La source [14] se trouve dans la sandbox du projet :
1 #!/usr/bin/env python
2
3 „““
4 A minimal front end to the Docutils Publisher, producing LaTeX.
5 „““
6
7 try:
8 import locale
9 locale.setlocale(locale.LC_ALL, ‚‘)
10 except:
11 pass
12
13 from docutils.parsers.rst.roles import register_canonical_role
14 from docutils import nodes
15 from docutils.writers.latex2e import LaTeXTranslator
16 from docutils.parsers.rst.directives import _directives
17 from docutils.core import publish_cmdline, default_description
18
19
20 # Define LaTeX math node:
21 class latex_math(nodes.Element):
22 tagname = ‘#latex-math’
23 def __init__(self, rawsource, latex):
24 nodes.Element.__init__(self, rawsource)
25 self.latex = latex
26
27 # Register role:
28 def latex_math_role(role, rawtext, text, lineno, inliner,
29 options={}, content=[]):
30 i = rawtext.find(‘`’)
31 latex = rawtext[i+1:-1]
32 node = latex_math(rawtext, latex)
33 return [node], []
34 register_canonical_role(‘latex-math’, latex_math_role)
35
36
37 # Register directive:
38 def latex_math_directive(name, arguments, options, content, lineno,
39 content_offset, block_text, state, state_machine):
40 latex = ‘’.join(content)
41 node = latex_math(block_text, latex)
42 return [node]
43 latex_math_directive.arguments = None
44 latex_math_directive.options = {}
45 latex_math_directive.content = 1
46 _directives[‘latex-math’] = latex_math_directive
47
48
49 # Add visit/depart methods to HTML-Translator:
50 def visit_latex_math(self, node):
51 inline = isinstance(node.parent, nodes.TextElement)
52 if inline:
53 self.body.append(‘$%s$’ % node.latex)
54 else:
55 self.body.extend([‘\\begin{equation*}\\begin{split}’,
56 node.latex,
57 ‘\\end{split}\\end{equation*}’])
58 def depart_latex_math(self, node):
59 pass
60 LaTeXTranslator.visit_latex_math = visit_latex_math
61 LaTeXTranslator.depart_latex_math = depart_latex_math
62
63
64 description = (‘Generates LaTeX documents from standalone reStructuredText ‘
65 ‘sources. ‘ + default_description)
66
67 publish_cmdline(writer_name=’latex’, description=description)
Les ajouts sont facilement repérables :
lignes 14 et 20-25
Définition du nouvel élément (node) pour l’arbre de représentation du document. Celui-ci doit hériter de la classe nodes.Element donc nodes est importé (ligne 14). Le constructeur appelle simplement le constructeur de la classe parente avec le contenu brut du nœud, puis crée un attribut latex qui ne contient que la formule dans la syntaxe LaTeX.
lignes 13 et 27-34
Définition du rôle pour les formules " en ligne ". On a vu, à la section précédente, la création de rôle de façon détaillée. Les lignes 30 et 31 permettent d’isoler la formule LaTeX du texte brut, le nœud (du type latex_math défini au-dessus) est créé ligne 32. Enfin, il ne faut pas oublier d’enregistrer la fonction de rôle (ligne 34).
lignes 16 et 37-46
Définition de la fonction de directive. Elle n’est pas très différente de la fonction de rôle. On se rappellera simplement que les directives peuvent avoir des arguments, des options et un contenu. L’appel de fonction contient donc ces paramètres (ligne 38) entre autres. Le contenu proprement dit de la fonction (lignes 40 et 41) est très simple : concaténation des lignes de contenu, et création du nœud. Les lignes 43 à 45 permettent de préciser que la fonction de directive latex_math_directive ne prend ni argument, ni option, mais admet un contenu. Enfin, la directive est enregistrée, d’une façon peu orthodoxe semble-t-il : une fonction register_directive dans le module docutils.parsers.rst.directives est normalement réservée à cet effet.
lignes 15 et 49-61
Définition des fonctions à ajouter au writer pour traiter l’élément latex_math qu’on vient de créer. Deux fonctions sont nécessaires pour traiter un élément blah : visit_blah, appelée lorsque l’élément est rencontré en parcourant l’arbre, depart_blah lorsque l’on quitte l’élément. On définit donc visit_latex_math (lignes 50-57) et depart_latex_math (lignes 58-59). Le code de visit_latex_math est simple : on teste si on est dans un contexte " en ligne ". Si oui, le contenu de la formule est placé entre $, conformément à la syntaxe LaTeX. Sinon, on a affaire à l’environnement equation qui est mis en place dans le document final. On pourra noter que celui-ci est très commodément traité comme une liste de lignes. Les lignes 60 et 61 permettent d’enregistrer les deux fonctions ainsi créées auprès du writer.
Pour plus de détails sur la création de directives, voyez la documentation de création des directives [15], qui contient d’autres exemples commentés, et, comme d’habitude, plongez dans les sources, l’eau est bonne...
Utiliser les publishers dans un shell ou un programme
Allez, une petite dernière pour la route : j’aimerais utiliser Docutils pour publier les billets d’un blog, ou un fragment de page HTML. On va donc bricoler une petite application pour ne sortir que le contenu du tag <body> du rendu HTML. En fait, il n’y a pas grand chose à faire, car tout est déjà prévu. En effet, les auteurs de Docutils ont concocté des publishers spécialisés, parmi lesquels publish_parts, qui permet comme son nom l’indique de ne publier que certaines parties du document. Les possibilités dépendent du writer utilisé, et c’est le writer HTML qui en offre le plus. Elles sont détaillées dans la documentation des publishers [16].
La distribution standard propose également le fichier docutils/examples.py qui contient des exemples d’utilisation de publish_parts. On va directement utiliser ces exemples :
1 #!/usr/bin/python
2
3 „““
4 A minimal front end to the Docutils Publisher, producing the body
5 of an HTML doc.
6 html_parts and html_body borrowed from docutils/examples.py
7 „““
8
9 try:
10 import locale
11 locale.setlocale(locale.LC_ALL, ‚‘)
12 except:
13 pass
14
15 from docutils import core
16
17 def html_parts(input_string, source_path=None, destination_path=None,
18 input_encoding=‘unicode‘, doctitle=1, initial_header_level=1):
19 „““
20 Given an input string, returns a dictionary of HTML document parts.
21
22 Dictionary keys are the names of parts, and values are Unicode strings;
23 encoding is up to the client.
24
25 Parameters:
26
27 - `input_string`: A multi-line text string; required.
28 - `source_path`: Path to the source file or object. Optional,
29 but useful for diagnostic output (system messages).
30 - `destination_path`: Path to the file or object which will
31 receive the output; optional. Used for determining relative
32 paths (stylesheets, source links, etc.).
33 - `input_encoding`: The encoding of `input_string`. If it is an
34 encoded 8-bit string, provide the correct encoding. If it is
35 a Unicode string, use "unicode", the default.
36 - `doctitle`: Disable the promotion of a lone top-level section
37 title to document title (and subsequent section title to document
38 subtitle promotion); enabled by default.
39 - `initial_header_level`: The initial level for header elements
40 (e.g. 1 for "<h1>").
41 """
42 overrides = {‘input_encoding’: input_encoding,
43 ‘doctitle_xform’: doctitle,
44 ‘initial_header_level’: initial_header_level}
45 parts = core.publish_parts(
46 source=input_string, source_path=source_path,
47 destination_path=destination_path,
48 writer_name=’html’, settings_overrides=overrides)
49 return parts
50
51 def html_body(input_string, source_path=None, destination_path=None,
52 input_encoding=’unicode’, output_encoding=’unicode’,
53 doctitle=1, initial_header_level=1):
54 """
55 Given an input string, returns an HTML fragment as a string.
56
57 The return value is the contents of the <body> element.
58
59 Parameters (see `html_parts()` for the remainder):
60
61 - `output_encoding`: The desired encoding of the output. If a Unicode
62 string is desired, use the default value of "unicode" .
63 """
64 parts = html_parts(
65 input_string=input_string, source_path=source_path,
66 destination_path=destination_path,
67 input_encoding=input_encoding, doctitle=doctitle,
68 initial_header_level=initial_header_level)
69 fragment = parts[‘html_body’]
70 if output_encoding != ‘unicode’:
71 fragment = fragment.encode(output_encoding)
72 return fragment
73
74
75 import sys
76
77 thebody = html_body(sys.stdin.read(),input_encoding=’utf8’)
78
79 sys.stdout.write(thebody)
Le code pourrait presque se passer de commentaires. On peut faire beaucoup plus court, mais on a préféré utiliser les deux fonctions html_parts et html_body fournies dans les exemples parce qu’elles sont propres et proposent des valeurs par défaut " raisonnables " pour les paramètres de publish_parts.
Quelques points à souligner :
lignes 45-48
C’est le cœur du travail qui est fait ici, avec l’appel à publish_parts et le passage des différents paramètres. Parmi ceux-ci, on notera le passage du writer choisi par son nom (une chaîne de caractères), le publisher se chargera de retrouver la classe correspondante, et settings_overrides qui permet de forcer certains paramètres, tel l’encodage du document, par exemple.
lignes 42-44
C’est ici que sont précisés les paramètres envoyés au travers de settings_override.
ligne 69
La fonction html_parts renvoie le dictionnaire fourni par publish_parts, dont les clés sont les noms des différentes parties du document, et les valeurs les contenus de celles-ci. On récupère donc celle qui correspond au contenu du tag <body> du rendu HTML.
ligne 77
On appelle simplement la fonction html_body en lui passant la chaîne de caractères en provenance de l’entrée standard, et en lui précisant l’encodage de celle-ci.
ligne 79
On envoie le résultat sur la sortie standard.
Comme on le voit, les trois dernières lignes sont pour le moins spartiates, et il faudrait bien sûr les enrichir pour une application digne de ce nom : traitement des erreurs, récupération de paramètres sur la ligne de commande, etc.
En guise de conclusion
Comme vous l’avez probablement constaté, Docutils promet des heures d’amusement pour peu que l’on soit un peu curieux. N’oublions pas toutefois que le but initial de cet outil est de permettre de produire rapidement une documentation de qualité, en gérant la source de celle-ci sous forme de fichiers textes, avec tous les avantages que cela présente. Dans cet esprit, on peut trouver autour de Docutils un certain nombre d’outils intéressants. On n’oubliera pas non plus qu’il existe des alternatives à cette solution.
Les goudizes
Évidemment, il ne s’agit pas d’être exhaustif. Le premier outil est particulièrement utile pour tester Docutils sans l’installer. Le site rst2a.com [17] permet en effet de traiter un document source pour obtenir un rendu PDF ou HTML, avec un choix de feuilles de style.
La sandbox de Docutils contient des writers qui n’ont pas encore intégré le projet en standard. Parmi ceux-ci, j’ai remarqué, et utilisé pour livrer cet article, odtwriter [18]. Ce writer permet, comme son nom l’indique, de produire un fichier au format Open Document [19], lisible directement par la suite OpenOffice. Ça s’installe tout seul, ça juste-marche et c’est le bonheur. Bien sûr, il est possible de personnaliser le rendu au travers de la feuille de style.
L’outil rest2web [20] propose quant à lui de gérer un site web complet au travers de sources Docutils. Il ajoute donc à la suite le nécessaire pour avoir des fichiers de templates de façon à obtenir menus et autres éléments de navigation.
Enfin, Vim reStructured Text [21] est un projet un peu fou de Mikolaj Machowski. Il a reconstruit le parser reST et les principaux writers de Docutils comme des scripts de Vim. On peut donc directement transformer le texte source en HTML (par exemple) sans quitter l’éditeur et à l’aide de commandes ajoutées à celui-ci. Bien qu’utilisateur de Vim, je dois avouer que je ne vois pas bien l’intérêt, sauf à ne pas vouloir ou pouvoir installer Python et Docutils.
La concurrence
Pour finir, je parlerai brièvement de deux projets " concurrents " de Docutils, qui sont également écrits en Python. Il y en a bien sûr d’autres...
Le projet asciidoc [22] de Stuart Rackham semble assez proche, au moins dans ses objectifs. Il est actif et utilisé (la documentation de git [23] par exemple est réalisée avec), et la feuille de style de base est, il faut l’avouer, assez élégante.
De son côté, txt2tags [24] se démarque complètement de la philosophie de Docutils, et le revendique, quoique sans jamais nommer son concurrent. Il s’agit en effet d’un seul programme Python qui accomplit l’ensemble du travail. C’est un outil abouti, qui offre de nombreux formats en sortie, dont par exemple celui du wiki MoinMoin [25] ou du système de présentation Magic Point [26].
J’ai également utilisé ces deux outils pour différents projets, et je pense que la préférence pour l’un ou l’autre est affaire de goût avant tout. Il reste que Docutils présente une documentation très développée, en particulier vers les utilisateurs avancés ou bricoleurs, ce qui ne manquera pas de chatouiller le hacker qui sommeille en vous. Amusez-vous bien !
Liens
- [1] http://docutils.sourceforge.net/README.txt#installation
- [2] http://docutils.sourceforge.net/README.txt
- [3] http://docutils.sourceforge.net/mirror/setext.html
- [4] http://dev.zope.org/Members/jim/StructuredTextWiki/FrontPage/
- [5] http://structuredtext.sourceforge.net/HISTORY.html
- [6] http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html
- [7] http://docutils.sourceforge.net/docs/ref/rst/directives.html
- [8] http://docutils.sourceforge.net/docs/howto/html-stylesheets.html
- [9] http://docutils.sourceforge.net/docs/user/latex.html
- [10] http://docutils.sourceforge.net/docs/user/config.html
- [11] http://docutils.sourceforge.net/docs/howto/rst-roles.html
- [12] http://www.gcu.info/
- [13] http://docutils.sourceforge.net/FAQ.html
- [14] http://docutils.sourceforge.net/sandbox/jensj/latex_math/tools/rst2latex.py
- [15] http://docutils.sourceforge.net/docs/howto/rst-directives.html
- [16] http://docutils.sourceforge.net/docs/api/publisher.html
- [17] http://rst2a.com/
- [18] http://www.rexx.com/~dkuhlman/odtwriter.html
- [19] http://www.oasis-open.org/committees/tc_home.php?wg_abbrev=office
- [20] http://www.voidspace.org.uk/python/rest2web/
- [21] http://skawina.eu.org/mikolaj/vst.html
- [22] http://www.methods.co.nz/asciidoc/
- [23] http://git.or.cz/
- [24] http://txt2tags.sourceforge.net/
- [25] http://moinmo.in/
- [26] http://member.wide.ad.jp/wg/mgp/
Retrouvez cet article dans : Linux Magazine 104


