Le formalisme Relax NG
révision du 19/03/2014
- Résumé
- Utilisation
- Exemple-1
- Exemple
2 (avec namespace)
- "Expressions
de patrons" dans Relax-NG
- Combinaison
de grammaires en Relax-NG
- Deux
manières de combiner
- Exemple
: météo étendue
- Application
à la description des menus
- Exemple de réutilisation d'une
grammaire
-
- Relax NG = REgular LAnguage for XML Next
Generation
- Ref :
http://www.relaxng.org/
- Origine : le consortium OASIS (et non le W3G !)
http://www.oasis-open.org/
- But : faire aussi puissant que XML-Schéma mais plus
léger à écrire et à
lire.
Bien que l'approche consiste à définir un système de types comme en
XML-Schéma, on parle de grammaires.
En dehors des questions de pure syntaxe, la principale différence entre
Relax NG et XML-Schéma est
la distinction que fait Relax NG entre le nom donné
à une partie du
discours (élément, attribut ou composé)
et la nature exacte de cette entité : on peut ainsi
organiser
logiquement la description, d'une manière abstraite,
et préciser ensuite comment cette description doit se réaliser. Voir
les exemples qui suivent.
- Deux formats :
- syntaxe XML (extension des noms de
fchiers "
.rng
")
- syntaxe compacte (extension "
.rnc
")
- Outillage :
- parseurs-vérificateurs, : l'outil
Jing
(en Java) en plus d'une collection
de vérificateurs généralistes
à noter que le package javax.xml.validation
permet de traiter les
grammaires RNG - mais avec certaines restrictions ;
- traducteurs de grammaires d'une syntaxe dans
l'autre :
l'outil
Trang
(en Java) et
autres.
- Excellement documenté par le livre on-line
d'Eric van der Vlist
http://books.xmlschemata.org/relaxng/page2.html
-
- L'outil
xmllint
(de la
bibliothèque libxml2) permet d'utiliser aussi bien
des grammaires RNG (en format XML)
que des DTDs ou des XML-Schémas.
Pour valider monFichier.xml
avec maGrammaire.rng
,
faire :
%xmllint
monFichier.xml --relaxng
maGrammaire.rng
- L'outil
Jing
(spécifique de RelaxNG) permet d'utiliser aussi bien
des grammaires RNG en format XML
que des grammaires RNG en format compact (avec
l'option -c
).
Si le fichier XML est conforme à la grammaire, Jing
reste
silencieux - sinon, il engendre un message.
%java -jar /le-chemin-de/jing.jar maGrammaire.rng monFichier.xml
Essayez
!
%java -jar /le-chemin-de/jing.jar -c
maGrammaire.rnc
monFichier.xml
;
Essayez
!
- L'outil
Trang
(spécifique de
RelaxNG) permet de traduire des schémas (DTDs, grammaires RNC et RNG,
etc ) d'un langage dans l'autre.
Il a un excellent mode d'emploi. - mais pas d'API pour une mise en
œuvre depuis Java.
À noter que l'outil déduit le type des fichiers source et but à partir
des extensions de leurs noms :
- de RNC vers RNG :
java -jar /
le-chemin-de
/trang.jar
maGrammaire
.rnc
maGrammaire
.rng
Essayez
!
- de DTD vers RNC :
java -jar /
le-chemin-de
/trang.jar
maDTD
.dtd
maGrammaire
.rnc
Essayez
!
- de RNC vers XML-schéma :
java
-jar /
le-chemin-de
/trang.jar
maGrammaire
.rnc
monSchéma
.xsd
Essayez
!
-
reformulation en RelaxNG de la
DTD
Nom_note1.dtd
:
<?xml version="1.0"
encoding='ISO-8859-1'?>
<!-- Tableau Noms-Notes par attributs -->
<!ELEMENT liste (eleve*)>
<!ELEMENT eleve EMPTY>
<!ATTLIST eleve nom CDATA #REQUIRED>
<!ATTLIST eleve note CDATA #REQUIRED>
En syntaxe compacte : fichier Nom_note1.rnc
#Nom_note1
#---------
start = listétudiants #
point de départ indispensable
listétudiants = element liste {étudiant*}
étudiant = element eleve {nomP, noteSur20}
nomP = attribute nom { nomPers }
noteSur20 = attribute note { xsd:integer }
nomPers = xsd:string { pattern = "[A-Z][a-z]*" }
N.B. on utilise les types de données de XML-schéma, le
préfixe xsd:
est prédéfini.
Sa forme en syntaxe XML : fichier Nom_note1.rng
<?xml version="1.0" encoding="UTF-8"?>
<grammar xmlns="http://relaxng.org/ns/structure/1.0"
datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
<!--
Nom_note1
- - - - - - - - -
-->
<start>
<ref name="listétudiants"/>
</start>
<!-- point de départ indispensable -->
<define name="listétudiants">
<element name="liste">
<zeroOrMore>
<ref name="étudiant"/>
</zeroOrMore>
</element>
</define>
<define name="étudiant">
<element name="eleve">
<ref
name="nomP"/>
<ref
name="noteSur20"/>
</element>
</define>
<define name="nomP">
<attribute name="nom">
<ref
name="nomPers"/>
</attribute>
</define>
<define name="noteSur20">
<attribute name="note">
<data
type="integer"/>
</attribute>
</define>
<define name="nomPers">
<data type="string">
<param
name="pattern">[A-Z][a-z]*</param>
</data>
</define>
</grammar>
Sa traduction en XML-schéma : fichier Nom_note1.xsd
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified">
<!--
Nom_note1
- - - - - - - - -
-->
<!-- point de départ indispensable -->
<xs:element name="liste">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded"
ref="eleve"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="eleve">
<xs:complexType>
<xs:attributeGroup ref="nomP"/>
<xs:attributeGroup ref="noteSur20"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="nomP">
<xs:attribute name="nom"
use="required" type="nomPers"/>
</xs:attributeGroup>
<xs:attributeGroup name="noteSur20">
<xs:attribute name="note"
use="required" type="xs:integer"/>
</xs:attributeGroup>
<xs:simpleType name="nomPers">
<xs:restriction
base="xs:string">
<xs:pattern
value="[A-Z][a-z]*"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>
-
L'exemple météo (J. Malenfant) : fichier
meteo1.rnc
- fichier-instance obs.xml
Noter l'emploi d'un espace de noms (default namespace
)
pour le vocabulaire spécifique
(autre que element
, attribute
, text
).
default namespace = "http://www.malenfant.fr/meteo3"
start = meteo
meteo = element meteo { obs+ } # plusieurs observations
obs = element obs {
attribute num { text },
element loc { element nom { text } },
element moment { text },
element temp { attribute unit { text }, text },
element hygro { text },
element nebulo { text },
element anemo { text },
element pluvio { text },
element message { attribute langue { text }?, text
}?
# message et langue
optionnels
}
version "linéarisée" : fichier meteo2.rnc
-
fichier-instance idem obs.xml
default namespace = "http://www.malenfant.fr/meteo3"
start = meteo
meteo = element meteo { obs+ }
obs = element obs { identification, mesures, message? }
identification = attribute num { text }, loc, moment
loc = element loc { nom }
nom = element nom { text }
moment = element moment { text }
mesures = temp, hygro, nebulo, anemo, pluvio
temp = element temp { attribute unit { text }, text }
hygro = element hygro { text }
nebulo = element nebulo { text }
anemo = element anemo { text }
pluvio = element pluvio { text }
message = element message { langue?, text }
langue = attribute langue { text }
version perfectionnée : fichier meteoP.rnc
-
fichier-instance idem obsP.xml
avec deux espaces de noms pour illustrer la syntaxe :
default namespace = "http://www.malenfant.fr/meteo3"
namespace cat = "http://www.malenfant.fr/catalogue"
start = meteo
meteo = element meteo { obs+ }
obs = element obs {
identification, mesures, message?
}
identification =
attribute num {
xsd:NMTOKEN {
pattern =
"[A-Z]{2,2}\d+V\d{3,3}"
}
},
loc, moment
loc = element loc { localisation }
localisation = element nom { text }
moment = element moment { xsd:dateTime }
mesures =
(attribute type { xsd:NMTOKEN "mobile" },
temp, anemo)
|
(attribute type { xsd:NMTOKEN "fixe" },
temp, hygro, nebulo, anemo,
pluvio)
temp = element temp {
attribute unit { "celsius" | "farenheit" },
xsd:decimal
}
hygro = element cat:hygro { xsd:decimal }
nebulo = element cat:nebulo { xsd:decimal }
anemo = element cat:anemo { xsd:decimal }
pluvio = element cat:pluvio { xsd:decimal }
message = element message { langue?, string }
langue = attribute langue { xsd:language }
Voyez comment les espaces de noms sont traduits dans la forme XML de la grammaire.
-
Toute la supériorité de Relax-NG réside dans la notion (apparemment
cachée) d'expression à valeur de pattern,
qui
permet d'écrire sans se tromper en utilisant les mécanismes habituels
de l'écriture algébrique :
peut-on parler de "transparence
référentielle" ?
Exemple tiré de meteoP.rnc
(attribute type {
"mobile"
}, temp,
anemo)
|
(attribute type { "fixe" },temp, hygro,
nebulo, anemo, pluvio)
Un "patron" (pattern) est bel et bien la valeur
d'une expression, dont
le langage comporte :
- des constantes nommées :
text
, empty
,
string
, token
N.B. token
->chaîne normalisée
(blancs etc. réduits à un seul)
string
,
->chaîne non normalisée
- des valeurs de base : chaînes explicites
Ex. attribute unit {
"celsius" |
"fahrenheit" }
- des variables : les noms de patrons,
introduits par des définitions de la
forme "<nom> = <expression>
"
ces définitions pouvant être récursives
: par exemple
categorie = element
categorie {
element
titre { text },
categorie*, livre*
}
- des constructeurs :
element <nom> {
<expression> }
attribute <nom> {
<expression> }
list { <expression> }
- des opérateurs :
- unaires postfixés : "
*
", "+
"
et "?
" (itération, itération stricte, option)
- binaires infixés : "
,
"
"&
" et "|
"
(séquence, ordre qcque, choix)
- sans
précédence, notation explicitement parenthésée (comme
dans les DTDs)
- des restrictions
sur les types de données
Ex : Considérons la définition d’un nœud dans un arbre binaire. Un
nœud
binaire peut contenir soit deux nœuds fils, soit une feuille de contenu
vide :
nœud = element nœud {
(nœud,
nœud) | feuille { empty }
}
-
-
Différencce entre
- la "référence exerne" par "
external
<URL>
"
- et l'inclusion de grammaire par "
include
<URL>
"
La référence externe délègue une partie du filtrage à la grammaire
référencée (qui est donc une grammaire complète, avec start
)
l'inclusion importe toute la mécanique (y compris les sous-structures)
pour servir dans la grammaire incluante,
elle peut se faire avec ou sans redéfinition.
-
On introduit une notion de coordonnées pour repérer les observations
mobiles - fichier-instance
obsC.xml
fichier coordonnees.rnc
(fragment de grammaire, sans start
)
coordonnees = element coordonnees {
element latitude {
attribute direction {
xsd:NMTOKEN "nord" | xsd:NMTOKEN "sud" },
position },
element longitude {
attribute direction {
xsd:NMTOKEN "est" | xsd:NMTOKEN "ouest" },
position
}
}
position =
element d { xsd:unsignedShort },
element m { xsd:unsignedShort },
element s { xsd:unsignedShort }
fichier meteoC.rnc
include "coordonnees.rnc" #
première
inclusion,
sans
rédéfinition
include "meteo3.rnc" { meteo = element meteo { intitules?, obs+ } }
#
seconde inclusion, avec
redéfinition
- en outre, elle fournit l'élément start
intitulés = element intitulés { # pour produire des tableaux
langue?,
(element int-loc { text } & element
int-moment { text } &
element int-temp { text } & element
int-hygro { text } &
element int-nebulo { text } & element
int-anemo { text } &
element int-pluvio { text } & element
int-message { text })
}
localisation |= coordonnees #
combinaison par choix
-
Les menus en question ont déjà été vus.
On souhaite en donner une description aussi précise que possible...
- Menus simples, sans prix - fichier
menu.rnc
,
exemple menu.xml
start = menu
menu = element liste { apéritif, entrée, plat, dessert, fin }
suite = annonce, choix*
annonce = element annonce { text }
choix = element choix { text }
apéritif = element étape { attribute nom { "apéritif" }, suite}
entrée = element étape { attribute nom { "entrée" }, suite}
plat = element étape { attribute nom { "plat" }, suite}
dessert = element étape { attribute nom { "dessert" }, suite}
fin = element étape { attribute nom { "fin" }, annonce}
- Menus avec prix - fichier
menuP.rnc
,
exemple menuP.xml
On souhaite exprimer le plus simplement possible qu'un menu avec
prix,
c'est tout bonnement un menu
dont les choix
portent un attribut "prix
".
include "menu.rnc" {choix = element
choix {prix & text }}
prix = attribute prix { xsd:unsignedShort }
-
À partir d'un
ensemble de fichiers de même structure (noms & notes, avec
indication de la matière visée)
on construit une fichier collectif. La grammaire de la collection passe
la main à celle des fichiers de données...
Grammaire de base (Nom_note1.rnc
)
#Nom_note
en format 1 (nom et note sont des attributs)
#-----------------------------------------------------
start = listétudiants #point de départ indispensable
#organisation générale
listétudiants = element liste {matière, étudiant*}
étudiant = element eleve {sonNom, saNote}
matière = element matière { nomMat }
nomMat = xsd:string { pattern =
"Chimie|Informatique|Physique|Mathématiques"}
# détails de réalisation
sonNom = attribute nom { nomPers }
saNote = attribute note { noteSur20 }
noteSur20 = xsd:integer { minInclusive = "0" maxInclusive = "20" }
nomPers = xsd:string { pattern = "[A-Z][a-z]*(-[A-Z][a-z]*)?" }
Grammaire collective
#Collection
sur plusieurs matières de
#Nom_note en format 1 (nom et note sont des attributs)
#-----------------------------------------------------
start = listeMatières
listeMatières = element lesMatières { listétudiants+ }
listétudiants = external "Nom_note1.rnc"
et en forme XML
<?xml
version="1.0" encoding="UTF-8"?>
<grammar xmlns="http://relaxng.org/ns/structure/1.0">
<!--
Collection sur plusieurs matières de
Nom_note en format 1 (nom et note sont des attributs)
- - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
-->
<start>
<ref name="listeMatières"/>
</start>
<define name="listeMatières">
<element name="lesMatières">
<oneOrMore>
<ref
name="listétudiants"/>
</oneOrMore>
</element>
</define>
<define name="listétudiants">
<externalRef href="Nom_note1.rng"/>
</define>
</grammar>
Malheureusement, xmllint
part dans une boucle infinie...
- mais ça marche avec jing
!
Peut-être avec rnv
?