Un seul logiciel pour tous mes textes !
Et aussi pour mon site Web avec Spip
Article mis en ligne le 30 septembre 2020
dernière modification le 10 octobre 2020

par Laurent Bloch

Souvent j’écris un texte en LaTeX pour avoir une sortie PDF présentable, puis on me demande une copie au format docx de Word, par exemple pour une publication. Traduire à la main du LaTeX en docx est fastidieux, et on oublie toujours quelque chose. Les logiciels censés faire cela ne m’ont jamais convaincu, et ceux qui le font en sens inverse sont pires.

Il y a quelque temps un article de Benoît Benedetti, administrateur système Linux à l’Université de Nice Sophia Antipolis, AsciiDoc et AsciiDoctor pour soigner votre documentation, dans Gnu/Linux Magazine France n° 190 de février 2016, m’a fait découvrir le format de document AsciiDoc, qui permet de saisir un texte une fois pour toutes, avec une syntaxe de balises assez simple mais complète (plus complète que celle de Markdown, un système du même genre). À partir de ce document source, les logiciels AsciiDoc ou, mieux, AsciiDoctor permettent de produire des sorties PDF, LaTeX, DocBook, Html, XML, etc. Et à partir de la sortie DocBook le logiciel Pandoc peut produire une sortie docx tout à fait acceptable. J’ai donné le mode d’emploi de ces merveilles dans un autre article.

Il manquait quelque chose : j’utilise pour mon site Web et mon blog le système de publication (CMS) Spip, qui me convient parfaitement, mais pour lequel n’existait pas de traducteur AsciiDoc. Il m’a fallu donc en écrire un, que je vous présente ici. Il s’appelle AsciiDoc-Spip.

AsciiDoc-Spip ne traite qu’un sous-ensemble « raisonnable » d’AsciiDoc : ainsi, il n’accepte que les URL suivis d’une annotation entre crochets, éventuellement vide, mais j’ai besoin de la paire de crochets pour détecter la fin de l’URL. Pour les programmes je suppose l’usage du plugin de coloration du code. Je suppose que chaque paragraphe est saisi sur une seule ligne et séparé du suivant par une ligne vide. Je n’ai pas traité l’insertion de figures, qui de toute façon, avec Spip, se traite facilement dans l’interface d’administration. Je ne traite qu’un seul format de tableau, le plus simple, de toute façon sur une page Web mieux vaut rester simple.

Comme vous le verrez, j’ai programmé cela à grands coups d’expressions régulières. Je sais, il aurait été mieux d’écrire un véritable analyseur syntaxique, ce sera pour une version ultérieure.

Le langage de programmation utilisé est Scheme, avec le compilateur Bigloo. Désolé, pour compiler ce programme il faut installer Bigloo (cet article décrit comment faire avec une machine virtuelle sous Windows, mais sur un machine avec Ubuntu natif ce serait la même chose ; attention aux pré-requis).

L’article que vous lisez a été écrit en AsciiDoc et traduit en Spip par AsciiDoc-Spip. Voici le programme :

  1. (module AsciiDoc-Spip
  2.    (main analyse-fichier))
  3.  
  4. (define (analyse-fichier args)
  5.    (let ((un-fichier (cadr args)))
  6.       (call-with-input-file un-fichier lire-des-lignes)))
  7.  
  8. (define (lire-des-lignes flux)
  9.    (let boucle ((ligne (read-line flux)))
  10.       (if (eof-object? ligne)
  11.           ligne
  12.           (begin
  13. ;; Tableaux
  14.              (if (substring=? ligne "|===" 4)
  15.                  (traite-tableau ligne flux))
  16. ;; Textes de programmes encadrés
  17.              (let ((regexp-code "\\[source, ([a-zA-Z0-9-]+)\\]"))
  18.                 (if (pregexp-match regexp-code ligne)
  19.                     (traite-code regexp-code ligne flux)))
  20.              (analyse ligne)
  21.              (boucle (read-line flux))))))
  22.  
  23. (define (traite-tableau ligne flux)
  24.    (do ((ligne (read-line flux) (read-line flux)))
  25.        ((substring=? ligne "|===" 4))
  26.        (if (not (substring=? ligne "|===" 4))
  27.            (analyse (string-append ligne " |")))))
  28.  
  29. (define (traite-code regexp-code ligne flux)
  30.    (let ((langage (dernier (pregexp-match regexp-code ligne))))
  31.       (set! ligne (string-append "<cadre class=\"" langage "\">"))
  32.       (print ligne)
  33.       (do ((ligne (read-line flux) (read-line flux))
  34.            (debut #t #f))
  35.           ((and (substring=? ligne "----" 4)
  36.                 (not debut)))
  37.           (if (not (substring=? ligne "----" 4))
  38.               (print ligne))
  39.           )
  40.       (print "</ cadre>") ;; espace après / pour éviter que Spip ne traite
  41. ;; la balise... Pour l'utiliser en vrai, il faut supprimer l'espace.
  42.       ))
  43.  
  44. (define (dernier L)
  45.    (if (list? L)
  46.        (if (null? (cdr L))
  47.            (car L)
  48.            (dernier (cdr L)))))
  49.  
  50. (define (traite-lien ligne regexp-lien)
  51.    (let boucle
  52.          ((debut-ligne "")
  53.           (fin-ligne ligne)
  54.           (chaine
  55.              (string-append
  56.                 "[" (dernier (pregexp-match regexp-lien ligne))
  57.                 "->" (cadr (pregexp-match regexp-lien ligne)) "]"))
  58.           (suite 0))
  59.       (set! ligne
  60.          (string-append debut-ligne
  61.             (pregexp-replace regexp-lien fin-ligne chaine)))
  62.       (set! suite (+ (string-contains ligne chaine)
  63.                      (string-length chaine)))
  64.       (set! debut-ligne (substring ligne 0 suite))
  65.       (set! fin-ligne (substring ligne suite))
  66.       (if (pregexp-match regexp-lien fin-ligne)
  67.           (boucle
  68.              debut-ligne
  69.              fin-ligne
  70.              (string-append
  71.                 "[" (dernier (pregexp-match regexp-lien fin-ligne))
  72.                 "->" (cadr (pregexp-match regexp-lien fin-ligne)) "]")
  73.              suite)
  74.           (set! ligne (string-append debut-ligne fin-ligne)))
  75.       ligne))
  76.  
  77. (define (analyse ligne)
  78.    (let* ((carcomp "éèàùçêîâÀÊÎÂÏïëËÇüôäö!-\\?,'’")
  79.           (regexp1 "^(====)(.*)$")
  80.           (regexp2 "^(===)(.*)$")
  81.           (regexp3 "^(==)(.*)$")
  82. ;; Espaces insécables
  83.           (regexp-nbsp "{nbsp}")
  84.           (regexp-ital (string-append
  85.                       "_([^_]*)_"))
  86.           (regexp-gras (string-append
  87.                       "\\*([^\\*]*)\\*"))
  88. ;; Exposants
  89.           (regexp-sup (string-append
  90.                       "\\^([^\\^]*)\\^"))
  91. ;; Indices
  92.           (regexp-sub (string-append
  93.                       "~([^~]*)~"))
  94. ;; Code, chasse fixe
  95.           (regexp-tt (string-append
  96.                       "`([^`]*)`"))
  97.           (regexp-appelref (string-append
  98.                               "<<([\\w " carcomp "]*)>>"))
  99.           (regexp-ref (string-append
  100.                          "\\[\\[\\[([\\w " carcomp "]*)\\]\\]\\]"))
  101. ;; URL
  102.           (regexp-url (string-append
  103.                          "(http(s)?://"
  104.                          "([a-zA-Z0-9-]+\\.){1,5}"
  105.                          "[a-zA-Z]{2,4}(:\\d+)?(/([^[]*)?)?)"))
  106. ;; Annotation d'un URL
  107.           (regexp-txturl "\\[([^\\]]*)\\]")
  108. ;; Lien annoté
  109.           (regexp-lien (string-append
  110.                           regexp-url regexp-txturl))
  111. ;; Texte de programme
  112.           (regexp-code "\\[source, ([a-zA-Z0-9-]+)\\]")
  113.           )
  114. ;; Liste
  115.       (if (substring=? ligne "* " 2)
  116.           (string-set! ligne 0 #\-))
  117. ;; Exposants
  118.       (if (pregexp-match regexp-sup ligne)
  119.           (set! ligne
  120.              (pregexp-replace*
  121.                 regexp-sup ligne "<sup>\\1</sup>")))
  122. ;; Indices
  123.       (if (pregexp-match regexp-sub ligne)
  124.           (set! ligne
  125.              (pregexp-replace*
  126.                 regexp-sub ligne "<sub>\\1</sub>")))
  127. ;; Code, chasse fixe
  128.       (if (pregexp-match regexp-tt ligne)
  129.           (set! ligne
  130.              (pregexp-replace*
  131.                 regexp-tt ligne "<code>\\1</code>")))
  132.       (if (pregexp-match regexp-appelref ligne)
  133.           (set! ligne
  134.              (pregexp-replace*
  135.                 regexp-appelref ligne "[\\1]")))
  136.       (if (pregexp-match regexp-ref ligne)
  137.           (set! ligne
  138.              (pregexp-replace*
  139.                 regexp-ref ligne "[\\1]")))
  140. ;; Espaces insécables
  141.       (if (pregexp-match regexp-nbsp ligne)
  142.           (set! ligne
  143.              (pregexp-replace*
  144.                 regexp-nbsp ligne " ")))
  145. ;; Lien annoté
  146.       (if (pregexp-match regexp-lien ligne)
  147.           (set! ligne (traite-lien ligne regexp-lien))
  148.           )
  149.       (if (pregexp-match regexp-ital ligne)
  150.           (set! ligne (pregexp-replace*
  151.                          regexp-ital ligne "{\\1}")))
  152.       (if (pregexp-match regexp-gras ligne)
  153.           (set! ligne (pregexp-replace*
  154.                          regexp-gras ligne "{{\\1}}")))
  155.       (cond ((pregexp-match regexp1 ligne)
  156.              (print (pregexp-replace
  157.                        regexp1 ligne "{{{***\\2}}}")))
  158.             ((pregexp-match regexp2 ligne)
  159.              (print (pregexp-replace
  160.                        regexp2 ligne "{{{**\\2}}}")))
  161.             ((pregexp-match regexp3 ligne)
  162.              (print (pregexp-replace
  163.                        regexp3 ligne "{{{*\\2}}}")))
  164.             ((zero? (string-length ligne))
  165.              (newline))
  166. ;; Pour sauter les lignes de commandes AsciiDoc
  167.             ((char=? (string-ref ligne 0) #\:))
  168.             ((substring=? ligne "[." 2))
  169.             ((substring=? ligne "|===" 4))
  170.             ((pregexp-match regexp-code ligne))
  171.             (else
  172.              (print ligne)))))

Télécharger

Et le texte AsciiDoc de l’article (moins le programme...) :