Index: trunk/archive/pslib/src/Metadata/metadata.c
===================================================================
--- trunk/archive/pslib/src/Metadata/metadata.c	(revision 141)
+++ trunk/archive/pslib/src/Metadata/metadata.c	(revision 156)
@@ -8,40 +8,93 @@
 #include "psUtils.h"
 #include "psMetaData.h"
-/*
- * Return a properly filled-out psMetaData
+
+/*****************************************************************************/
+/*
+ * Handle sets of metadata
+ */
+psMetaDataSet *psMetaDataSetNew(void)	// make a new set of metadata
+{
+    psMetaDataSet *ms = psAlloc(sizeof(psMetaDataSet));
+
+    ms->list = psDlistNew(NULL);	// list of metadata
+    ms->table = psHashNew(128);		// hash table
+
+    return ms;
+}
+
+void psMetaDataSetDel(psMetaDataSet *ms) // destroy a set of metadata
+{
+    if (ms == NULL) {
+	return;
+    }
+    
+    psDlistDel(ms->list, (void (*)(void *))NULL);
+    psHashDel(ms->table, (void (*)(void *))psMetaDataItemDel);
+
+    psFree(ms);
+}
+
+/*****************************************************************************/
+/*
+ * Return a properly filled-out psMetaDataItem
  *
  * N.b. For types PS_META_FLOAT, PS_META_INT, and PS_META_STR, a COPY
  * of the data will be kept. In all other cases, only a POINTER
  * to the data will be kept.
- *
- * An unused argument, copy, is provided to handle this better
- * once we have copy constructors available.
- */
-psMetaData *psMetaDataNew(
-    const char *name,			// name of new item of metadata
-    psMetaDataType type,		// type of this piece of metadata
+ */
+psMetaDataItem *psMetaDataItemNew(
+    int typeFlags,			// type of this piece of metadata + flags
     const void *data,			// value of new item
-    const char *comment,		// comment associated with item;
-					// may be NULL
-    int copy)				// always copy data; NOTUSED
-{
+    const char *comment,		// comment associated with item; may be NULL
+    const char *name,			// name of new item of metadata (may be an sprintf format)
+    ...					// possible arguments for name format
+    )
+{
+    const psMetaDataType type = typeFlags & PS_META_TYPE_MASK; // the type part of typeFlags
+    const long flags = typeFlags & ~PS_META_TYPE_MASK;	       // the flags part of typeFlags
     static int id = 0;			// unique ID for an item of metadata
-    psMetaData *ms = psAlloc(sizeof(psMetaData));
+    psMetaDataItem *ms = psAlloc(sizeof(psMetaDataItem));
 
     *(int *)&ms->id = id++;		// need to cast away const
+
+    assert ((PS_META_NTYPE & PS_META_TYPE_MASK) == PS_META_NTYPE); // we have enough type names available
+    if (flags & PS_META_NON_UNIQUE) {
+	if (flags & PS_META_UNIQUE) {
+	    psTrace("metadata.name", 1,
+		    "Keyword \"%s\" was specified as both UNIQUE and NON_UNIQUE; adopting UNIQUE\n", name);
+	    flags &= ~PS_META_NON_UNIQUE;
+	}
+    } else {
+	flags |= PS_META_UNIQUE;
+    }
+    
     ms->type = type;
+    ms->flags = flags;
+    ms->items = NULL;
 
     assert (name != NULL);
-    ms->name = psAlloc(strlen(name) + 1);
-    strcpy(ms->name, name);
-
-    if(comment == NULL) {
-	comment = "";
-    }
-    ms->comment = psAlloc(strlen(comment) + 1);
-    strcpy(ms->comment, comment);
+    if (strchr(name, '%') != NULL) {
+	static char tmp[201];		// should be enough; we won't overflow even if it isn't
+
+	va_list ap;
+	va_start(ap, name);
+	if(vsnprintf(tmp, sizeof(tmp), name, ap) > sizeof(tmp) - 1) {
+	    psTrace("metadata.name", 1, "Keyword \"%s\" was truncated to \"%s\" upon expansion\n", name, tmp);
+	}
+	va_end(ap);
+
+	name = tmp;	    
+    }
+    ms->name = psStringCopy(name);
+
+    ms->comment = psStringCopy(comment == NULL ? "" : comment);
 
     assert (type >= 0 && type < PS_META_NTYPE);
     switch (type) {
+      case PS_META_ITEM_SET:
+	*(void **)&ms->val.v = NULL;
+	ms->items = psDlistNew(NULL);
+	break;
+
       case PS_META_FLOAT:
 	*(float *)&ms->val.f = *(float *)data;
@@ -62,5 +115,5 @@
       case PS_META_ASTROM:
       case PS_META_UNKNOWN:
-	*(void **)ms->val.v = (void *)data;
+	*(void **)&ms->val.v = (void *)data;
 	break;
       case PS_META_NTYPE:
@@ -72,5 +125,5 @@
 }
 
-void psMetaDataDel(psMetaData *ms)	// piece of metadata to destroy
+void psMetaDataItemDel(psMetaDataItem *ms)	// piece of metadata to destroy
 {
     if(ms == NULL) {
@@ -83,8 +136,11 @@
     assert (ms->type >= 0 && ms->type < PS_META_NTYPE);
     switch (ms->type) {
+      case PS_META_ITEM_SET:
+	psDlistDel(ms->items, (void (*)(void *))NULL); // they're still in the hash table
+	ms->items = NULL;
+	break;
       case PS_META_FLOAT:
       case PS_META_INT:
 	break;				// nothing to do
-	
       case PS_META_STR:
 	psFree(ms->val.v);
@@ -107,7 +163,116 @@
 
 /*****************************************************************************/
-
-void psMetaDataPrint(FILE *fd,		// file descriptor to write to
-		     const psMetaData *ms) // item of metadata to print
+/*
+ * Add an new psMetaDataItem to a psMetaDataSet
+ */
+psMetaDataItem *psMetaDataAppend(psMetaDataSet *restrict ms, psMetaDataItem *restrict item)
+{
+    psMetaDataItem *meta = NULL;	// an item of metadata
+    assert (ms != NULL && ms->list != NULL && ms->table != NULL);
+
+    // Have we already seen this item?
+    meta = psHashLookup(ms->table, item->name);
+    
+    if (item->flags & PS_META_NON_UNIQUE) {
+	psMetaDataItem *head = meta;
+	if (head == NULL) {		// this is the first item of this name
+	    head = psMetaDataAppend(ms, psMetaDataItemNew(PS_META_ITEM_SET, NULL, NULL, item->name));
+	}	    
+
+	psDlistSetIterator(head->items, PS_DLIST_TAIL, 0);
+	meta = psDlistGetPrev(head->items, 0);
+
+	int i = 0;			// index of this instance of name
+	if (meta != NULL) {		// no items of this name
+	    char *ptr = strrchr(meta->name, '.');
+	    assert (ptr != NULL);
+	    i = atoi(ptr + 1) + 1;	// next value
+	}
+
+	char newname[strlen(item->name) + 2 + 1];
+	sprintf(newname, "%s.%d", item->name, i);
+
+	assert (psHashLookup(ms->table, newname) == NULL); // name isn't in use
+
+	psFree(item->name);
+	item->name = psStringCopy(newname);
+
+	psDlistAppend(head->items, item);
+	psHashInsert(ms->table, item->name, item, (void (*)(void *))psMetaDataItemDel);
+
+	return item;
+    } else if (meta != NULL) {
+	psError(__func__, "Key \"%s\" is already present in the metaDataSet 0x%x", item->name, ms);
+	//psMetaDataItemDel(item);
+
+	return NULL;
+    }
+
+    psDlistAppend(ms->list, item);
+    psHashInsert(ms->table, item->name, item, (void (*)(void *))psMetaDataItemDel);
+
+    return item;
+}
+
+/*****************************************************************************/
+/*
+ * Remove and return an item of metadata from a set
+ */
+psMetaDataItem *psMetaDataRemove(psMetaDataSet *restrict ms, const char *restrict key)
+{
+    assert (ms != NULL && ms->list != NULL && ms->table != NULL);
+
+    void *data = psHashRemove(ms->table, key);
+
+    if (data == NULL) {
+	return NULL;
+    } else {
+	return psDlistRemove(ms->list, data, PS_DLIST_UNKNOWN);
+    }
+}
+
+/*****************************************************************************/
+/*
+ * Iterators for the psDlist representation of the metadata
+ */
+void psMetaDataSetIterator(psMetaDataSet *ms)
+{
+    assert (ms != NULL);
+    psDlistSetIterator(ms->list, PS_DLIST_HEAD, 0);
+}
+
+psMetaDataItem *psMetaDataGetNext(psMetaDataSet *restrict ms,
+				  const char *restrict match)
+{
+    assert (ms != NULL);
+    psMetaDataItem *meta = psDlistGetNext(ms->list, 0);
+
+    if (match == NULL || meta == NULL) {
+	return meta;
+    } else {
+	if (strncmp(meta->name, match, strlen(match)) == 0) { // a match
+	    return meta;
+	} else {
+	    return psMetaDataGetNext(ms, match);
+	}
+    }
+}
+
+/*****************************************************************************/
+/*
+ * Access using the hash table
+ */
+psMetaDataItem *psMetaDataLookup(const psMetaDataSet *restrict ms,
+				 const char *restrict key)
+{
+    assert (ms != NULL);
+
+    return psHashLookup(ms->table, key);
+}
+
+/*****************************************************************************/
+void psMetaDataItemPrint(FILE *fd,		// file descriptor to write to
+			 const psMetaDataItem *restrict ms, // item of metadata to print
+			 const char *prefix)	   // print this at the beginning of each line
 {
     if(ms == NULL) {
@@ -115,44 +280,49 @@
     }
 
-    fprintf(fd, "Name:     %-40s (ID: %d)\n", ms->name, ms->id);
+    fprintf(fd, "%sName:     %-40s (ID: %d)\n", prefix, ms->name, ms->id);
     if (*ms->comment != '\0') {
-	fprintf(fd, "Comment:  %s\n", ms->comment);
-    }
-    fprintf(fd, "Value:    ");
+	fprintf(fd, "%sComment:  %s\n", prefix, ms->comment);
+    }
+    fprintf(fd, "%sValue:    ", prefix);
 
     assert (ms->type >= 0 && ms->type < PS_META_NTYPE);
+    fprintf(fd, "%s", prefix);
     switch (ms->type) {
+      case PS_META_ITEM_SET:
+	{				// gcc 3.3 doesn't allow a declaration here
+	    psMetaDataItem *meta = NULL;
+
+	    fprintf(fd, "\n");
+	    psDlistSetIterator(ms->items, PS_DLIST_HEAD, 0);
+	    while ((meta = psDlistGetNext(ms->items, 0)) != NULL) {
+		psMetaDataItemPrint(fd, meta, "\t");
+	    }
+	}
+	
+	break;
       case PS_META_FLOAT:
 	fprintf(fd, "%g\n", ms->val.f);
 	break;
-
       case PS_META_INT:
 	fprintf(fd, "%d\n", ms->val.i);
 	break;
-	
       case PS_META_STR:
 	fprintf(fd, "%s\n", (char *)ms->val.v);
 	break;
-	
       case PS_META_IMG:
 	fprintf(fd, "(IMG)\n");
 	break;
-	
       case PS_META_JPEG:
 	fprintf(fd, "(JPG)\n");
 	break;
-	
       case PS_META_PNG:
 	fprintf(fd, "(PNG)\n");
 	break;
-	
       case PS_META_ASTROM:
 	fprintf(fd, "(ASTROM)\n");
 	break;
-	
       case PS_META_UNKNOWN:
 	fprintf(fd, "(UNKNOWN)\n");
 	break;
-
       case PS_META_NTYPE:
 	psAbort(__FILE__, "You cannot get here (%d)\n", __LINE__);
@@ -170,37 +340,58 @@
 int main(void)
 {
-    psDlist *list = psDlistNew(NULL);	// list of metadata
-
-    const int ten = 10;
-    psDlistAppend(list, psMetaDataNew("const.ten", PS_META_INT, &ten, NULL,0));
-    const float sqrt2 = sqrt(2);
-    psDlistAppend(list, psMetaDataNew("const.sqrt2", PS_META_FLOAT, &sqrt2,
-				      "square root of two", 0));
-    psDlistAppend(list, psMetaDataNew("lang.hello", PS_META_STR, "Bonjour",
-				      "French", 0));
-    /*
-     * Print that data
-     */
-    for (psDlistElem *ptr = list->head; ptr != NULL; ptr = ptr->next) {
-	psMetaDataPrint(stdout, ptr->data);
-    }
-
+    psSetTraceLevel("metadata", 1);	// turn on tracing
+    psSetLogFormat("NM");		// suppress some of the psLogMsg verbosity
+
+    psMetaDataSet *ms = psMetaDataSetNew(); // set of metadata
+    psMetaDataItem *meta = NULL;	    // used to traverse a set of metadata
+
+    for (int i = 0; i < 10; i += 5) {
+	float sqrti = sqrt(i);
+	psMetaDataAppend(ms, psMetaDataItemNew(PS_META_INT, &i, NULL, "const.%d", i));
+	psMetaDataAppend(ms, psMetaDataItemNew(PS_META_FLOAT, &sqrti, "square root", "const.sqrt%d", i));
+    }
+    psMetaDataAppend(ms, psMetaDataItemNew(PS_META_STR | PS_META_NON_UNIQUE,
+					   "Bonjour", "French", "lang.hello"));
+    psMetaDataAppend(ms, psMetaDataItemNew(PS_META_STR | PS_META_NON_UNIQUE,
+					   "Aloha", "Hawaiian", "lang.hello"));
+    psMetaDataAppend(ms, psMetaDataItemNew(PS_META_STR | PS_META_NON_UNIQUE,
+					   "Good Morning", "English", "lang.hello"));
+    /*
+     * Print all metadata starting "lang"
+     */
+    psMetaDataSetIterator(ms);
+    while ((meta = psMetaDataGetNext(ms, "lang")) != NULL) {
+	psMetaDataItemPrint(stdout, meta, "");
+    }
+    /*
+     * Remove a key
+     */
+    psMetaDataItemDel(psMetaDataRemove(ms, "lang.hello"));
+    /*
+     * Print all metadata
+     */
     fprintf(stdout, "------\n");
-    /*
-     * Print it again
-     */
-    (void)psDlistGet(list, PS_DLIST_TAIL); // initialise iterator
-    psMetaData *ms = NULL;
-    while ((ms = psDlistGet(list, PS_DLIST_PREV)) != NULL) {
-	psMetaDataPrint(stdout, ms);
+    psMetaDataSetIterator(ms);
+    while ((meta = psMetaDataGetNext(ms, NULL)) != NULL) {
+	psMetaDataItemPrint(stdout, meta, "");
+    }
+    /*
+     * Look up a key by name
+     */
+    fprintf(stdout, "------\n");
+    fprintf(stdout, "Looking up by name:\n");
+    meta = psMetaDataLookup(ms, "const.5");
+    if (meta != NULL) {
+	psMetaDataItemPrint(stdout, meta, "");
     }
     /*
      * Clean up
      */
-    psDlistDel(list, (void (*)(void *))psMetaDataDel);
+    psMetaDataSetDel(ms);
     /*
      * Check for memory leaks
      */
     fprintf(stderr,"Checking for memory leaks:\n");
+    psTraceReset();
     psMemCheckLeaks(0, NULL, stderr);
     
