Un nouveau corpus, un peu d’XSLT et la transdisciplinarité

A la grande conférence de Hambourg du mois dernier (Second International Conference on Natural Sciences and Technology in Manuscript Analysis Centre for the Study of Manuscript Cultures, Hamburg, 29 February – 2 March 2016), j’ai fait la connaissance d’Andreas Fischer et nous nous sommes mis d’accord pour ajouter la “Saint Gall Database” aux corpus d’Oriflamms.

Cette base d’image et de texte permet de mesurer la précision de l’alignement texte-image et la précision de la reconnaissance d’écriture manuscrite. Elle contient en effet la transcription et des coordonnées pour le ms. St. Gallen, Stiftsbibliothek, Cod. Sang. 562 – Vitae sancti Galli et Otmari (http://www.e-codices.unifr.ch/fr/list/one/csg/0562) .

  • Sur ce corpus, voir :
    A. Fischer, V. Frinken, A. Fornés, and H. Bunke: “Transcription Alignment of Latin Manuscripts using Hidden Markov Models,” in Proc. 1st Int. Workshop on Historical Document Imaging and Processing, pages 29-36, 2011.
  • FKI Research Group on Computer Vision and Artificial Intelligence. « Saint Gall Database — Computer Vision and Artificial Intelligence ». IAM Institut für Informatik und angewandte Mathematik, 2012. http://www.iam.unibe.ch/fki/databases/iam-historical-document-database/saint-gall-database.

En effet Jihad El-Sana a mesuré sa performance sur cette base et les comparaisons n’étaient pas possibles.
C’est là que les ennuis commencent pour les Humanistes…
Le format est tout d’abord un peu exotique… euh non, en fait, franchement exotique ! Bon, pour le texte, c’est du “texte plein” (.txt), c’est bien, c’est pratique. Mais quand on regarde, le document “transcription.txt”, on voit, pour les deux premières lignes, ceci :
csg562-003-01 v-e-r-s-a-t-i-o|p-e-r|o-m-n-e-m|h-i-b-e-r-n-i-a-m|c-e-l-e-b-r-i-s|h-a-b-e-r-e-t-u-r-pt|et|v-e-l-u-t-i|s-p-l-e-n conversatio|per|omnem|hiberniam|celebris|haberetur|et|veluti|BREAK
csg562-003-02 d-i-d-u-m|i-g-n-e-i|s-o-l-i-s|i-u-b-a-r|s-i-n-g-u-l-a-r-i|d-e-c-o-r-e|o-m-n-i-u-m|i-n|s-e|p-r-o-v-o-c-a splendidum|ignei|solis|iubar|singulari|decore|omnium|in|se|BREAK

Donc on a, pour une ligne :

  1. un identifiant de ligne, constitué par la cote abrégée du manuscrit “csg562" un tiret -, le numéro du feuillet, un tiret, le numéro de la ligne ;
  2. l’ensemble des mots transcrits lettre à lettre: chaque lettre est séparée de la suivante par un tiret ; les mots sont séparés par un pipe “|” ; la perluette est, par exception, transcrite “et” sans tiret ; si la fin de ligne interrompt un mot, ses lettres isolées sont transcrites de la même façon ;
  3. un espace blanc ;
  4. le même ensemble de mots transcrits mot par mot : les abréviations sont résolues silencieusement ; les mots sont séparés par un pipe “|” ; la perluette est transcrite “et” (sans tiret) ; si la fin de ligne interrompt un mot, il n’est pas transcrit, mais remplacé par “|BREAK” et le début du mot est écrit en début de ligne suivante dans la deuxième série de transcription.

Quelques heures plus tard, à manier expressions régulières, j’arrive à un document TEI parfaitement formé et valide, après avoir inséré chaque mot dans un élément <w>, doté d’attributs @n et @rend pour séparer les deux transcriptions successives .  Quelques heures de plus, et j’ai un petit template XSLT pour collationner les mots abrégés, et les mots avec abréviations restituées pour insérer les lettres restituées dans des éléments <ex> (La feuille XSLT est plus bas.)

 St. Gallen, Stiftsbibliothek / Cod. Sang. 562 – Vitae sancti Galli et Otmari / p. 3

St. Gallen, Stiftsbibliothek / Cod. Sang. 562 – Vitae sancti Galli et Otmari / p. 3

Et c’est là que je découvre à mon plus grand étonnement que toute mesure de performance pour un alignement en conditions réelles est vaine. En effet, silencieusement, des lignes ont été omises qui n’apparaissent pas dans la numérotation. Cela apparaît au premier feuillet, avec une première ligne de transcription numérotée 1, qui est la troisième de l’image.

 St. Gallen, Stiftsbibliothek / Cod. Sang. 562 – Vitae sancti Galli et Otmari / p. 65
St. Gallen, Stiftsbibliothek / Cod. Sang. 562 – Vitae sancti Galli et Otmari / p. 65

mais surtout cela se répète dans la transcription où l’on trouve par exemple les deux lignes numérotées dans l’ordre

csg562-065-17 a-u-t|et|p-f-e-c-t-u-s|h-o-d-i-e|l-a-u-d-a-b-i-l-i-t-e-r|d-i-l-a-t-a-r-i|n-o-n|d-e-s-i-n-i-t autem|et|profectus|hodieque|laudabiliter|dilatari|non|desinit
csg562-065-18 s-u-m|s-a-t-i-s|v-e-r-e|c-r-e-d-i-m-u-s|q-u-o-m-o-d-o|s-a-c-e-r|l-o-c-u-s|e-m-u-n-i-t-a-t-i-s comprehensum|satis|vere|credimus|quomodo|sacer|locus|immunitatis

alors que l’image nous montre un numéro de chapitre rubriqué “XI.” et une première ligne de texte en capitales rustiques “PREMISSA NARRATIONE QUA COMPREHEN”, et la fin du mot se trouve à la ligne suivante (comprehen-|-sum).

Bref, nous allons travailler sur ce corpus, mais, encore une fois, on se rend compte que l’approche du texte peut être très différente d’une communauté à l’autre. Ici, nous travaillons à rapprocher les points de vue !

 

 

 
Et voici la feuille de style.

Cette feuille part du principe presque universellement vrai dans l’emploi des abréviations que toutes les lettres du mot abrégé se retrouvent dans le mot restitué et dans le même ordre, comme le rappelait Thomas Frenz à l’atelier “Mittelalterliche Kürzungspraktiken in Volkssprache und Latein” à Zurich du 12 mars dernier. En conséquence de quoi, dans la ligne suivante, le mot “heremus” a disparu, car la forme normalisée choisie est “eremus” et non “heremus” (également possible).
csg562-009-19 m-o-n-t-e-s|p-e-r|g-i-r-u-m|e-x-c-e-l-s-i-pt|h-e-r-e-m-u-s|v-a-s-t-a-pt|et|i-m-m-i-n-e-n-s|o-p-p-i-d-o montes|per|gyrum|excelsi|eremus|vasta|et|imminens|oppido
cs

On trouve aussi des transcriptions doublées :

csg562-013-01 a-b-o-l-i-t-i-o-n-e-m|p-r-a-e-d-i-c-a-t-i-o-n-i-s|e-o-r-u-m|m-o-n-i-t-a|c-o-n-t-e-m-p-n-e-n-t-e-s-pt|c-o-n-t-e-m-n-e-n-t-e-s-pt|c-o-e abolitionem|praedicationis|eorum|monita|contempnentes|contemnentes|BREAK


 

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xpath-default-namespace="http://www.tei-c.org/ns/1.0" xmlns="http://www.tei-c.org/ns/1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
  <xsl:output encoding="UTF-8" method="xml" indent="no"/>
  <xsl:strip-space elements="*"/>
  
 
  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>
  <xsl:template match="/">
    <xsl:apply-templates />
  </xsl:template>
  
  <xsl:template match="w[@rend='facs']">
    <xsl:param name="i" />
    <xsl:param name="length" />
    <xsl:variable name="n"><xsl:value-of select="./@n"/></xsl:variable>
    <xsl:choose>
      <!-- s'il n'y a pas d'abréviation ou de restitution -->
      <xsl:when test="string-length(.)=string-length(following-sibling::w[@rend='normal'][@n=$n])">
        <xsl:copy-of select="."></xsl:copy-of>
      </xsl:when>
      <!-- pour la perluette -->
      <xsl:when test="contains(., '&amp;')">
        <xsl:copy-of select="."></xsl:copy-of>
      </xsl:when>
      <!-- pour les mots coupés en fin de ligne -->
      <xsl:when test="contains(ancestor::p[1], 'BREAK')">
        <xsl:if test=".[last()]">
        <xsl:element name="w">
          <xsl:attribute name="n"><xsl:value-of select="$n"></xsl:value-of></xsl:attribute>
          <xsl:attribute name="rend"><xsl:text>facs</xsl:text></xsl:attribute>
          <xsl:element name="seg">
            <xsl:attribute name="type"><xsl:text>wp</xsl:text></xsl:attribute>
            <xsl:attribute name="part"><xsl:text>I</xsl:text></xsl:attribute>
          <xsl:copy-of select="text()"></xsl:copy-of>
        </xsl:element>
        </xsl:element>
        </xsl:if>
      </xsl:when>
      <!-- pour les mots coupés, en début de ligne -->
      <xsl:when test="preceding-sibling::lb[@break='no'] and @n=1">
        <xsl:element name="w">
          <xsl:attribute name="n"><xsl:value-of select="$n"></xsl:value-of></xsl:attribute>
          <xsl:attribute name="rend"><xsl:text>facs</xsl:text></xsl:attribute>
          <xsl:element name="seg">
            <xsl:attribute name="type"><xsl:text>wp</xsl:text></xsl:attribute>
            <xsl:attribute name="part"><xsl:text>F</xsl:text></xsl:attribute>
            <xsl:copy-of select="text()"></xsl:copy-of>
          </xsl:element>
        </xsl:element>
      </xsl:when>
      <xsl:when test="string-length(.) &lt; string-length(following-sibling::w[@rend='normal'][@n=$n])">
        <xsl:variable name="expanded" select="following-sibling::w[@rend='normal'][@n=$n]"/>
        <xsl:element name="w">
          <xsl:attribute name="n" select="$n"/>
          <xsl:attribute name="rend"><xsl:text>facs</xsl:text></xsl:attribute>
        <xsl:call-template name="collate">
          <xsl:with-param name="length" select="string-length(following-sibling::w[@rend='normal'][@n=$n])" />
          <xsl:with-param name="pos1" select="number(1)"></xsl:with-param>
          <xsl:with-param name="pos2" select="number(1)"></xsl:with-param>
          <xsl:with-param name="n" select="$n"></xsl:with-param>
          <xsl:with-param name="expanded" select="$expanded"></xsl:with-param>
          </xsl:call-template>
        </xsl:element>
      </xsl:when>
    </xsl:choose>
  </xsl:template>
  <!-- NOUVEAU TEMPLATE POUR COLLATION DES MOTS  avec abréviation, on crée une boucle qui va examiner chacun des caractères  -->
  <xsl:template name="collate">
    <xsl:param name="pos1"/>
    <xsl:param name="pos2"/>
    <xsl:param name="length"/>
    <xsl:param name="n"/>
    <xsl:param name="expanded"></xsl:param>
    
      <xsl:choose>
        <xsl:when test="lower-case(substring(.,$pos1,1)) = substring($expanded, $pos2,1)">
          <xsl:copy-of select="normalize-space(substring(.,$pos1,1))"/>
          <xsl:if test="number($pos2) &lt; number($length)">
            <xsl:call-template name="collate">
              <xsl:with-param name="length" select="$length" />
              <xsl:with-param name="pos1" select="$pos1 + 1"></xsl:with-param>
              <xsl:with-param name="pos2" select="$pos2 + 1"></xsl:with-param>
              <xsl:with-param name="expanded" select="$expanded"></xsl:with-param>
            </xsl:call-template>
          </xsl:if>
          </xsl:when>
        <xsl:when test="lower-case(substring(.,$pos1,1)) != substring($expanded, $pos2,1)">
          <xsl:element name="ex">
            <xsl:copy-of select="substring($expanded, $pos2,1)"/>
          </xsl:element>
          <xsl:if test="number($pos2) &lt; number($length)">
            <xsl:call-template name="collate">
              <xsl:with-param name="length" select="$length" />
              <xsl:with-param name="pos1" select="$pos1"></xsl:with-param>
              <xsl:with-param name="pos2" select="$pos2 + 1"></xsl:with-param>
              <xsl:with-param name="expanded" select="$expanded"></xsl:with-param>
            </xsl:call-template>
          </xsl:if>
          
        </xsl:when>
        </xsl:choose>
          
          <!-- Tant que length n'est pas atteint, l'on rappele le template pour continuer la boucle -->
         
       
  </xsl:template>
   
  <!-- SUPPRESSION DE LA TRANSCRIPTION NORMALISEE -->
  <xsl:template match="w[@rend='normal']"/>
  
  
    
        
  </xsl:stylesheet>