diff -Nbru clearsilver-0.9.3/Makefile clearsilver-0.9.6/Makefile
--- clearsilver-0.9.3/Makefile	Sun Aug 31 12:10:37 2003
+++ clearsilver-0.9.6/Makefile	Mon Oct 20 14:19:29 2003
@@ -35,12 +35,14 @@
 	  fi; \
 	done
 
-install: all
+install: all man
 	./mkinstalldirs $(DESTDIR)$(cs_includedir)
 	./mkinstalldirs $(DESTDIR)$(bindir)
 	./mkinstalldirs $(DESTDIR)$(libdir)
+	./mkinstalldirs $(DESTDIR)$(mandir)/man3
 	$(INSTALL) -m 644 ClearSilver.h $(DESTDIR)$(cs_includedir)/
 	$(INSTALL) -m 644 cs_config.h $(DESTDIR)$(cs_includedir)/
+	$(INSTALL) -m 644 man/man3/*.3 $(DESTDIR)$(mandir)/man3/
 	@for mdir in $(SUBDIRS); do \
 	  if test -d $$mdir; then \
 	    if test -f $$mdir/Makefile.PL -a ! -f $$mdir/Makefile; then \
@@ -73,14 +75,14 @@
 man:
 	@mkdir -p man/man3
 	@for mdir in $(SUBDIRS); do \
-		scripts/document.py --owner "Neotonic, Inc." --outdir man/man3/ $$mdir/*.h; \
+		scripts/document.py --owner "ClearSilver" --outdir man/man3/ $$mdir/*.h; \
 	done
 
 .PHONY: hdf
 hdf:
 	@mkdir -p docs/hdf
 	@for mdir in $(SUBDIRS); do \
-		scripts/document.py --hdf --owner "Neotonic, Inc." --outdir docs/hdf/ $$mdir/*.h; \
+		scripts/document.py --hdf --owner "ClearSilver." --outdir docs/hdf/ $$mdir/*.h; \
 	done
 
 changelog:
@@ -105,8 +107,8 @@
 		mkdir -p $$mdir; \
 	done
 
-CS_DISTDIR = clearsilver-0.9.3
-CS_LABEL = CLEARSILVER-0_9_3
+CS_DISTDIR = clearsilver-0.9.6
+CS_LABEL = CLEARSILVER-0_9_6
 CS_FILES = README README.python INSTALL LICENSE CS_LICENSE rules.mk.in Makefile util cs cgi python scripts mod_ecs imd java-jni perl ruby dso csharp acconfig.h autogen.sh config.guess config.sub configure.in cs_config.h.in mkinstalldirs install-sh ClearSilver.h ports contrib
 cs_dist:
 	rm -rf $(CS_DISTDIR)
diff -Nbru clearsilver-0.9.3/cgi/cgi.c clearsilver-0.9.6/cgi/cgi.c
--- clearsilver-0.9.3/cgi/cgi.c	Mon Aug 18 13:41:08 2003
+++ clearsilver-0.9.6/cgi/cgi.c	Wed Oct  8 16:45:40 2003
@@ -209,6 +209,7 @@
 static int ExceptionsInit = 0;
 NERR_TYPE CGIFinished = -1;
 NERR_TYPE CGIUploadCancelled = -1;
+NERR_TYPE CGIParseNotHandled = -1;
 
 static NEOERR *_add_cgi_env_var (CGI *cgi, char *env, char *name)
 {
@@ -404,19 +405,19 @@
 
   if (query)
   {
-    k = strtok_r(query, "=", &l);
+    k = strtok_r(query, "&", &l);
     while (k)
     {
-      if (l != NULL && *l == '&')
+      v = strchr(k, '=');
+      if (v == NULL)
       {
-	l++;
 	v = "";
       }
       else
       {
-	v = strtok_r(NULL, "&", &l);
+	*v = '\0';
+	v++;
       }
-      if (v == NULL) v = "";
       snprintf(buf, sizeof(buf), "Query.%s", cgi_url_unescape(k));
       if (!(cgi->ignore_empty_form_vars && (*v == '\0')))
       {
@@ -451,7 +452,7 @@
 	err = hdf_set_value (cgi->hdf, buf, v);
 	if (err != STATUS_OK) break;
       }
-      k = strtok_r(NULL, "=", &l);
+      k = strtok_r(NULL, "&", &l);
     }
   }
   return nerr_pass(err);
@@ -645,14 +646,61 @@
   return STATUS_OK;
 }
 
+NEOERR *cgi_register_parse_cb(CGI *cgi, char *method, char *ctype, void *rock, 
+    CGI_PARSE_CB parse_cb)
+{
+  struct _cgi_parse_cb *my_pcb;
+
+  if (method == NULL || ctype == NULL)
+    return nerr_raise(NERR_ASSERT, "method and type must not be NULL to register cb");
+
+  my_pcb = (struct _cgi_parse_cb *) calloc(1, sizeof(struct _cgi_parse_cb));
+  if (my_pcb == NULL)
+    return nerr_raise(NERR_NOMEM, "Unable to allocate memory to register parse cb");
+
+  my_pcb->method = strdup(method);
+  my_pcb->ctype = strdup(ctype);
+  if (my_pcb->method == NULL || my_pcb->ctype == NULL)
+  {
+    free(my_pcb);
+    return nerr_raise(NERR_NOMEM, "Unable to allocate memory to register parse cb");
+  } 
+  if (!strcmp(my_pcb->method, "*"))
+    my_pcb->any_method = 1;
+  if (!strcmp(my_pcb->ctype, "*"))
+    my_pcb->any_ctype = 1;
+  my_pcb->rock = rock;
+  my_pcb->parse_cb = parse_cb;
+  my_pcb->next = cgi->parse_callbacks;
+  cgi->parse_callbacks = my_pcb;
+  return STATUS_OK;
+}
+
 NEOERR *cgi_parse (CGI *cgi)
 {
   NEOERR *err;
   char *method, *type;
+  struct _cgi_parse_cb *pcb;
 
 
   method = hdf_get_value (cgi->hdf, "CGI.RequestMethod", "GET");
   type = hdf_get_value (cgi->hdf, "CGI.ContentType", NULL);
+  /* Walk the registered parse callbacks for a matching one */
+  pcb = cgi->parse_callbacks;
+  while (pcb != NULL)
+  {
+    if ( (pcb->any_method || !strcasecmp(pcb->method, method)) &&
+	 (pcb->any_ctype || (type && !strcasecmp(pcb->ctype, type))) )
+    {
+      err = pcb->parse_cb(cgi, method, type, pcb->rock);
+      if (err && !nerr_handle(&err, CGIParseNotHandled))
+	return nerr_pass(err);
+    }
+    pcb = pcb->next;
+  }
+
+  /* Fallback to internal methods */
+
   if (!strcmp(method, "POST"))
   {
     if (type && !strcmp(type, "application/x-www-form-urlencoded"))
@@ -759,6 +807,8 @@
     if (err) return nerr_pass(err);
     err = nerr_register(&CGIUploadCancelled, "CGIUploadCancelled");
     if (err) return nerr_pass(err);
+    err = nerr_register(&CGIUploadCancelled, "CGIParseNotHandled");
+    if (err) return nerr_pass(err);
     ExceptionsInit = 1;
   }
 
@@ -943,7 +993,7 @@
 }
 #endif 
 
-static void html_ws_strip(STRING *str)
+void cgi_html_ws_strip(STRING *str)
 {
   int ws = 0;
   int seen_nonws = 0;
@@ -1072,7 +1122,6 @@
   do_ws_strip = hdf_get_int_value (cgi->hdf, "Config.WhiteSpaceStrip", 1);
 
   dis = ne_timef();
-  if (err != STATUS_OK) return nerr_pass (err);
   s = hdf_get_value (cgi->hdf, "cgiout.ContentType", "text/html");
   if (!strcasecmp(s, "text/html"))
     is_html = 1;
@@ -1085,8 +1134,9 @@
     if (err != STATUS_OK) return nerr_pass (err);
     if (s)
     {
+      char *next;
 
-      e = strtok (s, ",");
+      e = strtok_r (s, ",", &next);
       while (e && !use_deflate)
       {
 	if (strstr(e, "deflate") != NULL)
@@ -1096,7 +1146,7 @@
 	}
 	else if (strstr(e, "gzip") != NULL)
 	  use_gzip = 1;
-	e = strtok (NULL, ",");
+	e = strtok_r (NULL, ",", &next);
       }
       free (s);
     }
@@ -1158,7 +1208,7 @@
 
     if (do_ws_strip)
     {
-      html_ws_strip(str);
+      cgi_html_ws_strip(str);
     }
 
     if (do_debug)
diff -Nbru clearsilver-0.9.3/cgi/cgi.h clearsilver-0.9.6/cgi/cgi.h
--- clearsilver-0.9.3/cgi/cgi.h	Thu Jul 17 12:44:08 2003
+++ clearsilver-0.9.6/cgi/cgi.h	Wed Oct  8 16:45:40 2003
@@ -20,6 +20,7 @@
 
 extern NERR_TYPE CGIFinished;
 extern NERR_TYPE CGIUploadCancelled;
+extern NERR_TYPE CGIParseNotHandled;
 
 /* HACK: Set this value if you want to treat empty CGI Query variables as
  * non-existant.
@@ -29,6 +30,18 @@
 typedef struct _cgi CGI;
 
 typedef int (*UPLOAD_CB)(CGI *, int nread, int expected);
+typedef NEOERR* (*CGI_PARSE_CB)(CGI *, char *method, char *ctype, void *rock);
+
+struct _cgi_parse_cb
+{
+  char *method;
+  int any_method;
+  char *ctype;
+  int any_ctype;
+  void *rock;
+  CGI_PARSE_CB parse_cb;
+  struct _cgi_parse_cb *next;
+};
 
 struct _cgi
 {
@@ -42,6 +55,7 @@
 
   int data_expected;
   int data_read;
+  struct _cgi_parse_cb *parse_callbacks;
 
   /* For line oriented reading of form-data input.  Used during cgi_init
    * only */
@@ -75,10 +89,8 @@
  * Description: cgi_init initializes the ClearSilver CGI environment,
  *              including creating the HDF data set.  It will then import 
  *              the standard CGI environment variables into that dataset,
- *              will parse the QUERY_STRING into the data set, will
- *              parse form submission from application/x-url-encoded and
- *              multipart/form-data POSTs into the data set, and parse
- *              the HTTP_COOKIE into the dat set.  Note that if the
+ *              will parse the QUERY_STRING into the data set, and parse
+ *              the HTTP_COOKIE into the data set.  Note that if the
  *              var xdisplay is in the form data, cgi_init will attempt
  *              to validate the value and launch the configured debugger
  *              on the CGI program.  These variables have to be
@@ -94,14 +106,91 @@
  * Return: NERR_PARSE - parse error in CGI input
  *         NERR_NOMEM - unable to allocate memory
  *         NERR_NOT_FOUND - hdf_file doesn't exist
- *         NERR_IO - error reading HDF file or reading CGI stdin, or
- *                   writing data on multipart/form-data file submission
  */
 NEOERR *cgi_init (CGI **cgi, HDF *hdf);
 
+/*
+ * Function: cgi_parse - Parse incoming CGI data
+ * Description: We split cgi_init into two sections, one that parses
+ * 		just the basics, and the second is cgi_parse.  cgi_parse
+ * 		is responsible for parsing the entity body of the HTTP
+ * 		request.  This payload is typically only sent (expected)
+ * 		on POST/PUT requests, but generally this is called on
+ * 		all incoming requests.  This function walks the list of
+ * 		registered parse callbacks (see cgi_register_parse_cb),
+ * 		and if none of those matches or handles the request, it
+ * 		falls back to the builtin handlers: 
+ * 		  POST w/ application/x-www-form-urlencoded 
+ * 		  POST w/ application/form-data
+ * 		  PUT w/ any content type
+ * 		In general, if there is no Content-Length, then
+ * 		cgi_parse ignores the payload and doesn't raise an
+ * 		error.
+ * Input: cgi - a pointer to a CGI pointer
+ * Output: Either data populated into files and cgi->hdf, or whatever
+ *         other side effects of your own registered callbacks.
+ * Return: NERR_PARSE - parse error in CGI input
+ *         NERR_NOMEM - unable to allocate memory
+ *         NERR_NOT_FOUND - hdf_file doesn't exist
+ *         NERR_IO - error reading HDF file or reading CGI stdin, or
+ *                   writing data on multipart/form-data file submission
+ *         Anything else you raise.
+ */
 NEOERR *cgi_parse (CGI *cgi);
 
 /*
+ * Function: cgi_register_parse_cb - Register a parse callback
+ * Description: The ClearSilver CGI Kit has built-in functionality to handle 
+ *              the following methods:
+ *              GET -> doesn't have any data except query string, which
+ *                is processed for all methods
+ *              POST w/ application/x-www-form-urlencoded
+ *              POST w/ multipart/form-data
+ *                processed as RFC2388 data into files and HDF (see
+ *                cgi_filehandle())
+ *              PUT (any type)
+ *                The entire data chunk is stored as a file, with meta
+ *                data in HDF (similar to single files in RFC2388). 
+ *                The data is accessible via cgi_filehandle with NULL
+ *                for name.
+ *              To handle other methods/content types, you have to
+ *              register your own parse function.  This isn't necessary
+ *              if you aren't expecting any data, and technically HTTP
+ *              only allows data on PUT/POST requests (and presumably
+ *              user defined methods).  In particular, if you want to
+ *              implement XML-RPC or SOAP, you'll have to register a
+ *              callback here to grab the XML data chunk.  Usually
+ *              you'll want to register POST w/ application/xml or POST
+ *              w/ text/xml (you either need to register both or
+ *              register POST w/ * and check the ctype yourself,
+ *              remember to nerr_raise(CGIParseNotHandled) if you aren't
+ *              handling the POST).
+ *              In general, your callback should:
+ *                Find out how much data is available:
+ *                 l = hdf_get_value (cgi->hdf, "CGI.ContentLength", NULL); 
+ *                 len = atoi(l);
+ *                And read/handle all of the data using cgiwrap_read.
+ *                See the builtin handlers for how this is done.  Note
+ *                that cgiwrap_read is not guarunteed to return all of
+ *                the data you request (just like fread(3)) since it
+ *                might be reading of a socket.  Sorry.
+ *                You should be careful when reading the data to watch
+ *                for short reads (ie, end of file) and cases where the
+ *                client sends you data ad infinitum.
+ * Input: cgi - a CGI struct
+ *        method - the HTTP method you want to handle, or * for all
+ *        ctype - the HTTP Content-Type you want to handle, or * for all
+ *        rock - opaque data that we'll pass to your call back
+ * Output: None
+ * Return: CGIParseNotHandled if your callback doesn't want to handle
+ *         this.  This causes cgi_parse to continue walking the list of
+ *         callbacks.
+ *
+ */
+NEOERR *cgi_register_parse_cb(CGI *cgi, char *method, char *ctype, void *rock, 
+    CGI_PARSE_CB parse_cb);
+
+/*
  * Function: cgi_destroy - deallocate the data associated with a CGI
  * Description: cgi_destroy will destroy all the data associated with a
  *              CGI, which mostly means the associated HDF and removal
@@ -356,6 +445,13 @@
  * Return: NERR_IO
  */
 NEOERR *cgi_cookie_clear (CGI *cgi, char *name, char *domain, char *path);
+
+/* not documented *yet* */
+NEOERR *cgi_text_html_strfunc(unsigned char *str, unsigned char **ret);
+NEOERR *cgi_html_strip_strfunc(unsigned char *str, unsigned char **ret);
+NEOERR *cgi_html_escape_strfunc(unsigned char *str, unsigned char **ret);
+NEOERR *cgi_js_escape (unsigned char *buf, unsigned char **esc);
+void cgi_html_ws_strip(STRING *str);
 
 /* internal use only */
 NEOERR * parse_rfc2388 (CGI *cgi);
diff -Nbru clearsilver-0.9.3/cgi/html.c clearsilver-0.9.6/cgi/html.c
--- clearsilver-0.9.3/cgi/html.c	Wed Apr  2 15:07:28 2003
+++ clearsilver-0.9.6/cgi/html.c	Thu Sep 11 19:02:40 2003
@@ -19,6 +19,7 @@
 #include "util/neo_err.h"
 #include "util/neo_str.h"
 #include "html.h"
+#include "cgi.h"
 
 static int has_space_formatting(unsigned char *src, int slen)
 {
@@ -98,7 +99,7 @@
 static char *EmailRe = "[^][@:;<>\\\"()[:space:][:cntrl:]]+@[-+a-zA-Z0-9]+\\.[-+a-zA-Z0-9\\.]+[-+a-zA-Z0-9]";
 static char *URLRe = "((http|https|ftp|mailto):(//)?[^[:space:]>\"\t]*|www\\.[-a-z0-9\\.]+)[^[:space:];\t\">]*";
 
-static NEOERR *split_and_convert (unsigned char *src, int slen, STRING *out, int newlines, int space_convert)
+static NEOERR *split_and_convert (unsigned char *src, int slen, STRING *out, HTML_CONVERT_OPTS *opts)
 {
   NEOERR *err = STATUS_OK;
   static int compiled = 0;
@@ -282,7 +283,7 @@
 	  x = ptr - src;
 	  if (src[x] == ' ')
 	  {
-	    if (space_convert)
+	    if (opts->space_convert)
 	    {
 	      spaces++;
 	    }
@@ -311,10 +312,10 @@
 	    else if (src[x] == '>')
 	      err = string_append (out, "&gt;");
 	    else if (src[x] == '\n')
-	      if (newlines) 
-		err = string_append (out, "<BR>");
+	      if (opts->newlines_convert) 
+		err = string_append (out, "<BR>\n");
 	      else if (x && src[x-1] == '\n')
-		err = string_append (out, "<P>");
+		err = string_append (out, "<P>\n");
 	      else 
 		err = string_append_char (out, '\n');
 	    else if (src[x] != '\r')
@@ -360,8 +361,70 @@
         unsigned 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 ");
 	if (err != STATUS_OK) break;
+	if (opts->url_class)
+	{
+	    err = string_appendf (out, "class=%s ", opts->url_class);
+	    if (err) break;
+	} 
+	if (opts->url_target)
+	{
+	  err = string_appendf (out, "target=\"%s\" ", opts->url_target);
+	  if (err) break;
+	}
+	err = string_append(out, "href=\"");
+	if (err) break;
+	if (opts->bounce_url)
+	{
+	  unsigned char *url, *esc_url, *new_url;
+	  int url_len;
+	  if (!strncasecmp(src + x, "www.", 4))
+	  {
+	    url_len = 7 + parts[i].end - x - suffix;
+	    url = (char *) malloc(url_len+1);
+	    if (url == NULL)
+	    {
+	      err = nerr_raise(NERR_NOMEM, 
+		  "Unable to allocate memory to convert url");
+	      break;
+	    }
+	    strcpy(url, "http://");
+	    strncat(url, src + x, parts[i].end - x - suffix);
+	  }
+	  else
+	  {
+	    url_len = parts[i].end - x - suffix;
+	    url = (char *) malloc(url_len+1);
+	    if (url == NULL)
+	    {
+	      err = nerr_raise(NERR_NOMEM, 
+		  "Unable to allocate memory to convert url");
+	      break;
+	    }
+	    strncpy(url, src + x, parts[i].end - x - suffix);
+	    url[url_len] = '\0';
+	  }
+	  err = cgi_url_escape(url, &esc_url);
+	  free(url);
+	  if (err) {
+	    free(esc_url);
+	    break;
+	  }
+
+	  new_url = sprintf_alloc(opts->bounce_url, esc_url);
+	  free(esc_url);
+	  if (new_url == NULL)
+	  {
+	    err = nerr_raise(NERR_NOMEM, "Unable to allocate memory to convert url");
+	    break;
+	  }
+	  err = string_append (out, new_url);
+	  free(new_url);
+	  if (err) break;
+	}
+	else
+	{
 	if (!strncasecmp(src + x, "www.", 4))
 	{
 	  err = string_append (out, "http://");
@@ -369,6 +432,7 @@
 	}
 	err = string_appendn (out, src + x, parts[i].end - x - suffix);
 	if (err != STATUS_OK) break;
+	}
 	err = string_append (out, "\">");
 	if (err != STATUS_OK) break;
 	err = html_escape_alloc(src + x, parts[i].end - x - suffix, &esc);
@@ -384,8 +448,15 @@
       }
       else /* type == SC_TYPE_EMAIL */
       {
-	err = string_append (out, "<a href=\"mailto:");
+	err = string_append (out, "<a ");
 	if (err != STATUS_OK) break;
+	if (opts->mailto_class)
+	{
+	    err = string_appendf (out, "class=%s ", opts->mailto_class);
+	    if (err) break;
+	} 
+	err = string_append(out, "href=\"mailto:");
+	if (err) break;
 	err = string_appendn (out, src + x, parts[i].end - x);
 	if (err != STATUS_OK) break;
 	err = string_append (out, "\">");
@@ -448,21 +519,46 @@
 
 NEOERR *convert_text_html_alloc (unsigned char *src, int slen, unsigned char **out)
 {
+    return nerr_pass(convert_text_html_alloc_options(src, slen, out, NULL));
+}
+
+NEOERR *convert_text_html_alloc_options (unsigned char *src, int slen, unsigned char **out, HTML_CONVERT_OPTS *opts)
+{
   NEOERR *err;
   STRING out_s;
-  int formatting;
+  int formatting = 0;
+  HTML_CONVERT_OPTS my_opts;
 
   string_init(&out_s);
 
+  if (opts == NULL)
+  {
+    opts = &my_opts;
+    opts->bounce_url = NULL;
+    opts->url_class = NULL;
+    opts->url_target = "_blank";
+    opts->mailto_class = NULL;
+    opts->long_lines = 0;
+    opts->space_convert = 0;
+    opts->newlines_convert = 1;
+    opts->longline_width = 75; /* This hasn't been used in a while, actually */
+    opts->check_ascii_art = 1;
+  }
+
   do
   {
+    if  (opts->check_ascii_art)
+    {
     formatting = has_space_formatting (src, slen);
+	if (formatting) opts->space_convert = 1;
+    }
     if (formatting == 2)
     {
       /* Do <pre> formatting */
+      opts->newlines_convert = 1;
       err = string_append (&out_s, "<tt>");
       if (err != STATUS_OK) break;
-      err = split_and_convert(src, slen, &out_s, 1, 1);
+      err = split_and_convert(src, slen, &out_s, opts);
       if (err != STATUS_OK) break;
       err = string_append (&out_s, "</tt>");
       if (err != STATUS_OK) break;
@@ -472,7 +568,7 @@
     else
     {
       /* int nl = has_long_lines (src, slen); */
-      err = split_and_convert(src, slen, &out_s, 1, formatting);
+      err = split_and_convert(src, slen, &out_s, opts);
     }
   } while (0);
   if (err != STATUS_OK) 
diff -Nbru clearsilver-0.9.3/cgi/html.h clearsilver-0.9.6/cgi/html.h
--- clearsilver-0.9.3/cgi/html.h	Fri Mar 14 14:58:02 2003
+++ clearsilver-0.9.6/cgi/html.h	Thu Sep 11 18:33:02 2003
@@ -17,7 +17,20 @@
 
 __BEGIN_DECLS
 
+typedef struct _text_html_opts {
+    char *bounce_url;
+    char *url_class;
+    char *url_target;
+    char *mailto_class;
+    int long_lines;
+    int space_convert;
+    int newlines_convert;
+    int longline_width;
+    int check_ascii_art;
+} HTML_CONVERT_OPTS;
+
 NEOERR *convert_text_html_alloc (unsigned char *src, int slen, unsigned char **out);
+NEOERR *convert_text_html_alloc_options (unsigned char *src, int slen, unsigned char **out, HTML_CONVERT_OPTS *opts);
 NEOERR *html_escape_alloc (unsigned char *src, int slen, unsigned char **out);
 NEOERR *html_strip_alloc(unsigned char *src, int slen, unsigned char **out);
 
diff -Nbru clearsilver-0.9.3/configure.in clearsilver-0.9.6/configure.in
--- clearsilver-0.9.3/configure.in	Sun Aug 31 10:33:24 2003
+++ clearsilver-0.9.6/configure.in	Mon Sep 22 19:09:38 2003
@@ -105,13 +105,13 @@
       cs_cv_need_reentrant=yes
       AC_MSG_RESULT(yes)],[
       cs_cv_missing=yes
-      AC_MSG_RESULT(no)])])])
+      AC_MSG_RESULT(no)])])], [cs_cv_missing=yes])
 
 AC_CHECK_FUNCS(gmtime_r, [
   AC_MSG_CHECKING(whether gmtime_r is declared)
   AC_EGREP_CPP(gmtime_r,[
 #include <time.h>],[
-    AC_DEFINE(HAVE_LOCALTIME_R)
+    AC_DEFINE(HAVE_GMTIME_R)
     AC_MSG_RESULT(yes)],[
     AC_MSG_RESULT(no)
     AC_MSG_CHECKING(whether gmtime_r with -D_REENTRANT is declared)
@@ -121,13 +121,13 @@
       cs_cv_need_reentrant=yes
       AC_MSG_RESULT(yes)],[
       cs_cv_missing=yes
-      AC_MSG_RESULT(no)])])])
+      AC_MSG_RESULT(no)])])], [cs_cv_missing=yes])
 
 AC_CHECK_FUNCS(strtok_r, [
   AC_MSG_CHECKING(whether strtok_r is declared)
   AC_EGREP_CPP(strtok_r,[
 #include <string.h>],[
-    AC_DEFINE(HAVE_LOCALTIME_R)
+    AC_DEFINE(HAVE_STRTOK_R)
     AC_MSG_RESULT(yes)],[
     AC_MSG_RESULT(no)
     AC_MSG_CHECKING(whether strtok_r with -D_REENTRANT is declared)
@@ -137,7 +137,7 @@
       cs_cv_need_reentrant=yes
       AC_MSG_RESULT(yes)],[
       cs_cv_missing=yes
-      AC_MSG_RESULT(no)])])])
+      AC_MSG_RESULT(no)])])], [cs_cv_missing=yes])
 
 AC_CHECK_FUNC(mkstemp, [AC_DEFINE(HAVE_MKSTEMP)], [cs_cv_missing=yes])
 if test $cs_cv_missing = yes; then
diff -Nbru clearsilver-0.9.3/cs_config.h.in clearsilver-0.9.6/cs_config.h.in
--- clearsilver-0.9.3/cs_config.h.in	Fri Aug  8 23:55:25 2003
+++ clearsilver-0.9.6/cs_config.h.in	Mon Sep 22 20:49:34 2003
@@ -78,8 +78,14 @@
 /* Does your system have the vsnprintf() call? */
 #undef HAVE_VSNPRINTF
 
+/* Does your system have the strtok_r() call? */
+#undef HAVE_STRTOK_R
+
 /* Does your system have the localtime_r() call? */
 #undef HAVE_LOCALTIME_R
+
+/* Does your system have the gmtime_r() call? */
+#undef HAVE_GMTIME_R
 
 /* Does your system have the mkstemp() call? */
 #undef HAVE_MKSTEMP
diff -Nbru clearsilver-0.9.3/java-jni/Makefile clearsilver-0.9.6/java-jni/Makefile
--- clearsilver-0.9.3/java-jni/Makefile	Mon Aug 18 13:09:20 2003
+++ clearsilver-0.9.6/java-jni/Makefile	Wed Sep  3 15:38:47 2003
@@ -51,7 +51,7 @@
 	LD_LIBRARY_PATH=$(NEOTONIC_ROOT)/java-jni; export LD_LIBRARY_PATH; \
 	CLASSPATH=$(NEO_UTIL_JAVA_JAR):.; export CLASSPATH; \
 	$(JAVA_PATH)/bin/java CSTest > javatest.out; \
-	diff -brief javatest.out javatest.gold 2>%1 > /dev/null; \
+	diff -brief javatest.out javatest.gold  > /dev/null 2>&1; \
 	return_code=$$?; \
 	if [ $$return_code -ne 0 ]; then \
 	  diff javatest.out javatest.gold > javatest.err; \
diff -Nbru clearsilver-0.9.3/ports/rpm/clearsilver.spec clearsilver-0.9.6/ports/rpm/clearsilver.spec
--- clearsilver-0.9.3/ports/rpm/clearsilver.spec	Sun Aug 31 12:07:39 2003
+++ clearsilver-0.9.6/ports/rpm/clearsilver.spec	Mon Oct 20 14:19:30 2003
@@ -53,11 +53,11 @@
 
 Summary: Neotonic ClearSilver
 Name: clearsilver
-Version: 0.9.3
+Version: 0.9.6
 Release: 1
 Copyright: Open Source - Neotonic ClearSilver License (Apache 1.1 based)
 Group: Development/Libraries
-Source: http://www.clearsilver.net/downloads/clearsilver-0.9.3.tar.gz
+Source: http://www.clearsilver.net/downloads/clearsilver-0.9.5.tar.gz
 URL: http://www.clearsilver.net/
 Vendor: Neotonic Software Corporation, Inc.
 Packager: Brandon Long <blong@neotonic.com>
@@ -188,6 +188,7 @@
 %{__prefix}/lib/libneo_utl.a
 %{__prefix}/bin/static.cgi
 %{__prefix}/bin/cstest
+%{__prefix}/man/man3
 
 %if %{with_python_subpackage}
 %files python
diff -Nbru clearsilver-0.9.3/python/Makefile clearsilver-0.9.6/python/Makefile
--- clearsilver-0.9.3/python/Makefile	Thu Jul 24 11:36:50 2003
+++ clearsilver-0.9.6/python/Makefile	Thu Sep 11 19:02:24 2003
@@ -23,7 +23,7 @@
 
 all: $(TARGETS)
 
-$(NEO_UTIL_SO): setup.py
+$(NEO_UTIL_SO): setup.py $(NEO_UTIL_SRC) $(DEP_LIBS)
 	$(PYTHON) setup.py build_ext --inplace
 
 OLD_NEO_UTIL_SO:
diff -Nbru clearsilver-0.9.3/python/examples/base/hdfhelp.py clearsilver-0.9.6/python/examples/base/hdfhelp.py
--- clearsilver-0.9.3/python/examples/base/hdfhelp.py	Wed Aug 20 22:57:16 2003
+++ clearsilver-0.9.6/python/examples/base/hdfhelp.py	Sat Sep 13 03:25:00 2003
@@ -33,7 +33,7 @@
 #      return hdfhelp.HdfItemList
 #
 
-import string
+import string, os
 import neo_cgi
 import neo_cs
 import neo_util
@@ -85,7 +85,10 @@
         return time.strftime("%m/%d/%Y %I:%M%p",then_tuple)
 
 class HdfRow(odb.Row):
-    def hdfExport(self,prefix,hdf_dataset,skip_fields = None, translate_dict = None):
+    def hdfExport(self, prefix, hdf_dataset, *extra, **extranamed):
+        skip_fields = extranamed.get("skip_fields", None)
+        translate_dict = extranamed.get("translate_dict", None)
+        tz = extranamed.get("tz", "US/Pacific")
 
         for col_name,value in self.items():
             if skip_fields and (col_name in skip_fields):
@@ -96,27 +99,53 @@
                 col_type = odb.kVarString
                 col_options = {}
             
-	    if (col_name != "value") and (value is not None):
+	    if (value is not None):
+                if col_options.get("no_export",0): continue
 		if type(value) in [ type(0), type(0L) ]:
 		    hdf_dataset.setValue(prefix + "." + col_name,"%d" % value)
+                elif type(value) == type(1.0):
+                    if int(value) == value:
+                        hdf_dataset.setValue(prefix + "." + col_name,"%d" % value)
+                    else:
+                        hdf_dataset.setValue(prefix + "." + col_name,"%0.2f" % value)
 		else:
+                    if col_type == odb.kReal:
+                        log("why are we here with this value: %s" % value)
                     if translate_dict:
                         for k,v in translate_dict.items():
                             value = string.replace(value,k,v)
-		    hdf_dataset.setValue(prefix + "." + col_name,neo_cgi.htmlEscape(value))
+		    hdf_dataset.setValue(prefix + "." + col_name,neo_cgi.htmlEscape(str(value)))
                 if col_options.get("int_date",0):
                     hdf_dataset.setValue(prefix + "." + col_name + ".string",renderDate(value))
                     hdf_dataset.setValue(prefix + "." + col_name + ".day_string",renderDate(value,day=1))
+                    if value: neo_cgi.exportDate(hdf_dataset, "%s.%s" % (prefix, col_name), tz, value)
 
-
+		if col_options.has_key("enum_values"):
+		    enum = col_options["enum_values"]
+		    hdf_dataset.setValue(prefix + "." + col_name + ".enum",
+					 str(enum.get(value,'')))
 
 class HdfItemList(UserList.UserList):
     def hdfExport(self,prefix,hdf_dataset,*extra,**extranamed):
+        export_by = extranamed.get("export_by", None)
 	n = 0
 	for row in self:
+            if export_by is not None:
+                n = row[export_by]
 	    row.hdfExport("%s.%d" % (prefix,n),hdf_dataset,*extra,**extranamed)
 	    n = n + 1
 
+def setList(hdf, prefix, lst):
+    hdf.setValue(prefix+".0", str(len(lst)))
+    for n in range(len(lst)):
+        hdf.setValue(prefix+".%d" %(n+1), lst[n]);
+
+def getList(hdf, name):
+    lst = []
+    for n in range(hdf.getIntValue(name,0)):
+        lst.append(hdf.getValue(name+".%d" %(n+1), ""))
+
+    return lst
 
 def eval_cs(hdf,a_cs_string):
     cs = neo_cs.CS(hdf)
diff -Nbru clearsilver-0.9.3/python/examples/base/odb.py clearsilver-0.9.6/python/examples/base/odb.py
--- clearsilver-0.9.3/python/examples/base/odb.py	Wed Aug 20 22:57:16 2003
+++ clearsilver-0.9.6/python/examples/base/odb.py	Thu Sep 11 03:09:47 2003
@@ -51,7 +51,6 @@
 #    list_rows = tbl.fetchRows( ('login', "foo") )
 #
 
-import tstart
 
 import string
 import sys, zlib
@@ -361,7 +360,7 @@
 
     def d_addColumn(self,col_name,ctype,size=None,primarykey = 0, notnull = 0,indexed=0,
 		    default=None,unique=0,autoincrement=0,safeupdate=0,enum_values = None,
-                    relations=None,compress_ok=0,int_date=0):
+                    relations=None,compress_ok=0,int_date=0,no_export=0):
 
 	self.__checkColumnLock()
 
@@ -381,6 +380,8 @@
 	    options['notnull']       = notnull
 	if size:
 	    options['size']          = size
+        if no_export:
+            options['no_export']     = no_export
         if int_date:
             if ctype != kInteger:
                 raise eInvalidData, "can't flag columns int_date unless they are kInteger"
diff -Nbru clearsilver-0.9.3/python/examples/base/wordwrap.py clearsilver-0.9.6/python/examples/base/wordwrap.py
--- clearsilver-0.9.3/python/examples/base/wordwrap.py	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.6/python/examples/base/wordwrap.py	Fri Sep 12 16:05:57 2003
@@ -0,0 +1,50 @@
+"""
+WordWrapping
+"""
+
+
+import os, sys, string, time, getopt
+import re
+
+def WordWrap(text, cols=70, detect_paragraphs = 0, is_header = 0):
+  text =  string.replace(text,"\r\n", "\n") # remove CRLF
+  def nlrepl(matchobj):
+    if matchobj.group(1) != ' ' and matchobj.group(2) != ' ':
+      repl_with = ' '
+    else:
+      repl_with = ''
+
+    return matchobj.group(1) + repl_with + matchobj.group(2)
+
+  if detect_paragraphs:
+    text = re.sub("([^\n])\n([^\n])",nlrepl,text)
+
+  body = []
+  i = 0
+  j = 0
+  ltext = len(text)
+
+  while i<ltext:
+    if i+cols < ltext:
+      r = string.find(text, "\n", i, i+cols)
+      j = r
+      if r == -1:
+        j = string.rfind(text, " ", i, i+cols)
+        if j == -1:
+          r = string.find(text, "\n", i+cols)
+          if r == -1: r = ltext
+	  j = string.find(text, " ", i+cols)
+	  if j == -1: j = ltext
+          j = min(j, r)
+    else:
+      j = ltext
+
+    body.append(string.strip(text[i:j]))
+    i = j+1
+
+  if is_header:
+    body = string.join(body, "\n ")
+  else:
+    body = string.join(body, "\n")
+  return body
+
diff -Nbru clearsilver-0.9.3/python/neo_cgi.c clearsilver-0.9.6/python/neo_cgi.c
--- clearsilver-0.9.3/python/neo_cgi.c	Thu Jul 17 12:44:09 2003
+++ clearsilver-0.9.6/python/neo_cgi.c	Thu Sep 11 19:03:11 2003
@@ -404,17 +404,34 @@
   return rv;
 }
 
-static PyObject * p_text_html (PyObject *self, PyObject *args)
+static PyObject * p_text_html (PyObject *self, PyObject *args, PyObject *keywds)
 {
   unsigned char *s, *esc;
   NEOERR *err;
   PyObject *rv;
   int len;
+  HTML_CONVERT_OPTS opts;
+  static char *kwlist[] = {"text", "bounce_url", "url_class", "url_target", "mailto_class", "long_lines", "space_convert", "newlines_convert", "longline_width", "check_ascii_art", NULL};
 
-  if (!PyArg_ParseTuple(args, "s#:text2html(str)", &s, &len))
+  /* These defaults all come from the old version */
+  opts.bounce_url = NULL;
+  opts.url_class = NULL;
+  opts.url_target = "_blank";
+  opts.mailto_class = NULL;
+  opts.long_lines = 0;
+  opts.space_convert = 0;
+  opts.newlines_convert = 1;
+  opts.longline_width = 75; /* This hasn't been used in a while, actually */
+  opts.check_ascii_art = 1;
+
+  if (!PyArg_ParseTupleAndKeywords(args, keywds, "s#|ssssiiiii:text2html(text)", 
+	kwlist, 
+	&s, &len, &(opts.bounce_url), &(opts.url_class), &(opts.url_target), 
+	&(opts.mailto_class), &(opts.long_lines), &(opts.space_convert), 
+	&(opts.newlines_convert), &(opts.longline_width), &(opts.check_ascii_art)))
     return NULL;
 
-  err = convert_text_html_alloc (s, len, &esc);
+  err = convert_text_html_alloc_options (s, len, &esc, &opts);
   if (err) return p_neo_error (err);
   rv = Py_BuildValue ("s", esc);
   free (esc);
@@ -884,7 +901,7 @@
   {"urlUnescape", p_cgi_url_unescape, METH_VARARGS, NULL},
   {"htmlEscape", p_html_escape, METH_VARARGS, NULL},
   {"htmlStrip", p_html_strip, METH_VARARGS, NULL},
-  {"text2html", p_text_html, METH_VARARGS, NULL},
+  {"text2html", (PyCFunction)p_text_html, METH_VARARGS|METH_KEYWORDS, NULL},
   {"cgiWrap", cgiwrap, METH_VARARGS, cgiwrap_doc},
   {"IgnoreEmptyFormVars", p_ignore, METH_VARARGS, NULL},
   {"exportDate", p_export_date, METH_VARARGS, NULL},
diff -Nbru clearsilver-0.9.3/ruby/Makefile clearsilver-0.9.6/ruby/Makefile
--- clearsilver-0.9.3/ruby/Makefile	Mon Aug 11 15:03:56 2003
+++ clearsilver-0.9.6/ruby/Makefile	Tue Sep 16 16:49:23 2003
@@ -9,10 +9,10 @@
 all: config.save ext/hdf/hdf.so testrb
 
 config.save: install.rb
-	$(RUBY) install.rb config -- --with-hdf-include=../../.. --with-hdf-lib=../../../libs
+	$(RUBY) install.rb config -- --with-hdf-include=../../.. --with-hdf-lib=../../../libs --make-prog=$(MAKE)
 
 ext/hdf/Makefile:
-	$(RUBY) install.rb config -- --with-hdf-include=../../.. --with-hdf-lib=../../../libs
+	$(RUBY) install.rb config -- --with-hdf-include=../../.. --with-hdf-lib=../../../libs --make-prog=$(MAKE)
 
 ext/hdf/hdf.so: config.save
 	$(RUBY) install.rb setup 
@@ -26,7 +26,7 @@
 	@failed=0; \
 	rm -f hdftest.out; \
 	$(RUBY) -Ilib -Iext/hdf test/hdftest.rb > hdftest.out; \
-	diff -brief hdftest.out hdftest.gold 2>%1 > /dev/null; \
+	diff --brief hdftest.out hdftest.gold > /dev/null 2>&1; \
 	return_code=$$?; \
 	if [ $$return_code -ne 0 ]; then \
 	  diff hdftest.out hdftest.gold > hdftest.err; \
diff -Nbru clearsilver-0.9.3/util/filter.h clearsilver-0.9.6/util/filter.h
--- clearsilver-0.9.3/util/filter.h	Fri May 10 17:41:07 2002
+++ clearsilver-0.9.6/util/filter.h	Tue Sep 30 02:07:12 2003
@@ -19,8 +19,70 @@
 #include "util/neo_misc.h"
 #include "util/neo_err.h"
 
+/*
+ * Function: filter_wait - wrap waitpid to decode the exitcode and why
+ *           your filter quit
+ * Description: filter_wait wraps the waitpid call and raises an error
+ *              (with description) if the call failed.  Note that if the
+ *              ask for the exitcode and the process exited with a code
+ *              other than zero, we don't raise an error.  If you don't
+ *              ask for the exitcode, and it is non-zero, we raise an
+ *              error
+ * Input: pid -> the process identifier to wait for
+ *        options -> the options to pass to waitpid (see wait(2))
+ * Output: exitcode -> the exitcode if the process existed normally
+ * Returns: NERR_SYSTEM, NERR_ASSERT
+ */
 NEOERR *filter_wait(pid_t pid, int options, int *exitcode);
+
+/*
+ * Function: filter_create_fd - Create a sub process and return the
+ * requested pipes
+ * Description: filter_create_fd and filter_create_fp are what popen
+ *              should have been: a mechanism to create sub processes
+ *              and have pipes to all their input/output.  The concept
+ *              was taken from mutt, though python has something similar
+ *              with popen3/popen4.  You control which pipes the
+ *              function returns by the fdin/fdout/fderr arguments.  A
+ *              NULL value means "don't create a pipe", a pointer to an
+ *              int will cause the pipes to be created and the value
+ *              of the file descriptor stored in the int.  You will have
+ *              to close(2) the file descriptors yourself.
+ * Input: cmd -> the sub command to execute.  Will be executed with
+ *               /bin/sh -c
+ *        fdin -> pointer to return the stdin pipe, or NULL if you don't
+ *                want the stdin pipe
+ *        fdout -> pointer to return the stdout pipe, or NULL if you don't
+ *                 want the stdout pipe
+ *        fderr -> pointer to return the stderr pipe, or NULL if you don't
+ *                 want the stderr pipe
+ * Output: fdin -> the stdin file descriptor of the sub process
+ *         fdout -> the stdout file descriptor of the sub process
+ *         fderr -> the stderr file descriptor of the sub process
+ *         pid -> the pid of the sub process
+ * Returns: NERR_SYSTEM
+ */
 NEOERR *filter_create_fd(char *cmd, int *fdin, int *fdout, int *fderr, pid_t *pid);
+
+/*
+ * Function: filter_create_fp - similar to filter_create_fd except with
+ *           buffered FILE* 
+ * Description: filter_create_fp is identical to filter_create_fd,
+ *              except each of the pipes is wrapped in a buffered stdio FILE 
+ * Input: cmd -> the sub command to execute.  Will be executed with
+ *               /bin/sh -c
+ *        in -> pointer to return the stdin pipe, or NULL if you don't
+ *              want the stdin pipe
+ *        out -> pointer to return the stdout pipe, or NULL if you don't
+ *               want the stdout pipe
+ *        err -> pointer to return the stderr pipe, or NULL if you don't
+ *                 want the stderr pipe
+ * Output: in -> the stdin FILE of the sub process
+ *         out -> the stdout FILE of the sub process
+ *         err -> the stderr FILE of the sub process
+ *         pid -> the pid of the sub process
+ * Returns: NERR_SYSTEM, NERR_IO
+ */
 NEOERR *filter_create_fp(char *cmd, FILE **in, FILE **out, FILE **err, pid_t *pid);
 
 __END_DECLS
diff -Nbru clearsilver-0.9.3/util/neo_err.c clearsilver-0.9.6/util/neo_err.c
--- clearsilver-0.9.3/util/neo_err.c	Wed Apr  2 15:07:36 2003
+++ clearsilver-0.9.6/util/neo_err.c	Tue Sep 16 16:37:42 2003
@@ -37,11 +37,18 @@
 static ULIST *Errors = NULL;
 static int Inited = 0;
 
+/* Set this to 1 to enable non-thread safe re-use of NEOERR data
+ * structures.  This was a premature performance optimization that isn't
+ * thread safe, if we want it thread safe we need to add mutex code...
+ * which has its own performance penalties...
+ */
+static int UseFreeList = 0;
+
 static NEOERR *_err_alloc(void)
 {
   NEOERR *err;
 
-  if (FreeList == NULL)
+  if (!UseFreeList || FreeList == NULL)
   {
     err = (NEOERR *)calloc (1, sizeof (NEOERR));
     if (err == NULL)
@@ -67,10 +74,17 @@
     return 0;
   if (err->next != NULL)
     _err_free(err->next);
+  if (UseFreeList)
+  {
   err->next = FreeList;
   FreeList = err;
   err->flags = 0;
   err->desc[0] = '\0';
+  }
+  else
+  {
+    free(err);
+  }
   return 0;
 }
 
diff -Nbru clearsilver-0.9.3/util/neo_err.h clearsilver-0.9.6/util/neo_err.h
--- clearsilver-0.9.3/util/neo_err.h	Wed Apr  2 15:07:36 2003
+++ clearsilver-0.9.6/util/neo_err.h	Wed Sep 24 16:50:39 2003
@@ -109,7 +109,7 @@
  */
 void nerr_log_error (NEOERR *err);
 
-#include "neo_str.h"
+#include "util/neo_str.h"
 void nerr_error_string (NEOERR *err, STRING *str);
 void nerr_error_traceback (NEOERR *err, STRING *str);
 
diff -Nbru clearsilver-0.9.3/util/neo_hdf.c clearsilver-0.9.6/util/neo_hdf.c
--- clearsilver-0.9.3/util/neo_hdf.c	Tue Aug 26 17:55:49 2003
+++ clearsilver-0.9.6/util/neo_hdf.c	Mon Oct 20 14:18:58 2003
@@ -17,6 +17,7 @@
 #include <ctype.h>
 #include <errno.h>
 #include <limits.h>
+#include <stdarg.h>
 #include <sys/stat.h>
 #include "neo_misc.h"
 #include "neo_err.h"
@@ -298,6 +299,33 @@
   return defval;
 }
 
+char* hdf_get_valuevf (HDF *hdf, char *namefmt, va_list ap) 
+{
+  HDF *node;
+  char *name;
+
+  name = vsprintf_alloc(namefmt, ap);
+  if (name == NULL) return NULL;
+  if ((_walk_hdf(hdf, name, &node) == 0) && (node->value != NULL))
+  {
+    free(name);
+    return node->value;
+  }
+  free(name);
+  return NULL;
+}
+
+char* hdf_get_valuef (HDF *hdf, char *namefmt, ...)
+{
+  char *val;
+  va_list ap;
+
+  va_start(ap, namefmt);
+  val = hdf_get_valuevf(hdf, namefmt, ap);
+  va_end(ap);
+  return val;
+}
+
 NEOERR* hdf_get_copy (HDF *hdf, char *name, char **value, char *defval)
 {
   HDF *node;
@@ -773,6 +801,41 @@
     return nerr_pass(_set_value (hdf, dest, node->value, 0, 0, 0, NULL, NULL));
   }
   return nerr_raise (NERR_NOT_FOUND, "Unable to find %s", src);
+}
+
+NEOERR* hdf_set_valuevf (HDF *hdf, char *fmt, va_list ap)
+{
+  NEOERR *err;
+  char *k;
+  char *v;
+
+  k = vsprintf_alloc(fmt, ap);
+  if (k == NULL)
+  {
+    return nerr_raise(NERR_NOMEM, "Unable to allocate memory for format string");
+  }
+  v = strchr(k, '=');
+  if (v == NULL)
+  {
+    err = nerr_raise(NERR_ASSERT, "No equals found: %s", k);
+    free(k);
+    return err;
+  }
+  *v++ = '\0';
+  err = hdf_set_value(hdf, k, v);
+  free(k);
+  return nerr_pass(err);
+}
+
+NEOERR* hdf_set_valuef (HDF *hdf, char *fmt, ...)
+{
+  NEOERR *err;
+  va_list ap;
+
+  va_start(ap, fmt);
+  err = hdf_set_valuevf(hdf, fmt, ap);
+  va_end(ap);
+  return nerr_pass(err);
 }
 
 NEOERR* hdf_get_node (HDF *hdf, char *name, HDF **ret)
diff -Nbru clearsilver-0.9.3/util/neo_hdf.h clearsilver-0.9.6/util/neo_hdf.h
--- clearsilver-0.9.3/util/neo_hdf.h	Mon Aug 18 13:40:03 2003
+++ clearsilver-0.9.6/util/neo_hdf.h	Mon Oct 20 14:18:59 2003
@@ -14,9 +14,8 @@
 __BEGIN_DECLS
 
 #include <stdio.h>
-#include "neo_err.h"
-#include "neo_hash.h"
-#include "ulist.h"
+#include "util/neo_err.h"
+#include "util/neo_hash.h"
 
 #define FORCE_HASH_AT 10
 
@@ -50,62 +49,491 @@
   struct _hdf *last_child;
 } HDF;
 
+/*
+ * Function: hdf_init - Initialize an HDF data set
+ * Description: hdf_init initializes an HDF data set and returns the
+ *              pointer to the top node in the data set. 
+ * Input: hdf - pointer to an HDF pointer
+ * Output: hdf - allocated hdf node
+ * Returns: NERR_NOMEM - unable to allocate memory for dataset
+ */
 NEOERR* hdf_init (HDF **hdf);
+
+/*
+ * Function: hdf_destroy - deallocate an HDF data set
+ * Description: hdf_destroy is used to deallocate all memory associated
+ *              with an hdf data set.  Although you can pass an HDF node
+ *              as an argument to this function, you are likely to cause
+ *              a segfault if you continue to access the data set.  In
+ *              the future, we may restrict hdf_destroy so it only works
+ *              on the top level node.
+ * Input: hdf - pointer to an HDF data set allocated with hdf_init
+ * Output: None
+ * Returns: None
+ */
 void hdf_destroy (HDF **hdf);
 
+/*
+ * Function: hdf_get_int_value - Return the integer value of a point in
+ *           the data set
+ * Description: hdf_get_int_value walks the HDF data set pointed to by
+ *              hdf to name, and returns the value of that node
+ *              converted to an integer.  If that node does not exist,
+ *              or it does not contain a number, the defval is returned.  
+ * Input: hdf -> a node in an HDF data set
+ *        name -> the name of a node to walk to in the data set
+ *        defval -> value to return in case of error or if the node
+ *                  doesn't exist
+ * Output: None
+ * Returns: The integer value of the node, or the defval
+ */
 int hdf_get_int_value (HDF *hdf, char *name, int defval);
+
+/*
+ * Function: hdf_get_value - Return the value of a node in the data set
+ * Description: hdf_get_value walks the data set pointed to by hdf via
+ *              name and returns the string value located there, or
+ *              defval if the node doesn't exist
+ * Input: hdf -> the dataset node to start from
+ *        name -> the name to walk the data set to
+ *        defval -> the default value to return if the node doesn't
+ *                  exist
+ * Output: None
+ * Returns: A pointer to the string stored in the data set, or defval.
+ *          The data set maintains ownership of the string, if you want
+ *          a copy you either have to call strdup yourself, or use
+ *          hdf_get_copy
+ */
 char *hdf_get_value (HDF *hdf, char *name, char *defval);
+
+/*
+ * Function: hdf_get_valuevf - Return the value of a node in the data set
+ * Description: hdf_get_valuevf walks the data set pointed to by hdf via
+ *              namefmt printf expanded with varargs ap, and returns the
+ *              string value located there, or NULL if it doesn't exist.
+ *              This differs from hdf_get_value in that there is no
+ *              default value possible.
+ * Input: hdf -> the dataset node to start from
+ *        namefmt -> the format string
+ *        ap -> va_list of varargs
+ * Output: None
+ * Returns: A pointer to the string stored in the data set, or NULL.
+ *          The data set maintains ownership of the string, if you want
+ *          a copy you either have to call strdup yourself.
+ */
+char* hdf_get_valuevf (HDF *hdf, char *namefmt, va_list ap);
+
+/*
+ * Function: hdf_get_valuef - Return the value of a node in the data set
+ * Description: hdf_get_valuef walks the data set pointed to by hdf via
+ *              namefmt printf expanded with varargs, and returns the
+ *              string value located there, or NULL if it doesn't exist.
+ *              This differs from hdf_get_value in that there is no
+ *              default value possible.
+ * Input: hdf -> the dataset node to start from
+ *        namefmt -> the printf-style format string
+ *        ... -> arguments to fill out namefmt 
+ * Output: None
+ * Returns: A pointer to the string stored in the data set, or NULL.
+ *          The data set maintains ownership of the string, if you want
+ *          a copy you either have to call strdup yourself.
+ */
+char* hdf_get_valuef (HDF *hdf, char *namefmt, ...);
+
+/*
+ * Function: hdf_get_copy - Returns a copy of a string in the HDF data set
+ * Description: hdf_get_copy is similar to hdf_get_value, except that it
+ *              returns an malloc'd copy of the string.
+ * Input: hdf -> the dataset node to start from
+ *        name -> the name to walk the data set to
+ *        defval -> the default value to return if the node doesn't
+ *                  exist
+ * Output: value -> the allocated string (if defval = NULL, then value
+ *                  will be NULL if defval is used)
+ * Returns: NERR_NOMEM if unable to allocate the new copy 
+ */
 NEOERR* hdf_get_copy (HDF *hdf, char *name, char **value, char *defval);
 
+/*
+ * Function: hdf_get_obj - return the HDF data set node at a named location
+ * Description: hdf_get_obj walks the dataset given by hdf to the node
+ *              named name, and then returns the pointer to that node
+ * Input: hdf -> the dataset node to start from
+ *        name -> the name to walk to
+ * Output: None
+ * Returns: the pointer to the named node, or NULL if it doesn't exist
+ */
 HDF* hdf_get_obj (HDF *hdf, char *name);
-/* Always returns the node (except on NOMEM error).  Creates if
- * necessary */
+
+/*
+ * Function: hdf_get_node - Similar to hdf_get_obj except all the nodes
+ *           are created if the don't exist.
+ * Description: hdf_get_node is similar to hdf_get_obj, except instead
+ *              of stopping if it can't find a node in the tree, it will
+ *              create all of the nodes necessary to hand you back the
+ *              node you ask for.  Nodes are created with no value.
+ * Input: hdf -> the dataset node to start from
+ *        name -> the name to walk to
+ * Output: ret -> the dataset node you asked for
+ * Returns: NERR_NOMEM - unable to allocate new nodes
+ */
 NEOERR * hdf_get_node (HDF *hdf, char *name, HDF **ret);
+
+/*
+ * Function: hdf_get_child - return the first child of the named node
+ * Description: hdf_get_child will walk the dataset starting at hdf to
+ *              name, and return the first child of that node
+ * Input: hdf -> the dataset node to start from
+ *        name -> the name to walk to
+ * Output: None
+ * Returns: The first child of the named dataset node or NULL if the
+ *          node is not found (or it has no children)
+ */
 HDF* hdf_get_child (HDF *hdf, char *name);
+
+/*
+ * Function: hdf_get_attr -
+ * Description:
+ * Input:
+ * Output:
+ * Returns:
+ */
 HDF_ATTR* hdf_get_attr (HDF *hdf, char *name);
+
+/*
+ * Function: hdf_set_attr -
+ * Description:
+ * Input:
+ * Output:
+ * Returns:
+ */
 NEOERR* hdf_set_attr (HDF *hdf, char *name, char *key, char *value);
+
+/*
+ * Function: hdf_obj_child - Return the first child of a dataset node
+ * Description: hdf_obj_child and the other hdf_obj_ functions are
+ *              accessors to the HDF dataset.  Although we do not
+ *              currently "hide" the HDF struct implementation, we
+ *              recommend you use the accessor functions instead of
+ *              accessing the values directly.
+ * Input: hdf -> the hdf dataset node
+ * Output: None
+ * Returns: The pointer to the first child, or NULL if there is none
+ */
 HDF* hdf_obj_child (HDF *hdf);
+
+/*
+ * Function: hdf_obj_next - Return the next node of a dataset level
+ * Description: hdf_obj_next is an accessor function for the HDF struct
+ * Input: hdf -> the hdf dataset node
+ * Output: None
+ * Returns: The pointer to the next node, or NULL if there is none
+ */
 HDF* hdf_obj_next (HDF *hdf);
+
+/*
+ * Function: hdf_obj_top - Return the pointer to the top dataset node
+ * Description: hdf_obj_top is an accessor function which returns a
+ *              pointer to the top of the dataset, the node which was
+ *              returned by hdf_init.  This is most useful for
+ *              implementations of language wrappers where individual
+ *              nodes are tied garbage colletion wise to the top node of
+ *              the data set
+ * Input: hdf -> the hdf dataset node
+ * Output: None
+ * Returns: The pointer to the top node
+ */
 HDF* hdf_obj_top (HDF *hdf);
+
+/*
+ * Function: hdf_obj_attr - Return the HDF Attributes for a node
+ * Description:
+ * Input:
+ * Output:
+ * Returns:
+ */
 HDF_ATTR* hdf_obj_attr (HDF *hdf);
+
+/*
+ * Function: hdf_obj_name - Return the name of a node
+ * Description: hdf_obj_name is an accessor function for a datset node
+ *              which returns the name of the node.  This is just the
+ *              local name, and not the full path.
+ * Input: hdf -> the hdf dataset node
+ * Output: None
+ * Returns: The name of the node.  If this is the top node, the name is
+ * NULL.
+ */
 char* hdf_obj_name (HDF *hdf);
+
+/*
+ * Function: hdf_obj_value - Return the value of a node
+ * Description: hdf_obj_value is an accessor function for a dataset node
+ *              which returns the value of the node, or NULL if the node
+ *              has no value.  This is not a copy of the value, so the
+ *              node retains ownership of the value
+ * Input: hdf -> the hdf dataset node
+ * Output: None
+ * Returns: The value of the node, or NULL if it has no value
+ */
 char* hdf_obj_value (HDF *hdf);
 
+/*
+ * Function: hdf_set_value - Set the value of a named node
+ * Description: hdf_set_value will set the value of a named node.  All
+ *              of the interstitial nodes which don't exist will be
+ *              created with a value of NULL.  Existing nodes are not
+ *              modified.  New nodes are created at the end of the list.
+ *              If a list of nodes exceeds FORCE_HASH_AT, then a HASH
+ *              will be created at that level and all of the nodes will
+ *              be added to the hash for faster lookup times.
+ *              The copy of the value will be made which the dataset
+ *              will own.
+ * Input: hdf -> the pointer to the hdf dataset
+ *        name -> the named node to walk to
+ *        value -> the value to set the node to
+ * Output: None
+ * Returns: NERR_NOMEM
+ */
 NEOERR* hdf_set_value (HDF *hdf, char *name, char *value);
+
+/*
+ * Function: hdf_set_valuef - Set the value of a named node
+ * Description: hdf_set_valuef is a convenience function that wraps
+ *              hdf_set_value.  Due to limitations of C, the fmt is in
+ *              the format "name=value", where we will first format the
+ *              entire string, and then break it at the first (from the
+ *              left) equal sign (=) and use the left portion as the
+ *              name and the right portion as the value.  This function
+ *              is somewhat inefficient in that it first allocates the
+ *              full name=value, and then the call to hdf_set_value
+ *              duplicates the value portion, and then we free the
+ *              name=value.
+ *              Currently, we don't strip whitespace from the key or
+ *              value.  In the future, this function might work more
+ *              like reading a single line of an HDF string or file,
+ *              allowing for attributes and symlinks to be specified...
+ *              maybe.
+ * Input: hdf -> the pointer to the hdf dataset
+ *        fmt -> the name=value printf(3) format string
+ * Output: None
+ * Returns: NERR_NOMEM
+ */
+NEOERR* hdf_set_valuef (HDF *hdf, char *fmt, ...);
+NEOERR* hdf_set_valuevf (HDF *hdf, char *fmt, va_list ap); 
+
+/*
+ * Function: hdf_set_int_value - Set the value of a named node to a number
+ * Description: hdf_set_int_value is a helper function that maps an
+ *              integer to a string, and then calls hdf_set_value with
+ *              that string
+ * Input: hdf -> the pointer to the hdf dataset
+ *        name -> the named node to walk to
+ *        value -> the value to set the node to
+ * Output: None
+ * Returns: NERR_NOMEM
+ */
 NEOERR* hdf_set_int_value (HDF *hdf, char *name, int value);
+
+/*
+ * Function: hdf_set_copy -> Copy a value from one location in the
+ *           dataset to another
+ * Description: hdf_set_copy first walks the hdf dataset to the named src
+ *              node, and then copies that value to the named dest node.
+ *              If the src node is not found, an error is raised.
+ * Input: hdf -> the pointer to the dataset node
+ *        dest -> the name of the destination node
+ *        src -> the name of the source node
+ * Output: None
+ * Returns: NERR_NOMEM, NERR_NOT_FOUND
+ */
 NEOERR* hdf_set_copy (HDF *hdf, char *dest, char *src);
+
+/*
+ * Function: hdf_set_buf - Set the value of a node without duplicating
+ *           the value
+ * Description: hdf_set_buf is similar to hdf_set_value, except the
+ *              dataset takes ownership of the value instead of making a
+ *              copy of it.  The dataset assumes that value was
+ *              malloc'd, since it will attempt to free it when
+ *              hdf_destroy is called
+ * Input: hdf -> the hdf dataset node
+ *        name -> the name to walk to
+ *        value -> the malloc'd value
+ * Output: None
+ * Returns: NERR_NOMEM - unable to allocate a node
+ */
+
 NEOERR* hdf_set_buf (HDF *hdf, char *name, char *value);
 
+/*
+ * Function: hdf_set_symlink - Set part of the tree to link to another
+ * Description: hdf_set_symlink creates a link between two sections of
+ *              an HDF dataset.  The link is "by name" hence the term
+ *              "symlink".  This means that the destination node does
+ *              not need to exist.  Any attempt to access the source
+ *              node will cause the function to walk to the dest node,
+ *              and then continue walking from there.  Using symlinks
+ *              can "hide" values in the dataset since you won't be able
+ *              to access any children of the linked node directly,
+ *              though dumps and other things which access the data
+ *              structure directly will bypass the symlink.  Use this
+ *              feature sparingly as its likely to surprise you.
+ * Input: hdf -> the dataset node
+ *        src -> the source node name 
+ *        dest -> the destination node name (from the top of the
+ *        dataset, not relative names)
+ * Output: None
+ * Returns: NERR_NOMEM
+ */
 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.
+ *              This function works by creating an array of the pointers
+ *              for each child object of h, using qsort to sort that
+ *              array, and then re-ordering the linked list of children
+ *              to the new order.  The qsort compare function uses a
+ *              pointer to the value in the array, which in our case is
+ *              a pointer to an HDF struct, so your comparison function
+ *              should work on HDF ** pointers.
  * Input: h - HDF node
  *        compareFunc - function which returns 1,0,-1 depending on some 
- *                      criteria.  Given two children of h
+ *                      criteria.  The arguments to this sort function
+ *                      are pointers to pointers to HDF elements.  For
+ *                      example:
+ *                      int sortByName(const void *a, const void *b) {
+ *                        HDF **ha = (HDF **)a;
+ *                        HDF **hb = (HDF **)b;
+ *
+ *                      return strcasecmp(hdf_obj_name(*ha), hdf_obj_name(*hb));
+ *                      }
+ *
  * Output: None (h children will be sorted)
- * Return: None
+ * Return: NERR_NOMEM 
  */
 NEOERR *hdf_sort_obj(HDF *h, int (*compareFunc)(const void *, const void *));
 
+/*
+ * Function: hdf_read_file - read an HDF data file
+ * Description:
+ * Input:
+ * Output:
+ * Returns: NERR_IO, NERR_NOMEM, NERR_PARSE
+ */
 NEOERR* hdf_read_file (HDF *hdf, char *path);
+
+/*
+ * Function: hdf_write_file - write an HDF data file
+ * Description:
+ * Input:
+ * Output:
+ * Returns: NERR_IO
+ */
 NEOERR* hdf_write_file (HDF *hdf, char *path);
+
+/*
+ * Function: hdf_write_file_atomic - write an HDF data file atomically
+ * Description: hdf_write_file_atomic is similar to hdf_write_file,
+ *              except the new file is created with a unique name and
+ *              then rename(2) is used to atomically replace the old
+ *              file with the new file
+ * Input:
+ * Output:
+ * Returns: NERR_IO
+ */
 NEOERR* hdf_write_file_atomic (HDF *hdf, char *path);
 
+/*
+ * Function: hdf_read_string - read an HDF string
+ * Description:
+ * Input:
+ * Output:
+ * Returns: NERR_NOMEM, NERR_PARSE
+ */
 NEOERR* hdf_read_string (HDF *hdf, char *s);
+
+/*
+ * Function: hdf_read_string_ignore - Read an HDF string and ignore errors
+ * Description:
+ * Input:
+ * Output:
+ * Returns: NERR_NOMEM
+ */
 NEOERR* hdf_read_string_ignore (HDF *hdf, char *s, int ignore);
+
+/*
+ * Function: hdf_write_string - serialize an HDF dataset to a string
+ * Description:
+ * Input:
+ * Output:
+ * Returns: NERR_NOMEM
+ */
 NEOERR* hdf_write_string (HDF *hdf, char **s);
 
+/*
+ * Function: hdf_dump - dump an HDF dataset to stdout
+ * Description:
+ * Input:
+ * Output:
+ * Returns:
+ */
 NEOERR* hdf_dump (HDF *hdf, char *prefix);
+
+/*
+ * Function: hdf_dump_format - dump an HDF dataset to FILE *fp
+ * Description:
+ * Input:
+ * Output:
+ * Returns:
+ */
 NEOERR* hdf_dump_format (HDF *hdf, int lvl, FILE *fp);
+
+/*
+ * Function: hdf_dump_str - dump an HDF dataset to STRING
+ * Description:
+ * Input:
+ * Output:
+ * Returns:
+ */
 NEOERR* hdf_dump_str(HDF *hdf, char *prefix, int compact, STRING *str);
 
+/*
+ * Function: hdf_remove_tree - delete a subtree of an HDF dataset
+ * Description:
+ * Input:
+ * Output:
+ * Returns:
+ */
 NEOERR* hdf_remove_tree (HDF *hdf, char *name);
+
+/*
+ * Function: hdf_copy - copy part of an HDF dataset to another
+ * Description: hdf_copy is a deep copy of an HDF tree pointed to by
+ *              src to the named node of dest.  dest and src need not be
+ *              part of the same data set
+ * Input: dest_hdf -> the destination dataset
+ *        name -> the name of the destination node
+ *        src -> the hdf dataset to copy to the destination
+ * Output: None
+ * Returns: NERR_NOMEM, NERR_NOT_FOUND
+ */
 NEOERR* hdf_copy (HDF *dest_hdf, char *name, HDF *src);
 
-/* Utility function */
+/*
+ * Function: hdf_search_path - Find a file given a search path in HDF
+ * Description: hdf_search_path is a convenience/utility function that
+ *              searches for relative filenames in a search path.  The
+ *              search path is the list given by the children of
+ *              hdf.loadpaths.
+ * Input: hdf -> the hdf dataset to use
+ *        path -> the relative path
+ *        full -> a pointer to a _POSIX_PATH_MAX buffer
+ * Output: full -> the full path of the file
+ * Returns: NERR_NOT_FOUND if the file wasn't found in the search path
+ */
 NEOERR* hdf_search_path (HDF *hdf, char *path, char *full);
 
 __END_DECLS
diff -Nbru clearsilver-0.9.3/util/neo_str.c clearsilver-0.9.6/util/neo_str.c
--- clearsilver-0.9.3/util/neo_str.c	Wed Apr  2 19:03:01 2003
+++ clearsilver-0.9.6/util/neo_str.c	Wed Oct  8 16:43:02 2003
@@ -140,7 +140,7 @@
   return STATUS_OK;
 }
 
-/* currently, this requires a C99 compliant snprintf */
+/* this is much more efficient with C99 snprintfs... */
 NEOERR *string_appendvf (STRING *str, char *fmt, va_list ap) 
 {
   NEOERR *err;
@@ -151,14 +151,29 @@
   va_copy(tmp, ap);
   /* determine length */
   size = sizeof (buf);
-  bl = vsnprintf (buf, size, fmt, ap);
+  bl = vsnprintf (buf, size, fmt, tmp);
   if (bl > -1 && bl < size)
     return string_appendn (str, buf, bl);
 
+  /* Handle non-C99 snprintfs (requires extra malloc/free and copy) */
+  if (bl == -1)
+  {
+    char *a_buf;
+
+    va_copy(tmp, ap);
+    a_buf = vnsprintf_alloc(size*2, fmt, tmp);
+    if (a_buf == NULL)
+      return nerr_raise(NERR_NOMEM, 
+	  "Unable to allocate memory for formatted string");
+    err = string_append(str, a_buf);
+    free(a_buf);
+    return nerr_pass(err);
+  }
+
   err = string_check_length (str, bl+1);
   if (err != STATUS_OK) return nerr_pass (err);
-  va_copy(ap, tmp);
-  vsprintf (str->buf + str->len, fmt, ap);
+  va_copy(tmp, ap);
+  vsprintf (str->buf + str->len, fmt, tmp);
   str->len += bl;
   str->buf[str->len] = '\0';
 
@@ -252,12 +267,38 @@
   arr->count = 0;
 }
 
+/* Mostly used by vprintf_alloc for non-C99 compliant snprintfs,
+ * this is like vsprintf_alloc except it takes a "suggested" size */
+char *vnsprintf_alloc (int start_size, char *fmt, va_list ap)
+{
+  char *b = NULL;
+  int bl, size;
+  va_list tmp;
+
+  size = start_size;
+
+  b = (char *) malloc (size * sizeof(char));
+  if (b == NULL) return NULL;
+  while (1)
+  {
+    va_copy(tmp, ap);
+    bl = vsnprintf (b, size, fmt, tmp);
+    if (bl > -1 && bl < size)
+      return b;
+    if (bl > -1)
+      size = bl + 1;
+    else
+      size *= 2;
+    b = (char *) realloc (b, size * sizeof(char));
+    if (b == NULL) return NULL;
+  }
+}
+
 /* This works better with a C99 compliant vsnprintf, but should work ok
  * with versions that return a -1 if it overflows the buffer */
 char *vsprintf_alloc (char *fmt, va_list ap)
 {
   char buf[4096];
-  char *b = NULL;
   int bl, size;
   va_list tmp;
 
@@ -266,7 +307,7 @@
   va_copy(tmp, ap);
   
   size = sizeof (buf);
-  bl = vsnprintf (buf, sizeof (buf), fmt, ap);
+  bl = vsnprintf (buf, sizeof (buf), fmt, tmp);
   if (bl > -1 && bl < size)
     return strdup (buf);
 
@@ -275,20 +316,10 @@
   else
     size *= 2;
 
-  b = (char *) malloc (size * sizeof(char));
-  if (b == NULL) return NULL;
-  while (1)
-  {
-    va_copy(ap, tmp);
-    bl = vsnprintf (b, size, fmt, ap);
-    if (bl > -1 && bl < size)
-      return b;
-    size *= 2;
-    b = (char *) realloc (b, size * sizeof(char));
-    if (b == NULL) return NULL;
-  }
+  return vnsprintf_alloc(size, fmt, ap);
 }
 
+
 char *sprintf_alloc (char *fmt, ...)
 {
   va_list ap;
@@ -296,6 +327,23 @@
 
   va_start (ap, fmt);
   r = vsprintf_alloc (fmt, ap);
+  va_end (ap);
+  return r;
+}
+
+/* This is mostly just here for completeness, I doubt anyone would use
+ * this (its more efficient (time-wise) if start_size is bigger than the
+ * resulting string.  Its less efficient than sprintf_alloc if we have a
+ * C99 snprintf and it doesn't fit in start_size. 
+ * BTW: If you are really worried about the efficiency of these
+ * functions, maybe you shouldn't be using them in the first place... */
+char *nsprintf_alloc (int start_size, char *fmt, ...)
+{
+  va_list ap;
+  char *r;
+
+  va_start (ap, fmt);
+  r = vnsprintf_alloc (start_size, fmt, ap);
   va_end (ap);
   return r;
 }
diff -Nbru clearsilver-0.9.3/util/neo_str.h clearsilver-0.9.6/util/neo_str.h
--- clearsilver-0.9.3/util/neo_str.h	Fri Aug 30 17:39:46 2002
+++ clearsilver-0.9.6/util/neo_str.h	Wed Oct  8 16:43:02 2003
@@ -27,7 +27,9 @@
 void neos_lower (char *s);
 
 char *sprintf_alloc (char *fmt, ...);
+char *nsprintf_alloc (int start_size, char *fmt, ...);
 char *vsprintf_alloc (char *fmt, va_list ap);
+char *vnsprintf_alloc (int start_size, char *fmt, va_list ap);
 
 typedef struct _string
 {
@@ -58,7 +60,7 @@
 void string_clear (STRING *str);
 
 /* typedef struct _ulist ULIST; */
-#include "ulist.h"
+#include "util/ulist.h"
 NEOERR *string_array_split (ULIST **list, char *s, char *sep, int max);
 
 BOOL reg_search (char *re, char *str);
diff -Nbru clearsilver-0.9.3/util/skiplist.h clearsilver-0.9.6/util/skiplist.h
--- clearsilver-0.9.3/util/skiplist.h	Wed Apr  2 15:07:36 2003
+++ clearsilver-0.9.6/util/skiplist.h	Wed Sep 24 16:50:39 2003
@@ -17,7 +17,7 @@
 #ifndef __SKIPLIST_H_
 #define __SKIPLIST_H_
 
-#include <util/neo_err.h>
+#include "util/neo_err.h"
 
 __BEGIN_DECLS
 
diff -Nbru clearsilver-0.9.3/util/test/Makefile clearsilver-0.9.6/util/test/Makefile
--- clearsilver-0.9.3/util/test/Makefile	Mon Mar 31 17:45:55 2003
+++ clearsilver-0.9.6/util/test/Makefile	Wed Oct  8 16:46:26 2003
@@ -10,6 +10,10 @@
 HDFTEST_SRC = hdftest.c
 HDFTEST_OBJ = $(HDFTEST_SRC:%.c=%.o)
 
+HDFSORTTEST_EXE = hdf_sort_test
+HDFSORTTEST_SRC = hdf_sort_test.c
+HDFSORTTEST_OBJ = $(HDFSORTTEST_SRC:%.c=%.o)
+
 HDFLOADTEST_EXE = hdfloadtest
 HDFLOADTEST_SRC = hdfloadtest.c
 HDFLOADTEST_OBJ = $(HDFLOADTEST_SRC:%.c=%.o)
@@ -38,6 +42,7 @@
 LIBS += -L$(LIB_DIR) -lneo_utl 
 
 TARGETS = $(HDFTEST_EXE) $(LISTDIRTEST_EXE) $(HDFCOPYTEST_EXE) \
+	$(HDFSORTTEST_EXE) \
 	$(HDFLOADTEST_EXE) $(NETTEST_EXE) $(DATETEST_EXE) \
 	$(HASHTEST_EXE)
 
@@ -45,6 +50,9 @@
 
 $(HDFTEST_EXE): $(HDFTEST_OBJ) $(NTR_LIB)
 	$(LD) $@ $(HDFTEST_OBJ) $(LIBS)
+
+$(HDFSORTTEST_EXE): $(HDFSORTTEST_OBJ) $(NTR_LIB)
+	$(LD) $@ $(HDFSORTTEST_OBJ) $(LIBS)
 
 $(HDFLOADTEST_EXE): $(HDFLOADTEST_OBJ) $(NTR_LIB)
 	$(LD) $@ $(HDFLOADTEST_OBJ) $(LIBS) # -lefence
diff -Nbru clearsilver-0.9.3/util/test/hdf_sort_test.c clearsilver-0.9.6/util/test/hdf_sort_test.c
--- clearsilver-0.9.3/util/test/hdf_sort_test.c	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.6/util/test/hdf_sort_test.c	Wed Oct  8 16:46:26 2003
@@ -0,0 +1,102 @@
+
+#include "cs_config.h"
+#include <unistd.h>
+#include <string.h>
+#include "util/neo_misc.h"
+#include "util/neo_hdf.h"
+#include "util/neo_rand.h"
+
+NEOERR* hdf_set_valuef(HDF *hdf, char *namefmt, char *valuefmt, ...)
+{
+  char newstr[2048];
+  char one[2];
+  va_list ap;
+  char *nametok;
+  char *valtok;
+
+  char newfmt[2048];
+
+
+  one[0]=(char)1;
+  one[1]=(char)0;
+
+  snprintf(newfmt,2048,"%s%s%s",namefmt,one,valuefmt);
+
+  va_start(ap, valuefmt);
+  vsnprintf (newstr, 1024, newfmt, ap);
+  va_end(ap);
+
+  /* split it at 1 */
+  nametok=strtok(newstr,one);
+  valtok=strtok(NULL,one);
+
+
+  return hdf_set_value(hdf,nametok,valtok);
+}
+
+
+int TestCompare(const void* pa, const void* pb)
+{
+  HDF **a = (HDF **)pa;
+  HDF **b = (HDF **)pb;
+  float aVal,bVal;
+
+  aVal = atof(hdf_get_value(*a,"val","0"));
+  bVal = atof(hdf_get_value(*b,"val","0"));
+
+  printf("TestCompare aVal=%f [%s]  bVal=%f [%s]\n",aVal,hdf_get_value(*a,"name","?"),bVal,hdf_get_value(*b,"name","?"));
+
+  if (aVal<bVal) return -1;
+  if (aVal==bVal) return 0;
+  return 1;
+}
+
+void TestSort(HDF* hdf)
+{
+  int i;
+  float value;
+
+  for (i=0;i<15;i++)
+  {
+    value = rand()/(RAND_MAX+1.0);
+
+    hdf_set_valuef(hdf, "test.%d", "%d", i, i);
+    hdf_set_valuef(hdf, "test.%d.name", "item #%d", i, i);
+    hdf_set_valuef(hdf, "test.%d.val", "%f", i, value );
+  }
+
+  hdf_dump(hdf,NULL);
+
+  hdf_sort_obj(hdf_get_obj(hdf, "test"), TestCompare);
+
+  hdf_dump(hdf,NULL);
+
+}
+
+
+int main(int argc, char *argv[])
+{
+  NEOERR *err;
+  HDF *hdf;
+  int x;
+  char name[256];
+  char value[256];
+  double tstart = 0;
+
+  err = hdf_init(&hdf);
+  if (err != STATUS_OK) 
+  {
+    nerr_log_error(err);
+    return -1;
+  }
+
+  tstart = ne_timef();
+  TestSort(hdf);
+  ne_warn("sort took %5.5fs", ne_timef() - tstart);
+
+  hdf_dump(hdf, NULL);
+
+  hdf_destroy(&hdf);
+
+  return 0;
+}
diff -Nbru clearsilver-0.9.3/util/test/hdftest.c clearsilver-0.9.6/util/test/hdftest.c
--- clearsilver-0.9.3/util/test/hdftest.c	Wed Apr  2 15:07:39 2003
+++ clearsilver-0.9.6/util/test/hdftest.c	Wed Oct  8 16:46:26 2003
@@ -61,7 +61,7 @@
     nerr_log_error(err);
     return -1;
   }
-  err = hdf_set_value (hdf, "Beware.The.Ides", "3");
+  err = hdf_set_valuef (hdf, "Beware.The.%s=%d", "Ides", 3);
   if (err != STATUS_OK) 
   {
     nerr_log_error(err);
@@ -142,7 +142,7 @@
   hdf_sort_obj(hdf, sortByName);
   ne_warn("sort took %5.5fs", ne_timef() - tstart);
 
-  /* hdf_dump(hdf, NULL); */
+  hdf_dump(hdf, NULL);
 
   hdf_destroy(&hdf);
 
diff -Nbru clearsilver-0.9.3/util/ulist.h clearsilver-0.9.6/util/ulist.h
--- clearsilver-0.9.3/util/ulist.h	Wed Apr  2 15:07:36 2003
+++ clearsilver-0.9.6/util/ulist.h	Wed Sep 24 16:50:39 2003
@@ -11,7 +11,7 @@
 #ifndef __ULIST_H_
 #define __ULIST_H_ 1
 
-#include "neo_err.h"
+#include "util/neo_err.h"
 
 typedef struct _ulist
 {
diff -Nbru clearsilver-0.9.3/util/wdb.h clearsilver-0.9.6/util/wdb.h
--- clearsilver-0.9.3/util/wdb.h	Mon Aug  6 14:28:17 2001
+++ clearsilver-0.9.6/util/wdb.h	Wed Sep 24 16:50:39 2003
@@ -11,9 +11,9 @@
 #ifndef __WDB_H_
 #define __WDB_H_ 1
 
-#include "skiplist.h"
-#include "dict.h"
-#include "ulist.h"
+#include "util/skiplist.h"
+#include "util/dict.h"
+#include "util/ulist.h"
 #include <db.h>
 
 typedef struct _column
