XML

Qu'est-ce que c'est ?
XML est une manière de structure de l'information textuelle, de manière indépendante de sa présentation typographique. Par exemple, en XML, un article ce sera un titre, un auteur, une date, des chapitres, des section à l'intérieur des chapitres, des paragraphes à l'intérieur des sections, etc.
XML laisse à d'autres programmes le soin de manipuler ces donnéer pour les mettre en forme (dans le cas d'un article, on pourra se retrouver avec un fichier texte, PostScript, PDF, RTF, etc.) ou les réordonnées (par exemple changer l'ordre des entrées dans une base de données bibliographiques).
On voit parfois les documents XML comme des « bases de données », mais il s'agirait de bases de données non optimisées pour la recherche d'information (contrairement à LDAP), non optimisées pour la modification des entrées (contrairement à toute base de donnée qui se respecte), mais juste optimisée pour la lecture dans l'ordre et en entier de tous les enregistrements. C'est très bien si on veut transférer une base de données _entière_, mais c'est tout.

Exemples d'utilisation
C'est le format utilisé par certains logiciels pour sauvegarder leur données : dia (logiciel de dessin) ou gnumeric (tableur).
C'est le format utilisé dans le fichier de configuration de certains logiciels, par exemple l'Interface Utilisateur (UI) de Mozilla.
C'est un choix judicieux pour échanger des données (arbitrairement complexes) entre différents processus, différentes machines : XML-RPC est un équivalent de CORBA en XML.
C'est aussi le format de certains fichiers liés au Web : XHTML (successeur(?) de HTML), SVG (dessins vectoriels).
C'est aussi le format que pourrait avoir toute l'information qui se trouve dans /etc/proc.
C'est aussi le format sous lequel on peut récupérer les données financières sur finance.yahoo.com.

Architecture d'un fichier XML
La première ligne sera
  <?xml version="1.0" encoding="iso-8859-1" ?>
Un document de type « toto » aura la forme
  <?xml version="1.0" encoding="iso-8859-1" ?>
  <toto>
    ...
  </toto>
Il n'y a donc qu'un seul élément racine. Si on veut écrire une base de données bibliographiques, il faudra englober toutes les entrées dans une même balise.
  <?xml version="1.0" encoding="iso-8859-1" ?>
  <bib>
    <bibentry>...</bibentry>
    <bibentry>...</bibentry>
    <bibentry>...</bibentry>
    <bibentry>...</bibentry>
    ...
  </bib>

Attributs et données
Voici deux manières de représenter de l'information : comme du texte entre les balises
  <person firstname="John" lastname="Smith"/>
ou comme des attributs à l'intérieur des balises
  <person>
    <firstname>John</firstname>
    <lastname>Smith</lastname>
  </person>
Il y a des partisants de l'un et de l'autre, je ne prends pas position.

DTD
Un DTD est un document décrivant la structure d'un document XML (ou SGML). Il n'est pas absolument nécessaire en XML : si on a un document XML sans DTD, on peut quand même vérifier qu'il est bien formé, ie, que les balises fermantes et ouvrantes se correspondent ; si on a en plus un DTD, on peut vérifier que les choses sont dans le bon ordre et qu'il n'en manque pas --- c'est plus propre.
Si le DTD est dans un fichier web.dtd, dans le répertoire courrant, le fichier XML commencera ainsi.
  <?xml version="1.0" encoding="iso-8859-1" ?>
  <!DOCTYPE web SYSTEM "web.dtd">
Un éditeur de texte comme Emacs est capable de lire un DTD et de dire quelles sont les balises à utiliser.
Si l'on reprend l'exemple précédent d'un article (c'est un DTD SGML, il n'y a quelques différences en XML) :
  <!ENTITY % text "#PCDATA|img|ref|label|a|link|itemize|enum|descrip|verb|perl|tt|em|html|latex">
  <!ENTITY % simpletext "#PCDATA|tt|em">

  <!ELEMENT document  - - (head,body)>
  <!ELEMENT head      O O (title, author?, date?)>
  <!ELEMENT title     - O (#PCDATA)>
  <!ELEMENT author    - O (#PCDATA)>
  <!ELEMENT date      - O (#PCDATA)>
  <!ELEMENT body      O O (abstract?,toc?,sect*)>
  <!ELEMENT abstract  - O (p+)>
  <!ELEMENT toc       - O EMPTY>
  <!ELEMENT sect      - O (stitle, (p|sect1)+)>
  <!ELEMENT sect1     - O (stitle, (p|sect2)+)>
  <!ELEMENT sect2     - O (stitle, p+)>
  <!ELEMENT stitle    O O (%simpletext)+>
  <!ELEMENT p         O O (%text)+>

  <!ELEMENT tt        - - (#PCDATA)>
  <!ELEMENT em        - - (#PCDATA)>

  <!ELEMENT html      - O EMPTY>
  <!ATTLIST html
            src       CDATA   #REQUIRED>
  <!ELEMENT latex     - O EMPTY>
  <!ATTLIST latex
            src       CDATA   #REQUIRED>

  <!ELEMENT img       - O EMPTY>
  <!ATTLIST img 
            src       CDATA   #REQUIRED>

  <!ELEMENT a         - - (%simpletext)+>
  <!ATTLIST a
            href      CDATA   #REQUIRED>
  <!ELEMENT link      - - (linktext, href+)>
  <!ELEMENT href      - - (#PCDATA)>
  <!ELEMENT linktext  - - (%simpletext)+>

  <!ATTLIST sect
            id        ID      #IMPLIED>
  <!ATTLIST sect1
            id        ID      #IMPLIED>
  <!ATTLIST sect2
            id        ID      #IMPLIED>
  <!ELEMENT label     - O  EMPTY>
  <!ATTLIST label
            id        ID      #REQUIRED>
  <!ELEMENT ref       - O  EMPTY>
  <!ATTLIST ref
            id        IDREF   #REQUIRED>

  <!ELEMENT itemize   - - (item+)>
  <!ELEMENT enum      - - (item+)>
  <!ELEMENT item      - O (p+)>
  
  <!ELEMENT descrip   - - (tag,p+)+>
  <!ELEMENT tag       - - (%simpletext)+> 

  <!ELEMENT verb      - - (#PCDATA)>
  <!ATTLIST verb
            lang      (tex|perl|shell) tex>

  <!ENTITY  lt        "<">
  <!ENTITY  gt        ">">
  <!ENTITY  amp       "&">
  <!ENTITY  dollar    "$">
La principale différence avec SGML, c'est que les « - - », « - O », « O - » ou « O O » ne servent plus à rien. En XML, il faut les enlever.
L'élément racine porte impérativement le même nom que le DTD.
La ligne suivante signifie que toto est constitué de A puis de B.
  <!ELEMENT toto      (A,B)>
La ligne suivante signifie que toto est constitué soit de A, soit de B.
  <!ELEMENT toto      (A|B)>
La ligne suivante signifie que A et B doivent apparaitre, mais l'ordre importe peu.
  <!ELEMENT toto      (A & B)>
C'est équivalent à la ligne suivante.
  <!ELEMENT toto      ((A,B)|(B,A))>
La ligne suivante signifie qu'un en-tête d'article est constitué d'un titre, d'un auteur et éventiellement d'une date.
  <!ELEMENT head (title, author, date?)>
La ligne suivante signifie que le texte est contitué d'un ou plusieurs paragraphes.
  <!ELEMENT texte (paragraphe+)>
La ligne suivante signifie qu'un paragraphe est contitué de texte brut, sans aucune balise (le texte brut s'appelle « #PCDATA »).
  <!ELEMENT paragraphe (#PCDATA)>
La ligne suivante dit que le corps de l'article est constitué d'un éventuel résumé, d'une éventuelle table des matières, et d'éventuelles sections.
  <!ELEMENT body      (abstract?,toc?,sect*)>
La ligne suivante dit que la balise « toc » est vide,
  <!ELEMENT toc       EMPTY>
ie, qu'elle s'utilise comme
  <toc/>
charge au logiciel de conversion XML --> PostScript de calculer la table des matières et de l'imprimer à cet endroit-là.
Certains éléments peuvent avoir des attributs obligatoires
  <!ELEMENT a         (#PCDATA)>
  <!ATTLIST a
            href      CDATA   #REQUIRED>
des attributs facultatifs
  <!ELEMENT sect      (#PCDATA)>
  <!ATTLIST sect
            id        ID      #IMPLIED>
des attributs prenant une valeur dans une liste
  <!ELEMENT verb      (#PCDATA)>
  <!ATTLIST verb
            lang      (tex|perl|shell) #REQUIRED>
On peut même préciser une valeur par défaut.
  <!ELEMENT verb      (#PCDATA)>
  <!ATTLIST verb
            lang      (tex|perl|shell) tex>

Schéma
Le problème avec les DTD, c'est qu'ils ne sont pas en XML. Les schémas sont donc un remplacement des DTD, en XML.
Pour l'instant, ils ne sont pas encore vraiment utilisés, car pas vraiment normalisés.
  http://www.w3.org/XML/Schema
À titre purement indicatif, ça ressemble à ça :
  <?xml version="1.0"?>
  <xsd:schema xmlns:xsd="http://www.w3.org/2000/10/XMLSchema"
          targetNamespace="http://www.publishing.org"
          xmlns="http://www.publishing.org"
          elementFormDefault="qualified">
      <xsd:element name="BookCatalogue">
          <xsd:complexType>
              <xsd:sequence>
                  <xsd:element ref="Book" minOccurs="0" maxOccurs="unbounded"/>
              </xsd:sequence>
          </xsd:complexType>
      </xsd:element>
      <xsd:element name="Book">
          <xsd:complexType>
              <xsd:sequence>
                  <xsd:element ref="Title" minOccurs="1" maxOccurs="1"/>
                  <xsd:element ref="Author" minOccurs="1" maxOccurs="1"/>
                  <xsd:element ref="Date" minOccurs="1" maxOccurs="1"/>
                  <xsd:element ref="ISBN" minOccurs="1" maxOccurs="1"/>
                  <xsd:element ref="Publisher" minOccurs="1" maxOccurs="1"/>
              </xsd:sequence>
          </xsd:complexType>
      </xsd:element>
      <xsd:element name="Title" type="xsd:string"/>
      <xsd:element name="Author" type="xsd:string"/>
      <xsd:element name="Date" type="xsd:string"/>
      <xsd:element name="ISBN" type="xsd:string"/>
      <xsd:element name="Publisher" type="xsd:string"/>
  </xsd:schema>

Namespaces (espace de nommage)
Il est parfois assez compliqué d'écrire soi-même son DTD : heureusement, il est possible de reprendre des morceaux de DTD un peu partout et de les recoller. La notion d'espace de nommage permet d'éviter que ces différents morceaux ne se chevauchent.
Chaque espace de nommage est définie par un URI (c'est comme un URL, mais c'est juste l'adresse, qu'il y ait ou non un contenu à l'endroit indiqué). On précise cet URI comme attibut « xmlns:toto » de l'élément racine (ou d'un autre élément, mais ce n'est pas très propre). Ici, « toto » est ne nom de cet espace de nommage. Tous les éléments commençant par « toto: » s'y réfèreront.
  <?xml version="1.0" ?>
  <diagram xmlns:dia="http://www.lysator.liu.se/~alla/dia">
  ...
  </diagram>
Voici un autre exemple avec SVG
  <?xml version="1.0"?>

  <shape xmlns="http://www.daa.com.au/~james/dia-shape-ns"
         xmlns:svg="http://www.w3.org/TR/2000/03/WD-SVG-20000303/DTD/svg-20000303-stylable.dtd">
    <name>Circuit - Ground</name>
    <description>A ground point</description>
    <icon>ground.xpm</icon>
    <connections>
      <point x="0" y="0"/>
    </connections>
    <svg:svg width="3.0" height="3.0">
      <svg:line x1="0" y1="0" x2="0" y2="1" />
      <svg:line x1="-2" y1="1" x2="2" y2="1" />
      <svg:line x1="-1.33" y1="1.5" x2="1.33" y2="1.5" />
      <svg:line x1="-0.66" y1="2" x2="0.66" y2="2" />
    </svg:svg>
  </shape>
Il est possible de préciser plusieurs espaces de nommage
  <?xml version="1.0"?>
   <!-- both namespace prefixes are available throughout -->
   <bk:book xmlns:bk='urn:loc.gov:books'
            xmlns:isbn='urn:ISBN:0-395-36341-6'>
       <bk:title>Cheaper by the Dozen</bk:title>
       <isbn:number>1568491379</isbn:number>
   </bk:book>
On peut aussi avoir des espaces de nommage par défaut
  <?xml version="1.0"?>
  <!-- elements are in the HTML namespace, in this case by default -->
  <html xmlns='http://www.w3.org/TR/REC-html40'>
    <head><title>Frobnostication</title></head>
    <body><p>Moved to 
      <a href='http://frob.com'>here</a>.</p></body>
  </html>

  <?xml version="1.0"?>
  <!-- unprefixed element types are from "books" -->
  <book xmlns='urn:loc.gov:books'
        xmlns:isbn='urn:ISBN:0-395-36341-6'>
      <title>Cheaper by the Dozen</title>
      <isbn:number>1568491379</isbn:number>
  </book>

Docbook
Docbook est un DTD généraliste, conçu pour à peu près tous types de documents (article, manuel, livre, etc.)
Un document Docbook a la tête suivante :
  <?xml version="1.0"?>
  <article>
    <title>...</title>
    <artheader>
      <abstract>...</abstract>
      <author>
        <firstname>...</firstname>
        <surname>...</surname>
      </author>
      <date>...</date>
    </artheader>
    <section>
      <title>...</title>
      <para>...</para>
    ...
Il y a un environement « example »
  <example>
    <title>...</title>
    <programlisting>
      ...
    </programlisting>
  </example>
Il y a des énumérations
  <itemizedlist>
    <listitem><para>...</para></listitem>
    <listitem><para>...</para></listitem>
    <listitem><para>...</para></listitem>
  </itemizedlist>
Il est aussi possible de mettre des images
  <mediaobject>
    <imageobject>
      <imagedata fileref="1.gif" format="gif"/>
    </imageobject>
    <textobject>
      <para>If it were possible, there would be an image here</para>
    </textobject>
  </mediaobject>
Voici d'autres petits morceaux de document Docbook
  <email>...</email>
  <abbrev>...</abbrev>
  <acronym>...</acronym>
  <keyword>...</keyword>
L'intérêt de Docbook est qu'il existe des outils tout prêts (openjade) pour convertir un tel document en texte, postscript, HTML, etc. L'inconvénient est que l'on ne peut que très superficiellement paramétrer cette conversion (on le fait à l'aide de feuilles de styles, ou avec un truc du genre DSSSL). Et si on veut écrire soi-même son outil de conversion, la complexité du DTD rend cette tâche très longue.

Perl et XML
On a besoin de :
  http://sourceforge.net/projects/expat/
  XML-Parser
  XML-DOM
  libxml
  XML-LibXSLT
Installer expat :
  ./configure --prefix=$HOME/gnu_tmp/`uname`
  make
  make install
Installer XML-Parser :
  perl Makefile.PL EXPATLIBPATH=$HOME/gnu_tmp/`uname`/lib EXPATINCPATH=$HOME/gnu_tmp/`uname`/include
  make
  make test
  make install
Installer XML-DOM :
  perl Makefile.PL
  make
  make test
  (PROBLEMES --- visiblement sans trop de conséquences)
  make install
Installer libxml :
  perl Makefile.PL
  make
  make test
  make install
Je n'ai pas regardé XML-LibXSLT.
  checking for main() in -lxslt... no
  libxslt not found
  Try setting LIBS and INC values on the command line
  Or get libxslt and libxml2 from 
    http://www.libxml.org/
Pour la documentation :
  perldoc XML::Parser::PerlSAX
  PerlSAX.pod  
  perldoc XML::DOM
  http://www.perlxml.com/faq/perl-xml-faq.html
  http://www.xml.com/

Manipulation d'un fichier XML : SAX
Il suffit de préciser ce que l'on fait quand on rencontre une balise ouvrante ou une balise fermante. C'est très limité, mais très simple. À utiliser quand on n'a pas besoin de connaître l'ensemble du document, par exemple lors d'une traduction XML --> HTML, en absence de références ou de table des matières.
  #! perl -w

  package MyHandler;
  use strict;
  sub new { my $type = shift; return bless {}, $type; }
  sub start_element { my $self=shift; my $el=shift; print "Start $el->{Name}\n"; }
  sub end_element { my $self=shift; my $el=shift; print "End $el->{Name}\n"; }

  package main;
  use strict;
  use XML::Parser::PerlSAX;
  import MyHandler;
  my $my_handler = MyHandler->new;
  my $parser = XML::Parser::PerlSAX->new( Handler => $my_handler );
  die "I need an XML file as argument" unless $ARGV[0];
  $parser->parse( Source => { SystemId => $ARGV[0] } );
On peut aussi récupérer les attributs.
#! perl -w
  package MyHandler;
  use strict;
  sub new { my $type = shift; return bless {}, $type; }
  sub start_element {
    my ($self, $el) = @_;
    print "Start $el->{Name}\n";
    my %a = %{ $el->{Attributes} };
    foreach (keys %a) {
      print "  Attribut $_ => $a{$_}\n";
    }
  }
  sub end_element { my $self=shift; my $el=shift; print "End $el->{Name}\n"; }

  package main;
  use strict;
  use XML::Parser::PerlSAX;
  import MyHandler;
  my $my_handler = MyHandler->new;
  my $parser = XML::Parser::PerlSAX->new( Handler => $my_handler );
  die "I need an XML file as argument" unless $ARGV[0];
  $parser->parse( Source => { SystemId => $ARGV[0] } );
On peut ainsi écrire un programme qui réécrive un document XML.
  #! perl -w

  package MyHandler;
  use strict;
  sub new { my $type = shift; return bless {}, $type; }
  sub start_element {
    my ($self, $el) = @_;
    print "<$el->{Name}";
    my %a = %{ $el->{Attributes} };
    foreach (keys %a) {
      print " $_=\"$a{$_}\"";
    }
    print ">";
  }
  sub end_element { my $self=shift; my $el=shift; print "</$el->{Name}>"; }
  sub characters { my ($self,$a) = @_; print $a->{Data}; }
  
  package main;
  use strict;
  use XML::Parser::PerlSAX;
  import MyHandler;
  my $my_handler = MyHandler->new;
  my $parser = XML::Parser::PerlSAX->new( Handler => $my_handler );
  die "I need an XML file as argument" unless $ARGV[0];
  $parser->parse( Source => { SystemId => $ARGV[0] } );
Quant au DTD, comme on utilise expat (qui ne lit pas le DTD), on n'y a pas accès.

Manipulation d'un fichier XML : DOM
Avec DOM, on accède au document XML au travers d'une API spécifique, permettant de parcourrir l'arbre dans l'ordre dans lequel on veut ou de rechercher certains éléments. On commence ainsi :
  #! perl -w

  use strict;
  use XML::DOM;
  my $parser = new XML::DOM::Parser;
  die "I need an XML file as first argument" unless $ARGV[0];
  my $doc = $parser->parsefile ($ARGV[0]);
Le code suivant parcourt l'arbre, récursivement.
  #! perl -w
  
  use strict;
  use XML::DOM;
  my $parser = new XML::DOM::Parser;
  die "I need an XML file as first argument" unless $ARGV[0];
  my $doc = $parser->parsefile ($ARGV[0]);
  
  sub parcours {
    my $node = shift;
    print "Start Node ". $node->getNodeName ."\n";
    my $att = $node->getAttributes;
    if( defined $att ){
      my $n = $att->getLength;
      for(my $i=0; $i<$n; $i++){
        my $a = $att->item($i);
        print "  Attribute $a->{Name} => ". $a->getValue ."\n";
      }
    }
    if( $node->getNodeName eq "#text" ){
      print "  Value: ". $node->getData ."\n";
    }
    for my $kid ($node->getChildNodes) { parcours($kid); }
    print "End Node ". $node->getNodeName ."\n";
  }
  
  # On commence avec l'élément racine
  parcours( $doc->getDocumentElement );
Le code suivant fait de même, en réimprimant (à la main, sans la commande printToFile), le fichier XML.
  #! perl -w
  
  use strict;
  use XML::DOM;
  my $parser = new XML::DOM::Parser;
  die "I need an XML file as first argument" unless $ARGV[0];
  my $doc = $parser->parsefile ($ARGV[0]);
  
  sub parcours {
    my $node = shift;
    if( $node->getNodeName eq "#text" ){ print $node->getData; }
    else {
      print "<". $node->getNodeName;
      my $att = $node->getAttributes;
      if( defined $att ){
        my $n = $att->getLength;
        for(my $i=0; $i<$n; $i++){
          my $a = $att->item($i);
          print " $a->{Name}=\"". $a->getValue ."\"";
        }
      }
      print ">";
      for my $kid ($node->getChildNodes) { parcours($kid); }
      print "</". $node->getNodeName .">";
    }
  }
  
  # On commence avec l'élément racine
  print "<?xml version=\"1.0\"?>\n";
  parcours( $doc->getDocumentElement );
  print "\n";
Il est aussi possible de parcourrir l'arbre sans récursivité, car chaque noeud sait quel est son père et s'il est un dernier enfant. Nous laissons cela en exercice pour le lecteur.
Il est aussi possible de faire des recherches (par exemple pour créer une table des matières).
  getElementsByTagName (name [, recurse])
Il est aussi possible de modifier le document XML, à l'aide de commandes du genre
  setNodeValue
  insertBefore
  replaceChild
  removeChild
  appendChild
  printToFile (filename)

Manipulation d'un fichier XML : Grove
Permet de transformer un document XML en une table de hachage Perl.

Manipulation d'un fichier XML : XML::Simple
Comparable à SAX

Manipulation d'un fichier XML : XML::Twig
Mélange de SAX et DOM

Manipulation d'un fichier XML : XSLT
Feuilles de style, très limité.

Exemple
Le document que vous lisez a été réalisé ainsi. Je pars d'un fichier texte, qui ressemble à cela
  Titre

  * Introduction

  Bla bla bla

  * Début 

  Voici un peu de code

    #! perl -w
    use strict;
    ...

  * Suite

    Voir aussi 

      http://www.w3.org/XML/Schema
Je le convertis en un fichier XML, conforme au DTD
  <!ENTITY % text       "#PCDATA|a|tt|em|ul|code|table|img">
  <!ENTITY % simpletext "#PCDATA|a|tt|em">
  
  <!ELEMENT web        (title,imagetitle?,date?,toc?,(p|h1|code|table|img)*)>
  <!ELEMENT date       (%simpletext)>
  <!ELEMENT title      (%simpletext)>
  <!ELEMENT imagetitle EMPTY>
  <!ATTLIST imagetitle
            src        CDATA #REQUIRED>
  <!ELEMENT toc        EMPTY>
  <!ELEMENT p          (%text)>
  <!ELEMENT h1         (%simpletext)>
  
  <!ELEMENT img        EMPTY>
  <!ATTLIST img 
            src        CDATA #REQUIRED
            alt        CDATA #REQUIRED>
  
  <!ELEMENT tt         (%simpletext)>
  <!ELEMENT em         (%simpletext)>
  <!ELEMENT a          (%simpletext)+>
  <!ATTLIST a
            href       CDATA #REQUIRED>
  
  <!ELEMENT ul         (li+)>
  <!ELEMENT li         (p+)>
  
  <!ELEMENT code       (#PCDATA)>
  
  <!ENTITY  lt        "<">
  <!ENTITY  gt        ">">
  <!ENTITY  amp       "&">
  <!ENTITY  dollar    "$">
à l'aide du script suivant
  #! perl -w
  use strict;
  
  use constant TRUE  => (0==0);
  use constant FALSE => (0==1);
  
  use constant NONE    => 0;
  use constant TEXT    => 1;
  use constant CODE    => 2;
  use constant SECTION => 3;
  use constant TITLE   => 4;
  
  our $type = TITLE;
  
  our $result = "";
  sub affiche { foreach my $a (@_){ $result .= $a } }
  
  affiche "<?xml version=\"1.0\" encoding=\"iso-8859-1\" ?>
  <web>
    <head>
      <title>";
  
  while(<>){
    # Une ligne blanche termine le titre du document et des sections
    # Sinon, elle est juste ignorée.
    if( m/^\s*$/ ){
      print STDERR "Empty line: $_";
      if( $type == TITLE ){
        affiche "</title></head>\n";
      } elsif($type == SECTION ){
        affiche "</h1>";
      } elsif($type == TEXT ){
        affiche "</p>";
      } 
      $type = NONE unless $type == CODE;
    }
    # Ligne qui commence par « = »
    elsif( s/^\=\s*(.*)// ){
      print STDERR "Image: $1\n";
      if( $type == NONE ){
        affiche "<img src=\"$1\"/>";
      } elsif( $type == CODE ){
        affiche "</code>";
        $type = NONE;
        affiche "<img src=\"$1\"/>";
      } else {
        die "Missing empty line?";
      }
    }
    # Une ligne (non blanche) qui commence par deux espaces
    elsif( m/^\s\s/ ){
      print STDERR "Code:       $_";
      if( $type == NONE ){
        affiche "<code>\n";
      } elsif( $type != CODE ){
        die "Missing empty line?\n";
      }
      $type = CODE;
    }
    # Une ligne qui commence par *
    elsif( s/^\*\s*// ){
      print STDERR "Section:    $_";
      if( $type == CODE ){
        affiche "</code>\n\n<h1>";
      } elsif( $type == NONE ){
        affiche "<h1>";
      } else { die "Missing empty line ?" }
      $type = SECTION;
    }
    # Une ligne commençant par autre chose qu'un espace : texte.
    elsif( m/^[^\s]/ and ($type != TITLE) ){
      print STDERR "Text:       $_";
      if( $type == CODE ){
        affiche "</code><p>\n";
      } elsif( $type == NONE ){
        affiche "<p>\n";
      } elsif( $type != TEXT ){
        die "Missing empty line?";
      }
      $type = TEXT;
    }
  
    s/\&/\&amp\;/g;
    s/\</\&lt\;/g;
    affiche $_;
  }
  
  if( $type == TEXT ){
    affiche "</p>";
  } elsif( $type == CODE ){
    affiche "</code>";
  } elsif( $type == SECTION ){
    affiche "</h1>";
  } elsif( $type == TITLE ){
    affiche "</title></head>";
  }
  affiche "</web>\n";
  
  ######################################################################
  
  ## On nettoie un peu tout cela
  
  $result =~ s#\s*\<\/code\>#</code>#gsm;
  $result =~ s#\s*\<\/h1\>#</h1>#gsm;
  $result =~ s#\s*\<\/title\>#</title>#gsm;
  
  ######################################################################
  
  print $result;
Le résultat est alors convertit en HTML à l'aide de ce script :
  #!/share/nfs/users1/umr-tge/zoonek/gnu/Linux/bin/perl -w
  
  # xml2html.pl
  # Version 0.02
  # (c) 2001 Vincent Zoonekynd <zoonek@math.jussieu.fr>
  # Distributed under the GPL
  
  use strict;
  our @toc;
  our $result="";
  
  package TOCHandler;
  use strict;
  use constant TRUE  => (0==0);
  use constant FALSE => (0==1);
  my $remember = FALSE;
  my $string;
  my $section_number = 0;
  sub new { my $type = shift; return bless {}, $type; }
  sub characters { 
    my ($self,$a) = @_;
    my $b = $a->{Data};
    $string .= $b;
  }
  sub start_element {
    my ($self, $el) = @_;
    if($el->{Name} eq "h1"){
      $remember = TRUE;
      $string = "";
    }
  }
  sub end_element {
    my($self, $el) = @_;
    if($el->{Name} eq "h1") {
      $remember = FALSE;
      $section_number++;
      push @toc, [$section_number, $string];
      $string = "";
    }
  }
  
  ######################################################################
  
  package MyHandler;
  use strict;
  sub new { my $type = shift; return bless {}, $type; }
  
  ## Constantes
  use constant TRUE  => (0==0);
  use constant FALSE => (0==1);
  
  ## Variables globales
  our $save_text = FALSE;
  our $saved_text;
  our $inside_p = 0;
  our $section_number=0;
  
  our $charset = "iso-8859-1" || "UTF-8" || "ISO-2022-JP";
  
  our $bgcolor = '#FFFFFF';
  our $text    = '#000000';
  our $alink   = '#FFFFFF';
  our $link    = '#6D8ADA';
  our $vlink   = '#415383';
  
  our $title_bgcolor   = '#ffdb43';
  our $title_fgcolor   = $text;
  
  our $section_bgcolor = '#6D8ADA';
  our $section_fgcolor = '#FFFFFF';
  
  our $code_bgcolor = '#FFFFAA';
  our $code_fgcolor = $text;
  
  our $tailer_fgcolor = '#c8c8c8';
  
  our $author = "Vincent Zoonekynd";
  our $web = "http://www.math.jussieu.fr/~zoonek/";
  our $mail = 'zoonek@math.jussieu.fr';
  our $title;
  our $imagetitle;
  our $date;
  our $keywords;
  
  ## Affichage (ou sauvegarde) du texte
  sub affiche {
    my $a = shift;
    if($save_text){ $saved_text .= $a }
    else{ $result .= $a }
  }
  sub debug { my $a = shift; affiche "<!-- $a -->"; }
  
  sub characters { 
    my ($self,$a) = @_;
    my $b = $a->{Data};
    $b =~ s/\&/\&amp\;/g;
    $b =~ s/\</\&lt\;/g;
    $b =~ s#((http|ftp|file)://[^\s\">]+)#<A HREF="$1">$1</A>#g;
    affiche $b;
  }
  
  sub start_p {
    debug "implicit paragraph start";
    if( $inside_p == 0 ){
      affiche "<center><table width=\"95\%\"><tr><td>";
    } else {
      affiche "<table width=\"100\%\"><tr><td>";
    }
    $inside_p++;
  #  print STDERR "<P> $inside_p\n";
  }
  sub end_p {
    debug "implicit paragraph end";
    affiche "</td></tr></table></center>\n";
    $inside_p--;
    print STDERR "<P> $inside_p\n";
  }
  
  sub start_element {
    my ($self, $el) = @_;
  
    if($el->{Name} eq "web"){
    } elsif($el->{Name} eq "head"){
    } elsif($el->{Name} eq "title"){
      $save_text = TRUE;
      $saved_text = "";
    } elsif($el->{Name} eq "date"){
      $save_text = TRUE;
      $saved_text = "";
    } elsif($el->{Name} eq "keywords"){
      $save_text = TRUE;
      $saved_text = "";
    } elsif($el->{Name} eq "imagetitle"){
      $imagetitle = $el->{Attributes}->{src};
    } elsif($el->{Name} eq "h1"){
      $section_number++;
      affiche "\n<!-- Section $section_number -->\n";
      affiche "<p></p><table width=\"100\%\" cellpadding=\"2\" cellspacing=\"3\" border=\"0\">\n";
      affiche "<tr><td bgcolor=\"$section_bgcolor\"><font color=\"$section_fgcolor\" face=\"Arial,Helvetica\"><A name=\"$section_number\"></A>";
  #    affiche "$section_number. ";
    } elsif($el->{Name} eq "a"){
      affiche("<a href=\"$el->{Attributes}->{href}\">");
    } elsif($el->{Name} eq "p"){
      debug "paragraph";
      start_p;
    } elsif($el->{Name} eq "table"){
      debug "table";
      start_p;
      affiche "<table cellpadding=0 cellspacing=0 border=0>\n";
      affiche "<tr><td bgcolor=\"$text\"><table cellpadding=3 cellspacing=1 border=0>";
      debug "table body";
    } elsif($el->{Name} eq "tr"){
      affiche('<tr>');
    } elsif($el->{Name} eq "td"){
      affiche "<td bgcolor=\"$bgcolor\">";
    } elsif($el->{Name} eq "ul"){
      debug "unnumbered list";
      start_p;
      affiche('<ul>');
    } elsif($el->{Name} eq "li"){
      affiche('<li>');
    } elsif($el->{Name} eq "img"){
      debug "image";
      start_p;
      affiche("<center><IMG SRC=\"$el->{Attributes}->{src}\" ALT=\"$el->{Attributes}->{alt}\"></center>");
      end_p;
    } elsif($el->{Name} eq "code"){
      debug "code";
      start_p;
      affiche "<table width=\"100\%\"><tr><td bgcolor=\"$code_bgcolor\"><font color=\"$code_fgcolor\"><pre>";
    } elsif($el->{Name} eq "em"){
      affiche('<em>');
    } elsif($el->{Name} eq "tt"){
      affiche('<tt>');
    } elsif($el->{Name} eq "toc"){
      start_p;
      affiche '<center>';
      foreach(@toc){
        my ($n, $t) = @$_;
        affiche '<A HREF="#'. $n .'">'. $t .'</A><br>';
      }
      affiche '</center>';
      end_p;
    }
  }
  sub end_element {
    my $self=shift;
    my $el=shift;
    if($el->{Name} eq "web"){
      start_p;
      affiche "<p align =\"RIGHT\">";
      affiche "<font color=\"$tailer_fgcolor\">";
      affiche "<a href=\"$web\" style=\"text-decoration: none\">$author</a><br>\n";
      affiche "<a href=\"mailto:$mail\" style=\"text-decoration: none\">\&lt;$mail></a><br>\n";
      affiche "$date<br>\n" if $date;
      affiche "latest modification on ". `date`;
      affiche "</font></p>\n";
      end_p;
      affiche "</body></html>";
  
    } elsif($el->{Name} eq "head"){
      affiche "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\n";
      affiche "\"http://www.w3.org/TR/html4/loose.dtd\">\n";
      affiche "<!-- This is a generated file -->\n";
      affiche "<html>\n";
      affiche "  <head>\n";
      affiche "    <title>$title</title>\n";
      affiche "    <meta http-equiv=\"Content-Style-Type\" content=\"text/css\">";
      affiche "    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=$charset\">\n";
      affiche "    <meta NAME=\"keywords\" CONTENT=\"$keywords\">" if $keywords;
      affiche "  </head>\n\n";
      affiche "  <body  bgcolor=\"$bgcolor\" text=\"$text\" link=\"$link\" alink=\"$alink\" vlink=\"$vlink\">\n";
      affiche "\n<!-- title -->\n";
      affiche "<center>\n";
      if( defined $imagetitle ){
        affiche "<img src=\"$imagetitle\" alt=\"$title\">\n";
      } else {
        affiche "  <table cellpadding=\"10\">\n";
        affiche "    <tr><td bgcolor=\"$title_bgcolor\">";
        affiche "<font color=\"$title_fgcolor\" face=\"Arial,Helvetica\">$title</font></td></tr>\n";
        affiche "  </table>\n";1
      }
      affiche "</center><p></p>";
    } elsif($el->{Name} eq "title"){
      $save_text = FALSE;
      $title = $saved_text;
    } elsif($el->{Name} eq "date"){
      $save_text = FALSE;
      $date = $saved_text;
    } elsif($el->{Name} eq "keywords"){
      $save_text = FALSE;
      $keywords = $saved_text;
    } elsif($el->{Name} eq "imagetitle"){
    } elsif($el->{Name} eq "h1"){
      affiche "</font></td></tr></table>\n";
    } elsif($el->{Name} eq "a"){
      affiche("</a>");
    } elsif($el->{Name} eq "p"){
      end_p;
      debug "paragraph end";
    } elsif($el->{Name} eq "table"){
      debug "table body end";
      affiche('</table></td></tr></table>');
      end_p;
      debug "table end";
    } elsif($el->{Name} eq "tr"){
      affiche('</tr>');
    } elsif($el->{Name} eq "td"){
      affiche('</td>');
    } elsif($el->{Name} eq "ul"){
      affiche('</ul>');
      end_p;
      debug "unnumbered list end";
    } elsif($el->{Name} eq "li"){
      affiche('</li>');
    } elsif($el->{Name} eq "img"){
    } elsif($el->{Name} eq "code"){
      affiche('</pre></font></td></tr></table>');
      end_p;
      debug "code end";
    } elsif($el->{Name} eq "em"){
      affiche('</em>');
    } elsif($el->{Name} eq "tt"){
      affiche('</tt>');
    }
  }
  
  ######################################################################
  
  package main;
  use strict;
  our $xml = join('',<>);
  
  use XML::Parser::PerlSAX;
  import MyHandler;
  my $toc_handler = TOCHandler->new;
  my $toc_parser = XML::Parser::PerlSAX->new( Handler => $toc_handler );
  $toc_parser->parse( Source => { String => $xml } );
  
  my $my_handler = MyHandler->new;
  my $parser = XML::Parser::PerlSAX->new( Handler => $my_handler );
  $parser = XML::Parser::PerlSAX->new( Handler => $my_handler );
  $parser->parse( Source => { String => $xml } );
  
  ######################################################################
  
  ## Correction du codage
  
  {
    open(A, '>', 'tmp.html') || die "Cannot open tmp.html for writing: $!";
    print A $result;
    close A;
    system "recode UTF-8..latin1 <tmp.html >tmp2.html"
      || die "Problem with recode: $!";
    open(A, '<', "tmp2.html");
    $result = join('',<A>);
    close A;
  #  unlink "tmp.html";
  #  unlink "tmp2.html";
  }
  
  ######################################################################
  
  ## Ajout de la taille des images
  {
    my $new = "";
    while( $result =~ s/^(.*?)SRC\=\"([^"]*)\"//si ){ #"
      my $avant = $1;
      my $file = $2;
      print STDERR "Looking for the size of $file\n";
      open(SIZE, "convert -verbose $file /dev/null|") || 
        warn "Cannot run `file $file /dev/null': $!";
    #    warn "Cannot run `convert -verbose $file /dev/null': $!";
      my $tmp = join('',<SIZE>);
      close SIZE;
      my($width,$height)=(320,256);
      if($tmp =~ m/([0-9]+)x([0-9]+)/){
        $width = $1;
        $height = $2;
      }
      print STDERR " width: $width height: $height\n";
      $new .= "$avant WIDTH=$width HEIGHT=$height SRC=\"$file\" ";
    }
    $new .= $result;
    $result = $new;
  }
  
  ######################################################################
  
  ## On essaye d'enlever les espaces avant </pre>
  
  $result =~ s|\s+</pre>|</pre>|gi;
  
  ######################################################################
  
  print $result;

Vincent Zoonekynd
<zoonek@math.jussieu.fr>
latest modification on ven avr 27 14:12:40 CEST 2001