Index: trunk/psLib/src/types/psMetadataItemCompare.c
===================================================================
--- trunk/psLib/src/types/psMetadataItemCompare.c	(revision 19885)
+++ trunk/psLib/src/types/psMetadataItemCompare.c	(revision 19892)
@@ -4,4 +4,5 @@
 
 #include <stdio.h>
+#include <stdlib.h>
 #include <ctype.h>
 #include <string.h>
@@ -15,100 +16,212 @@
 #include "psMetadataItemCompare.h"
 
+// Parse a string according to some provided format; check for leftovers
+#define PARSE_STRING(GOOD, RESULT, STRING, FORMAT) \
+    bool GOOD = true; \
+    { \
+        int read;                       /* Number of characters read */ \
+        if (sscanf(STRING, "%" FORMAT "%n", &(RESULT), &read) <= 0) { \
+            GOOD = false; \
+        } \
+        if (read < strlen(STRING)) { \
+            /* Something's left over in the string, so they don't match */ \
+            GOOD = false; \
+        } \
+    }
+
+// Parse a string as a sexagesimal value
+static bool parseSexagesimal(double *value, const char *string)
+{
+    int big, medium;                    // Big and medium-sized values
+    float small;                        // Small value
+    int read;                           // Number of characters read
+    if (sscanf(string, "%d:%d:%f%n", &big, &medium, &small, &read) != 3 &&
+        sscanf(string, "%d %d %f%n", &big, &medium, &small, &read) != 3) {
+        return false;
+    }
+    if (read < strlen(string)) {
+        // Something's left over in the string, so they don't match
+        return false;
+    }
+    *value = abs(big) + (float)medium/60.0 + small/3600.0;
+    if (big < 0) {
+        *value *= -1.0;
+    }
+    return true;
+}
+
+// Parse a string as a boolean value
+static bool parseBool(bool *value, const char *string)
+{
+    if (strcasecmp(string, "T") == 0 || strcasecmp(string, "TRUE") == 0 || strcmp(string, "1") == 0) {
+        *value = true;
+        return true;
+    }
+    if (strcasecmp(string, "F") == 0 || strcasecmp(string, "FALSE") == 0 || strcmp(string, "0") == 0) {
+        *value = false;
+        return true;
+    }
+    return false;
+}
+
+
+// Operations for comparison
 typedef enum {
-    PS_MD_OP_EQ,
-    PS_MD_OP_LT,
-    PS_MD_OP_LE,
-    PS_MD_OP_GT,
-    PS_MD_OP_GE,
-    PS_MD_OP_NE
-} psMetadataItemCompareOp;
+    COMPARE_OP_EQ,                      // Equality
+    COMPARE_OP_LT,                      // Less than
+    COMPARE_OP_LE,                      // Less than or equal
+    COMPARE_OP_GT,                      // Greater than
+    COMPARE_OP_GE,                      // Greater than or equal
+    COMPARE_OP_NE                       // Not equal
+} compareOp;
 
 /* determine boolean operator specified in comment: OP: XX (default is ==) */
-psMetadataItemCompareOp p_psMetadataItemCompareGetOp (const psMetadataItem *item) {
-
-    if (!item->comment) return PS_MD_OP_EQ;
-
-    char *p1 = strstr (item->comment, "OP:");
-    if (!p1) return PS_MD_OP_EQ;
+compareOp getCompareOp(const psMetadataItem *item) {
+
+    if (!item->comment) {
+        return COMPARE_OP_EQ;
+    }
+
+    char *p1 = strstr(item->comment, "OP:");
+    if (!p1) {
+        return COMPARE_OP_EQ;
+    }
     p1 += 3; // point to first char after @OP:
-    while (isblank(p1[0])) p1++;
+    while (isblank(p1[0])) {
+        p1++;
+    }
 
     // the string starting at p1 must contain one of the following:
-    // == or = PS_MD_OP_EQ,
-    // < PS_MD_OP_LT,
-    // <= PS_MD_OP_LE,
-    // > PS_MD_OP_GT,
-    // >= PS_MD_OP_GE,
-    // ! PS_MD_OP_NE
-    
+    // == or = COMPARE_OP_EQ,
+    // < COMPARE_OP_LT,
+    // <= COMPARE_OP_LE,
+    // > COMPARE_OP_GT,
+    // >= COMPARE_OP_GE,
+    // ! COMPARE_OP_NE
+
     // XXX a bit crude: does not catch the case of invalid chars after boolean op
-    if (!strncmp (p1, "==", 2)) return PS_MD_OP_EQ;
-    if (!strncmp (p1, "=",  1)) return PS_MD_OP_EQ;
-    if (!strncmp (p1, "<=", 2)) return PS_MD_OP_LE;
-    if (!strncmp (p1, "<",  1)) return PS_MD_OP_LT;
-    if (!strncmp (p1, ">=", 2)) return PS_MD_OP_GE;
-    if (!strncmp (p1, ">",  1)) return PS_MD_OP_GT;
-
-    return PS_MD_OP_EQ;
+    if (!strncmp (p1, "==", 2)) return COMPARE_OP_EQ;
+    if (!strncmp (p1, "=",  1)) return COMPARE_OP_EQ;
+    if (!strncmp (p1, "<=", 2)) return COMPARE_OP_LE;
+    if (!strncmp (p1, "<",  1)) return COMPARE_OP_LT;
+    if (!strncmp (p1, ">=", 2)) return COMPARE_OP_GE;
+    if (!strncmp (p1, ">",  1)) return COMPARE_OP_GT;
+
+    return COMPARE_OP_EQ;
 }
 
 // XXX better value for tolerance?
-# define EQ_TOL 1e-6
-
-# define COMPARE_CASE(TEMPLATENAME, TEMPLATETYPE, COMPARENAME, COMPARETYPENAME) \
-  case PS_TYPE_##COMPARETYPENAME: {					 \
-      TEMPLATETYPE valueC = (TEMPLATETYPE)compare->data.COMPARENAME;	 \
-      TEMPLATETYPE valueT = (TEMPLATETYPE)template->data.TEMPLATENAME;			  \
-      /* XXX check the validiy of the type casting? */                 \
-      if (valueC != compare->data.COMPARENAME) {			 \
-	  return false;						 \
-      }								 \
-      /* does template specify a boolean operation in comment? */ \
-      psMetadataItemCompareOp op = p_psMetadataItemCompareGetOp (template);  \
-      switch (op) { \
-	case PS_MD_OP_EQ: \
-	  if ((template->type == PS_TYPE_F32) || (template->type == PS_TYPE_F64)) { \
-	      result = fabs(valueT - valueC) < EQ_TOL; \
-	  } else { \
-	      result = valueT == valueC; \
-	  } \
-	  break; \
-	case PS_MD_OP_LT: \
-	  result = valueC < valueT; \
-	  break; \
-	case PS_MD_OP_LE: \
-	  result = valueC <= valueT; \
-	  break; \
-	case PS_MD_OP_GT: \
-	  result = valueC > valueT; \
-	  break; \
-	case PS_MD_OP_GE: \
-	  result = valueC >= valueT; \
-	  break; \
-	case PS_MD_OP_NE: \
-	  result = valueC != valueT; \
-	  break; \
-	default: \
-	  psAbort ("all cases should have been handled..."); \
+#define EQ_TOL 1e-6                     // Tolerance for equality
+
+// Compare values directly
+#define COMPARE_VALUES(TEMPLATE, COMPARE, FLOATINGPOINT) { \
+     /* does template specify a boolean operation in comment? */ \
+     compareOp op = getCompareOp(template);  \
+     switch (op) { \
+       case COMPARE_OP_EQ: \
+         if (FLOATINGPOINT) { \
+             return fabs(TEMPLATE - COMPARE) < EQ_TOL; \
+         } else { \
+             return TEMPLATE == COMPARE; \
+         } \
+       case COMPARE_OP_LT: \
+         return COMPARE < TEMPLATE; \
+       case COMPARE_OP_LE: \
+         return COMPARE <= TEMPLATE; \
+       case COMPARE_OP_GT: \
+         return COMPARE > TEMPLATE; \
+       case COMPARE_OP_GE: \
+         return COMPARE >= TEMPLATE; \
+       case COMPARE_OP_NE: \
+         return COMPARE != TEMPLATE; \
+       default: \
+         psAbort("all cases should have been handled..."); \
+    } \
+}
+
+#define COMPARE_NUMERICAL_CASE(TEMPLATENAME, TEMPLATETYPE, COMPARENAME, COMPARETYPENAME) \
+  case PS_TYPE_##COMPARETYPENAME: { \
+      TEMPLATETYPE valueC = (TEMPLATETYPE)compare->data.COMPARENAME; \
+      TEMPLATETYPE valueT = (TEMPLATETYPE)template->data.TEMPLATENAME; \
+      /* XXX check the validiy of the type casting? */ \
+      if (valueC != compare->data.COMPARENAME) { \
+          return false; \
       } \
-      return (result); \
-    }
-
-#define TEMPLATE_CASE(TYPENAME, NAME, TYPE)				\
-    case PS_TYPE_##TYPENAME:						\
-    switch(compare->type) {						\
-        COMPARE_CASE(NAME, TYPE, B  , BOOL);				\
-        COMPARE_CASE(NAME, TYPE, U8 , U8 );				\
-        COMPARE_CASE(NAME, TYPE, U16, U16);				\
-        COMPARE_CASE(NAME, TYPE, U32, U32);				\
-        COMPARE_CASE(NAME, TYPE, S8 , S8 );				\
-        COMPARE_CASE(NAME, TYPE, S16, S16);				\
-        COMPARE_CASE(NAME, TYPE, S32, S32);				\
-        COMPARE_CASE(NAME, TYPE, F32, F32);				\
-        COMPARE_CASE(NAME, TYPE, F64, F64);				\
-      default:								\
+      COMPARE_VALUES(valueT, valueC, (template->type == PS_TYPE_F32 || template->type == PS_TYPE_F64)); \
+      psAbort("Should never reach here."); \
+  }
+
+// Compare a string template with an int
+#define COMPARE_STRING_INT_CASE(TYPE, FORMAT) \
+  case PS_TYPE_##TYPE: { \
+    ps##TYPE valueT; \
+    PARSE_STRING(status, valueT, template->data.V, FORMAT); \
+    if (!status) { \
+        return false; \
+    } \
+    ps##TYPE valueC = compare->data.TYPE; \
+    COMPARE_VALUES(valueT, valueC, false); \
+}
+
+
+// Compare a boolean template with a string
+#define COMPARE_BOOL_STRING(TYPE, FORMAT) { \
+    bool valueC; \
+    if (!parseBool(&valueC, compare->data.V)) { \
+        return false; \
+    } \
+    return (template->data.B == valueC); \
+}
+
+// Compare an integer template with a string
+#define COMPARE_INT_STRING(TYPE, FORMAT) { \
+    ps##TYPE valueC; \
+    PARSE_STRING(status, valueC, compare->data.V, FORMAT); \
+    if (!status) { \
+        return false; \
+    } \
+    ps##TYPE valueT = template->data.TYPE; \
+    COMPARE_VALUES(valueT, valueC, false); \
+}
+
+// Compare a float template with a string
+#define COMPARE_FLOAT_STRING(TYPE, FORMAT) { \
+    ps##TYPE valueC; \
+    PARSE_STRING(status, valueC, compare->data.V, FORMAT); \
+    if (!status) { \
+        double sexValue; /* Sexagesimal value; just in case the type is not F32 */ \
+        if (parseSexagesimal(&sexValue, compare->data.V)) { \
+            valueC = sexValue; \
+        } else { \
+            return false; \
+        } \
+    } \
+    ps##TYPE valueT = template->data.TYPE; \
+    COMPARE_VALUES(valueT, valueC, true); \
+}
+
+#define TEMPLATE_CASE(TYPENAME, NAME, TYPE, COMPARESTRING, FORMAT) \
+    case PS_TYPE_##TYPENAME: \
+    switch(compare->type) { \
+        COMPARE_NUMERICAL_CASE(NAME, TYPE, B  , BOOL); \
+        COMPARE_NUMERICAL_CASE(NAME, TYPE, U8 , U8 ); \
+        COMPARE_NUMERICAL_CASE(NAME, TYPE, U16, U16); \
+        COMPARE_NUMERICAL_CASE(NAME, TYPE, U32, U32); \
+        COMPARE_NUMERICAL_CASE(NAME, TYPE, U64, U64); \
+        COMPARE_NUMERICAL_CASE(NAME, TYPE, S8 , S8 ); \
+        COMPARE_NUMERICAL_CASE(NAME, TYPE, S16, S16); \
+        COMPARE_NUMERICAL_CASE(NAME, TYPE, S32, S32); \
+        COMPARE_NUMERICAL_CASE(NAME, TYPE, F32, F32); \
+        COMPARE_NUMERICAL_CASE(NAME, TYPE, F64, F64); \
+      case PS_DATA_STRING: { \
+          if (!template->data.V) { \
+              return false; \
+          } \
+          COMPARESTRING(TYPENAME, FORMAT); \
+      } \
+      default: \
         psError(PS_ERR_BAD_PARAMETER_TYPE, true, "Don't know how to compare types %x and %x\n", \
-                compare->type, template->type);				\
-        return false;							\
+                compare->type, template->type); \
+        return false; \
     }
 
@@ -117,5 +230,4 @@
                            const psMetadataItem *template) // The template
 {
-
     // First order checks:
 
@@ -130,59 +242,92 @@
     }
 
-    bool result = false;
-
     switch (template->type) {
-        TEMPLATE_CASE(BOOL, B,   bool) ;
-        TEMPLATE_CASE(U8,   U8,  psU8 );
-        TEMPLATE_CASE(U16,  U16, psU16);
-        TEMPLATE_CASE(U32,  U32, psU32);
-        TEMPLATE_CASE(S8,   S8,  psS8 );
-        TEMPLATE_CASE(S16,  S16, psS16);
-        TEMPLATE_CASE(S32,  S32, psS32);
-        TEMPLATE_CASE(F32,  F32, psF32);
-        TEMPLATE_CASE(F64,  F64, psF64);
-
-    case PS_DATA_STRING:
-
-        // for MULTI, try each one & succeed if any match (valid = true is default state)
-        if (compare->type == PS_DATA_METADATA_MULTI) {
-            for (int j = 0; j < compare->data.list->n; j++) {
-                psMetadataItem *entry = psListGet (compare->data.list, j);
-		if (!entry) continue;
-		if (entry->type != PS_DATA_STRING) {
-		  continue;
-		}
-		if (template->data.V && !entry->data.V) { // expecting valid data, found NULL
-		  continue;
-		}
-		if (!template->data.V && entry->data.V) { // expecting NULL, found valid data
-		  continue;
-		}
-		// XXX should we return true for both NULL?
-		if (!template->data.V && !entry->data.V) { // expecting NULL, found NULL
-		  return true;
-		}
-		if (!strcasecmp(entry->data.V, template->data.V)) {
-		  return true;
-		}
-		// XXX this is a hack : also compare with the comment field (for HISTORY, COMMENT)
-		if (!strcasecmp(entry->comment, template->data.V)) {
-		  return true;
-		}
+        TEMPLATE_CASE(BOOL, B,   bool,  COMPARE_BOOL_STRING, "");
+        TEMPLATE_CASE(U8,   U8,  psU8,  COMPARE_INT_STRING,  SCNu8);
+        TEMPLATE_CASE(U16,  U16, psU16, COMPARE_INT_STRING,  SCNu16);
+        TEMPLATE_CASE(U32,  U32, psU32, COMPARE_INT_STRING,  SCNu32);
+        TEMPLATE_CASE(U64,  U64, psU64, COMPARE_INT_STRING,  SCNu64);
+        TEMPLATE_CASE(S8,   S8,  psS8,  COMPARE_INT_STRING,  SCNd8);
+        TEMPLATE_CASE(S16,  S16, psS16, COMPARE_INT_STRING,  SCNd16);
+        TEMPLATE_CASE(S32,  S32, psS32, COMPARE_INT_STRING,  SCNd32);
+        TEMPLATE_CASE(S64,  S64, psS64, COMPARE_INT_STRING,  SCNd64);
+        TEMPLATE_CASE(F32,  F32, psF32, COMPARE_FLOAT_STRING, "f");
+        TEMPLATE_CASE(F64,  F64, psF64, COMPARE_FLOAT_STRING, "lf");
+      case PS_DATA_STRING: {
+          switch (compare->type) {
+            case PS_DATA_STRING: {
+                psTrace("psLib.types", 10, "Comparing '%s' with '%s'\n",
+                        compare->data.str, template->data.str);
+                if ((!compare->data.V && template->data.V) || (!template->data.V && compare->data.V)) {
+                    return false;
+                }
+                return (strcasecmp(compare->data.V, template->data.V) == 0) ? true : false;
             }
-	    return false;
-        }
-
-	// any other (non-MULTI) is a mis-match
-        if (compare->type != PS_DATA_STRING) {
-            return false;
-        }
-        psTrace("psLib.types", 10, "Comparing '%s' with '%s'\n", compare->data.str, template->data.str);
-        if ((!compare->data.V && template->data.V) || (!template->data.V && compare->data.V)) {
-            return false;
-        }
-        return (strcasecmp(compare->data.V, template->data.V) == 0) ? true : false;
-
-    default:
+            case PS_TYPE_BOOL: {
+                bool templateValue;
+                if (!parseBool(&templateValue, template->data.V)) {
+                    return false;
+                }
+                return templateValue == compare->data.B;
+            }
+              COMPARE_STRING_INT_CASE(U8,  SCNu8);
+              COMPARE_STRING_INT_CASE(U16, SCNu16);
+              COMPARE_STRING_INT_CASE(U32, SCNu32);
+              COMPARE_STRING_INT_CASE(U64, SCNu64);
+              COMPARE_STRING_INT_CASE(S8,  SCNd8);
+              COMPARE_STRING_INT_CASE(S16, SCNd16);
+              COMPARE_STRING_INT_CASE(S32, SCNd32);
+              COMPARE_STRING_INT_CASE(S64, SCNd64);
+            case PS_TYPE_F32:
+            case PS_TYPE_F64: {
+                // Check for sexagesimal formating
+                if (!template->data.V) {
+                    return false;
+                }
+                double templateValue; // Value of template
+                // Attempt to read the string first as a plain floating-point value, then as sexagesimal
+                PARSE_STRING(status, templateValue, template->data.V, "lf");
+                if (!status && !parseSexagesimal(&templateValue, template->data.V)) {
+                    return false;
+                }
+                double compareValue = compare->type == PS_TYPE_F32 ? compare->data.F32 : compare->data.F64;
+                COMPARE_VALUES(templateValue, compareValue, true);
+            }
+            case PS_DATA_METADATA_MULTI: {
+                // for MULTI, try each one & succeed if any match (valid = true is default state)
+                for (int j = 0; j < compare->data.list->n; j++) {
+                    psMetadataItem *entry = psListGet(compare->data.list, j);
+                    if (!entry) {
+                        continue;
+                    }
+                    if (entry->type != PS_DATA_STRING) {
+                        continue;
+                    }
+                    if (template->data.V && !entry->data.V) { // expecting valid data, found NULL
+                        continue;
+                    }
+                    if (!template->data.V && entry->data.V) { // expecting NULL, found valid data
+                        continue;
+                    }
+                    // XXX should we return true for both NULL?
+                    if (!template->data.V && !entry->data.V) { // expecting NULL, found NULL
+                        return true;
+                    }
+                    if (!strcasecmp(entry->data.V, template->data.V)) {
+                        return true;
+                    }
+                    // XXX this is a hack : also compare with the comment field (for HISTORY, COMMENT)
+                    if (!strcasecmp(entry->comment, template->data.V)) {
+                        return true;
+                    }
+                }
+                return false;
+            }
+            default:
+              // any other type is a mis-match against a string
+              return false;
+          }
+      }
+      default:
         // Simply don't know how to compare more complex types.
         psError(PS_ERR_BAD_PARAMETER_TYPE, true, "Don't know how to compare types %x and %x\n",
