<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <!--
    xrd2header.xsl
    Copyright (c) 2004 Llamagraphics, Inc.
    http://www.llamagraphics.com/developer/

    Permission is hereby granted, free of charge, to any person
    obtaining a copy of this software, xrd2header.xsl, and associated
    documentation files (the "Software"), to deal in the Software
    without restriction, including without limitation the rights to
    use, copy, modify, merge, publish, distribute, sublicense, and/or
    sell copies of the Software, and to permit persons to whom the
    Software is furnished to do so, subject to the following
    conditions:

    The above copyright notice and this permission notice shall be
    included in all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
    WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    DEALINGS IN THE SOFTWARE.

    Please send praise, bug reports, suggestions and improvements to
    Stuart A. Malone <samalone@llamagraphics.com>.
  -->

  <!--
    This XSL file is designed to create a C/C++ header file from a
    Palm Resource (XRD) file.  It generates #defines for the resources
    based on COMMENT attributes in the XRD file, mapping the resource
    names (comments) to the resource ID numbers.  It also provides
    identifiers for alert buttons, commented form objects and menu
    items.

    The macro names generated by this file are designed to match the
    ones produced by Metrowerks Constructor for Palm OS.  That is, if
    you import a Constructor .rsrc file using PalmSource's GenerateXRD
    utility and then run the XRD file through this transform, you
    should get the same macro names that Constructor would have
    generated.  In order to maintain this compatibility, this file
    reproduces some quirks from Constructor, like sometimes
    capitalizing the first letter of a macro name even when the
    original name/comment is in lower case.

    Use this file in conjunction with your favorite xslt processor.
    My favorite one right now is xsltproc, available from
    http://xmlsoft.org/XSLT/xsltproc2.html.

        xsltproc xrd2header.xsl resources.xrd > resources.h

    There are some optional parameters that control the output format.
    See the comments below.
  -->

  <!--
    Change history:

    Version 1.0, January 18, 2004
    Initial release to Palm OS 6 Syncup mailing list.

    Version 1.1
    Added missing template for FORM_SELECTOR_TRIGGER.
    Added code to only append the type name if it is not already
    in the comment, since this appears to be how Constructor works.

    Version 1.2, September 23, 2004
    Added templates for missing resource types.

    Version 1.3, October 19, 2004
    Fixed output code to strip international characters so that
    invalid symbol names will not be generated.  Fixed menu item
    code to recognize menu separators and not output them.

    Version 1.4, March 27, 2006
    Fixed name generation so that numbers are retained in symbol
    names.  Tweaked generation guard macro name.
  -->
  
  <!--
    If you define the "guard" parameter, the entire file will be
    wrapped in an #ifndef to prevent multiple definitions.
  -->
  <xsl:param name="guard" select="''"/>

  <!--
    If you define the "namespace" parameter, all of the definitions
    will be put into the specified C++ namespace.  In fact, you can
    provide a ::-delimited list of namespace names, and the
    definitions will be put into that nested namespace.
  -->
  <xsl:param name="namespace" select="''"/>

  <!--
    We can generate three different kinds of output.  Set the format
    parameter to "c" to get #defines.  Set format to "c++" to get
    "static const uint16_t".  Set format to "xml" to get a simple XML
    format suitable for input to other XSL transforms.
  -->
  <xsl:param name="format">
    <xsl:choose>
      <xsl:when test="string-length($namespace) > 0">
	<xsl:text>c++</xsl:text>
      </xsl:when>
      <xsl:otherwise>
	<xsl:text>c</xsl:text>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:param>

  <!--
    By default, each line is terminated with a Unix newline, but you
    can customize this to produce Macintosh or Windows-style newlines.
  -->
  <xsl:param name="newline" select="'&#x0A;'"/>

  <xsl:strip-space elements="*"/>

  <xsl:output method="text" encoding="UTF-8"/>

  <!--
    The new XRD format duplicates menus that were shared under the old
    scheme, so there may be menu items with the same name and ID in
    different menu bars.  In order to avoid having multiple
    definitions for the same symbol, we create an index of menu items
    by name and then only output the first ID for any given name.

    This could cause trouble if two menus had the same comment and
    contained menu items with the same title but different ids.
  -->
  <xsl:key name="menu-items" match="MENU_ITEM" use="concat(../../@COMMENT, TITLE)"/>

  <xsl:param name="prefix">
    <xsl:call-template name="namespace-indentation">
      <xsl:with-param name="list" select="$namespace"/>
    </xsl:call-template>
    <xsl:choose>
      <xsl:when test="$format = 'c++'">static const uint16_t </xsl:when>
      <xsl:when test="$format = 'xml'">    &lt;set name="</xsl:when>
      <xsl:otherwise>#define </xsl:otherwise>
    </xsl:choose>
  </xsl:param>

  <xsl:param name="infix">
    <xsl:choose>
      <xsl:when test="$format = 'c++'"> = </xsl:when>
      <xsl:when test="$format = 'xml'">" value="</xsl:when>
      <xsl:otherwise><xsl:text> </xsl:text></xsl:otherwise>
    </xsl:choose>
  </xsl:param>

  <xsl:param name="suffix">
    <xsl:choose>
      <xsl:when test="$format = 'c++'">;</xsl:when>
      <xsl:when test="$format = 'xml'">"/&gt;</xsl:when>
      <xsl:otherwise></xsl:otherwise>
    </xsl:choose>
  </xsl:param>

  <xsl:template match="/">
    <xsl:choose>
      <xsl:when test="$format = 'xml'">
	<xsl:text>&lt;?xml version="1.0" encoding="UTF-8"?&gt;</xsl:text>
	<xsl:value-of select="$newline"/>
	<xsl:text>&lt;mappings&gt;</xsl:text>
	<xsl:value-of select="$newline"/>
	<xsl:apply-templates/>
	<xsl:text>&lt;/mappings&gt;</xsl:text>
	<xsl:value-of select="$newline"/>
      </xsl:when>
      <xsl:otherwise>
	<xsl:text>/* This header file was automatically generated from a .xrd file</xsl:text>
	<xsl:value-of select="$newline"/>
	<xsl:text>   using xrd2header.xsl.  You should probably edit the source</xsl:text>
	<xsl:value-of select="$newline"/>
	<xsl:text>   rather than this file. */</xsl:text>
	<xsl:value-of select="$newline"/>
	<xsl:value-of select="$newline"/>
	<xsl:if test="$guard">
	  <xsl:variable name="fullguard" select="concat('_', translate($guard, 'abcdefghijklmnopqrstuvwxyz. -', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ___'), '_')"/>
	  <xsl:text>#ifndef </xsl:text>
	  <xsl:value-of select="$fullguard"/>
	  <xsl:value-of select="$newline"/>
	  <xsl:text>#define </xsl:text>
	  <xsl:value-of select="$fullguard"/>
	  <xsl:value-of select="$newline"/>
	</xsl:if>
	<xsl:text>#ifndef __GNUC__</xsl:text>
	<xsl:value-of select="$newline"/>
	<xsl:text>#  pragma once</xsl:text>
	<xsl:value-of select="$newline"/>
	<xsl:text>#endif</xsl:text>
	<xsl:value-of select="$newline"/>
	<xsl:value-of select="$newline"/>
	<xsl:call-template name="open-namespace">
	  <xsl:with-param name="list" select="$namespace"/>
	</xsl:call-template>
	<xsl:apply-templates/>
	<xsl:call-template name="close-namespace">
	  <xsl:with-param name="list" select="$namespace"/>
	</xsl:call-template>
	    <xsl:if test="$guard">
	      <xsl:text>#endif</xsl:text>
	      <xsl:value-of select="$newline"/>
	    </xsl:if>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <xsl:template name="open-namespace">
    <xsl:param name="list" select="''"/>
    <xsl:param name="indent" select="''"/>
    <xsl:choose>
      <xsl:when test="$list = ''">
      </xsl:when>
      <xsl:when test="contains($list, '::')">
	<xsl:value-of select="$indent"/>
	<xsl:text>namespace </xsl:text>
	<xsl:value-of select="substring-before($list, '::')"/>
	<xsl:text> {</xsl:text>
	<xsl:value-of select="$newline"/>
	<xsl:call-template name="open-namespace">
	  <xsl:with-param name="list" select="substring-after($list, '::')"/>
	  <xsl:with-param name="indent" select="concat($indent, '    ')"/>
	</xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
	<xsl:value-of select="$indent"/>
	<xsl:text>namespace </xsl:text>
	<xsl:value-of select="$list"/>
	<xsl:text> {</xsl:text>
	<xsl:value-of select="$newline"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <xsl:template name="close-namespace">
    <xsl:param name="list" select="''"/>
    <xsl:param name="indent" select="''"/>
    <xsl:choose>
      <xsl:when test="$list = ''">
      </xsl:when>
      <xsl:when test="contains($list, '::')">
	<xsl:call-template name="close-namespace">
	  <xsl:with-param name="list" select="substring-after($list, '::')"/>
	  <xsl:with-param name="indent" select="concat($indent, '    ')"/>
	</xsl:call-template>
	<xsl:value-of select="$indent"/>
	<xsl:text>} </xsl:text>
	<xsl:value-of select="$newline"/>
      </xsl:when>
      <xsl:otherwise>
	<xsl:value-of select="$indent"/>
	<xsl:text>}</xsl:text>
	<xsl:value-of select="$newline"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <xsl:template name="namespace-indentation">
    <xsl:param name="list" select="''"/>
    <xsl:choose>
      <xsl:when test="$list = ''">
      </xsl:when>
      <xsl:when test="contains($list, '::')">
        <xsl:call-template name="namespace-indentation">
          <xsl:with-param name="list" select="substring-after($list, '::')"/>
        </xsl:call-template>
        <xsl:text>    </xsl:text>
      </xsl:when>
      <xsl:otherwise>
        <xsl:text>    </xsl:text>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <xsl:template name="text-to-symbol">
    <xsl:param name="text" select="''"/>
    <xsl:if test="$text">
      <xsl:if test="contains('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789', substring($text, 1, 1))">
        <xsl:value-of select="substring($text, 1, 1)"/>
      </xsl:if>
      <xsl:call-template name="text-to-symbol">
        <xsl:with-param name="text" select="substring($text, 2)"/>
      </xsl:call-template>
    </xsl:if>
  </xsl:template>

  <xsl:template name="output-definition">
    <xsl:param name="symbol"/>
    <xsl:param name="value"/>
    <xsl:param name="type" select="''"/>
    <xsl:param name="capitalize" select="false()"/>

    <xsl:variable name="compact-symbol">
      <xsl:call-template name="text-to-symbol">
        <xsl:with-param name="text" select="$symbol"/>
      </xsl:call-template>
    </xsl:variable>

    <xsl:variable name="typed-symbol">
      <xsl:value-of select="$compact-symbol"/>
      <xsl:if test="substring($compact-symbol, string-length($compact-symbol) - string-length($type) + 1, string-length($type)) != $type">
        <xsl:value-of select="$type"/>
      </xsl:if>
    </xsl:variable>

    <xsl:value-of select="$prefix"/>
    <xsl:choose>
      <xsl:when test="$capitalize">
        <xsl:value-of select="concat(translate(substring($typed-symbol, 1, 1), 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'), substring($typed-symbol, 2, string-length($typed-symbol) - 1))"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$typed-symbol"/>
      </xsl:otherwise>
    </xsl:choose>
    <xsl:value-of select="$infix"/>
    <xsl:value-of select="normalize-space($value)"/>
    <xsl:value-of select="$suffix"/>
    <xsl:value-of select="$newline"/>
  </xsl:template>

  <xsl:template match="ALERT_RESOURCE[@COMMENT]">
    <xsl:call-template name="output-definition">
      <xsl:with-param name="symbol" select="@COMMENT"/>
      <xsl:with-param name="value" select="@RESOURCE_ID"/>
      <xsl:with-param name="type" select="'Alert'"/>
    </xsl:call-template>
    <xsl:apply-templates/>
    <xsl:value-of select="$newline"/>
  </xsl:template>

  <xsl:template match="ALERT_RESOURCE[@COMMENT]/BUTTONS/TEXT">
    <xsl:call-template name="output-definition">
      <xsl:with-param name="symbol" select="concat(../../@COMMENT, .)"/>
      <xsl:with-param name="value" select="position() - 1"/>
    </xsl:call-template>
  </xsl:template>

  <xsl:template match="APP_INFO_STRINGS_RESOURCE[@COMMENT]">
    <xsl:call-template name="output-definition">
      <xsl:with-param name="symbol" select="@COMMENT"/>
      <xsl:with-param name="value" select="@RESOURCE_ID"/>
      <xsl:with-param name="type" select="'AppInfoStr'"/>
    </xsl:call-template>
  </xsl:template>

  <xsl:template match="APP_ICON_BITMAP_RESOURCE[@COMMENT]">
    <xsl:call-template name="output-definition">
      <xsl:with-param name="symbol" select="@COMMENT"/>
      <xsl:with-param name="value" select="@RESOURCE_ID"/>
      <xsl:with-param name="type" select="'AppIconFamily'"/>
    </xsl:call-template>
  </xsl:template>

  <xsl:template match="BITMAP_RESOURCE[@COMMENT]">
  	<!-- Constructor had separate sections for Bitmaps and
  		 BitmapFamilies, but these are merged in XRD format
  		 and we don't know which was used originally, so
  		 generate both definitions. -->
    <xsl:call-template name="output-definition">
      <xsl:with-param name="symbol" select="@COMMENT"/>
      <xsl:with-param name="value" select="@RESOURCE_ID"/>
      <xsl:with-param name="type" select="'Bitmap'"/>
    </xsl:call-template>
    <xsl:call-template name="output-definition">
      <xsl:with-param name="symbol" select="@COMMENT"/>
      <xsl:with-param name="value" select="@RESOURCE_ID"/>
      <xsl:with-param name="type" select="'BitmapFamily'"/>
    </xsl:call-template>
  </xsl:template>

  <xsl:template match="FORM_RESOURCE[@COMMENT]">
    <xsl:call-template name="output-definition">
      <xsl:with-param name="symbol" select="@COMMENT"/>
      <xsl:with-param name="value" select="@RESOURCE_ID"/>
      <xsl:with-param name="capitalize" select="true()"/>
      <xsl:with-param name="type" select="'Form'"/>
    </xsl:call-template>
    <xsl:apply-templates/>
    <xsl:value-of select="$newline"/>
  </xsl:template>

  <xsl:template match="FORM_RESOURCE[@COMMENT]/FORM_OBJECTS/FORM_GADGET[@COMMENT]">
    <xsl:call-template name="output-definition">
      <xsl:with-param name="symbol" select="concat(../../@COMMENT, @COMMENT)"/>
      <xsl:with-param name="value" select="ID"/>
      <xsl:with-param name="type" select="'Gadget'"/>
    </xsl:call-template>
  </xsl:template>

  <xsl:template match="FORM_RESOURCE[@COMMENT]/FORM_OBJECTS/FORM_BUTTON[@COMMENT]">
    <xsl:call-template name="output-definition">
      <xsl:with-param name="symbol" select="concat(../../@COMMENT, @COMMENT)"/>
      <xsl:with-param name="value" select="ID"/>
      <xsl:with-param name="type" select="'Button'"/>
    </xsl:call-template>
  </xsl:template>

  <xsl:template match="FORM_RESOURCE[@COMMENT]/FORM_OBJECTS/FORM_FIELD[@COMMENT]">
    <xsl:call-template name="output-definition">
      <xsl:with-param name="symbol" select="concat(../../@COMMENT, @COMMENT)"/>
      <xsl:with-param name="value" select="ID"/>
      <xsl:with-param name="type" select="'Field'"/>
    </xsl:call-template>
  </xsl:template>

  <xsl:template match="FORM_RESOURCE[@COMMENT]/FORM_OBJECTS/FORM_LABEL[@COMMENT]">
    <xsl:call-template name="output-definition">
      <xsl:with-param name="symbol" select="concat(../../@COMMENT, @COMMENT)"/>
      <xsl:with-param name="value" select="ID"/>
      <xsl:with-param name="type" select="'Label'"/>
    </xsl:call-template>
  </xsl:template>

  <xsl:template match="FORM_RESOURCE[@COMMENT]/FORM_OBJECTS/FORM_CHECKBOX[@COMMENT]">
    <xsl:call-template name="output-definition">
      <xsl:with-param name="symbol" select="concat(../../@COMMENT, @COMMENT)"/>
      <xsl:with-param name="value" select="ID"/>
      <xsl:with-param name="type" select="'Checkbox'"/>
    </xsl:call-template>
  </xsl:template>

  <xsl:template match="FORM_RESOURCE[@COMMENT]/FORM_OBJECTS/FORM_GRAPHIC_BUTTON[@COMMENT]">
    <xsl:call-template name="output-definition">
      <xsl:with-param name="symbol" select="concat(../../@COMMENT, @COMMENT)"/>
      <xsl:with-param name="value" select="ID"/>
      <xsl:with-param name="type" select="'GraphicButton'"/>
    </xsl:call-template>
  </xsl:template>

  <xsl:template match="FORM_RESOURCE[@COMMENT]/FORM_OBJECTS/FORM_GRAPHIC_PUSH_BUTTON[@COMMENT]">
    <xsl:call-template name="output-definition">
      <xsl:with-param name="symbol" select="concat(../../@COMMENT, @COMMENT)"/>
      <xsl:with-param name="value" select="ID"/>
      <xsl:with-param name="type" select="'GraphicPushButton'"/>
    </xsl:call-template>
  </xsl:template>

  <xsl:template match="FORM_RESOURCE[@COMMENT]/FORM_OBJECTS/FORM_POPUP_TRIGGER[@COMMENT]">
    <xsl:call-template name="output-definition">
      <xsl:with-param name="symbol" select="concat(../../@COMMENT, @COMMENT)"/>
      <xsl:with-param name="value" select="ID"/>
      <xsl:with-param name="type" select="'PopTrigger'"/>
    </xsl:call-template>
  </xsl:template>

  <xsl:template match="FORM_RESOURCE[@COMMENT]/FORM_OBJECTS/FORM_SCROLLBAR[@COMMENT]">
    <xsl:call-template name="output-definition">
      <xsl:with-param name="symbol" select="concat(../../@COMMENT, @COMMENT)"/>
      <xsl:with-param name="value" select="ID"/>
      <xsl:with-param name="type" select="'ScrollBar'"/>
    </xsl:call-template>
  </xsl:template>

  <xsl:template match="FORM_RESOURCE[@COMMENT]/FORM_OBJECTS/FORM_TABLE[@COMMENT]">
    <xsl:call-template name="output-definition">
      <xsl:with-param name="symbol" select="concat(../../@COMMENT, @COMMENT)"/>
      <xsl:with-param name="value" select="ID"/>
      <xsl:with-param name="type" select="'Table'"/>
    </xsl:call-template>
  </xsl:template>

  <xsl:template match="FORM_RESOURCE[@COMMENT]/FORM_OBJECTS/FORM_LIST[@COMMENT]">
    <xsl:call-template name="output-definition">
      <xsl:with-param name="symbol" select="concat(../../@COMMENT, @COMMENT)"/>
      <xsl:with-param name="value" select="ID"/>
      <xsl:with-param name="type" select="'List'"/>
    </xsl:call-template>
  </xsl:template>

  <xsl:template match="FORM_RESOURCE[@COMMENT]/FORM_OBJECTS/FORM_SELECTOR_TRIGGER[@COMMENT]">
    <xsl:call-template name="output-definition">
      <xsl:with-param name="symbol" select="concat(../../@COMMENT, @COMMENT, 'SelTrigger')"/>
      <xsl:with-param name="value" select="ID"/>
      <xsl:with-param name="type" select="'SelTrigger'"/>
    </xsl:call-template>
  </xsl:template>

  <xsl:template match="FORM_RESOURCE[@COMMENT]/FORM_OBJECTS/FORM_PUSH_BUTTON[@COMMENT]">
    <xsl:call-template name="output-definition">
      <xsl:with-param name="symbol" select="concat(../../@COMMENT, @COMMENT)"/>
      <xsl:with-param name="value" select="ID"/>
      <xsl:with-param name="type" select="'PushButton'"/>
    </xsl:call-template>
  </xsl:template>

  <xsl:template match="MENU_BAR_RESOURCE[@COMMENT]">
    <xsl:call-template name="output-definition">
      <xsl:with-param name="symbol" select="@COMMENT"/>
      <xsl:with-param name="value" select="@RESOURCE_ID"/>
      <xsl:with-param name="type" select="'MenuBar'"/>
    </xsl:call-template>
    <xsl:apply-templates/>
    <xsl:value-of select="$newline"/>
  </xsl:template>

  <xsl:template match="MENU_ITEM[normalize-space(ID) > 0]">
    <xsl:if test="normalize-space(TITLE) != '&quot;-&quot;'">
      <xsl:variable name="symbol">
        <xsl:choose>
          <xsl:when test="@COMMENT">
            <xsl:value-of select="@COMMENT"/>
          </xsl:when>
          <xsl:otherwise>
            <xsl:value-of select="concat(../../@COMMENT, TITLE)"/>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:variable>
      <xsl:variable name="matches" select="key('menu-items', $symbol)"/>
      <xsl:if test="not($matches) or (generate-id(.) = generate-id($matches[1]))">
        <xsl:call-template name="output-definition">
          <xsl:with-param name="symbol" select="$symbol"/>
          <xsl:with-param name="value" select="ID"/>
          <xsl:with-param name="capitalize" select="true()"/>
        </xsl:call-template>
      </xsl:if>
    </xsl:if>
  </xsl:template>

  <xsl:template match="STRING_LIST_RESOURCE[@COMMENT]">
    <xsl:call-template name="output-definition">
      <xsl:with-param name="symbol" select="@COMMENT"/>
      <xsl:with-param name="value" select="@RESOURCE_ID"/>
      <xsl:with-param name="type" select="'StringList'"/>
    </xsl:call-template>
  </xsl:template>

  <xsl:template match="STRING_RESOURCE[@COMMENT]">
    <xsl:call-template name="output-definition">
      <xsl:with-param name="symbol" select="@COMMENT"/>
      <xsl:with-param name="value" select="@RESOURCE_ID"/>
      <xsl:with-param name="type" select="'String'"/>
    </xsl:call-template>
  </xsl:template>

  <xsl:template match="text()"/>

</xsl:stylesheet>
