sobota, 29 września 2012

Not so simple configuration continued - enum types


Presented in previous post solution with xml-based configuration for embedded apps lacks enumeration type handling. To solve the problem additional xslt programs are required. But first some definitions in xml schema shall be provided to describe enum types.
Here is fragment of base config_def_base.xsd file:

<schema xmlns="http://www.w3.org/2001/XMLSchema"
        targetNamespace="http://www.mycorp.com/path"
        xmlns:es="http://www.mycorp.com/path"
        elementFormDefault="qualified">

   <annotation>
      <documentation xml:lang="en">
         Default configuration file.
         The file provides base types allowed for configuration items.
         Author: Andrzej Polanski (andrzej.polanski(at)gmail.com)
      </documentation>
   </annotation>
...
   <complexType name="enum">
      <simpleContent>
         <extension base="es:keywordInternal">
            <attribute name="type" type="string" use="required" fixed="enum"/>
            <attribute name="enumTypeName" type="es:keywordInternal" use="required"/>
            <attribute name="comment" type="string" use="optional"/>
         </extension>
      </simpleContent>
   </complexType>


   <simpleType name="keywordInternal">
      <restriction base="string">
         <pattern value="[A-Za-z_]+[A-Za-z0-9_\.]+"/>
      </restriction>
   </simpleType>
...


</schema>



Fragment of base schema above defines base enum type to be used by all enumeration types. In other words extending 'es:enum' type creates new enumeration type in app configuration.
It can be also seen that name of output enum type shall be defined with 'enumTypeName'. The name is constraint with 'es:keywordInternal' type which puts C language rules on enum type name.

Concrete enumeration type can be defined as follows (config.xsd file):

<schema xmlns="http://www.w3.org/2001/XMLSchema"
        targetNamespace="http://www.mycorp.com/path"
        xmlns:es="http://www.mycorp.com/path"
        elementFormDefault="qualified">

   <include schemaLocation="config_def_base.xsd"/>

   <annotation>
      <documentation xml:lang="en">
         Default configuration file.
         The file provides base types allowed for configuration items.
         Author: Andrzej Polanski (andrzej.polanski(at)gmail.com)
      </documentation>
   </annotation>

   <element name="config">
      <complexType>
         <sequence>
...

            <element name="PARAM_ENUM">
               <complexType>
                  <simpleContent>
                     <restriction base="es:enum">
                        <enumeration value="PARAM_ENUM_VALUE_4_3"/>
                        <enumeration value="PARAM_ENUM_VALUE_16_9"/>
                        <attribute name="enumTypeName" type="es:keywordInternal" use="required" fixed="Param_Enum_T"/>
                     </restriction>
                  </simpleContent>
               </complexType>
            </element>

...
         </sequence>
      </complexType>
   </element>
</schema>

In schema defined above 'Param_Enum_T' is enum type with two values: PARAM_ENUM_VALUE_4_3 and PARAM_ENUM_VALUE_16_9.

To complicate things little more lets assume app configuration is defined in xml schema extending above and shall additionally use one more schema from different file. Therefore we have four xsd files: base definitions file, base app schema file and additional app schema file to be used by target app schema file. It shall be noticed that any (or all) of the schema files can provide enum types.
For example - here is additional schema file (config_add.xsd):
 

<schema xmlns="http://www.w3.org/2001/XMLSchema"

        targetNamespace="http://www.mycorp.com/path"
        xmlns:es="http://www.mycorp.com/path"
        elementFormDefault="qualified">

   <include schemaLocation="config_def_base.xsd"/>

   <annotation>
      <documentation xml:lang="en">
         Additional configuration file.
         Author: Andrzej Polanski (andrzej.polanski(at)gmail.com)
      </documentation>
   </annotation>

   <complexType name="config_add">
         <sequence>
            <element name="ADD_PARAM_ENUM">
               <complexType>
                  <simpleContent>
                     <restriction base="es:enum">
                        <enumeration value="ADD_PARAM_ENUM_VALUE1"/>
                        <enumeration value="ADD_PARAM_ENUM_VALUE2"/>
                        <enumeration value="ADD_PARAM_ENUM_VALUE3"/>
                        <enumeration value="ADD_PARAM_ENUM_VALUE4"/>
                        <attribute name="enumTypeName" type="es:keywordInternal" use="required" fixed="Add_Param_Enum_T"/>
                     </restriction>
                  </simpleContent>
               </complexType>
            </element>
         </sequence>
   </complexType>
</schema>


Defined above is Add_Param_Enum_T enum type with four values.

Last (target) schema file for the app is following:



<schema xmlns="http://www.w3.org/2001/XMLSchema"

        targetNamespace="http://www.mycorp.com/path"
        xmlns:es="http://www.mycorp.com/path"
        elementFormDefault="qualified">

   <include schemaLocation="config.xsd"/>
   <include schemaLocation="config_add.xsd"/>

   <element name="config">
      <complexType>
         <complexContent>
            <extension base="es:config">
               <sequence>
                  <!-- additional parameter -->
                  <element name="VARIANT1_BOOL_PARAM" type="es:bool"/>
                  <element name="ADD_CONFIG_PARAMS" type="es:config_add"/>
                  <element name="VARIANT_PARAM_ENUM">
                     <complexType>
                        <simpleContent>
                           <restriction base="es:enum">
                              <enumeration value="VARIANT_PARAM_ENUM_VALUE1"/>
                              <enumeration value="VARIANT_PARAM_ENUM_VALUE2"/>
                              <enumeration value="VARIANT_PARAM_ENUM_VALUE3"/>
                              <enumeration value="VARIANT_PARAM_ENUM_VALUE4"/>
                              <attribute name="enumTypeName" type="es:keywordInternal" use="required" fixed="Variant_Param_Enum_T"/>
                           </restriction>
                        </simpleContent>
                     </complexType>
                  </element>
               </sequence>
            </extension>
         </complexContent>
      </complexType>
   </element>
</schema>

Above is schema for the app extends 'es:config' type from config.xsd app configuration file. Also parameters from additional schema (config_add.xsd) are included (ADD_CONFIG_PARAMS element). In this scenario three enumeration types are defined, each in different xsd file.
In order to create enums definitions, xslt shall be preapared. Two new xslt transformations are required - first to create header file with enum types definitions, second to create C source file with functions required to parse xml data with enum items.


<?xml version="1.0"?>


<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                xmlns:es="http://www.mycorp.com/path">

<xsl:output omit-xml-declaration="yes"/>

<xsl:param name="appName"/>
<xsl:param name="outFileName"/>

<xsl:template match="/">
/*
 *=========================================================================
 * Automatically generated file - do not edit.
 *=========================================================================
 */

<xsl:variable name="defineName" select="concat('__', substring-before($outFileName, '.'), '_', substring-after($outFileName, '.'), '__')"/>

<xsl:text>&#xa;</xsl:text>
<xsl:value-of select="concat('#ifndef ', $defineName)"/>
<xsl:text>&#xa;</xsl:text>
<xsl:value-of select="concat('#define ', $defineName)"/>
<xsl:text>&#xa;</xsl:text>

<xsl:call-template name="findAllEnums">
   <xsl:with-param name="el" select="."/>
</xsl:call-template>

<xsl:text>&#xa;</xsl:text>
<xsl:value-of select="concat('#endif /* ', $defineName, ' */')"/>

</xsl:template>


<xsl:template name="findAllEnums">
   <xsl:param name="el"/>

   <xsl:for-each select="$el//xsd:restriction[@base='es:enum']">
<xsl:text>&#xa;</xsl:text>
<xsl:text>&#xa;</xsl:text>
<xsl:value-of select="concat('#define CONFIGURATION_', ./xsd:attribute/@fixed, ' ', ./xsd:attribute/@fixed)"/>
<xsl:text>&#xa;</xsl:text>
typedef enum {
      <xsl:for-each select="./xsd:enumeration">
         <xsl:value-of select="./@value"/>,
      </xsl:for-each>
}<xsl:value-of select="./xsd:attribute/@fixed"/>;
   <xsl:text>&#xa;</xsl:text>
   <xsl:value-of select="concat(./xsd:attribute/@fixed, ' conf_get_value_', ./xsd:attribute/@fixed, '(const char *val, size_t item_id, ', ./xsd:attribute/@fixed, ' def_val, const char *conf_names[]);')"/>
   <xsl:text>&#xa;</xsl:text>
   <xsl:value-of select="concat('void conf_print_def_value_', ./xsd:attribute/@fixed, '(size_t item_id, ', ./xsd:attribute/@fixed, ' def_val, const char *conf_names[]);')"/>
   <xsl:text>&#xa;</xsl:text>
   </xsl:for-each>

   <xsl:for-each select="$el//xsd:include">
      <xsl:call-template name="findAllEnums">
         <xsl:with-param name="el" select="document(./@schemaLocation)/xsd:schema"/>
      </xsl:call-template>
   </xsl:for-each>

</xsl:template>

</xsl:stylesheet>



Interesting point in above stylesheet is perhaps 'document()' function which allows to read data from different xml files. Here schema files are read recursively to process all enum types defined.

Next stylesheet creates C source file with definitions required to parse xml data with enum configuration items.


<?xml version="1.0"?>


<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                xmlns:es="http://www.mycorp.com/path">

<xsl:output omit-xml-declaration="yes" method="text"/>

<xsl:param name="enumHeaderFileName"/>

<xsl:template match="/">
/*
 *=========================================================================
 * Automatically generated file - do not edit.
 *=========================================================================
 */

#include &lt;assert.h&gt;
<xsl:text>&#xa;</xsl:text>
#include "config_enum_parse.h"
<xsl:text>&#xa;</xsl:text>
#include "<xsl:value-of select="$enumHeaderFileName"/>"
<xsl:text>&#xa;</xsl:text>

<xsl:call-template name="findAllEnums">
   <xsl:with-param name="el" select="."/>
</xsl:call-template>

</xsl:template>


<xsl:template name="findAllEnums">
   <xsl:param name="el"/>

   <xsl:for-each select="$el//xsd:restriction[@base='es:enum']">
      <xsl:variable name="mapEnumName" select="concat('map_enum_', ./xsd:attribute/@fixed)"/>

      <xsl:value-of select="concat('&#xa;static const char *', $mapEnumName, '[] = {')"/>
      <xsl:for-each select="./xsd:enumeration">
         <xsl:value-of select="concat('&quot;', ./@value, '&quot;, ')"/>
      </xsl:for-each>
      <xsl:text>};&#xa;</xsl:text>

      <xsl:value-of select="concat(./xsd:attribute/@fixed, ' conf_get_value_', ./xsd:attribute/@fixed, '(const char *val, size_t item_id, ', ./xsd:attribute/@fixed, ' def_val, const char *conf_names[]) {')"/>

      ssize_t i;
      <xsl:value-of select="./xsd:attribute/@fixed"/> res;
      
      i = config_get_enum_value(val, <xsl:value-of select="$mapEnumName"/>, sizeof(<xsl:value-of select="$mapEnumName"/>)/sizeof(<xsl:value-of select="$mapEnumName"/>[0]));
      res = (<xsl:value-of select="./xsd:attribute/@fixed"/>)i;

      if (-1 == i) {
         assert(0 &amp;&amp; "Wrong configuration item value for type <xsl:value-of select="./xsd:attribute/@fixed"/>");
         res = def_val;
      }
      if (res != def_val) {
         printf("%s = %s (overrides default value %s)\n", conf_names[item_id], <xsl:value-of select="$mapEnumName"/>[res], <xsl:value-of select="$mapEnumName"/>[def_val]);
      }
      return res;
      <xsl:text>}&#xa;&#xa;</xsl:text>

      <xsl:value-of select="concat('void conf_print_def_value_', ./xsd:attribute/@fixed, '(size_t item_id, ', ./xsd:attribute/@fixed, ' def_val, const char *conf_names[]) {')"/>
         printf("%s = %s\n", conf_names[item_id], <xsl:value-of select="$mapEnumName"/>[def_val]);
      <xsl:text>}&#xa;&#xa;</xsl:text>

   </xsl:for-each>

   <xsl:for-each select="$el//xsd:include">
      <xsl:call-template name="findAllEnums">
         <xsl:with-param name="el" select="document(./@schemaLocation)/xsd:schema"/>
      </xsl:call-template>
   </xsl:for-each>

</xsl:template>

</xsl:stylesheet>





Presented transformations can be executed as follows:


# create enums header file

echo "Creating enums header file config_enums.h"
xsltproc --stringparam appName MY_APP --stringparam outFileName config_enums.h enums.xsl app_schema.xsd | indent -bfda -o config_enums.h

# create enums module file
echo "Creating enums module file config_enums.c"
xsltproc --stringparam appName MY_APP --stringparam enumHeaderFileName config_enums.h enums_mod.xsl app_schema.xsd | indent -bfda -o config_enums.c



Each of the xsltproc invocations has two parameters passed (first - app name, second - name of the header file). Output of the transformation is additionally beautified with 'indent' program.

Resulting files from xlst - first header file with enum definitions:


</xsl:template>

/*
 *=========================================================================
 * Automatically generated file - do not edit.
 *=========================================================================
 */

#ifndef __config_enums_h__
#define __config_enums_h__

#define CONFIGURATION_Variant_Param_Enum_T Variant_Param_Enum_T

typedef enum
{
  VARIANT_PARAM_ENUM_VALUE1,
  VARIANT_PARAM_ENUM_VALUE2,
  VARIANT_PARAM_ENUM_VALUE3,
  VARIANT_PARAM_ENUM_VALUE4,
} Variant_Param_Enum_T;

Variant_Param_Enum_T conf_get_value_Variant_Param_Enum_T (
  const char *val,
  size_t item_id,
  Variant_Param_Enum_T def_val,
  const char *conf_names[]);
void conf_print_def_value_Variant_Param_Enum_T (
  size_t item_id,
  Variant_Param_Enum_T def_val,
  const char *conf_names[]);


#define CONFIGURATION_Param_Enum_T Param_Enum_T

typedef enum
{
  PARAM_ENUM_VALUE_4_3,
  PARAM_ENUM_VALUE_16_9,
} Param_Enum_T;

Param_Enum_T conf_get_value_Param_Enum_T (
  const char *val,
  size_t item_id,
  Param_Enum_T def_val,
  const char *conf_names[]);
void conf_print_def_value_Param_Enum_T (
  size_t item_id,
  Param_Enum_T def_val,
  const char *conf_names[]);


#define CONFIGURATION_Add_Param_Enum_T Add_Param_Enum_T

typedef enum
{
  ADD_PARAM_ENUM_VALUE1,
  ADD_PARAM_ENUM_VALUE2,
  ADD_PARAM_ENUM_VALUE3,
  ADD_PARAM_ENUM_VALUE4,
} Add_Param_Enum_T;

Add_Param_Enum_T conf_get_value_Add_Param_Enum_T (
  const char *val,
  size_t item_id,
  Add_Param_Enum_T def_val,
  const char *conf_names[]);
void conf_print_def_value_Add_Param_Enum_T (
  size_t item_id,
  Add_Param_Enum_T def_val,
  const char *conf_names[]);

#endif /* __config_enums_h__ */



Beside enum typedefs, declarations of parsing functions are included.
C source file with functions' definitions are generated like:


/*
 *=========================================================================
 * Automatically generated file - do not edit.
 *=========================================================================
 */

#include <assert.h>
#include "config_enum_parse.h"
#include "config_enums.h"

static const char *map_enum_Variant_Param_Enum_T[] = {
   "VARIANT_PARAM_ENUM_VALUE1",
   "VARIANT_PARAM_ENUM_VALUE2",
   "VARIANT_PARAM_ENUM_VALUE3",
   "VARIANT_PARAM_ENUM_VALUE4",
};

Variant_Param_Enum_T
conf_get_value_Variant_Param_Enum_T (
  const char *val,
  size_t item_id,
  Variant_Param_Enum_T def_val,
  const char *conf_names[])
{

  ssize_t i;
  Variant_Param_Enum_T res;

  i =
    config_get_enum_value (val, map_enum_Variant_Param_Enum_T,
  sizeof (map_enum_Variant_Param_Enum_T) /
  sizeof (map_enum_Variant_Param_Enum_T[0]));
  res = (Variant_Param_Enum_T) i;

  if (-1 == i)
    {
      assert (0
     &&
     "Wrong configuration item value for type Variant_Param_Enum_T");
      res = def_val;
    }
  if (res != def_val)
    {
      printf ("%s = %s (overrides default value %s)\n", conf_names[item_id],
     map_enum_Variant_Param_Enum_T[res],
     map_enum_Variant_Param_Enum_T[def_val]);
    }
  return res;
}

void
conf_print_def_value_Variant_Param_Enum_T (
  size_t item_id,
  Variant_Param_Enum_T def_val,
  const char *conf_names[])
{
  printf ("%s = %s\n", conf_names[item_id],
 map_enum_Variant_Param_Enum_T[def_val]);
}


static const char *map_enum_Param_Enum_T[] = {
   "PARAM_ENUM_VALUE_4_3",
   "PARAM_ENUM_VALUE_16_9",
};

Param_Enum_T
conf_get_value_Param_Enum_T (
  const char *val,
  size_t item_id,
  Param_Enum_T def_val,
  const char *conf_names[])
{

  ssize_t i;
  Param_Enum_T res;

  i = config_get_enum_value (val, map_enum_Param_Enum_T,
       sizeof (map_enum_Param_Enum_T) /
     sizeof (map_enum_Param_Enum_T[0]));
  res = (Param_Enum_T) i;

  if (-1 == i)
    {
      assert (0 && "Wrong configuration item value for type Param_Enum_T");
      res = def_val;
    }
  if (res != def_val)
    {
      printf ("%s = %s (overrides default value %s)\n", conf_names[item_id],
     map_enum_Param_Enum_T[res], map_enum_Param_Enum_T[def_val]);
    }
  return res;
}

void
conf_print_def_value_Param_Enum_T (
  size_t item_id,
  Param_Enum_T def_val,
  const char *conf_names[])
{
  printf ("%s = %s\n", conf_names[item_id], map_enum_Param_Enum_T[def_val]);
}


static const char *map_enum_Add_Param_Enum_T[] = {
   "ADD_PARAM_ENUM_VALUE1",
   "ADD_PARAM_ENUM_VALUE2",
   "ADD_PARAM_ENUM_VALUE3",
   "ADD_PARAM_ENUM_VALUE4", 
};

Add_Param_Enum_T
conf_get_value_Add_Param_Enum_T (
  const char *val,
  size_t item_id,
  Add_Param_Enum_T def_val,
  const char *conf_names[])
{

  ssize_t i;
  Add_Param_Enum_T res;

  i =
    config_get_enum_value (val, map_enum_Add_Param_Enum_T,
  sizeof (map_enum_Add_Param_Enum_T) /
  sizeof (map_enum_Add_Param_Enum_T[0]));
  res = (Add_Param_Enum_T) i;

  if (-1 == i)
    {
      assert (0
     && "Wrong configuration item value for type Add_Param_Enum_T");
      res = def_val;
    }
  if (res != def_val)
    {
      printf ("%s = %s (overrides default value %s)\n", conf_names[item_id],
     map_enum_Add_Param_Enum_T[res],
     map_enum_Add_Param_Enum_T[def_val]);
    }
  return res;
}

void
conf_print_def_value_Add_Param_Enum_T (
  size_t item_id,
  Add_Param_Enum_T def_val,
  const char *conf_names[])
{
  printf ("%s = %s\n", conf_names[item_id],
 map_enum_Add_Param_Enum_T[def_val]);
}




And that's it. Now we can use enumeration types and values in app configuration.
In app configuration header file like:


CONFIGURATION_ITEMS_START
...
CONFIGURATION_ITEM(MY_APP_NAME_PARAM_ENUM, Param_Enum_T, PARAM_ENUM_VALUE_4_3)
CONFIGURATION_ITEM(MY_APP_NAME_ADD_CONFIG_PARAMS_ADD_PARAM_ENUM, Add_Param_Enum_T, ADD_PARAM_ENUM_VALUE1)
CONFIGURATION_ITEM(MY_APP_NAME_VARIANT_PARAM_ENUM, Variant_Param_Enum_T, VARIANT_PARAM_ENUM_VALUE3)
...
CONFIGURATION_ITEMS_END


In "flat" configuration xml file:


<?xml version="1.0"?>
<!--Automatically generated file.-->
<configuration>
...
<MY_APP_NAME_PARAM_ENUM>PARAM_ENUM_VALUE_4_3</MY_APP_NAME_PARAM_ENUM>
<MY_APP_NAME_ADD_CONFIG_PARAMS_ADD_PARAM_ENUM>ADD_PARAM_ENUM_VALUE4</MY_APP_NAME_ADD_CONFIG_PARAMS_ADD_PARAM_ENUM>
<MY_APP_NAME_VARIANT_PARAM_ENUM>VARIANT_PARAM_ENUM_VALUE3</MY_APP_NAME_VARIANT_PARAM_ENUM>
</configuration>