Le formalisme Relax NG

Jean-François Perrot

révision du 19/03/2014
    1. Résumé
    2. Utilisation
    3. Exemple-1
    4. Exemple 2 (avec namespace)

    5. "Expressions de patrons" dans Relax-NG

    6. Combinaison de grammaires en Relax-NG
      1. Deux manières de combiner
      2. Exemple : météo étendue 
      3. Application à la description des menus
      4. Exemple de réutilisation d'une grammaire

  1. Résumé


  2. Utilisation


  3. Exemple-1

    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>



  4. Exemple 2

    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 elementattribute, 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.
  5. "Expressions de patrons" dans Relax-NG


    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 :

    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 }
        }

  6. Combinaison de grammaires en Relax-NG

    1. Deux manières de combiner

      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.

    2. Exemple : météo étendue 

      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


    3. Application à la description des menus

      Les menus en question ont déjà été vus
      On souhaite en donner une description aussi précise que possible...

      1. 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}



      2. 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 }



    4. Exemple de réutilisation d'une grammaire

      À 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 ?