diff -bru clearsilver-0.1/cgi/cgi.c clearsilver-0.2/cgi/cgi.c
--- clearsilver-0.1/cgi/cgi.c	Mon Aug  6 14:28:16 2001
+++ clearsilver-0.2/cgi/cgi.c	Mon Aug 20 19:39:16 2001
@@ -81,6 +81,12 @@
 
 static char *Argv0 = "";
 
+int IgnoreEmptyFormVars = 0;
+
+static int ExceptionsInit = 0;
+NERR_TYPE CGIFinished = -1;
+NERR_TYPE CGIUploadCancelled = -1;
+
 static NEOERR *_add_cgi_env_var (CGI *cgi, char *env, char *name)
 {
   NEOERR *err;
@@ -199,7 +205,10 @@
       {
 	v = strtok_r(NULL, "&", &l);
       }
+      if (v == NULL) v = "";
       snprintf(buf, sizeof(buf), "Query.%s", url_decode(k));
+      if (!(cgi->ignore_empty_form_vars && (v == NULL || *v == '\0')))
+      {
       url_decode(v);
       obj = hdf_get_obj (cgi->hdf, buf);
       if (obj != NULL)
@@ -230,6 +239,7 @@
       }
       err = hdf_set_value (cgi->hdf, buf, v);
       if (err != STATUS_OK) break;
+      }
       k = strtok_r(NULL, "=", &l);
     }
   }
@@ -246,6 +256,9 @@
   l = hdf_get_value (cgi->hdf, "CGI.ContentLength", NULL);
   if (l == NULL) return STATUS_OK;
   len = atoi (l);
+
+  cgi->data_expected = len;
+
   query = (char *) malloc (sizeof(char) * (len + 1));
   if (query == NULL) 
     return nerr_raise (NERR_NOMEM, 
@@ -315,12 +328,50 @@
   return nerr_pass(err);
 }
 
-static NEOERR *cgi_parse (CGI *cgi)
+static void _launch_debugger (CGI *cgi, char *display)
+{
+  pid_t myPid, pid;
+  char buffer[127];
+  char *debugger;
+  HDF *obj;
+  char *allowed;
+
+  /* Only allow remote debugging from allowed hosts */
+  for (obj = hdf_get_child (cgi->hdf, "Config.Displays");
+      obj; obj = hdf_obj_next (obj))
+  {
+    allowed = hdf_obj_value (obj);
+    if (allowed && !strcmp (display, allowed)) break;
+  }
+  if (obj == NULL) return;
+
+  myPid = getpid();
+
+  if ((pid = fork()) < 0)
+    return;
+
+  if ((debugger = hdf_get_value (cgi->hdf, "Config.Debugger", NULL)) == NULL)
+  {
+    debugger = "/usr/local/bin/sudo /usr/local/bin/ddd -display %s %s %d";
+  }
+
+  if (!pid)
+  {
+    sprintf(buffer, debugger, display, Argv0, myPid);
+    execl("/bin/sh", "sh", "-c", buffer, NULL);
+  }
+  else
+  {
+    sleep(60);
+  }
+}
+
+static NEOERR *cgi_pre_parse (CGI *cgi)
 {
   NEOERR *err;
   int x = 0;
   char buf[256];
-  char *method, *type, *query;
+  char *query;
 
   while (CGIVars[x].env_name)
   {
@@ -349,6 +400,26 @@
     if (err != STATUS_OK) return nerr_pass (err);
   }
 
+  {
+    char *display;
+
+    display = hdf_get_value (cgi->hdf, "Query.xdisplay", NULL);
+    if (display)
+    {
+      fprintf(stderr, "** Got display %s\n", display);
+      _launch_debugger(cgi, display);
+    }
+  }
+
+  return STATUS_OK;
+}
+
+NEOERR *cgi_parse (CGI *cgi)
+{
+  NEOERR *err;
+  char *method, *type;
+
+
   method = hdf_get_value (cgi->hdf, "CGI.RequestMethod", "GET");
   type = hdf_get_value (cgi->hdf, "CGI.ContentType", NULL);
   if (!strcmp(method, "POST"))
@@ -393,52 +464,24 @@
     }
 #endif
   }
-
   return STATUS_OK;
 }
 
-static void _launch_debugger (CGI *cgi, char *display)
+NEOERR *cgi_init (CGI **cgi, HDF *hdf)
 {
-  pid_t myPid, pid;
-  char buffer[127];
-  char *debugger;
-  HDF *obj;
-  char *allowed;
-
-  /* Only allow remote debugging from allowed hosts */
-  for (obj = hdf_get_child (cgi->hdf, "Config.Displays");
-      obj; obj = hdf_obj_next (obj))
-  {
-    allowed = hdf_obj_value (obj);
-    if (allowed && !strcmp (display, allowed)) break;
-  }
-  if (obj == NULL) return;
-
-  myPid = getpid();
-
-  if ((pid = fork()) < 0)
-    return;
-
-  if ((debugger = hdf_get_value (cgi->hdf, "Config.Debugger", NULL)) == NULL)
-  {
-    debugger = "/usr/local/bin/sudo /usr/local/bin/ddd -display %s %s %d";
-  }
+  NEOERR *err = STATUS_OK;
+  CGI *mycgi;
 
-  if (!pid)
+  if (ExceptionsInit == 0)
   {
-    sprintf(buffer, debugger, display, Argv0, myPid);
-    execl("/bin/sh", "sh", "-c", buffer, NULL);
-  }
-  else
-  {
-    sleep(60);
+    err = nerr_init();
+    if (err) return nerr_pass(err);
+    err = nerr_register(&CGIFinished, "CGIFinished");
+    if (err) return nerr_pass(err);
+    err = nerr_register(&CGIUploadCancelled, "CGIUploadCancelled");
+    if (err) return nerr_pass(err);
+    ExceptionsInit = 1;
   }
-}
-
-NEOERR *cgi_init (CGI **cgi, char *hdf_file)
-{
-  NEOERR *err = STATUS_OK;
-  CGI *mycgi;
 
   *cgi = NULL;
   mycgi = (CGI *) calloc (1, sizeof(CGI));
@@ -447,29 +490,22 @@
 
   mycgi->time_start = ne_timef();
 
+  mycgi->ignore_empty_form_vars = IgnoreEmptyFormVars;
+
   do 
   {
-    err = hdf_init (&(mycgi->hdf));
-    if (err != STATUS_OK) break;
-    err = cgi_parse (mycgi);
-    if (err != STATUS_OK) break;
-
-    if (hdf_file != NULL && hdf_file[0] != '\0')
+    if (hdf == NULL)
     {
-      err = hdf_read_file (mycgi->hdf, hdf_file);
+      err = hdf_init (&(mycgi->hdf));
       if (err != STATUS_OK) break;
     }
-
-    {
-      char *display;
-
-      display = hdf_get_value (mycgi->hdf, "Query.xdisplay", NULL);
-      if (display)
+    else
       {
-	fprintf(stderr, "** Got display %s\n", display);
-	_launch_debugger(mycgi, display);
-      }
+      mycgi->hdf = hdf;
     }
+    err = cgi_pre_parse (mycgi);
+    if (err != STATUS_OK) break;
+
   } while (0);
 
   if (err == STATUS_OK)
@@ -511,7 +547,7 @@
 {
   NEOERR *err = STATUS_OK;
   HDF *obj, *child;
-  char *s;
+  char *s, *charset = NULL;
 
   if (hdf_get_int_value (cgi->hdf, "Config.NoCache", 0))
   {
@@ -547,7 +583,11 @@
 	child = hdf_obj_next(child);
       }
     }
+    charset = hdf_get_value (obj, "charset", NULL);
     s = hdf_get_value (obj, "ContentType", "text/html");
+    if (charset)
+      err = cgiwrap_writef ("Content-Type: %s; charset=%s\r\n\r\n", s, charset);
+    else
     err = cgiwrap_writef ("Content-Type: %s\r\n\r\n", s);
     if (err != STATUS_OK) return nerr_pass (err);
   }
@@ -709,7 +749,7 @@
       }
       err = string_append (str, "<pre>");
       if (err != STATUS_OK) return nerr_pass(err);
-      err = hdf_dump_str (cgi->hdf, NULL, str);
+      err = hdf_dump_str (cgi->hdf, NULL, 0, str);
       if (err != STATUS_OK) return nerr_pass(err);
     }
   }
@@ -790,7 +830,7 @@
     if (do_dump)
     {
       cgiwrap_writef("Content-Type: text/plain\n\n");
-      hdf_dump_str(cgi->hdf, "", &str);
+      hdf_dump_str(cgi->hdf, "", 0, &str);
       cs_dump(cs, &str, render_cb);
       cgiwrap_writef("%s", str.buf);
       break;
@@ -888,6 +928,12 @@
   }
   cgiwrap_writevf (fmt, ap);
   cgiwrap_writef ("\r\n\r\n");
+  cgiwrap_writef ("Redirect page<br><br>\n");
+  cgiwrap_writef ("  Destination: <A HREF=\"");
+  cgiwrap_writevf (fmt, ap);
+  cgiwrap_writef ("\">");
+  cgiwrap_writevf (fmt, ap);
+  cgiwrap_writef ("</A><BR>\n<BR>\n");
   cgiwrap_writef ("There is nothing to see here, please move along...");
 
 }
diff -bru clearsilver-0.1/cgi/cgi.h clearsilver-0.2/cgi/cgi.h
--- clearsilver-0.1/cgi/cgi.h	Mon Aug  6 14:28:16 2001
+++ clearsilver-0.2/cgi/cgi.h	Mon Aug 20 19:39:16 2001
@@ -15,14 +15,31 @@
 #include "util/neo_err.h"
 #include "util/neo_hdf.h"
 
-extern int CGIFinished;
+extern NERR_TYPE CGIFinished;
+extern NERR_TYPE CGIUploadCancelled;
 
-typedef struct _cgi
+/* HACK: Set this value if you want to treat empty CGI Query variables as
+ * non-existant.
+ */
+extern int IgnoreEmptyFormVars;
+
+typedef struct _cgi CGI;
+
+typedef int (*UPLOAD_CB)(CGI *, int nread, int expected);
+
+struct _cgi
 {
   /* Only public parts of this structure */
   void *data;  /* you can store your own information here */
   HDF *hdf;    /* the HDF dataset associated with this CGI */
 
+  BOOL ignore_empty_form_vars;
+
+  UPLOAD_CB upload_cb;
+
+  int data_expected;
+  int data_read;
+
   /* For line oriented reading of form-data input.  Used during cgi_init
    * only */
   char *buf;
@@ -39,7 +56,8 @@
   /* keep track of the time between cgi_init and cgi_render */
   double time_start;
   double time_end;
-} CGI;
+};
+
 
 /*
  * Function: cgi_init - Initialize ClearSilver CGI environment
@@ -68,7 +86,9 @@
  *         NERR_IO - error reading HDF file or reading CGI stdin, or
  *                   writing data on multipart/form-data file submission
  */
-NEOERR *cgi_init (CGI **cgi, char *hdf_file);
+NEOERR *cgi_init (CGI **cgi, HDF *hdf);
+
+NEOERR *cgi_parse (CGI *cgi);
 
 /*
  * Function: cgi_destroy - deallocate the data associated with a CGI
diff -bru clearsilver-0.1/cgi/html.c clearsilver-0.2/cgi/html.c
--- clearsilver-0.1/cgi/html.c	Mon Aug  6 14:28:16 2001
+++ clearsilver-0.2/cgi/html.c	Fri Aug 10 20:19:25 2001
@@ -309,15 +309,27 @@
     {
       if (parts[i].type == SC_TYPE_URL)
       {
-	err = string_append (out, "<a target=_top href=\"");
+        char last_char = src[parts[i].end-1];
+        int suffix=0;
+        if (last_char == '.' || last_char == ',') { suffix=1; }
+	err = string_append (out, " <a target=_top href=\"");
 	if (err != STATUS_OK) break;
-	err = string_appendn (out, src + x, parts[i].end - x);
+	if (!strncmp(src + x, "www.", 4))
+	{
+	  err = string_append (out, "http://");
+	  if (err != STATUS_OK) break;
+	}
+	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 = string_appendn (out, src + x, parts[i].end - x);
+	err = string_appendn (out, src + x, parts[i].end - x - suffix);
 	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;
+        }
       }
       else /* type == SC_TYPE_EMAIL */
       {
diff -bru clearsilver-0.1/cgi/rfc2388.c clearsilver-0.2/cgi/rfc2388.c
--- clearsilver-0.1/cgi/rfc2388.c	Fri Jun 29 20:10:36 2001
+++ clearsilver-0.2/cgi/rfc2388.c	Mon Aug 20 19:39:16 2001
@@ -171,6 +171,12 @@
     }
   }
   cgiwrap_read (cgi->buf + ofs, cgi->buflen - ofs, &(cgi->readlen));
+  cgi->data_read += cgi->readlen;
+  if (cgi->upload_cb)
+  {
+    if (cgi->upload_cb (cgi, cgi->data_read, cgi->data_expected))
+      return nerr_raise (CGIUploadCancelled, "Upload Cancelled");
+  }
   cgi->readlen += ofs;
   p = memchr (cgi->buf, '\n', cgi->readlen);
   if (!p)
@@ -441,6 +447,7 @@
 	str.buf[str.len-1] = '\0';
 	str.len--;
       }
+      if (!(cgi->ignore_empty_form_vars && str.len == 0))
       err = hdf_set_value (cgi->hdf, buf, str.buf);
     }
   }
@@ -466,6 +473,14 @@
   if (ct_hdr == NULL) 
     return nerr_raise (NERR_ASSERT, "No content type header?");
 
+  cgi->data_expected = l;
+  cgi->data_read = 0;
+  if (cgi->upload_cb)
+  {
+    if (cgi->upload_cb (cgi, cgi->data_read, cgi->data_expected))
+      return nerr_raise (CGIUploadCancelled, "Upload Cancelled");
+  }
+
   err = _header_attr (ct_hdr, "boundary", &boundary);
   if (err) return nerr_pass (err);
   err = _find_boundary(cgi, boundary, &done);
@@ -492,7 +507,7 @@
   err = uListGet(cgi->files, n-1, (void **)&fp);
   if (err)
   {
-    nerr_ignore(err);
+    nerr_ignore(&err);
     return NULL;
   }
   return fp;
diff -bru clearsilver-0.1/cs/test.hdf clearsilver-0.2/cs/test.hdf
--- clearsilver-0.1/cs/test.hdf	Wed Apr 25 22:58:37 2001
+++ clearsilver-0.2/cs/test.hdf	Tue Aug  7 14:58:46 2001
@@ -54,3 +54,5 @@
   6 = 6
   6.Abbr = Sun
 }
+
+Neg = -1
Only in clearsilver-0.2: man
diff -bru clearsilver-0.1/python/neo_cgi.c clearsilver-0.2/python/neo_cgi.c
--- clearsilver-0.1/python/neo_cgi.c	Mon Aug  6 14:28:17 2001
+++ clearsilver-0.2/python/neo_cgi.c	Mon Aug 20 19:39:17 2001
@@ -16,6 +16,7 @@
 #include "util/neo_hdf.h"
 #include "cgi/cgi.h"
 #include "cgi/cgiwrap.h"
+#include "cgi/date.h"
 #include "cgi/html.h"
 #include "p_neo_util.h"
 
@@ -28,6 +29,9 @@
    PyObject_HEAD
    CGI *cgi;
    PyObject *hdf;
+   PyObject *upload_cb;
+   PyObject *upload_rock;
+   int upload_error;
 } CGIObject;
 
 static PyObject *p_cgi_value_get_attr (CGIObject *self, char *name);
@@ -87,16 +91,90 @@
 {
   CGI *cgi = NULL;
   NEOERR *err;
-  char *file;
-
-  if (!PyArg_ParseTuple(args, "s:CGI(file)", &file))
-    return NULL;
 
-  err = cgi_init (&cgi, file);
+  err = cgi_init (&cgi, NULL);
   if (err) return p_neo_error (err);
   return p_cgi_to_object (cgi);
 }
 
+static PyObject * p_cgi_parse (PyObject *self, PyObject *args)
+{
+  CGI *cgi = ((CGIObject *) self)->cgi;
+  CGIObject *p_cgi = (CGIObject *) self;
+  PyObject *rv;
+  NEOERR *err;
+
+  p_cgi->upload_error = 0;
+
+  err = cgi_parse (cgi);
+  if (err) return p_neo_error (err);
+
+  if (p_cgi->upload_error)
+  {
+    p_cgi->upload_error = 0;
+    return NULL;
+  }
+
+  rv = Py_None;
+  Py_INCREF(rv);
+  return rv;
+}
+
+static int python_upload_cb (CGI *cgi, int nread, int expected)
+{
+  CGIObject *self = (CGIObject *)(cgi->data);
+  PyObject *cb, *rock;
+  PyObject *args, *result;
+  int r;
+
+  /* fprintf(stderr, "upload_cb: %d/%d\n", nread, expected); */
+  cb = self->upload_cb;
+  rock = self->upload_rock;
+  
+  if (cb == NULL) return 0;
+  args = Py_BuildValue("(Oii)", rock, nread, expected);
+
+  if (args == NULL) {
+    self->upload_error = 1;
+    return 1;
+  }
+  result = PyEval_CallObject(cb, args);
+  Py_DECREF(args);
+  if (result != NULL && !PyInt_Check(result)) {
+    Py_DECREF(result);
+    result = NULL;
+    PyErr_SetString(PyExc_TypeError,
+	"upload_cb () returned non-integer");
+    self->upload_error = 1;
+    return 1;
+  }
+  r = PyInt_AsLong(result);
+  Py_DECREF(result);
+  result = NULL;
+  return r;
+}
+
+static PyObject * p_cgi_set_upload_cb (PyObject *self, PyObject *args)
+{
+  CGI *cgi = ((CGIObject *) self)->cgi;
+  CGIObject *p_cgi = (CGIObject *) self;
+  PyObject *rock, *cb;
+
+  if (!PyArg_ParseTuple(args, "OO:setUploadCB(rock, func)", &rock, &cb))
+    return NULL;
+
+  cgi->data = self;
+  cgi->upload_cb = python_upload_cb;
+  p_cgi->upload_cb = cb;
+  p_cgi->upload_rock = rock;
+  p_cgi->upload_error = 0;
+  Py_INCREF(cb);
+  Py_INCREF(rock);
+
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
 static PyObject * p_cgi_error (PyObject *self, PyObject *args)
 {
   CGI *cgi = ((CGIObject *) self)->cgi;
@@ -238,6 +316,8 @@
   {"debugInit", p_cgi_debug_init, METH_VARARGS, NULL},
   {"wrapInit", p_cgi_wrap_init, METH_VARARGS, NULL},
 #endif
+  {"parse", p_cgi_parse, METH_VARARGS, NULL},
+  {"setUploadCB", p_cgi_set_upload_cb, METH_VARARGS, NULL},
   {"error", p_cgi_error, METH_VARARGS, NULL},
   {"display", p_cgi_display, METH_VARARGS, NULL},
   {"redirect", p_cgi_redirect, METH_VARARGS, NULL},
@@ -673,6 +753,44 @@
   }
 }
 
+static PyObject * p_ignore (PyObject *self, PyObject *args)
+{
+  int i = 0;
+
+  if (!PyArg_ParseTuple(args, "i:IgnoreEmptyFormVars(bool)", &i))
+    return NULL;
+
+  IgnoreEmptyFormVars = i;
+  Py_INCREF(Py_None);
+  return Py_None; 
+}
+
+static PyObject * p_export_date (PyObject *self, PyObject *args)
+{
+  NEOERR *err;
+  PyObject *ho;
+  int i = 0;
+  char *prefix;
+  char *timezone;
+  HDF *hdf;
+
+  if (!PyArg_ParseTuple(args, "Ossi:exportDate(hdf, prefix, timezone, time_t)", &ho, &prefix, &timezone, &i))
+    return NULL;
+
+  hdf = p_object_to_hdf (ho);
+  if (hdf == NULL)
+  {
+    PyErr_SetString(PyExc_TypeError, "First argument must be an HDF Object");
+    return NULL;
+  }
+
+  err = export_date_time_t (hdf, prefix, timezone, i);
+  if (err) return p_neo_error (err);
+
+  Py_INCREF(Py_None);
+  return Py_None; 
+}
+
 static PyMethodDef ModuleMethods[] =
 {
   {"CGI", p_cgi_init, METH_VARARGS, NULL},
@@ -680,6 +798,8 @@
   {"htmlEscape", p_html_escape, METH_VARARGS, NULL},
   {"text2html", p_text_html, METH_VARARGS, NULL},
   {"cgiWrap", cgiwrap, METH_VARARGS, cgiwrap_doc},
+  {"IgnoreEmptyFormVars", p_ignore, METH_VARARGS, NULL},
+  {"exportDate", p_export_date, METH_VARARGS, NULL},
   {NULL, NULL}
 };
 
diff -bru clearsilver-0.1/python/neo_util.c clearsilver-0.2/python/neo_util.c
--- clearsilver-0.1/python/neo_util.c	Mon Aug  6 14:28:17 2001
+++ clearsilver-0.2/python/neo_util.c	Tue Aug  7 14:58:46 2001
@@ -330,13 +330,42 @@
 
   string_init (&str);
 
-  err = hdf_dump_str (ho->data, NULL, &str);
+  err = hdf_dump_str (ho->data, NULL, 0, &str);
   if (err) return p_neo_error(err); 
   rv = Py_BuildValue ("s", str.buf);
   string_clear (&str);
   return rv;
 }
 
+static PyObject * p_hdf_write_string (PyObject *self, PyObject *args)
+{
+  HDFObject *ho = (HDFObject *)self;
+  PyObject *rv;
+  NEOERR *err;
+  char *s = NULL;
+
+  err = hdf_write_string (ho->data, &s);
+  if (err) return p_neo_error(err); 
+  rv = Py_BuildValue ("s", s);
+  if (s) free(s);
+  return rv;
+}
+
+static PyObject * p_hdf_read_string (PyObject *self, PyObject *args)
+{
+  HDFObject *ho = (HDFObject *)self;
+  NEOERR *err;
+  char *s = NULL;
+
+  if (!PyArg_ParseTuple(args, "s:readString(string)", &s))
+    return NULL;
+
+  err = hdf_read_string (ho->data, s);
+  if (err) return p_neo_error(err); 
+  Py_INCREF (Py_None);
+  return Py_None;
+}
+
 static PyMethodDef HDFMethods[] =
 {
   {"getIntValue", p_hdf_get_int_value, METH_VARARGS, NULL},
@@ -350,6 +379,8 @@
   {"setValue", p_hdf_set_value, METH_VARARGS, NULL},
   {"readFile", p_hdf_read_file, METH_VARARGS, NULL},
   {"writeFile", p_hdf_write_file, METH_VARARGS, NULL},
+  {"readString", p_hdf_read_string, METH_VARARGS, NULL},
+  {"writeString", p_hdf_write_string, METH_VARARGS, NULL},
   {"removeTree", p_hdf_remove_tree, METH_VARARGS, NULL},
   {"dump", p_hdf_dump, METH_VARARGS, NULL},
   {NULL, NULL}
diff -bru clearsilver-0.1/util/neo_hdf.c clearsilver-0.2/util/neo_hdf.c
--- clearsilver-0.1/util/neo_hdf.c	Mon Aug  6 14:28:17 2001
+++ clearsilver-0.2/util/neo_hdf.c	Tue Aug  7 14:58:46 2001
@@ -574,41 +574,64 @@
   return STATUS_OK;
 }
 
-NEOERR* hdf_dump_str(HDF *hdf, char *prefix, STRING *str)
+NEOERR* hdf_dump_str(HDF *hdf, char *prefix, int compact, STRING *str)
 {
   NEOERR *err;
   char *p;
 
   if (hdf->value)
   {
-    if (prefix)
+    if (prefix && !compact)
     {
-      err = string_appendf (str, "%s.%s = %s\n", prefix, hdf->name, hdf->value);
+      err = string_appendf (str, "%s.%s", prefix, hdf->name);
     }
     else
     {
-      err = string_appendf (str, "%s = %s\n", hdf->name, hdf->value);
+      err = string_append (str, hdf->name);
+    }
+    if (err) return nerr_pass (err);
+    if (strchr (hdf->value, '\n'))
+    {
+      if (hdf->value[strlen(hdf->value)-1] != '\n')
+	err = string_appendf (str, " << EOM\n%s\nEOM\n", hdf->value);
+      else
+	err = string_appendf (str, " << EOM\n%sEOM\n", hdf->value);
+    }
+    else
+    {
+      err = string_appendf (str, " = %s\n", hdf->value);
     }
     if (err) return nerr_pass (err);
   }
   if (hdf->child)
   {
-    if (prefix)
+    if (prefix && !compact)
     {
       p = (char *) malloc (strlen(hdf->name) + strlen(prefix) + 2);
       sprintf (p, "%s.%s", prefix, hdf->name);
-      err = hdf_dump_str (hdf->child, p, str);
+      err = hdf_dump_str (hdf->child, p, compact, str);
       free(p);
     }
     else
     {
-      err = hdf_dump_str (hdf->child, hdf->name, str);
+      if (compact && hdf->name)
+      {
+	err = string_appendf(str, "%s {\n", hdf->name);
+	if (err) return nerr_pass (err);
+	err = hdf_dump_str (hdf->child, hdf->name, compact, str);
+	if (err) return nerr_pass (err);
+	err = string_append(str, "}\n");
+      }
+      else
+      {
+	err = hdf_dump_str (hdf->child, hdf->name, compact, str);
+      }
     }
     if (err) return nerr_pass (err);
   }
   if (hdf->next)
   {
-    err = hdf_dump_str (hdf->next, prefix, str);
+    err = hdf_dump_str (hdf->next, prefix, compact, str);
     if (err) return nerr_pass (err);
   }
   return STATUS_OK;
@@ -671,8 +694,190 @@
   return STATUS_OK;
 }
 
+NEOERR *hdf_write_string (HDF *hdf, char **s)
+{
+  STRING str;
+  NEOERR *err;
+
+  *s = NULL;
+
+  string_init (&str);
+
+  err = hdf_dump_str (hdf, NULL, 1, &str);
+  if (err) 
+  {
+    string_clear (&str);
+    return nerr_pass(err);
+  }
+
+  *s = str.buf;
+
+  return STATUS_OK;
+}
+
+
 /* HDF file looks like the following: */
 #define SKIPWS(s) while (*s && isspace(*s)) s++;
+
+static int _copy_line (char **s, char *buf, size_t buf_len)
+{
+  int x = 0;
+  char *st = *s;
+
+  while (*st && x < buf_len)
+  {
+    buf[x++] = *st;
+    if (*st++ == '\n') break;
+  }
+  buf[x] = '\0';
+  *s = st;
+
+  return x;
+}
+
+static NEOERR* _hdf_read_string (HDF *hdf, char **str, int *line)
+{
+  NEOERR *err;
+  HDF *lower;
+  char buf[4096];
+  char *s;
+  char *name, *value;
+
+  while (_copy_line(str, buf, sizeof(buf)) != 0)
+  {
+    (*line)++;
+    s = buf;
+    SKIPWS(s);
+    if (!strncmp(s, "#include ", 9))
+    {
+      return nerr_raise (NERR_PARSE, "[%d]: #include not supported in string parse", *line);
+    }
+    else if (s[0] == '#')
+    {
+      /* comment: pass */
+    }
+    else if (s[0] == '}') /* up */
+    {
+      s = neos_strip(s);
+      if (strcmp(s, "}"))
+      {
+	return nerr_raise(NERR_PARSE, 
+	    "[%d] Trailing garbage on line following }: %s", *line,
+	    buf);
+      }
+      return STATUS_OK;
+    }
+    else if (s[0])
+    {
+      /* Valid hdf name is [0-9a-zA-Z_.]+ */
+      name = s;
+      while (*s && (isalnum(*s) || *s == '_' || *s == '.')) s++;
+      /*
+      if (*s != '\0')
+      {
+	*s++ = '\0';
+      }
+      */
+      SKIPWS(s);
+
+      if (s[0] == '=') /* assignment */
+      {
+	*s = '\0';
+	name = neos_strip(name);
+	s++;
+	value = neos_strip(s);
+	err = hdf_set_value (hdf, name, value);
+	if (err != STATUS_OK)
+	  return nerr_pass_ctx(err, "In String %d", *line);
+      }
+      else if (s[0] == ':') /* copy */
+      {
+	*s = '\0';
+	name = neos_strip(name);
+	s++;
+	value = neos_strip(s);
+	err = hdf_set_copy (hdf, name, value);
+	if (err != STATUS_OK)
+	  return nerr_pass_ctx(err, "In string %d", *line);
+      }
+      else if (s[0] == '{') /* deeper */
+      {
+	*s = '\0';
+	name = neos_strip(name);
+	lower = hdf_get_obj (hdf, name);
+	if (lower == NULL)
+	{
+	  err = hdf_set_value (hdf, name, NULL);
+	  if (err != STATUS_OK) 
+	    return nerr_pass_ctx(err, "In string %d", *line);
+	  lower = hdf_get_obj (hdf, name);
+	}
+	err = _hdf_read_string (lower, str, line);
+	if (err != STATUS_OK) 
+	  return nerr_pass_ctx(err, "In string %d", *line);
+      }
+      else if (s[0] == '<' && s[1] == '<') /* multi-line assignment */
+      {
+	char *m;
+	int msize = 0;
+	int mmax = 128;
+	int l;
+
+	*s = '\0';
+	name = neos_strip(name);
+	s+=2;
+	value = neos_strip(s);
+	l = strlen(value);
+	if (l == 0)
+	  return nerr_raise(NERR_PARSE, 
+	      "[%d] No multi-assignment terminator given: %s", *line, 
+	      buf);
+	m = (char *) malloc (mmax * sizeof(char));
+	if (m == NULL)
+	  return nerr_raise(NERR_NOMEM, 
+	    "[%d] Unable to allocate memory for multi-line assignment to %s",
+	    *line, name);
+	while (_copy_line (str, m+msize, mmax-msize) != 0)
+	{
+	  if (!strncmp(value, m+msize, l) && isspace(m[msize+l]))
+	  {
+	    m[msize] = '\0';
+	    break;
+	  }
+	  msize += strlen(m+msize);
+	  if (msize + l + 10 > mmax)
+	  {
+	    mmax += 128;
+	    m = (char *) realloc (m, mmax * sizeof(char));
+	    if (m == NULL)
+	      return nerr_raise(NERR_NOMEM, 
+		  "[%d] Unable to allocate memory for multi-line assignment to %s: size=%d",
+		  *line, name, mmax);
+	  }
+	}
+	err = hdf_set_buf(hdf, name, m);
+	if (err != STATUS_OK)
+	{
+	  free (m);
+	  return nerr_pass_ctx(err, "In string %d", *line);
+	}
+
+      }
+      else
+      {
+	return nerr_raise(NERR_PARSE, "[%d] Unable to parse line %s",
+	    *line, buf);
+      }
+    }
+  }
+  return STATUS_OK;
+}
+
+NEOERR * hdf_read_string (HDF *hdf, char *str)
+{
+  int line = 0;
+  return nerr_pass (_hdf_read_string (hdf, &str, &line));
+}
 
 static NEOERR* hdf_read_file_fp (HDF *hdf, FILE *fp, char *path, int *line)
 {
diff -bru clearsilver-0.1/util/neo_hdf.h clearsilver-0.2/util/neo_hdf.h
--- clearsilver-0.1/util/neo_hdf.h	Mon Aug  6 14:28:17 2001
+++ clearsilver-0.2/util/neo_hdf.h	Tue Aug  7 14:58:46 2001
@@ -50,9 +50,12 @@
 NEOERR* hdf_read_file (HDF *hdf, char *path);
 NEOERR* hdf_write_file (HDF *hdf, char *path);
 
+NEOERR* hdf_read_string (HDF *hdf, char *s);
+NEOERR* hdf_write_string (HDF *hdf, char **s);
+
 NEOERR* hdf_dump (HDF *hdf, char *prefix);
 NEOERR* hdf_dump_format (HDF *hdf, int lvl, FILE *fp);
-NEOERR* hdf_dump_str(HDF *hdf, char *prefix, STRING *str);
+NEOERR* hdf_dump_str(HDF *hdf, char *prefix, int compact, STRING *str);
 
 NEOERR* hdf_remove_tree (HDF *hdf, char *name);
 NEOERR* hdf_copy (HDF *dest_hdf, char *name, HDF *src);
