<?xml version="1.0" encoding="iso-8859-1"?>
<!-- ===========================================================================
     (@) PROJECT  : ATELIER
     (@) VERSION  : 0.1
     (@) FILE     : dbschema_generation.xsl
     (@) AUTHORS  : Benoît Clouet (BCL) - benoit.clouet@6sens.com
     (@) COMMENT  : Génération d'un modèle relationnel pour la base de l'atelier
     (@) CREATION : 2003/02/16
     (@) UPDATE   : 2003/02/17
     (@) HISTORY  : 
     ======================================================================= -->
<!-- 
	$Log: dbschema_generation.xsl,v $
	Revision 1.6  2003/02/26 19:08:47  bclouet
	Ça m'en touche une sans faire bouger l'autre !
	
-->
<!-- Feuille de style de génération du script de création de la base de données à partir du fichier atelier.xsl
     Admet 1 paramètre : clean qui s'il ne vaut pas 'false' permet de générér les requetes de nettoyage de la base (pensez à les décommenter dans le fichier). -->
<!-- !!!!Attention le namespace xmlns:exslt="http://exslt.org/common" n'est pas 
     inclus dans la norme xslt 1.0 et ne fonctionne que sur certains moteurs xslt
     (notamment xsltproc et Saxon) !!!! -->

<xsl:stylesheet 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:exslt="http://exslt.org/common"
  version="1.0">

  <xsl:output method="text" encoding="iso-8859-1"/>

  <!-- Ce paramètre permet de nettoyer la base avant de commencer la création 
       des tables -->
  <xsl:param name="clean">false</xsl:param>

  <!-- Gestion de l'indentation (c'est plus propre) -->
  <xsl:variable name="indent">
    <xsl:text>    </xsl:text>
  </xsl:variable>

  <!-- ===================================================================== -->
  <!-- Règle racine du traitement -->
  <!-- ===================================================================== -->
  <xsl:template match="/">
    <xsl:text>/* Script de génération de la base de données </xsl:text>
    <xsl:text>Atelier */&#10;</xsl:text>
    <!-- Nettoyage de la base -->
    <xsl:if test="$clean != 'false'">
      <xsl:call-template name="clean-database"/>
    </xsl:if>
    <!-- Déclaration des tables de la base -->
    <xsl:apply-templates select="project/object" mode="create"/>
    <!-- Déclaration des contraintes relationnelles de la base -->
    <xsl:apply-templates select="project/object" mode="constraint"/>
  </xsl:template>

  <!-- ===================================================================== -->
  <!-- Nettoyage de la base de données (désactivé pour des raisons de sécurité
       pour le mettre en place, il vous faudra le décommenter à la main).    -->
  <!-- ===================================================================== -->
  <xsl:template name="clean-database">
    <xsl:text>/*    !!!! DANGER !!!!     */&#10;</xsl:text>
    <xsl:text>/*  NETTOYAGE DE LA BASE   */&#10;</xsl:text>
    <xsl:text>/* A DÉCOMMENTER VOUS MÊME */&#10;</xsl:text>
    <xsl:text>/*&#10;\c template1&#10;DROP DATABASE atelier;&#10;</xsl:text>
    <xsl:text>CREATE DATABASE atelier;&#10;\c atelier&#10;*/&#10;</xsl:text>
    <xsl:text>/*   !!!! DANGER !!!!   */&#10;&#10;</xsl:text>
  </xsl:template>

  <!-- ===================================================================== -->
  <!-- Mise en forme d'une table par objet -->
  <!-- ===================================================================== -->
  <xsl:template match="object" mode="create">
    <!-- Début de la déclaration de la table -->
    <!-- Traitement des chaînes de documentation d'une table -->
    <xsl:if test="doc">
      <xsl:text>/* </xsl:text>
      <xsl:value-of select="doc"/>
      <xsl:text> */&#10;</xsl:text>
    </xsl:if>

    <xsl:text>CREATE TABLE </xsl:text>
    <xsl:value-of select="@name"/>
    <xsl:text> ( </xsl:text>
    <xsl:text>&#10;</xsl:text>

    <!-- Collecter les attributs dans une variable attribute (chaque attribut
         peut-être récupéré plusieurs fois en particulier s'il s'agit d'une 
         partie de clé primaire). 
         Tout ce m***ier juste pour avoir une gestion correcte de la virgule -->
    <xsl:variable name="attributes">
      <!-- Tous les attributs sans exception sont placés dans un élément 
           attribute.
           Les attributs qui composent une clé étrangère sont également 
           récupérés (dans l'objet dont ils proviennent). -->
      <xsl:for-each select="attribute">
        <xsl:call-template name="format-attributes"/>
      </xsl:for-each>

      <!-- Tous les attributs formant la clé primaire. Il peut y avoir des 
           doublons avec les attributs récupérés plus haut -->
      <pkey>
        <xsl:call-template name="format-pkey"/>
      </pkey>
    </xsl:variable>


    <!-- Pour tous les attributs contenus dans la liste "attributes" triés de la
         façon suivante d'abord les attributs, ensuite la déclaration de clé 
         primaire. !!Attention!! la fonction exslt:node-set($attributes) n'est 
         pas incluse dans le standard xslt 1.0 mais est supportée pas plusieurs 
         moteurs -->
    <xsl:for-each select="exslt:node-set($attributes)/*">
      <xsl:sort select="exslt:node-set($attributes)/*"
        order="ascending"/>
      <!-- En fonction du type de noeud instancier le bon patron -->
      <xsl:choose>
        <xsl:when test="local-name(.) = 'pkey'">
          <!-- Récupérer la déclaration de clé primaire -->
          <xsl:call-template name="get-pkey">
            <xsl:with-param name="element" select="."/>            
          </xsl:call-template>
        </xsl:when>

        <xsl:when test="local-name(.) = 'attribute'">
          <!-- Récupérer la déclaration d'un attribut -->
          <xsl:call-template name="format-attribute">
            <xsl:with-param name="element" select="."/>            
          </xsl:call-template>
        </xsl:when>
      </xsl:choose>
      <!-- Gestion de la virgule -->
      <xsl:if test="not(position() = last())">
        <xsl:text>,&#10;</xsl:text>
      </xsl:if>
    </xsl:for-each>

    <!-- Mise en forme du texte de sortie -->
    <xsl:text>&#10;);&#10;&#10;</xsl:text>
    <!-- Fin de déclaration de la table -->
  </xsl:template>

  <!-- ===================================================================== -->
  <!-- Traitement des contraintes d'un objet  -->
  <!-- ===================================================================== -->
  <xsl:template match="object" mode="constraint">
    <xsl:variable name="current-object" select="@name"/>

    <!-- Traitement des index -->
    <xsl:for-each select="index">
      <xsl:call-template name="format-index"/>
    </xsl:for-each>

    <!-- Traitement des clés étrangères chaque attribut qui désigne une autre 
         table est traité -->
    <xsl:for-each select="attribute[@card != '']">
      <xsl:call-template name="format-fkey">
        <xsl:with-param name="element" select="."/>
        <xsl:with-param name="current-object" select="$current-object"/>
      </xsl:call-template>
    </xsl:for-each>
  </xsl:template>

  <!-- ===================================================================== -->
  <!-- Récupération d'une définition d'attribut (nom + type SQL) -->
  <!-- ===================================================================== -->
  <xsl:template name="format-attribute">
    <xsl:param name="element"/>
    <!-- Traitement des chaînes de documentation d'un attribut -->
    <xsl:if test="@doc">
      <!-- Indentation -->
      <xsl:value-of select="$indent"/>
      <xsl:text>/* </xsl:text>
      <xsl:value-of select="@doc"/>
      <xsl:text> */&#10;</xsl:text>
    </xsl:if>
    <!-- Déclaration à proprement parler -->
    <xsl:value-of select="$indent"/>
    <xsl:call-template name="lowercase">
      <xsl:with-param name="n" select="exslt:node-set($element)/@name"/>      
    </xsl:call-template>
    <xsl:text> </xsl:text>
    <xsl:value-of select="exslt:node-set($element)/@type"/>
  </xsl:template>

  <!-- ===================================================================== -->
  <!-- Traitement de la définition de la clé primaire -->
  <!-- ===================================================================== -->
  <xsl:template name="get-pkey">
    <xsl:param name="element"/>
    <!-- Gestion de l'indentation -->
    <xsl:text>&#10;</xsl:text>
    <xsl:value-of select="$indent"/>
    <xsl:text>/* CLÉ PRIMAIRE */&#10;</xsl:text>
    <xsl:value-of select="$indent"/>
    <xsl:text>CONSTRAINT </xsl:text>
    <xsl:call-template name="lowercase">
      <xsl:with-param name="n" select="exslt:node-set($element)/@name"/>      
    </xsl:call-template>
    <xsl:text>_pkey PRIMARY KEY &#10;</xsl:text>
    <!-- Gestion de l'indentation -->
    <xsl:value-of select="$indent"/>
    <xsl:value-of select="$indent"/>
    <xsl:text>(</xsl:text>
    <xsl:for-each select="exslt:node-set($element)/attribute">
      <xsl:if test="not(position() = '1')">
        <!-- Gestion de l'indentation -->
        <xsl:value-of select="$indent"/>
        <xsl:value-of select="$indent"/>
      </xsl:if>
      <xsl:call-template name="lowercase">
        <xsl:with-param name="n" select="@name"/>      
      </xsl:call-template>
      <!-- Gestion de la virgule -->
      <xsl:if test="not(position() = last())">
        <xsl:text>,&#10;</xsl:text>
      </xsl:if>
    </xsl:for-each>
    <xsl:text>)</xsl:text>
  </xsl:template>

  <!-- ===================================================================== -->
  <!-- Traitement d'une définition de clé étrangère -->
  <!-- ===================================================================== -->
  <xsl:template name="format-fkey">
    <xsl:param name="element"/>
    <xsl:param name="current-object"/>

    <xsl:text>ALTER TABLE </xsl:text>
    <!-- Nom de la table sur laquelle portera la contrainte -->
    <xsl:value-of select="$current-object"/>
    <xsl:text> ADD CONSTRAINT </xsl:text>
    <!-- Nom de la contrainte (suffixée par _fkey) -->
    <xsl:call-template name="lowercase">
      <xsl:with-param name="n" select="exslt:node-set($element)/@type"/>      
    </xsl:call-template>
    <xsl:text>_fkey FOREIGN KEY&#10;</xsl:text>
    <!-- Gestion de l'indentation -->
    <xsl:value-of select="$indent"/>

    <!-- Liste des attributs composant la clé étrangère dans la table source -->
    <xsl:text>(</xsl:text>
    <xsl:variable name="fkeys">
      <xsl:call-template name="format-attributes"/>
    </xsl:variable>
    <!-- Mise en forme des éléments de clé étrangère -->
    <xsl:for-each select="exslt:node-set($fkeys)/attribute">
      <!-- Gestion de l'indentation -->
      <xsl:if test="not(position() = '1')">
        <xsl:value-of select="$indent"/>
      </xsl:if>
      <xsl:value-of select="@name"/>
      <!-- Gestion de la virgule -->
      <xsl:if test="not(position() = last())">
        <xsl:text>,&#10;</xsl:text>
      </xsl:if>
    </xsl:for-each>
    <xsl:text>)&#10;</xsl:text>

    <!-- Gestion de l'indentation -->
    <xsl:value-of select="$indent"/>

    <!-- Déclaration de la table cible -->
    <xsl:text>REFERENCES </xsl:text>
    <!-- Nom de la table cible de la relation -->
    <xsl:value-of select="@type"/>
    <xsl:text>&#10;</xsl:text>
    <!-- Gestion de l'indentation -->
    <xsl:value-of select="$indent"/>

    <!-- Liste des attributs composant la clé primaire dans la table cible -->
    <xsl:text>(</xsl:text>
    <!-- Récupération des éléments qui composent la clé primaire -->
    <xsl:for-each select="exslt:node-set($fkeys)/attribute">
      <!-- Gestion de l'indentation -->
      <xsl:if test="not(position() = '1')">
        <xsl:value-of select="$indent"/>
      </xsl:if>
      <xsl:value-of select="@name"/>
      <!-- Gestion de la virgule -->
      <xsl:if test="not(position() = last())">
        <xsl:text>,&#10;</xsl:text>
      </xsl:if>
    </xsl:for-each>
    <xsl:text>)&#10;</xsl:text>

    <!-- Gestion de l'indentation -->
    <xsl:value-of select="$indent"/>
    <!-- Comportement en cas de modification dans la base par défaut CASCADE
         (a vérifier) -->
    <xsl:text>ON DELETE CASCADE&#10;</xsl:text>
    <!-- Gestion de l'indentation -->
    <xsl:value-of select="$indent"/>
    <xsl:text>ON UPDATE CASCADE;&#10;&#10;</xsl:text>
  </xsl:template>

  <!-- ===================================================================== -->
  <!-- Traitement des types : conversion des types donnés dans le fichier xml 
       vers les types disponibles sous PostgreSQL -->
  <!-- ===================================================================== -->
  <xsl:template name="get-type">
    <xsl:param name="type" select="@type"/>    
    <xsl:param name="foreign">false</xsl:param>
    <xsl:choose>
      <xsl:when test="$type = 'string'">
        <xsl:text>TEXT</xsl:text>
      </xsl:when>
      <xsl:when test="$type = 'text'">
        <xsl:text>TEXT</xsl:text>
      </xsl:when>
      <xsl:when test="$type = 'date'">
        <xsl:text>DATE</xsl:text>
      </xsl:when>
      <xsl:when test="$type = 'boolean'">
        <xsl:text>BOOL</xsl:text>
      </xsl:when>
      <xsl:when test="$type = 'integer'">
        <xsl:text>INTEGER</xsl:text>
      </xsl:when>
      <xsl:when test="$type = 'float'">
        <xsl:text>FLOAT</xsl:text>
      </xsl:when>
      <xsl:when test="$type = 'autoinc'">
        <xsl:choose>
          <xsl:when test="$foreign != 'false'">
            <xsl:text>SERIAL</xsl:text>                        
          </xsl:when>
          <xsl:otherwise>
            <xsl:text>INTEGER</xsl:text>                        
          </xsl:otherwise>
        </xsl:choose>
      </xsl:when>
      <xsl:otherwise>
        <!-- Cas d'un type non référencé -->
        <xsl:text>!!! ERREUR DU FICHIER !!!</xsl:text>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
  
  <!-- ===================================================================== -->
  <!-- Mise en forme de la clé primaire de la table courante -->
  <!-- ===================================================================== -->
  <xsl:template name="format-pkey">
    <!-- Nom de la contrainte de clé primaire -->
    <xsl:attribute name="name">
      <xsl:value-of select="@name"/>
    </xsl:attribute>

    <!-- Pour chaque attribut composant la clé primaire -->
    <xsl:for-each select="attribute[@primarykey = 'true']">
      <xsl:choose>
        <!-- Traitement du cas où la clé primaire comprend aussi des 
             éléments de clés étrangères Cf. plus loin. -->
        <xsl:when test="@card != ''">
          <xsl:variable name="type" select="@type"/>
          <xsl:for-each select="/descendant::object[@name = $type]/
                                attribute[@primarykey = 'true']">
            <xsl:call-template name="format-attributes"/>
          </xsl:for-each>
        </xsl:when>
        <xsl:otherwise>
          <xsl:element name="attribute">
            <xsl:attribute name="name">
              <xsl:value-of select="@name"/>
            </xsl:attribute>          
          </xsl:element>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:for-each>
  </xsl:template>

  <!-- ===================================================================== -->
  <!-- Traitement des déclarations d'index -->
  <!-- ===================================================================== -->
  <xsl:template name="format-index">
    <xsl:text>CREATE </xsl:text>
    <xsl:if test="@unique = 'true'">UNIQUE </xsl:if>
    <xsl:text>INDEX </xsl:text>
    <!-- Nom de l'index suffixé par __idx -->
    <xsl:call-template name="lowercase">
      <xsl:with-param name="n" select="@name"/>      
    </xsl:call-template>
    <xsl:text>_idx ON </xsl:text>
    <!-- Table cible de l'index -->
    <xsl:call-template name="lowercase">
      <xsl:with-param name="n" select="ancestor::object/@name"/>
    </xsl:call-template>
    <xsl:text>&#10;</xsl:text>
    <xsl:value-of select="$indent"/>

    <!-- Liste des attributs indexés -->
    <xsl:text>(</xsl:text>
    <xsl:for-each select="descendant::attribute">
      <!-- Gestion de l'indentation pour plus de lisibilité de la sortie -->
      <xsl:if test="not(position() = '1')">
        <xsl:value-of select="$indent"/>
      </xsl:if>
      <xsl:call-template name="lowercase">
        <xsl:with-param name="n" select="@name"/>      
      </xsl:call-template>
      <!-- Gestion de la virgule -->
      <xsl:if test="not(position() = last())">
        <xsl:text>,&#10;</xsl:text>
      </xsl:if>
    </xsl:for-each>

    <!-- Mise en forme de la sortie -->
    <xsl:text>);&#10;&#10;</xsl:text>
  </xsl:template>

  <!-- ===================================================================== -->
  <!-- Traitement de la récupération d'éléments composant une clé étrangère 
       (resp. primaire). L'idée principale est qu'il est nécessaire de tenir 
       compte du fait qu'une clé primaire peut être composée d'une clé étrangère
       désignant une autre table (dans le cas d'une relation de type 0..* -->
  <!-- ===================================================================== -->
  <xsl:template name="format-attributes">
    <xsl:choose>
      <!-- Reprendre tous les attributs qui composent la clé primaire 
           pour la constitution d'une clé étrangère -->
      <xsl:when test="@card != ''">
        <xsl:variable name="type" select="@type"/>
        <xsl:for-each select="/descendant::object[@name = $type]/
                              attribute[@primarykey = 'true']">
          <xsl:choose>
            <!--Récursion pour les raisons évoquées dans l'en-tête du patron -->
            <xsl:when test="@card != ''">
              <xsl:call-template name="format-attributes"/>
            </xsl:when>

            <xsl:otherwise>
              <!-- Cas d'arrêt 1 : l'attribut clé étrangère a été entièrement
                   récupéré -->
              <!-- Mise en forme de ce qui est récupéré -->
              <xsl:element name="attribute">
                <xsl:attribute name="name">
                  <xsl:value-of select="@name"/>
                </xsl:attribute>
                <xsl:attribute name="type">
                  <xsl:call-template name="get-type">
                    <xsl:with-param name="type" select="@type"/>
                  </xsl:call-template>      
                </xsl:attribute>
                <xsl:attribute name="doc">
                  <xsl:value-of select="doc"/>
                </xsl:attribute>
              </xsl:element>              
            </xsl:otherwise>
          </xsl:choose>
        </xsl:for-each>
      </xsl:when>

      <!-- Cas d'arrêt 2 : l'attribut n'est pas une référence vers une autre 
           table -->
      <xsl:otherwise>
        <xsl:element name="attribute">
          <xsl:attribute name="name">
            <xsl:value-of select="@name"/>
          </xsl:attribute>
          <xsl:attribute name="type">
            <xsl:call-template name="get-type">
              <xsl:with-param name="type" select="@type"/>
              <xsl:with-param name="foreign">true</xsl:with-param>
            </xsl:call-template>      
          </xsl:attribute>
          <xsl:attribute name="doc">
            <xsl:value-of select="doc"/>
          </xsl:attribute>
        </xsl:element>
      </xsl:otherwise>
    </xsl:choose>    
  </xsl:template>

  <!-- ===================================================================== -->
  <!-- Mise en minuscule du paramètre                                        -->
  <!-- ===================================================================== -->
  <xsl:template name="lowercase">
    <xsl:param name="n"/>
    <xsl:value-of select="translate($n, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
                          'abcdefghijklmnopqrstuvwxyz')"/>
  </xsl:template>

  <!-- ===================================================================== -->
  <!-- Élimination des noeuds non explicitement traités par une des règles 
       précédentes -->
  <!-- ===================================================================== -->
  <xsl:template match="*"/>

</xsl:stylesheet>

