More is less

Xml ou comment tortiller du cul pour chier droit

XML : eXacerbate Merdum Language. On traverse des piles de balises, et vas-y que je t'ouvre une porte et voilà que j'en referme une autre. Tout ça pour stocker quelques maigres données. Je suis un peu de mauvaise foi, ...

Origine de l'affaire

Je connaissais un logiciel en console de conjugaison fait par Pierre Sarrazin (un québéquois). Pour ceux qui ont toujours un terminal sous la main, il suffisait de taper verbiste jouer, pour avoir les conjugaions de jouer à presque tous les temps et tous le modes. Depuis quelques temps c'est devenu une application gui difficile à compiler pour la console et qui te mettait des fichiers un peu partout.

Source : verbiste

Ma mauvaise foi est toute relative

Ma critique du xml est un peu naïve. Car à l'origine ce métalangage était destiné à un peu plus que contenir des données. Mais l'usage le plus courant se limite à ça. Ce qui est indéniable c'est que xml est extrèmement verbeux. M'inspirant de python il m'a semblé qu'on devait pouvoir structurer des données avec des tabulations et presque rien d'autre. Le presque fait référence au caractère # qui signale un commentaire.

Un format épuré et pythonnic

Les balises sont hiérarchisées avec les tabulations se trouvant au début, à la manière de python.

Exemple voici un extrait du fichier conjugation-fr.xml de Pierre Sarrazin :

<template name="pla:cer">
	<infinitive>
		<infinitive-present>
			<p><i>cer</i></p>
		</infinitive-present>
	</infinitive>
	<indicative>
		<present>
			<p><i>ce</i></p>
			<p><i>ces</i></p>
			<p><i>ce</i></p>
			<p><i>çons</i></p>
			<p><i>cez</i></p>
			<p><i>cent</i></p>
		</present>
		<imperfect>
			<p><i>çais</i></p>
			<p><i>çais</i></p>
			<p><i>çait</i></p>
			<p><i>cions</i></p>
			<p><i>ciez</i></p>
			<p><i>çaient</i></p>
		</imperfect>

Et voici l'équivalent en tabtag :

placer	cer
	indicatif
		présent
			ce
			ces
			ce
			çons
			cez
			cent
		imparfait
			çais
			çais
			çait
			cions
			ciez
			çaient

C'est quand même plus simple et plus lisible. La taille des fichiers s'en ressent d'un facteur 2.

Règles d'écriture des fichiers tabtag

Faute de mieux, tabtag c'est le nom de mon format de fichier.

Le vocabulaire employé fait référence à celui du xml.

Balise

Une balise tabtag est composée d'un identifiant, précédé éventuellement d'une ou plusieurs tabulations et suivi d'un (ou plusieurs) arguments.

L'identifiant est composé de n'importe quel caractère excepté une tabulation. S'il commence par le caractère #, c'est un commentaire et sera ignoré.

Avec les notations des expressions régulières une balise a la forme suivante :

(\t*)([\t]*)(\t.*)?

On peut avec cette grammaire construire un texte hiérachisé comme un arbre.

L'argument facultatif est composé d'une tabulation suivie de caractères quelconques. L'argument peut à son tour contenir des tabulations. Ces tabulations servent à délimiter des arguments, qui sont l'équivalent des attributs de xml.

Remarque : A la différence de xml, il n'y a pas de nœud document. À la racine il y a généralement plusieurs balises.

Lire une balise en python peut se faire alors ainsi

import re
retag = re.compile(r'(\t*)([\t]*)(\t.*)?')
m = retag.match('\t\tBalise de test\tArgument')

Avec cet exemple :

m.group(1)
vaut '\t\t' est désigne le rang de la balise
m.group(2)
vaut 'Balise de test' c'est l'identifiant de la balise
m.group(3)
vaut 'Argument' qui lui même peut se décomposer en arguments séparés par des tabulations.

Ainsi créer un enfant revient à ajouter une tabulation.

Revenir au parent revient à enlever une tabulation.

Exception : des balises sans enfant, (nœud texte en xml) seront interprétées comme des données textuelles. Cerise sur le gâteau, et à la différence de xml, on peut avoir des balises textuelles superposées (donc avec le même rang). Ainsi on peut coder une liste de données. L'algorithme de lecture est simple : tant qu'une balise a le mêre rang que la précédente on l'ajoute à la liste.

Un script pourri toxml.py pour convertir du tabtag en xml. J'aurais pu utiliser un générateur au lieu de mon zip (1).


 1 #!/usr/bin/env python3
 2 # toxml.py 
 3 # (c) 2017 Released under the GPL by marnout à free pt fr
 4 # lines to convert in xml
 5 import re
 6 # source tabtag
 7 tt = """# comment
 8 ul
 9    line 1
10    line 2
11    li
12       line 3
13       line 4
14    ul
15       line 5
16       line 6
17    line 7"""
18 # input list
19 rows = tt.split('\n')
20 retag = re.compile(r'(\t*)([^\t]+)(\t.*)?')
21 retabs = re.compile(r'\t*')
22 xml = ['<?xml version="1.0" encoding="UTF-8"?>'] # output
23 stack = [] # to retrieve end tag
24 for current, _next in zip(rows[:-1], rows[1:]):
25    if current.startswith('#'): continue # comment
26    m = retag.match(current)
27    tabs, tag, arg = m.groups()
28    nextrank = len(retabs.match(_next).group())
29    if len(tabs) < nextrank: # tag xml
30       xml.append('\t'*len(tabs)+'<'+tag+'>')
31       stack.append('\t'*len(tabs)+'</'+tag+'>')
32    else:
33       xml.append(current) # data 
34    if len(tabs) > nextrank: # end of last tag
35          xml.append(stack.pop())
36 xml.append(stack.pop()) # don't forget the last
37 #
38 print("◆ tabtag")
39 for x in rows: print(x)
40 print("◆ xml")
41 for x in xml: print(x)

Résultat

◆ tabtag
# comment
ul
	line 1
	line 2
	li
		line 3
		line 4
	ul
		line 5
		line 6
	line 7
◆ xml
<?xml version="1.0" encoding="UTF-8"?>
<ul>
	line 1
	line 2
	<li>
		line 3
		line 4
	</li>
	<ul>
		line 5
		line 6
	</ul>
</ul>

Ébauche d'api pour les manipuler

J'en ai fait deux :

En plus j'ai écrit deux conjugateurs en console :

Pour les deux il faut les fichiers .modèles et .verbes qui ont été crées à partir des fichiers xml de Pierre Sarrazin. Que le grand manitou me le pardonne.

(1) Elle est là la fonction générateur qui donne un terme et le suivant
def pair(liste):
	it = iter(liste)
	current = next(it)
	for _next in it:
		yield(current, _next)
		current = _next

Réalisé avec Qlam - LGPL