diff -Nbru clearsilver-0.4/Makefile clearsilver-0.5/Makefile
--- clearsilver-0.4/Makefile	Fri Jan 11 15:45:44 2002
+++ clearsilver-0.5/Makefile	Fri Mar 15 13:50:52 2002
@@ -5,6 +5,7 @@
 #
 #
 
+NEOTONIC_ROOT = ./
 
 include rules.mk
 
@@ -69,8 +70,8 @@
 		mkdir -p $$mdir; \
 	done
 
-CS_DISTDIR = clearsilver-0.3
-CS_LABEL = CLEARSILVER-0_3
+CS_DISTDIR = clearsilver-0.5
+CS_LABEL = CLEARSILVER-0_5_0
 CS_FILES = LICENSE CS_LICENSE rules.mk Makefile util cs cgi python scripts mod_ecs
 cs_dist:
 	rm -rf $(CS_DISTDIR)
diff -Nbru clearsilver-0.4/cgi/cgi.c clearsilver-0.5/cgi/cgi.c
--- clearsilver-0.4/cgi/cgi.c	Sat Sep 15 16:37:24 2001
+++ clearsilver-0.5/cgi/cgi.c	Thu Apr 11 18:11:39 2002
@@ -105,7 +105,7 @@
   return STATUS_OK;
 }
 
-static char *url_decode (char *s)
+char *cgi_url_unescape (char *s)
 {
   int i = 0, o = 0;
 
@@ -134,20 +134,35 @@
   return s;
 }
 
-NEOERR *cgi_url_escape (char *buf, char **esc)
+NEOERR *cgi_url_escape_more (char *buf, char **esc, char *other)
 {
   int nl = 0;
   int l = 0;
+  int x = 0;
   char *s;
+  int match = 0;
 
   while (buf[l])
   {
     if (buf[l] == '/' || buf[l] == '+' || buf[l] == '=' || buf[l] == '&' || 
-	buf[l] == '"' || buf[l] == '%' ||
+	buf[l] == '"' || buf[l] == '%' || buf[l] == '?' ||
 	buf[l] < 32 || buf[l] > 122)
     {
       nl += 2;
     }
+    else if (other)
+    {
+      x = 0;
+      while (other[x])
+      {
+	if (other[x] == buf[l])
+	{
+	  nl +=2;
+	  break;
+	}
+	x++;
+      }
+    }
     nl++;
     l++;
   }
@@ -160,16 +175,35 @@
   nl = 0; l = 0;
   while (buf[l])
   {
+    match = 0;
     if (buf[l] == ' ')
     {
       s[nl++] = '+';
       l++;
     }
     else
+    {
     if (buf[l] == '/' || buf[l] == '+' || buf[l] == '=' || buf[l] == '&' || 
-	buf[l] == '"' || buf[l] == '%' ||
+	  buf[l] == '"' || buf[l] == '%' || buf[l] == '?' ||
 	buf[l] < 32 || buf[l] > 122)
     {
+	match = 1;
+      }
+      else if (other)
+      {
+	x = 0;
+	while (other[x])
+	{
+	  if (other[x] == buf[l])
+	  {
+	    match = 1;
+	    break;
+	  }
+	  x++;
+	}
+      }
+      if (match)
+      {
       s[nl++] = '%';
       s[nl++] = "0123456789ABCDEF"[buf[l] / 16];
       s[nl++] = "0123456789ABCDEF"[buf[l] % 16];
@@ -180,12 +214,18 @@
       s[nl++] = buf[l++];
     }
   }
+  }
   s[nl] = '\0';
 
   *esc = s;
   return STATUS_OK;
 }
 
+NEOERR *cgi_url_escape (char *buf, char **esc)
+{
+  return nerr_pass(cgi_url_escape_more(buf, esc, NULL));
+}
+
 static NEOERR *_parse_query (CGI *cgi, char *query)
 {
   NEOERR *err = STATUS_OK;
@@ -208,10 +248,10 @@
 	v = strtok_r(NULL, "&", &l);
       }
       if (v == NULL) v = "";
-      snprintf(buf, sizeof(buf), "Query.%s", url_decode(k));
+      snprintf(buf, sizeof(buf), "Query.%s", cgi_url_unescape(k));
       if (!(cgi->ignore_empty_form_vars && (v == NULL || *v == '\0')))
       {
-	url_decode(v);
+	cgi_url_unescape(v);
 	obj = hdf_get_obj (cgi->hdf, buf);
 	if (obj != NULL)
 	{
@@ -258,6 +298,7 @@
   l = hdf_get_value (cgi->hdf, "CGI.ContentLength", NULL);
   if (l == NULL) return STATUS_OK;
   len = atoi (l);
+  if (len == 0) return STATUS_OK;
 
   cgi->data_expected = len;
 
@@ -271,9 +312,15 @@
   while (o < len)
   {
     cgiwrap_read (query + o, len - o, &r);
-    if (r == 0) break;
+    if (r <= 0) break;
     o = o + r;
   }
+  if (r < 0)
+  {
+    free(query);
+    return nerr_raise_errno (NERR_IO, "Short read on CGI POST input (%d < %d)",
+	o, len);
+  }
   if (o != len)
   {
     free(query);
@@ -472,6 +519,55 @@
     }
 #endif
   }
+  else if (!strcmp(method, "PUT"))
+  {
+    FILE *fp;
+    int len, x, r, w;
+    char *l;
+    char buf[4096];
+    int unlink_files = hdf_get_int_value(cgi->hdf, "Config.Upload.Unlink", 1);
+
+    err = open_upload(cgi, unlink_files, &fp);
+    if (err) return nerr_pass(err);
+
+    l = hdf_get_value (cgi->hdf, "CGI.ContentLength", NULL);
+    if (l == NULL) return STATUS_OK;
+    len = atoi (l);
+
+    x = 0;
+    while (x < len)
+    {
+      if (len-x > sizeof(buf))
+	cgiwrap_read (buf, sizeof(buf), &r);
+      else
+	cgiwrap_read (buf, len - x, &r);
+      w = fwrite (buf, sizeof(char), r, fp);
+      if (w != r)
+      {
+	err = nerr_raise_errno(NERR_IO, "Short write on PUT: %d < %d", w, r);
+	break;
+      }
+      x += r;
+    }
+    if (err) return nerr_pass(err);
+    fseek(fp, 0, SEEK_SET);
+    l = hdf_get_value(cgi->hdf, "CGI.PathInfo", NULL);
+    if (l) err = hdf_set_value (cgi->hdf, "PUT", l);
+    if (err) return nerr_pass(err);
+    if (type) err = hdf_set_value (cgi->hdf, "PUT.Type", type);
+    if (err) return nerr_pass(err);
+    err = hdf_set_int_value (cgi->hdf, "PUT.FileHandle", uListLength(cgi->files));
+    if (err) return nerr_pass(err);
+    if (!unlink_files)
+    {
+      char *name;
+      err = uListGet(cgi->filenames, uListLength(cgi->filenames)-1, 
+	  (void **)&name);
+      if (err) return nerr_pass(err);
+      err = hdf_set_value (cgi->hdf, "PUT.FileName", name);
+      if (err) return nerr_pass(err);
+    }
+  }
   return STATUS_OK;
 }
 
@@ -791,6 +887,7 @@
     dest = (char *) malloc (sizeof(char) * len2);
     if (dest != NULL)
     {
+	do {
       err = cgi_compress (str, dest, &len2);
       if (err == STATUS_OK)
       {
@@ -799,12 +896,16 @@
 	  err = cgiwrap_writef("%c%c%c%c%c%c%c%c%c%c", gz_magic[0], gz_magic[1],
 	      Z_DEFLATED, 0 /*flags*/, 0,0,0,0 /*time*/, 0 /*xflags*/, OS_CODE);
 	}
+	    if (err != STATUS_OK) break;
 	err = cgiwrap_write(dest, len2);
+	    if (err != STATUS_OK) break;
 
 	if (use_gzip)
 	{
 	  err = cgiwrap_writef("%c%c%c%c", (0xff & (crc >> 0)), (0xff & (crc >> 8)), (0xff & (crc >> 16)), (0xff & (crc >> 24)));
+	      if (err != STATUS_OK) break;
 	  err = cgiwrap_writef("%c%c%c%c", (0xff & (str->len >> 0)), (0xff & (str->len >> 8)), (0xff & (str->len >> 16)), (0xff & (str->len >> 24)));
+	      if (err != STATUS_OK) break;
 	}
       }
       else
@@ -812,6 +913,7 @@
 	nerr_log_error (err);
 	err = cgiwrap_write(str->buf, str->len);
       }
+	} while (0);
       free (dest);
     }
     else
@@ -825,7 +927,7 @@
     err = cgiwrap_write(str->buf, str->len);
   }
 
-  return err;
+  return nerr_pass(err);
 }
 
 NEOERR *cgi_display (CGI *cgi, char *cs_file)
@@ -880,7 +982,7 @@
   cgiwrap_writef("Content-Type: text/html\n\n");
 
   cgiwrap_writef("<html><body>\nAn error occured:<pre>");
-  nerr_error_string (err, &str);
+  nerr_error_traceback(err, &str);
   cgiwrap_write(str.buf, str.len);
   cgiwrap_writef("</pre></body></html>\n");
 }
diff -Nbru clearsilver-0.4/cgi/cgi.h clearsilver-0.5/cgi/cgi.h
--- clearsilver-0.4/cgi/cgi.h	Sun Sep  9 16:53:10 2001
+++ clearsilver-0.5/cgi/cgi.h	Thu Apr 11 18:11:39 2002
@@ -134,7 +134,8 @@
  *              the start of the file when first available.
  * Input: cgi - a pointer to a CGI struct allocated with cgi_init
  *        form_name - the form name that the file was uploaded as
- *                    (not the filename)
+ *                    (not the filename) (if NULL, we're asking for the
+ *                    file handle for the PUT upload)
  * Output: None
  * Return: A stdio FILE pointer, or NULL if an error occurs (usually
  *         indicates that the form_name wasn't found, but might indicate
@@ -196,6 +197,31 @@
 NEOERR *cgi_url_escape (char *buf, char **esc);
 
 /*
+ * Function: cgi_url_escape_more - url escape a string
+ * Description: cgi_url_escape_more will do URL escaping on the passed in
+ *              string, and return a newly allocated string that is escaped.
+ *              Characters which are escaped include control characters,
+ *              %, ?, +, space, =, &, /, and " and any characters in
+ *              other
+ * Input: buf - a 0 terminated string
+ *        other - a 0 terminated string of characters to escape
+ * Output: esc - a newly allocated string 
+ * Return: NERR_NOMEM - no memory available to allocate the escaped string
+ */
+NEOERR *cgi_url_escape_more (char *buf, char **esc, char *other);
+
+/*
+ * Function: cgi_url_unescape - unescape an url encoded string
+ * Description: cgi_url_unescape will do URL unescaping on the passed in
+ *              string.  This function modifies the string in place
+ *              This function will decode any %XX character, and will
+ *              decode + as space
+ * Input: buf - a 0 terminated string
+ * Return: pointer to same buf
+ */
+char *cgi_url_unescape (char *buf);
+
+/*
  * Function: cgi_redirect - send an HTTP 302 redirect response
  * Description: cgi_redirect will redirect the user to another page on
  *              your site.  This version takes only the path portion of
@@ -306,5 +332,6 @@
 
 /* internal use only */
 NEOERR * parse_rfc2388 (CGI *cgi);
+NEOERR * open_upload(CGI *cgi, int unlink_files, FILE **fpw);
 
 #endif /* __CGI_H_ */
diff -Nbru clearsilver-0.4/cgi/cgiwrap.c clearsilver-0.5/cgi/cgiwrap.c
--- clearsilver-0.4/cgi/cgiwrap.c	Fri Jan 11 15:42:08 2002
+++ clearsilver-0.5/cgi/cgiwrap.c	Thu Feb 21 16:49:23 2002
@@ -178,7 +178,7 @@
   {
     r = GlobalWrapper.writef_cb (GlobalWrapper.data, fmt, ap);
     if (r) 
-      return nerr_raise (NERR_SYSTEM, "writef_cb returned %d", r);
+      return nerr_raise_errno (NERR_IO, "writef_cb returned %d", r);
   }
   else
   {
@@ -196,14 +196,14 @@
   {
     r = GlobalWrapper.write_cb (GlobalWrapper.data, buf, buf_len);
     if (r != buf_len)
-      return nerr_raise (NERR_IO, "write_cb returned %d<%d", r, buf_len);
+      return nerr_raise_errno (NERR_IO, "write_cb returned %d<%d", r, buf_len);
   }
   else
   {
     /* r = fwrite(buf, sizeof(char), buf_len, stderr);  */
     r = fwrite(buf, sizeof(char), buf_len, stdout);
     if (r != buf_len)
-      return nerr_raise (NERR_IO, "fwrite returned %d<%d", r, buf_len);
+      return nerr_raise_errno (NERR_IO, "fwrite returned %d<%d", r, buf_len);
   }
   return STATUS_OK;
 }
diff -Nbru clearsilver-0.4/cgi/html.c clearsilver-0.5/cgi/html.c
--- clearsilver-0.4/cgi/html.c	Tue Nov  6 16:23:01 2001
+++ clearsilver-0.5/cgi/html.c	Wed Mar 27 10:20:34 2002
@@ -55,6 +55,7 @@
   return 0;
 }
 
+/*
 static int has_long_lines (char *s, int l)
 {
   char *ptr;
@@ -73,6 +74,7 @@
   }
   return 0;
 }
+*/
 
 /* The first step is to actually find all of the URLs and email
  * addresses using our handy regular expressions.  We then mark these,
@@ -89,8 +91,8 @@
 #define SC_TYPE_URL   2
 #define SC_TYPE_EMAIL 3
 
-static char *EmailRe = "[^][@:;<>\\\"()[:space:][:cntrl:]]+@[-+a-zA-Z0-9].[-+a-zA-Z0-9.]+";
-static char *URLRe = "((((ht|f)tp)|mailto):(//)?[^[:space:]>\"\t]*|www\\.[-a-z0-9\\.]+)[^[:space:];\t\">]";
+static char *EmailRe = "[^][@:;<>\\\"()[:space:][:cntrl:]]+@[-+a-zA-Z0-9]+\\.[-+a-zA-Z0-9\\.]+[-+a-zA-Z0-9]";
+static char *URLRe = "((((ht|f)tp)|mailto):(//)?[^[:space:]>\"\t]*|www\\.[-a-z0-9\\.]+)[^[:space:];\t\">]*";
 
 static NEOERR *split_and_convert (char *src, int slen, STRING *out, int newlines, int space_convert)
 {
@@ -99,7 +101,7 @@
   static regex_t email_re, url_re;
   regmatch_t email_match, url_match;
   int errcode;
-  char buf[256], *ptr;
+  char buf[256], *ptr, *esc;
   struct _parts *parts;
   int part_count;
   int part;
@@ -337,12 +339,24 @@
     }
     else 
     {
+      if (spaces)
+      {
+	int sp;
+	for (sp = 0; sp < spaces - 1; sp++)
+	{
+	  err = string_append (out, "&nbsp;");
+	  if (err != STATUS_OK) break;
+	}
+	if (err != STATUS_OK) break;
+	err = string_append_char (out, ' ');
+      }
+      spaces = 0;
       if (parts[i].type == SC_TYPE_URL)
       {
         char last_char = src[parts[i].end-1];
         int suffix=0;
         if (last_char == '.' || last_char == ',') { suffix=1; }
-	err = string_append (out, " <A TARGET=\"_blank\" HREF=\"");
+	err = string_append (out, " <a target=\"_blank\" href=\"");
 	if (err != STATUS_OK) break;
 	if (!strncmp(src + x, "www.", 4))
 	{
@@ -353,9 +367,12 @@
 	if (err != STATUS_OK) break;
 	err = string_append (out, "\">");
 	if (err != STATUS_OK) break;
-	err = string_appendn (out, src + x, parts[i].end - x - suffix);
+	err = html_escape_alloc(src + x, parts[i].end - x - suffix, &esc);
 	if (err != STATUS_OK) break;
-	err = string_append (out, "</A>");
+	err = string_append (out, esc);
+	free(esc);
+	if (err != STATUS_OK) break;
+	err = string_append (out, "</a>");
         if (suffix) {
             err  = string_appendn(out,src + parts[i].end - 1,1);
 	    if (err != STATUS_OK) break;
@@ -363,15 +380,18 @@
       }
       else /* type == SC_TYPE_EMAIL */
       {
-	err = string_append (out, "<A HREF=\"mailto:");
+	err = string_append (out, "<a href=\"mailto:");
 	if (err != STATUS_OK) break;
 	err = string_appendn (out, src + x, parts[i].end - x);
 	if (err != STATUS_OK) break;
 	err = string_append (out, "\">");
 	if (err != STATUS_OK) break;
-	err = string_appendn (out, src + x, parts[i].end - x);
+	err = html_escape_alloc(src + x, parts[i].end - x, &esc);
 	if (err != STATUS_OK) break;
-	err = string_append (out, "</A>");
+	err = string_append (out, esc);
+	free(esc);
+	if (err != STATUS_OK) break;
+	err = string_append (out, "</a>");
       }
       x = parts[i].end;
       i++;
@@ -406,7 +426,7 @@
     }
     else
     {
-      x = i = ptr - str->buf;
+      x = i = ptr - (char *) str->buf;
       if (x)
       {
 	x--;
@@ -447,7 +467,7 @@
     }
     else
     {
-      int nl = has_long_lines (src, slen);
+      /* int nl = has_long_lines (src, slen); */
       err = split_and_convert(src, slen, &out_s, 1, formatting);
     }
   } while (0);
@@ -483,9 +503,9 @@
   while (x < slen)
   {
     ptr = strpbrk(src + x, "&<>\"\r");
-    if (ptr == NULL)
+    if (ptr == NULL || (ptr-src >= slen))
     {
-      err = string_append (&out_s, src + x);
+      err = string_appendn (&out_s, src + x, slen-x);
       x = slen;
     }
     else 
diff -Nbru clearsilver-0.4/cgi/rfc2388.c clearsilver-0.5/cgi/rfc2388.c
--- clearsilver-0.4/cgi/rfc2388.c	Tue Oct 16 15:39:59 2001
+++ clearsilver-0.5/cgi/rfc2388.c	Thu Apr 11 18:11:39 2002
@@ -260,6 +260,68 @@
   return STATUS_OK;
 }
 
+NEOERR *open_upload(CGI *cgi, int unlink_files, FILE **fpw)
+{
+  NEOERR *err = STATUS_OK;
+  FILE *fp;
+  char path[_POSIX_PATH_MAX];
+  int fd;
+
+  *fpw = NULL;
+
+  snprintf (path, sizeof(path), "%s/cgi_upload.XXXXXX", 
+      hdf_get_value(cgi->hdf, "Config.Upload.TmpDir", "/var/tmp"));
+
+  fd = mkstemp(path);
+  if (fd == -1)
+  {
+    return nerr_raise_errno (NERR_SYSTEM, "Unable to open temp file %s", 
+	path);
+  }
+
+  fp = fdopen (fd, "w+");
+  if (fp == NULL)
+  {
+    close(fd);
+    return nerr_raise_errno (NERR_SYSTEM, "Unable to fdopen file %s", path);
+  }
+  if (unlink_files) unlink(path);
+  if (cgi->files == NULL)
+  {
+    err = uListInit (&(cgi->files), 10, 0);
+    if (err)
+    {
+      fclose(fp);
+      return nerr_pass(err);
+    }
+  }
+  err = uListAppend (cgi->files, fp);
+  if (err)
+  {
+    fclose (fp);
+    return nerr_pass(err);
+  }
+  if (!unlink_files) {
+    if (cgi->filenames == NULL)
+    {
+      err = uListInit (&(cgi->filenames), 10, 0);
+      if (err)
+      {
+	fclose(fp);
+	return nerr_pass(err);
+      }
+    }
+    err = uListAppend (cgi->filenames, strdup(path));
+    if (err)
+    {
+      fclose (fp);
+      return nerr_pass(err);
+    }
+  }
+  *fpw = fp;
+  return STATUS_OK;
+}
+
 static NEOERR * _read_part (CGI *cgi, char *boundary, int *done)
 {
   NEOERR *err = STATUS_OK;
@@ -324,61 +386,10 @@
   {
     if (filename)
     {
-      char path[_POSIX_PATH_MAX];
-      int fd;
-
-      snprintf (path, sizeof(path), "%s/cgi_upload.XXXXXX", 
-                hdf_get_value(cgi->hdf, "Config.Upload.TmpDir", "/var/tmp"));
-
-      fd = mkstemp(path);
-      if (fd == -1)
-      {
-	err = nerr_raise_errno (NERR_SYSTEM, "Unable to open temp file %s", 
-	    path);
-	break;
+      err = open_upload(cgi, unlink_files, &fp);
+      if (err) break;
       }
 
-      fp = fdopen (fd, "w+");
-      if (fp == NULL)
-      {
-	close(fd);
-	err = nerr_raise_errno (NERR_SYSTEM, "Unable to fdopen file %s", path);
-	break;
-      }
-      if (unlink_files) unlink(path);
-      if (cgi->files == NULL)
-      {
-	err = uListInit (&(cgi->files), 10, 0);
-	if (err)
-	{
-	  fclose(fp);
-	  break;
-	}
-      }
-      err = uListAppend (cgi->files, fp);
-      if (err)
-      {
-	fclose (fp);
-	break;
-      }
-      if (!unlink_files) {
-        if (cgi->filenames == NULL)
-        {
-	  err = uListInit (&(cgi->filenames), 10, 0);
-	  if (err)
-	  {
-	    fclose(fp);
-	    break;
-	  }
-        }
-        err = uListAppend (cgi->filenames, strdup(path));
-        if (err)
-        {
-	  fclose (fp);
-	  break;
-        }
-      }
-    }
     string_set(&str, "");
     while (1)
     {
@@ -509,8 +520,16 @@
   char buf[256];
   int n;
 
+  if ((form_name == NULL) || (form_name[0] == '\0'))
+  {
+    /* if NULL, then its the PUT data we're looking for... */
+    n = hdf_get_int_value (cgi->hdf, "PUT.FileHandle", -1);
+  }
+  else
+  {
   snprintf (buf, sizeof(buf), "Query.%s.FileHandle", form_name);
   n = hdf_get_int_value (cgi->hdf, buf, -1);
+  }
   if (n == -1) return NULL;
   err = uListGet(cgi->files, n-1, (void **)&fp);
   if (err)
diff -Nbru clearsilver-0.4/cs/Makefile clearsilver-0.5/cs/Makefile
--- clearsilver-0.4/cs/Makefile	Tue Nov 13 13:58:15 2001
+++ clearsilver-0.5/cs/Makefile	Thu Mar 28 01:18:51 2002
@@ -23,7 +23,7 @@
 
 TARGETS = $(CS_LIB) $(CSTEST_EXE) test
 
-CS_TESTS = test.cs test2.cs test3.cs test4.cs test5.cs test6.cs test7.cs
+CS_TESTS = test.cs test2.cs test3.cs test4.cs test5.cs test6.cs test7.cs test8.cs test9.cs test10.cs test11.cs
 
 all: $(TARGETS)
 
diff -Nbru clearsilver-0.4/cs/cs.h clearsilver-0.5/cs/cs.h
--- clearsilver-0.4/cs/cs.h	Tue Nov 13 13:58:15 2001
+++ clearsilver-0.5/cs/cs.h	Thu Mar 28 01:18:51 2002
@@ -66,23 +66,33 @@
   CS_OP_DIV = (1<<13),
   CS_OP_MOD = (1<<14),
 
+  /* Associative Operators */
+  CS_OP_LPAREN = (1<<15),
+  CS_OP_RPAREN = (1<<16),
+  CS_OP_LBRACKET = (1<<17),
+  CS_OP_RBRACKET = (1<<18),
+
+
   /* Types */
-  CS_TYPE_STRING = (1<<15),
-  CS_TYPE_NUM = (1<<16),
-  CS_TYPE_VAR = (1<<17),
-  CS_TYPE_VAR_NUM = (1<<18),
-  CS_TYPE_MACRO = (1<<19),
-  CS_TYPE_EXPR = (1<<20),
-  CS_TYPE_STRING_ALLOC = (1<<21)
+  CS_TYPE_STRING = (1<<19),
+  CS_TYPE_NUM = (1<<20),
+  CS_TYPE_VAR = (1<<21),
+  CS_TYPE_VAR_NUM = (1<<22),
+  CS_TYPE_MACRO = (1<<23),
+  CS_TYPE_EXPR = (1<<24)
 } CSTOKEN_TYPE;
 
-#define CS_TYPES (CS_TYPE_STRING | CS_TYPE_NUM | CS_TYPE_VAR | CS_TYPE_VAR_NUM | CS_TYPE_STRING_ALLOC)
+#define CS_TYPES (CS_TYPE_STRING | CS_TYPE_NUM | CS_TYPE_VAR | CS_TYPE_VAR_NUM)
+#define CS_TYPES_VAR (CS_TYPE_VAR | CS_TYPE_VAR_NUM)
+#define CS_TYPES_CONST (CS_TYPE_STRING | CS_TYPE_NUM)
+#define CS_ASSOC (CS_OP_RPAREN | CS_OP_RBRACKET)
 
 typedef struct _arg
 {
   CSTOKEN_TYPE op_type;
   char *s;
   long int n;
+  int alloc;
   struct _macro *macro;
   struct _arg *expr1;
   struct _arg *expr2;
@@ -111,6 +121,7 @@
 {
   CSTOKEN_TYPE type;
   char *name;
+  int alloc;
   union
   {
     char *s;
diff -Nbru clearsilver-0.4/cs/csgrammer.y clearsilver-0.5/cs/csgrammer.y
--- clearsilver-0.4/cs/csgrammer.y	Sat Sep 15 00:05:21 2001
+++ clearsilver-0.5/cs/csgrammer.y	Wed Dec 31 16:00:00 1969
@@ -1,51 +0,0 @@
-
-file ::= contents.
-
-contents ::= LITERAL contents.
-contents ::= command contents.
-contents ::= LITERAL.
-contents ::= command.
-
-command ::= CS_OPEN cmd CS_CLOSE.
-command	::= if_cmd.
-command	::= each_cmd.
-command	::= def_cmd.
-
-cmd ::= VAR opt_req expr.
-cmd ::= EVAR opt_req expr.
-cmd ::= INCLUDE opt_req expr.
-cmd ::= SET opt_req expr EQUALS expr.
-cmd ::= CALL opt_req expr LPAREN comma_exprs RPAREN.
-
-if_cmd ::= CS_OPEN IF opt_req expr CS_CLOSE else_contents CS_OPEN ENDIF CS_CLOSE.
-each_cmd ::= CS_OPEN EACH opt_req expr EQUALS expr CS_CLOSE contents CS_OPEN ENDEACH CS_CLOSE.
-def_cmd ::= CS_OPEN DEF opt_req expr LPAREN comma_exprs RPAREN CS_CLOSE contents CS_OPEN ENDDEF CS_CLOSE.
-
-else_contents ::= contents else_contents.
-else_contents ::= CS_OPEN ELIF opt_req expr CS_CLOSE else_contents.
-else_contents ::= CS_OPEN ELSE CS_CLOSE contents.
-
-comma_exprs ::= expr COMMA comma_exprs.
-comma_exprs ::= expr.
-
-expr ::= expr PLUS expr.
-expr ::= expr MINUS expr.
-expr ::= expr MULT expr.
-expr ::= expr DIV expr.
-expr ::= expr MOD expr.
-expr ::= expr AND expr.
-expr ::= expr OR expr.
-expr ::= expr EQ expr.
-expr ::= expr NE expr.
-expr ::= expr GT expr.
-expr ::= expr GE expr.
-expr ::= expr LT expr.
-expr ::= expr LE expr.
-expr ::= NOT expr.
-expr ::= NUM.
-expr ::= STRING.
-expr ::= VAR.
-expr ::= VARNUM.
-
-opt_req ::= COLON.
-opt_req ::= BANG.
diff -Nbru clearsilver-0.4/cs/csparse.c clearsilver-0.5/cs/csparse.c
--- clearsilver-0.4/cs/csparse.c	Thu Jan 10 15:11:39 2002
+++ clearsilver-0.5/cs/csparse.c	Thu Mar 28 17:40:56 2002
@@ -21,10 +21,15 @@
 
 #include "util/neo_err.h"
 #include "util/neo_misc.h"
+#include "util/neo_files.h"
 #include "util/neo_str.h"
 #include "util/ulist.h"
 #include "cs.h"
 
+/* turn on some debug output for expressions */
+#define DEBUG_EXPR_PARSE 0
+#define DEBUG_EXPR_EVAL 0
+
 typedef enum
 {
   ST_SAME = 0,
@@ -35,9 +40,10 @@
   ST_POP = 1<<4,
   ST_DEF = 1<<5,
   ST_LOOP =  1<<6,
+  ST_ALT = 1<<7,
 } CS_STATE;
 
-#define ST_ANYWHERE (ST_EACH | ST_ELSE | ST_IF | ST_GLOBAL | ST_DEF | ST_LOOP)
+#define ST_ANYWHERE (ST_EACH | ST_ELSE | ST_IF | ST_GLOBAL | ST_DEF | ST_LOOP | ST_ALT)
 
 typedef struct _stack_entry 
 {
@@ -62,18 +68,18 @@
 static NEOERR *endif_parse (CSPARSE *parse, int cmd, char *arg);
 static NEOERR *each_parse (CSPARSE *parse, int cmd, char *arg);
 static NEOERR *each_eval (CSPARSE *parse, CSTREE *node, CSTREE **next);
-static NEOERR *endeach_parse (CSPARSE *parse, int cmd, char *arg);
+static NEOERR *end_parse (CSPARSE *parse, int cmd, char *arg);
 static NEOERR *include_parse (CSPARSE *parse, int cmd, char *arg);
 static NEOERR *def_parse (CSPARSE *parse, int cmd, char *arg);
 static NEOERR *skip_eval (CSPARSE *parse, CSTREE *node, CSTREE **next);
-static NEOERR *enddef_parse (CSPARSE *parse, int cmd, char *arg);
 static NEOERR *call_parse (CSPARSE *parse, int cmd, char *arg);
 static NEOERR *call_eval (CSPARSE *parse, CSTREE *node, CSTREE **next);
 static NEOERR *set_parse (CSPARSE *parse, int cmd, char *arg);
 static NEOERR *set_eval (CSPARSE *parse, CSTREE *node, CSTREE **next);
 static NEOERR *loop_parse (CSPARSE *parse, int cmd, char *arg);
 static NEOERR *loop_eval (CSPARSE *parse, CSTREE *node, CSTREE **next);
-static NEOERR *endloop_parse (CSPARSE *parse, int cmd, char *arg);
+static NEOERR *alt_parse (CSPARSE *parse, int cmd, char *arg);
+static NEOERR *alt_eval (CSPARSE *parse, CSTREE *node, CSTREE **next);
 
 static NEOERR *render_node (CSPARSE *parse, CSTREE *node);
 
@@ -110,13 +116,13 @@
   {"each",    sizeof("each")-1,    ST_ANYWHERE,     ST_EACH, 
     each_parse, each_eval,    1},
   {"/each",   sizeof("/each")-1,   ST_EACH,         ST_POP,  
-    endeach_parse, skip_eval, 0},
+    end_parse, skip_eval, 0},
   {"include", sizeof("include")-1, ST_ANYWHERE,     ST_SAME, 
     include_parse, skip_eval, 1},
   {"def",     sizeof("def")-1,     ST_ANYWHERE,     ST_DEF, 
     def_parse, skip_eval, 1},
   {"/def",    sizeof("/def")-1,    ST_DEF,          ST_POP,
-    enddef_parse, skip_eval, 0},
+    end_parse, skip_eval, 0},
   {"call",    sizeof("call")-1,    ST_ANYWHERE,     ST_SAME,
     call_parse, call_eval, 1},
   {"set",    sizeof("set")-1,    ST_ANYWHERE,     ST_SAME,
@@ -124,7 +130,11 @@
   {"loop",    sizeof("loop")-1,    ST_ANYWHERE,     ST_LOOP,
     loop_parse, loop_eval, 1},
   {"/loop",    sizeof("/loop")-1,    ST_LOOP,     ST_POP,
-    endloop_parse, skip_eval, 1},
+    end_parse, skip_eval, 1},
+  {"alt",    sizeof("alt")-1,    ST_ANYWHERE,     ST_ALT,
+    alt_parse, alt_eval, 1},
+  {"/alt",    sizeof("/alt")-1,    ST_ALT,     ST_POP,
+    end_parse, skip_eval, 1},
   {NULL},
 };
 
@@ -323,17 +333,15 @@
 {
   FILE *fp;
   int err = 1;
-  char *context;
   char line[256];
   int count = 0;
   int lineno = 0;
 
-  context = parse->context ? parse->context : "";
   if (offset == -1) offset = parse->offset;
 
   do
   {
-    if (parse->in_file)
+    if (parse->in_file && parse->context)
     {
       /* Open the file and find which line we're on */
 
@@ -354,12 +362,20 @@
     }
     else
     {
+      if (parse->context)
       snprintf (buf, blen, "[%s:%d]", parse->context, offset);
+      else
+	snprintf (buf, blen, "[offset:%d]", offset);
     }
     err = 0;
   } while (0);
   if (err)
+  {
+    if (parse->context)
     snprintf (buf, blen, "[-E- %s:%d]", parse->context, offset);
+    else
+      snprintf (buf, blen, "[-E- offset:%d]", offset);
+  }
 
   return buf;
 }
@@ -378,6 +394,10 @@
     return "EACH";
   else if (state & ST_DEF)
     return "DEF";
+  else if (state & ST_LOOP)
+    return "LOOP";
+  else if (state & ST_ALT)
+    return "ALT";
 
   snprintf(buf, sizeof(buf), "Unknown state %d", state);
   return buf;
@@ -532,6 +552,8 @@
   CS_LOCAL_MAP *map;
   char *c;
 
+  /* This shouldn't happen, but it did once... */
+  if (name == NULL) return NULL;
   map = parse->locals;
   c = strchr (name, '.');
   if (c != NULL) *c = '\0';
@@ -621,6 +643,8 @@
     /* Hmm, if c != NULL, they are asking for a sub member of something
      * which isn't a var... right now we ignore them, I don't know what
      * the right thing is */
+    /* hmm, its possible now that they are getting a reference to a
+     * string that will be deleted... where is it used? */
     else if (map->type == CS_TYPE_STRING)
     {
       return map->value.s;
@@ -674,6 +698,10 @@
   { FALSE, "*", CS_OP_MULT },
   { FALSE, "/", CS_OP_DIV },
   { FALSE, "%", CS_OP_MOD },
+  { FALSE, "(", CS_OP_LPAREN },
+  { FALSE, ")", CS_OP_RPAREN },
+  { FALSE, "[", CS_OP_LBRACKET },
+  { FALSE, "]", CS_OP_RBRACKET },
   { FALSE, NULL, 0 }
 };
 
@@ -780,15 +808,57 @@
    CS_OP_GT | CS_OP_GTE | CS_OP_LT | CS_OP_LTE,
    CS_OP_ADD | CS_OP_SUB, 
    CS_OP_MULT | CS_OP_DIV | CS_OP_MOD,
+   CS_OP_LBRACKET,
    0
 };
 
+static char *expand_token_type(CSTOKEN_TYPE t_type)
+{
+  switch (t_type)
+  {
+    case CS_OP_EXISTS: return "?";
+    case CS_OP_NOT: return "!";
+    case CS_OP_EQUAL: return "==";
+    case CS_OP_NEQUAL: return "!=";
+    case CS_OP_LT: return "<";
+    case CS_OP_LTE: return "<=";
+    case CS_OP_GT: return ">";
+    case CS_OP_GTE: return ">=";
+    case CS_OP_AND: return "&&";
+    case CS_OP_OR: return "||";
+    case CS_OP_ADD: return "+";
+    case CS_OP_SUB: return "-";
+    case CS_OP_MULT: return "*";
+    case CS_OP_DIV: return "/";
+    case CS_OP_MOD: return "%";
+    case CS_OP_LPAREN: return "(";
+    case CS_OP_RPAREN: return ")";
+    case CS_OP_LBRACKET: return "[";
+    case CS_OP_RBRACKET: return "]";
+    case CS_TYPE_STRING: return "s";
+    case CS_TYPE_NUM: return "n";
+    case CS_TYPE_VAR: return "v";
+    case CS_TYPE_VAR_NUM: return "vn";
+    default: return "u";
+  }
+  return "u";
+}
+
 static NEOERR *parse_expr2 (CSPARSE *parse, CSTOKEN *tokens, int ntokens, 
     CSARG *arg)
 {
   NEOERR *err;
   char tmp[256];
   int x, op = 0;
+  int m;
+
+#if DEBUG_EXPR_PARSE
+  for (x = 0; x < ntokens; x++)
+  {
+    fprintf (stderr, "%s ", expand_token_type(tokens[x].type));
+  }
+  fprintf(stderr, "\n");
+#endif
 
   if (ntokens == 1 || (ntokens == 2 && tokens[0].type == CS_OP_NOT))
   {
@@ -809,8 +879,8 @@
     else
     {
       return nerr_raise (NERR_PARSE, 
-	  "%s Terminal token is not an argument, type is %d",
-	  find_context(parse, -1, tmp, sizeof(tmp)), tokens[0].type);
+	  "%s Terminal token is not an argument, type is %s",
+	  find_context(parse, -1, tmp, sizeof(tmp)), expand_token_type(tokens[0].type));
     }
   }
 
@@ -819,6 +889,52 @@
     x = ntokens-1;
     while (x >= 0)
     {
+      /* handle associative ops by skipping through the entire set here,
+       * ie the whole thing is an expression that can't match a binary op */
+      if (tokens[x].type & CS_OP_RPAREN)
+      {
+	m = 1;
+	x--;
+	while (x >= 0)
+	{
+	  if (tokens[x].type & CS_OP_RPAREN) m++;
+	  if (tokens[x].type & CS_OP_LPAREN) m--;
+	  if (m == 0) break;
+	  x--;
+	}
+	if (m)
+	  return nerr_raise (NERR_PARSE, 
+	      "%s Missing left parenthesis in expression",
+	      find_context(parse, -1, tmp, sizeof(tmp)));
+	if (x == 0) break;
+	x--;
+      }
+      if (tokens[x].type & CS_OP_RBRACKET)
+      {
+	m = 1;
+	x--;
+	while (x >= 0)
+	{
+	  if (tokens[x].type & CS_OP_RBRACKET) m++;
+	  if (tokens[x].type & CS_OP_LBRACKET) m--;
+	  if (m == 0) break;
+	  x--;
+	}
+	if (m)
+	  return nerr_raise (NERR_PARSE, 
+	      "%s Missing left bracket in expression",
+	      find_context(parse, -1, tmp, sizeof(tmp)));
+	if (x == 0) break;
+	/* we don't do an x-- here, because we are special casing the
+	 * left bracket to be both an operator and an associative */
+      }
+      else if (tokens[x].type & (CS_OP_LBRACKET | CS_OP_LPAREN))
+      {
+	return nerr_raise (NERR_PARSE, 
+	    "%s Missing right %s in expression",
+	    find_context(parse, -1, tmp, sizeof(tmp)), 
+	    (tokens[x].type == CS_OP_LBRACKET) ? "bracket" : "parenthesis");
+      }
       if (tokens[x].type & BinaryOpOrder[op])
       {
 	arg->op_type = tokens[x].type;
@@ -828,7 +944,14 @@
 	  return nerr_raise (NERR_NOMEM, 
 	      "%s Unable to allocate memory for expression", 
 	      find_context(parse, -1, tmp, sizeof(tmp)));
+	if (tokens[x].type == CS_OP_LBRACKET)
+	{
+	  err = parse_expr2(parse, tokens + x, ntokens-x, arg->expr2);
+	}
+	else
+	{
 	err = parse_expr2(parse, tokens + x + 1, ntokens-x-1, arg->expr2);
+	}
 	if (err) return nerr_pass (err);
 	err = parse_expr2(parse, tokens, x, arg->expr1);
 	if (err) return nerr_pass (err);
@@ -838,6 +961,15 @@
     }
     op++;
   }
+  /* ah, this expression just contains enclosing associatives, strip them */
+  x = ntokens-1;
+  if ((tokens[0].type == CS_OP_LPAREN && tokens[x].type == CS_OP_RPAREN) ||
+      (tokens[0].type == CS_OP_LBRACKET && tokens[x].type == CS_OP_RBRACKET))
+  {
+    /* parens don't do anything, just strip them and pass */
+    return nerr_pass(parse_expr2(parse, tokens + 1, ntokens-2, arg));
+  }
+
   return nerr_raise (NERR_PARSE, "%s Bad Expression",
       find_context(parse, -1, tmp, sizeof(tmp)));
 }
@@ -964,6 +1096,33 @@
   return STATUS_OK;
 }
 
+static NEOERR *alt_parse (CSPARSE *parse, int cmd, char *arg)
+{
+  NEOERR *err;
+  CSTREE *node;
+
+  /* ne_warn ("var: %s", arg); */
+  err = alloc_node (&node);
+  if (err) return nerr_pass(err);
+  node->cmd = cmd;
+  if (arg[0] == '!')
+    node->flags |= CSF_REQUIRED;
+  arg++;
+  /* Validate arg is a var (regex /^[#" ]$/) */
+  err = parse_expr (parse, arg, &(node->arg1));
+  if (err)
+  {
+    dealloc_node(&node);
+    return nerr_pass(err);
+  }
+
+  *(parse->next) = node;
+  parse->next = &(node->case_0);
+  parse->current = node;
+  
+  return STATUS_OK;
+}
+
 static NEOERR *evar_parse (CSPARSE *parse, int cmd, char *arg)
 {
   NEOERR *err;
@@ -1058,16 +1217,13 @@
     switch ((arg->op_type & CS_TYPES))
     {
       case CS_TYPE_STRING:
-      case CS_TYPE_STRING_ALLOC:
 	return arg->s;
       case CS_TYPE_VAR:
 	return var_lookup (parse, arg->s);
       case CS_TYPE_NUM:
       case CS_TYPE_VAR_NUM:
-	/* These types should force numeric evaluation... so nothing
-	 * should get here */
       default:
-	ne_warn ("Unsupported type %s in arg_eval", arg->op_type);
+	ne_warn ("Unsupported type %d in arg_eval", arg->op_type);
 	return NULL;
     }
   }
@@ -1080,7 +1236,6 @@
   switch ((arg->op_type & CS_TYPES))
   {
     case CS_TYPE_STRING:
-    case CS_TYPE_STRING_ALLOC:
       v = strtol(arg->s, NULL, 0);
       break;
     case CS_TYPE_NUM:
@@ -1101,33 +1256,98 @@
   return v;
 }
 
+#if DEBUG_EXPR_EVAL
+static char *expand_arg (CSARG *arg)
+{
+  fprintf(stderr, "op: %s alloc: %d value: ", expand_token_type(arg->op_type), arg->alloc);
+  if (arg->op_type & CS_OP_NOT)
+    fprintf(stderr, "!");
+  if (arg->op_type & (CS_TYPE_VAR_NUM | CS_TYPE_NUM))
+    fprintf(stderr, "#");
+  if (arg->op_type & CS_TYPE_NUM)
+    fprintf(stderr, "%ld\n", arg->n);
+  else if (arg->op_type & (CS_TYPE_VAR_NUM | CS_TYPE_VAR | CS_TYPE_STRING))
+    fprintf(stderr, "%s\n", arg->s);
+  else
+    fprintf(stderr, "\n");
+}
+#endif
+
 static NEOERR *eval_expr (CSPARSE *parse, CSARG *expr, CSARG *result)
 {
+  CSARG arg1, arg2;
+  NEOERR *err;
+  long int n1, n2;
+  char *s1, *s2;
+  int out;
+
+#if DEBUG_EXPR_EVAL
+  fprintf(stderr, "expr ");
+  expand_arg(expr);
+#endif
+
   memset(result, 0, sizeof(CSARG));
   if (expr->op_type & CS_TYPES)
   {
     *result = *expr;
     /* we transfer ownership of the string here.. ugh */
-    if (expr->op_type == CS_TYPE_STRING_ALLOC)
-      expr->op_type = CS_TYPE_STRING;
+    if (expr->alloc) expr->alloc = 0;
+#if DEBUG_EXPR_EVAL
+    fprintf(stderr, "result ");
+    expand_arg(result);
+#endif
     return STATUS_OK;
   }
   else
   {
-    CSARG arg1, arg2;
-    NEOERR *err;
-
     err = eval_expr (parse, expr->expr1, &arg1);
     if (err) return nerr_pass(err);
+#if DEBUG_EXPR_EVAL
+    fprintf(stderr, "arg1 ");
+    expand_arg(&arg1);
+#endif
     err = eval_expr (parse, expr->expr2, &arg2);
+#if DEBUG_EXPR_EVAL
+    fprintf(stderr, "arg2 ");
+    expand_arg(&arg2);
+#endif
     if (err) return nerr_pass(err);
-    /* Check for type conversion */
-    if ((arg1.op_type & (CS_OP_NOT | CS_TYPE_NUM | CS_TYPE_VAR_NUM)) ||
+
+    if (expr->op_type == CS_OP_LBRACKET)
+    {
+      /* the bracket op is essentially hdf array lookups, which just
+       * means appending .0 */
+      result->op_type = CS_TYPE_VAR;
+      result->alloc = 1;
+      if (arg2.op_type & (CS_TYPE_VAR_NUM | CS_TYPE_NUM))
+      {
+	n2 = arg_eval_num (parse, &arg2);
+	result->s = sprintf_alloc("%s.%d", arg1.s, n2);
+	if (result->s == NULL)
+	  return nerr_raise (NERR_NOMEM, "Unable to allocate memory to concatenate varnames in expression: %s + %d", arg1.s, n2);
+      }
+      else
+      {
+	s2 = arg_eval (parse, &arg2);
+	if (s2 && s2[0])
+	{
+	  result->s = sprintf_alloc("%s.%s", arg1.s, s2);
+	  if (result->s == NULL)
+	    return nerr_raise (NERR_NOMEM, "Unable to allocate memory to concatenate varnames in expression: %s + %s", arg1.s, s2);
+	}
+	else
+	{
+	  /* if s2 doesn't match anything, then the whole thing is empty */
+	  result->s = "";
+	  result->alloc = 0;
+	}
+      }
+    }
+    else if ((arg1.op_type & (CS_OP_NOT | CS_TYPE_NUM | CS_TYPE_VAR_NUM)) ||
 	(arg2.op_type & (CS_OP_NOT | CS_TYPE_NUM | CS_TYPE_VAR_NUM)) ||
 	(expr->op_type & (CS_OP_AND | CS_OP_OR | CS_OP_SUB | CS_OP_MULT | CS_OP_DIV | CS_OP_MOD)))
     {
       /* eval as num */
-      long int n1, n2;
 
       result->op_type = CS_TYPE_NUM;
       n1 = arg_eval_num (parse, &arg1);
@@ -1183,9 +1403,6 @@
     }
     else /* eval as string */
     {
-      char *s1, *s2;
-      int out;
-
       result->op_type = CS_TYPE_NUM;
       s1 = arg_eval (parse, &arg1);
       s2 = arg_eval (parse, &arg2);
@@ -1213,17 +1430,19 @@
 	    result->n = (s2 == NULL) ? 1 : 0;
 	    break;
 	  case CS_OP_ADD:
+	    /* be sure to transfer ownership of the string here */
+	    result->op_type = CS_TYPE_STRING;
 	    if (s1 == NULL) 
 	    {
 	      result->s = s2;
-	      result->op_type = arg2.op_type;
-	      arg2.op_type = CS_TYPE_STRING;
+	      result->alloc = arg2.alloc;
+	      arg2.alloc = 0;
 	    }
 	    else
 	    {
 	      result->s = s1;
-	      result->op_type = arg1.op_type;
-	      arg1.op_type = CS_TYPE_STRING;
+	      result->alloc = arg1.alloc;
+	      arg1.alloc = 0;
 	    }
 	    break;
 	  default:
@@ -1255,7 +1474,8 @@
 	    result->n = (out >= 0) ? 1 : 0;
 	    break;
 	  case CS_OP_ADD:
-	    result->op_type = CS_TYPE_STRING_ALLOC;
+	    result->op_type = CS_TYPE_STRING;
+	    result->alloc = 1;
 	    result->s = (char *) calloc ((strlen(s1) + strlen(s2) + 1), sizeof(char));
 	    if (result->s == NULL)
 	      return nerr_raise (NERR_NOMEM, "Unable to allocate memory to concatenate strings in expression: %s + %s", s1, s2);
@@ -1263,17 +1483,19 @@
 	    strcat(result->s, s2);
 	    break;
 	  default:
-	    ne_warn ("Unsupported op %d in expr_eval", expr->op_type);
+	    ne_warn ("Unsupported op %d in eval_expr", expr->op_type);
 	    break;
 	}
       }
     }
     
-    if (arg1.op_type == CS_TYPE_STRING_ALLOC)
-      free(arg1.s);
-    if (arg2.op_type == CS_TYPE_STRING_ALLOC)
-      free(arg2.s);
+    if (arg1.alloc) free(arg1.s);
+    if (arg2.alloc) free(arg2.s);
   }
+#if DEBUG_EXPR_EVAL
+  fprintf(stderr, "result ");
+  expand_arg(result);
+#endif
   return STATUS_OK;
 }
 
@@ -1302,8 +1524,64 @@
       err = parse->output_cb (parse->output_ctx, s);
     }
   }
-  if (val.op_type == CS_TYPE_STRING_ALLOC)
-    free(val.s);
+  if (val.alloc) free(val.s);
+
+  *next = node->next;
+  return nerr_pass(err);
+}
+
+/* if the expr evaluates to true, display it, else render the alternate */
+static NEOERR *alt_eval (CSPARSE *parse, CSTREE *node, CSTREE **next)
+{
+  NEOERR *err = STATUS_OK;
+  CSARG val;
+  int eval_true = 1;
+
+  err = eval_expr(parse, &(node->arg1), &val);
+  if (err) return nerr_pass(err);
+  if (val.op_type & (CS_TYPE_NUM | CS_TYPE_VAR_NUM))
+  { 
+    char buf[256];
+    long int n_val;
+
+    n_val = arg_eval_num (parse, &val);
+    if (n_val)
+    {
+      snprintf (buf, sizeof(buf), "%ld", n_val);
+      err = parse->output_cb (parse->output_ctx, buf);
+    }
+    else
+    {
+      eval_true = 0;
+    }
+  }
+  else
+  {
+    char *s;
+    BOOL not = FALSE;
+    if (val.op_type & CS_OP_NOT)
+    {
+      not = TRUE;
+      val.op_type &= ~CS_OP_NOT;
+    }
+    s = arg_eval (parse, &val);
+    if (s == NULL || *s == '\0')
+      eval_true = 0;
+
+    if (not == TRUE)
+      eval_true = !eval_true;
+
+    if (eval_true && s)
+    {
+      err = parse->output_cb (parse->output_ctx, s);
+    }
+  }
+  if (val.alloc) free(val.s);
+
+  if (eval_true == 0)
+  {
+    err = render_node (parse, node->case_0);
+  }
 
   *next = node->next;
   return nerr_pass(err);
@@ -1338,8 +1616,7 @@
     if (not == TRUE)
       eval_true = !eval_true;
   }
-  if (val.op_type == CS_TYPE_STRING_ALLOC)
-    free(val.s);
+  if (val.alloc) free(val.s);
 
   if (eval_true)
   {
@@ -1455,9 +1732,12 @@
   node->arg1.op_type = CS_TYPE_VAR;
   node->arg1.s = lvar;
 
-  node->arg2.op_type = CS_TYPE_VAR;
-  node->arg2.s = p;
-
+  err = parse_expr(parse, p, &(node->arg2));
+  if (err) 
+  {
+    dealloc_node(&node);
+    return nerr_pass(err);
+  }
   /* ne_warn ("each %s %s", lvar, p); */
 
   *(parse->next) = node;
@@ -1471,9 +1751,15 @@
 {
   NEOERR *err = STATUS_OK;
   CS_LOCAL_MAP each_map;
+  CSARG val;
   HDF *var, *child;
 
-  var = var_lookup_obj (parse, node->arg2.s);
+  err = eval_expr(parse, &(node->arg2), &val);
+  if (err) return nerr_pass(err);
+
+  if (val.op_type == CS_TYPE_VAR)
+  {
+    var = var_lookup_obj (parse, val.s);
 
   if (var != NULL)
   {
@@ -1499,12 +1785,14 @@
     /* Remove local map */
     parse->locals = each_map.next;
   }
+  } /* else WARNING */
+  if (val.alloc) free(val.s);
 
   *next = node->next;
   return nerr_pass (err);
 }
 
-static NEOERR *endeach_parse (CSPARSE *parse, int cmd, char *arg)
+static NEOERR *end_parse (CSPARSE *parse, int cmd, char *arg)
 {
   NEOERR *err;
   STACK_ENTRY *entry;
@@ -1703,19 +1991,6 @@
   return STATUS_OK;
 }
 
-static NEOERR *enddef_parse (CSPARSE *parse, int cmd, char *arg)
-{
-  NEOERR *err;
-  STACK_ENTRY *entry;
-
-  err = uListGet (parse->stack, -1, (void **)&entry);
-  if (err != STATUS_OK) return nerr_pass(err);
-
-  parse->next = &(entry->tree->next);
-  parse->current = entry->tree;
-  return STATUS_OK;
-}
-
 static NEOERR *call_parse (CSPARSE *parse, int cmd, char *arg)
 {
   NEOERR *err;
@@ -1843,36 +2118,74 @@
 
   for (x = 0; x < macro->n_args; x++)
   {
+    CSARG val;
     map = &call_map[x];
     if (x) call_map[x-1].next = map;
 
     map->name = darg->s;
-    if (carg->op_type == CS_TYPE_STRING)
+    err = eval_expr(parse, carg, &val);
+    if (err) break;
+    if (val.op_type & CS_TYPE_STRING)
     {
-      map->value.s = carg->s;
-      map->type = CS_TYPE_STRING;
+      map->value.s = val.s;
+      map->type = val.op_type;
+      map->alloc = val.alloc;
+      val.alloc = 0;
     }
-    else if (carg->op_type == CS_TYPE_NUM)
+    else if (val.op_type & CS_TYPE_NUM)
     {
-      map->value.n = carg->n;
+      map->value.n = val.n;
       map->type = CS_TYPE_NUM;
     }
+    else if (val.op_type & (CS_TYPE_VAR | CS_TYPE_VAR_NUM))
+    {
+      CS_LOCAL_MAP *lmap;
+      char *c;
+      lmap = lookup_map (parse, val.s, &c);
+      if (lmap != NULL && (lmap->type != CS_TYPE_VAR && lmap->type != CS_TYPE_VAR_NUM))
+      {
+	/* if we're referencing a local var which maps to a string or
+	 * number... then copy  */
+	if (lmap->type == CS_TYPE_NUM)
+	{
+	  map->value.n = lmap->value.n;
+	  map->type = lmap->type;
+	}
     else 
     {
-      var = var_lookup_obj (parse, carg->s);
+	  map->value.s = lmap->value.s;
+	  map->type = lmap->type;
+	}
+      }
+      else
+      {
+	var = var_lookup_obj (parse, val.s);
       map->value.h = var;
       map->type = CS_TYPE_VAR;
     }
+    }
+    else
+    {
+      ne_warn("Unsupported type %d in call_expr", val.op_type);
+    }
+    if (val.alloc) free(val.s);
     map->next = parse->locals;
 
     darg = darg->next;
     carg = carg->next;
   }
 
+  if (err == STATUS_OK)
+  {
   map = parse->locals;
   if (macro->n_args) parse->locals = call_map;
   err = render_node (parse, macro->tree->case_0);
   parse->locals = map;
+  }
+  for (x = 0; x < macro->n_args; x++)
+  {
+    if (call_map[x].alloc) free(call_map[x].value.s);
+  }
   free (call_map);
 
   *next = node->next;
@@ -1885,32 +2198,28 @@
   CSTREE *node;
   char *s;
   char tmp[256];
-  char name[256];
-  int x = 0;
 
   err = alloc_node (&node);
   if (err) return nerr_pass(err);
   node->cmd = cmd;
   arg++;
   s = arg;
-  while (*s && isspace(*s)) s++;
-  while (x < 256 && *s && *s != ' ' && *s != '#' && *s != '=')
-  {
-    name[x++] = *s;
-    s++;
-  }
-  name[x] = '\0';
-  while (*s && isspace(*s)) s++;
-  if (*s == '\0' || *s != '=')
+  while (*s && *s != '=') s++;
+  if (*s == '\0')
   {
     dealloc_node(&node);
     return nerr_raise (NERR_PARSE, 
 	"%s Missing equals in set %s", 
 	find_context(parse, -1, tmp, sizeof(tmp)), arg);
   }
+  *s = '\0';
   s++;
-  node->arg1.op_type = CS_TYPE_VAR;
-  node->arg1.s = strdup(name);
+  err = parse_expr(parse, arg, &(node->arg1));
+  if (err)
+  {
+    dealloc_node(&node);
+    return nerr_pass(err);
+  }
 
   err = parse_expr(parse, s, &(node->arg2));
   if (err)
@@ -1930,10 +2239,20 @@
 {
   NEOERR *err = STATUS_OK;
   CSARG val;
+  CSARG set;
 
-  err = eval_expr(parse, &(node->arg2), &val);
+  err = eval_expr(parse, &(node->arg1), &set);
   if (err) return nerr_pass (err);
+  err = eval_expr(parse, &(node->arg2), &val);
+  if (err) {
+    if (set.alloc) free(set.s);
+    return nerr_pass (err);
+  }
 
+  if (set.op_type != CS_TYPE_NUM)
+  {
+    /* this allow for a weirdness where set:"foo"="bar" 
+     * actually sets the hdf var foo... */
   if (val.op_type & (CS_TYPE_NUM | CS_TYPE_VAR_NUM))
   { 
     char buf[256];
@@ -1941,7 +2260,7 @@
 
     n_val = arg_eval_num (parse, &val);
     snprintf (buf, sizeof(buf), "%ld", n_val);
-    err = var_set_value (parse, node->arg1.s, buf);
+      err = var_set_value (parse, set.s, buf);
   }
   else
   {
@@ -1949,11 +2268,12 @@
     /* Do we set it to blank if s == NULL? */
     if (s)
     {
-      err = var_set_value (parse, node->arg1.s, s);
+	err = var_set_value (parse, set.s, s);
     }
   }
-  if (val.op_type == CS_TYPE_STRING_ALLOC)
-    free(val.s);
+  } /* else WARNING */
+  if (set.alloc) free(set.s);
+  if (val.alloc) free(val.s);
 
   *next = node->next;
   return nerr_pass (err);
@@ -2075,7 +2395,7 @@
   err = eval_expr(parse, carg, &val);
   if (err) return nerr_pass(err);
   end = arg_eval_num(parse, &val);
-  if (val.op_type == CS_TYPE_STRING_ALLOC) free(val.s);
+  if (val.alloc) free(val.s);
   if (carg->next)
   {
     start = end;
@@ -2083,14 +2403,14 @@
     err = eval_expr(parse, carg, &val);
     if (err) return nerr_pass(err);
     end = arg_eval_num(parse, &val);
-    if (val.op_type == CS_TYPE_STRING_ALLOC) free(val.s);
+    if (val.alloc) free(val.s);
     if (carg->next)
     {
       carg = carg->next;
       err = eval_expr(parse, carg, &val);
       if (err) return nerr_pass(err);
       step = arg_eval_num(parse, &val);
-      if (val.op_type == CS_TYPE_STRING_ALLOC) free(val.s);
+      if (val.alloc) free(val.s);
     }
   }
   /* automatically handle cases where the step is backwards */
@@ -2133,18 +2453,6 @@
 
   *next = node->next;
   return nerr_pass (err);
-}
-static NEOERR *endloop_parse (CSPARSE *parse, int cmd, char *arg)
-{
-  NEOERR *err;
-  STACK_ENTRY *entry;
-
-  err = uListGet (parse->stack, -1, (void **)&entry);
-  if (err != STATUS_OK) return nerr_pass(err);
-
-  parse->next = &(entry->tree->next);
-  parse->current = entry->tree;
-  return STATUS_OK;
 }
 
 static NEOERR *skip_eval (CSPARSE *parse, CSTREE *node, CSTREE **next)
diff -Nbru clearsilver-0.4/cs/test.hdf clearsilver-0.5/cs/test.hdf
--- clearsilver-0.4/cs/test.hdf	Sat Sep 15 17:08:55 2001
+++ clearsilver-0.5/cs/test.hdf	Thu Mar 28 17:40:56 2002
@@ -60,3 +60,22 @@
 My.Test2 : Days.0
 
 Color = #fffff
+
+CGI.box.msgs {
+  0 {
+    ticket_id = 1
+  }
+  1 {
+    ticket_id = 2
+  }
+  2 {
+    ticket_id = 3
+  }
+}
+
+Query.boxid = 2
+Query.split = 1
+Query.filter = 0
+Query.sort = t
+Query.sort_dir = u
+CGI.box.cur.min_box_idx = 1
diff -Nbru clearsilver-0.4/cs/test10.cs clearsilver-0.5/cs/test10.cs
--- clearsilver-0.4/cs/test10.cs	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.5/cs/test10.cs	Wed Mar 13 00:31:27 2002
@@ -0,0 +1,22 @@
+test new alt tag
+
+<?cs alt:#0 ?>
+this should always display [1]
+<?cs /alt ?>
+
+<?cs alt:"this should display [2]" ?>
+ERROR: this shouldn't display (static string)
+<?cs /alt ?>
+
+<?cs alt:#1 ?>
+ERROR: this should never display (#1)
+<?cs /alt ?>
+
+<?cs var:Foo.Bar.Baz.0 ?>
+<?cs alt:Foo.Bar.Baz.0 ?>
+ERROR: this should never display (Foo.Bar.Baz.0 exists)
+<?cs /alt ?>
+
+<?cs alt:MyDadday ?>
+This should display [3]
+<?cs /alt ?>
diff -Nbru clearsilver-0.4/cs/test10.cs.gold clearsilver-0.5/cs/test10.cs.gold
--- clearsilver-0.4/cs/test10.cs.gold	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.5/cs/test10.cs.gold	Wed Mar 13 00:31:27 2002
@@ -0,0 +1,17 @@
+Parsing test10.cs
+test new alt tag
+
+
+this should always display [1]
+
+
+this should display [2]
+
+1
+
+zero
+zero
+
+
+This should display [3]
+
diff -Nbru clearsilver-0.4/cs/test11.cs clearsilver-0.5/cs/test11.cs
--- clearsilver-0.4/cs/test11.cs	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.5/cs/test11.cs	Thu Mar 28 01:18:52 2002
@@ -0,0 +1,34 @@
+
+testing parenthesis for order of operations
+
+<?cs var:#5 * (#4 + #3) + #5 ?> == 40
+<?cs var:(#4 + #3) ?> == 7
+<?cs var:#4 + #3 * #5 ?> == 19
+<?cs var:(#4 + #3) * #5 ?> == 35
+<?cs var:((#4 + #3) * #5) + #5 ?> == 40
+<?cs var:#5 + ((#4 + #3) * #5) ?> == 40
+<?cs var:(#4 + #5) * (#3 + #6) ?> == 81
+<?cs var:#6 + #5 * (#5 + #3) ?> == 46
+<?cs var:(#6 + #3) + #5 * (#5 + #3) ?> == 49
+
+testing brackets for hdf var arrays
+
+<?cs var:v[#5+#3] ?>
+<?cs var:#3 + v[#5+#3] + #6 ?>
+
+<?cs var:Days[#0] ?> == 0
+<?cs var:Days[#1] ?> == 1
+<?cs var:Days[#2] ?> == 2
+
+<?cs var:Days[#0]["Abbr"] ?> == Mon
+<?cs var:Days[#1]["Abbr"] ?> == Tues
+<?cs var:Days[#2]["Abbr"] ?> == Wed
+
+<?cs set:ins = "Inside" ?>
+<?cs each:in=Outside[#1][ins] ?> <?cs var:in ?><?cs /each ?> == 2 3
+
+<?cs loop:x=#1,#20 ?><?cs set:foo[x] = x ?><?cs /loop ?>
+<?cs loop:x=#1,#20 ?><?cs var:foo[x] ?> == <?cs var:x ?>
+<?cs /loop ?>
+<?cs each:x=foo ?><?cs var:x ?>
+<?cs /each ?>
diff -Nbru clearsilver-0.4/cs/test11.cs.gold clearsilver-0.5/cs/test11.cs.gold
--- clearsilver-0.4/cs/test11.cs.gold	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.5/cs/test11.cs.gold	Thu Mar 28 01:18:52 2002
@@ -0,0 +1,73 @@
+Parsing test11.cs
+
+testing parenthesis for order of operations
+
+40 == 40
+7 == 7
+19 == 19
+35 == 35
+40 == 40
+40 == 40
+81 == 81
+46 == 46
+49 == 49
+
+testing brackets for hdf var arrays
+
+
+9
+
+0 == 0
+1 == 1
+2 == 2
+
+Mon == Mon
+Tues == Tues
+Wed == Wed
+
+
+ 2 3 == 2 3
+
+
+1 == 1
+2 == 2
+3 == 3
+4 == 4
+5 == 5
+6 == 6
+7 == 7
+8 == 8
+9 == 9
+10 == 10
+11 == 11
+12 == 12
+13 == 13
+14 == 14
+15 == 15
+16 == 16
+17 == 17
+18 == 18
+19 == 19
+20 == 20
+
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
diff -Nbru clearsilver-0.4/cs/test11.cs.out clearsilver-0.5/cs/test11.cs.out
--- clearsilver-0.4/cs/test11.cs.out	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.5/cs/test11.cs.out	Thu Mar 28 01:18:52 2002
@@ -0,0 +1,73 @@
+Parsing test11.cs
+
+testing parenthesis for order of operations
+
+40 == 40
+7 == 7
+19 == 19
+35 == 35
+40 == 40
+40 == 40
+81 == 81
+46 == 46
+49 == 49
+
+testing brackets for hdf var arrays
+
+
+9
+
+0 == 0
+1 == 1
+2 == 2
+
+Mon == Mon
+Tues == Tues
+Wed == Wed
+
+
+ 2 3 == 2 3
+
+
+1 == 1
+2 == 2
+3 == 3
+4 == 4
+5 == 5
+6 == 6
+7 == 7
+8 == 8
+9 == 9
+10 == 10
+11 == 11
+12 == 12
+13 == 13
+14 == 14
+15 == 15
+16 == 16
+17 == 17
+18 == 18
+19 == 19
+20 == 20
+
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
diff -Nbru clearsilver-0.4/cs/test3.cs clearsilver-0.5/cs/test3.cs
--- clearsilver-0.4/cs/test3.cs	Thu Jul  5 01:23:13 2001
+++ clearsilver-0.5/cs/test3.cs	Wed Mar 27 16:47:28 2002
@@ -34,10 +34,47 @@
 before echo
 
 echo a variable: 3
-<?cs call:echo(Woo.Foo) ?>
+<?cs call:echo(Wow.Foo) ?>
 echo a string: hellow world
 <?cs call:echo("hello world") ?>
 echo a number: 5
 <?cs call:echo(#5) ?>
 
+<?cs def:call_echo(wow) ?>
+<?cs call:echo(wow) ?>
+<?cs /def ?>
+
+echo a variable: 3
+<?cs call:call_echo(Wow.Foo) ?>
+echo a string: hellow world
+<?cs call:call_echo("hello world") ?>
+echo a number: 5
+<?cs call:call_echo(#5) ?>
+
+<?cs def:echo2(bar) ?>
+  <?cs var:wow ?>
+<?cs /def ?>
+
+<?cs def:call_echo2(wow, weird) ?>
+  <?cs call:echo2(weird) ?>
+<?cs /def ?>
+
+these tests show that local variables are live in sub calls 
+echo a variable: 3
+<?cs call:call_echo2(Wow.Foo, "error") ?>
+echo a string: hellow world
+<?cs call:call_echo2("hello world", "error") ?>
+echo a number: 5
+<?cs call:call_echo2(#5, "error") ?>
+
 after echo
+
+<?cs def:print_day(d) ?>
+  <?cs var:d ?> == <?cs var:d.Abbr ?>
+<?cs /def ?>
+
+testing macro calls in local vars in an each
+<?cs each:day=Days ?>
+  <?cs call:print_day(day) ?>
+  <?cs call:echo(day.Abbr) ?>
+<?cs /each ?>
diff -Nbru clearsilver-0.4/cs/test3.cs.gold clearsilver-0.5/cs/test3.cs.gold
--- clearsilver-0.4/cs/test3.cs.gold	Fri Jul  6 00:29:42 2001
+++ clearsilver-0.5/cs/test3.cs.gold	Wed Mar 27 16:47:28 2002
@@ -34,15 +34,114 @@
 
 echo a variable: 3
 
+  3
+
+echo a string: hellow world
+
+  hello world
+
+echo a number: 5
+
+  5
+
+
+
+
+echo a variable: 3
+
+
+  3
   
 
 echo a string: hellow world
 
+
   hello world
 
+
 echo a number: 5
 
+
   5
 
 
+
+
+
+
+
+these tests show that local variables are live in sub calls 
+echo a variable: 3
+
+  
+  3
+
+
+echo a string: hellow world
+
+  
+  hello world
+
+
+echo a number: 5
+
+  
+  5
+
+
+
 after echo
+
+
+
+testing macro calls in local vars in an each
+
+  
+  0 == Mon
+
+  
+  Mon
+
+
+  
+  1 == Tues
+
+  
+  Tues
+
+
+  
+  2 == Wed
+
+  
+  Wed
+
+
+  
+  3 == Thur
+
+  
+  Thur
+
+
+  
+  4 == Fri
+
+  
+  Fri
+
+
+  
+  5 == Sat
+
+  
+  Sat
+
+
+  
+  6 == Sun
+
+  
+  Sun
+
+
diff -Nbru clearsilver-0.4/cs/test4.cs clearsilver-0.5/cs/test4.cs
--- clearsilver-0.4/cs/test4.cs	Tue Jul  3 17:43:05 2001
+++ clearsilver-0.5/cs/test4.cs	Thu Mar 28 17:40:56 2002
@@ -48,3 +48,54 @@
 <?cs else ?>
 right, 4 > 0
 <?cs /if ?>
+
+<?cs if:#0 <= #5 ?>
+right, 0 <= 5
+<?cs else ?>
+ERROR! 0 <= 5
+<?cs /if ?>
+
+<?cs if:#0 >= #5 ?>
+ERROR! 0 >= 5
+<?cs else ?>
+right, 0 >= 5
+<?cs /if ?>
+
+<?cs if:"0" <= #5 ?>
+right "0" <= #5
+<?cs else ?>
+ERROR! "0" <= #5
+<?cs /if ?>
+
+
+<?cs each:msg = CGI.box.msgs ?>
+
+<?cs set:row_url = "/box_bm_body_frm.cs?boxid=" +
+                        Query.boxid + "&cur=" + msg.ticket_id +
+                       "&idx_cur=" + CGI.box.cur.min_box_idx +
+                       "&split=" + Query.split  +
+                       "&filter=" + Query.filter +
+                       "&sort=" + Query.sort +
+                       "&sort_dir=" + Query.sort_dir +
+                       "&from_search=" + Query.from_search ?>
+
+<?cs var:row_url ?>
+
+<?cs /each ?>
+
+<?cs each:msg = CGI.box.msgs ?>
+
+<?cs set:row_url = "/box_bm_body_frm.cs?boxid=" +
+                        boxid + "&cur=" + msg.ticket_id +
+                       "&idx_cur=" + CGI.box.cur.min_box_idx +
+                       "&split=" + split  +
+                       "&filter=" + filter +
+                       "&sort=" + sort +
+                       "&sort_dir=" + sort_dir +
+                       "&from_search=" + from_search ?>
+
+<?cs var:row_url ?>
+
+<?cs /each ?>
+
+
diff -Nbru clearsilver-0.4/cs/test4.cs.gold clearsilver-0.5/cs/test4.cs.gold
--- clearsilver-0.4/cs/test4.cs.gold	Sat Sep 15 16:38:40 2001
+++ clearsilver-0.5/cs/test4.cs.gold	Thu Mar 28 17:40:56 2002
@@ -43,3 +43,58 @@
 
 right, 4 > 0
 
+
+
+right, 0 <= 5
+
+
+
+right, 0 >= 5
+
+
+
+right "0" <= #5
+
+
+
+
+
+
+
+/box_bm_body_frm.cs?boxid=2&cur=1&idx_cur=1&split=1&filter=0&sort=t&sort_dir=u&from_search=
+
+
+
+
+
+/box_bm_body_frm.cs?boxid=2&cur=2&idx_cur=1&split=1&filter=0&sort=t&sort_dir=u&from_search=
+
+
+
+
+
+/box_bm_body_frm.cs?boxid=2&cur=3&idx_cur=1&split=1&filter=0&sort=t&sort_dir=u&from_search=
+
+
+
+
+
+
+
+/box_bm_body_frm.cs?boxid=&cur=1&idx_cur=1&split=&filter=&sort=&sort_dir=&from_search=
+
+
+
+
+
+/box_bm_body_frm.cs?boxid=&cur=2&idx_cur=1&split=&filter=&sort=&sort_dir=&from_search=
+
+
+
+
+
+/box_bm_body_frm.cs?boxid=&cur=3&idx_cur=1&split=&filter=&sort=&sort_dir=&from_search=
+
+
+
+
diff -Nbru clearsilver-0.4/cs/test8.cs clearsilver-0.5/cs/test8.cs
--- clearsilver-0.4/cs/test8.cs	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.5/cs/test8.cs	Wed Mar 13 00:27:11 2002
@@ -0,0 +1,10 @@
+
+some tests for new var is an expression
+
+<?cs var:"5" ?>
+
+<?cs var:"5" + #1 ?>
+
+<?cs var:"big" + " is better" ?>
+
+<?cs var:Blah + " potato " + Foo.Bar.Baz.0 ?>
diff -Nbru clearsilver-0.4/cs/test8.cs.gold clearsilver-0.5/cs/test8.cs.gold
--- clearsilver-0.4/cs/test8.cs.gold	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.5/cs/test8.cs.gold	Wed Mar 13 00:27:11 2002
@@ -0,0 +1,11 @@
+Parsing test8.cs
+
+some tests for new var is an expression
+
+5
+
+6
+
+big is better
+
+wow potato zero
diff -Nbru clearsilver-0.4/cs/test9.cs clearsilver-0.5/cs/test9.cs
--- clearsilver-0.4/cs/test9.cs	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.5/cs/test9.cs	Wed Mar 13 00:27:11 2002
@@ -0,0 +1,6 @@
+Test for bug where in certain cases we didn't find the ending cs tag if
+there was a newline
+
+<?cs each:agent = CGI.agents ?>
+<?cs /each
+?>
diff -Nbru clearsilver-0.4/cs/test9.cs.gold clearsilver-0.5/cs/test9.cs.gold
--- clearsilver-0.4/cs/test9.cs.gold	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.5/cs/test9.cs.gold	Wed Mar 13 00:27:11 2002
@@ -0,0 +1,5 @@
+Parsing test9.cs
+Test for bug where in certain cases we didn't find the ending cs tag if
+there was a newline
+
+
Binary files clearsilver-0.4/imd/0.gif and clearsilver-0.5/imd/0.gif differ
Binary files clearsilver-0.4/imd/1.gif and clearsilver-0.5/imd/1.gif differ
Binary files clearsilver-0.4/imd/2.gif and clearsilver-0.5/imd/2.gif differ
Binary files clearsilver-0.4/imd/3.gif and clearsilver-0.5/imd/3.gif differ
Binary files clearsilver-0.4/imd/4.gif and clearsilver-0.5/imd/4.gif differ
Binary files clearsilver-0.4/imd/5.gif and clearsilver-0.5/imd/5.gif differ
Binary files clearsilver-0.4/imd/6.gif and clearsilver-0.5/imd/6.gif differ
Binary files clearsilver-0.4/imd/7.gif and clearsilver-0.5/imd/7.gif differ
diff -Nbru clearsilver-0.4/imd/Makefile clearsilver-0.5/imd/Makefile
--- clearsilver-0.4/imd/Makefile	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.5/imd/Makefile	Sat Dec 30 01:57:16 2000
@@ -0,0 +1,28 @@
+
+
+ifeq ($(NEOTONIC_ROOT),)
+NEOTONIC_ROOT = ../
+endif
+
+include $(NEOTONIC_ROOT)rules.mk
+
+IMD_EXE = imd.cgi
+IMD_SRC = imd.c
+IMD_OBJ = $(IMD_SRC:%.c=%.o)
+
+CFLAGS += -I$(NEOTONIC_ROOT) -I/usr/local/include
+DLIBS += -lneo_cgi -lneo_cs -lneo_utl # -lefence
+LIBS += -L$(LIB_DIR) $(DLIBS) -L/usr/local/lib -lgd -ljpeg -lz
+
+TARGETS = $(IMD_EXE)
+
+all: $(TARGETS)
+
+$(IMD_EXE): $(IMD_OBJ) $(DEP_LIBS)
+	$(LD) $@ $(IMD_OBJ) $(LIBS)
+
+clean:
+	$(RM) *.o
+
+distclean:
+	$(RM) $(TARGETS) *.o
diff -Nbru clearsilver-0.4/imd/README clearsilver-0.5/imd/README
--- clearsilver-0.4/imd/README	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.5/imd/README	Sat Dec 30 01:57:16 2000
@@ -0,0 +1,14 @@
+
+To set up imd, you need to add the following directives to an apache
+conf file.  If you have sufficient permissions, you can add these to the
+.htaccess files for an image directory:
+
+AddHandler cgi-script .cgi
+AddHandler imd-handler .imd
+Action imd-handler /url/path/to/imd.cgi
+
+Then, copy example.imd to somewhere in your URL space, and modify the
+settings in there.  You will probably need to modify Template to point
+to the installed location of the imd.cs file.  You can personalize the
+imd.cs file to personalize your image server.
+
diff -Nbru clearsilver-0.4/imd/example.imd clearsilver-0.5/imd/example.imd
--- clearsilver-0.4/imd/example.imd	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.5/imd/example.imd	Sat Feb  2 20:08:47 2002
@@ -0,0 +1,22 @@
+# Location of the image albums (an album is a directory of images
+# so this is a directory of directories of imaages)
+BASEDIR = /home/blong/images
+
+# Full Path to the CS template file used for display
+Template=/home/blong/public_html/Images/imd.cs
+
+# Scaling sizes.  imd will scale the image to be at or below this size
+# Thumb is for the thumbnails, and Picture is for individual pictures
+ThumbWidth = 120
+ThumbHeight = 90
+PictureWidth = 600
+PictureHeight = 450
+
+# Number of thumbnails to show per page
+PerPage = 50
+
+# Assumed browser width for album show
+PageWidth = 640
+
+# Image Server Title
+Title = My Pictures
diff -Nbru clearsilver-0.4/imd/imd.c clearsilver-0.5/imd/imd.c
--- clearsilver-0.4/imd/imd.c	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.5/imd/imd.c	Mon Feb 11 12:16:23 2002
@@ -0,0 +1,1110 @@
+/*
+ * IMD image server
+ *
+ * Neotonic ClearSilver Templating System
+ *
+ * This code is made available under the terms of the FSF's
+ * Library Gnu Public License (LGPL).
+ *
+ * Copyright (C) 2001 by Brandon Long
+ */
+
+/* Bring in gd library functions */
+#include "gd.h"
+
+/* Bring in standard I/O so we can output the PNG to a file */
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/fcntl.h>
+#include <time.h>
+#include <ctype.h>
+
+#include "cgi/cgi.h"
+#include "cgi/cgiwrap.h"
+#include "util/neo_misc.h"
+
+/* from httpd util.c : made infamous with Roy owes Rob beer. */
+static char *months[] = {
+  "Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"
+};
+
+int find_month(char *mon) {
+  register int x;
+
+  for(x=0;x<12;x++)
+    if(!strcmp(months[x],mon))
+      return x;
+  return -1;
+}
+
+int later_than(struct tm *lms, char *ims) {
+  char *ip;
+  char mname[256];
+  int year = 0, month = 0, day = 0, hour = 0, min = 0, sec = 0, x;
+
+  /* Whatever format we're looking at, it will start
+   * with weekday. */
+  /* Skip to first space. */
+  if(!(ip = strchr(ims,' ')))
+    return 0;
+  else
+    while(isspace(*ip))
+      ++ip;
+
+  if(isalpha(*ip)) {
+    /* ctime */
+    sscanf(ip,"%s %d %d:%d:%d %d",mname,&day,&hour,&min,&sec,&year);
+  }
+  else if(ip[2] == '-') {
+    /* RFC 850 (normal HTTP) */
+    char t[256];
+    sscanf(ip,"%s %d:%d:%d",t,&hour,&min,&sec);
+    t[2] = '\0';
+    day = atoi(t);
+    t[6] = '\0';
+    strcpy(mname,&t[3]);
+    x = atoi(&t[7]);
+    /* Prevent
+     * wraparound
+     * from
+     * ambiguity
+     * */
+    if(x < 70)
+      x += 100;
+    year = 1900 + x;
+  }
+  else {
+    /* RFC 822 */
+    sscanf(ip,"%d %s %d %d:%d:%d",&day,mname,&year,&hour,&min,&sec);
+  }
+  month = find_month(mname);
+
+  if((x = (1900+lms->tm_year) - year))
+    return x < 0;
+  if((x = lms->tm_mon - month))
+    return x < 0;
+  if((x = lms->tm_mday - day))
+    return x < 0;
+  if((x = lms->tm_hour - hour))
+    return x < 0;
+  if((x = lms->tm_min - min))
+    return x < 0;
+  if((x = lms->tm_sec - sec))
+    return x < 0;
+
+  return 1;
+}
+
+
+
+int gif_size (char *file, int *width, int *height)
+{
+  UINT8 data[256];
+  int fd;
+  int blen;
+
+  *width = 0; *height = 0;
+  fd = open (file, O_RDONLY);
+  if (fd == -1)
+    return -1;
+
+  blen = read(fd, data, sizeof(data));
+  close(fd);
+
+  if (blen < 10) return -1;
+  if (strncmp(data, "GIF87a", 6) && strncmp(data, "GIF89a", 6))
+    return -1;
+
+  *width = data[6] + data[7]*256;
+  *height = data[8] + data[9]*256;
+
+  return 0;
+}
+
+int jpeg_size (char *file, int *width, int *height)
+{
+  UINT8 data[64*1024];
+  int blen;
+  int fd;
+  int pos;
+  int length;
+  UINT8 tag, marker;
+
+
+  *width = 0; *height = 0;
+  fd = open (file, O_RDONLY);
+  if (fd == -1)
+    return -1;
+
+  blen = read(fd, data, sizeof(data));
+  close(fd);
+  pos = 2;
+  while (pos+8 < blen)
+  {
+    tag = data[pos+0];
+    if (tag != 0xff) return -1;
+    marker = data[pos+1];
+    length = data[pos+2] * 256 + data[pos+3] + 2;
+    if (marker >= 0xc0 && marker <= 0xcf && marker != 0xc4 && 
+	marker != 0xc8 && marker != 0xcc)
+    {
+      *height = data[pos+5] * 256 + data[pos+6];
+      *width = data[pos+7] * 256 + data[pos+8];
+      return 0;
+    }
+    pos += length;
+  }
+  return -1;
+}
+
+int isdir(char *dir) {
+  struct stat statinfo;
+  if ( stat(dir, &statinfo) != 0) {
+    return 0;
+  }
+
+  return S_ISDIR(statinfo.st_mode);
+}
+
+int create_directories(char *fullpath) {
+  char s[4000];
+  char *last_slash;
+  int last_slash_pos;
+
+  if ((fullpath == NULL) || (strlen(fullpath) > 4000)) {
+    return 1;
+  }
+
+  last_slash = strrchr(fullpath,'/');
+  last_slash_pos = (last_slash - fullpath);
+  /* fprintf(stderr,"dira(%d): %s\n", last_slash_pos,fullpath); */
+
+  if (last_slash_pos > 2) {
+    strncpy(s,fullpath,last_slash_pos);
+    s[last_slash_pos] = 0;
+    /* fprintf(stderr,"dir: %s\n", s); */
+
+    if (!isdir(s)) {
+      char s2[4000];
+      sprintf(s2,"mkdir -p %s", s);
+      return system(s2);
+    }
+
+  } else {
+    return 1;
+  }
+
+  return 0;
+}
+
+NEOERR *rotate_image(char *path, char *file, int degree, char *rpath)
+{
+  char cmd[256];
+  char nfile[_POSIX_PATH_MAX];
+  char ofile[_POSIX_PATH_MAX];
+  char *ch, *opt;
+  int is_jpeg = 0;
+  struct stat s;
+  int r;
+
+  snprintf (ofile, sizeof(ofile), "%s/%s", path, file);
+  snprintf (rpath, _POSIX_PATH_MAX, "%s/%s", path, file);
+  ch = strrchr(rpath, '.');
+  if ((!strcasecmp(ch, ".jpg")) ||
+      (!strcasecmp(ch, ".jpeg")))
+  {
+    is_jpeg = 1;
+  } 
+  else if (strcasecmp(ch, ".gif"))
+  {
+    return nerr_raise(NERR_ASSERT, "Only support gif/jpeg for rotation, ext %s", 
+	ch);
+  }
+  *ch = '\0';
+  if (degree == 90)
+  {
+    strcat(rpath, "_r");
+    opt = "-cw";
+  }
+  else if (degree == -90)
+  {
+    strcat(rpath, "_l");
+    opt = "-ccw";
+  }
+  else if (degree == 180)
+  {
+    strcat(rpath, "_u");
+    opt = "-rotate180";
+  }
+  else 
+  {
+    return nerr_raise(NERR_ASSERT, "currently only support 90/-90/180 rotations");
+  }
+  if (is_jpeg)
+  {
+    strcat(rpath, ".jpg");
+    snprintf(cmd, sizeof(cmd), "djpeg -pnm %s | pnmflip %s | cjpeg -quality 85 > %s", ofile, opt, rpath);
+  }
+  else
+  {
+    strcat(rpath, ".gif");
+    snprintf(cmd, sizeof(cmd), "giftopnm %s | pnmflip %s | ppmtogif > %s", ofile, opt, rpath);
+  }
+  /* already exists? */
+  if (!stat(rpath, &s))
+  {
+    return STATUS_OK;
+  }
+  r = system(cmd);
+  if (r) return nerr_raise_errno (NERR_SYSTEM, "%s returned %d", cmd, r);
+  /* always save off the old file */
+  snprintf (nfile, sizeof(nfile), "%s/%s.orig", path, file);
+  if (stat(nfile, &s))
+  {
+    if (link(ofile, nfile))
+      return nerr_raise_errno (NERR_SYSTEM, "Unable to link %s -> %s", ofile, nfile);
+    unlink(ofile);
+  }
+  return STATUS_OK;
+}
+
+NEOERR *scale_and_display_image(char *fname,int maxW,int maxH,char *cachepath,
+    int quality) 
+{
+  NEOERR *err = STATUS_OK;
+  /* Declare the image */
+  gdImagePtr src_im = 0;
+  /* Declare input file */
+  FILE *infile=0, *cachefile=0;
+  int srcX,srcY,srcW,srcH;
+  FILE *dispfile=0;
+  struct stat s;
+
+  /* if we can open the cachepath, then just print it */
+  if (!stat(cachepath, &s) && s.st_size)
+    cachefile = fopen(cachepath,"rb");
+  if (cachefile) {
+    /* we should probably stat the files and make sure the thumbnail
+       is current */
+    /* fprintf(stderr,"using cachefile: %s\n",cachepath); */
+    dispfile = cachefile;
+  } else {
+    char cmd[1024];
+    int factor=1;
+    int l;
+    int is_jpeg = 0, is_gif = 0;
+
+    l = strlen(fname);
+    if ((l>4 && !strcasecmp(fname+l-4, ".jpg")) ||
+	(l>5 && !strcasecmp(fname+l-5, ".jpeg")))
+      is_jpeg = 1;
+    else if (l>4 && !strcasecmp(fname+l-4, ".gif"))
+      is_gif = 1;
+
+    if (is_jpeg)
+    {
+      if (!quality)
+      {
+	if (!jpeg_size (fname, &srcW, &srcH))
+	{
+	  if ((srcW > maxW) || (srcH > maxH)) 
+	  {
+	    factor = 2;
+	    if (srcW / factor > maxW)
+	    {
+	      factor = 4;
+	      if (srcW / factor > maxW)
+		factor = 8;
+	    }
+	  }
+	}
+
+	/* ne_warn("factor %d\n", factor); */
+	snprintf (cmd, sizeof(cmd), "/usr/bin/djpeg -fast -scale 1/%d '%s' | /usr/bin/cjpeg -quality 60 -progressive -dct fast -outfile '%s'", factor, fname, cachepath);
+
+	create_directories(cachepath);
+	system(cmd);
+	if (!stat(cachepath, &s) && s.st_size)
+	  cachefile = fopen(cachepath,"rb");
+	else
+	  ne_warn("external command failed to create file\n");
+      }
+      if (cachefile) {
+	dispfile = cachefile;
+
+      } else /* no cachefile */ { 
+
+
+	/* fprintf(stderr,"reading image\n"); */
+	/* Read the image in */
+	infile = fopen(fname,"rb");
+	src_im = gdImageCreateFromJpeg(infile);
+	srcX=0; srcY=0; srcW=src_im->sx; srcH=src_im->sy;
+
+
+	/* figure out if we need to scale it */
+
+	if ((srcW > maxW) || (srcH > maxH)) {
+	  /* scale paramaters */
+	  int dstX,dstY,dstW,dstH;
+	  /* Declare output file */
+	  FILE *jpegout;
+	  gdImagePtr dest_im;
+	  float srcAspect,dstAspect;
+
+	  /* create the destination image */
+
+	  dstX=0; dstY=0; 
+
+
+	  srcAspect = ((float)srcW/(float)srcH);
+	  dstAspect = ((float)maxW/(float)maxH);
+
+	  if (srcAspect == dstAspect) {
+	    /* they are the same aspect ratio */
+	    dstW = maxW;
+	    dstH = maxH;
+	  } else if ( srcAspect > dstAspect ) {
+	    /* if the src image has wider aspect ratio than the max */
+	    dstW = maxW;
+	    dstH = (int) ( ((float)dstW/(float)srcW) * srcH );
+	  } else {
+	    /* if the src image has taller aspect ratio than the max */
+	    dstH = maxW;
+	    dstW = (int) ( ((float)dstH/(float)srcH) * srcW );
+	  }
+
+	  dest_im = gdImageCreate(dstW,dstH);
+
+	  /* fprintf(stderr,"scaling to (%d,%d)\n",dstW,dstH); */
+
+	  /* Scale it to the destination image */
+
+	  gdImageCopyResized(dest_im,src_im,dstX,dstY,srcX,srcY,dstW,dstH,srcW,srcH);
+
+	  /* fprintf(stderr,"scaling finished\n"); */
+
+	  /* write the output image */
+	  create_directories(cachepath);
+	  jpegout = fopen(cachepath,"wb+");
+	  if (!jpegout) {
+	    jpegout = fopen("/tmp/foobar.jpg","wb+");
+	  }
+	  if (jpegout) {
+	    gdImageJpeg(dest_im,jpegout,-1);
+	    fflush(jpegout);
+
+	    /* now print that data out the stream */
+	    dispfile = jpegout;
+	  } else {
+	    return nerr_raise_errno(NERR_IO, "Unable to create output file: %s", cachepath);
+	  }
+
+
+	  gdImageDestroy(dest_im);
+
+	} else {
+	  /* just print the input file because it's small enough */
+	  dispfile = infile;
+	}
+
+      }
+    }
+    else if (is_gif)
+    {
+      float scale = 1.0;
+      if (!gif_size (fname, &srcW, &srcH))
+      {
+	if ((srcW > maxW) || (srcH > maxH)) 
+	{
+	  scale = 0.5;
+	  if (srcW * scale > maxW)
+	  {
+	    scale = 0.25;
+	    if (srcW * scale > maxW)
+	      factor = 0.125;
+	  }
+	}
+      }
+
+      if (scale < 1.0)
+      {
+	snprintf (cmd, sizeof(cmd), "/usr/bin/giftopnm '%s' | /usr/bin/pnmscale  %5.3f | ppmquant 256 | ppmtogif > '%s'", fname, scale, cachepath);
+
+	create_directories(cachepath);
+	system(cmd);
+	dispfile = fopen(cachepath,"rb");
+	if (dispfile == NULL)
+	  return nerr_raise_errno(NERR_IO, "Unable to open file: %s", cachepath);
+
+      }
+      else
+      {
+	dispfile = fopen(fname, "rb");
+	if (dispfile == NULL)
+	  return nerr_raise_errno(NERR_IO, "Unable to open file: %s", fname);
+      }
+    }
+    else {
+      ne_warn("How'd I get here?");
+      return nerr_raise(NERR_ASSERT, "I shouldn't get here...");
+    }
+  }
+
+  /* the data in "dispfile" is going to be printed now */
+  {
+
+    char buf[8192];
+    int count;
+    
+    fseek(dispfile,0,SEEK_SET);
+
+    do {
+      count = fread(buf,1,sizeof(buf),dispfile);
+      if (count > 0) {
+	err = cgiwrap_write(buf,count); 
+      }
+    } while (count > 0);
+
+  }
+
+  if (dispfile) fclose(dispfile); 
+  if (src_im) gdImageDestroy(src_im);
+
+  return nerr_pass(err);
+}
+
+char *url_escape (char *buf)
+{
+  int nl = 0;
+  int l = 0;
+  char *s;
+
+  while (buf[l])
+  {
+    if (buf[l] == '/' || buf[l] == '+' || buf[l] == '=' || buf[l] == '&' || 
+	buf[l] == '"' ||
+	buf[l] < 32 || buf[l] > 122)
+    {
+      nl += 2;
+    }
+    nl++;
+    l++;
+  }
+
+  s = (char *) malloc (sizeof(char) * (nl + 1));
+  if (s == NULL) return NULL;
+
+  nl = 0; l = 0;
+  while (buf[l])
+  {
+    if (buf[l] == ' ')
+    {
+      s[nl++] = '+';
+      l++;
+    }
+    else
+    if (buf[l] == '/' || buf[l] == '+' || buf[l] == '=' || buf[l] == '&' || 
+	buf[l] == '"' ||
+	buf[l] < 32 || buf[l] > 122)
+    {
+      s[nl++] = '%';
+      s[nl++] = "0123456789ABCDEF"[buf[l] / 16];
+      s[nl++] = "0123456789ABCDEF"[buf[l] % 16];
+      l++;
+    }
+    else
+    {
+      s[nl++] = buf[l++];
+    }
+  }
+  s[nl] = '\0';
+
+  return s;
+}
+
+NEOERR *load_images (char *path, ULIST **rfiles, char *partial, int descend)
+{
+  NEOERR *err = STATUS_OK;
+  DIR *dp;
+  struct dirent *de;
+  int is_jpeg, is_gif, l;
+  char fpath[_POSIX_PATH_MAX];
+  char ppath[_POSIX_PATH_MAX];
+  ULIST *files = NULL;
+
+  if ((dp = opendir (path)) == NULL)
+  {
+    return nerr_raise(NERR_IO, "Unable to opendir %s: [%d] %s", path, errno, 
+	strerror(errno));
+  }
+
+  if (rfiles == NULL || *rfiles == NULL)
+  {
+    err = uListInit(&files, 50, 0);
+    if (err) return nerr_pass(err);
+    *rfiles = files;
+  }
+  else
+  {
+    files = *rfiles;
+  }
+
+  while ((de = readdir (dp)) != NULL)
+  {
+    if (de->d_name[0] != '.')
+    {
+      snprintf(fpath, sizeof(fpath), "%s/%s", path, de->d_name);
+      if (partial)
+      {
+	snprintf(ppath, sizeof(ppath), "%s/%s", partial, de->d_name);
+      }
+      else
+      {
+	strncpy(ppath, de->d_name, sizeof(ppath));
+      }
+      if (descend && isdir(fpath))
+      {
+	err = load_images(fpath, rfiles, ppath, descend);
+	if (err) break;
+      }
+      else
+      {
+	l = strlen(de->d_name);
+	is_jpeg = 0; is_gif = 0;
+
+	if ((l>4 && !strcasecmp(de->d_name+l-4, ".jpg")) ||
+	    (l>5 && !strcasecmp(de->d_name+l-5, ".jpeg")))
+	  is_jpeg = 1;
+	else if (l>4 && !strcasecmp(de->d_name+l-4, ".gif"))
+	  is_gif = 1;
+
+	if (is_gif || is_jpeg) 
+	{
+	  err = uListAppend(files, strdup(ppath));
+	  if (err) break;
+	}
+      }
+    }
+  }
+  closedir(dp);
+  if (err)
+  {
+    uListDestroy(&files, ULIST_FREE);
+  }
+  else
+  {
+    *rfiles = files;
+  }
+  return nerr_pass(err);
+}
+
+NEOERR *export_image(CGI *cgi, char *prefix, char *path, char *file)
+{
+  NEOERR *err;
+  char buf[256];
+  char num[20];
+  int i = 0;
+  int r, l;
+  int width, height;
+  char ipath[_POSIX_PATH_MAX];
+  int is_jpeg = 0, is_gif = 0;
+
+  l = strlen(file);
+  if ((l>4 && !strcasecmp(file+l-4, ".jpg")) ||
+      (l>5 && !strcasecmp(file+l-5, ".jpeg")))
+    is_jpeg = 1;
+  else if (l>4 && !strcasecmp(file+l-4, ".gif"))
+    is_gif = 1;
+
+  snprintf (buf, sizeof(buf), "%s.%d", prefix, i);
+  err = hdf_set_buf (cgi->hdf, prefix, url_escape(file));
+  if (err != STATUS_OK) return nerr_pass(err);
+  snprintf (ipath, sizeof(ipath), "%s/%s", path, file);
+  if (is_jpeg)
+    r = jpeg_size(ipath, &width, &height);
+  else
+    r = gif_size(ipath, &width, &height);
+  if (!r)
+  {
+    snprintf (buf, sizeof(buf), "%s.width", prefix);
+    snprintf (num, sizeof(num), "%d", width);
+    err = hdf_set_value (cgi->hdf, buf, num);
+    if (err != STATUS_OK) return nerr_pass(err);
+    snprintf (buf, sizeof(buf), "%s.height", prefix);
+    snprintf (num, sizeof(num), "%d", height);
+    err = hdf_set_value (cgi->hdf, buf, num);
+    if (err != STATUS_OK) return nerr_pass(err);
+  }
+  return STATUS_OK;
+}
+
+NEOERR *scale_images (CGI *cgi, char *prefix, int width, int height, int force)
+{
+  NEOERR *err;
+  char num[20];
+  HDF *obj;
+  int i, x;
+  int factor;
+
+  obj = hdf_get_obj (cgi->hdf, prefix);
+  if (obj) obj = hdf_obj_child (obj);
+  while (obj)
+  {
+    factor = 1;
+    i = hdf_get_int_value(obj, "height", -1);
+    if (i != -1)
+    {
+      x = i;
+      while (x > height)
+      {
+	/* factor = factor * 2;*/
+	factor++;
+	x = i / factor;
+      }
+      snprintf (num, sizeof(num), "%d", x);
+      err = hdf_set_value (obj, "height", num);
+      if (err != STATUS_OK) return nerr_pass (err);
+
+      i = hdf_get_int_value(obj, "width", -1);
+      if (i != -1)
+      {
+	i = i / factor;
+	snprintf (num, sizeof(num), "%d", i);
+	err = hdf_set_value (obj, "width", num);
+	if (err != STATUS_OK) return nerr_pass (err);
+      }
+    }
+    else
+    {
+      snprintf (num, sizeof(num), "%d", height);
+      err = hdf_set_value (obj, "height", num);
+      if (err != STATUS_OK) return nerr_pass (err);
+      snprintf (num, sizeof(num), "%d", width);
+      err = hdf_set_value (obj, "width", num);
+      if (err != STATUS_OK) return nerr_pass (err);
+    }
+    obj = hdf_obj_next(obj);
+  }
+  return STATUS_OK;
+}
+
+int alpha_sort(const void *a, const void *b)
+{
+  char **sa = (char **)a;
+  char **sb = (char **)b;
+
+  /* ne_warn("%s %s: %d", *sa, *sb, strcmp(*sa, *sb)); */
+
+  return strcmp(*sa, *sb);
+}
+
+NEOERR *dowork_picture (CGI *cgi, char *album, char *picture)
+{
+  NEOERR *err = STATUS_OK;
+  char *base, *name;
+  char path[_POSIX_PATH_MAX];
+  char buf[256];
+  char *enc_picture;
+  int i, x, factor, y;
+  int thumb_width, thumb_height;
+  int pic_width, pic_height;
+  ULIST *files = NULL;
+  char t_album[_POSIX_PATH_MAX];
+  char t_pic[_POSIX_PATH_MAX];
+  char nfile[_POSIX_PATH_MAX];
+  char *ch;
+  int rotate;
+
+  ch = strrchr(picture, '/');
+  if (ch != NULL)
+  {
+    *ch = '\0';
+    snprintf(t_album, sizeof(t_album), "%s/%s", album, picture);
+    *ch = '/';
+    strncpy(t_pic, ch+1, sizeof(t_pic));
+    picture = t_pic;
+    album = t_album;
+  }
+
+  base = hdf_get_value (cgi->hdf, "BASEDIR", NULL);
+  if (base == NULL)
+  {
+    cgi_error (cgi, "No BASEDIR in imd file");
+    return nerr_raise(CGIFinished, "Finished");
+  }
+
+  thumb_width = hdf_get_int_value (cgi->hdf, "ThumbWidth", 120);
+  thumb_height = hdf_get_int_value (cgi->hdf, "ThumbWidth", 90);
+  pic_width = hdf_get_int_value (cgi->hdf, "PictureWidth", 120);
+  pic_height = hdf_get_int_value (cgi->hdf, "PictureWidth", 90);
+
+  err = hdf_set_value (cgi->hdf, "Context", "picture");
+  if (err != STATUS_OK) return nerr_pass(err);
+
+  snprintf (path, sizeof(path), "%s/%s", base, album);
+  rotate = hdf_get_int_value(cgi->hdf, "Query.rotate", 0);
+  if (rotate)
+  {
+    err = rotate_image(path, picture, rotate, nfile);
+    if (err) return nerr_pass(err);
+    picture = strrchr(nfile, '/') + 1;
+  }
+
+  err = hdf_set_buf (cgi->hdf, "Album", url_escape(album));
+  if (err != STATUS_OK) return nerr_pass(err);
+  err = hdf_set_value (cgi->hdf, "Album.Raw", album);
+  if (err != STATUS_OK) return nerr_pass(err);
+  enc_picture = url_escape(picture);
+  err = hdf_set_buf (cgi->hdf, "Picture", enc_picture);
+  if (err != STATUS_OK) return nerr_pass(err);
+
+  err = load_images(path, &files, NULL, 0);
+  if (err != STATUS_OK) return nerr_pass(err);
+  err = uListSort(files, alpha_sort);
+  if (err != STATUS_OK) return nerr_pass(err);
+
+  i = -1;
+  for (x = 0; x < uListLength(files); x++)
+  {
+    err = uListGet(files, x, (void *)&name);
+    if (err) break;
+    if (!strcmp(name, picture))
+    {
+      i = x;
+      break;
+    }
+  }
+  if (i != -1)
+  {
+    for (x = 2; x > 0; x--)
+    {
+      if (i - x < 0) continue;
+      err = uListGet(files, i-x, (void *)&name);
+      if (err) break;
+      snprintf(buf, sizeof(buf), "Show.%d", i-x);
+      err = export_image(cgi, buf, path, name);
+      if (err) break;
+    }
+    for (x = 0; x < 3; x++)
+    {
+      if (i + x > uListLength(files)) break;
+      err = uListGet(files, i+x, (void *)&name);
+      if (err) break;
+      snprintf(buf, sizeof(buf), "Show.%d", i+x);
+      err = export_image(cgi, buf, path, name);
+      if (err) break;
+    }
+    snprintf (buf, sizeof(buf), "Show.%d.width", i);
+    x = hdf_get_int_value (cgi->hdf, buf, -1);
+    if (x != -1)
+    {
+      factor = 1;
+      y = x;
+      while (y > pic_width)
+      {
+	/* factor = factor * 2; */
+	factor++;
+	y = x / factor;
+      }
+      snprintf (buf, sizeof(buf), "%d", y);
+      hdf_set_value (cgi->hdf, "Picture.width", buf);
+      snprintf (buf, sizeof(buf), "Show.%d.height", i);
+      x = hdf_get_int_value (cgi->hdf, buf, -1);
+      y = x / factor;
+      snprintf (buf, sizeof(buf), "%d", y);
+      hdf_set_value (cgi->hdf, "Picture.height", buf);
+    }
+    else
+    {
+      snprintf (buf, sizeof(buf), "%d", pic_width);
+      hdf_set_value (cgi->hdf, "Picture.width", buf);
+      snprintf (buf, sizeof(buf), "%d", pic_height);
+      hdf_set_value (cgi->hdf, "Picture.height", buf);
+    }
+
+    err = scale_images (cgi, "Show", thumb_width, thumb_height, 0);
+  }
+  uListDestroy(&files, ULIST_FREE);
+
+  return nerr_pass(err);
+}
+
+NEOERR *dowork_album_overview (CGI *cgi, char *album)
+{
+  NEOERR *err = STATUS_OK;
+  DIR *dp;
+  struct dirent *de;
+  char path[_POSIX_PATH_MAX];
+  char buf[256];
+  int i = 0, x;
+  int thumb_width, thumb_height;
+  ULIST *files = NULL;
+  char *name;
+
+  thumb_width = hdf_get_int_value (cgi->hdf, "ThumbWidth", 120);
+  thumb_height = hdf_get_int_value (cgi->hdf, "ThumbWidth", 90);
+
+  if ((dp = opendir (album)) == NULL)
+  {
+    return nerr_raise(NERR_IO, "Unable to opendir %s: [%d] %s", album, errno, 
+	strerror(errno));
+  }
+
+  while ((de = readdir (dp)) != NULL)
+  {
+    if (de->d_name[0] != '.')
+    {
+      snprintf(path, sizeof(path), "%s/%s", album, de->d_name);
+      if (isdir(path))
+      {
+	snprintf(buf, sizeof(buf), "Albums.%d", i);
+	err = hdf_set_buf (cgi->hdf, buf, url_escape(de->d_name));
+	if (err != STATUS_OK) break;
+	err = load_images(path, &files, NULL, 1);
+	if (err != STATUS_OK) break;
+	err = uListSort(files, alpha_sort);
+	if (err != STATUS_OK) break;
+	snprintf(buf, sizeof(buf), "Albums.%d.Count", i);
+	err = hdf_set_int_value(cgi->hdf, buf, uListLength(files));
+	if (err != STATUS_OK) break;
+	for (x = 0; (x < 4) && (x < uListLength(files)); x++)
+	{
+	  err = uListGet(files, x, (void *)&name);
+	  if (err) break;
+	  snprintf(buf, sizeof(buf), "Albums.%d.Images.%d", i, x);
+	  err = export_image(cgi, buf, path, name);
+	  if (err) break;
+	}
+	uListDestroy(&files, ULIST_FREE);
+	if (err != STATUS_OK) break;
+	snprintf(buf, sizeof(buf), "Albums.%d.Images", i);
+	err = scale_images (cgi, buf, thumb_width, thumb_height, 0);
+	if (err != STATUS_OK) break;
+	i++;
+      }
+    }
+  }
+  closedir(dp);
+  return nerr_pass(err);
+}
+
+NEOERR *dowork_album (CGI *cgi, char *album)
+{
+  NEOERR *err;
+  char *base;
+  char buf[256];
+  char path[_POSIX_PATH_MAX];
+  int thumb_width, thumb_height;
+  int per_page, start, next, prev, last;
+  ULIST *files = NULL;
+  char *name;
+  int x;
+
+  base = hdf_get_value (cgi->hdf, "BASEDIR", NULL);
+  if (base == NULL)
+  {
+    cgi_error (cgi, "No BASEDIR in imd file");
+    return nerr_raise(CGIFinished, "Finished");
+  }
+  thumb_width = hdf_get_int_value (cgi->hdf, "ThumbWidth", 120);
+  thumb_height = hdf_get_int_value (cgi->hdf, "ThumbWidth", 90);
+  per_page = hdf_get_int_value (cgi->hdf, "PerPage", 50);
+  start = hdf_get_int_value (cgi->hdf, "Query.start", 0);
+
+  err = hdf_set_buf (cgi->hdf, "Album", url_escape(album));
+  if (err != STATUS_OK) return nerr_pass(err);
+  err = hdf_set_value (cgi->hdf, "Album.Raw", album);
+  if (err != STATUS_OK) return nerr_pass(err);
+
+  err = hdf_set_value (cgi->hdf, "Context", "album");
+  if (err != STATUS_OK) return nerr_pass(err);
+
+  snprintf (path, sizeof(path), "%s/%s", base, album);
+  err = dowork_album_overview(cgi, path);
+  if (err != STATUS_OK) return nerr_pass(err);
+  
+  err = load_images(path, &files, NULL, 0);
+  if (err != STATUS_OK) return nerr_pass (err);
+  err = uListSort(files, alpha_sort);
+  if (err != STATUS_OK) return nerr_pass (err);
+  err = hdf_set_int_value(cgi->hdf, "Album.Count", uListLength(files));
+  if (err != STATUS_OK) return nerr_pass (err);
+  if (start > uListLength(files)) start = 0;
+  next = start + per_page;
+  if (next > uListLength(files)) next = uListLength(files);
+  prev = start - per_page;
+  if (prev < 0) prev = 0;
+  last = uListLength(files) - per_page;
+  if (last < 0) last = 0;
+  err = hdf_set_int_value(cgi->hdf, "Album.Start", start);
+  if (err != STATUS_OK) return nerr_pass (err);
+  err = hdf_set_int_value(cgi->hdf, "Album.Next", next);
+  if (err != STATUS_OK) return nerr_pass (err);
+  err = hdf_set_int_value(cgi->hdf, "Album.Prev", prev);
+  if (err != STATUS_OK) return nerr_pass (err);
+  err = hdf_set_int_value(cgi->hdf, "Album.Last", last);
+  if (err != STATUS_OK) return nerr_pass (err);
+  for (x = start; x < next; x++)
+  {
+    err = uListGet(files, x, (void *)&name);
+    if (err) break;
+    snprintf(buf, sizeof(buf), "Images.%d", x);
+    err = export_image(cgi, buf, path, name);
+    if (err) break;
+  }
+  uListDestroy(&files, ULIST_FREE);
+  if (err != STATUS_OK) return nerr_pass (err);
+  err = scale_images (cgi, "Images", thumb_width, thumb_height, 0);
+  if (err != STATUS_OK) return nerr_pass (err);
+  return STATUS_OK;
+}
+
+NEOERR *dowork_image (CGI *cgi, char *image) 
+{
+  NEOERR *err = STATUS_OK;
+  int maxW = 1024, maxH = 1024;
+  char *basepath = "";
+  char *cache_basepath = "/tmp/.imgcache/";
+  char srcpath[_POSIX_PATH_MAX] = "";
+  char cachepath[_POSIX_PATH_MAX] = "";
+  char buf[256];
+  char *if_mod;
+  int i, l, quality;
+  struct stat s;
+  struct tm *t;
+
+  if ((i = hdf_get_int_value(cgi->hdf, "Query.width", 0)) != 0) {
+    maxW = i;
+  }
+
+  if ((i = hdf_get_int_value(cgi->hdf, "Query.height", 0)) != 0) {
+    maxH = i;
+  }
+  quality = hdf_get_int_value(cgi->hdf, "Query.quality", 0);
+
+  if_mod = hdf_get_value(cgi->hdf, "HTTP.IfModifiedSince", NULL);
+
+  basepath = hdf_get_value(cgi->hdf, "BASEDIR", NULL);
+  if (basepath == NULL)
+  {
+    cgi_error (cgi, "No BASEDIR in imd file");
+    return nerr_raise(CGIFinished, "Finished");
+  }
+
+  snprintf (srcpath, sizeof(srcpath), "%s/%s", basepath, image);
+  snprintf (cachepath, sizeof(cachepath), "%s/%dx%d/%s", cache_basepath, 
+      maxW, maxH,image);
+
+  if (stat(srcpath, &s))
+  {
+    cgiwrap_writef("Status: 404\nContent-Type: text/html\n\n");
+    cgiwrap_writef("File %s not found.", srcpath);
+    return nerr_raise_errno(NERR_IO, "Unable to stat file %s", srcpath);
+  }
+
+  t = gmtime(&(s.st_mtime));
+  if (if_mod && later_than(t, if_mod))
+  {
+    cgiwrap_writef("Status: 304\nContent-Type: text/html\n\n");
+    cgiwrap_writef("Use Local Copy");
+    return STATUS_OK;
+  }
+
+  /* fprintf(stderr,"cachepath: %s\n",cachepath); */
+
+  l = strlen(srcpath);
+  if ((l>4 && !strcasecmp(srcpath+l-4, ".jpg")) ||
+      (l>5 && !strcasecmp(srcpath+l-5, ".jpeg")))
+    cgiwrap_writef("Content-Type: image/jpeg\n");
+  else if (l>4 && !strcasecmp(srcpath+l-4, ".gif"))
+    cgiwrap_writef("Content-Type: image/gif\n");
+  t = gmtime(&(s.st_mtime));
+  strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S GMT", t);
+  cgiwrap_writef("Last-modified: %s\n\n", buf);
+
+  fflush(stdout);
+
+  err = scale_and_display_image(srcpath,maxW,maxH,cachepath,quality);
+  return nerr_pass(err);
+}
+
+int main(int argc, char **argv, char **envp)
+{
+  NEOERR *err;
+  CGI *cgi;
+  char *image;
+  char *album;
+  char *imd_file;
+  char *cs_file;
+  char *picture;
+
+  cgi_debug_init (argc,argv);
+  cgiwrap_init_std (argc, argv, envp);
+  
+  nerr_init();
+
+  err = cgi_init(&cgi, NULL);
+  if (err != STATUS_OK)
+  {
+    cgi_neo_error(cgi, err);
+    nerr_log_error(err);
+    return -1;
+  }
+  imd_file = hdf_get_value(cgi->hdf, "CGI.PathTranslated", NULL);
+  err = hdf_read_file (cgi->hdf, imd_file);
+  if (err != STATUS_OK)
+  {
+    cgi_neo_error(cgi, err);
+    nerr_log_error(err);
+    return -1;
+  }
+
+  cs_file = hdf_get_value(cgi->hdf, "Template", NULL);
+  image = hdf_get_value(cgi->hdf, "Query.image", NULL);
+  album = hdf_get_value(cgi->hdf, "Query.album", "");
+  picture = hdf_get_value(cgi->hdf, "Query.picture", NULL);
+  if (image)
+  {
+    err = dowork_image(cgi, image);
+    if (err)
+    {
+      nerr_log_error(err);
+      return -1;
+    }
+  }
+  else 
+  {
+    if (!picture)
+    {
+      err = dowork_album (cgi, album);
+    }
+    else
+    {
+      err = dowork_picture (cgi, album, picture);
+    }
+    if (err != STATUS_OK)
+    {
+      if (nerr_handle(&err, CGIFinished))
+      {
+	/* pass */
+      }
+    }
+    else
+    {
+      err = cgi_display(cgi, cs_file);
+      if (err != STATUS_OK)
+      {
+	cgi_neo_error(cgi, err);
+	nerr_log_error(err);
+	return -1;
+      }
+    }
+  }
+  return 0;
+}
diff -Nbru clearsilver-0.4/imd/imd.cs clearsilver-0.5/imd/imd.cs
--- clearsilver-0.4/imd/imd.cs	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.5/imd/imd.cs	Mon Feb 11 12:16:23 2002
@@ -0,0 +1,195 @@
+<?cs def:frame_picture(image, link, size) ?>
+    <TABLE cellspacing=0 cellpadding=0 border=0 WIDTH=1%>
+      <TR>
+	<TD><IMG name="frame0" border=0 height=8 width=8 src="0.gif"></TD>
+	<TD><IMG name="frame1" border=0 height=8 width=<?cs var:image.width ?> src="1.gif"></TD>
+	<TD><IMG name="frame2" border=0 height=8 width=8 src="2.gif"></TD>
+      </TR>
+      <TR>
+	<TD><IMG name="frame3" border=0 height=<?cs var:image.height ?> width=8 src="3.gif"></TD>
+	<TD><a href="<?cs var:link ?>"><img border=0 width=<?cs var:image.width ?> height=<?cs var:image.height ?> src="<?cs var:CGI.PathInfo?>?image=<?cs var:Album ?>/<?cs var:image ?>&width=<?cs var:image.width ?>&height=<?cs var:image.height ?>"></a></TD>
+	<TD><IMG name="frame4" border=0 height=<?cs var:image.height ?> width=8 src="4.gif"></TD>
+      </TR>
+      <TR>
+	<TD><IMG name="frame5" border=0 height=8 width=8 src="5.gif"></TD>
+	<TD><IMG name="frame6" border=0 height=8 width=<?cs var:image.width ?> src="6.gif"></TD>
+	<TD><IMG name="frame7" border=0 height=8 width=8 src="7.gif"></TD>
+      </TR>
+    </TABLE>
+<?cs /def ?>
+<HTML>
+<HEAD>
+<TITLE><?cs var:Title ?><?cs if:Context == "album" ?> <?cs var:Album.Raw ?> <?cs var:Album.Start + #1 ?> - <?cs var:Album.Next ?> of <?cs var:Album.Count ?><?cs else ?> <?cs var:Album.Raw ?>
+- <?cs var:Picture ?><?cs /if ?></TITLE>
+</HEAD>
+<BODY BGCOLOR=#ffffff>
+  <?cs if:Context == "album" ?>
+    <table border=0 bgcolor=#cccccc width=100%>
+      <tr><td align=center><font size=+2><?cs var:Album.Raw ?></font></td></tr>
+    </table>
+  <?cs if:Albums.0 ?>
+    <H1><?cs var:Title ?></H1>
+    <CENTER>
+      <?cs each:album = Albums ?>
+	<TABLE BORDER=0 CELLSPACING=1 CELLPADDING=1>
+	  <TR><TD BGCOLOR=#cccccc COLSPAN=4><font size=+2>
+	    <a href="<?cs var:CGI.PathInfo?>?album=<?cs if:Album ?><?cs var:Album ?>/<?cs /if ?><?cs var:album ?>"><?cs var:album ?></a></font> (<?cs var:album.Count ?> images)
+	  </td></tr>
+	  <TR>
+		  <?cs each:image = album.Images ?>
+		    <td align=center>
+    <TABLE cellspacing=0 cellpadding=0 border=0 WIDTH=1%>
+      <TR>
+	<TD><IMG name="frame0" border=0 height=8 width=8 src="0.gif"></TD>
+	<TD><IMG name="frame1" border=0 height=8 width=<?cs var:image.width ?> src="1.gif"></TD>
+	<TD><IMG name="frame2" border=0 height=8 width=8 src="2.gif"></TD>
+      </TR>
+      <TR>
+	<TD><IMG name="frame3" border=0 height=<?cs var:image.height ?> width=8 src="3.gif"></TD>
+	<TD><a href="<?cs var:CGI.PathInfo?>?album=<?cs if:Album ?><?cs var:Album ?>/<?cs /if ?><?cs var:album ?>&picture=<?cs var:image ?>"><img border=0 width=<?cs var:image.width ?> height=<?cs var:image.height ?> src="<?cs var:CGI.PathInfo?>?image=<?cs if:Album ?><?cs var:Album ?>/<?cs /if ?><?cs var:album ?>/<?cs var:image ?>&width=<?cs var:image.width ?>&height=<?cs var:image.height ?>"></a></TD>
+	<TD><IMG name="frame4" border=0 height=<?cs var:image.height ?> width=8 src="4.gif"></TD>
+      </TR>
+      <TR>
+	<TD><IMG name="frame5" border=0 height=8 width=8 src="5.gif"></TD>
+	<TD><IMG name="frame6" border=0 height=8 width=<?cs var:image.width ?> src="6.gif"></TD>
+	<TD><IMG name="frame7" border=0 height=8 width=8 src="7.gif"></TD>
+      </TR>
+    </TABLE>
+		    </td>
+		  <?cs /each ?>
+	  </TR>
+	</TABLE>
+      <?cs /each ?>
+    </CENTER>
+    <?cs /if ?>
+    <?cs if:#Album.Count ?>
+    <A HREF="<?cs var:CGI.PathInfo?>"><?cs var:Title ?></A> 
+    <DIV ALIGN=RIGHT>
+    <?cs if:Album.Start > #0 ?>
+    <A HREF="<?cs var:CGI.PathInfo ?>?album=<?cs var:Album ?>">First</A>
+    <?cs else ?>
+    First
+    <?cs /if ?>
+    &nbsp;
+    <?cs if:Album.Prev > #0 ?>
+    <A HREF="<?cs var:CGI.PathInfo ?>?album=<?cs var:Album ?>&start=<?cs var:Album.Prev ?>">Prev</A>
+    <?cs else ?>
+    Prev
+    <?cs /if ?>
+    &nbsp;
+    <?cs var:#Album.Start + #1 ?> - <?cs var:#Album.Next ?> of <?cs var:#Album.Count ?>
+    &nbsp;
+    <?cs if:#Album.Next < #Album.Count ?>
+    <A HREF="<?cs var:CGI.PathInfo ?>?album=<?cs var:Album ?>&start=<?cs var:Album.Next ?>">Next</A>
+    <?cs else ?>
+    Next
+    <?cs /if ?>
+    &nbsp;
+    <?cs if:#Album.Start < #Album.Last ?>
+    <A HREF="<?cs var:CGI.PathInfo ?>?album=<?cs var:Album ?>&start=<?cs var:Album.Last ?>">Last</A>
+    <?cs else ?>
+    Last
+    <?cs /if ?>
+    </DIV>
+    
+    <table border=0 bgcolor=#cccccc width=100%>
+      <tr><td align=center><font size=+2><?cs var:Album.Raw ?></font></td></tr>
+    </table>
+    <TABLE>
+      <TR>
+       <?cs set:TotalWidth = #0 ?>
+    <?cs each:image=Images ?>
+      <?cs set:nextWidth = #TotalWidth + #image.width ?>
+      <?cs if:#nextWidth > #PageWidth ?></TR></TABLE><TABLE><TR><?cs set:TotalWidth = image.width ?><?cs else ?><?cs set:TotalWidth = nextWidth ?><?cs /if ?>
+      <TD><?cs call:frame_picture(image, CGI.PathInfo + "?album=" + Album + "&picture=" + image, "8") ?></TD>
+    <?cs /each ?>
+      </TR>
+    </TABLE>
+    <DIV ALIGN=RIGHT>
+    <?cs if:Album.Start > #0 ?>
+    <A HREF="<?cs var:CGI.PathInfo ?>?album=<?cs var:Album ?>">First</A>
+    <?cs else ?>
+    First
+    <?cs /if ?>
+    &nbsp;
+    <?cs if:Album.Prev > #0 ?>
+    <A HREF="<?cs var:CGI.PathInfo ?>?album=<?cs var:Album ?>&start=<?cs var:Album.Prev ?>">Prev</A>
+    <?cs else ?>
+    Prev
+    <?cs /if ?>
+    &nbsp;
+    <?cs var:#Album.Start + #1 ?> - <?cs var:#Album.Next ?> of <?cs var:#Album.Count ?>
+    &nbsp;
+    <?cs if:#Album.Next < #Album.Count ?>
+    <A HREF="<?cs var:CGI.PathInfo ?>?album=<?cs var:Album ?>&start=<?cs var:Album.Next ?>">Next</A>
+    <?cs else ?>
+    Next
+    <?cs /if ?>
+    &nbsp;
+    <?cs if:#Album.Start < #Album.Last ?>
+    <A HREF="<?cs var:CGI.PathInfo ?>?album=<?cs var:Album ?>&start=<?cs var:Album.Last ?>">Last</A>
+    <?cs else ?>
+    Last
+    <?cs /if ?>
+    </DIV>
+  <?cs /if ?>
+  <?cs else ?><?cs # picture ?>
+    <A HREF="<?cs var:CGI.PathInfo?>"><?cs var:Title ?></A> 
+    -- <A HREF="<?cs var:CGI.PathInfo?>?album=<?cs var:Album ?>"><?cs var:Album.Raw ?></A>
+    <DIV ALIGN=RIGHT>
+      <?cs set:count = #0 ?>
+      <?cs each:image=Show ?>
+	<a href="<?cs var:CGI.PathInfo?>?album=<?cs var:Album ?>&picture=<?cs var:image ?>">
+	 <?cs if:count == #0 ?>
+	   -2
+	 <?cs elif:count == #1 ?>
+	   -1
+	 <?cs elif:count == #2 ?>
+	    <?cs Name:image ?>
+	 <?cs elif:count == #3 ?>
+	    1
+	 <?cs elif:count == #4 ?>
+	   +2
+	 <?cs /if ?>
+	 <?cs set:count = count + #1 ?>
+	</a> &nbsp;
+      <?cs /each ?>
+    </DIV>
+    <hr>
+    <TABLE cellspacing=0 cellpadding=0 border=0 align=center>
+      <TR>
+        <TD><IMG name="frame0" border=0 height=18 width=18 src="0.gif"></TD>
+        <TD><IMG name="frame1" border=0 height=18 width=<?cs var:Picture.width ?> src="1.gif"></TD>
+        <TD><IMG name="frame2" border=0 height=18 width=18 src="2.gif"></TD>
+      </TR>
+      <TR>
+        <TD><IMG name="frame3" border=0 height=<?cs var:Picture.height ?> width=18 src="3.gif"></TD>
+	<TD><img border=0 width=<?cs var:Picture.width?> height=<?cs var:Picture.height?> src="<?cs var:CGI.PathInfo?>?image=<?cs var:Album ?>/<?cs var:Picture ?>&width=<?cs var:Picture.width ?>&height=<?cs var:Picture.height?>&quality=1"></TD>
+        <TD><IMG name="frame4" border=0 height=<?cs var:Picture.height ?> width=18 src="4.gif"></TD>
+      </TR>
+      <TR>
+        <TD><IMG name="frame5" border=0 height=18 width=18 src="5.gif"></TD>
+        <TD><IMG name="frame6" border=0 height=18 width=<?cs var:Picture.width ?> src="6.gif"></TD>
+        <TD><IMG name="frame7" border=0 height=18 width=18 src="7.gif"></TD>
+      </TR>
+    </TABLE>
+    <CENTER>
+      <TABLE>
+	<TR>
+	  <?cs each:image=Show ?>
+	  <?cs if:image != Picture ?>
+	    <TD><?cs call:frame_picture(image, CGI.PathInfo + "?album=" + Album + "&picture=" + image, "8") ?></TD>
+	  <?cs /if ?>
+	  <?cs /each ?>
+	</TR>
+      </TABLE>
+    </CENTER>
+    <?cs if:CGI.RemoteAddress == "216.103.193.236" ?>
+      &nbsp; Rotate: 
+      <A HREF="<?cs var:CGI.PathInfo ?>?album=<?cs var:Album ?>&picture=<?cs var:Picture ?>&rotate=90">Right</A> -
+      <A HREF="<?cs var:CGI.PathInfo ?>?album=<?cs var:Album ?>&picture=<?cs var:Picture ?>&rotate=-90">Left</A> -
+      <A HREF="<?cs var:CGI.PathInfo ?>?album=<?cs var:Album ?>&picture=<?cs var:Picture ?>&rotate=180">Flip</A>
+    <?cs /if ?>
+  <?cs /if ?>
+</BODY>
+</HTML>
diff -Nbru clearsilver-0.4/imd/imdm.py clearsilver-0.5/imd/imdm.py
--- clearsilver-0.4/imd/imdm.py	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.5/imd/imdm.py	Sat Dec 30 01:57:16 2000
@@ -0,0 +1,209 @@
+#!/usr/local/bin/python
+#
+# imdm
+# 
+# IMage Display Master
+#
+# This uses the affiliated C-cgi "imd" to build a caching image display
+# server with only passive Apache cgis.
+#
+import sys,os,string
+import cgi
+
+# this function should find the first four images inside a
+# nested subdirectory
+
+albumstartfile = "/~jeske/Images/jeskealbum.imd"
+imagestartfile = "/~jeske/Images/jeskealbum.imc"
+
+THUMB_WIDTH = 120
+THUMB_HEIGHT = 90 
+
+# ------------------------------------------------------------------------------------
+#
+# utility functions
+
+def albumoverview(basedir,sub_dir,count = 4,skip = 0):
+    images = []
+
+    fulldir = os.path.join(basedir,sub_dir)
+    for a_entry in os.listdir(fulldir):
+	fullpath = os.path.join(fulldir,a_entry)
+	if os.path.isfile(fullpath):
+	    if string.lower(string.split(a_entry,".")[-1]) in ["jpeg","jpg"]:
+		images.append(os.path.join(sub_dir,a_entry))
+	elif os.path.isdir(fullpath):
+	    images + albumoverview(basedir,os.path.join(sub_dir,a_entry),1)
+	if len(images) >= (count + skip):
+	    return images[skip:]
+
+    return images[skip:]
+
+
+def makethumbnailimgtag(filename,width=THUMB_WIDTH,height=THUMB_HEIGHT):
+    global imagestartfile
+    return '<IMG SRC="%s?image=%s&width=%s&height=%s">' % (imagestartfile,filename,width,height)
+
+def makealbumurl(dir):
+    global albumstartfile
+    return "%s?album=%s" % (albumstartfile,dir)
+
+def makepictureurl(dir,picture):
+    global albumstartfile
+    return "%s?album=%s&picture=%s" % (albumstartfile,dir,picture)
+
+# ------------------------------------------------------------------------------------
+#
+# picturedisplay
+
+def picturedisplay(basedir,album,picture):
+    sys.stdout.write("<A HREF=\"%s?\">top</A> " % albumstartfile)
+
+    sys.stdout.write("-- <A HREF=\"%s\">%s</A>" % (makealbumurl(album),album))
+
+    imagename = os.path.join(album,picture)
+
+    sys.stdout.write("<br><hr>")
+
+    sys.stdout.write("<TABLE WIDTH=100%><TR><TD ALIGN=CENTER>\n")
+    sys.stdout.write(makethumbnailimgtag(imagename,width=600,height=500))
+    sys.stdout.write("</TD></TR></TABLE>\n")
+
+    images = albumoverview(basedir,album,count=500)
+    image_index = None
+    for x in range(len(images)):
+	if images[x] == imagename:
+	    image_index = x
+	    break
+
+    if not image_index is None:
+	sys.stdout.write("<CENTER><TABLE WIDTH=50% BORDER=1><TR>")
+	
+	# pre-images
+	for i in range(1,3):
+	    pic_index = image_index - i
+	    
+	    picture_path = string.join(string.split(images[pic_index],'/')[1:],'/')
+	    sys.stdout.write("<TD ALIGN=CENTER><A HREF=\"%s\">%s</A></TD>" % (makepictureurl(album,picture_path),makethumbnailimgtag(images[pic_index])))
+
+	sys.stdout.write("<br>")
+
+	# post-images
+	for i in range(1,3):
+	    pic_index = image_index + i
+	    if pic_index >= len(images):
+		pic_index = pic_index - len(images)
+	    
+	    picture_path = string.join(string.split(images[pic_index],'/')[1:],'/')
+	    sys.stdout.write("<TD ALIGN=CENTER><A HREF=\"%s\">%s</A></TD>" % (makepictureurl(album,picture_path), makethumbnailimgtag(images[pic_index])))
+	sys.stdout.write("</TR></TABLE></CENTER>\n")
+	    
+    # navigation
+
+
+# ------------------------------------------------------------------------------------
+#
+#  albumdisplay
+
+    
+
+def albumdisplay(basedir,album,columns=7,rows=5):
+    next_page = 0
+
+    
+    sys.stdout.write("<table border=0 bgcolor=#cccccc width=100%%><tr><td align=center><font size=+2>%s</font></td></tr></table>" % album)
+
+    imgcount = columns * rows 
+    images = albumoverview(basedir,album,count=(imgcount + 1),skip=0)
+
+    if len(images) > imgcount:
+	images = images[:-1]
+	next_page = 1
+
+    while images:
+	sys.stdout.write("<CENTER><TABLE WIDTH=90% CELLSPACING=3 BORDER=1><TR>")
+	for a_col in range(columns):
+	    if len(images):
+		picture_path = string.join(string.split(images[0],'/')[1:],'/')
+		sys.stdout.write("<TD ALIGN=CENTER><A HREF=\"%s\">%s</A></TD>" % (makepictureurl(album,picture_path),makethumbnailimgtag(images[0])))
+		images = images[1:]
+	sys.stdout.write("</tr></table></CENTER>")
+
+    if next_page:
+	sys.stdout.write("more...")
+    
+
+# ------------------------------------------------------------------------------------
+#
+# topalbumoverview
+
+def topalbumoverview(dir):
+    for a_dir in os.listdir(dir):
+	if os.path.isdir(os.path.join(dir,a_dir)):
+	    sys.stdout.write("<CENTER>")
+	    sys.stdout.write("<TABLE BGCOLOR=#ccccc WIDTH=50% BORDER=0 CELLSPACING=1 CELLPADDING=1>")
+	    
+	    sys.stdout.write("<TR><TD> <font size=+2><A HREF=\"%s\">%s</A></font></TD></TR>" % (makealbumurl(a_dir),a_dir))
+
+	    sys.stdout.write("<TR><TD ALIGN=CENTER><TABLE BGCOLOR=#FFFFFF WIDTH=100% BORDER=0 CELSPACING=0 CELLPADDING=0><TR>")
+			     
+	    for a_file in albumoverview(dir,a_dir):
+		picture_path = string.join(string.split(a_file,'/')[1:],'/')
+		sys.stdout.write("<TD ALIGN=CENTER><A HREF=\"%s\">%s</A></TD>\n" % (makepictureurl(a_dir,picture_path),makethumbnailimgtag(a_file)))
+	    sys.stdout.write("</TR></TABLE></TD></TR></TABLE></CENTER>\n<p>\n")
+
+# ------------------------------------------------------------------------------------
+#
+# readvars() -- simple file format reader
+
+def readvars(filename):
+    vars = {}
+    data = open(filename,"rb").read()
+    lines = string.split(data,"\n")
+    for a_line in lines:
+	stripped_line = string.strip(a_line)
+	if not stripped_line or stripped_line[0] == "#":
+	    continue
+	try:
+	    key,value = string.split(a_line,"=")
+	    vars[key] = value
+	except:
+	    pass
+    return vars
+
+# ------------------------------------------------------------------------------------
+#
+# main()
+
+
+def main():
+    global cgiform
+    cgiform = cgi.FieldStorage()
+
+    sys.stdout.write("Content-Type: text/html\n\n")
+    sys.stdout.write("<h1>HTML Image Viewer!</h1><p>")
+
+    myvars = readvars(os.environ['PATH_TRANSLATED'])
+
+    sys.stderr.write(repr(myvars))
+    
+    global albumstartfile, imagestartfile
+    albumstartfile = os.environ['PATH_INFO']
+    imagestartfile = myvars['IMGSTARTFILE']
+    BASEDIR        = myvars['BASEDIR']
+
+    album = cgiform.getvalue('album',None)
+    picture = cgiform.getvalue('picture',None)
+
+    if album is None:
+	topalbumoverview(BASEDIR)
+    elif picture is None:
+	albumdisplay(BASEDIR,album)
+    else:
+	picturedisplay(BASEDIR,album,picture)
+
+if __name__ == "__main__":
+    main()
+
+
+
diff -Nbru clearsilver-0.4/imd/test.in clearsilver-0.5/imd/test.in
--- clearsilver-0.4/imd/test.in	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.5/imd/test.in	Sat Dec 30 01:57:16 2000
@@ -0,0 +1,2 @@
+PATH_TRANSLATED=/home/blong/public_html/Images/test.imd
+QUERY_STRING=
diff -Nbru clearsilver-0.4/python/Makefile clearsilver-0.5/python/Makefile
--- clearsilver-0.4/python/Makefile	Mon Aug  6 15:16:45 2001
+++ clearsilver-0.5/python/Makefile	Fri Mar 22 11:26:41 2002
@@ -13,6 +13,7 @@
 ifeq ($(PYTHON_INC),)
 PYTHON_INC = -I/usr/include/python1.5
 endif
+PYTHON_INC += -I/usr/include/python1.5
 
 CFLAGS += -I$(NEOTONIC_ROOT) $(PYTHON_INC)
 DLIBS += -lneo_cgi -lneo_cs -lneo_utl 
diff -Nbru clearsilver-0.4/python/neo_cgi.c clearsilver-0.5/python/neo_cgi.c
--- clearsilver-0.4/python/neo_cgi.c	Fri Aug 31 15:19:48 2001
+++ clearsilver-0.5/python/neo_cgi.c	Sat Apr 13 17:49:50 2002
@@ -18,6 +18,8 @@
 #include "cgi/cgiwrap.h"
 #include "cgi/date.h"
 #include "cgi/html.h"
+
+#define NEO_CGI_MODULE
 #include "p_neo_util.h"
 
 static PyObject *CGIFinishedException;
@@ -283,7 +285,7 @@
   char *name, *domain = NULL, *path = NULL;
   NEOERR *err;
 
-  if (!PyArg_ParseTuple(args, "s|ss:cookieClear(name, domain, path)", &name, domain, path))
+  if (!PyArg_ParseTuple(args, "s|ss:cookieClear(name, domain, path)", &name, &domain, &path))
     return NULL;
 
   err = cgi_cookie_clear (cgi, name, domain, path);
@@ -307,7 +309,7 @@
     Py_INCREF(Py_None);
     return Py_None;
   }
-  return PyFile_FromFile (fp, "name", "w+", NULL);
+  return PyFile_FromFile (fp, name, "w+", NULL);
 }
 
 static PyMethodDef CGIMethods[] =
@@ -331,20 +333,37 @@
 
 static PyObject * p_cgi_url_escape (PyObject *self, PyObject *args)
 {
-  char *s, *esc;
+  char *s, *esc, *o = NULL;
   NEOERR *err;
   PyObject *rv;
 
-  if (!PyArg_ParseTuple(args, "s:urlEscape(str)", &s))
+  if (!PyArg_ParseTuple(args, "s|s:urlEscape(str, other=None)", &s, &o))
     return NULL;
 
-  err = cgi_url_escape (s, &esc);
+  err = cgi_url_escape_more (s, &esc, o);
   if (err) return p_neo_error (err);
   rv = Py_BuildValue ("s", esc);
   free (esc);
   return rv;
 }
 
+static PyObject * p_cgi_url_unescape (PyObject *self, PyObject *args)
+{
+  char *s;
+  PyObject *rv;
+  char *r;
+
+  if (!PyArg_ParseTuple(args, "s:urlUnescape(str)", &s))
+    return NULL;
+
+  r = strdup(s);
+  if (r == NULL) return PyErr_NoMemory();
+  cgi_url_unescape (r);
+  rv = Py_BuildValue ("s", r);
+  free (r);
+  return rv;
+}
+
 static PyObject * p_html_escape (PyObject *self, PyObject *args)
 {
   char *s, *esc;
@@ -523,15 +542,6 @@
       PyErr_SetString(PyExc_TypeError,
 	  "object.read() returned non-string");
     }
-    if (n < 0 && result != NULL) {
-      int len = PyString_Size(result);
-      if (len == 0) {
-	Py_DECREF(result);
-	result = NULL;
-	PyErr_SetString(PyExc_EOFError,
-	    "EOF on object.read()");
-      }
-    }
     return result;
   }
 }
@@ -547,7 +557,7 @@
   if (buf == NULL)
   {
     PyErr_Clear();
-    return 0;
+    return -1;
   }
 
   len = PyString_Size(buf);
@@ -609,7 +619,7 @@
   items = PyObject_GetAttrString(wrap->p_env, "items");
   if (items == NULL)
   {
-    /* ne_warn ("p_iterenv: Unable to get items method"); */
+    ne_warn ("p_iterenv: Unable to get items method");
     PyErr_Clear();
     return -1;
   }
@@ -617,14 +627,21 @@
   Py_DECREF(items);
   if (env_list == NULL)
   {
-    /* ne_warn ("p_iterenv: Unable to call items method"); */
+    ne_warn ("p_iterenv: Unable to call items method");
     PyErr_Clear();
     return -1;
   }
+  if (x >= PyList_Size(env_list))
+  {
+    *rk = NULL;
+    *rv = NULL;
+    Py_DECREF(env_list); 
+    return 0;
+  }
   result = PyList_GetItem (env_list, x);
   if (result == NULL)
   {
-    /* ne_warn ("p_iterenv: Unable to get env %d", x); */
+    ne_warn ("p_iterenv: Unable to get env %d", x);
     Py_DECREF(env_list); 
     PyErr_Clear();
     return -1;
@@ -633,7 +650,7 @@
   v = PyTuple_GetItem (result, 1);
   if (k == NULL || v == NULL)
   {
-    /* ne_warn ("p_iterenv: Unable to get k,v %s,%s", k, v); */
+    ne_warn ("p_iterenv: Unable to get k,v %s,%s", k, v); 
     Py_DECREF(env_list); 
     PyErr_Clear();
     return -1;
@@ -793,9 +810,6 @@
 
 static PyObject * p_update (PyObject *self, PyObject *args)
 {
-  PyObject *dict;
-  int i = 0;
-
   if (_PyImport_FindExtension("neo_util","neo_util") == NULL)
     initneo_util();
 
@@ -810,6 +824,7 @@
 {
   {"CGI", p_cgi_init, METH_VARARGS, NULL},
   {"urlEscape", p_cgi_url_escape, METH_VARARGS, NULL},
+  {"urlUnescape", p_cgi_url_unescape, METH_VARARGS, NULL},
   {"htmlEscape", p_html_escape, METH_VARARGS, NULL},
   {"text2html", p_text_html, METH_VARARGS, NULL},
   {"cgiWrap", cgiwrap, METH_VARARGS, cgiwrap_doc},
@@ -822,6 +837,8 @@
 void initneo_cgi(void)
 {
   PyObject *m, *d;
+  static void *NEO_PYTHON_API[P_NEO_CGI_POINTERS];
+  PyObject *c_api_object;
 
   initneo_util();
   _PyImport_FixupExtension("neo_util", "neo_util");
@@ -834,4 +851,18 @@
   d = PyModule_GetDict(m);
   CGIFinishedException = PyErr_NewException("neo_cgi.CGIFinished", NULL, NULL);
   PyDict_SetItemString(d, "CGIFinished", CGIFinishedException);
+
+  /* Initialize the C API Pointer array */
+  NEO_PYTHON_API[P_HDF_TO_OBJECT_NUM] = (void *)p_hdf_to_object;
+  NEO_PYTHON_API[P_OBJECT_TO_HDF_NUM] = (void *)p_object_to_hdf;
+  NEO_PYTHON_API[P_NEO_ERROR_NUM] = (void *)p_neo_error;
+
+  /* create a CObject containing the API pointer array's address */
+  c_api_object = PyCObject_FromVoidPtr((void *)NEO_PYTHON_API, NULL);
+  if (c_api_object != NULL) {
+    /* create a name for this object in the module's namespace */
+    PyDict_SetItemString(d, "_C_API", c_api_object);
+    Py_DECREF(c_api_object);
+    PyDict_SetItemString(d, "_C_API_NUM", PyInt_FromLong(P_NEO_CGI_POINTERS));
+  }
 }
diff -Nbru clearsilver-0.4/python/neo_cs.c clearsilver-0.5/python/neo_cs.c
--- clearsilver-0.4/python/neo_cs.c	Mon Aug  6 14:28:17 2001
+++ clearsilver-0.5/python/neo_cs.c	Sat Apr 13 17:49:50 2002
@@ -14,6 +14,8 @@
 #include "util/neo_str.h"
 #include "util/neo_hdf.h"
 #include "cs/cs.h"
+
+#define NEO_CGI_MODULE
 #include "p_neo_util.h"
 
 
diff -Nbru clearsilver-0.4/python/neo_util.c clearsilver-0.5/python/neo_util.c
--- clearsilver-0.4/python/neo_util.c	Tue Oct 23 00:01:35 2001
+++ clearsilver-0.5/python/neo_util.c	Sat Apr 13 17:49:50 2002
@@ -14,15 +14,27 @@
 #include "util/neo_str.h"
 #include "util/neo_hdf.h"
 
+#define NEO_CGI_MODULE
+#include "p_neo_util.h"
+
 static PyObject *NeoError;
+static PyObject *NeoParseError;
 
 PyObject * p_neo_error (NEOERR *err)
 {
   STRING str;
 
   string_init (&str);
+  if (nerr_match(err, NERR_PARSE))
+  {
   nerr_error_string (err, &str);
+    PyErr_SetString (NeoParseError, str.buf);
+  }
+  else
+  {
+    nerr_error_traceback (err, &str);
   PyErr_SetString (NeoError, str.buf);
+  }
   string_clear (&str);
   return NULL;
 }
@@ -392,6 +404,25 @@
   return rv;
 }
 
+static PyObject * p_hdf_set_symlink (PyObject *self, PyObject *args)
+{
+  HDFObject *ho = (HDFObject *)self;
+  PyObject *rv;
+  char *src;
+  char *dest;
+  NEOERR *err;
+
+  if (!PyArg_ParseTuple(args, "ss:copy(src, dest)", &src, &dest))
+    return NULL;
+
+  err = hdf_set_symlink (ho->data, src, dest);
+  if (err) return p_neo_error(err); 
+
+  rv = Py_None;
+  Py_INCREF(rv);
+  return rv;
+}
+
 static PyMethodDef HDFMethods[] =
 {
   {"getIntValue", p_hdf_get_int_value, METH_VARARGS, NULL},
@@ -410,12 +441,56 @@
   {"removeTree", p_hdf_remove_tree, METH_VARARGS, NULL},
   {"dump", p_hdf_dump, METH_VARARGS, NULL},
   {"copy", p_hdf_copy, METH_VARARGS, NULL},
+  {"setSymLink", p_hdf_set_symlink, METH_VARARGS, NULL},
   {NULL, NULL}
 };
 
+static PyObject * p_escape (PyObject *self, PyObject *args)
+{
+  PyObject *rv;
+  char *s;
+  char *escape;
+  char *esc_char;
+  int buflen;
+  char *ret = NULL;
+  NEOERR *err;
+
+  if (!PyArg_ParseTuple(args, "s#ss:escape(str, char, escape)", &s, &buflen, &esc_char, &escape))
+    return NULL;
+
+  err = neos_escape(s, buflen, esc_char[0], escape, &ret);
+  if (err) return p_neo_error(err); 
+
+  rv = Py_BuildValue("s", ret);
+  free(ret);
+  return rv;
+}
+
+static PyObject * p_unescape (PyObject *self, PyObject *args)
+{
+  PyObject *rv;
+  char *s;
+  char *copy;
+  char *esc_char;
+  int buflen;
+
+  if (!PyArg_ParseTuple(args, "s#s:unescape(str, char)", &s, &buflen, &esc_char))
+    return NULL;
+
+  copy = strdup(s);
+  if (copy == NULL) return PyErr_NoMemory();
+  neos_unescape(copy, buflen, esc_char[0]);
+
+  rv = Py_BuildValue("s", copy);
+  free(copy);
+  return rv;
+}
+
 static PyMethodDef UtilMethods[] =
 {
   {"HDF", p_hdf_init, METH_VARARGS, NULL},
+  {"escape", p_escape, METH_VARARGS, NULL},
+  {"unescape", p_unescape, METH_VARARGS, NULL},
   {NULL, NULL}
 };
 
@@ -430,6 +505,8 @@
 
   m = Py_InitModule("neo_util", UtilMethods);
   d = PyModule_GetDict(m);
-  NeoError = PyErr_NewException("neo_util.error", NULL, NULL);
-  PyDict_SetItemString(d, "error", NeoError);
+  NeoError = PyErr_NewException("neo_util.Error", NULL, NULL);
+  NeoParseError = PyErr_NewException("neo_util.ParseError", NULL, NULL);
+  PyDict_SetItemString(d, "Error", NeoError);
+  PyDict_SetItemString(d, "ParseError", NeoParseError);
 }
diff -Nbru clearsilver-0.4/python/p_neo_util.h clearsilver-0.5/python/p_neo_util.h
--- clearsilver-0.4/python/p_neo_util.h	Mon Aug  6 14:28:17 2001
+++ clearsilver-0.5/python/p_neo_util.h	Sat Apr 13 17:49:51 2002
@@ -11,10 +11,66 @@
 #ifndef __P_NEO_UTIL_H_
 #define __P_NEO_UTIL_H_ 1
 
-PyObject * p_neo_error (NEOERR *err);
-PyObject * p_hdf_to_object (HDF *data, int dealloc);
-HDF * p_object_to_hdf (PyObject *ho);
+#include <util/neo_hdf.h>
+
+__BEGIN_DECLS
+
+/* external HDF object interface. */
+
+#define P_HDF_TO_OBJECT_NUM 0
+#define P_HDF_TO_OBJECT_RETURN PyObject *
+#define P_HDF_TO_OBJECT_PROTO (HDF *data, int dealloc)
+
+#define P_OBJECT_TO_HDF_NUM 1
+#define P_OBJECT_TO_HDF_RETURN HDF *
+#define P_OBJECT_TO_HDF_PROTO (PyObject *ho)
+
+#define P_NEO_ERROR_NUM 2
+#define P_NEO_ERROR_RETURN PyObject *
+#define P_NEO_ERROR_PROTO (NEOERR *err)
+
+#define P_NEO_CGI_POINTERS 3
+
+#ifdef NEO_CGI_MODULE
+P_HDF_TO_OBJECT_RETURN p_hdf_to_object P_HDF_TO_OBJECT_PROTO;
+P_OBJECT_TO_HDF_RETURN p_object_to_hdf P_OBJECT_TO_HDF_PROTO;
+P_NEO_ERROR_RETURN p_neo_error P_NEO_ERROR_PROTO;
+
+/* other functions */
+
 void initneo_util(void);
 void initneo_cs(void);
+
+#else
+static void **NEO_PYTHON_API;
+
+#define p_hdf_to_object \
+  (*(P_HDF_TO_OBJECT_RETURN (*)P_HDF_TO_OBJECT_PROTO) NEO_PYTHON_API[P_HDF_TO_OBJECT_NUM])
+
+#define p_object_to_hdf \
+  (*(P_OBJECT_TO_HDF_RETURN (*)P_OBJECT_TO_HDF_PROTO) NEO_PYTHON_API[P_OBJECT_TO_HDF_NUM])
+
+#define p_neo_error \
+  (*(P_NEO_ERROR_RETURN (*)P_NEO_ERROR_PROTO) NEO_PYTHON_API[P_NEO_ERROR_NUM])
+
+#define import_neo_cgi() \
+{ \
+  PyObject *module = PyImport_ImportModule("neo_cgi"); \
+  if (module != NULL) { \
+    PyObject *module_dict = PyModule_GetDict(module); \
+    PyObject *c_api_object = PyDict_GetItemString(module_dict, "_C_API"); \
+    PyObject *c_api_num_o = PyDict_GetItemString(module_dict, "_C_API_NUM"); \
+    if (PyInt_AsLong(c_api_num_o) < P_NEO_CGI_POINTERS) { \
+      PyErr_Format(PyExc_ImportError, "neo_cgi module doesn't match header compiled against, use of this module may cause a core dump: %ld < %ld", PyInt_AsLong(c_api_num_o), P_NEO_CGI_POINTERS); \
+    } \
+    if (PyCObject_Check(c_api_object)) { \
+      NEO_PYTHON_API = (void **)PyCObject_AsVoidPtr(c_api_object); \
+    } \
+  } \
+}
+
+#endif
+
+__END_DECLS
 
 #endif /* __P_NEO_UTIL_H_ */
diff -Nbru clearsilver-0.4/rules.mk clearsilver-0.5/rules.mk
--- clearsilver-0.4/rules.mk	Mon Aug  6 15:16:45 2001
+++ clearsilver-0.5/rules.mk	Sat Apr 13 11:59:01 2002
@@ -14,18 +14,20 @@
 ## 2.7.7 instead
 DB2_INC = -I$(HOME)/src/db-2.7.7/dist
 DB2_LIB = -L$(HOME)/src/db-2.7.7/dist -ldb
-PYTHON_INC = -I/neo/opt/include/python2.1
+PYTHON_INC = -I/neo/opt/include/python2.1 -I/neo/opt/include/python2.2
 
 ## Programs
 MKDIR      = mkdir -p
 RM         = rm -f
 CC         = gcc
+CPP        = g++
 
 CFLAGS     = -g -O2 -Wall -c -I$(NEOTONIC_ROOT) $(DB2_INC) -I/neo/opt/include
 OUTPUT_OPTION = -o $@
 LD         = $(CC) -o
 LDFLAGS    = -L$(LIB_DIR)
-LDSHARED   = $(CC) -shared -fPic
+LDSHARED   = $(CC) -shared -fPi
+CPPLDSHARED   = $(CPP) -shared -fPic
 AR         = $(MKDIR) $(LIB_DIR); ar -cr
 DEP_LIBS   = $(DLIBS:-l%=$(LIB_DIR)lib%.a)
 
@@ -40,7 +42,7 @@
 .PHONY: depend
 depend: Makefile.depends
 
-Makefile.depends:
+Makefile.depends: $(NEOTONIC_ROOT)/rules.mk Makefile
 	@echo "*******************************************"
 	@echo "** Building Dependencies "
 	@rm -f Makefile.depends
diff -Nbru clearsilver-0.4/util/Makefile clearsilver-0.5/util/Makefile
--- clearsilver-0.4/util/Makefile	Mon Jun 25 04:24:40 2001
+++ clearsilver-0.5/util/Makefile	Thu Feb 28 16:53:41 2002
@@ -7,8 +7,9 @@
 include $(NEOTONIC_ROOT)rules.mk
 
 UTL_LIB = $(LIB_DIR)libneo_utl.a
-UTL_SRC = neo_err.c neo_misc.c neo_test.c ulist.c neo_hdf.c neo_str.c \
-          ulocks.c skiplist.c dict.c wdb.c neo_date.c rcfs.c
+UTL_SRC = neo_err.c neo_files.c neo_misc.c neo_test.c ulist.c neo_hdf.c \
+	  neo_str.c ulocks.c skiplist.c dict.c wdb.c neo_date.c rcfs.c \
+	  wildmat.c
 UTL_OBJ = $(UTL_SRC:%.c=%.o)
 
 TARGETS = $(UTL_LIB)
diff -Nbru clearsilver-0.4/util/neo_date.h clearsilver-0.5/util/neo_date.h
--- clearsilver-0.4/util/neo_date.h	Mon Aug  6 14:28:17 2001
+++ clearsilver-0.5/util/neo_date.h	Thu Apr 18 14:42:33 2002
@@ -11,10 +11,14 @@
 #ifndef _NEO_DATE_H_
 #define _NEO_DATE_H_ 1
 
+__BEGIN_DECLS
+
 /* UTC time_t -> struct tm in local timezone */
 void neo_time_expand (const time_t tt, char *timezone, struct tm *ttm);
 
 /* local timezone struct tm -> time_t UTC */
 time_t neo_time_compact (struct tm *ttm, char *timezone);
+
+__END_DECLS
 
 #endif /* _NEO_DATE_H_ */
diff -Nbru clearsilver-0.4/util/neo_err.c clearsilver-0.5/util/neo_err.c
--- clearsilver-0.4/util/neo_err.c	Mon Aug  6 14:28:17 2001
+++ clearsilver-0.5/util/neo_err.c	Tue Mar 12 23:46:03 2002
@@ -226,6 +226,50 @@
 {
   NEOERR *more;
   char buf[1024];
+  char *err_name;
+
+  if (err == STATUS_OK)
+    return;
+
+  if (err == INTERNAL_ERR)
+  {
+    string_append (str, "Internal error");
+    return;
+  }
+
+  more = err;
+  while (more && more != INTERNAL_ERR)
+  {
+    err = more;
+    more = err->next;
+    if (err->error != NERR_PASS)
+    {
+      NEOERR *r;
+      if (err->error == 0)
+      {
+	err_name = buf;
+	snprintf (buf, sizeof (buf), "Unknown Error");
+      }
+      else
+      {
+	r = uListGet (Errors, err->error - 1, (void *)&err_name);
+	if (r != STATUS_OK)
+	{
+	  err_name = buf;
+	  snprintf (buf, sizeof (buf), "Error %d", err->error);
+	}
+      }
+
+      string_appendf(str, "%s: %s", err_name, err->desc);
+      return;
+    }
+  }
+}
+
+void nerr_error_traceback (NEOERR *err, STRING *str)
+{
+  NEOERR *more;
+  char buf[1024];
   char buf2[1024];
   char *err_name;
 
@@ -289,33 +333,55 @@
 
 int nerr_handle (NEOERR **err, int type)
 {
-  if (*err == STATUS_OK && (NEOERR *)type == STATUS_OK)
+  NEOERR *walk = *err;
+
+  while (walk != STATUS_OK && walk != INTERNAL_ERR)
+  {
+
+    if (walk->error == type)
+    {
+      _err_free(*err);
+      *err = STATUS_OK;
     return 1;
-  if (*err == STATUS_OK)
-    return 0;
+    }
+    walk = walk->next;
+  }
 
-  if (*err == INTERNAL_ERR && (NEOERR *)type == INTERNAL_ERR)
+  if (walk == STATUS_OK && (NEOERR *)type == STATUS_OK)
     return 1;
-  if (*err == INTERNAL_ERR)
+  if (walk == STATUS_OK)
     return 0;
 
-  if (((*err)->error == NERR_PASS) && (type != NERR_PASS))
-  {
-    int r = nerr_handle (&((*err)->next), type);
-    if (r)
+  if (walk == INTERNAL_ERR && (NEOERR *)type == INTERNAL_ERR)
     {
-      _err_free (*err);
       *err = STATUS_OK;
       return 1;
     }
-  }
+  if (walk == INTERNAL_ERR)
+    return 0;
+
+  return 0;
+}
 
-  if ((*err)->error == type)
+int nerr_match (NEOERR *err, int type)
+{
+  while (err != STATUS_OK && err != INTERNAL_ERR)
   {
-    _err_free (*err);
-    *err = STATUS_OK;
+
+    if (err->error == type)
     return 1;
+    err = err->next;
   }
+
+  if (err == STATUS_OK && (NEOERR *)type == STATUS_OK)
+    return 1;
+  if (err == STATUS_OK)
+    return 0;
+
+  if (err == INTERNAL_ERR && (NEOERR *)type == INTERNAL_ERR)
+    return 1;
+  if (err == INTERNAL_ERR)
+    return 0;
 
   return 0;
 }
diff -Nbru clearsilver-0.4/util/neo_err.h clearsilver-0.5/util/neo_err.h
--- clearsilver-0.4/util/neo_err.h	Mon Aug  6 14:28:17 2001
+++ clearsilver-0.5/util/neo_err.h	Tue Mar 12 23:42:32 2002
@@ -111,6 +111,7 @@
 
 #include "neo_str.h"
 void nerr_error_string (NEOERR *err, STRING *str);
+void nerr_error_traceback (NEOERR *err, STRING *str);
 
 /* function: nerr_ignore
  * description: you should only call this if you actually handle the
@@ -123,6 +124,7 @@
 NEOERR *nerr_init (void);
 
 int nerr_handle (NEOERR **err, NERR_TYPE type);
+int nerr_match (NEOERR *err, NERR_TYPE type);
 
 __END_DECLS
 
diff -Nbru clearsilver-0.4/util/neo_files.c clearsilver-0.5/util/neo_files.c
--- clearsilver-0.4/util/neo_files.c	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.5/util/neo_files.c	Thu Feb 28 16:53:41 2002
@@ -0,0 +1,237 @@
+/*
+ * Neotonic ClearSilver Templating System
+ *
+ * This code is made available under the terms of the 
+ * Neotonic ClearSilver License.
+ * http://www.neotonic.com/clearsilver/license.hdf
+ *
+ * Copyright (C) 2001 by Brandon Long
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <limits.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include "neo_err.h"
+#include "neo_misc.h"
+#include "neo_files.h"
+#include "wildmat.h"
+
+NEOERR *ne_mkdirs (char *path, mode_t mode)
+{
+  char mypath[_POSIX_PATH_MAX];
+  int x;
+  int r;
+
+  strncpy (mypath, path, sizeof(mypath));
+  x = strlen(mypath);
+  if ((x < sizeof(mypath)) && (mypath[x-1] != '/'))
+  {
+    mypath[x] = '/';
+    mypath[x+1] = '\0';
+  }
+
+  for (x = 1; mypath[x]; x++)
+  {
+    if (mypath[x] == '/')
+    {
+      mypath[x] = '\0';
+      r = mkdir (mypath, mode);
+      if (r == -1 && errno != EEXIST)
+      {
+	return nerr_raise_errno(NERR_SYSTEM, "ne_mkdirs: mkdir(%s, %x) failed", mypath, mode);
+      }
+      mypath[x] = '/';
+    }
+  }
+  return STATUS_OK;
+}
+
+NEOERR *ne_load_file (char *path, char **str)
+{
+  struct stat s;
+  int fd;
+  int len;
+
+  *str = NULL;
+
+  if (stat(path, &s) == -1)
+  {
+    if (errno == ENOENT)
+      return nerr_raise (NERR_NOT_FOUND, "File %s not found", path);
+    return nerr_raise_errno (NERR_SYSTEM, "Unable to stat file %s", path);
+  }
+
+  fd = open (path, O_RDONLY);
+  if (fd == -1)
+  {
+    return nerr_raise_errno (NERR_SYSTEM, "Unable to open file %s", path);
+  }
+  len = s.st_size;
+  *str = (char *) malloc (len + 1);
+
+  if (*str == NULL)
+  {
+    close(fd);
+    return nerr_raise (NERR_NOMEM, 
+	"Unable to allocate memory (%d) to load file %s", s.st_size, path);
+  }
+  if (read (fd, *str, len) == -1)
+  {
+    close(fd);
+    free(*str);
+    return nerr_raise_errno (NERR_SYSTEM, "Unable to read file %s", path);
+  }
+  (*str)[len] = '\0';
+  close(fd);
+
+  return STATUS_OK;
+}
+
+NEOERR *ne_save_file (char *path, char *str)
+{
+  NEOERR *err;
+  int fd;
+  int w, l;
+
+  fd = open (path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
+  if (fd == -1)
+  {
+    return nerr_raise_errno (NERR_IO, "Unable to create file %s", path);
+  }
+  l = strlen(str);
+  w = write (fd, str, l);
+  if (w != l)
+  {
+    err = nerr_raise_errno (NERR_IO, "Unable to write file %s", path);
+    close (fd);
+    return err;
+  }
+  close (fd);
+
+  return STATUS_OK;
+}
+
+NEOERR *ne_remove_dir (char *path)
+{
+  NEOERR *err;
+  DIR *dp;
+  struct stat s;
+  struct dirent *de;
+  char npath[_POSIX_PATH_MAX];
+
+  if (stat(path, &s) == -1)
+  {
+    if (errno == ENOENT) return STATUS_OK;
+    return nerr_raise_errno (NERR_SYSTEM, "Unable to stat file %s", path);
+  }
+  if (!S_ISDIR(s.st_mode))
+  {
+    return nerr_raise (NERR_ASSERT, "Path %s is not a directory", path);
+  }
+  dp = opendir(path);
+  if (dp == NULL)
+    return nerr_raise_errno (NERR_IO, "Unable to open directory %s", path);
+  while ((de = readdir (dp)) != NULL)
+  {
+    if (strcmp(de->d_name, ".") && strcmp(de->d_name, ".."))
+    {
+      snprintf (npath, sizeof(npath), "%s/%s", path, de->d_name);
+      if (stat(npath, &s) == -1)
+      {
+	if (errno == ENOENT) continue;
+	closedir(dp);
+	return nerr_raise_errno (NERR_SYSTEM, "Unable to stat file %s", npath);
+      }
+      if (S_ISDIR(s.st_mode))
+      {
+	err = ne_remove_dir(npath);
+	if (err) break;
+      }
+      else
+      {
+	if (unlink(npath) == -1)
+	{
+	  if (errno == ENOENT) continue;
+	  closedir(dp);
+	  return nerr_raise_errno (NERR_SYSTEM, "Unable to unlink file %s", 
+	      npath);
+	}
+      }
+    }
+  }
+  closedir(dp);
+  if (rmdir(path) == -1)
+  {
+    return nerr_raise_errno (NERR_SYSTEM, "Unable to rmdir %s", path);
+  }
+  return STATUS_OK;
+}
+
+NEOERR *ne_listdir(char *path, ULIST **files)
+{
+  return nerr_pass(ne_listdir_fmatch(path, files, NULL, NULL));
+}
+
+static int _glob_match(void *rock, char *filename)
+{
+  return wildmat(filename, rock);
+}
+
+NEOERR *ne_listdir_match(char *path, ULIST **files, char *match)
+{
+  return nerr_pass(ne_listdir_fmatch(path, files, _glob_match, match));
+}
+
+NEOERR *ne_listdir_fmatch(char *path, ULIST **files, MATCH_FUNC fmatch, void *rock)
+{
+  DIR *dp;
+  struct dirent *de;
+  ULIST *myfiles = NULL;
+  NEOERR *err = STATUS_OK;
+
+  if (files == NULL) 
+    return nerr_raise(NERR_ASSERT, "Invalid call to ne_listdir_fmatch");
+
+  if (*files == NULL)
+  {
+    err = uListInit(&myfiles, 10, 0);
+    if (err) return nerr_pass(err);
+  }
+  else
+  {
+    myfiles = *files;
+  }
+
+  if ((dp = opendir (path)) == NULL)
+  {
+    return nerr_raise_errno(NERR_IO, "Unable to opendir %s", path);
+  }
+  while ((de = readdir (dp)) != NULL)
+  {
+    if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
+      continue;
+
+    if (fmatch != NULL && !fmatch(rock, de->d_name))
+      continue;
+
+    err = uListAppend(myfiles, strdup(de->d_name));
+    if (err) break;
+  }
+  closedir(dp);
+  if (err && *files == NULL)
+  {
+    uListDestroy(&myfiles, ULIST_FREE);
+  }
+  else if (*files == NULL)
+  {
+    *files = myfiles;
+  }
+  return nerr_pass(err);
+}
diff -Nbru clearsilver-0.4/util/neo_files.h clearsilver-0.5/util/neo_files.h
--- clearsilver-0.4/util/neo_files.h	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.5/util/neo_files.h	Thu Feb 28 16:53:41 2002
@@ -0,0 +1,32 @@
+/*
+ * Neotonic ClearSilver Templating System
+ *
+ * This code is made available under the terms of the 
+ * Neotonic ClearSilver License.
+ * http://www.neotonic.com/clearsilver/license.hdf
+ *
+ * Copyright (C) 2001 by Brandon Long
+ */
+
+#ifndef __NEO_FILES_H_
+#define __NEO_FILES_H_ 1
+
+__BEGIN_DECLS
+
+#include <stdarg.h>
+#include <sys/types.h>
+#include "util/ulist.h"
+
+typedef int (* MATCH_FUNC)(void *rock, char *filename);
+
+NEOERR *ne_mkdirs (char *path, mode_t mode);
+NEOERR *ne_load_file (char *path, char **str);
+NEOERR *ne_save_file (char *path, char *str);
+NEOERR *ne_remove_dir (char *path);
+NEOERR *ne_listdir(char *path, ULIST **files);
+NEOERR *ne_listdir_match(char *path, ULIST **files, char *match);
+NEOERR *ne_listdir_fmatch(char *path, ULIST **files, MATCH_FUNC fmatch, void *rock);
+
+__END_DECLS
+
+#endif /* __NEO_FILES_H_ */
diff -Nbru clearsilver-0.4/util/neo_hdf.c clearsilver-0.5/util/neo_hdf.c
--- clearsilver-0.4/util/neo_hdf.c	Wed Oct 10 17:26:13 2001
+++ clearsilver-0.5/util/neo_hdf.c	Thu Apr 18 12:24:53 2002
@@ -19,6 +19,7 @@
 #include "neo_err.h"
 #include "neo_hdf.h"
 #include "neo_str.h"
+#include "ulist.h"
 
 static NEOERR *_alloc_hdf (HDF **hdf, char *name, size_t nlen, char *value, 
     int dup, int wf, HDF *top)
@@ -131,6 +132,11 @@
   *node = NULL;
 
   if (hdf == NULL) return -1;
+  if (name == NULL || name[0] == '\0')
+  {
+    *node = hdf;
+    return 0;
+  }
 
   if (hdf->link)
   {
@@ -448,6 +454,66 @@
   return nerr_raise (NERR_NOT_FOUND, "Unable to find %s", src);
 }
 
+/* grody bubble sort from Wheeler, but sorting a singly linked list
+ * is annoying */
+void hdf_sort_obj_bubble(HDF *h, int (*compareFunc)(HDF *, HDF *))
+{
+  HDF *p, *c = hdf_obj_child(h);
+  HDF *prev;
+  int swapped;
+
+  do {
+    swapped = 0;
+    for (p = c, prev = c; p && p->next; prev = p, p = p->next) {
+      if (compareFunc(p, p->next) > 0) {
+	HDF *tmp = p->next;
+	prev->next = tmp;
+	p->next = tmp->next;
+	tmp->next = p;
+	p = tmp;
+	swapped = 1;
+      }
+    }
+  } while (swapped);
+}
+
+/* Ok, this version avoids the bubble sort by walking the level once to
+ * load them all into a ULIST, qsort'ing the list, and then dumping them
+ * back out... */
+NEOERR *hdf_sort_obj (HDF *h, int (*compareFunc)(const void *, const void *))
+{
+  NEOERR *err = STATUS_OK;
+  ULIST *level = NULL;
+  HDF *p, *c;
+  int x;
+
+  if (h == NULL) return STATUS_OK;
+  c = h->child;
+  if (c == NULL) return STATUS_OK;
+
+  do {
+    err = uListInit(&level, 40, 0);
+    if (err) return nerr_pass(err);
+    for (p = c; p; p = p->next) {
+      err = uListAppend(level, p);
+      if (err) break;
+    }
+    err = uListSort(level, compareFunc);
+    if (err) break;
+    uListGet(level, 0, (void **)&c);
+    h->child = c;
+    for (x = 1; x < uListLength(level); x++)
+    {
+      uListGet(level, x, (void **)&p);
+      c->next = p;
+      p->next = NULL;
+      c = p;
+    }
+  } while (0);
+  uListDestroy(&level, 0);
+  return nerr_pass(err);
+}
+
 NEOERR* hdf_remove_tree (HDF *hdf, char *name)
 {
   HDF *hp = hdf;
@@ -464,6 +530,9 @@
     return STATUS_OK;
   }
 
+  lp = hdf;
+  ln = NULL;
+
   n = name;
   s = strchr (n, '.');
   x = (s == NULL) ? strlen(n) : s - n;
@@ -932,19 +1001,37 @@
   return nerr_pass (_hdf_read_string (hdf, &str, &line));
 }
 
+static int count_newlines (char *s)
+{
+  int i = 0;
+  char *n = s;
+
+  n = strchr(s, '\n');
+  while (n != NULL)
+  {
+    i++;
+    n = strchr(n+1, '\n');
+  }
+  return i;
+}
+
 static NEOERR* hdf_read_file_fp (HDF *hdf, FILE *fp, char *path, int *line)
 {
   NEOERR *err;
+  STRING str;
   HDF *lower;
   char buf[4096];
   char *s;
   char *name, *value;
   int l;
 
-  while (fgets(buf, sizeof(buf), fp) != NULL)
+  string_init(&str);
+  err = string_readline(&str, fp);
+  if (err) return nerr_pass(err);
+  while (str.len != 0)
   {
     (*line)++;
-    s = buf;
+    s = str.buf;
     SKIPWS(s);
     if (!strncmp(s, "#include ", 9))
     {
@@ -1048,7 +1135,7 @@
 	    path, *line, name);
 	while (fgets(m+msize, mmax-msize, fp) != NULL)
 	{
-	  if (!strncmp(value, m+msize, l) && isspace(m[msize+l]))
+	  if (!strncmp(value, m+msize, l) && (m[msize+l] == '\r' || m[msize+l] == '\n'))
 	  {
 	    m[msize] = '\0';
 	    break;
@@ -1070,7 +1157,8 @@
 	  free (m);
 	  return nerr_pass_ctx(err, "In file %s:%d", path, *line);
 	}
-
+	/* count the newlines so we know what line we're on... +1 for EOM */
+	(*line) = (*line) + count_newlines(m) + 1;
       }
       else
       {
@@ -1078,6 +1166,9 @@
 	    path, *line, buf);
       }
     }
+    str.len = 0;
+    err = string_readline(&str, fp);
+    if (err) return nerr_pass(err);
   }
   return STATUS_OK;
 }
@@ -1123,6 +1214,8 @@
   int line = 0;
   char fpath[_POSIX_PATH_MAX];
 
+  if (path == NULL) 
+    return nerr_raise(NERR_ASSERT, "Can't read NULL file");
   if (path[0] != '/')
   {
     err = hdf_search_path (hdf, path, fpath);
diff -Nbru clearsilver-0.4/util/neo_hdf.h clearsilver-0.5/util/neo_hdf.h
--- clearsilver-0.4/util/neo_hdf.h	Fri Sep 14 16:45:46 2001
+++ clearsilver-0.5/util/neo_hdf.h	Mon Apr 15 17:37:10 2002
@@ -50,6 +50,19 @@
 
 NEOERR *hdf_set_symlink (HDF *hdf, char *src, char *dest);
 
+/*
+ * Function: hdf_sort_obj - sort the children of an HDF node 
+ * Description: hdf_sort_obj will sort the children of an HDF node,
+ *              based on the given comparison function.  The current
+ *              implementation uses a bubble sort, so be warned.
+ * Input: h - HDF node
+ *        compareFunc - function which returns 1,0,-1 depending on some 
+ *                      criteria.  Given two children of h
+ * Output: None (h children will be sorted)
+ * Return: None
+ */
+NEOERR *hdf_sort_obj(HDF *h, int (*compareFunc)(const void *, const void *));
+
 NEOERR* hdf_read_file (HDF *hdf, char *path);
 NEOERR* hdf_write_file (HDF *hdf, char *path);
 
diff -Nbru clearsilver-0.4/util/neo_misc.c clearsilver-0.5/util/neo_misc.c
--- clearsilver-0.4/util/neo_misc.c	Wed Jan  2 16:36:42 2002
+++ clearsilver-0.5/util/neo_misc.c	Thu Feb 28 16:53:41 2002
@@ -10,18 +10,12 @@
 
 #include <time.h>
 #include <stdarg.h>
-#include <stdio.h>
 #include <stdlib.h>
 #include <ctype.h>
 #include <sys/time.h>
 #include <sys/types.h>
-#include <fcntl.h>
 #include <string.h>
 #include <unistd.h>
-#include <errno.h>
-#include <limits.h>
-#include <dirent.h>
-#include <sys/stat.h>
 #include "neo_err.h"
 #include "neo_misc.h"
 
@@ -140,36 +134,6 @@
   return f;
 }
 
-NEOERR *ne_mkdirs (char *path, mode_t mode)
-{
-  char mypath[_POSIX_PATH_MAX];
-  int x;
-  int r;
-
-  strncpy (mypath, path, sizeof(mypath));
-  x = strlen(mypath);
-  if ((x < sizeof(mypath)) && (mypath[x-1] != '/'))
-  {
-    mypath[x] = '/';
-    mypath[x+1] = '\0';
-  }
-
-  for (x = 1; mypath[x]; x++)
-  {
-    if (mypath[x] == '/')
-    {
-      mypath[x] = '\0';
-      r = mkdir (mypath, mode);
-      if (r == -1 && errno != EEXIST)
-      {
-	return nerr_raise_errno(NERR_SYSTEM, "ne_mkdirs: mkdir(%s, %x) failed", mypath, mode);
-      }
-      mypath[x] = '/';
-    }
-  }
-  return STATUS_OK;
-}
-
 static const UINT32 CRCTable[256] = {
 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F,
 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988,
@@ -230,124 +194,3 @@
   return crc;
 }
 
-
-NEOERR *ne_load_file (char *path, char **str)
-{
-  struct stat s;
-  int fd;
-  int len;
-
-  *str = NULL;
-
-  if (stat(path, &s) == -1)
-  {
-    if (errno == ENOENT)
-      return nerr_raise (NERR_NOT_FOUND, "File %s not found", path);
-    return nerr_raise_errno (NERR_SYSTEM, "Unable to stat file %s", path);
-  }
-
-  fd = open (path, O_RDONLY);
-  if (fd == -1)
-  {
-    return nerr_raise_errno (NERR_SYSTEM, "Unable to open file %s", path);
-  }
-  len = s.st_size;
-  *str = (char *) malloc (len + 1);
-
-  if (*str == NULL)
-  {
-    close(fd);
-    return nerr_raise (NERR_NOMEM, 
-	"Unable to allocate memory (%d) to load file %s", s.st_size, path);
-  }
-  if (read (fd, *str, len) == -1)
-  {
-    close(fd);
-    free(*str);
-    return nerr_raise_errno (NERR_SYSTEM, "Unable to read file %s", path);
-  }
-  (*str)[len] = '\0';
-  close(fd);
-
-  return STATUS_OK;
-}
-
-NEOERR *ne_save_file (char *path, char *str)
-{
-  NEOERR *err;
-  int fd;
-  int w, l;
-
-  fd = open (path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
-  if (fd == -1)
-  {
-    return nerr_raise_errno (NERR_IO, "Unable to create file %s", path);
-  }
-  l = strlen(str);
-  w = write (fd, str, l);
-  if (w != l)
-  {
-    err = nerr_raise_errno (NERR_IO, "Unable to write file %s", path);
-    close (fd);
-    return err;
-  }
-  close (fd);
-
-  return STATUS_OK;
-}
-
-NEOERR *ne_remove_dir (char *path)
-{
-  NEOERR *err;
-  DIR *dp;
-  struct stat s;
-  struct dirent *de;
-  char npath[_POSIX_PATH_MAX];
-
-  if (stat(path, &s) == -1)
-  {
-    if (errno == ENOENT) return STATUS_OK;
-    return nerr_raise_errno (NERR_SYSTEM, "Unable to stat file %s", path);
-  }
-  if (!S_ISDIR(s.st_mode))
-  {
-    return nerr_raise (NERR_ASSERT, "Path %s is not a directory", path);
-  }
-  dp = opendir(path);
-  if (dp == NULL)
-    return nerr_raise_errno (NERR_IO, "Unable to open directory %s", path);
-  while ((de = readdir (dp)) != NULL)
-  {
-    if (strcmp(de->d_name, ".") && strcmp(de->d_name, ".."))
-    {
-      snprintf (npath, sizeof(npath), "%s/%s", path, de->d_name);
-      if (stat(npath, &s) == -1)
-      {
-	if (errno == ENOENT) continue;
-	closedir(dp);
-	return nerr_raise_errno (NERR_SYSTEM, "Unable to stat file %s", npath);
-      }
-      if (S_ISDIR(s.st_mode))
-      {
-	err = ne_remove_dir(npath);
-	if (err) break;
-      }
-      else
-      {
-	if (unlink(npath) == -1)
-	{
-	  if (errno == ENOENT) continue;
-	  closedir(dp);
-	  return nerr_raise_errno (NERR_SYSTEM, "Unable to unlink file %s", 
-	      npath);
-	}
-      }
-    }
-  }
-  closedir(dp);
-  if (rmdir(path) == -1)
-  {
-    return nerr_raise_errno (NERR_SYSTEM, "Unable to rmdir %s", path);
-  }
-  return STATUS_OK;
-}
diff -Nbru clearsilver-0.4/util/neo_misc.h clearsilver-0.5/util/neo_misc.h
--- clearsilver-0.4/util/neo_misc.h	Mon Dec 17 23:17:05 2001
+++ clearsilver-0.5/util/neo_misc.h	Thu Feb 28 16:53:41 2002
@@ -43,11 +43,7 @@
 UINT8 *ne_stream_str (UINT8 *dest, char *s, int l);
 UINT8 *ne_unstream_str (char *s, int l, UINT8 *src);
 double ne_timef (void);
-NEOERR *ne_mkdirs (char *path, mode_t mode);
-NEOERR *ne_load_file (char *path, char **str);
-NEOERR *ne_save_file (char *path, char *str);
 UINT32 ne_crc (UINT8 *data, UINT32 bytes);
-NEOERR *ne_remove_dir (char *path);
 
 __END_DECLS
 
diff -Nbru clearsilver-0.4/util/neo_str.c clearsilver-0.5/util/neo_str.c
--- clearsilver-0.4/util/neo_str.c	Mon Dec 17 23:17:05 2001
+++ clearsilver-0.5/util/neo_str.c	Thu Apr 11 18:10:59 2002
@@ -121,7 +121,7 @@
 
   err = string_check_length (str, l+1);
   if (err != STATUS_OK) return nerr_pass (err);
-  strncpy(str->buf + str->len, buf, l);
+  memcpy(str->buf + str->len, buf, l);
   str->len += l;
   str->buf[str->len] = '\0';
 
@@ -296,4 +296,122 @@
   if (errcode == 0)
     return TRUE;
   return FALSE;
+}
+
+NEOERR *string_readline (STRING *str, FILE *fp)
+{
+  NEOERR *err;
+
+  /* minimum size for a readline is 256 above current position */
+  err = string_check_length (str, str->len + 256);
+  if (err != STATUS_OK) return nerr_pass (err);
+
+  while (fgets(str->buf + str->len, str->max - str->len, fp) != NULL)
+  {
+    str->len = strlen(str->buf);
+    if (str->buf[str->len-1] == '\n') break;
+    err = string_check_length (str, str->len + 256);
+    if (err != STATUS_OK) return nerr_pass (err);
+  }
+  return STATUS_OK;
+}
+
+NEOERR* neos_escape(UINT8 *buf, int buflen, char esc_char, char *escape, char **esc)
+{
+  int nl = 0;
+  int l = 0;
+  int x = 0;
+  char *s;
+  int match = 0;
+
+  while (l < buflen)
+  {
+    if (buf[l] == esc_char)
+    {
+      nl += 2;
+    } 
+    else
+    {
+      x = 0;
+      while (escape[x])
+      {
+	if (escape[x] == buf[l])
+	{
+	  nl +=2;
+	  break;
+	}
+	x++;
+      }
+    }
+    nl++;
+    l++;
+  }
+
+  s = (char *) malloc (sizeof(char) * (nl + 1));
+  if (s == NULL) 
+    return nerr_raise (NERR_NOMEM, "Unable to allocate memory to escape %s", 
+	buf);
+
+  nl = 0; l = 0;
+  while (l < buflen)
+  {
+    match = 0;
+    if (buf[l] == esc_char)
+    {
+      match = 1;
+    }
+    else
+    {
+      x = 0;
+      while (escape[x])
+      {
+	if (escape[x] == buf[l])
+	{
+	  match = 1;
+	  break;
+	}
+	x++;
+      }
+    }
+    if (match)
+    {
+      s[nl++] = esc_char;
+      s[nl++] = "0123456789ABCDEF"[buf[l] / 16];
+      s[nl++] = "0123456789ABCDEF"[buf[l] % 16];
+      l++;
+    }
+    else
+    {
+      s[nl++] = buf[l++];
+    }
+  }
+  s[nl] = '\0';
+
+  *esc = s;
+  return STATUS_OK;
+}
+
+UINT8 *neos_unescape (UINT8 *s, int buflen, char esc_char)
+{
+  int i = 0, o = 0;
+
+  if (s == NULL) return s;
+  while (i < buflen)
+  {
+    if (s[i] == esc_char && (i+2 < buflen) && 
+	isxdigit(s[i+1]) && isxdigit(s[i+2]))
+    {
+      UINT8 num;
+      num = (s[i+1] >= 'A') ? ((s[i+1] & 0xdf) - 'A') + 10 : (s[i+1] - '0');
+      num *= 16;
+      num += (s[i+2] >= 'A') ? ((s[i+2] & 0xdf) - 'A') + 10 : (s[i+2] - '0');
+      s[o++] = num;
+      i+=3;
+    }
+    else {
+      s[o++] = s[i++];
+    }
+  }
+  if (i && o) s[o] = '\0';
+  return s;
 }
diff -Nbru clearsilver-0.4/util/neo_str.h clearsilver-0.5/util/neo_str.h
--- clearsilver-0.4/util/neo_str.h	Mon Dec 17 23:17:05 2001
+++ clearsilver-0.5/util/neo_str.h	Thu Apr 11 18:10:59 2002
@@ -15,6 +15,7 @@
 __BEGIN_DECLS
 
 #include <stdarg.h>
+#include <stdio.h>
 #include "util/neo_misc.h"
 
 /* This modifies the string its called with by replacing all the white
@@ -30,7 +31,7 @@
 
 typedef struct _string
 {
-  char *buf;
+  UINT8 *buf;
   int len;
   int max;
 } STRING;
@@ -49,6 +50,7 @@
 NEOERR *string_append_char (STRING *str, char c);
 NEOERR *string_appendf (STRING *str, char *fmt, ...);
 NEOERR *string_appendvf (STRING *str, char *fmt, va_list ap);
+NEOERR *string_readline (STRING *str, FILE *fp);
 void string_clear (STRING *str);
 
 /* typedef struct _ulist ULIST; */
@@ -57,6 +59,9 @@
 
 BOOL reg_search (char *re, char *str);
 
+
+NEOERR* neos_escape(UINT8 *buf, int buflen, char esc_char, char *escape, char **esc);
+UINT8 *neos_unescape (UINT8 *s, int buflen, char esc_char);
 
 __END_DECLS
 
diff -Nbru clearsilver-0.4/util/rcfs.c clearsilver-0.5/util/rcfs.c
--- clearsilver-0.4/util/rcfs.c	Mon Aug  6 14:28:17 2001
+++ clearsilver-0.5/util/rcfs.c	Thu Feb 28 16:53:41 2002
@@ -24,6 +24,7 @@
 
 #include "util/neo_err.h"
 #include "util/neo_misc.h"
+#include "util/neo_files.h"
 #include "util/neo_hdf.h"
 #include "util/ulocks.h"
 #include "rcfs.h"
diff -Nbru clearsilver-0.4/util/skiplist.c clearsilver-0.5/util/skiplist.c
--- clearsilver-0.4/util/skiplist.c	Mon Aug  6 14:28:17 2001
+++ clearsilver-0.5/util/skiplist.c	Thu Apr 11 14:17:46 2002
@@ -37,7 +37,7 @@
 #define SIZEOFITEM(max) (sizeof(struct skipItem) + \
                          ((max+1) * sizeof(skipItem)))
 
-struct skipList {
+struct skipList_struct {
   INT32 topLevel;                           /* current max level in any item */
   INT32 levelHint;                          /* hint at level to start search */
   skipItem header;                           /* header item (has all levels) */
@@ -361,7 +361,7 @@
   UINT32 i;
 
   *skip = NULL;
-  if(! (list = calloc(1, sizeof(struct skipList))))
+  if(! (list = calloc(1, sizeof(struct skipList_struct))))
     return nerr_raise(NERR_NOMEM, "Unable to allocate memore for skiplist");
 
   if (maxLevel == 0)
diff -Nbru clearsilver-0.4/util/skiplist.h clearsilver-0.5/util/skiplist.h
--- clearsilver-0.4/util/skiplist.h	Mon Aug  6 14:28:17 2001
+++ clearsilver-0.5/util/skiplist.h	Thu Apr 11 14:17:46 2002
@@ -17,6 +17,10 @@
 #ifndef _SKIPLIST_H_
 #define _SKIPLIST_H_
 
+#include <util/neo_err.h>
+
+__BEGIN_DECLS
+
 /* 
  * Larger values of <root> means fewer levels and faster lookups,
  * but more variability in those lookup times (range limited from 2 to 4).
@@ -27,11 +31,15 @@
  *
  * I've capped <maxLevel> at 20, which would be good for a minimum of
  * 1 million items on lists with <root> == 2.
+ *
+ *
+ * Example
+ *    skipNewList(&(my_wdb->ondisk), 0, 4, 2, 0, NULL, NULL);
  */
 #define SKIP_MAXLEVEL 20
 
 /* SKIP LIST TYPEDEFS */
-typedef struct skipList *skipList;
+typedef struct skipList_struct *skipList;
 typedef void (*skipFreeValue)(void *value, void *ctx);
 
 NEOERR *skipNewList(skipList *skip, int threaded, int root, int maxLevel,
@@ -146,4 +154,12 @@
  * MT-Level:    Safe if <list> thread-safe.
  */
 
+__END_DECLS
+
 #endif                                                       /* _SKIPLIST_H_ */
+
+
+
+
+
+
diff -Nbru clearsilver-0.4/util/test/Makefile clearsilver-0.5/util/test/Makefile
--- clearsilver-0.4/util/test/Makefile	Fri Dec 22 00:31:27 2000
+++ clearsilver-0.5/util/test/Makefile	Thu Apr 18 12:24:55 2002
@@ -10,16 +10,30 @@
 HDFTEST_SRC = hdftest.c
 HDFTEST_OBJ = $(HDFTEST_SRC:%.c=%.o)
 
+LISTDIRTEST_EXE = listdir_test
+LISTDIRTEST_SRC = listdir_test.c
+LISTDIRTEST_OBJ = $(LISTDIRTEST_SRC:%.c=%.o)
+
+HDFCOPYTEST_EXE = hdf_copy_test
+HDFCOPYTEST_SRC = hdf_copy_test.c
+HDFCOPYTEST_OBJ = $(HDFCOPYTEST_SRC:%.c=%.o)
+
 
 CFLAGS += -I$(NEOTONIC_ROOT)/util
-LIBS += -L$(LIB_DIR) -lneo_utl # -lefence
+LIBS += -L$(LIB_DIR) -lneo_utl 
 
-TARGETS = $(HDFTEST_EXE)
+TARGETS = $(HDFTEST_EXE) $(LISTDIRTEST_EXE) $(HDFCOPYTEST_EXE)
 
 all: $(TARGETS)
 
 $(HDFTEST_EXE): $(HDFTEST_OBJ) $(NTR_LIB)
 	$(LD) $@ $(HDFTEST_OBJ) $(LIBS)
+
+$(LISTDIRTEST_EXE): $(LISTDIRTEST_OBJ) $(NTR_LIB)
+	$(LD) $@ $(LISTDIRTEST_OBJ) $(LIBS)
+
+$(HDFCOPYTEST_EXE): $(HDFCOPYTEST_OBJ) $(NTR_LIB)
+	$(LD) $@ $(HDFCOPYTEST_OBJ) $(LIBS) -lefence
 
 clean:
 	$(RM) *.o
diff -Nbru clearsilver-0.4/util/test/hdf_copy_test.c clearsilver-0.5/util/test/hdf_copy_test.c
--- clearsilver-0.4/util/test/hdf_copy_test.c	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.5/util/test/hdf_copy_test.c	Thu Apr 18 12:24:55 2002
@@ -0,0 +1,37 @@
+
+#include <stdio.h>
+#include <neo_hdf.h>
+
+int main(void) {
+  HDF *hdf_1;
+  HDF *hdf_2;
+  HDF *cur_node,*last_node;
+
+  hdf_init(&hdf_1);
+
+  hdf_read_file(hdf_1,"hdf_copy_test.hdf");
+
+  cur_node = hdf_get_obj(hdf_1,"Chart");
+  last_node = cur_node;
+
+  cur_node = hdf_get_obj(cur_node,"next_stage");
+  while (hdf_get_obj(cur_node,"next_stage") && strcmp(hdf_get_value(cur_node,"Bucket.FieldId",""),"QUEUE")) {
+    last_node = cur_node;
+    cur_node = hdf_get_obj(cur_node,"next_stage");
+  }
+
+  if (hdf_get_obj(cur_node,"next_stage")) {
+    hdf_copy(hdf_1,"TempHolderPlace",hdf_get_obj(cur_node,"next_stage"));
+  }
+  ne_warn("Delete tree from node: %s", hdf_obj_name(last_node));
+  hdf_remove_tree(last_node,"next_stage");
+
+  hdf_dump(hdf_1,NULL);
+  fprintf(stderr,"-----------------\n");
+
+
+  hdf_copy(last_node,"next_stage",hdf_get_obj(hdf_1,"TempHolderPlace"));
+  hdf_dump(hdf_1,NULL);
+
+  return 0;
+}
diff -Nbru clearsilver-0.4/util/test/hdf_copy_test.hdf clearsilver-0.5/util/test/hdf_copy_test.hdf
--- clearsilver-0.4/util/test/hdf_copy_test.hdf	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.5/util/test/hdf_copy_test.hdf	Thu Apr 18 11:46:44 2002
@@ -0,0 +1,50 @@
+
+# --------------------------------------------------
+# this is an example definition of a chart pipeline
+#
+
+
+DataSource = outfile.sdb
+DataSource {
+# TODO: specify time range for the dataset....
+}
+
+DomainData {
+# TODO: include data values for agents, queues, etc.
+}
+
+Filters {
+# TODO: configure filters to limit data
+}
+
+Chart {
+  next_stage {
+     id                = 1
+     Type              = Mux
+     Bucket            = Discrete
+     Bucket.FieldId    = QUEUE
+     Bucket.Dimension  = Y
+     
+     next_stage {
+         id                = 2
+         Type              = Mux
+         Bucket            = Time
+         Bucket.FieldId    = TIME
+         Bucket.Dimension  = Y
+
+         next_stage {
+             id                = 3
+             Type              = Mux
+             Bucket            = Discrete
+             Bucket.FieldId    = AGENT
+             Bucket.Dimension  = X
+
+             next_stage {
+                 id                  = 4
+                 Type                = Accumulator
+             }
+         }
+     }
+    
+  }
+}
diff -Nbru clearsilver-0.4/util/test/hdftest.c clearsilver-0.5/util/test/hdftest.c
--- clearsilver-0.4/util/test/hdftest.c	Fri Dec 22 00:31:27 2000
+++ clearsilver-0.5/util/test/hdftest.c	Mon Apr 15 17:37:11 2002
@@ -21,6 +21,15 @@
   return 0;
 }
 
+static int sortByName(const void *a, const void *b) {
+  HDF **ha = (HDF **)a;
+  HDF **hb = (HDF **)b;
+
+  /* fprintf(stderr, "%s <=> %s\n", hdf_obj_name(*ha), hdf_obj_name(*hb));  */
+  return strcasecmp(hdf_obj_name(*ha), hdf_obj_name(*hb));
+}
+
+
 int main(int argc, char *argv[])
 {
   NEOERR *err;
@@ -28,6 +37,7 @@
   int x;
   char name[256];
   char value[256];
+  double tstart = 0;
 
   err = hdf_init(&hdf);
   if (err != STATUS_OK) 
@@ -75,7 +85,7 @@
   hdf_dump(hdf, NULL);
 
 
-  err = hdf_get_int_value (hdf, "Beware.The.Ides", &x, 0);
+  x = hdf_get_int_value (hdf, "Beware.The.Ides", 0);
   if (err != STATUS_OK) 
   {
     nerr_log_error(err);
@@ -87,7 +97,7 @@
     return -1;
   } 
 
-  for (x = 0; x < 100; x++)
+  for (x = 0; x < 10000; x++)
   {
     rand_name(name, sizeof(name));
     neot_rand_word(value, sizeof(value));
@@ -107,7 +117,11 @@
     return -1;
   }
 
-  hdf_dump(hdf, NULL);
+  tstart = ne_timef();
+  hdf_sort_obj(hdf, sortByName);
+  ne_warn("sort took %5.5fs", ne_timef() - tstart);
+
+  /* hdf_dump(hdf, NULL); */
 
   hdf_destroy(&hdf);
 
diff -Nbru clearsilver-0.4/util/test/listdir_test.c clearsilver-0.5/util/test/listdir_test.c
--- clearsilver-0.4/util/test/listdir_test.c	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.5/util/test/listdir_test.c	Thu Feb 28 16:53:42 2002
@@ -0,0 +1,52 @@
+
+#include <stdio.h>
+#include "util/neo_err.h"
+#include "util/neo_misc.h"
+#include "util/neo_files.h"
+
+int main(int argc, char **argv)
+{
+  char *path;
+  ULIST *files = NULL;
+  char *filename;
+  NEOERR *err;
+  int x;
+
+  if (argc > 1)
+    path = argv[1];
+  else
+    path = ".";
+
+  ne_warn("Testing ne_listdir()");
+  err = ne_listdir(path, &files);
+  if (err)
+  {
+    nerr_log_error(err);
+    return -1;
+  }
+
+  for (x = 0; x < uListLength(files); x++)
+  {
+    err = uListGet(files, x, &filename);
+    printf("%s\n", filename);
+  }
+
+  uListDestroy(&files, ULIST_FREE);
+
+  ne_warn("Testing ne_listdir_match() with *.c");
+  err = ne_listdir_match(path, &files, "*.c");
+  if (err)
+  {
+    nerr_log_error(err);
+    return -1;
+  }
+
+  for (x = 0; x < uListLength(files); x++)
+  {
+    err = uListGet(files, x, &filename);
+    printf("%s\n", filename);
+  }
+
+  uListDestroy(&files, ULIST_FREE);
+  return 0;
+}
diff -Nbru clearsilver-0.4/util/ulist.c clearsilver-0.5/util/ulist.c
--- clearsilver-0.4/util/ulist.c	Mon Aug  6 14:28:17 2001
+++ clearsilver-0.5/util/ulist.c	Thu Mar 28 10:35:43 2002
@@ -216,6 +216,29 @@
   return STATUS_OK;
 }
 
+void *uListSearch (ULIST *ul, const void *key, int
+    (*compareFunc)(const void *, const void*)) {
+  return bsearch(key, ul->items, ul->num, sizeof(void *), compareFunc);
+}
+
+void *uListIn (ULIST *ul, const void *key, int (*compareFunc)(const void *, const void*)) {
+  int i;
+
+  for (i = 0; i < ul->num; ++i) {
+    if (!compareFunc(key, &ul->items[i])) {
+      return &ul->items[i];
+    }
+  }
+  return NULL;
+}
+
+int uListIndex (ULIST *ul, const void *key, int (*compareFunc)(const void *, const void*)) {
+  void **p = uListIn(ul, key, compareFunc);
+  return p ? (p - ul->items) : -1;
+}
+
+
+
 NEOERR *uListDestroy (ULIST **ul, int flags)
 {
   if (flags & ULIST_FREE)
@@ -254,5 +277,6 @@
 
 int uListLength (ULIST *ul)
 {
+  if (ul == NULL) return 0;
   return ul->num;
 }
diff -Nbru clearsilver-0.4/util/ulist.h clearsilver-0.5/util/ulist.h
--- clearsilver-0.4/util/ulist.h	Mon Aug  6 14:28:17 2001
+++ clearsilver-0.5/util/ulist.h	Thu Mar 28 10:35:43 2002
@@ -36,6 +36,9 @@
 NEOERR * uListSet (ULIST *ul, int x, void *data);
 NEOERR * uListReverse (ULIST *ul);
 NEOERR * uListSort (ULIST *ul, int (*compareFunc)(const void*, const void*));
+void *uListSearch (ULIST *ul, const void *key, int (*compareFunc)(const void *, const void*));
+void *uListIn (ULIST *ul, const void *key, int (*compareFunc)(const void *, const void*));
+int uListIndex (ULIST *ul, const void *key, int (*compareFunc)(const void *, const void*));
 NEOERR * uListDestroy (ULIST **ul, int flags);
 NEOERR * uListDestroyFunc (ULIST **ul, void (*destroyFunc)(void *));
 
diff -Nbru clearsilver-0.4/util/ulocks.c clearsilver-0.5/util/ulocks.c
--- clearsilver-0.4/util/ulocks.c	Wed Jan  2 16:36:31 2002
+++ clearsilver-0.5/util/ulocks.c	Thu Feb 28 16:53:41 2002
@@ -17,6 +17,7 @@
 
 #include "neo_err.h"
 #include "neo_misc.h"
+#include "neo_files.h"
 #include "ulocks.h"
 
 NEOERR *fCreate(int *plock, char *file) 
diff -Nbru clearsilver-0.4/util/wildmat.c clearsilver-0.5/util/wildmat.c
--- clearsilver-0.4/util/wildmat.c	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.5/util/wildmat.c	Thu Feb 28 16:53:41 2002
@@ -0,0 +1,205 @@
+/* $Revision: 1.1 $
+ **
+ ** Do shell-style pattern matching for ?, \, [], and * characters.
+ ** Might not be robust in face of malformed patterns; e.g., "foo[a-"
+ ** could cause a segmentation violation. It is 8bit clean.
+ **
+ ** Written by Rich $alz, mirror!rs, Wed Nov 26 19:03:17 EST 1986.
+ ** Rich $alz is now <rsalz@osf.org>.
+ ** April, 1991: Replaced mutually-recursive calls with in-line code
+ ** for the star character.
+ **
+ ** Special thanks to Lars Mathiesen <thorinn@diku.dk> for the ABORT code.
+ ** This can greatly speed up failing wildcard patterns. For example:
+ ** pattern: -*-*-*-*-*-*-12-*-*-*-m-*-*-*
+ ** text 1: -adobe-courier-bold-o-normal--12-120-75-75-m-70-iso8859-1
+ ** text 2: -adobe-courier-bold-o-normal--12-120-75-75-X-70-iso8859-1
+ ** Text 1 matches with 51 calls, while text 2 fails with 54 calls. Without
+ ** the ABORT code, it takes 22310 calls to fail. Ugh. The following
+ ** explanation is from Lars:
+ ** The precondition that must be fulfilled is that DoMatch will consume
+ ** at least one character in text. This is true if *p is neither '*' nor
+ ** '\0'.) The last return has ABORT instead of FALSE to avoid quadratic
+ ** behaviour in cases like pattern "*a*b*c*d" with text "abcxxxxx".  With
+ ** FALSE, each star-loop has to run to the end of the text; with ABORT
+ ** only the last one does.
+ **
+ ** Once the control of one instance of DoMatch enters the star-loop, that
+ ** instance will return either TRUE or ABORT, and any calling instance
+ ** will therefore return immediately after (without calling recursively
+ ** again). In effect, only one star-loop is ever active. It would be
+ ** possible to modify the code to maintain this context explicitly,
+ ** eliminating all recursive calls at the cost of some complication and
+ ** loss of clarity (and the ABORT stuff seems to be unclear enough by
+ ** itself). I think it would be unwise to try to get this into a
+ ** released version unless you have a good test data base to try it
+ ** out on.
+ */
+
+#include <ctype.h>
+#include "neo_misc.h"
+
+#define ABORT -1
+
+
+ /* What character marks an inverted character class? */
+#define NEGATE_CLASS '^'
+ /* Is "*" a common pattern? */
+#define OPTIMIZE_JUST_STAR
+ /* Do tar(1) matching rules, which ignore a trailing slash? */
+#undef MATCH_TAR_PATTERN
+
+
+/*
+ ** Match text and p, return TRUE, FALSE, or ABORT.
+ */
+  static int 
+DoMatch(register char *text, register char *p)
+{
+  register int last;
+  register int matched;
+  register int reverse;
+
+  for ( ; *p; text++, p++) {
+    if (*text == '\0' && *p != '*')
+      return ABORT;
+    switch (*p) {
+      case '\\':
+	/* Literal match with following character. */
+	p++;
+	/* FALLTHROUGH */
+      default:
+	if (*text != *p)
+	  return FALSE;
+	continue;
+      case '?':
+	/* Match anything. */
+	continue;
+      case '*':
+	while (*++p == '*')
+	  /* Consecutive stars act just like one. */
+	  continue;
+	if (*p == '\0')
+	  /* Trailing star matches everything. */
+	  return TRUE;
+	while (*text)
+	  if ((matched = DoMatch(text++, p)) != FALSE)
+	    return matched;
+	return ABORT;
+      case '[':
+	reverse = p[1] == NEGATE_CLASS ? TRUE : FALSE;
+	if (reverse)
+	  /* Inverted character class. */
+	  p++;
+	matched = FALSE;
+	if (p[1] == ']' || p[1] == '-')
+	  if (*++p == *text)
+	    matched = TRUE;
+	for (last = *p; *++p && *p != ']'; last = *p)
+	  /* This next line requires a good C compiler. */
+	  if (*p == '-' && p[1] != ']'
+	      ? *text <= *++p && *text >= last : *text == *p)
+	    matched = TRUE;
+	if (matched == reverse)
+	  return FALSE;
+	continue;
+    }
+  }
+
+#ifdef MATCH_TAR_PATTERN
+  if (*text == '/')
+    return TRUE;
+#endif /* MATCH_TAR_ATTERN */
+  return *text == '\0';
+}
+
+
+/*
+ ** Match text and p, return TRUE, FALSE, or ABORT.
+ */
+static int
+DoMatchCaseInsensitive(register char *text, register char *p)
+{
+  register int last;
+  register int matched;
+  register int reverse;
+
+  for ( ; *p; text++, p++) {
+    if (*text == '\0' && *p != '*')
+      return ABORT;
+    switch (*p) {
+      case '\\':
+	/* Literal match with following character. */
+	p++;
+	/* FALLTHROUGH */
+      default:
+	if (toupper(*text) != toupper(*p))
+	  return FALSE;
+	continue;
+      case '?':
+	/* Match anything. */
+	continue;
+      case '*':
+	while (*++p == '*')
+	  /* Consecutive stars act just like one. */
+	  continue;
+	if (*p == '\0')
+	  /* Trailing star matches everything. */
+	  return TRUE;
+	while (*text)
+	  if ((matched = DoMatchCaseInsensitive(text++, p)) != FALSE)
+	    return matched;
+	return ABORT;
+      case '[':
+	reverse = p[1] == NEGATE_CLASS ? TRUE : FALSE;
+	if (reverse)
+	  /* Inverted character class. */
+	  p++;
+	matched = FALSE;
+	if (p[1] == ']' || p[1] == '-')
+	  if (toupper(*++p) == toupper(*text))
+	    matched = TRUE;
+	for (last = toupper(*p); *++p && *p != ']'; last = toupper(*p))
+	  /* This next line requires a good C compiler. */
+	  if (*p == '-' && p[1] != ']'
+	      ? toupper(*text) <= toupper(*++p) && toupper(*text) >= last : toupper(*text) == toupper(*p))
+	    matched = TRUE;
+	if (matched == reverse)
+	  return FALSE;
+	continue;
+    }
+  }
+
+#ifdef MATCH_TAR_PATTERN
+  if (*text == '/')
+    return TRUE;
+#endif /* MATCH_TAR_ATTERN */
+  return *text == '\0';
+}
+
+
+/*
+ ** User-level routine. Returns TRUE or FALSE.
+ */
+int
+wildmat(char *text, char *p)
+{
+#ifdef OPTIMIZE_JUST_STAR
+  if (p[0] == '*' && p[1] == '\0')
+    return TRUE;
+#endif /* OPTIMIZE_JUST_STAR */
+  return DoMatch(text, p) == TRUE;
+}
+
+/*
+ ** User-level routine. Returns TRUE or FALSE.
+ */
+int
+wildmatcase(char *text, char *p)
+{
+#ifdef OPTIMIZE_JUST_STAR
+  if (p[0] == '*' && p[1] == '\0')
+    return TRUE;
+#endif /* OPTIMIZE_JUST_STAR */
+  return DoMatchCaseInsensitive(text, p) == TRUE;
+}
diff -Nbru clearsilver-0.4/util/wildmat.h clearsilver-0.5/util/wildmat.h
--- clearsilver-0.4/util/wildmat.h	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.5/util/wildmat.h	Thu Feb 28 16:53:41 2002
@@ -0,0 +1,16 @@
+/*
+ * Copyright (C) 1986-1991 Rich Salz <rsalz@osf.org>
+ *
+ */
+
+#ifndef __WILDMAT_H_
+#define __WILDMAT_H_ 1
+
+__BEGIN_DECLS
+
+int wildmat(char *text, char *p);
+int wildmatcase(char *text, char *p);
+
+__END_DECLS
+
+#endif /* __WILDMAT_H_ */
