poniedziałek, 1 kwietnia 2013

Weekend with GWT, Google maps and K-D trees - working example

Working example of algorithm presented in last post can be found here.
It shows map with some of my home town's bus stops. User can drag marker with and then second marker shows nearest bus stop location. Also number of closest busstops to be found can be changed with value of "number" get parameter. Additional parameters are "treedepth" and "showchecked". The former is int value which determines depth for separation lines presentation. The latter is bool value indicating whether to mark points checked during search or not.

Weekend with GWT, Google maps and K-D trees

It started from desire to remind Java language programming. How to program in Java with nice web output - obvious answer is GWT. One may say that it is obsolescent programming environment, but on the other hand it is quite mature and easy to fast-start examples. Of course client side Java is rather limited but you are always free to do normal Java programming on server side.
So, Eclipse + GWT + Chrome makes convenient programming environment for Java development with basic user web interface included (plus friendly debugging). But the biggest advantage is lack of Javascript - oh, how I hate this language, its horrible syntax, dynamic typing leading to "who knows what it is and where it is from".

Next, as I've entered Google world it is tempting and easy to use Google technologies. What I needed was access to maps.
The task is following - efficient (and quite simple - as for one weekend) K-D tree implementation  with presentation on Google maps. But how to easily access GMaps from GWT. One might expect easy integration (as for one company solutions). Indeed there is library for GWT that allows to access GMap, the project is hosted hosted at gwt-google-apis. But GMaps library presented there is for ver.2 and deprecated. If we dig dipper we can find library gwt-maps-3.8.0-pre1.zip for GMapa ver.3 there, but it is in prerelease stage. It is there since Mar 30, 2012 - it shows best how Google carries about GWT technology and why I called it obsolescent.

Lets go back to the task - K-D tree. This old and quite simple algorithm allows for efficient lookup of closest point in n-dimensional space (see wikipedia). In case of map, space is 2-dimensional, but the problem/inconvenience appears - it is not planar (Cartesian) space, but latitude-longitude coordinates on sphere (or rather GPS/WGS 84 ellipsoid). Of course, to calculate distance between coordinate points simple spherical Earth model is considered in presented example (the error is negligible even for Google as they use spherical Mercator projection - read wikipedia).
Digression: interesting page on map projections is at kartoweb.itc.nl, there is also copy of book on the topic.

First step is to create K-D tree for subsequent efficient queries. Algorithm is described in details in wikipedia, therefore only simple Java-pseudo-but-working-code implementation is presented here:

private PointNode createKDTree(LatLngCommon[] points, int from, int to, int depth) {
if (from >= to) {
return null;
}

final int axis = depth % 2;
Arrays.sort(points, from, to, new Comparator<LatLngCommon>(){
@Override
public int compare(LatLngCommon ll1, LatLngCommon ll2) {
return (0==axis)
?(Double.compare(ll1.lat, ll2.lat))
:(Double.compare(ll1.lng, ll2.lng));
}
});
int med = (from + to) / 2;
PointNode res = new PointNode();
res.point = points[med];
res.left  = createKDTree(points, from, med, depth+1);
res.right = createKDTree(points, med+1, to, depth+1);
return res;
}

In code above partition is along parallels for even and meridians for odd tree levels. Thanks to that partition is achieved by just comparing coordinates.

More difficult is second method - finding nearest point using lat-lng coords. The problem is calculation of distance between two points on sphere. One cannot use Euclidean distance as coords are not planar. Distance has to be calculated as great circle distance (shortest distance on sphere between two points).
Standard way of such distance calculation is using spherical law of cosines. But there are two issues here:
  • first - derivation of equation for lat-lng coords. From law of cosines we have:
    cos(c) = cos(a) cos(b) + sin(a) sin(b) cos(C),
    where 'a','b','c' are great circle distances and 'C' is angle between edges of length 'a' and 'b' (opposite to edge with length 'c').
    It might be tempting to think of 'C' as angle between meridian and parallel, which is right angle and cos(C) would be equal to 0. In such case 'a' would be difference between latitudes and 'b' difference between longitudes, and equation would be as simple as
    cos(c) = cos(a) cos(b),
    of course everybody knows that equation is different - why? Because parallels are not great circle distances (except for the Equator).
    Correct equation for law of cosines in lat-lng coords is following:
    cos(c) = sin(lat1) sin(lat2) + cos(lat1) cos(lat2) cos(lng1 - lng2)
    where 'c' is interesting distance between points of coords (lat1, lng1) and (lat2, lng2).
    One can find graphical representation of equation in fig.3-15 in book 'Mapping Hacks' by  Erle S.,  Gibson R.,  Walsh J..
  • second - numerical problems with cos/arccos calculation for small angles - standard way out of the problem is analytic transformation of equation to more numerically suitable called Haversine formula.
But do we really need to calculate distance? I mean distance value between points? Lets take a look at algorithm:

private LatLngCommon findNearest(PointNode root, LatLngCommon point, int depth) {

// end of recursion? if (root.left == null && root.right == null) { return root.point; }
// which partition in tree - lat or lng? final int axis = depth % 2; int cmp = (0==axis) ?(Double.compare(point.lat, root.point.lat)) :(Double.compare(point.lng, root.point.lng)); // deep with recursion to go to the bottom - find approx. best

LatLngCommon best; boolean left = false; if ((root.left != null) && (cmp < 0)) { best = findNearest(root.left, point, depth+1); left = true; } else if (root.right != null) {
best = findNearest(root.right, point, depth+1); }

// check if bottom best is better than actual point?
double best_dist = distance(point, best);

double new_dist = distance(point, root.point);

if (best_dist > new_dist) {

best = root.point;

best_dist = new_dist;

}


// there might be better on the other side of the fence

int otherside = (0==axis) ?(Double.compare(best_dist, distance_lat(point, root.point))) :(Double.compare(best_dist, distance_lng(point, root.point))); LatLngCommon best_otherside = null; if (otherside > 0) {

// get best from the other side - go to the bottom if (left) { if (root.right != null) { best_otherside = findNearest(root.right, point, depth+1); } } else { if (root.left != null) { best_otherside = findNearest(root.left, point, depth+1); } }

// if other side found and better - we have new best if (best_otherside != null) { if (best_dist > distance(point, best_otherside)) { best = best_otherside; } } } 


return best;
}

Please take a look how distance is used. What we need to know is comparison result and not a distance value. Valuable is information if distance between one pair of points is larger/smaller than distance between other pair of points. Now, please note that when:
dist1 > dist2, then
cos(dist1) < cos(dist2),
assuming that dist1 and dist2 are reasonable, i.e. from -180 deg to 180 deg. As cos is even, we do not have to carry about absolute value of distance.
Therefore it is not needed for the algorithm to calculate arccos (or arcsin - for Haversine formula).

Can we do better?
Additional benefits can come from pre-calculating trigonometric functions values, to avoid calculation during nearest point search, i.e.:
cos(dist) = sin(lat1) sin(lat2) + cos(lat1) cos(lat2) cos(lng1 - lng2), then
cos(dist) = sin(lat1) sin(lat2) + cos(lat1) cos(lat2) [sin(lng1) sin(lng2) + cos(lng1) cos(lng2)].

Last optimization is passing as result best point together with distance (cos of distance). This avoids calculating distance (cos of distance) multiple times as result travels back from recursion.

Assuming, final code can look like:

private CosDistance findNearest(PointNode root, LatLngCommon point, int depth) { if (root.left == null && root.right == null) { return new CosDistance(point, root.point); } final int axis = depth % 2; int cmp = (0==axis) ?(Double.compare(point.lat, root.point.lat)) :(Double.compare(point.lng, root.point.lng)); CosDistance best; boolean left = false; if ((root.left != null) && (cmp < 0)) { best = findNearest(root.left, point, depth+1); left = true; } else if (root.right != null) {
best = findNearest(root.right, point, depth+1); }
CosDistance cos_new_dist = new CosDistance(point, root.point); if (best.cos_dist < cos_new_dist.cos_dist) { best = cos_new_dist; } int otherside = (0==axis) ?(Double.compare(best.cos_dist, cos_new_dist.sin_from_lat__sin_to_lat + cos_new_dist.cos_from_lat__cos_to_lat)) :(Double.compare(best.cos_dist, 1.0 + point.cos_lat*point.cos_lat*(cos_new_dist.sin_from_lng__sin_to_lng__cos_from_lng__cos_to_lng - 1.0))); if (otherside <= 0) { CosDistance best_otherside = null; if (left) { if (root.right != null) { best_otherside = findNearest(root.right, point, depth+1); } } else { if (root.left != null) { best_otherside = findNearest(root.left, point, depth+1); } } if (best_otherside != null) {
if (best.cos_dist < best_otherside.cos_dist) { best = best_otherside; } } }
return best; }

With following helper class used for result:

private class CosDistance {
public LatLngCommon to_point; public double sin_from_lat__sin_to_lat; public double cos_from_lat__cos_to_lat; public double sin_from_lng__sin_to_lng__cos_from_lng__cos_to_lng; public double cos_dist;
public CosDistance(LatLngCommon from, LatLngCommon to) { to_point = to; sin_from_lat__sin_to_lat = from.sin_lat*to.sin_lat; cos_from_lat__cos_to_lat = from.cos_lat*to.cos_lat; sin_from_lng__sin_to_lng__cos_from_lng__cos_to_lng = from.sin_lng*to.sin_lng + from.cos_lng*to.cos_lng; cos_dist = sin_from_lat__sin_to_lat + cos_from_lat__cos_to_lat*(sin_from_lng__sin_to_lng__cos_from_lng__cos_to_lng); } }


czwartek, 14 marca 2013

callbacks - composition vs.inheritance

The problem - how to implement base mechanism that contains callback.
For example - MessageHandler class - allows user to start action (via callback) when appropriate message arrives.

class MessageHandler
{
...
public:
   virtual void doMessageHande(const Message &m) = 0;
   MessageHandler(MessageHandlerRegistry &reg)
   {
      reg.registry(*this); // now registry can call doMessageHandle() when message arrives
   }
...
};



Solution 1 - base class (inheritance)

Problem - for example when class Auses MessageHandler. Class A is also base class for class B that should also use message handler (for its own purpose). In such situation callback implemented as virtual function will improperly end in class B also for message handler from class A.

class A : private MessageHandler
{
...
   void doMessageHandle(const Message &m); // won't get called for B objects
   A(MessageHandlerRegistry &reg)
   : MessageHandler(reg) // registry A object as message handler
...
};
class B : public A, private MessageHandler
{
...
   void doMessageHandle(const Message &m);
   B(MessageHandlerRegistry &reg)
   : A(reg),
     MessageHandler(reg) // registry B object as message handler
...
};


If somewhere in program we have MessageHandler pointer or reference that points to B object (e.g. message registry), doMessageHandle from B will be invoked. This is normal behavior of virtual functions. Note that here we do not want to have one base class object therefore virtual inheritance is not proper approach. What we need is to allow B object to handle messages in both MessageHandlers (from A and B classes).


Solution 2 - composition - additional class

To break virtual function chain have to break inheritance and use additional class to contain message handler.
Even in solution 1 we had private inheritance it suggest that this is not the inheritance (class A is not a MessageHandler). Now we know that we have to go further.
To break inheritance chain we can define additional classes as inheritance endpoints. Here we have embedded class AMsgHandler that encapsulates callback.

class A
{
...

   void doMessageHandle(const Message &m);
   class AMsgHander : private MessageHandler
   {
      A &_a;
      void doMessageHandle(const Message &m)
      {
         _a.doMessageHandle(m);
      }
   public:
      AMsgHander(MessageHandlerRegistry &reg, A &a)
      : MessageHandler(reg),
        _a(a)
      {}

   };
   AMsgHandler msg_handler;
public:

   A(MessageHandlerRegistry &reg) : msg_handler(reg, *this)
...
};


This solution will work but here we have lot of additional code what make the code not very convenient to extend.


Solution 3 - composition - generalized callback - delegate

The only element that has to be excluded is callback. Therefore it is useful to have some common solution for all callback-related problems. The answer is delegate - the world of member function pointers and not so commonly known '.*' and '->*' operators and template programming.
The point is to have one line solution that makes code clear and easy to extend and maintain.
Delegates are deeply explored topic in C++ (see references). Problem with general callback is that they are general, what in such strict language as C++ is difficult to achieve.
If we use some of existing delegate implementations (e.g. BOOST) we can simplify solution.
Below we have solution that do not change MessageHandlerRegistry - there is still MessageHandler class that acts as interface between registry and end-user class. If we change registry class then we can get rid of MessageHandler class and use directly callback in registry (and also get rid of "handler" member from A class).


class MessageHandler
{
...
public:

   typedef function1<void, const Message&> MessageCallback;
private:
   MessageCallback _callback;
public:
   void doMessageHande(const Message &m)
   {
      _callback(m);
   }
   void MessageHandler(MessageHandlerRegistry &reg, const MessageCallback &c)
   : _callback(c)
   {
      reg.registry(*this);
   }
...
};


class A
{

private:
   MessageHandler handler;
   void doMessageHandle(const Message &m);
...
public:

   A(MessageHandlerRegistry &reg)
   : handler(reg, std::bind1st(std::mem_fun(&A::
doMessageHandle), this))
...
};





Of course presented problem is simplified. In real world it can be obscured. For example the MessageHandler class can be used by TimeoutHandler class and also by some user's DoItRight class. DoItRight class is also using TimeoutHandler. In the end DoItRight object gets MessageHandler's callbacks from TimeoutHandler part and also from its own MessageHandler part. In case of inheritance we end up in problem presented in solution 1.


References

http://www.boost.org/doc/html/function.html
http://www.codeproject.com/Articles/7150/Member-Function-Pointers-and-the-Fastest-Possible
http://www.codeproject.com/Articles/11015/The-Impossibly-Fast-C-Delegates

czwartek, 28 lutego 2013

Function templates inlining

It is typical to think that inline function is kind of space-time trade-off. We waste some memory (redundant code) to avoid some operations (function call). But what in case of function that is invoked only once? In that case inlining will make code also smaller (probably), because there is no code related to function call, also additional compiler optimization is possible.
Such idea might be especially tempting when dealing with function templates. It is quite common for template definitions to "explode" as used with different types of parameters (especially when dealing with kind of metaprogramming). When usage for each version of the function is very limited (e.g. once), "inline" may allow compiler to produce code that is smaller than without inlining.
Of course it is not always true, as "inline" is merely hint for compiler.
Here is output from my one case test - 3 function templates used with different argument types before and after inlining.
For "gcc -O1" (ver. 4.5.3)
Size of object files - 238kB inlined, 244kB not inlined, size of executable 1354 v.s. 1357kB.
As I've checked with "nm" not every function definition instantiated from template was inlined.

piątek, 28 grudnia 2012

Google code hosted C++ projects

Here is short list of IMHO interesting projects hosted at google code (C++ only)


linking and optimization

Some info regarding shared libraries and link optimization (just not to forget).

Command to check library's specific headers, e.g. library "soname" and required library and their versions:
objdump -p libXXX.so

To display exported symbols:
nm -g -D -C --defined-only libXXX.so

Some makefile macros to create shared library:
LIB_LINKER_NAME := libXXX.so
LIB_VERSION_MAJOR := 1
LIB_VERSION_MINOR := 1
LIB_SONAME_NAME := $(LIB_LINKER_NAME).$(LIB_VERSION_MAJOR)
LIB_REAL_NAME := $(LIB_SONAME_NAME).$(LIB_VERSION_MINOR)
$(CC) -shared -o $(LIB_REAL_NAME) $(OBJ) -L$(LIB_DIR) $(LIBS) -Wl,-soname=$(LIB_SONAME_NAME) -Wl,--version-script=XXX.expmap


Export map file for shared library:
XXX.expmap:
{
  global:
    fun1;
    fun2;

  local:
    *;
};


References
http://www.akkadia.org/drepper/dsohowto.pdf
http://tldp.org/HOWTO/Program-Library-HOWTO/shared-libraries.html
http://mail-index.netbsd.org/tech-toolchain/1998/07/17/0000.html
wiki.linuxquestions.org/wiki/Library-related_Commands_and_Files
http://www.oracle.com/technetwork/articles/servers-storage-dev/linkinglibraries-396782.html
http://www.shrubbery.net/solaris9ab/SUNWdev/LLM/p17.html
http://www.iecc.com/linker/linker10.html
http://accu.org/index.php/journals/1372
http://www.sco.com/developers/devspecs/gabi41.pdf

Reference regarding ldconfig and embedded doubts
http://sourceware.org/ml/crossgcc/2008-11/msg00001.html

References regarding prelinking
www.mvista.com/download/vision08/Strategies-to-improve-embedded-Linux-application-performance-Vision2008.pdf
http://www.sourceware.org/ml/binutils/2006-06/msg00418.html
http://people.redhat.com/jakub/prelink.pdf
http://lwn.net/Articles/341244/

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>