Des squelettes SPIP en XML, est-ce possible ?

, par Committo, Ergo Sum

Ce texte a été présenté sous forme de diaporama à une SPIP-party à Avignon en Juin 2009. Il est toujours d’actualité.

Plan

  1. Ce qui ne va pas dans l’existant
  2. MIME et SPIP
  3. Edition des squelettes : distinguer syntaxiquement le but et le moyen
  4. Analyse XML des squelettes : XML puissance 2 ?
  5. Validation XML des squelettes : typer les inclusions, les balises et les filtres ?

Le point fort de SPIP n’a pas de nom

Maintenant que SPIP est entouré de beaucoup d’outils semblables, ce qui reste un de ses points forts, savoir son système de squelettes, n’est connu que de ceux qui ont déjà développé en SPIP.

On présente ici une actualisation et une mise en avant de ce système.

Cette actualisation consiste en particulier à faire rentrer la syntaxe des squelettes dans le moule XML, sans tomber dans la verbosité fréquente dans ce formalisme.

Première partie

Plan

  1. Ce qui ne va pas dans l’existant
  2. MIME et SPIP
  3. Edition des squelettes : distinguer syntaxiquement le but et le moyen
  4. Analyse XML des squelettes : XML puissance 2 ?
  5. Validation XML des squelettes : typer les inclusions, les balises et les filtres ?

Les incohérences de la syntaxe actuelle

  • faux filtre, comme dans [(#LOGO_ARTICLE)|left||foo)] ;
  • filtre trompeur, comme dans <INCLURE{fond=x}|foo> ;
  • composition absurde, comme dans <BOUCLE1(AUTEURS){nom=<INCLURE{fond=x}|foo>}
  • statut ambigu des séparateurs, comme dans
    <BOUCLE1(AUTEURS) > </BOUCLE1>
    où le dernier est fondamental, les précédents sont superflus.

Les limitations de la syntaxe actuelle

  • impossible de conditionner une boucle à un champ, comme dans
    [(#X)<BOUCLE1(ARTICLES)>#TITRE</BOUCLE1>] ;
  • impossible qu’une boucle soit argument d’un filtre, comme dans
    [(#X|filtre{<BOUCLE1(ARTICLES)>#TITRE</BOUCLE1>})] ;
  • limitation du niveau d’imbrication, ceci étant rejeté à tort :
    <INCLURE{fond=f}{z=<:i{p=#GET{x}}:>}>.

Une syntaxe à part, est-ce bien raisonnable ?

  • aucun éditeur de texte n’y est adapté (notamment, pas de coloration spécifique, pas d’affichage dynamique des emboîtements à la saisie) ;
  • dénonciation de fausses erreurs dans les outils dédiés (les analyseurs HTML en particulier) ;
  • difficulté de diffusion due à l’absence de type MIME officiel.

Changer de syntaxe, est-ce bien raisonnable ?

Le compilateur de squelettes fonctionne en 2 passes, une d’analyse syntaxique (22Ko de code), l’autre de production de texte PHP/SQL à partir de l’arbre de syntaxe abstraite fourni par la première (270Ko) ; il n’y a donc que 20Ko de code à mettre au point en vérifiant que l’arbre de syntaxe est le même.

Un traducteur de l’ancienne syntaxe à la nouvelle a été écrit et testé sur 4000 squelettes de spip-zone ; 2 pour cent d’entre eux se sont révélés en fait déjà mal écrits dans l’ancienne syntaxe, et après correction les 4000 sont traduits sans problème (test en plusieurs minutes de temps CPU) ; ce traducteur pourra donc être intégré aux fonctions de mise à jour d’une prochaine version de SPIP.

Deuxième partie

Plan

  1. Ce qui ne va pas dans l’existant
  2. MIME et SPIP
  3. Edition des squelettes : distinguer syntaxiquement le but et le moyen
  4. Analyse XML des squelettes : XML puissance 2 ?
  5. Validation XML des squelettes : typer les inclusions, les balises et les filtres ?

SPIP, un type MIME

Les squelettes étant écrits dans un langage, celui-ci mérite d’être officialisé dans un type MIME déclaré par le formulaire MIME de l’IETF.

Les squelettes pouvant contenir du code à exécuter, on est clairement dans la famille MIME
dite application.

La conformité XML visée suggère donc de prendre comme type MIME
application/spip+xml

Le Content-Type pourra admettre le paramètre charset pour fiabiliser les échanges.

Le paramètre profile

Le type application/xhtml+xml a introduit le paramètre profile, idée déjà reprise par smile, pour préciser la syntaxe du document produit par l’application.

Dans le cas d’un document XML (XHTML, RSS, SVG...) le mieux est de donner l’URL de la DTD, à défaut le RFC décrivant le format.

Pour les squelettes ne donnant qu’un extrait (cf. inclusion), l’URL pourrait se terminer par #BALISE pour plus de précision.

Ce paramètre permettrait donc de rechercher facilement sur le Web un squelette remplissant une certaine fonctionnalité (par exemple tous les squelettes XHTML stricts fournissant un bloc head).

La langue du squelette

Dans une optique d’internationalisation des squelettes, où même les mot-clés seraient paramétrables, il faudra utiliser le paramètre MIME language, dont la valeur est une chaîne de deux ou trois lettres.

Au total, télécharger fiablement un squelette reviendrait à recevoir :

Content-Type: application/spip+xml; charset=utf8; language=fr; profile=http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd

L’extension du fichier d’un squelette

L’extension des fichiers de squelette doit évidemment s’appeller .spip.

Toutefois, pour préciser le contenu effectif après utilisation, cette extension serait précédée de l’extension visée, ce qui donnerrait par exemple article.html.spip et backend.rss.spip.

La langue du squelette pourrait aussi intervenir dans le nom, comme le fait Apache : article.html.spip.fr, article.html.spip.de etc.

SPIP chercherait les squelettes terminés par le spip_lang du site, et à défaut se rabattrait sur les noms sans langues.

Troisième partie

Plan

  1. Ce qui ne va pas dans l’existant
  2. MIME et SPIP
  3. Edition des squelettes : distinguer syntaxiquement le but et le moyen
  4. Analyse XML des squelettes : XML puissance 2 ?
  5. Validation XML des squelettes : typer les inclusions, les balises et les filtres ?

La construction multi

La construction <multi> [??]...[??].. </multi> en SPIP est conforme à ce que XML attend, mais a l’inconvénient d’être perçue comme une balise du document final, alors qu’elle ne fait pas partie de sa DTD.

Actuellement, elle n’accepte ni argument ni filtre, alors que cela peut être utile.

Pour notifier qu’il s’agit d’une construction de SPIP, disposant d’arguments et de filtres, et non du document à produire, je propose de l’écrire ainsi :

#:{[fr] bonjour [en] hello}

Les chaînes de langues

La construction <:X:> elle aussi fait croire qu’elle fera partie du document visé. A nouveau, il vaut mieux se rabattre sur le caratère distinctif # suivi de : pour minimiser les changements et éviter les ambiguités avec les champs SQL, ce qui donne :

#:X quand il n’y a ni argument, ni filtre

#:X{arg1=truc, arg2=machin} quand il n’y a pas de filtre

[(#:X{arg1=truc, arg2=machin}|filtre)] dans le cas le plus complexe.

Les inclusions

La construction <INCLURE... semble elle aussi appartenir au document final.

Un moyen d’évacuer le problème est d’utiliser plutôt #INCLURE en signalant que l’inclusion n’est pas en ligne, par exemple avec l’écriture #INCLURE** (la simpe étoile est déjà utilisée).

Toutefois cela ne fait qu’aggraver le problème du filtre terminal qui devrait être rejeté.

La question se posant pour en fait toutes les balises dynamiques, on reposera le problème dans ce contexte plus général à la fin de l’exposé.

Le quintette des boucles

Les 5 mots-clés <BOUCLE </BOUCLE <B </B <//B sont particulièrement trompeurs dans leur non appartenance au document visé.

Les 4 premiers reflètent parfois la structure finale (par exemple le ul avec ses li), mais ce n’est pas toujours vrai,
et le cinquième est inévitablement dérogatoire.

Il vaut mieux adopter la solution de PHP et consorts : ces constructions n’ont a priori rien à voir avec celles produites in fine, remplaçons-les par les processing-instructions de XML.

On introduit donc la syntaxe <?spip ... ?> pour signaler que tout ce qui est à l’intérieur ne fait pas partie du document final, et possède son autonomie syntaxique.

Marge de manœuvre

A ce stade, on pourrait garder la quasi-totalité de l’ancienne syntaxe, la seule différence étant le remplacement de
<
par <?spip
et de > par ?>, ce qui suffit à atteindre la différenciation entre les deux univers syntaxiques.

Toutefois, que <?spip /BOUCLE1 ?> ferme <?spip BOUCLE1(MOTS) .... ?>, n’est pas évident pour un utilisateur, ni connu des éditeurs habituels ; on profite donc de l’occasion pour changer un peu plus la syntaxe
et retomber sur une dénotation plus usuelle des structures parenthétiques, afin de respecter les habitudes de chacun, et de réemployer des outils d’analyse.

Critiques des lexèmes actuels

Outre les problèmes de syntaxe, le choix des lexèmes de SPIP est lui aussi critiquable :

  • les éléments morphologiques ne sont pas séparés par des espaces, erreur graphique antédiluvienne qu’on ne devrait plus commettre depuis l’invention de l’imprimerie ;
  • ces éléments sont parfois des signes sans signification (B / //), parfois des mots (BOUCLE, INCLURE) pas toujours en majuscules (fond) ;
  • dans le cas des mots, un certain ethnocentrisme linguistique est perceptible.

(Non-) Correspondances parenthétiques

Une nouvelle syntaxe débarassée de ces écueils doit tenir compte des aspects suivants :

  • le début de boucle et la fin de boucle décrivant une structure parenthétique, il faut utiliser des signes habituels pour cela (accolades, parenthèses ou crochets) ;
  • les lexèmes <B </B <//B pouvant être utilisés chacun sans les autres, il n’y a pas de correspondance entre eux, il faut prendre des mots-clés et non des signes connotés par des usages (/ veut souvent dire fermer quelque chose préalablement ouvert).

Une proposition de nouvelle syntaxe

On retient ici 4 mots-clés (français, mais c’est paramétrable) ; on sépare les entités morphologiques, et on utilise l’accolade ouvrante pour signifier le début de boucle. Cela donne :

<?spip AVANT dernieres_breves ?>
<h1> Dernières Brèves</h1>
<ul>
<?spip BOUCLE dernieres_breves (BREVES) { ?>
<li>#NOM</li>
<?spip }dernieres_breves ?>
</ul>
<?spip APRES dernieres_breves ?>
#:pas_de_breve
<?spip SANS dernieres_breves ?>

Les listes d’arguments

Cette proposition n’implique rien en ce qui concerne la syntaxe des critères, dont la liste n’a a priori pas de raison de changer.

Pour l’inclusion, il faut en profiter pour évacuer le mot-clé français, d’autant qu’il n’apporte rien, et alléger un peu l’écriture :

<INCLURE{fond=F}{A1}....{An}>

devient

<?spip INCLURE{F}(A1, ..., An) ?>

Abréviations

La séquence ?><?spip peut être totalement remplacée par un saut de ligne suivi éventuellement d’autres séparateurs :

<?spip BOUCLE 1 (MOTS) { ?><?spip INCLURE {#NOM} ?><?spip } 1 ?>

s’abrège

<?spip BOUCLE 1 (MOTS) { 
       INCLURE  {#NOM}
} 1 ?>

et même :

<?spip BOUCLE 1 (MOTS) {} ?>#TOTAL_BOUCLE<?spip SANS 1 ?>

Petit bilan provisoire

  • les éditeurs de texte usuels indiquent dynamiquement à la saisie la boucle fermée par une accolade fermante ;
  • la visualisation d’un squelette, notamment sous un navigateur, distingue bien les deux univers syntaxiques, la structure du futur résultat apparaissant plus nettement ;
  • la taille des squelettes varie peu, et parfois en moins ;
  • les séparateurs (sauts de ligne et tabulation) entre deux entités SPIP ne sont plus inclus dans le document final ;
  • il est possible d’avoir des boucles dans les crochets et les accolades d’un champ étendu.

Quatrième partie

Plan

  1. Ce qui ne va pas dans l’existant
  2. MIME et SPIP
  3. Edition des squelettes : distinguer syntaxiquement le but et le moyen
  4. Analyse XML des squelettes : XML puissance 2 ?
  5. Validation XML des squelettes : typer les inclusions, les balises et les filtres ?

Problèmes de conformité restants

La traduction de squelettes standards dans la syntaxe proposée fournit des squelettes conformes XML dans beaucoup de cas mais pas tous.

De plus, comme pour PHP, un analyseur XML standard ne suffit pas à analyser tout le texte fourni, et pas seulement à cause du contenu des processing instructions.

Petite revue des problèmes, et des solutions envisagées.

Une boucle dans un attribut

Il est assez fréquent d’écrire ceci :

<meta name='description'
content='<BOUCLE1(mots){","}>#TITRE</BOUCLE1>' />

soit, en nouvelle syntaxe :

<meta name='description'
content='<?spip BOUCLE 1 (mots) { {","} ?>#TITRE<?spip } 1 ?>' />

Pas de problème particulier, mais un analyseur XML standard ne considère pas le contenu d’un attribut comme pouvant contenir des processing instruction, il faut donc bien s’en écrire un spécifique.

Un attribut conditionné par une boucle

Un problème plus important est posé par l’attribut qui n’apparaît même pas si la boucle ne rend rien, typiquement en ancienne syntaxe :

<meta name='description'
<B1> content='<BOUCLE1(mots){","}>#TITRE</BOUCLE1>'</B1> />

ici, je pense qu’il faut être pragmatique et dire qu’une boucle dans un contexte aussi particulier qu’un attribut implique que le nom de l’attribut, le signe égal et les apostrophes ne seront là que si la boucle produit quelque chose.

<meta name='description' 
content='<?spip BOUCLE 1 (mots) { {","} ?>#TITRE<?spip } 1 ?>' 
/>

N’importe quoi conditionné par n’importe quoi

Dernier cas franchement hors conformité XML :

<<BOUCLE1(ARTICLES){id_article=0}></BOUCLE1>i<//B1>>ce n'était qu'une balise i.</i>

Je pense légitime d’interdire ça, surtout qu’au pire on peut toujours interpoler des processing-instructions PHP pour produire autrement ces cas, si tant est qu’ils soient utiles.

Attribut et champ étendu

De manière similaire aux boucles, un champ étendu conditionnant un attribut pose un problème de conformité. Dans l’ancienne syntaxe :

<balise[ attribut="(#X)"]>

On peut prendre la même logique que pour les boucles :
<balise attribut="[(#X)]"> produirait "<balise>" si X est vide (mais l’écriture <balise attribut="#X"> avec X vide produirait <balise attribut="">).

Cinquième partie

  1. Ce qui ne va pas dans l’existant
  2. MIME et SPIP
  3. Edition des squelettes : distinguer syntaxiquement le but et le moyen
  4. Analyse XML des squelettes : XML puissance 2 ?
  5. Validation XML des squelettes : typer les inclusions, les balises et les filtres ?

Vers une méta-validation

Problématique : est-il possible de garantir qu’un squelette donné, quelles que soient les données sur lesquelles il sera appliqué, donnera un document conforme à sa DTD ?

Le préalable est de pouvoir prédire ce qui sera produit par les 3 outils insérant des portions de texte lors de l’application du squelette :

  • les filtres ;
  • les balises ;
  • les inclusions par INCLURE.

Vous avez dit balise ?

Le mot balise désigne habituellement ce qui est défini par le mot ELEMENT dans une DTD, mais SPIP l’utilise pour désigner une fonction PHP avec une interface de programmation spécifique, qui n’a rien à voir avec la notion de balisage, et si cette fonction n’existe pas cela désigne un champ SQL.

Le résulat de ces fonctions est imprévisible : texte brut (cf #TOTAL_BOUCLE), balise HTML (cf #LOGO_MOT), suite de balises (cf INSERT_HEAD), voire interpolation de PHP (les formulaires).

Solution radicale : réécrire les prétendues balises SPIP sous forme de filtres appliqués à des champs SQL ou une inclusion.

Est-ce toujours possible ?

Vous avez dit filtre ?

Les filtres de SPIP sont en fait des fonctions PHP dont le premier est écrit avant le nom de la fonction, et les éventuels suivants après.

Valider un squelette implique de disposer d’informations sur le résultat d’un filtre, notamment s’il renvoie un texte brut, une balise ou une suite de balises, et dans les deux derniers cas lesquels.

Une solution est de donner l’information dans le nom du filtre, par exemple ce qui précède le premier "_" est le nom de la balise à venir, et si _ est le premier caractère c’est du texte brut.

Est-ce toujours possible ?

Inclure quoi ?

Les inclusions de squelettes devraient également indiquer ce qui sera inséré, de nouveau en débutant le nom du squelette par la balise insérée.

On peut remarquer que les balises dynamiques nommées #FORMULAIRE_ sont en fait des inclusions observant pratiquement déjà cette directive de nommage, avec en prime la recherche du squelette limité à certains répertoires.

On pourrait donc écrire systématiquement <?spip INCLURE {head/....} ?> pour expliciter ce qui sera inséré, et où le chercher.

Conclusion

SPIP totalise aujourd’hui 40 Mo de source (dont moins de la moitié de fichiers de langue).

On ne développe pas un système de cette taille comme on développe une poignée de scripts : les exceptions et les redondances conceptuelles doivent être bannies.

Et en vidéo ça donne quoi ?

<param name="FlashVars" value="flv=http://spip.arscenic.tv/IMG/flv/ESJ_XML-encoded.flv&width=480&height=270&showstop=1&showfullscreen=1&showplayer=autohide&showloading=autohide&bgcolor=ffffff&bgcolor1=ffffff&bgcolor2=cccccc&playercolor=000000&loadingcolor=ffff00&buttoncolor=ffffff&buttonovercolor=ffff00&slidercolor1=cccccc&slidercolor2=888888&sliderovercolor=ffff00&buffercolor=ffffff&bufferbgcolor=000000&titlecolor=ffffff&buffer=5&buffershowbg=1&titlesize=12 &startimage=http://spip.arscenic.tv/IMG/jpg/73a749a21adfcda5755476e20ad038f2.jpg&ondoubleclick=fullscreen&autoplay=0" /> IMG/flv/ESJ_XML-encoded.flv