<?xml version="1.0" encoding="utf-8" ?>
<!--
    Application Description Language framework
    adl2canonical.xsl
    
    (c) 2007 Cygnet Solutions Ltd
    
    Transform ADL into a canonical form, expanding and making explicit 
    things left implicit in the manually maintained form. Specifically,
    in the canonicalised form
    
    (i) every entity element has a key subelement. If it did not have one
      in the supplied ADL, then a formal primary key is auto generated
    (ii) every form, page or list has properties='listed'; where the supplied
      ADL had properties='all' or properties='user-distinct' the correct fields 
      are generated.
    
    $Author: sb $
    $Revision: 1.2 $
    $Date: 2008-02-01 18:25:38 $
  -->

<xsl:stylesheet version="1.0"
  xmlns="http://cygnets.co.uk/schemas/adl-1.2" 
  xmlns:adl="http://cygnets.co.uk/schemas/adl-1.2"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  exclude-result-prefixes="adl">

  <xsl:output encoding="UTF-8" indent="yes" method="xml" />

  <!-- 
  The convention to use for naming auto-generated abstract primary keys. Known values are
  Id - the autogenerated primary key, if any, is called just 'Id'
  Name - the autogenerated primary key has the same name as the entity
  NameId - the name of the auto generated primary key is the name of the entity followed by 'Id'
  Name_Id - the name of the auto generated primary key is the name of the entity followed by '_Id'  
  -->
  <xsl:param name="abstract-key-name-convention" select="Id"/>

  <xsl:template match="adl:application">
    <xsl:copy>
      <xsl:comment>
        ***************************************************************************
        *
        *	©2007 Cygnet Solutions Ltd
        *
        *	THIS FILE IS AUTOMATICALLY GENERATED AND SHOULD NOT
        *	BE MANUALLY EDITED.
        *
        *	Generated using adl2canonical.xsl revision <xsl:value-of select="substring('$Revision: 1.2 $', 12)"/>
        *
        ***************************************************************************
      </xsl:comment>
      <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
  </xsl:template>

  <!-- an entity which already has a key tag - just copy it through -->
  <xsl:template match="adl:entity[adl:key]">
    <xsl:comment> 
      entity <xsl:value-of select="@name"/> already has a key - not generating one
    </xsl:comment>
    <entity>
      <xsl:apply-templates select="@* | node()"/>
    </entity>
  </xsl:template>

  <!-- an entity which has a '@natural-key' attribute. 
  Since we've got the key tag, I think this should be disallowed -->
  <xsl:template match="adl:entity[@natural-key]">
    <xsl:message terminate="no">
      [In entity '<xsl:value-of select="@name"/>']: '@natural-key' is deprecated - use the 'key' sub element instead</xsl:message>
    <entity>
      <xsl:variable name="nkey" select="@natural-key"/>
      <xsl:apply-templates select="@*"/>
      <!-- children copied through in legal order, to ensure the document remains valid -->
      <xsl:apply-templates select="adl:documentation"/>
      <xsl:apply-templates select="adl:content"/>
      <key>
        <xsl:apply-templates select="adl:property[@name=$nkey]"/>
        <xsl:apply-templates select="adl:key/adl:property"/>
      </key>
      <xsl:apply-templates select="adl:property[not(@name=$nkey)] | adl:one-to-many | adl:many-to-many | adl:many-to-one"/>
      <xsl:apply-templates select="adl:permission"/>
      <xsl:apply-templates select="adl:form | adl:page | adl:list"/>
    </entity>
  </xsl:template>

  <!-- an entity which has no explicit key: auto-generate one -->
  <xsl:template match="adl:entity">
    <xsl:comment>
      entity <xsl:value-of select="@name"/> has no key - generating one
    </xsl:comment>
    <entity>
      <!-- copy attributes through -->
      <xsl:apply-templates select="@*"/>
      <!-- children copied through in legal order, to ensure the document remains valid -->
      <xsl:apply-templates select="adl:documentation"/>
      <xsl:apply-templates select="adl:content"/>
      <key>
        <!-- autogenerate a key element... -->
        <!-- select a name... -->
        <xsl:variable name="key">
          <xsl:choose>
            <xsl:when test="$abstract-key-name-convention='Name'">
              <xsl:value-of select="@name"/>
            </xsl:when>
            <xsl:when test="$abstract-key-name-convention = 'NameId'">
              <xsl:value-of select="concat( @name, 'Id')"/>
            </xsl:when>
            <xsl:when test="$abstract-key-name-convention = 'Name_Id'">
              <xsl:value-of select="concat( @name, '_Id')"/>
            </xsl:when>
            <xsl:otherwise>
              <xsl:value-of select="'Id'"/>
            </xsl:otherwise>
          </xsl:choose>
        </xsl:variable>

        <!-- check that it is unique, and abort hard if not... -->
        <xsl:if test="descendant::adl:property[@name=$key]">
          <xsl:message terminate="yes">
            Entity '<xsl:value-of select="@name"/>' has a property '<xsl:value-of select="$key"/>' which conflicts 
            with your chosen key naming convention <xsl:value-of select="$abstract-key-name-convention"/>. Either:
            (i) Make property '<xsl:value-of select="$key"/>' an explicit key by putting it in the &lt;key&gt; tag;
            (ii) Name property '<xsl:value-of select="$key"/>' something else; or
            (iii) Choose a different key naming convention.
          </xsl:message>
        </xsl:if>
        
        <!-- generate one property, the abstract primary key -->
        <property type="serial" distinct="system">
          <xsl:attribute name="name">
            <xsl:value-of select="normalize-space( $key)"/>
          </xsl:attribute>
        </property>
      </key>
      <xsl:apply-templates select="adl:property | adl:one-to-many | adl:many-to-many | adl:many-to-one"/>
      <xsl:apply-templates select="adl:permission"/>
      <xsl:apply-templates select="adl:form | adl:page | adl:list"/>
    </entity>
  </xsl:template>

  <!-- If properties='all', unroll them into a properties='listed' form. 
  We need to do this for lists, pages and forms; there's probably some clever 
  way of doing it all in a oner, but I don't know what that is -->
  <xsl:template match="adl:form[ @properties='all']">
    <form properties='listed'>
      <!-- copy the name attribute through -->
      <xsl:apply-templates select="@name"/>
      <!-- children copied through in legal order, to ensure the document remains valid -->
      <xsl:apply-templates select="adl:documentation"/>
      <!-- unroll the properties -->
      <xsl:call-template name="unroll-properties"/>
      <xsl:apply-templates select="adl:head|adl:top|adl:foot|adl:field|
                           adl:fieldgroup|adl:auxlist|adl:verb|
                           adl:permission|adl:pragma"/>
    </form>
  </xsl:template>

  <!-- If properties='all', unroll them into a properties='listed' form. 
  We need to do this for lists, pages and forms; there's probably some clever 
  way of doing it all in a oner, but I don't know what that is -->
  <xsl:template match="adl:page[ @properties='all']">
    <page properties='listed'>
      <!-- copy the name attribute through -->
      <xsl:apply-templates select="@name"/>
      <!-- children copied through in legal order, to ensure the document remains valid -->
      <xsl:apply-templates select="adl:documentation"/>
      <!-- unroll the properties -->
      <xsl:call-template name="unroll-properties"/>
      <xsl:apply-templates select="adl:head|adl:top|adl:foot|adl:field|
                           adl:fieldgroup|adl:auxlist|adl:verb|
                           adl:permission|adl:pragma"/>
    </page>
  </xsl:template>

  <!-- If properties='all', unroll them into a properties='listed' form. 
  We need to do this for lists, pages and forms; there's probably some clever 
  way of doing it all in a oner, but I don't know what that is -->
  <xsl:template match="adl:list[ @properties='all']">
    <list properties='listed'>
      <!-- copy the name attribute through -->
      <xsl:apply-templates select="@name"/>
      <!-- children copied through in legal order, to ensure the document remains valid -->
      <xsl:apply-templates select="adl:documentation"/>
      <!-- unroll the properties -->
      <xsl:call-template name="unroll-properties"/>
      <xsl:apply-templates select="adl:head|adl:top|adl:foot|adl:field|
                           adl:fieldgroup|adl:auxlist|adl:verb|
                           adl:permission|adl:pragma"/>
    </list>
  </xsl:template>

  <!-- In practice it's likely only to be lists which have properties='user-distinct', 
  but the grammar allows this for pages and forms as well so we'll deal with it -->
  <xsl:template match="adl:form[ @properties='user-distinct']">
    <form properties='listed'>
      <!-- copy the name attribute through -->
      <xsl:apply-templates select="@name"/>
      <!-- children copied through in legal order, to ensure the document remains valid -->
      <xsl:apply-templates select="adl:documentation"/>
      <!-- unroll the properties -->
      <xsl:call-template name="unroll-user-distinct"/>
      <xsl:apply-templates select="adl:head|adl:top|adl:foot|adl:field|
                           adl:fieldgroup|adl:auxlist|adl:verb|
                           adl:permission|adl:pragma"/>
    </form>
  </xsl:template>

  <!-- In practice it's likely only to be lists which have properties='user-distinct', 
  but the grammar allows this for pages and forms as well so we'll deal with it -->
  <xsl:template match="adl:page[ @properties='user-distinct']">
    <page properties='listed'>
      <!-- copy the name attribute through -->
      <xsl:apply-templates select="@name"/>
      <!-- children copied through in legal order, to ensure the document remains valid -->
      <xsl:apply-templates select="adl:documentation"/>
      <!-- unroll the properties -->
      <xsl:call-template name="unroll-user-distinct"/>
      <xsl:apply-templates select="adl:head|adl:top|adl:foot|adl:field|
                           adl:fieldgroup|adl:auxlist|adl:verb|
                           adl:permission|adl:pragma"/>
    </page>
  </xsl:template>


  <!-- In practice it's likely only to be lists which have properties='user-distinct', 
  but the grammar allows this for pages and forms as well so we'll deal with it -->
  <xsl:template match="adl:list[ @properties='user-distinct']">
    <list properties='listed'>
      <!-- copy the name attribute through -->
      <xsl:apply-templates select="@name"/>
      <!-- children copied through in legal order, to ensure the document remains valid -->
      <xsl:apply-templates select="adl:documentation"/>
      <!-- unroll the properties -->
      <xsl:call-template name="unroll-user-distinct"/>
      <xsl:apply-templates select="adl:head|adl:top|adl:foot|adl:field|
                           adl:fieldgroup|adl:auxlist|adl:verb|
                           adl:permission|adl:pragma"/>
    </list>
  </xsl:template>

  <!-- unroll all the explicit properties in the ancestor entity of 
  the context (assumed to be form, page or list) into a list of fields -->
  <xsl:template name="unroll-properties">
    <xsl:for-each select="ancestor::adl:entity/descendant::adl:property |
                  ancestor::adl:entity/descendant::adl:one-to-many |
                  ancestor::adl:entity/descendant::adl:many-to-many |
                  ancestor::adl:entity/descendant::adl:many-to-one">
      <field>
        <xsl:attribute name="property">
          <xsl:value-of select="@name"/>
        </xsl:attribute>
      </field>
    </xsl:for-each>
  </xsl:template>

  <!-- unroll all the user-distinct properties in the ancestor entity of 
  the context (assumed to be form, page or list) into a list of fields.
  NOTE that n-to-n properties cannot currently be user-distinct and are
  therefore not inspected -->
  <xsl:template name="unroll-user-distinct">
    <xsl:for-each select="ancestor::adl:entity/descendant::adl:property[@distinct='user']">
      <field>
        <xsl:attribute name="property">
          <xsl:value-of select="@name"/>
        </xsl:attribute>
      </field>
    </xsl:for-each>
  </xsl:template>

  <!-- Language constraints -->
  <xsl:template match="property[ @type = 'string' and not( number( @size))]">
    <xsl:message terminate="yes">
      ADL ERROR: Properties of type 'string' must have a valid value for 'size'
    </xsl:message>
  </xsl:template>

  <xsl:template match="property[ @type = 'entity' and not( @entity)]">
    <xsl:message terminate="yes">
      ADL ERROR: Properties of type 'entity' must have a valid value for 'entity'
    </xsl:message>
  </xsl:template>

  <xsl:template match="property[ @type = 'defined' and not( @typedef)]">
    <xsl:message terminate="yes">
      ADL ERROR: Properties of type 'defined' must have a valid value for 'typedef'
    </xsl:message>
  </xsl:template>


  <!-- copy anything that isn't explicitly matched -->
  <xsl:template match="@* | node()">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>