diff -Nru clearsilver-0.9.7/INSTALL clearsilver-0.9.8/INSTALL
--- clearsilver-0.9.7/INSTALL	Mon Aug 18 13:24:45 2003
+++ clearsilver-0.9.8/INSTALL	Tue Mar 16 01:43:46 2004
@@ -30,6 +30,7 @@
   --with-java=path              Set location of J2SDK
   --disable-csharp      	Disables building of csharp module
   --with-csharp=path    	Set location of csharp
+  --enable-gettext		Enable gettext message translation
  
 --disable-compression:  Currently, the CGI output code in the cgi
 kit automatically attempts to detect whether the remote browser can
@@ -43,6 +44,10 @@
 controls such as a configurable list of allowed displays, but remote
 debugging is disabled by default.
  
+--enable-gettext: Enables gettext message translation. Once enabled
+you have a new builtin function "_(<message>)". This function calls the
+function gettext (man 3 gettext).
+
 The rest of the --disable/--with either disable a specific module, or
 point ClearSilver at the right program to enable it.  The configure
 script will simple not build any module it can't find the right versions
diff -Nru clearsilver-0.9.7/Makefile clearsilver-0.9.8/Makefile
--- clearsilver-0.9.7/Makefile	Tue Nov 18 18:07:55 2003
+++ clearsilver-0.9.8/Makefile	Mon Jan  5 02:36:11 2004
@@ -82,7 +82,7 @@
 hdf:
 	@mkdir -p docs/hdf
 	@for mdir in $(SUBDIRS); do \
-		scripts/document.py --hdf --owner "ClearSilver." --outdir docs/hdf/ $$mdir/*.h; \
+		scripts/document.py --hdf --owner "ClearSilver" --outdir docs/hdf/ $$mdir/*.h; \
 	done
 
 changelog:
diff -Nru clearsilver-0.9.7/acconfig.h clearsilver-0.9.8/acconfig.h
--- clearsilver-0.9.7/acconfig.h	Mon Apr 14 16:05:07 2003
+++ clearsilver-0.9.8/acconfig.h	Tue Mar 16 01:43:46 2004
@@ -56,6 +56,9 @@
 /* Does your system have Berkeley DB v2 ? */
 #undef HAVE_DB2
 
+/* Enable support for gettext message translation */
+#undef ENABLE_GETTEXT
+
 @BOTTOM@
 
 #endif /* __CS_CONFIG_H_ */
diff -Nru clearsilver-0.9.7/cgi/cgi.c clearsilver-0.9.8/cgi/cgi.c
--- clearsilver-0.9.7/cgi/cgi.c	Tue Nov 18 17:54:37 2003
+++ clearsilver-0.9.8/cgi/cgi.c	Thu Mar 25 23:13:11 2004
@@ -199,6 +199,7 @@
   {"HTTP_USER_AGENT", "UserAgent"},
   {"HTTP_IF_MODIFIED_SINCE", "IfModifiedSince"},
   {"HTTP_REFERER", "Referer"},
+  {"HTTP_VIA", "Via"},
   /* SOAP */
   {"HTTP_SOAPACTION", "Soap.Action"},
   {NULL, NULL}
@@ -403,12 +404,14 @@
   NEOERR *err = STATUS_OK;
   char *t, *k, *v, *l;
   char buf[256];
+  char unnamed[10];
+  int unnamed_count = 0;
   HDF *obj, *child;
 
-  if (query)
+  if (query && *query)
   {
     k = strtok_r(query, "&", &l);
-    while (k)
+    while (k && *k)
     {
       v = strchr(k, '=');
       if (v == NULL)
@@ -420,9 +423,23 @@
 	*v = '\0';
 	v++;
       }
+
+
+      /* Check for some invalid query strings */
+      if (*k == 0) { 
+        /*  '?=foo' gets mapped in as Query._1=foo */
+        snprintf(unnamed,sizeof(unnamed), "_%d", unnamed_count++);
+        k = unnamed;
+      } else if (*k == '.') {
+        /* an hdf element can't start with a period */
+        *k = '_';
+      }
       snprintf(buf, sizeof(buf), "Query.%s", cgi_url_unescape(k));
+
       if (!(cgi->ignore_empty_form_vars && (*v == '\0')))
       {
+
+
 	cgi_url_unescape(v);
 	obj = hdf_get_obj (cgi->hdf, buf);
 	if (obj != NULL)
@@ -452,6 +469,15 @@
 	  if (err != STATUS_OK) break;
 	}
 	err = hdf_set_value (cgi->hdf, buf, v);
+	if (nerr_match(err, NERR_ASSERT)) {
+	  STRING str;
+
+	  string_init(&str);
+	  nerr_error_string(err, &str);
+	  ne_warn("Unable to set Query value: %s = %s: %s", buf, v, str.buf);
+	  string_clear(&str);
+	  nerr_ignore(&err);
+	}
 	if (err != STATUS_OK) break;
       }
       k = strtok_r(NULL, "&", &l);
@@ -545,6 +571,15 @@
     if (k[0] && v[0])
     {
       err = hdf_set_value (obj, k, v);
+      if (nerr_match(err, NERR_ASSERT)) {
+	STRING str;
+
+	string_init(&str);
+	nerr_error_string(err, &str);
+	ne_warn("Unable to set Cookie value: %s = %s: %s", k, v, str.buf);
+	string_clear(&str);
+	nerr_ignore(&err);
+      }
       if (err) break;
     }
     k = l;
@@ -1242,7 +1277,8 @@
     {
       char *dest;
       static int gz_magic[2] = {0x1f, 0x8b}; /* gzip magic header */
-      unsigned long crc = 0;
+      char gz_buf[20]; /* gzip header/footer buffer, len of header is 10 bytes */
+      unsigned int crc = 0;
       int len2;
 
       if (use_gzip)
@@ -1260,8 +1296,14 @@
 	  {
 	    if (use_gzip)
 	    {
-	      err = cgiwrap_writef("%c%c%c%c%c%c%c%c%c%c", gz_magic[0], gz_magic[1],
-		  Z_DEFLATED, 0 /*flags*/, 0,0,0,0 /*time*/, 0 /*xflags*/, OS_CODE);
+	      /* I'm using sprintf instead of cgiwrap_writef since
+	       * the wrapper writef might not handle values with
+	       * embedded NULLs... though I should fix the python one
+	       * now as well */
+	      sprintf(gz_buf, "%c%c%c%c%c%c%c%c%c%c", gz_magic[0], gz_magic[1],
+		  Z_DEFLATED, 0 /*flags*/, 0,0,0,0 /*time*/, 0 /*xflags*/, 
+		  OS_CODE);
+	      err = cgiwrap_write(gz_buf, 10);
 	    }
 	    if (err != STATUS_OK) break;
 	    err = cgiwrap_write(dest, len2);
@@ -1269,9 +1311,17 @@
 
 	    if (use_gzip)
 	    {
-	      err = cgiwrap_writef("%c%c%c%c", (0xff & (crc >> 0)), (0xff & (crc >> 8)), (0xff & (crc >> 16)), (0xff & (crc >> 24)));
-	      if (err != STATUS_OK) break;
-	      err = cgiwrap_writef("%c%c%c%c", (0xff & (str->len >> 0)), (0xff & (str->len >> 8)), (0xff & (str->len >> 16)), (0xff & (str->len >> 24)));
+	      /* write crc and len in network order */
+	      sprintf(gz_buf, "%c%c%c%c%c%c%c%c", 
+		  (0xff & (crc >> 0)), 
+		  (0xff & (crc >> 8)), 
+		  (0xff & (crc >> 16)), 
+		  (0xff & (crc >> 24)),
+		  (0xff & (str->len >> 0)), 
+		  (0xff & (str->len >> 8)), 
+		  (0xff & (str->len >> 16)), 
+		  (0xff & (str->len >> 24)));
+	      err = cgiwrap_write(gz_buf, 8);
 	      if (err != STATUS_OK) break;
 	    }
 	  }
@@ -1449,8 +1499,6 @@
 
 void cgi_vredirect (CGI *cgi, int uri, char *fmt, va_list ap)
 {
-  char *host;
-
   cgiwrap_writef ("Status: 302\r\n");
   cgiwrap_writef ("Content-Type: text/html\r\n");
   cgiwrap_writef ("Pragma: no-cache\r\n");
@@ -1463,10 +1511,28 @@
   }
   else
   {
+    char *host;
+    int https = 0;
+
+    if (!strcmp(hdf_get_value(cgi->hdf, "CGI.HTTPS", "off"), "on"))
+    {
+      https = 1;
+    }
+
     host = hdf_get_value (cgi->hdf, "HTTP.Host", NULL);
     if (host == NULL)
       host = hdf_get_value (cgi->hdf, "CGI.ServerName", NULL);
-    cgiwrap_writef ("Location: http://%s", host);
+
+    cgiwrap_writef ("Location: %s://%s", https ? "https" : "http", host);
+
+    if ((strchr(host, ':') == NULL)) {
+      int port = hdf_get_int_value(cgi->hdf, "CGI.ServerPort", 80);
+
+      if (!((https && port == 443) || (!https && port == 80)))
+      {
+	cgiwrap_writef(":%d", port);
+      }
+    }
   }
   cgiwrap_writevf (fmt, ap);
   cgiwrap_writef ("\r\n\r\n");
@@ -1536,46 +1602,61 @@
   return NULL;
 }
 
+/* For more information about Cookies, see:
+ * The original Netscape Cookie Spec:
+ * http://wp.netscape.com/newsref/std/cookie_spec.html
+ *
+ * HTTP State Management Mechanism
+ * http://www.ietf.org/rfc/rfc2109.txt
+ */
+
 NEOERR *cgi_cookie_set (CGI *cgi, char *name, char *value, char *path, 
-        char *domain, char *time_str, int persistent)
+        char *domain, char *time_str, int persistent, int secure)
 {
+  NEOERR *err;
+  STRING str;
   char my_time[256];
 
   if (path == NULL) path = "/";
-  if (persistent)
-  {
-    if (time_str == NULL)
+
+  string_init(&str);
+  do {
+    err = string_appendf(&str, "Set-Cookie: %s=%s; path=%s", name, value, path);
+    if (err) break;
+
+    if (persistent)
     {
-      /* Expires in one year */
-      time_t exp_date = time(NULL) + 31536000;
+      if (time_str == NULL)
+      {
+	/* Expires in one year */
+	time_t exp_date = time(NULL) + 31536000;
 
-      strftime (my_time, 48, "%A, %d-%b-%Y 23:59:59 GMT",
-	  gmtime (&exp_date));
-      time_str = my_time;
+	strftime (my_time, 48, "%A, %d-%b-%Y 23:59:59 GMT",
+	    gmtime (&exp_date));
+	time_str = my_time;
+      }
+      err = string_appendf(&str, "; expires=%s", time_str);
+      if (err) break;
     }
     if (domain)
     {
-      cgiwrap_writef ("Set-Cookie: %s=%s; path=%s; expires=%s; domain=%s\r\n",
-	  name, value, path, time_str, domain);
+      err = string_appendf(&str, "; domain=%s", domain);
+      if (err) break;
     }
-    else
+    if (secure)
     {
-      cgiwrap_writef ("Set-Cookie: %s=%s; path=%s; expires=%s\r\n", name, 
-	  value, path, time_str);
+      err = string_append(&str, "; secure");
+      if (err) break;
     }
-  }
-  else
+    err = string_append(&str, "\r\n");
+  } while (0);
+  if (err) 
   {
-    if (domain)
-    {
-      cgiwrap_writef ("Set-Cookie: %s=%s; path=%s; domain=%s\r\n", name, 
-	  value, path, domain);
-    }
-    else
-    {
-      cgiwrap_writef ("Set-Cookie: %s=%s; path=%s\r\n", name, value, path);
-    }
+    string_clear(&str);
+    return nerr_pass(err);
   }
+  cgiwrap_write(str.buf, str.len);
+  string_clear(&str);
   return STATUS_OK;
 }
 
diff -Nru clearsilver-0.9.7/cgi/cgi.h clearsilver-0.9.8/cgi/cgi.h
--- clearsilver-0.9.7/cgi/cgi.h	Wed Oct  8 16:45:40 2003
+++ clearsilver-0.9.8/cgi/cgi.h	Tue Mar 16 01:40:56 2004
@@ -421,11 +421,12 @@
  *                   Wdy, DD-Mon-YYYY HH:MM:SS GMT.  Only used if
  *                   persistent.  Default is one year from time of call.
  *        persistent - cookie will be stored by the browser between sessions
+ *        secure - cookie will only be sent over secure connections
  * Output: None
  * Return: NERR_IO
  */
 NEOERR *cgi_cookie_set (CGI *cgi, char *name, char *value, char *path, 
-    char *domain, char *time_str, int persistent);
+    char *domain, char *time_str, int persistent, int secure);
 
 /*
  * Function: cgi_cookie_clear - clear browser cookie
diff -Nru clearsilver-0.9.7/configure.in clearsilver-0.9.8/configure.in
--- clearsilver-0.9.7/configure.in	Mon Sep 22 19:09:38 2003
+++ clearsilver-0.9.8/configure.in	Tue Mar 16 01:43:46 2004
@@ -437,6 +437,21 @@
   fi
 fi
 
+AC_ARG_ENABLE(gettext, [  --enable-gettext	Enables gettext message translation],
+  [if test $enableval = yes; then
+     dnl Check for gettext
+     AC_CHECK_FUNC(gettext, [
+       cs_cv_libintl=no
+       AC_CHECK_HEADER(libintl.h, [cs_cv_libintl=yes])
+       if test $cs_cv_libintl = yes; then
+         AC_DEFINE(ENABLE_GETTEXT)
+         AC_MSG_RESULT(Enabling gettext message translation)
+       else
+         AC_MSG_RESULT(not found)
+       fi
+     ])
+   fi])
+
 
 AC_SUBST(RANLIB)
 AC_SUBST(AR)
diff -Nru clearsilver-0.9.7/cs/Makefile clearsilver-0.9.8/cs/Makefile
--- clearsilver-0.9.7/cs/Makefile	Sun Jul 27 09:26:19 2003
+++ clearsilver-0.9.8/cs/Makefile	Thu Apr 22 00:41:53 2004
@@ -27,7 +27,12 @@
 
 TARGETS = $(CS_LIB) $(CSTEST_EXE) $(CSR_EXE) test
 
-CS_TESTS = test.cs test2.cs test3.cs test4.cs test5.cs test6.cs test7.cs test8.cs test9.cs test10.cs test11.cs test12.cs test13.cs test14.cs test15.cs test16.cs test17.cs test18.cs test_var.cs test_paren.cs test_chuck.cs test_trak1.cs test_iter.cs test_each_array.cs test_name.cs test_with.cs test_numbers.cs
+CS_TESTS = test.cs test2.cs test3.cs test4.cs test5.cs test6.cs test7.cs \
+           test8.cs test9.cs test10.cs test11.cs test12.cs test13.cs \
+	   test14.cs test15.cs test16.cs test17.cs test18.cs test_var.cs \
+	   test_paren.cs test_chuck.cs test_trak1.cs test_iter.cs \
+	   test_each_array.cs test_name.cs test_with.cs test_numbers.cs \
+	   test_splice.cs
 
 all: $(TARGETS)
 
@@ -53,7 +58,7 @@
 	./cstest test_tag.hdf test_tag.cs > test_tag.cs.gold
 	@echo "Generated Gold Files"
 
-test: $(CSTEST_EXE) $(CS_TESTS)
+test: $(CSTEST_EXE) $(CS_TESTS) 
 	@echo "Running cs regression tests"
 	@failed=0; \
 	for test in $(CS_TESTS); do \
diff -Nru clearsilver-0.9.7/cs/cs.h clearsilver-0.9.8/cs/cs.h
--- clearsilver-0.9.7/cs/cs.h	Fri Aug  8 23:54:12 2003
+++ clearsilver-0.9.8/cs/cs.h	Sat Nov 29 23:24:14 2003
@@ -78,6 +78,7 @@
   CS_OP_RBRACKET = (1<<20),
 
   CS_OP_DOT = (1<<21),
+  CS_OP_COMMA = (1<<22),
 
   /* Types */
   CS_TYPE_STRING = (1<<25),
diff -Nru clearsilver-0.9.7/cs/csparse.c clearsilver-0.9.8/cs/csparse.c
--- clearsilver-0.9.7/cs/csparse.c	Mon Aug  4 18:49:40 2003
+++ clearsilver-0.9.8/cs/csparse.c	Thu Apr 22 00:44:20 2004
@@ -20,6 +20,11 @@
 #include <stdio.h>
 #include <ctype.h>
 #include <limits.h>
+#include <stdarg.h>
+
+#ifdef ENABLE_GETTEXT
+#include <libintl.h>
+#endif
 
 #include "util/neo_misc.h"
 #include "util/neo_err.h"
@@ -90,6 +95,8 @@
 static NEOERR *alt_eval (CSPARSE *parse, CSTREE *node, CSTREE **next);
 
 static NEOERR *render_node (CSPARSE *parse, CSTREE *node);
+static NEOERR *cs_init_internal (CSPARSE **parse, HDF *hdf, BOOL init_funcs);
+static int rearrange_for_call(CSARG **args);
 
 typedef struct _cmds
 {
@@ -719,6 +726,7 @@
   { FALSE, "[", CS_OP_LBRACKET },
   { FALSE, "]", CS_OP_RBRACKET },
   { FALSE, ".", CS_OP_DOT },
+  { FALSE, ",", CS_OP_COMMA },
   { FALSE, NULL, 0 }
 };
 
@@ -874,15 +882,16 @@
 }
 
 CSTOKEN_TYPE OperatorOrder[] = {
-   CS_OP_OR,
-   CS_OP_AND,
-   CS_OP_NOT | CS_OP_EXISTS,
-   CS_OP_EQUAL | CS_OP_NEQUAL,
-   CS_OP_GT | CS_OP_GTE | CS_OP_LT | CS_OP_LTE,
-   CS_OP_ADD | CS_OP_SUB, 
-   CS_OP_MULT | CS_OP_DIV | CS_OP_MOD | CS_OP_DOT,
-   CS_OP_LBRACKET,
-   0
+  CS_OP_COMMA,
+  CS_OP_OR,
+  CS_OP_AND,
+  CS_OP_NOT | CS_OP_EXISTS,
+  CS_OP_EQUAL | CS_OP_NEQUAL,
+  CS_OP_GT | CS_OP_GTE | CS_OP_LT | CS_OP_LTE,
+  CS_OP_ADD | CS_OP_SUB, 
+  CS_OP_MULT | CS_OP_DIV | CS_OP_MOD | CS_OP_DOT,
+  CS_OP_LBRACKET,
+  0
 };
 
 static char *expand_token_type(CSTOKEN_TYPE t_type, int more)
@@ -910,6 +919,7 @@
     case CS_OP_LBRACKET: return "[";
     case CS_OP_RBRACKET: return "]";
     case CS_OP_DOT : return ".";
+    case CS_OP_COMMA : return ",";
     case CS_TYPE_STRING: return more ? "STRING" : "s";
     case CS_TYPE_NUM: return more ? "NUM" : "n";
     case CS_TYPE_VAR: return more ? "VAR" : "v";
@@ -947,7 +957,6 @@
   return buf;
 }
 
-
 static NEOERR *parse_expr2 (CSPARSE *parse, CSTOKEN *tokens, int ntokens, int lvalue, CSARG *arg)
 {
   NEOERR *err;
@@ -1065,23 +1074,43 @@
 	    find_context(parse, -1, tmp, sizeof(tmp)), 
 	    expand_token_type(tokens[x].type, 0));
       }
-      if (OperatorOrder[op] & CS_OPS_UNARY)
+      if (tokens[x].type & OperatorOrder[op])
       {
-	if (x == 0 && tokens[x].type & OperatorOrder[op])
+	if (OperatorOrder[op] & CS_OPS_UNARY)
+	{
+	  if (x == 0)
+	  {
+	    arg->op_type = tokens[x].type;
+	    arg->expr1 = (CSARG *) calloc (1, sizeof (CSARG));
+	    if (arg->expr1 == NULL)
+	      return nerr_raise (NERR_NOMEM, 
+		  "%s Unable to allocate memory for expression", 
+		  find_context(parse, -1, tmp, sizeof(tmp)));
+	    err = parse_expr2(parse, tokens + 1, ntokens-1, lvalue, arg->expr1);
+	    return nerr_pass(err);
+	  }
+	}
+	else if (tokens[x].type == CS_OP_COMMA)
 	{
+	  /* Technically, comma should be a left to right, not right to
+	   * left, so we're going to build up the arguments in reverse
+	   * order... */
 	  arg->op_type = tokens[x].type;
+	  /* The actual argument is expr1 */
 	  arg->expr1 = (CSARG *) calloc (1, sizeof (CSARG));
-	  if (arg->expr1 == NULL)
+	  /* The previous argument is next */
+	  arg->next = (CSARG *) calloc (1, sizeof (CSARG));
+	  if (arg->expr1 == NULL || arg->next == NULL)
 	    return nerr_raise (NERR_NOMEM, 
 		"%s Unable to allocate memory for expression", 
 		find_context(parse, -1, tmp, sizeof(tmp)));
-	  err = parse_expr2(parse, tokens + 1, ntokens-1, lvalue, arg->expr1);
-	  return nerr_pass(err);
+	  err = parse_expr2(parse, tokens + x + 1, ntokens-x-1, lvalue, arg->expr1);
+	  if (err) return nerr_pass (err);
+	  err = parse_expr2(parse, tokens, x, lvalue, arg->next);
+	  if (err) return nerr_pass (err);
+	  return STATUS_OK;
 	}
-      }
-      else 
-      {
-	if (tokens[x].type & OperatorOrder[op])
+	else
 	{
 	  arg->op_type = tokens[x].type;
 	  arg->expr2 = (CSARG *) calloc (1, sizeof (CSARG));
@@ -1143,11 +1172,12 @@
     return nerr_pass(err);
   }
 
-  /* function call (we only handle a single arg for now) */
+  /* function call */
   if ((tokens[0].type & CS_TYPE_VAR) && tokens[1].type == CS_OP_LPAREN && 
       tokens[x].type == CS_OP_RPAREN)
   {
     CS_FUNCTION *csf;
+    int nargs;
 
     if (tokens[0].len >= 0)
       tokens[0].value[tokens[0].len] = '\0';
@@ -1174,6 +1204,15 @@
 	  "%s Unable to allocate memory for expression", 
 	  find_context(parse, -1, tmp, sizeof(tmp)));
     err = parse_expr2(parse, tokens + 2, ntokens-3, lvalue, arg->expr1);
+    if (err) return nerr_pass(err);
+    nargs = rearrange_for_call(&(arg->expr1));
+    if (nargs != arg->function->n_args)
+    {
+      return nerr_raise (NERR_PARSE, 
+	  "%s Incorrect number of arguments in call to %s, expected %d, got %d",
+	  find_context(parse, -1, tmp, sizeof(tmp)), tokens[0].value, 
+	  arg->function->n_args, nargs);
+    }
     return nerr_pass(err);
   }
 
@@ -1546,6 +1585,36 @@
   return v;
 }
 
+char *arg_eval_str_alloc (CSPARSE *parse, CSARG *arg)
+{
+  char *s = NULL;
+  char buf[256];
+  long int n_val;
+
+  switch ((arg->op_type & CS_TYPES))
+  {
+    case CS_TYPE_STRING:
+      s = arg->s;
+      break;
+    case CS_TYPE_VAR:
+      s = var_lookup (parse, arg->s);
+      break;
+    case CS_TYPE_NUM:
+    case CS_TYPE_VAR_NUM:
+      s = buf;
+      n_val = arg_eval_num (parse, arg);
+      snprintf (buf, sizeof(buf), "%ld", n_val);
+      break;
+    default:
+      ne_warn ("Unsupported type %s in arg_eval_str_alloc", 
+	  expand_token_type(arg->op_type, 1));
+      s = NULL;
+      break;
+  }
+  if (s) return strdup(s);
+  return NULL;
+}
+
 #if DEBUG_EXPR_EVAL
 static void expand_arg (CSPARSE *parse, int depth, char *where, CSARG *arg)
 {
@@ -1840,6 +1909,29 @@
 	  break;
       }
     }
+    else if (expr->op_type == CS_OP_COMMA)
+    {
+      /* The comma operator, like in C, we return the value of the right
+       * most argument, in this case that's expr1, but we still need to
+       * evaluate the other stuff */
+      if (expr->next)
+      {
+	err = eval_expr (parse, expr->next, &arg2);
+#if DEBUG_EXPR_EVAL
+	expand_arg(parse, _depth, "arg2", &arg2);
+#endif
+	if (err) return nerr_pass(err);
+	if (arg2.alloc) free(arg2.s);
+      }
+      *result = arg1;
+      /* we transfer ownership of the string here.. ugh */
+      if (arg1.alloc) arg1.alloc = 0;
+#if DEBUG_EXPR_EVAL
+      expand_arg(parse, _depth, "result", result);
+      _depth--;
+#endif
+      return STATUS_OK;
+    }
     else
     {
       err = eval_expr (parse, expr->expr2, &arg2);
@@ -1924,7 +2016,7 @@
       }
       else if ((arg1.op_type & (CS_TYPE_NUM | CS_TYPE_VAR_NUM)) ||
 	  (arg2.op_type & (CS_TYPE_NUM | CS_TYPE_VAR_NUM)) ||
-	  (expr->op_type & (CS_OP_AND | CS_OP_OR | CS_OP_SUB | CS_OP_MULT | CS_OP_DIV | CS_OP_MOD | CS_OP_GT | CS_OP_GTE | CS_OP_LT | CS_OP_LTE )))
+	  (expr->op_type & (CS_OP_AND | CS_OP_OR | CS_OP_SUB | CS_OP_MULT | CS_OP_DIV | CS_OP_MOD | CS_OP_GT | CS_OP_GTE | CS_OP_LT | CS_OP_LTE)))
       {
 	/* eval as num */
 	err = eval_expr_num(parse, &arg1, &arg2, expr->op_type, result);
@@ -2016,7 +2108,7 @@
       }
 
       do {
-	err = cs_init(&cs, parse->hdf);
+	err = cs_init_internal(&cs, parse->hdf, FALSE);
 	if (err) break;
 	cs->functions = parse->functions;
 	err = cs_parse_string(cs, s, strlen(s));
@@ -2058,7 +2150,7 @@
     {
       CSPARSE *cs = NULL;
       do {
-	err = cs_init(&cs, parse->hdf);
+	err = cs_init_internal(&cs, parse->hdf, FALSE);
 	if (err) break;
 	cs->functions = parse->functions;
 	err = cs_parse_file(cs, s);
@@ -2540,45 +2632,60 @@
   return STATUS_OK;
 }
 
-static char* get_arg(char* top)
+static int rearrange_for_call(CSARG **args)
 {
-  int mode = 0;
-  char* p;
-  for (p = top; *p; p++) {
-    if (mode == 0) {
-      if (*p == ',') {
-	return p;
-      } else if (*p == '"') {
-	mode = 1;
-      }
-    } else {
-      if (*p == '"') {
-	mode = 0;
-      }
-    }
+  CSARG *larg = NULL;
+  CSARG *carg = *args;
+  CSARG *vargs = NULL;
+  int nargs = 0;
+
+  /* multiple argument case, we have to walk the args and reverse
+   * them. Also handles single arg case since its the same as the
+   * last arg */
+  while (carg)
+  {
+    nargs++;
+    if (carg->op_type != CS_OP_COMMA)
+    {
+      /* last argument */
+      if (vargs)
+	carg->next = vargs;
+      vargs = carg;
+      break;
+    }
+    if (vargs)
+      carg->expr1->next = vargs;
+    vargs = carg->expr1;
+    larg = carg;
+    carg = carg->next;
+    /* dealloc comma, but not its descendents */
+    larg->next = NULL;
+    larg->expr1 = NULL;
+    dealloc_arg(&larg);
   }
-  return NULL;
-}
+  *args = vargs;
 
+  return nargs;
+}
 
 static NEOERR *call_parse (CSPARSE *parse, int cmd, char *arg)
 {
   NEOERR *err;
   CSTREE *node;
   CS_MACRO *macro;
-  CSARG *carg, *larg = NULL;
+  CSARG *carg;
   char *s, *a = NULL;
   char tmp[256];
   char name[256];
   int x = 0;
-  BOOL last = FALSE;
+  int nargs = 0;
 
   err = alloc_node (&node);
   if (err) return nerr_pass(err);
   node->cmd = cmd;
   arg++;
   s = arg;
-  while (x < 256 && *s && *s != ' ' && *s != '#' && *s != '(')
+  while (x < sizeof(name) && *s && *s != ' ' && *s != '#' && *s != '(')
   {
     name[x++] = *s;
     s++;
@@ -2620,54 +2727,32 @@
   }
   *a = '\0';
 
-  x = 0;
-  while (*s)
+  while (*s && isspace(*s)) s++;
+  /* No arguments case */
+  if (*s == '\0')
   {
-    while (*s && isspace(*s)) s++;
-    /* No arguments case */
-    if (*s == '\0' && x == 0) break;
-    /* Empty argument case */
-    if (*s == '\0')
-    {
-      err =nerr_raise (NERR_PARSE, 
-	"%s Missing argument in call %s",
-	find_context(parse, -1, tmp, sizeof(tmp)), arg);
-      break;
-    }
-    a = get_arg(s);
-    if (a == NULL)
-    {
-      last = TRUE;
-    }
-    else
-    {
-      *a = '\0';
-    }
-    carg = (CSARG *) calloc (1, sizeof(CSARG));
-    if (carg == NULL)
-    {
-      err = nerr_raise (NERR_NOMEM, 
-	  "%s Unable to allocate memory for CSARG in call %s",
-	  find_context(parse, -1, tmp, sizeof(tmp)), arg);
-      break;
-    }
-    if (larg == NULL)
+    nargs = 0;
+  }
+  else 
+  {
+    /* Parse arguments case */
+    do
     {
+      carg = (CSARG *) calloc (1, sizeof(CSARG));
+      if (carg == NULL)
+      {
+	err = nerr_raise (NERR_NOMEM, 
+	    "%s Unable to allocate memory for CSARG in call %s",
+	    find_context(parse, -1, tmp, sizeof(tmp)), arg);
+	break;
+      }
+      err = parse_expr (parse, s, 0, carg);
+      if (err) break;
+      nargs = rearrange_for_call(&carg);
       node->vargs = carg;
-      larg = carg;
-    }
-    else
-    {
-      larg->next = carg;
-      larg = carg;
-    }
-    x++;
-    err = parse_expr (parse, s, 0, carg);
-    if (err) break;
-    if (last == TRUE) break;
-    s = a+1;
+    } while (0);
   }
-  if (!err && x != macro->n_args)
+  if (!err && nargs != macro->n_args)
   {
     err = nerr_raise (NERR_PARSE, 
 	"%s Incorrect number of arguments, expected %d, got %d in call to macro %s: %s",
@@ -3093,9 +3178,6 @@
 {
   CS_FUNCTION *csf;
 
-  if (n_args != 1)
-    return nerr_raise(NERR_ASSERT, "Currently, only 1 argument functions are supported");
-
   /* Should we validate the parseability of the name? */
 
   csf = parse->functions;
@@ -3110,12 +3192,14 @@
   }
   csf = (CS_FUNCTION *) calloc (1, sizeof(CS_FUNCTION));
   if (csf == NULL)
-    return nerr_raise(NERR_NOMEM, "Unable to allocate memory to register function %s", funcname);
+    return nerr_raise(NERR_NOMEM, 
+	"Unable to allocate memory to register function %s", funcname);
   csf->name = strdup(funcname);
   if (csf->name == NULL)
   {
     free(csf);
-    return nerr_raise(NERR_NOMEM, "Unable to allocate memory to register function %s", funcname);
+    return nerr_raise(NERR_NOMEM, 
+	"Unable to allocate memory to register function %s", funcname);
   }
   csf->function = function;
   csf->n_args = n_args;
@@ -3125,7 +3209,7 @@
   return STATUS_OK;
 }
 
-static NEOERR * _builtin_len(CSPARSE *parse, CS_FUNCTION *csf, CSARG *args, CSARG *result)
+static NEOERR * _builtin_subcount(CSPARSE *parse, CS_FUNCTION *csf, CSARG *args, CSARG *result)
 {
   HDF *obj;
   int count = 0;
@@ -3147,6 +3231,32 @@
     }
     result->n = count;
   }
+  else
+  {
+    // everything else has zero children
+    result->n = 0;
+  }
+
+  return STATUS_OK;
+}
+
+static NEOERR * _builtin_str_length(CSPARSE *parse, CS_FUNCTION *csf, CSARG *args, CSARG *result)
+{
+  HDF *obj;
+
+  result->op_type = CS_TYPE_NUM;
+  result->n = 0;
+
+  if (args->op_type & CS_TYPE_VAR)
+  {
+    obj = var_lookup_obj (parse, args->s);
+    if (obj) {
+      char *s = hdf_obj_value(obj);
+      if (s) {
+	result->n = strlen(s);
+      }
+    }
+  }
   else if (args->op_type & CS_TYPE_STRING)
   {
     result->n = strlen(args->s);
@@ -3154,6 +3264,7 @@
   return STATUS_OK;
 }
 
+
 static NEOERR * _builtin_name(CSPARSE *parse, CS_FUNCTION *csf, CSARG *args, CSARG *result)
 {
   HDF *obj;
@@ -3182,6 +3293,147 @@
   return STATUS_OK;
 }
 
+/* This is similar to python's PyArg_ParseTuple, :
+ *   s - string (allocated)
+ *   i - int
+ *   A - arg ptr (maybe later)
+ */
+static NEOERR * cs_arg_parsev(CSPARSE *parse, CSARG *args, char *fmt, 
+    va_list ap) 
+{
+  NEOERR *err = STATUS_OK;
+  char **s;
+  long int *i;
+  CSARG val;
+
+  while (*fmt || args || err)
+  {
+    memset(&val, 0, sizeof(val));
+    err = eval_expr(parse, args, &val);
+    if (err) return nerr_pass(err);
+
+    switch (*fmt)
+    {
+      case 's':
+	s = va_arg(ap, char **);
+	if (s == NULL)
+	{
+	  err = nerr_raise(NERR_ASSERT, 
+	      "Invalid number of arguments in call to cs_arg_parse");
+	  break;
+	}
+	*s = arg_eval_str_alloc(parse, &val);
+	break;
+      case 'i':
+	i = va_arg(ap, long int *);
+	if (i == NULL)
+	{
+	  err = nerr_raise(NERR_ASSERT, 
+	      "Invalid number of arguments in call to cs_arg_parse");
+	  break;
+	}
+	*i = arg_eval_num(parse, &val);
+	break;
+      default:
+	break;
+    }
+    fmt++;
+    args = args->next;
+    if (val.alloc) free(val.s);
+  }
+  if (err) return nerr_pass(err);
+  return STATUS_OK;
+}
+
+static NEOERR * cs_arg_parse(CSPARSE *parse, CSARG *args, char *fmt, ...)
+{
+  NEOERR *err;
+  va_list ap;
+
+  va_start(ap, fmt);
+  err = cs_arg_parsev(parse, args, fmt, ap);
+  va_end(ap);
+  return nerr_pass(err);
+}
+
+static NEOERR * _builtin_str_slice (CSPARSE *parse, CS_FUNCTION *csf, CSARG *args, CSARG *result)
+{
+  NEOERR *err;
+  char *s = NULL;
+  char *slice;
+  int b = 0;
+  int e = 0;
+  int len;
+
+  result->op_type = CS_TYPE_STRING;
+  result->s = "";
+
+  err = cs_arg_parse(parse, args, "sii", &s, &b, &e);
+  if (err) return nerr_pass(err);
+  /* If null, return empty string */
+  if (s == NULL) return STATUS_OK;
+  len = strlen(s);
+  if (b < 0 && e == 0) e = len;
+  if (b < 0) b += len;
+  if (e < 0) e += len;
+  if (e > len) e = len;
+  /* Its the whole string */
+  if (b == 0 && e == len)
+  {
+    result->s = s;
+    result->alloc = 1;
+    return STATUS_OK;
+  }
+  if (e < b) b = e;
+  if (b == e) 
+  {
+    /* If null, return empty string */
+    free(s);
+    return STATUS_OK;
+  }
+  slice = (char *) malloc (sizeof(char) * (e-b+1));
+  if (slice == NULL)
+    return nerr_raise(NERR_NOMEM, "Unable to allocate memory for string slice");
+  strncpy(slice, s + b, e-b);
+  free(s);
+  slice[e-b] = '\0';
+
+  result->s = slice;
+  result->alloc = 1;
+
+  return STATUS_OK;
+}
+
+#ifdef ENABLE_GETTEXT
+static NEOERR * _builtin_gettext(CSPARSE *parse, CS_FUNCTION *csf, CSARG *args, CSARG *result)
+{
+  HDF *obj;
+
+  result->op_type = CS_TYPE_STRING;
+  result->s = "";
+
+  if (args->op_type & CS_TYPE_VAR)
+  {
+    obj = var_lookup_obj (parse, args->s);
+    if (obj != NULL)
+    {
+      result->s = gettext(hdf_obj_value(obj));
+    }
+    else 
+    {
+      result->s = "";
+    }
+  }
+  else if (args->op_type & CS_TYPE_STRING)
+  {
+    result->s = gettext(args->s);
+    result->alloc = args->alloc;
+    args->alloc = 0;
+  }
+  return STATUS_OK;
+}
+#endif
+
 static NEOERR * _str_func_wrapper (CSPARSE *parse, CS_FUNCTION *csf, CSARG *args, CSARG *result)
 {
   NEOERR *err;
@@ -3224,8 +3476,11 @@
 
 
 /* **** CS Initialize/Destroy ************************************ */
+NEOERR *cs_init (CSPARSE **parse, HDF *hdf) {
+  return nerr_pass(cs_init_internal(parse, hdf, TRUE));
+}
 
-NEOERR *cs_init (CSPARSE **parse, HDF *hdf)
+static NEOERR *cs_init_internal (CSPARSE **parse, HDF *hdf, BOOL init_funcs)
 {
   NEOERR *err = STATUS_OK;
   CSPARSE *my_parse;
@@ -3238,7 +3493,6 @@
   if (my_parse == NULL)
     return nerr_raise (NERR_NOMEM, "Unable to allocate memory for CSPARSE");
 
-
   err = uListInit (&(my_parse->stack), 10, 0);
   if (err != STATUS_OK)
   {
@@ -3276,18 +3530,49 @@
     cs_destroy(&my_parse);
     return nerr_pass(err);
   }
-  err = _register_function(my_parse, "len", 1, _builtin_len);
-  if (err)
+  if (init_funcs)
   {
-    cs_destroy(&my_parse);
-    return nerr_pass(err);
+    err = _register_function(my_parse, "len", 1, _builtin_subcount);
+    if (err)
+    {
+      cs_destroy(&my_parse);
+      return nerr_pass(err);
+    }
+    err = _register_function(my_parse, "subcount", 1, _builtin_subcount);
+    if (err)
+    {
+      cs_destroy(&my_parse);
+      return nerr_pass(err);
+    }
+    err = _register_function(my_parse, "name", 1, _builtin_name);
+    if (err)
+    {
+      cs_destroy(&my_parse);
+      return nerr_pass(err);
+    }
+    err = _register_function(my_parse, "string.slice", 3, _builtin_str_slice);
+    if (err)
+    {
+      cs_destroy(&my_parse);
+      return nerr_pass(err);
+    }
+    err = _register_function(my_parse, "string.length", 1, _builtin_str_length);
+    if (err)
+    {
+      cs_destroy(&my_parse);
+      return nerr_pass(err);
+    }
+
+
   }
-  err = _register_function(my_parse, "name", 1, _builtin_name);
+#ifdef ENABLE_GETTEXT
+  err = _register_function(my_parse, "_", 1, _builtin_gettext);
   if (err)
   {
     cs_destroy(&my_parse);
     return nerr_pass(err);
   }
+#endif
   my_parse->tag = hdf_get_value(hdf, "Config.TagStart", "cs");
   my_parse->taglen = strlen(my_parse->tag);
   my_parse->hdf = hdf;
diff -Nru clearsilver-0.9.7/cs/test.hdf clearsilver-0.9.8/cs/test.hdf
--- clearsilver-0.9.7/cs/test.hdf	Wed Jun 18 11:43:52 2003
+++ clearsilver-0.9.8/cs/test.hdf	Thu Feb 26 19:32:36 2004
@@ -3,6 +3,11 @@
 var =
 
 
+Numbers {
+  hdf9 = 9
+  hdf14 = 14
+}
+
 Blah = wow
 Foo = Worn Out
 
diff -Nru clearsilver-0.9.7/cs/test15.cs clearsilver-0.9.8/cs/test15.cs
--- clearsilver-0.9.7/cs/test15.cs	Mon Jun 10 18:46:23 2002
+++ clearsilver-0.9.8/cs/test15.cs	Thu Feb 26 19:55:30 2004
@@ -1,6 +1,10 @@
 
 test functions
 
-<?cs var:len("This is the end of the world...") ?> == 31
+string.length <?cs var:string.length("This is the end of the world...") ?> == 31
+
+subcount <?cs var:subcount(Foo.Bar.Baz) ?> == 4
+
+len (depreciated) <?cs var:len(Foo.Bar.Baz) ?> == 4
+
 
-<?cs var:len(Foo.Bar.Baz) ?> == 4
diff -Nru clearsilver-0.9.7/cs/test15.cs.gold clearsilver-0.9.8/cs/test15.cs.gold
--- clearsilver-0.9.7/cs/test15.cs.gold	Mon Jun 10 18:46:23 2002
+++ clearsilver-0.9.8/cs/test15.cs.gold	Thu Feb 26 19:55:30 2004
@@ -2,6 +2,10 @@
 
 test functions
 
-31 == 31
+string.length 31 == 31
+
+subcount 4 == 4
+
+len (depreciated) 4 == 4
+
 
-4 == 4
diff -Nru clearsilver-0.9.7/cs/test4.cs clearsilver-0.9.8/cs/test4.cs
--- clearsilver-0.9.7/cs/test4.cs	Sun Jul 27 09:26:19 2003
+++ clearsilver-0.9.8/cs/test4.cs	Thu Feb 26 19:32:36 2004
@@ -72,13 +72,24 @@
 <?cs if:"9" > #14 ?>
 ERROR! "9" > #14
 <?cs else ?>
-right "9" > #14
+right "9" < #14
 <?cs /if ?>
 
+
+<?cs # --- explicit strings --- ?>
+
 <?cs if:"9" > "14" ?>
-ERROR! "9" > "14"
+ERROR "9" > "14" (strings)
+<?cs else ?>
+right "9" < "14" (strings)
+<?cs /if ?>
+
+<?cs # --- hdf strings --- ?>
+
+<?cs if:Numbers.hdf9 > Numbers.hdf14 ?>
+ERROR! hdf 9 > hdf 14
 <?cs else ?>
-right "9" > "14"
+right hdf "9" < hdf "14"
 <?cs /if ?>
 
 
diff -Nru clearsilver-0.9.7/cs/test4.cs.gold clearsilver-0.9.8/cs/test4.cs.gold
--- clearsilver-0.9.7/cs/test4.cs.gold	Sun Jul 27 09:59:05 2003
+++ clearsilver-0.9.8/cs/test4.cs.gold	Thu Feb 26 19:32:36 2004
@@ -59,11 +59,20 @@
 
 
 
-right "9" > #14
+right "9" < #14
 
 
 
-right "9" > "14"
+
+
+
+right "9" < "14" (strings)
+
+
+
+
+
+right hdf "9" < hdf "14"
 
 
 
diff -Nru clearsilver-0.9.7/cs/test_splice.cs clearsilver-0.9.8/cs/test_splice.cs
--- clearsilver-0.9.7/cs/test_splice.cs	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.8/cs/test_splice.cs	Thu Apr 22 00:41:53 2004
@@ -0,0 +1,11 @@
+
+<?cs var:Foo ?>
+<?cs loop:b=0,10 ?>
+<?cs loop:e=10,0,-1 ?>
+<?cs var:string.slice(Foo, b, e) ?>
+<?cs /loop ?>
+<?cs /loop ?>
+
+Check end of string slice:
+<?cs var:string.slice(Foo, -5, -1) ?>
+<?cs var:string.slice(Foo, -5, 0) ?>
diff -Nru clearsilver-0.9.7/cs/test_splice.cs.gold clearsilver-0.9.8/cs/test_splice.cs.gold
--- clearsilver-0.9.7/cs/test_splice.cs.gold	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.8/cs/test_splice.cs.gold	Thu Apr 22 00:41:53 2004
@@ -0,0 +1,272 @@
+Parsing test_splice.cs
+
+Worn Out
+
+
+Worn Out
+
+Worn Out
+
+Worn Out
+
+Worn Ou
+
+Worn O
+
+Worn 
+
+Worn
+
+Wor
+
+Wo
+
+W
+
+
+
+
+
+orn Out
+
+orn Out
+
+orn Out
+
+orn Ou
+
+orn O
+
+orn 
+
+orn
+
+or
+
+o
+
+
+
+
+
+
+
+rn Out
+
+rn Out
+
+rn Out
+
+rn Ou
+
+rn O
+
+rn 
+
+rn
+
+r
+
+
+
+
+
+
+
+
+
+n Out
+
+n Out
+
+n Out
+
+n Ou
+
+n O
+
+n 
+
+n
+
+
+
+
+
+
+
+
+
+
+
+ Out
+
+ Out
+
+ Out
+
+ Ou
+
+ O
+
+ 
+
+
+
+
+
+
+
+
+
+
+
+
+
+Out
+
+Out
+
+Out
+
+Ou
+
+O
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ut
+
+ut
+
+ut
+
+u
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+t
+
+t
+
+t
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Check end of string slice:
+n Ou
+n Out
diff -Nru clearsilver-0.9.7/cs_config.h.in clearsilver-0.9.8/cs_config.h.in
--- clearsilver-0.9.7/cs_config.h.in	Mon Sep 22 20:49:34 2003
+++ clearsilver-0.9.8/cs_config.h.in	Tue Mar 16 01:43:46 2004
@@ -102,6 +102,9 @@
 /* Does your system have Berkeley DB v2 ? */
 #undef HAVE_DB2
 
+/* Enable support for gettext message translation */
+#undef ENABLE_GETTEXT
+
 /* Define if you have the drand48 function.  */
 #undef HAVE_DRAND48
 
diff -Nru clearsilver-0.9.7/perl/ClearSilver.xs clearsilver-0.9.8/perl/ClearSilver.xs
--- clearsilver-0.9.7/perl/ClearSilver.xs	Mon Apr 14 16:05:10 2003
+++ clearsilver-0.9.8/perl/ClearSilver.xs	Tue Mar 16 00:44:19 2004
@@ -164,6 +164,20 @@
     OUTPUT:
         RETVAL
 
+int
+perlhdf_writeFile(hdf, filename)
+       ClearSilver::HDF hdf
+       char* filename
+    CODE:
+        hdf->err = hdf_write_file(hdf->hdf, filename);
+       if (hdf->err == STATUS_OK) {
+           RETVAL = 1;
+       } else {
+           RETVAL = 0;
+       }
+    OUTPUT:
+        RETVAL
+
 ClearSilver::HDF
 perlhdf_getObj(hdf, name)
 	ClearSilver::HDF hdf;
diff -Nru clearsilver-0.9.7/python/Makefile clearsilver-0.9.8/python/Makefile
--- clearsilver-0.9.7/python/Makefile	Thu Sep 11 19:02:24 2003
+++ clearsilver-0.9.8/python/Makefile	Thu Feb 26 19:34:39 2004
@@ -24,6 +24,7 @@
 all: $(TARGETS)
 
 $(NEO_UTIL_SO): setup.py $(NEO_UTIL_SRC) $(DEP_LIBS)
+	rm -f $(NEO_UTIL_SO)
 	$(PYTHON) setup.py build_ext --inplace
 
 OLD_NEO_UTIL_SO:
diff -Nru clearsilver-0.9.7/python/examples/base/CSPage.py clearsilver-0.9.8/python/examples/base/CSPage.py
--- clearsilver-0.9.7/python/examples/base/CSPage.py	Sun Aug 17 22:24:27 2003
+++ clearsilver-0.9.8/python/examples/base/CSPage.py	Thu Mar 25 12:53:55 2004
@@ -23,24 +23,25 @@
         self.environ = os.environ
 
 class CSPage:
-    def __init__(self, context, pagename=0,readDefaultHDF=1,israwpage=0):
-	if pagename == 0:
-	    raise NoPageName, "missing pagename"
-	self.pagename = pagename
-	self.readDefaultHDF = readDefaultHDF
+    def __init__(self, context, pagename=0,readDefaultHDF=1,israwpage=0,**parms):
+        if pagename == 0:
+            raise NoPageName, "missing pagename"
+        self.pagename = pagename
+        self.readDefaultHDF = readDefaultHDF
         self._israwpage = israwpage
         self.context = context
+        self._pageparms = parms
 
         self._error_template = None
 
         self.page_start_time = time.time()
-	neo_cgi.cgiWrap(context.stdin, context.stdout, context.environ)
+        neo_cgi.cgiWrap(context.stdin, context.stdout, context.environ)
         neo_cgi.IgnoreEmptyFormVars(1)
-	self.ncgi = neo_cgi.CGI()
+        self.ncgi = neo_cgi.CGI()
         self.ncgi.parse()
         self._path_num = 0
-	domain = self.ncgi.hdf.getValue("CGI.ServerName","")
-	domain = self.ncgi.hdf.getValue("HTTP.Host", domain)
+        domain = self.ncgi.hdf.getValue("CGI.ServerName","")
+        domain = self.ncgi.hdf.getValue("HTTP.Host", domain)
         self.domain = domain
         self.subclassinit()
         self.setPaths([self.ncgi.hdf.getValue("CGI.DocumentRoot","")])
@@ -97,13 +98,13 @@
                 apply(method,[])
 
     def start(self):
-	SHOULD_DISPLAY = 1
+        SHOULD_DISPLAY = 1
         if self._israwpage:
             SHOULD_DISPLAY = 0
         
-	ncgi = self.ncgi
-	
-	if self.readDefaultHDF:
+        ncgi = self.ncgi
+        
+        if self.readDefaultHDF:
             try:
                 if not self.pagename is None:
                     ncgi.hdf.readFile("%s.hdf" % self.pagename)
@@ -160,15 +161,21 @@
         if SHOULD_DISPLAY and self.pagename:
             debug_output = ncgi.hdf.getIntValue("page.debug",ncgi.hdf.getIntValue("Cookie.debug",0))
 
+            # hijack the built-in debug output method...
+            if ncgi.hdf.getValue("Query.debug","") == ncgi.hdf.getValue("Config.DebugPassword","1"):
+                ncgi.hdf.setValue("Config.DebugPassword","CSPage.py DEBUG hijack (%s)" %
+                    ncgi.hdf.getValue("Config.DebugPassword",""))
+                debug_output = 1
+
             if not debug_output:
               ncgi.hdf.setValue("Config.CompressionEnabled","1")
 
-	    # default display
-	    template_name = ncgi.hdf.getValue("Content","%s.cs" % self.pagename)
+            # default display
+            template_name = ncgi.hdf.getValue("Content","%s.cs" % self.pagename)
             # ncgi.hdf.setValue ("cgiout.charset", "utf-8");
 
             try:
-	        ncgi.display(template_name)
+                ncgi.display(template_name)
             except:
                 print "Content-Type: text/html\n\n"
                 print "CSPage: Error occured"
@@ -177,15 +184,15 @@
                 debug_output = 1
                  
 
-	    # debug output
-	    if debug_output:
-		print "<HR>\n"
-                print "Execution Time: %5.3f<BR><HR>" % (etime)
-		print "<PRE>"
-		print neo_cgi.htmlEscape(ncgi.hdf.dump())
-		print "</PRE>"
-		# ncgi.hdf.setValue("hdf.DEBUG",ncgi.hdf.dump())
-		# ncgi.display("debug.cs")
+            # debug output
+            if debug_output:
+                print "<HR>\n"
+                print "CSPage Debug, Execution Time: %5.3f<BR><HR>" % (etime)
+                print "<PRE>"
+                print neo_cgi.htmlEscape(ncgi.hdf.dump())
+                print "</PRE>"
+                # ncgi.hdf.setValue("hdf.DEBUG",ncgi.hdf.dump())
+                # ncgi.display("debug.cs")
                 
         script_name = ncgi.hdf.getValue("CGI.ScriptName","")
         if script_name:
diff -Nru clearsilver-0.9.7/python/examples/base/PassSGMLParser.py clearsilver-0.9.8/python/examples/base/PassSGMLParser.py
--- clearsilver-0.9.7/python/examples/base/PassSGMLParser.py	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.8/python/examples/base/PassSGMLParser.py	Tue Mar 16 13:18:18 2004
@@ -0,0 +1,65 @@
+##
+## deja-vu batman, didn't I write this before?
+## This parser is designed to parse an SGML document, and the default action 
+## is just to pass the data through.  Based on TestSGMLParser from sgmllib.py
+## Hmm, actually, make it a flag whether to handle unknown elements
+##
+
+from sgmllib import SGMLParser
+
+class PassSGMLParser(SGMLParser):
+  def __init__(self, fp, pass_unknown=0, verbose=0):
+    self.pass_unknown = pass_unknown
+    self.data = ""
+    self.fp = fp
+    SGMLParser.__init__(self, verbose)
+
+  def handle_data(self, data):
+    self.data = self.data + data
+
+  def flush(self):
+    data = self.data
+    if data:
+      self.data = ""
+      self.write(data)
+
+  def write (self, data):
+    return self.fp.write(data)
+
+  def write_starttag (self, tag, attrs):
+    self.flush()
+    if not attrs:
+      self.write ("<%s>" % tag)
+    else:
+      self.write ("<" + tag)
+      for name, value in attrs:
+        self.write (" " + name + '=' + '"' + value + '"')
+      self.write (">")
+
+  def write_endtag (self, tag):
+    self.flush()
+    self.write ("</%s>" % tag)
+
+  def handle_comment(self, data):
+    # don't pass comments
+    pass
+
+  def unknown_starttag(self, tag, attrs):
+    if self.pass_unknown:
+      self.write_starttag (tag, attrs)
+
+  def unknown_endtag(self, tag):
+    if self.pass_unknown:
+      self.write_endtag(tag)
+
+  def handle_entityref(self, ref):
+    self.flush()
+    self.write ("&%s;" % ref)
+
+  def handle_charref(self, ref):
+    self.flush()
+    self.write ("&#%s;" % ref)
+
+  def close(self):
+    SGMLParser.close(self)
+    self.flush()
diff -Nru clearsilver-0.9.7/python/examples/base/SafeHtml.py clearsilver-0.9.8/python/examples/base/SafeHtml.py
--- clearsilver-0.9.7/python/examples/base/SafeHtml.py	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.8/python/examples/base/SafeHtml.py	Tue Mar 16 13:18:18 2004
@@ -0,0 +1,144 @@
+## another case of deja-vu
+## this time, we want the slashdot style (what Yahoo said to do) only allow
+## certain tags... we'll make it an option
+## we'll have to tie this in some way to our HTML body displayer...
+##
+## Ok, there are basically four types of tags:
+## 1) safe - ie, <b>, <i>, etc.
+## 2) render problems - <table><form><body><frame> - these we either strip, 
+##    or we have to ensure they match
+## 3) definitely evil independent tags that we always strip
+## 4) definitely evil tags which denote a region, we strip the entire region
+
+from PassSGMLParser import PassSGMLParser
+from urllib import basejoin
+import string, sys
+import neo_cgi
+
+try:
+  from cStringIO import StringIO
+except:
+  from StringIO import StringIO
+
+class SafeHtml (PassSGMLParser):
+  _safeTags = {"P":1, "LI":1, "DD":1, "DT":1, "EM":1, "BR":1, "CITE":1, 
+               "DFN":1, "Q":1, "STRONG":1, "IMG":1, "HR":1,
+               "TR":1, "TD":1, "TH":1, "CAPTION":1, "THEAD":1, "TFOOT":1, 
+               "TBODY":1}
+  _matchTags = {"TABLE":1, "OL":1, "UL":1, "DL":1, "CENTER":1, "DIV":1, "PRE":1,
+                "SUB":1, "SUP":1, "BIG":1, "SMALL":1, "CODE":1,
+                "B":1, "I":1, "A":1, "TT":1, "BLOCKQUOTE":1, "U":1,
+                "H1":1, "H2":1, "H3":1, "H4":1, "H5":1, "H6":1, "FONT":1}
+  _skipTags = {"FORM":1, "HTML":1, "BODY":1, "EMBED":1, "AREA":1, "MAP":1,
+               "FRAME":1, "FRAMESET":1, "IFRAME":1, "META":1}
+  _stripTags = {"HEAD":1, "JAVA":1, "APPLET":1, "OBJECT":1,
+                "JAVASCRIPT":1, "LAYER":1, "STYLE":1, "SCRIPT":1}
+
+  def __init__ (self, fp, extra_safe=1, base=None, map_urls=None, new_window=1):
+    self._extra_safe = extra_safe
+    PassSGMLParser.__init__ (self, fp, extra_safe)
+    self._matchDict = {}
+    self._stripping = 0
+    self._base = base 
+    self._map_urls = map_urls
+    self._new_window = new_window
+
+  def safe_start_strip (self):
+    if self._stripping == 0:
+      self.flush()
+    self._stripping = self._stripping + 1
+
+  def safe_end_strip (self):
+    self.flush()
+    self._stripping = self._stripping - 1
+    if self._stripping < 0: self._stripping = 0
+
+  def write (self, data):
+    # sys.stderr.write("write[%d] %s\n" % (self._stripping, data))
+    if self._stripping == 0:
+      # sys.stderr.write("write %s\n" % data)
+      PassSGMLParser.write(self, data)
+
+  def cleanup_attrs (self, tag, attrs):
+    new_attrs = [] 
+    tag = string.lower(tag)
+    if self._new_window and tag == "a":
+        new_attrs.append(('target', '_blank'))
+    for name, value in attrs:
+      name = string.lower(name)
+      if name[:2] == "on": continue   ## skip any javascript events
+      if string.lower(value)[:11] == "javascript:": continue
+      if self._map_urls and name in ["action", "href", "src", "lowsrc", "background"] and value[:4] == 'cid:':
+        try:
+          value = self._map_urls[value[4:]]
+        except KeyError:
+          pass
+      else:
+          if self._base and name in ["action", "href", "src", "lowsrc", "background"]:
+            value = basejoin (self._base, value)
+          if name in ["action", "href", "src", "lowsrc", "background"]:
+            value = 'http://www.google.com/url?sa=D&q=%s' % (neo_cgi.urlEscape(value))
+      if self._new_window and tag == "a" and name == "target": continue
+      new_attrs.append ((name, value))
+    return new_attrs
+
+  def unknown_starttag(self, tag, attrs):
+    tag = string.upper(tag)
+    if SafeHtml._stripTags.has_key(tag):
+      self.safe_start_strip()
+      # sys.stderr.write("Stripping tag %s: %d\n" % (tag, self._stripping))
+    elif SafeHtml._skipTags.has_key(tag):
+      # sys.stderr.write("Skipping tag %s\n" % tag)
+      pass
+    elif SafeHtml._matchTags.has_key(tag):
+      # sys.stderr.write("Matching tag %s\n" % tag)
+      if self._matchDict.has_key(tag):
+        self._matchDict[tag] = self._matchDict[tag] + 1
+      else:
+        self._matchDict[tag] = 1
+      self.write_starttag (tag, self.cleanup_attrs(tag, attrs))
+    elif SafeHtml._safeTags.has_key(tag):
+      # sys.stderr.write("Safe tag %s\n" % tag)
+      self.write_starttag (tag, self.cleanup_attrs(tag, attrs))
+    elif not self._extra_safe:
+      # sys.stderr.write("Other tag %s\n" % tag)
+      self.write_starttag (tag, self.cleanup_attrs(tag, attrs))
+
+  def unknown_endtag(self, tag):
+    tag = string.upper(tag)
+    if SafeHtml._stripTags.has_key(tag):
+      self.safe_end_strip()
+      # sys.stderr.write("End Stripping tag %s: %d\n" % (tag, self._stripping))
+    elif SafeHtml._skipTags.has_key(tag):
+      pass
+    elif SafeHtml._matchTags.has_key(tag):
+      if self._matchDict.has_key(tag):
+        self._matchDict[tag] = self._matchDict[tag] - 1
+      self.write_endtag (tag)
+    elif SafeHtml._safeTags.has_key(tag):
+      self.write_endtag (tag)
+    elif not self._extra_safe:
+      self.write_endtag (tag)
+
+  def close (self):
+    self._stripping = 0
+    for tag in self._matchDict.keys():
+      if self._matchDict[tag] > 0:
+        for x in range (self._matchDict[tag]):
+          self.write_endtag(tag)
+    PassSGMLParser.close(self)
+
+def SafeHtmlString (s, really_safe=1, map_urls=None):
+#  fp = open("/tmp/safe_html.in", "w")
+#  fp.write(s)
+#  fp.close()
+  fp = StringIO()
+  parser = SafeHtml(fp, really_safe, map_urls=map_urls)
+  parser.feed (s)
+  parser.close ()
+  s = fp.getvalue()
+#  fp = open("/tmp/safe_html.out", "w")
+#  fp.write(s)
+#  fp.close()
+  return s
+  
diff -Nru clearsilver-0.9.7/python/examples/base/handle_error.py clearsilver-0.9.8/python/examples/base/handle_error.py
--- clearsilver-0.9.7/python/examples/base/handle_error.py	Sun Jun 15 18:54:49 2003
+++ clearsilver-0.9.8/python/examples/base/handle_error.py	Thu Mar 25 12:53:55 2004
@@ -2,7 +2,8 @@
 import traceback, sys, string, time, socket, os
 import who_calls
 
-DUMP_DIR = "/neo/data/bugs"
+#DUMP_DIR = "/neo/data/bugs"
+DUMP_DIR = "/tmp/bugs"
 
 Warning = "handle_error.Warning"
 
@@ -61,6 +62,15 @@
   except:
     handleException("Unable to dump_bug", dump = 0)
 
+def checkPaths():
+  paths = (DUMP_DIR, 
+           os.path.join (DUMP_DIR, "tmp"),
+           os.path.join (DUMP_DIR, "new"))
+  for path in paths:
+    if not os.path.isdir(path):
+      os.mkdir(path, 0755)
+
+
 def dump_bug (level, etype, msg, location=None, nhdf=None):
     global DISABLE_DUMP
     if DISABLE_DUMP: return
@@ -84,6 +94,8 @@
     global Count
     Count = Count + 1
     fname = "%d.%d_%d.%s" % (now, pid, Count, socket.gethostname())
+    checkPaths()
+
     tpath = os.path.join (DUMP_DIR, "tmp", fname)
     npath = os.path.join (DUMP_DIR, "new", fname)
     hdf.writeFile(tpath)
diff -Nru clearsilver-0.9.7/python/examples/base/hdfhelp.py clearsilver-0.9.8/python/examples/base/hdfhelp.py
--- clearsilver-0.9.7/python/examples/base/hdfhelp.py	Sat Sep 13 03:25:00 2003
+++ clearsilver-0.9.8/python/examples/base/hdfhelp.py	Thu Mar 25 12:53:55 2004
@@ -155,6 +155,95 @@
     except:
       return "Error in CS tags: %s" % neo_cgi.htmlEscape(repr(a_cs_string))
 
+def childloop(hdf):
+    children = []
+    if hdf:
+        hdf = hdf.child()
+        while hdf:
+            children.append(hdf)
+            hdf = hdf.next()
+    return children
+
+# ----------------------------
+
+class HDF_Database(odb.Database):
+  def defaultRowClass(self):
+    return HdfRow
+  def defaultRowListClass(self):
+    return HdfItemList
+
+# ----------------------------
+
+
+def loopHDF(hdf, name=None):
+  results = []
+  if name: o = hdf.getObj(name)
+  else: o = hdf
+  if o:
+    o = o.child()
+    while o:
+      results.append(o)
+      o = o.next()
+  return results
+
+
+def loopKVHDF(hdf, name=None):
+  results = []
+  if name: o = hdf.getObj(name)
+  else: o = hdf
+  if o:
+    o = o.child()
+    while o:
+      results.append((o.name(), o.value()))
+      o = o.next()
+  return results
+
+
+class hdf_iterator:
+  def __init__(self, hdf):
+    self.hdf = hdf
+    self.node = None
+    if self.hdf:
+      self.node = self.hdf.child()
+
+  def __iter__(self): return self
+
+  def next(self):
+    if not self.node:
+      raise StopIteration
+
+    ret = self.node
+    self.node = self.node.next()
+      
+    return ret
+
+class hdf_kv_iterator(hdf_iterator):
+  def next(self):
+    if not self.node: raise StopIteration
+
+    ret = (self.node.name(), self.node.value())
+    self.node = self.node.next()
+      
+    return ret
+
+class hdf_key_iterator(hdf_iterator):
+  def next(self):
+    if not self.node: raise StopIteration
+
+    ret = self.node.name()
+    self.node = self.node.next()
+      
+    return ret
+
+class hdf_ko_iterator(hdf_iterator):
+  def next(self):
+    if not self.node: raise StopIteration
+
+    ret = (self.node.name(), self.node)
+    self.node = self.node.next()
+      
+    return ret
+  
 # ----------------------------
 
 def test():
diff -Nru clearsilver-0.9.7/python/examples/base/log.py clearsilver-0.9.8/python/examples/base/log.py
--- clearsilver-0.9.7/python/examples/base/log.py	Sun Jun 15 23:14:49 2003
+++ clearsilver-0.9.8/python/examples/base/log.py	Sun Mar 14 14:24:22 2004
@@ -2,7 +2,7 @@
 
 # log.py
 
-import sys, time
+import sys, time, string
 
 DEV = "development"
 DEV_UPDATE = "update queries"
@@ -37,7 +37,7 @@
         else:
             sys.stderr.write("[%s] %s\n" % (time_stamp, astr))
 
-def log(astr):
+def orig_log(astr):
     if len(astr) > 1024:
         astr = astr[:1024]
 
@@ -49,3 +49,82 @@
         else:
             sys.stderr.write("[%s] %s\n" % (time_stamp, astr))
     # sys.stderr.flush()
+
+
+#----------------------------------------------------------------------
+# static functions
+
+_gDebug = 0
+_gFileDebug = 0
+
+kBLACK = 0
+kRED = 1
+kGREEN = 2
+kYELLOW = 3
+kBLUE = 4
+kMAGENTA = 5
+kCYAN = 6
+kWHITE = 7
+kBRIGHT = 8
+
+def ansicolor (str, fgcolor = None, bgcolor = None):
+  o = ""
+  if fgcolor:
+    if fgcolor & kBRIGHT:
+      bright = ';1'
+    else:
+      bright = ''
+    o = o + '%c[3%d%sm' % (chr(27), fgcolor & 0x7, bright)
+  if bgcolor:
+    o = o + '%c[4%dm' % (chr(27), bgcolor)
+  o = o + str
+  if fgcolor or bgcolor:
+    o = o + '%c[0m' % (chr(27))
+  return o
+
+
+def _log(*args):
+  t = time.time()
+
+  log_line = ""
+
+  log_line = log_line + "[" + time.strftime("%m/%d %T", time.localtime(t)) + "] "
+
+  l = []
+  for arg in args:
+    l.append(str(arg))
+  log_line = log_line + string.join(l, " ") + "\n"
+
+  sys.stderr.write(log_line)
+
+def warn(*args):
+  apply(_log, args)
+
+def warnred(*args):
+  args = tuple (["[31m"] + list(args) + ["[0m"])
+  apply(_log, args)
+
+def log(*args):
+  apply(_log, args)
+
+def logred(*args):
+  if _gDebug>=1: 
+    args = tuple (["[31m"] + list(args) + ["[0m"])
+    apply(_log, args)
+
+def debug(*args):
+  if _gDebug>=2: apply(_log, args)
+
+
+def debugfull():
+  global _gDebug
+  _gDebug = 2
+
+def debugon():
+  global _gDebug
+  _gDebug = 1
+
+def debugoff():
+  global _gDebug
+  _gDebug = 0
+
diff -Nru clearsilver-0.9.7/python/examples/base/odb.py clearsilver-0.9.8/python/examples/base/odb.py
--- clearsilver-0.9.7/python/examples/base/odb.py	Thu Sep 11 03:09:47 2003
+++ clearsilver-0.9.8/python/examples/base/odb.py	Thu Mar 25 12:53:55 2004
@@ -1,4 +1,4 @@
-#!/neo/opt/bin/python
+#!/usr/bin/env python
 #
 # odb.py
 #
@@ -18,7 +18,6 @@
 # Example:
 #
 #  import odb
-#  import MySQLdb
 #
 #  # define table
 #  class AgentsTable(odb.Table):
@@ -51,12 +50,11 @@
 #    list_rows = tbl.fetchRows( ('login', "foo") )
 #
 
-
 import string
 import sys, zlib
 from log import *
 
-import MySQLdb
+import handle_error
 
 eNoSuchColumn         = "odb.eNoSuchColumn"
 eNonUniqueMatchSpec   = "odb.eNonUniqueMatchSpec"
@@ -92,17 +90,16 @@
 #
 
 class Database:
-    def __init__(self,db):
+    def __init__(self, db, debug=0):
         self._tables = {}
         self.db = db
         self._cursor = None
         self.compression_enabled = 0
+        self.debug = debug
+        self.SQLError = None
 
-    # __init__ = None
-    # list_tables = None   # list_tables() -> ['a_table','b_table']
-    # list_fields = None   # list_fields(tbl_name) -> ['
-    # checkTable = None    # checkTable
-    # createTable
+	self.__defaultRowClass = self.defaultRowClass()
+	self.__defaultRowListClass = self.defaultRowListClass()
 
     def defaultCursor(self):
         if self._cursor is None:
@@ -110,16 +107,26 @@
         return self._cursor
 
     def escape(self,str):
-        return MySQLdb.escape_string(str)
+	raise "Unimplemented Error"
+
+    def getDefaultRowClass(self): return self.__defaultRowClass
+    def setDefaultRowClass(self, clss): self.__defaultRowClass = clss
+    def getDefaultRowListClass(self): return self.__defaultRowListClass
+    def setDefaultRowListClass(self, clss): self.__defaultRowListClass = clss
+
     def defaultRowClass(self):
-	return Row
+        return Row
 
     def defaultRowListClass(self):
         # base type is list...
-	return list
+        return list
 
-    def addTable(self, attrname, tblname, tblclass, rowClass = None, check = 0, create = 0, rowListClass = None):
-        self._tables[attrname] = tblclass(self, tblname, rowClass=rowClass, check=check, create=create, rowListClass=rowListClass)
+    def addTable(self, attrname, tblname, tblclass, 
+                 rowClass = None, check = 0, create = 0, rowListClass = None):
+        tbl = tblclass(self, tblname, rowClass=rowClass, check=check, 
+                       create=create, rowListClass=rowListClass)
+        self._tables[attrname] = tbl
+        return tbl
 
     def close(self):
         for name, tbl in self._tables.items():
@@ -157,6 +164,50 @@
         dlog(DEV_UPDATE,"rollback")
         cursor.execute("rollback")
 
+    ## 
+    ## schema creation code
+    ##
+
+    def createTables(self):
+      tables = self.listTables()
+
+      for attrname, tbl in self._tables.items():
+        tblname = tbl.getTableName()
+
+        if tblname not in tables:
+          print "table %s does not exist" % tblname
+          tbl.createTable()
+        else:
+          invalidAppCols, invalidDBCols = tbl.checkTable()
+
+##          self.alterTableToMatch(tbl)
+
+    def createIndices(self):
+      indices = self.listIndices()
+
+      for attrname, tbl in self._tables.items():
+        for indexName, (columns, unique) in tbl.getIndices().items():
+          if indexName in indices: continue
+
+          tbl.createIndex(columns, indexName=indexName, unique=unique)
+
+    def synchronizeSchema(self):
+      tables = self.listTables()
+
+      for attrname, tbl in self._tables.items():
+        tblname = tbl.getTableName()
+        self.alterTableToMatch(tbl)
+        
+    def listTables(self, cursor=None):
+      raise "Unimplemented Error"
+
+    def listFieldsDict(self, table_name, cursor=None):
+      raise "Unimplemented Error"
+
+    def listFields(self, table_name, cursor=None):
+      columns = self.listFieldsDict(table_name, cursor=cursor)
+      return columns.keys()
+
 ##########################################
 # Table
 #
@@ -165,45 +216,187 @@
 class Table:
     def subclassinit(self):
         pass
-    def __init__(self,database,table_name,rowClass = None, check = 0, create = 0, rowListClass = None):
-	self.db = database
-	self.__table_name = table_name
-	if rowClass:
-	    self.__defaultRowClass = rowClass
-	else:
-	    self.__defaultRowClass = database.defaultRowClass()
-
-	if rowListClass:
-	    self.__defaultRowListClass = rowListClass
-	else:
-	    self.__defaultRowListClass = database.defaultRowListClass()
-
-	# get this stuff ready!
-	
-	self.__column_list = []
-	self.__vcolumn_list = []
-	self.__columns_locked = 0
-	self.__has_value_column = 0
-
-	# this will be used during init...
-	self.__col_def_hash = None
-	self.__vcol_def_hash = None
-	self.__primary_key_list = None
+    def __init__(self,database,table_name,
+                 rowClass = None, check = 0, create = 0, rowListClass = None):
+        self.db = database
+        self.__table_name = table_name
+        if rowClass:
+            self.__defaultRowClass = rowClass
+        else:
+            self.__defaultRowClass = database.getDefaultRowClass()
+
+        if rowListClass:
+            self.__defaultRowListClass = rowListClass
+        else:
+            self.__defaultRowListClass = database.getDefaultRowListClass()
+
+        # get this stuff ready!
+        
+        self.__column_list = []
+        self.__vcolumn_list = []
+        self.__columns_locked = 0
+        self.__has_value_column = 0
+
+        self.__indices = {}
+
+        # this will be used during init...
+        self.__col_def_hash = None
+        self.__vcol_def_hash = None
+        self.__primary_key_list = None
         self.__relations_by_table = {}
 
-	# ask the subclass to def his rows
-	self._defineRows()
+        # ask the subclass to def his rows
+        self._defineRows()
 
-	# get ready to run!
-	self.__lockColumnsAndInit()
+        # get ready to run!
+        self.__lockColumnsAndInit()
 
         self.subclassinit()
         
-	if create:
-	    self.db.createTable(self)
+        if create:
+            self.createTable()
+
+        if check:
+            self.checkTable()
 
-	if check:
-	    self.db.checkTable(self)
+    def _colTypeToSQLType(self, colname, coltype, options):
+      
+      if coltype == kInteger:
+        coltype = "integer"
+      elif coltype == kFixedString:
+        sz = options.get('size', None)
+        if sz is None: coltype = 'char'
+        else:  coltype = "char(%s)" % sz
+      elif coltype == kVarString:
+        sz = options.get('size', None)
+        if sz is None: coltype = 'varchar'
+        else:  coltype = "varchar(%s)" % sz
+      elif coltype == kBigString:
+        coltype = "text"
+      elif coltype == kIncInteger:
+        coltype = "integer"
+      elif coltype == kDateTime:
+        coltype = "datetime"
+      elif coltype == kTimeStamp:
+        coltype = "timestamp"
+      elif coltype == kReal:
+        coltype = "real"
+
+      coldef = "%s %s" % (colname, coltype)
+
+      if options.get('notnull', 0): coldef = coldef + " NOT NULL"
+      if options.get('autoincrement', 0): coldef = coldef + " AUTO_INCREMENT"
+      if options.get('unique', 0): coldef = coldef + " UNIQUE"
+#      if options.get('primarykey', 0): coldef = coldef + " primary key"
+      if options.get('default', None) is not None: coldef = coldef + " DEFAULT %s" % options.get('default')
+
+      return coldef
+
+    def getTableName(self):  return self.__table_name
+    def setTableName(self, tablename):  self.__table_name = tablename
+
+    def getIndices(self): return self.__indices
+
+    def _createTableSQL(self):
+      defs = []
+      for colname, coltype, options in self.__column_list:
+        defs.append(self._colTypeToSQLType(colname, coltype, options))
+
+      defs = string.join(defs, ", ")
+
+      primarykeys = self.getPrimaryKeyList()
+      primarykey_str = ""
+      if primarykeys:
+	primarykey_str = ", PRIMARY KEY (" + string.join(primarykeys, ",") + ")"
+
+      sql = "create table %s (%s %s)" % (self.__table_name, defs, primarykey_str)
+      return sql
+
+    def createTable(self, cursor=None):
+      if cursor is None: cursor = self.db.defaultCursor()
+      sql = self._createTableSQL()
+      print "CREATING TABLE:", sql
+      cursor.execute(sql)
+
+    def dropTable(self, cursor=None):
+      if cursor is None: cursor = self.db.defaultCursor()
+      try:
+        cursor.execute("drop table %s" % self.__table_name)   # clean out the table
+      except self.SQLError, reason:
+        pass
+
+    def renameTable(self, newTableName, cursor=None):
+      if cursor is None: cursor = self.db.defaultCursor()
+      try:
+        cursor.execute("rename table %s to %s" % (self.__table_name, newTableName))
+      except sel.SQLError, reason:
+        pass
+
+      self.setTableName(newTableName)
+      
+    def getTableColumnsFromDB(self):
+      return self.db.listFieldsDict(self.__table_name)
+      
+    def checkTable(self, warnflag=1):
+      invalidDBCols = {}
+      invalidAppCols = {}
+
+      dbcolumns = self.getTableColumnsFromDB()
+      for coldef in self.__column_list:
+        colname = coldef[0]
+
+        dbcoldef = dbcolumns.get(colname, None)
+        if dbcoldef is None:
+          invalidAppCols[colname] = 1
+      
+      for colname, row in dbcolumns.items():
+        coldef = self.__col_def_hash.get(colname, None)
+        if coldef is None:
+          invalidDBCols[colname] = 1
+
+      if warnflag == 1:
+        if invalidDBCols:
+          print "----- WARNING ------------------------------------------"
+          print "  There are columns defined in the database schema that do"
+          print "  not match the application's schema."
+          print "  columns:", invalidDBCols.keys()
+          print "--------------------------------------------------------"
+
+        if invalidAppCols: 
+          print "----- WARNING ------------------------------------------"
+          print "  There are new columns defined in the application schema"
+          print "  that do not match the database's schema."
+          print "  columns:", invalidAppCols.keys()
+          print "--------------------------------------------------------"
+
+      return invalidAppCols, invalidDBCols
+
+
+    def alterTableToMatch(self):
+      raise "Unimplemented Error!"
+
+    def addIndex(self, columns, indexName=None, unique=0):
+      if indexName is None:
+        indexName = self.getTableName() + "_index_" + string.join(columns, "_")
+
+      self.__indices[indexName] = (columns, unique)
+      
+    def createIndex(self, columns, indexName=None, unique=0, cursor=None):
+      if cursor is None: cursor = self.db.defaultCursor()
+      cols = string.join(columns, ",")
+
+      if indexName is None:
+        indexName = self.getTableName() + "_index_" + string.join(columns, "_")
+
+      uniquesql = ""
+      if unique:
+        uniquesql = " unique"
+      sql = "create %s index %s on %s (%s)" % (uniquesql, indexName, self.getTableName(), cols)
+      warn("creating index", sql)
+      cursor.execute(sql)
+
+
+    ## Column Definition
 
     def getColumnDef(self,column_name):
         try:
@@ -214,17 +407,18 @@
             except KeyError:
                 raise eNoSuchColumn, "no column (%s) on table %s" % (column_name,self.__table_name)
 
-    def getColumnList(self):
-        return self.__column_list + self.__vcolumn_list
+    def getColumnList(self):  
+      return self.__column_list + self.__vcolumn_list
+    def getAppColumnList(self): return self.__column_list
 
     def databaseSizeForData_ColumnName_(self,data,col_name):
-	try:
-	    col_def = self.__col_def_hash[col_name]
-	except KeyError:
-	    try:
-		col_def = self.__vcol_def_hash[col_name]
-	    except KeyError:
-		raise eNoSuchColumn, "no column (%s) on table %s" % (col_name,self.__table_name)
+        try:
+            col_def = self.__col_def_hash[col_name]
+        except KeyError:
+            try:
+                col_def = self.__vcol_def_hash[col_name]
+            except KeyError:
+                raise eNoSuchColumn, "no column (%s) on table %s" % (col_name,self.__table_name)
 
         c_name,c_type,c_options = col_def
 
@@ -248,37 +442,37 @@
             
 
     def columnType(self, col_name):
-	try:
-	    col_def = self.__col_def_hash[col_name]
-	except KeyError:
-	    try:
-		col_def = self.__vcol_def_hash[col_name]
-	    except KeyError:
-		raise eNoSuchColumn, "no column (%s) on table %s" % (col_name,self.__table_name)
+        try:
+            col_def = self.__col_def_hash[col_name]
+        except KeyError:
+            try:
+                col_def = self.__vcol_def_hash[col_name]
+            except KeyError:
+                raise eNoSuchColumn, "no column (%s) on table %s" % (col_name,self.__table_name)
 
-	c_name,c_type,c_options = col_def
+        c_name,c_type,c_options = col_def
         return c_type
 
     def convertDataForColumn(self,data,col_name):
-	try:
-	    col_def = self.__col_def_hash[col_name]
-	except KeyError:
-	    try:
-		col_def = self.__vcol_def_hash[col_name]
-	    except KeyError:
-		raise eNoSuchColumn, "no column (%s) on table %s" % (col_name,self.__table_name)
+        try:
+            col_def = self.__col_def_hash[col_name]
+        except KeyError:
+            try:
+                col_def = self.__vcol_def_hash[col_name]
+            except KeyError:
+                raise eNoSuchColumn, "no column (%s) on table %s" % (col_name,self.__table_name)
 
-	c_name,c_type,c_options = col_def
+        c_name,c_type,c_options = col_def
 
         if c_type == kIncInteger:
             raise eInvalidData, "invalid operation for column (%s:%s) on table (%s)" % (col_name,c_type,self.__table_name)
 
-	if c_type == kInteger:
-	    try:
+        if c_type == kInteger:
+            try:
                 if data is None: data = 0
                 else: return long(data)
-	    except (ValueError,TypeError):
-		raise eInvalidData, "invalid data (%s) for col (%s:%s) on table (%s)" % (repr(data),col_name,c_type,self.__table_name)
+            except (ValueError,TypeError):
+                raise eInvalidData, "invalid data (%s) for col (%s:%s) on table (%s)" % (repr(data),col_name,c_type,self.__table_name)
         elif c_type == kReal:
             try:
                 if data is None: data = 0.0
@@ -287,63 +481,61 @@
                 raise eInvalidData, "invalid data (%s) for col (%s:%s) on table (%s)" % (repr(data), col_name,c_type,self.__table_name)
 
         else:
-	    if type(data) == type(long(0)):
-		return "%d" % data
-	    else:
-		return str(data)
+            if type(data) == type(long(0)):
+                return "%d" % data
+            else:
+                return str(data)
 
     def getPrimaryKeyList(self):
-	return self.__primary_key_list
+        return self.__primary_key_list
     
-    def getTableName(self):
-	return self.__table_name
     def hasValueColumn(self):
-	return self.__has_value_column
+        return self.__has_value_column
 
     def hasColumn(self,name):
-	return self.__col_def_hash.has_key(name)
+        return self.__col_def_hash.has_key(name)
     def hasVColumn(self,name):
-	return self.__vcol_def_hash.has_key(name)
-	
+        return self.__vcol_def_hash.has_key(name)
+        
 
     def _defineRows(self):
-	raise "can't instantiate base odb.Table type, make a subclass and override _defineRows()"
+        raise "can't instantiate base odb.Table type, make a subclass and override _defineRows()"
 
     def __lockColumnsAndInit(self):
-	# add a 'odb_value column' before we lockdown the table def
-	if self.__has_value_column:
-	    self.d_addColumn("odb_value",kBigText,default='')
-
-	self.__columns_locked = 1
-	# walk column list and make lookup hashes, primary_key_list, etc..
-
-	primary_key_list = []
-	col_def_hash = {}
-	for a_col in self.__column_list:
-	    name,type,options = a_col
-	    col_def_hash[name] = a_col
-	    if options.has_key('primarykey'):
-		primary_key_list.append(name)
-
-	self.__col_def_hash = col_def_hash
-	self.__primary_key_list = primary_key_list
-
-	# setup the value columns!
-
-	if (not self.__has_value_column) and (len(self.__vcolumn_list) > 0):
-	    raise "can't define vcolumns on table without ValueColumn, call d_addValueColumn() in your _defineRows()"
-
-	vcol_def_hash = {}
-	for a_col in self.__vcolumn_list:
-	    name,type,size_data,options = a_col
-	    vcol_def_hash[name] = a_col
-
-	self.__vcol_def_hash = vcol_def_hash
-	
-	
+        # add a 'odb_value column' before we lockdown the table def
+        if self.__has_value_column:
+            self.d_addColumn("odb_value",kBigText,default='')
+
+        self.__columns_locked = 1
+        # walk column list and make lookup hashes, primary_key_list, etc..
+
+        primary_key_list = []
+        col_def_hash = {}
+        for a_col in self.__column_list:
+            name,type,options = a_col
+            col_def_hash[name] = a_col
+            if options.has_key('primarykey'):
+                primary_key_list.append(name)
+
+        self.__col_def_hash = col_def_hash
+        self.__primary_key_list = primary_key_list
+
+        # setup the value columns!
+
+        if (not self.__has_value_column) and (len(self.__vcolumn_list) > 0):
+            raise "can't define vcolumns on table without ValueColumn, call d_addValueColumn() in your _defineRows()"
+
+        vcol_def_hash = {}
+        for a_col in self.__vcolumn_list:
+            name,type,size_data,options = a_col
+            vcol_def_hash[name] = a_col
+
+        self.__vcol_def_hash = vcol_def_hash
+        
+        
     def __checkColumnLock(self):
-	if self.__columns_locked:
-	    raise "can't change column definitions outside of subclass' _defineRows() method!"
+        if self.__columns_locked:
+            raise "can't change column definitions outside of subclass' _defineRows() method!"
 
     # table definition methods, these are only available while inside the
     # subclass's _defineRows method
@@ -358,28 +550,32 @@
     #     self.d_addColumn("type",kInteger,
     #                      enum_values = { 0 : "alive", 1 : "dead" }
 
-    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,no_export=0):
-
-	self.__checkColumnLock()
-
-	options = {}
-	options['default']       = default
-	if primarykey:
-	    options['primarykey']    = primarykey
-	if indexed:
-	    options['indexed']       = indexed
-	if unique:
-	    options['unique']        = unique
-	if safeupdate:
-	    options['safeupdate']    = safeupdate
-	if autoincrement:
-	    options['autoincrement'] = autoincrement
-	if notnull:
-	    options['notnull']       = notnull
-	if size:
-	    options['size']          = size
+    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,
+		    no_export = 0,
+                    relations=None,compress_ok=0,int_date=0):
+
+        self.__checkColumnLock()
+
+        options = {}
+        options['default']       = default
+        if primarykey:
+            options['primarykey']    = primarykey
+        if unique:
+            options['unique']        = unique
+        if indexed:
+            options['indexed']       = indexed
+            self.addIndex((col_name,))
+        if safeupdate:
+            options['safeupdate']    = safeupdate
+        if autoincrement:
+            options['autoincrement'] = autoincrement
+        if notnull:
+            options['notnull']       = notnull
+        if size:
+            options['size']          = size
         if no_export:
             options['no_export']     = no_export
         if int_date:
@@ -388,15 +584,15 @@
             else:
                 options['int_date'] = int_date
             
-	if enum_values:
-	    options['enum_values']   = enum_values
-	    inv_enum_values = {}
-	    for k,v in enum_values.items():
-		if inv_enum_values.has_key(v):
-		    raise eInvalidData, "enum_values paramater must be a 1 to 1 mapping for Table(%s)" % self.__table_name
-		else:
-		    inv_enum_values[v] = k
-	    options['inv_enum_values'] = inv_enum_values
+        if enum_values:
+            options['enum_values']   = enum_values
+            inv_enum_values = {}
+            for k,v in enum_values.items():
+                if inv_enum_values.has_key(v):
+                    raise eInvalidData, "enum_values paramater must be a 1 to 1 mapping for Table(%s)" % self.__table_name
+                else:
+                    inv_enum_values[v] = k
+            options['inv_enum_values'] = inv_enum_values
         if relations:
             options['relations']      = relations
             for a_relation in relations:
@@ -409,27 +605,26 @@
                 options['compress_ok'] = 1
             else:
                 raise eInvalidData, "only kBigString fields can be compress_ok=1"
-	
-	self.__column_list.append( (col_name,ctype,options) )
-	
+        
+        self.__column_list.append( (col_name,ctype,options) )
 
     def d_addValueColumn(self):
-	self.__checkColumnLock()
-	self.__has_value_column = 1
+        self.__checkColumnLock()
+        self.__has_value_column = 1
 
     def d_addVColumn(self,col_name,type,size=None,default=None):
-	self.__checkColumnLock()
+        self.__checkColumnLock()
 
-	if (not self.__has_value_column):
-	    raise "can't define VColumns on table without ValueColumn, call d_addValueColumn() first"
+        if (not self.__has_value_column):
+            raise "can't define VColumns on table without ValueColumn, call d_addValueColumn() first"
 
-	options = {}
-	if default:
-	    options['default'] = default
-	if size:
-	    options['size']    = size
+        options = {}
+        if default:
+            options['default'] = default
+        if size:
+            options['size']    = size
 
-	self.__vcolumn_list.append( (col_name,type,options) )
+        self.__vcolumn_list.append( (col_name,type,options) )
 
     #####################
     # _checkColMatchSpec(col_match_spec,should_match_unique_row = 0)
@@ -442,26 +637,26 @@
     #
     
     def _fixColMatchSpec(self,col_match_spec, should_match_unique_row = 0):
-	if type(col_match_spec) == type([]):
-	    if type(col_match_spec[0]) != type((0,)):
-		raise eInvalidMatchSpec, "invalid types in match spec, use [(,)..] or (,)"
-	elif type(col_match_spec) == type((0,)):
-	    col_match_spec = [ col_match_spec ]
+        if type(col_match_spec) == type([]):
+            if type(col_match_spec[0]) != type((0,)):
+                raise eInvalidMatchSpec, "invalid types in match spec, use [(,)..] or (,)"
+        elif type(col_match_spec) == type((0,)):
+            col_match_spec = [ col_match_spec ]
         elif type(col_match_spec) == type(None):
             if should_match_unique_row:
                 raise eNonUniqueMatchSpec, "can't use a non-unique match spec (%s) here" % col_match_spec
             else:
                 return None
-	else:
-	    raise eInvalidMatchSpec, "invalid types in match spec, use [(,)..] or (,)"
+        else:
+            raise eInvalidMatchSpec, "invalid types in match spec, use [(,)..] or (,)"
 
-	if should_match_unique_row:
+        if should_match_unique_row:
             unique_column_lists = []
 
             # first the primary key list
-	    my_primary_key_list = []
-	    for a_key in self.__primary_key_list:
-		my_primary_key_list.append(a_key)
+            my_primary_key_list = []
+            for a_key in self.__primary_key_list:
+                my_primary_key_list.append(a_key)
 
             # then other unique keys
             for a_col in self.__column_list:
@@ -471,19 +666,19 @@
 
             unique_column_lists.append( ('primary_key', my_primary_key_list) )
                 
-	
-	new_col_match_spec = []
-	for a_col in col_match_spec:
-	    name,val = a_col
-	    # newname = string.lower(name)
-	    #  what is this doing?? - jeske
-	    newname = name
-	    if not self.__col_def_hash.has_key(newname):
-		raise eNoSuchColumn, "no such column in match spec: '%s'" % newname
+        
+        new_col_match_spec = []
+        for a_col in col_match_spec:
+            name,val = a_col
+            # newname = string.lower(name)
+            #  what is this doing?? - jeske
+            newname = name
+            if not self.__col_def_hash.has_key(newname):
+                raise eNoSuchColumn, "no such column in match spec: '%s'" % newname
 
-	    new_col_match_spec.append( (newname,val) )
+            new_col_match_spec.append( (newname,val) )
 
-	    if should_match_unique_row:
+            if should_match_unique_row:
                 for name,a_list in unique_column_lists:
                     try:
                         a_list.remove(newname)
@@ -491,7 +686,7 @@
                         # it's okay if they specify too many columns!
                         pass
 
-	if should_match_unique_row:
+        if should_match_unique_row:
             for name,a_list in unique_column_lists:
                 if len(a_list) == 0:
                     # we matched at least one unique colum spec!
@@ -500,10 +695,10 @@
             
             raise eNonUniqueMatchSpec, "can't use a non-unique match spec (%s) here" % col_match_spec
 
-	return new_col_match_spec
+        return new_col_match_spec
 
     def __buildWhereClause (self, col_match_spec,other_clauses = None):
-	sql_where_list = []
+        sql_where_list = []
 
         if not col_match_spec is None:
             for m_col in col_match_spec:
@@ -538,8 +733,8 @@
 
     def __fetchRows(self,col_match_spec,cursor = None, where = None, order_by = None, limit_to = None,
                     skip_to = None, join = None):
-	if cursor is None:
-	    cursor = self.db.defaultCursor()
+        if cursor is None:
+            cursor = self.db.defaultCursor()
 
         # build column list
         sql_columns = []
@@ -567,17 +762,17 @@
                     eInvalidJoinSpec, "can't find table %s in defined relations for %s" % (a_table,self.__table_name)
                     
         # start buildling SQL
-    	sql = "select %s from %s" % (string.join(sql_columns,","),
+        sql = "select %s from %s" % (string.join(sql_columns,","),
                                      self.__table_name)
 
         # add join clause
         if join_clauses:
             sql = sql + string.join(join_clauses," ")
-	
-	# add where clause elements
+        
+        # add where clause elements
         sql_where_list = self.__buildWhereClause (col_match_spec,where)
-	if sql_where_list:
-	    sql = sql + " where %s" % (string.join(sql_where_list," and "))
+        if sql_where_list:
+            sql = sql + " where %s" % (string.join(sql_where_list," and "))
 
         # add order by clause
         if order_by:
@@ -586,7 +781,11 @@
         # add limit
         if not limit_to is None:
             if not skip_to is None:
-                sql = sql + " limit %s, %s" % (skip_to,limit_to)
+#                log("limit,skip = %s,%s" % (limit_to,skip_to))
+                if self.db.db.__module__ == "sqlite.main":
+                    sql = sql + " limit %s offset %s " % (limit_to,skip_to)
+                else:
+                    sql = sql + " limit %s, %s" % (skip_to,limit_to)
             else:
                 sql = sql + " limit %s" % limit_to
         else:
@@ -594,23 +793,23 @@
                 raise eInvalidData, "can't specify skip_to without limit_to in MySQL"
 
         dlog(DEV_SELECT,sql)
-	cursor.execute(sql)
+        cursor.execute(sql)
 
         # create defaultRowListClass instance...
         return_rows = self.__defaultRowListClass()
-	    
-	# should do fetchmany!
-	all_rows = cursor.fetchall()
-	for a_row in all_rows:
-	    data_dict = {}
+            
+        # should do fetchmany!
+        all_rows = cursor.fetchall()
+        for a_row in all_rows:
+            data_dict = {}
 
-	    col_num = 0
+            col_num = 0
             
-            #	    for a_col in cursor.description:
-            #		(name,type_code,display_size,internal_size,precision,scale,null_ok) = a_col
+            #            for a_col in cursor.description:
+            #                (name,type_code,display_size,internal_size,precision,scale,null_ok) = a_col
             for name in sql_columns:
-		if self.__col_def_hash.has_key(name) or joined_cols_hash.has_key(name):
-		    # only include declared columns!
+                if self.__col_def_hash.has_key(name) or joined_cols_hash.has_key(name):
+                    # only include declared columns!
                     if self.__col_def_hash.has_key(name):
                         c_name,c_type,c_options = self.__col_def_hash[name]
                         if c_type == kBigString and c_options.get("compress_ok",0) and a_row[col_num]:
@@ -620,23 +819,30 @@
                                 a_col_data = a_row[col_num]
 
                             data_dict[name] = a_col_data
+                        elif c_type == kInteger or c_type == kIncInteger:
+                            value = a_row[col_num]
+                            if not value is None:
+                                data_dict[name] = int(value)
+                            else:
+                                data_dict[name] = None
                         else:
                             data_dict[name] = a_row[col_num]
 
                     else:
                         data_dict[name] = a_row[col_num]
                         
-		    col_num = col_num + 1
+                    col_num = col_num + 1
 
 	    newrowobj = self.__defaultRowClass(self,data_dict,joined_cols = joined_cols)
-
 	    return_rows.append(newrowobj)
-	    
-	return return_rows
+	      
+
+            
+        return return_rows
 
     def __deleteRow(self,a_row,cursor = None):
-	if cursor is None:
-	    cursor = self.db.defaultCursor()
+        if cursor is None:
+            cursor = self.db.defaultCursor()
 
         # build the where clause!
         match_spec = a_row.getPKMatchSpec()
@@ -649,17 +855,17 @@
        
 
     def __updateRowList(self,a_row_list,cursor = None):
-	if cursor is None:
-	    cursor = self.db.defaultCursor()
+        if cursor is None:
+            cursor = self.db.defaultCursor()
 
-	for a_row in a_row_list:
-	    update_list = a_row.changedList()
+        for a_row in a_row_list:
+            update_list = a_row.changedList()
 
-	    # build the set list!
-	    sql_set_list = []
-	    for a_change in update_list:
-		col_name,col_val,col_inc_val = a_change
-		c_name,c_type,c_options = self.__col_def_hash[col_name]
+            # build the set list!
+            sql_set_list = []
+            for a_change in update_list:
+                col_name,col_val,col_inc_val = a_change
+                c_name,c_type,c_options = self.__col_def_hash[col_name]
 
                 if c_type != kIncInteger and col_val is None:
                     sql_set_list.append("%s = NULL" % c_name)
@@ -682,14 +888,14 @@
                     else:
                         sql_set_list.append("%s = '%s'" % (c_name, self.db.escape(col_val)))
 
-	    # build the where clause!
-	    match_spec = a_row.getPKMatchSpec()
+            # build the where clause!
+            match_spec = a_row.getPKMatchSpec()
             sql_where_list = self.__buildWhereClause (match_spec)
 
-	    if sql_set_list:
-		sql = "update %s set %s where %s" % (self.__table_name,
-						 string.join(sql_set_list,","),
-						 string.join(sql_where_list," and "))
+            if sql_set_list:
+                sql = "update %s set %s where %s" % (self.__table_name,
+                                                 string.join(sql_set_list,","),
+                                                 string.join(sql_where_list," and "))
 
                 dlog(DEV_UPDATE,sql)
                 try:
@@ -698,23 +904,23 @@
                     if string.find(str(reason), "Duplicate entry") != -1:
                         raise eDuplicateKey, reason
                     raise Exception, reason
-		a_row.markClean()
+                a_row.markClean()
 
-    def __insertRow(self,a_row_obj,cursor = None):
-	if cursor is None:
-	    cursor = self.db.defaultCursor()
+    def __insertRow(self,a_row_obj,cursor = None,replace=0):
+        if cursor is None:
+            cursor = self.db.defaultCursor()
 
-	sql_col_list = []
-	sql_data_list = []
-	auto_increment_column_name = None
+        sql_col_list = []
+        sql_data_list = []
+        auto_increment_column_name = None
 
-	for a_col in self.__column_list:
-	    name,type,options = a_col
+        for a_col in self.__column_list:
+            name,type,options = a_col
 
-	    try:
-		data = a_row_obj[name]
+            try:
+                data = a_row_obj[name]
 
-		sql_col_list.append(name)
+                sql_col_list.append(name)
                 if data is None:
                     sql_data_list.append("NULL")
                 else:
@@ -731,35 +937,47 @@
                     else:
                         sql_data_list.append("'%s'" % self.db.escape(data))
 
-	    except KeyError:
-		if options.has_key("autoincrement"):
-		    if auto_increment_column_name:
-			raise eInternalError, "two autoincrement columns (%s,%s) in table (%s)" % (auto_increment_column_name, name,self.__table_name)
-		    else:
-			auto_increment_column_name = name
-		
-	
-	sql = "insert into %s (%s) values (%s)" % (self.__table_name,
-						   string.join(sql_col_list,","),
-						   string.join(sql_data_list,","))
+            except KeyError:
+                if options.has_key("autoincrement"):
+                    if auto_increment_column_name:
+                        raise eInternalError, "two autoincrement columns (%s,%s) in table (%s)" % (auto_increment_column_name, name,self.__table_name)
+                    else:
+                        auto_increment_column_name = name
+
+        if replace:
+            sql = "replace into %s (%s) values (%s)" % (self.__table_name,
+                                                   string.join(sql_col_list,","),
+                                                   string.join(sql_data_list,","))
+        else:
+            sql = "insert into %s (%s) values (%s)" % (self.__table_name,
+                                                   string.join(sql_col_list,","),
+                                                   string.join(sql_data_list,","))
 
         dlog(DEV_UPDATE,sql)
         try:
           cursor.execute(sql)
         except Exception, reason:
+          # sys.stderr.write("errror in statement: " + sql + "\n")
+          log("error in statement: " + sql + "\n")
           if string.find(str(reason), "Duplicate entry") != -1:
             raise eDuplicateKey, reason
           raise Exception, reason
             
-	if auto_increment_column_name:
-	    a_row_obj[auto_increment_column_name] = cursor.insert_id()
+        if auto_increment_column_name:
+            if cursor.__module__ == "sqlite.main":
+                a_row_obj[auto_increment_column_name] = cursor.lastrowid
+            elif cursor.__module__ == "MySQLdb.cursors":
+                a_row_obj[auto_increment_column_name] = cursor.insert_id()
+            else:
+                # fallback to acting like mysql
+                a_row_obj[auto_increment_column_name] = cursor.insert_id()
 
     # ----------------------------------------------------
     #   Helper methods for Rows...
     # ----------------------------------------------------
 
 
-	
+        
     #####################
     # r_deleteRow(a_row_obj,cursor = None)
     #
@@ -768,8 +986,8 @@
     #
 
     def r_deleteRow(self,a_row_obj, cursor = None):
-	curs = cursor
-	self.__deleteRow(a_row_obj, cursor = curs)
+        curs = cursor
+        self.__deleteRow(a_row_obj, cursor = curs)
 
 
     #####################
@@ -780,8 +998,8 @@
     #
 
     def r_updateRow(self,a_row_obj, cursor = None):
-	curs = cursor
-	self.__updateRowList([a_row_obj], cursor = curs)
+        curs = cursor
+        self.__updateRowList([a_row_obj], cursor = curs)
 
     #####################
     # InsertRow(a_row_obj,cursor = None)
@@ -790,9 +1008,9 @@
     # but you can call it yourself if you want
     #
 
-    def r_insertRow(self,a_row_obj, cursor = None):
-	curs = cursor
-	self.__insertRow(a_row_obj, cursor = curs)
+    def r_insertRow(self,a_row_obj, cursor = None,replace=0):
+        curs = cursor
+        self.__insertRow(a_row_obj, cursor = curs,replace=replace)
 
 
     # ----------------------------------------------------
@@ -800,7 +1018,7 @@
     # ----------------------------------------------------
 
 
-	
+        
     #####################
     # deleteRow(col_match_spec)
     #
@@ -824,7 +1042,7 @@
 
         dlog(DEV_UPDATE,sql)
         cursor.execute(sql)
-	
+        
     #####################
     # fetchRow(col_match_spec)
     #
@@ -836,17 +1054,17 @@
 
 
     def fetchRow(self, col_match_spec, cursor = None):
-	n_match_spec = self._fixColMatchSpec(col_match_spec, should_match_unique_row = 1)
+        n_match_spec = self._fixColMatchSpec(col_match_spec, should_match_unique_row = 1)
 
-	rows = self.__fetchRows(n_match_spec, cursor = cursor)
-	if len(rows) == 0:
-	    raise eNoMatchingRows, "no row matches %s" % repr(n_match_spec)
+        rows = self.__fetchRows(n_match_spec, cursor = cursor)
+        if len(rows) == 0:
+            raise eNoMatchingRows, "no row matches %s" % repr(n_match_spec)
 
-	if len(rows) > 1:
-	    raise eInternalError, "unique where clause shouldn't return > 1 row"
+        if len(rows) > 1:
+            raise eInternalError, "unique where clause shouldn't return > 1 row"
 
-	return rows[0]
-	    
+        return rows[0]
+            
 
     #####################
     # fetchRows(col_match_spec)
@@ -856,10 +1074,12 @@
     #    a_row_list = tbl.fetchRows( [ ("order_id", 1), ("enterTime", now) ] )
 
 
-    def fetchRows(self, col_match_spec = None, cursor = None, where = None, order_by = None, limit_to = None, skip_to = None, join = None):
-	n_match_spec = self._fixColMatchSpec(col_match_spec)
+    def fetchRows(self, col_match_spec = None, cursor = None, 
+		  where = None, order_by = None, limit_to = None, 
+		  skip_to = None, join = None):
+        n_match_spec = self._fixColMatchSpec(col_match_spec)
 
-	return self.__fetchRows(n_match_spec,
+        return self.__fetchRows(n_match_spec,
                                 cursor = cursor,
                                 where = where,
                                 order_by = order_by,
@@ -867,21 +1087,19 @@
                                 skip_to = skip_to,
                                 join = join)
 
-    def fetchRowCount (self, col_match_spec = None, cursor = None, where = None):
-	n_match_spec = self._fixColMatchSpec(col_match_spec)
-
+    def fetchRowCount (self, col_match_spec = None, 
+		       cursor = None, where = None):
+        n_match_spec = self._fixColMatchSpec(col_match_spec)
         sql_where_list = self.__buildWhereClause (n_match_spec,where)
-
-    	sql = "select count(*) from %s" % self.__table_name
-	if sql_where_list:
-	    sql = "%s where %s" % (sql,string.join(sql_where_list," and "))
-
+	sql = "select count(*) from %s" % self.__table_name
+        if sql_where_list:
+            sql = "%s where %s" % (sql,string.join(sql_where_list," and "))
         if cursor is None:
           cursor = self.db.defaultCursor()
         dlog(DEV_SELECT,sql)
-	cursor.execute(sql)
+        cursor.execute(sql)
         try:
-	    count, = cursor.fetchone()
+            count, = cursor.fetchone()
         except TypeError:
             count = 0
         return count
@@ -901,8 +1119,8 @@
             # else return empty list...
             return self.__defaultRowListClass()
 
-    def newRow(self):
-	row = self.__defaultRowClass(self,None,create=1)
+    def newRow(self,replace=0):
+        row = self.__defaultRowClass(self,None,create=1,replace=replace)
         for (cname, ctype, opts) in self.__column_list:
             if opts['default'] is not None and ctype is not kIncInteger:
                 row[cname] = opts['default']
@@ -912,76 +1130,77 @@
     __instance_data_locked  = 0
     def subclassinit(self):
         pass
-    def __init__(self,_table,data_dict,create=0,joined_cols = None):
+    def __init__(self,_table,data_dict,create=0,joined_cols = None,replace=0):
 
         self._inside_getattr = 0  # stop recursive __getattr__
-	self._table = _table
-	self._should_insert = create
+        self._table = _table
+        self._should_insert = create or replace
+        self._should_replace = replace
         self._rowInactive = None
         self._joinedRows = []
-	
-	self.__pk_match_spec = None
-	self.__vcoldata = {}
+        
+        self.__pk_match_spec = None
+        self.__vcoldata = {}
         self.__inc_coldata = {}
 
         self.__joined_cols_dict = {}
         for a_col in joined_cols or []:
             self.__joined_cols_dict[a_col] = 1
-	
-	if create:
-	    self.__coldata = {}
-	else:
-	    if type(data_dict) != type({}):
-		raise eInternalError, "rowdict instantiate with bad data_dict"
-	    self.__coldata = data_dict
-	    self.__unpackVColumn()
+        
+        if create:
+            self.__coldata = {}
+        else:
+            if type(data_dict) != type({}):
+                raise eInternalError, "rowdict instantiate with bad data_dict"
+            self.__coldata = data_dict
+            self.__unpackVColumn()
 
-	self.markClean()
+        self.markClean()
 
         self.subclassinit()
-	self.__instance_data_locked = 1
+        self.__instance_data_locked = 1
 
     def joinRowData(self,another_row):
         self._joinedRows.append(another_row)
 
     def getPKMatchSpec(self):
-	return self.__pk_match_spec
+        return self.__pk_match_spec
 
     def markClean(self):
-	self.__vcolchanged = 0
-	self.__colchanged_dict = {}
+        self.__vcolchanged = 0
+        self.__colchanged_dict = {}
 
         for key in self.__inc_coldata.keys():
-	    self.__coldata[key] = self.__coldata.get(key, 0) + self.__inc_coldata[key]
+            self.__coldata[key] = self.__coldata.get(key, 0) + self.__inc_coldata[key]
 
         self.__inc_coldata = {}
 
-	if not self._should_insert:
-	    # rebuild primary column match spec
-	    new_match_spec = []
-	    for col_name in self._table.getPrimaryKeyList():
-		try:
-		    rdata = self[col_name]
-		except KeyError:
-		    raise eInternalError, "must have primary key data filled in to save %s:Row(col:%s)" % (self._table.getTableName(),col_name)
-		    
-		new_match_spec.append( (col_name, rdata) )
-	    self.__pk_match_spec = new_match_spec
+        if not self._should_insert:
+            # rebuild primary column match spec
+            new_match_spec = []
+            for col_name in self._table.getPrimaryKeyList():
+                try:
+                    rdata = self[col_name]
+                except KeyError:
+                    raise eInternalError, "must have primary key data filled in to save %s:Row(col:%s)" % (self._table.getTableName(),col_name)
+                    
+                new_match_spec.append( (col_name, rdata) )
+            self.__pk_match_spec = new_match_spec
 
     def __unpackVColumn(self):
-	if self._table.hasValueColumn():
-	    pass
-	
+        if self._table.hasValueColumn():
+            pass
+        
     def __packVColumn(self):
-	if self._table.hasValueColumn():
-	    pass
+        if self._table.hasValueColumn():
+            pass
 
     ## ----- utility stuff ----------------------------------
 
     def __del__(self):
-	# check for unsaved changes
-	changed_list = self.changedList()
-	if len(changed_list):
+        # check for unsaved changes
+        changed_list = self.changedList()
+        if len(changed_list):
             info = "unsaved Row for table (%s) lost, call discard() to avoid this error. Lost changes: %s\n" % (self._table.getTableName(), repr(changed_list)[:256])
             if 0:
                 raise eUnsavedObjectLost, info
@@ -990,7 +1209,7 @@
                 
 
     def __repr__(self):
-	return "Row from (%s): %s" % (self._table.getTableName(),repr(self.__coldata) + repr(self.__vcoldata))
+        return "Row from (%s): %s" % (self._table.getTableName(),repr(self.__coldata) + repr(self.__vcoldata))
 
     ## ---- class emulation --------------------------------
 
@@ -1010,18 +1229,18 @@
             self._inside_getattr = 0
 
     def __setattr__(self,key,val):
-	if not self.__instance_data_locked:
-	    self.__dict__[key] = val
-	else:
-	    my_dict = self.__dict__
-	    if my_dict.has_key(key):
-		my_dict[key] = val
-	    else:
-		# try and put it into the rowdata
-		try:
-		    self[key] = val
-		except KeyError, reason:
-		    raise AttributeError, reason
+        if not self.__instance_data_locked:
+            self.__dict__[key] = val
+        else:
+            my_dict = self.__dict__
+            if my_dict.has_key(key):
+                my_dict[key] = val
+            else:
+                # try and put it into the rowdata
+                try:
+                    self[key] = val
+                except KeyError, reason:
+                    raise AttributeError, reason
 
 
     ## ---- dict emulation ---------------------------------
@@ -1031,7 +1250,7 @@
 
         try:
             c_type = self._table.columnType(key)
-        except eNoSuchColumn:
+        except eNoSuchColumn, reason:
             # Ugh, this sucks, we can't determine the type for a joined
             # row, so we just default to kVarString and let the code below
             # determine if this is a joined column or not
@@ -1044,9 +1263,9 @@
             if i_data is None: i_data = 0
             return c_data + i_data
         
-	try:
-	    return self.__coldata[key]
-	except KeyError:
+        try:
+            return self.__coldata[key]
+        except KeyError:
             try:
                 return self.__vcoldata[key]
             except KeyError:
@@ -1061,39 +1280,39 @@
     def __setitem__(self,key,data):
         self.checkRowActive()
         
-	try:
-	    newdata = self._table.convertDataForColumn(data,key)
-	except eNoSuchColumn, reason:
-	    raise KeyError, reason
-
-	if self._table.hasColumn(key):
-	    self.__coldata[key] = newdata
-	    self.__colchanged_dict[key] = 1
-	elif self._table.hasVColumn(key):
-	    self.__vcoldata[key] = newdata
-	    self.__vcolchanged = 1
-	else:
+        try:
+            newdata = self._table.convertDataForColumn(data,key)
+        except eNoSuchColumn, reason:
+            raise KeyError, reason
+
+        if self._table.hasColumn(key):
+            self.__coldata[key] = newdata
+            self.__colchanged_dict[key] = 1
+        elif self._table.hasVColumn(key):
+            self.__vcoldata[key] = newdata
+            self.__vcolchanged = 1
+        else:
             for a_joined_row in self._joinedRows:
                 try:
                     a_joined_row[key] = data
                     return
                 except KeyError:
                     pass
-	    raise KeyError, "unknown column name %s" % key
+            raise KeyError, "unknown column name %s" % key
 
     def __delitem__(self,key,data):
         self.checkRowActive()
         
-	if self.table.hasVColumn(key):
-	    del self.__vcoldata[key]
-	else:
+        if self.table.hasVColumn(key):
+            del self.__vcoldata[key]
+        else:
             for a_joined_row in self._joinedRows:
                 try:
                     del a_joined_row[key]
                     return
                 except KeyError:
                     pass
-	    raise KeyError, "unknown column name %s" % key
+            raise KeyError, "unknown column name %s" % key
 
 
     def copyFrom(self,source):
@@ -1148,7 +1367,7 @@
     def __len__(self):
         self.checkRowActive()
         
-	my_len = len(self.__coldata) + len(self.__vcoldata)
+        my_len = len(self.__coldata) + len(self.__vcoldata)
 
         for a_joined_row in self._joinedRows:
             my_len = my_len + len(a_joined_row)
@@ -1158,25 +1377,25 @@
     def has_key(self,key):
         self.checkRowActive()
         
-	if self.__coldata.has_key(key) or self.__vcoldata.has_key(key):
-	    return 1
-	else:
+        if self.__coldata.has_key(key) or self.__vcoldata.has_key(key):
+            return 1
+        else:
 
             for a_joined_row in self._joinedRows:
                 if a_joined_row.has_key(key):
                     return 1
-	    return 0
-	
+            return 0
+        
     def get(self,key,default = None):
         self.checkRowActive()
 
         
         
-	if self.__coldata.has_key(key):
-	    return self.__coldata[key]
-	elif self.__vcoldata.has_key(key):
-	    return self.__vcoldata[key]
-	else:
+        if self.__coldata.has_key(key):
+            return self.__coldata[key]
+        elif self.__vcoldata.has_key(key):
+            return self.__vcoldata[key]
+        else:
             for a_joined_row in self._joinedRows:
                 try:
                     return a_joined_row.get(key,default)
@@ -1186,7 +1405,7 @@
             if self._table.hasColumn(key):
                 return default
             
-	    raise eNoSuchColumn, "no such column %s" % key
+            raise eNoSuchColumn, "no such column %s" % key
 
     def inc(self,key,count=1):
         self.checkRowActive()
@@ -1207,10 +1426,10 @@
 
 
     def fillDefaults(self):
-	for field_def in self._table.fieldList():
-	    name,type,size,options = field_def
-	    if options.has_key("default"):
-		self[name] = options["default"]
+        for field_def in self._table.fieldList():
+            name,type,size,options = field_def
+            if options.has_key("default"):
+                self[name] = options["default"]
 
     ###############
     # changedList()
@@ -1220,20 +1439,20 @@
     #   changedList() -> [ ('name', 'fred'), ('age', 20) ]
 
     def changedList(self):
-	if self.__vcolchanged:
-	    self.__packVColumn()
+        if self.__vcolchanged:
+            self.__packVColumn()
 
-	changed_list = []
-	for a_col in self.__colchanged_dict.keys():
-	    changed_list.append( (a_col,self.get(a_col,None),self.__inc_coldata.get(a_col,None)) )
+        changed_list = []
+        for a_col in self.__colchanged_dict.keys():
+            changed_list.append( (a_col,self.get(a_col,None),self.__inc_coldata.get(a_col,None)) )
 
-	return changed_list
+        return changed_list
 
     def discard(self):
-	self.__coldata = None
-	self.__vcoldata = None
-	self.__colchanged_dict = {}
-	self.__vcolchanged = 0
+        self.__coldata = None
+        self.__vcoldata = None
+        self.__colchanged_dict = {}
+        self.__vcolchanged = 0
 
     def delete(self,cursor = None):
         self.checkRowActive()
@@ -1245,20 +1464,21 @@
         self._rowInactive = "deleted"
 
     def save(self,cursor = None):
-	toTable = self._table
+        toTable = self._table
 
         self.checkRowActive()
 
-	if self._should_insert:
-	    toTable.r_insertRow(self)
-	    self._should_insert = 0
-	    self.markClean()  # rebuild the primary key list
-	else:
+        if self._should_insert:
+            toTable.r_insertRow(self,replace=self._should_replace)
+            self._should_insert = 0
+            self._should_replace = 0
+            self.markClean()  # rebuild the primary key list
+        else:
             curs = cursor
-	    toTable.r_updateRow(self,cursor = curs)
+            toTable.r_updateRow(self,cursor = curs)
 
-	# the table will mark us clean!
-	# self.markClean()
+        # the table will mark us clean!
+        # self.markClean()
 
     def checkRowActive(self):
         if self._rowInactive:
@@ -1267,229 +1487,6 @@
     def databaseSizeForColumn(self,key):
         return self._table.databaseSizeForData_ColumnName_(self[key],key)
 
-## -----------------------------------------------------------------------
-##                            T  E  S  T S
-## -----------------------------------------------------------------------
-
-	
-def TEST(output=log):
-    LOGGING_STATUS[DEV_SELECT] = 1
-    LOGGING_STATUS[DEV_UPDATE] = 1
-    class AgentsTable(Table):
-	def _defineRows(self):
-	    self.d_addColumn("agent_id",kInteger,None,primarykey = 1,autoincrement = 1)
-	    self.d_addColumn("login",kVarString,200,notnull=1)
-	    self.d_addColumn("ext_email",kVarString,200,notnull=1)
-	    self.d_addColumn("hashed_pw",kVarString,20,notnull=1)
-	    self.d_addColumn("name",kBigString,compress_ok=1)
-	    self.d_addColumn("auth_level",kInteger,None)
-            self.d_addColumn("ticket_count",kIncInteger,None)
-
-
-    import MySQLdb
-    rdb = MySQLdb.connect(host = 'localhost',user='root', passwd = '', db='testdb')
-    ndb = MySQLdb.connect(host = 'localhost',user='trakken', passwd = 'trakpas', db='testdb')
-    db = Database(ndb)
-
-    tbl = AgentsTable(db,"agents")
-
-    cursor = rdb.cursor()
-
-    # ---------------------------------------------------------------
-    # initialize
-    output("drop table agents")
-    try:
-        cursor.execute("drop table agents")   # clean out the table
-    except MySQLdb.OperationalError:
-        pass
-    output("creating table")
-    cursor.execute("create table agents (agent_id integer not null primary key auto_increment, login varchar(200) not null, unique (login), ext_email varchar(200) not null, hashed_pw varchar(20) not null, name varchar(200), auth_level integer default 0, ticket_count integer default 0)")
-
-    TEST_INSERT_COUNT = 5
-
-    # ---------------------------------------------------------------
-    # make sure we can catch a missing row
-
-    try:
-	a_row = tbl.fetchRow( ("agent_id", 1000) )
-	raise "test error"
-    except eNoMatchingRows:
-	pass
-
-    output("PASSED! fetch missing row test")
-
-    # --------------------------------------------------------------
-    # create new rows and insert them
-
-    for n in range(TEST_INSERT_COUNT):
-	new_id = n + 1
-	
-	newrow = tbl.newRow()
-	newrow.name = "name #%d" % new_id
-	newrow.login = "name%d" % new_id
-        newrow.ext_email = "%d@name" % new_id
-	newrow.save()
-	if newrow.agent_id != new_id:
-	    raise "new insert id (%s) does not match expected value (%d)" % (newrow.agent_id,new_id)
-
-    output("PASSED! autoinsert test")
-
-    # --------------------------------------------------------------
-    # fetch one row
-    a_row = tbl.fetchRow( ("agent_id", 1) )
-
-    if a_row.name != "name #1":
-	raise "row data incorrect"
-
-    output("PASSED! fetch one row test")
-
-    # ---------------------------------------------------------------
-    # don't change and save it
-    # (i.e. the "dummy cursor" string should never be called!)
-    #
-    try:
-	a_row.save(cursor = "dummy cursor")
-    except AttributeError, reason:
-	raise "row tried to access cursor on save() when no changes were made!"
-
-    output("PASSED! don't save when there are no changed")
-
-    # ---------------------------------------------------------------
-    # change, save, load, test
-    
-    a_row.auth_level = 10
-    a_row.save()
-    b_row = tbl.fetchRow( ("agent_id", 1) )
-    if b_row.auth_level != 10:
-	raise "save and load failed"
-    
-
-    output("PASSED! change, save, load")
-
-    # --------------------------------------------------------------
-    # access unknown attribute
-    try:
-	a = a_row.UNKNOWN_ATTRIBUTE
-	raise "test error"
-    except AttributeError, reason:
-	pass
-
-    try:
-	a_row.UNKNOWN_ATTRIBUTE = 1
-	raise "test error"
-    except AttributeError, reason:
-	pass
-
-    output("PASSED! unknown attribute exception")
-
-    # --------------------------------------------------------------
-    # access unknown dict item
-
-    try:
-	a = a_row["UNKNOWN_ATTRIBUTE"]
-	raise "test error"
-    except KeyError, reason:
-	pass
-
-    try:
-	a_row["UNKNOWN_ATTRIBUTE"] = 1
-	raise "test error"
-    except KeyError, reason:
-	pass
-
-    output("PASSED! unknown dict item exception")
-
-    # --------------------------------------------------------------
-    # use wrong data for column type
-
-    try:
-	a_row.agent_id = "this is a string"
-	raise "test error"
-    except eInvalidData, reason:
-	pass
-
-    output("PASSED! invalid data for column type")
-
-    # --------------------------------------------------------------
-    # fetch 1 rows
-
-    rows = tbl.fetchRows( ('agent_id', 1) )
-    if len(rows) != 1:
-	raise "fetchRows() did not return 1 row!" % (TEST_INSERT_COUNT)
-
-    output("PASSED! fetch one row")
-
-
-    # --------------------------------------------------------------
-    # fetch All rows
-    
-    rows = tbl.fetchAllRows()
-    if len(rows) != TEST_INSERT_COUNT:
-        for a_row in rows:
-            output(repr(a_row))
-	raise "fetchAllRows() did not return TEST_INSERT_COUNT(%d) rows!" % (TEST_INSERT_COUNT)
-
-    output("PASSED! fetchall rows")
-
-  
-    # --------------------------------------------------------------
-    # delete row object
-
-    row = tbl.fetchRow( ('agent_id', 1) )
-    row.delete()
-    try:
-        row = tbl.fetchRow( ('agent_id', 1) )
-        raise "delete failed to delete row!"
-    except eNoMatchingRows:
-        pass
-
-    # --------------------------------------------------------------
-    # table deleteRow() call
-
-    row = tbl.fetchRow( ('agent_id',2) )
-    tbl.deleteRow( ('agent_id', 2) )
-    try:
-        row = tbl.fetchRow( ('agent_id',2) )
-        raise "table delete failed"
-    except eNoMatchingRows:
-        pass
-
-    # --------------------------------------------------------------
-    # table deleteRow() call
-
-    row = tbl.fetchRow( ('agent_id',3) )
-    if row.databaseSizeForColumn('name') != len(row.name):
-        raise "databaseSizeForColumn('name') failed"
-    
-    # --------------------------------------------------------------
-    # test inc fields
-    row = tbl.newRow()
-    new_id = 1092
-    row.name = "name #%d" % new_id
-    row.login = "name%d" % new_id
-    row.ext_email = "%d@name" % new_id
-    row.inc('ticket_count')
-    row.save()
-    new_id = row.agent_id
-
-    trow = tbl.fetchRow( ('agent_id',new_id) )
-    if trow.ticket_count != 1:
-        raise "ticket_count didn't inc!"
-
-    row.inc('ticket_count', count=2)
-    row.save()
-    trow = tbl.fetchRow( ('agent_id',new_id) )
-    if trow.ticket_count != 3:
-        raise "ticket_count wrong, expected 3, got %d" % trow.ticket_count
-
-    trow.inc('ticket_count')
-    trow.save()
-    if trow.ticket_count != 4:
-        raise "ticket_count wrong, expected 4, got %d" % trow.ticket_count
-
-    output("\n==== ALL TESTS PASSED ====")
-    
 
 if __name__ == "__main__":
-    TEST()
-
+    print "run odb_test.py"
diff -Nru clearsilver-0.9.7/python/examples/base/odb_mysql.py clearsilver-0.9.8/python/examples/base/odb_mysql.py
--- clearsilver-0.9.7/python/examples/base/odb_mysql.py	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.8/python/examples/base/odb_mysql.py	Thu Mar 25 12:53:55 2004
@@ -0,0 +1,72 @@
+#! /usr/bin/env python
+
+"""
+usage: %(progname)s [args]
+"""
+
+
+# import startscript; startscript.init(__name__)
+import os, sys, string, time, getopt
+from log import *
+
+import odb
+import MySQLdb
+
+class Database(odb.Database):
+  def __init__(self,db, debug=0):
+    odb.Database.__init__(self, db, debug=debug)
+    self.SQLError = MySQLdb.Error
+    
+  def escape(self,str):
+    if str is None: return None
+    return MySQLdb.escape_string(str)
+
+  def listTables(self, cursor=None):
+    if cursor is None: cursor = self.defaultCursor()
+    cursor.execute("show tables")
+    rows = cursor.fetchall()
+    tables = []
+    for row in rows:
+      tables.append(row[0])
+    return tables
+
+  def listIndices(self, cursor=None):
+    return []
+
+  def listFieldsDict(self, table_name, cursor=None):
+    if cursor is None: cursor = self.defaultCursor()
+    sql = "show columns from %s" % table_name
+    cursor.execute(sql)
+    rows = cursor.fetchall()
+
+    columns = {}
+    for row in rows:
+      colname = row[0]
+      columns[colname] = row
+
+    return columns
+
+  def alterTableToMatch(self):
+    invalidAppCols, invalidDBCols = self.checkTable()
+    if not invalidAppCols: return
+
+    defs = []
+    for colname in invalidAppCols.keys():
+      col = self.getColumnDef(colname)
+      colname = col[0]
+      coltype = col[1]
+      options = col[2]
+      defs.append(self.colTypeToSQLType(colname, coltype, options))
+
+    defs = string.join(defs, ", ")
+
+    sql = "alter table %s add column " % self.getTableName()
+    sql = sql + "(" + defs + ")"
+
+    print sql
+
+    cur = self.db.defaultCursor()
+    cur.execute(sql)
+      
+
+    
Binary files clearsilver-0.9.7/python/examples/base/odb_mysql.pyc and clearsilver-0.9.8/python/examples/base/odb_mysql.pyc differ
diff -Nru clearsilver-0.9.7/python/examples/base/odb_sqlite.py clearsilver-0.9.8/python/examples/base/odb_sqlite.py
--- clearsilver-0.9.7/python/examples/base/odb_sqlite.py	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.8/python/examples/base/odb_sqlite.py	Thu Mar 25 12:53:55 2004
@@ -0,0 +1,171 @@
+#! /usr/bin/env python
+
+"""
+usage: %(progname)s [args]
+"""
+
+
+import os, sys, string, time, getopt
+from log import *
+
+import odb
+import sqlite
+
+import re
+
+# --- these are using for removing nulls from strings
+# --- because sqlite can't handle them
+
+def escape_string(str):
+    def subfn(m):
+        c = m.group(0)
+        return "%%%02X" % ord(c)
+
+    return re.sub("('|\0|%)",subfn,str)
+
+def unescape_string(str):
+    def subfn(m):
+        hexnum = int(m.group(1),16)
+        return "%c" % hexnum
+    return re.sub("%(..)",subfn,str)
+
+class Database(odb.Database):
+  def __init__(self,db, debug=0):
+    odb.Database.__init__(self, db, debug=debug)
+    self.SQLError = sqlite.Error
+    
+  def escape(self,str):
+    if str is None:
+      return None
+    elif type(str) == type(""):
+      return string.replace(str,"'","''")
+    elif type(str) == type(1):
+      return str
+    else:
+      raise "unknown column data type: %s" % type(str)
+
+
+  def listTables(self, cursor=None):
+    if cursor is None: cursor = self.defaultCursor()
+    cursor.execute("select name from sqlite_master where type='table'")
+    rows = cursor.fetchall()
+    tables = []
+    for row in rows: tables.append(row[0])
+    return tables
+
+  def listIndices(self, cursor=None):
+    if cursor is None: cursor = self.defaultCursor()
+    cursor.execute("select name from sqlite_master where type='index'")
+    rows = cursor.fetchall()
+    tables = []
+    for row in rows: tables.append(row[0])
+    return tables
+
+  def listFieldsDict(self, table_name, cursor=None):
+    if cursor is None: cursor = self.defaultCursor()
+    sql = "pragma table_info(%s)" % table_name
+    cursor.execute(sql)
+    rows = cursor.fetchall()
+
+    columns = {}
+    for row in rows:
+      colname = row[1]
+      columns[colname] = row
+    return columns
+
+  def _tableCreateStatement(self, table_name, cursor=None):
+    if cursor is None: cursor = self.defaultCursor()
+    sql = "select sql from sqlite_master where type='table' and name='%s'" % table_name
+    print sql
+    cursor.execute(sql)
+    row = cursor.fetchone()
+    sqlstatement = row[0]
+    return sqlstatement
+    
+
+  def alterTableToMatch(self, table):
+    tableName = table.getTableName()
+    tmpTableName = tableName + "_" + str(os.getpid())
+
+
+    invalidAppCols, invalidDBCols = table.checkTable(warnflag=0)
+
+##     if invalidAppCols or invalidDBCols:
+##       return
+
+    if not invalidAppCols and not invalidDBCols:
+      return
+
+
+    oldcols = self.listFieldsDict(tableName)
+#    tmpcols = oldcols.keys()
+    
+    tmpcols = []
+    newcols = table.getAppColumnList()
+    for colname, coltype, options in newcols:
+      if oldcols.has_key(colname): tmpcols.append(colname)
+    
+    tmpcolnames = string.join(tmpcols, ",")
+      
+    statements = []
+
+    sql = "begin transaction"
+    statements.append(sql)
+
+    sql = "create temporary table %s (%s)" % (tmpTableName, tmpcolnames)
+    statements.append(sql)
+
+    sql = "insert into %s select %s from %s" % (tmpTableName, tmpcolnames, tableName)
+    statements.append(sql)
+
+    sql = "drop table %s" % tableName
+    statements.append(sql)
+    
+    sql = table._createTableSQL()
+    statements.append(sql)
+
+    sql = "insert into %s(%s) select %s from %s" % (tableName, tmpcolnames, tmpcolnames, tmpTableName)
+    statements.append(sql)
+
+    sql = "drop table %s" % tmpTableName
+    statements.append(sql)
+
+    sql = "commit"
+    statements.append(sql)
+
+    cur = self.defaultCursor()
+    for statement in statements:
+#      print statement
+      cur.execute(statement)
+
+
+def test():
+  pass
+
+def usage(progname):
+  print __doc__ % vars()
+
+def main(argv, stdout, environ):
+  progname = argv[0]
+  optlist, args = getopt.getopt(argv[1:], "", ["help", "test", "debug"])
+
+  testflag = 0
+  if len(args) == 0:
+    usage(progname)
+    return
+  for (field, val) in optlist:
+    if field == "--help":
+      usage(progname)
+      return
+    elif field == "--debug":
+      debugfull()
+    elif field == "--test":
+      testflag = 1
+
+  if testflag:
+    test()
+    return
+
+
+if __name__ == "__main__":
+  main(sys.argv, sys.stdout, os.environ)
Binary files clearsilver-0.9.7/python/examples/base/odb_sqlite.pyc and clearsilver-0.9.8/python/examples/base/odb_sqlite.pyc differ
diff -Nru clearsilver-0.9.7/python/examples/base/odb_test.py clearsilver-0.9.8/python/examples/base/odb_test.py
--- clearsilver-0.9.7/python/examples/base/odb_test.py	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.8/python/examples/base/odb_test.py	Thu Mar 25 12:53:55 2004
@@ -0,0 +1,296 @@
+#!/usr/bin/env python
+
+from odb import *
+
+## -----------------------------------------------------------------------
+##                            T  E  S  T S
+## -----------------------------------------------------------------------
+
+import MySQLdb
+import odb_mysql
+import sqlite
+import odb_sqlite
+        
+def TEST(output=log):
+    LOGGING_STATUS[DEV_SELECT] = 1
+    LOGGING_STATUS[DEV_UPDATE] = 1
+
+    print "------ TESTING MySQLdb ---------"
+    rdb = MySQLdb.connect(host = 'localhost',user='root', passwd = '', db='testdb')
+    ndb = MySQLdb.connect(host = 'localhost',user='trakken', passwd = 'trakpas', db='testdb')
+    cursor = rdb.cursor()
+    
+    output("drop table agents")
+    try:
+        cursor.execute("drop table agents")   # clean out the table
+    except:
+        pass
+    output("creating table")
+
+    SQL = """
+
+    create table agents (
+       agent_id integer not null primary key auto_increment,
+       login varchar(200) not null,
+       unique (login),
+       ext_email varchar(200) not null,
+       hashed_pw varchar(20) not null,
+       name varchar(200),
+       auth_level integer default 0,
+       ticket_count integer default 0)
+       """
+
+    cursor.execute(SQL)
+    db = odb_mysql.Database(ndb)
+    TEST_DATABASE(rdb,db,output=output)
+
+    print "------ TESTING sqlite ----------"
+    rdb = sqlite.connect("/tmp/test.db",autocommit=1)
+    cursor = rdb.cursor()
+    try:
+        cursor.execute("drop table agents")
+    except:
+        pass
+    SQL = """
+    create table agents (
+       agent_id integer primary key,
+       login varchar(200) not null,
+       ext_email varchar(200) not null,
+       hashed_pw varchar(20),
+       name varchar(200),
+       auth_level integer default 0,
+       ticket_count integer default 0)"""
+    cursor.execute(SQL)
+    rdb = sqlite.connect("/tmp/test.db",autocommit=1)
+    ndb = sqlite.connect("/tmp/test.db",autocommit=1)
+        
+    db = odb_sqlite.Database(ndb)
+    TEST_DATABASE(rdb,db,output=output,is_mysql=0)
+    
+    
+
+
+def TEST_DATABASE(rdb,db,output=log,is_mysql=1):
+
+    cursor = rdb.cursor()
+    
+    class AgentsTable(Table):
+        def _defineRows(self):
+            self.d_addColumn("agent_id",kInteger,None,primarykey = 1,autoincrement = 1)
+            self.d_addColumn("login",kVarString,200,notnull=1)
+            self.d_addColumn("ext_email",kVarString,200,notnull=1)
+            self.d_addColumn("hashed_pw",kVarString,20,notnull=1)
+            self.d_addColumn("name",kBigString,compress_ok=1)
+            self.d_addColumn("auth_level",kInteger,None)
+            self.d_addColumn("ticket_count",kIncInteger,None)
+
+    tbl = AgentsTable(db,"agents")
+
+
+
+
+    TEST_INSERT_COUNT = 5
+
+    # ---------------------------------------------------------------
+    # make sure we can catch a missing row
+
+    try:
+        a_row = tbl.fetchRow( ("agent_id", 1000) )
+        raise "test error"
+    except eNoMatchingRows:
+        pass
+
+    output("PASSED! fetch missing row test")
+
+    # --------------------------------------------------------------
+    # create new rows and insert them
+
+    for n in range(TEST_INSERT_COUNT):
+        new_id = n + 1
+        
+        newrow = tbl.newRow()
+        newrow.name = "name #%d" % new_id
+        newrow.login = "name%d" % new_id
+        newrow.ext_email = "%d@name" % new_id
+        newrow.save()
+        if newrow.agent_id != new_id:
+            raise "new insert id (%s) does not match expected value (%d)" % (newrow.agent_id,new_id)
+
+    output("PASSED! autoinsert test")
+
+    # --------------------------------------------------------------
+    # fetch one row
+    a_row = tbl.fetchRow( ("agent_id", 1) )
+
+    if a_row.name != "name #1":
+        raise "row data incorrect"
+
+    output("PASSED! fetch one row test")
+
+    # ---------------------------------------------------------------
+    # don't change and save it
+    # (i.e. the "dummy cursor" string should never be called!)
+    #
+    try:
+        a_row.save(cursor = "dummy cursor")
+    except AttributeError, reason:
+        raise "row tried to access cursor on save() when no changes were made!"
+
+    output("PASSED! don't save when there are no changed")
+
+    # ---------------------------------------------------------------
+    # change, save, load, test
+    
+    a_row.auth_level = 10
+    a_row.save()
+    b_row = tbl.fetchRow( ("agent_id", 1) )
+    if b_row.auth_level != 10:
+        log(repr(b_row))
+        raise "save and load failed"
+    
+
+    output("PASSED! change, save, load")
+
+    # ---------------------------------------------------------------
+    # replace
+
+
+    repl_row = tbl.newRow(replace=1)
+    repl_row.agent_id = a_row.agent_id
+    repl_row.login = a_row.login + "-" + a_row.login
+    repl_row.ext_email = "foo"
+    repl_row.save()
+
+    b_row = tbl.fetchRow( ("agent_id", a_row.agent_id) )
+    if b_row.login != repl_row.login:
+        raise "replace failed"
+    output("PASSED! replace")
+
+    # --------------------------------------------------------------
+    # access unknown dict item
+
+    try:
+        a = a_row["UNKNOWN_ATTRIBUTE"]
+        raise "test error"
+    except KeyError, reason:
+        pass
+
+    try:
+        a_row["UNKNOWN_ATTRIBUTE"] = 1
+        raise "test error"
+    except KeyError, reason:
+        pass
+
+    output("PASSED! unknown dict item exception")
+
+    # --------------------------------------------------------------
+    # access unknown attribute
+    try:
+        a = a_row.UNKNOWN_ATTRIBUTE
+        raise "test error"
+    except AttributeError, reason:
+        pass
+
+    try:
+        a_row.UNKNOWN_ATTRIBUTE = 1
+        raise "test error"
+    except AttributeError, reason:
+        pass
+
+    output("PASSED! unknown attribute exception")
+
+ 
+    # --------------------------------------------------------------
+    # use wrong data for column type
+
+    try:
+        a_row.agent_id = "this is a string"
+        raise "test error"
+    except eInvalidData, reason:
+        pass
+
+    output("PASSED! invalid data for column type")
+
+    # --------------------------------------------------------------
+    # fetch 1 rows
+
+    rows = tbl.fetchRows( ('agent_id', 1) )
+    if len(rows) != 1:
+        raise "fetchRows() did not return 1 row!" % (TEST_INSERT_COUNT)
+
+    output("PASSED! fetch one row")
+
+
+    # --------------------------------------------------------------
+    # fetch All rows
+    
+    rows = tbl.fetchAllRows()
+    if len(rows) != TEST_INSERT_COUNT:
+        for a_row in rows:
+            output(repr(a_row))
+        raise "fetchAllRows() did not return TEST_INSERT_COUNT(%d) rows!" % (TEST_INSERT_COUNT)
+
+    output("PASSED! fetchall rows")
+
+  
+    # --------------------------------------------------------------
+    # delete row object
+
+    row = tbl.fetchRow( ('agent_id', 1) )
+    row.delete()
+    try:
+        row = tbl.fetchRow( ('agent_id', 1) )
+        raise "delete failed to delete row!"
+    except eNoMatchingRows:
+        pass
+
+    # --------------------------------------------------------------
+    # table deleteRow() call
+
+    row = tbl.fetchRow( ('agent_id',2) )
+    tbl.deleteRow( ('agent_id', 2) )
+    try:
+        row = tbl.fetchRow( ('agent_id',2) )
+        raise "table delete failed"
+    except eNoMatchingRows:
+        pass
+
+    # --------------------------------------------------------------
+    # table deleteRow() call
+
+    row = tbl.fetchRow( ('agent_id',3) )
+    if row.databaseSizeForColumn('name') != len(row.name):
+        raise "databaseSizeForColumn('name') failed"
+    
+    # --------------------------------------------------------------
+    # test inc fields
+    row = tbl.newRow()
+    new_id = 1092
+    row.name = "name #%d" % new_id
+    row.login = "name%d" % new_id
+    row.ext_email = "%d@name" % new_id
+    row.inc('ticket_count')
+    row.save()
+    new_id = row.agent_id
+
+    trow = tbl.fetchRow( ('agent_id',new_id) )
+    if trow.ticket_count != 1:
+        raise "ticket_count didn't inc!"
+
+    row.inc('ticket_count', count=2)
+    row.save()
+    trow = tbl.fetchRow( ('agent_id',new_id) )
+    if trow.ticket_count != 3:
+        raise "ticket_count wrong, expected 3, got %d" % trow.ticket_count
+
+    trow.inc('ticket_count')
+    trow.save()
+    if trow.ticket_count != 4:
+        raise "ticket_count wrong, expected 4, got %d" % trow.ticket_count
+
+    output("\n==== ALL TESTS PASSED ====")
+    
+
+if __name__ == "__main__":
+    TEST()
+
diff -Nru clearsilver-0.9.7/python/examples/base/sgmllib.py clearsilver-0.9.8/python/examples/base/sgmllib.py
--- clearsilver-0.9.7/python/examples/base/sgmllib.py	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.8/python/examples/base/sgmllib.py	Tue Mar 16 13:18:18 2004
@@ -0,0 +1,615 @@
+# A parser for SGML, using the derived class as static DTD.
+
+# XXX This only supports those SGML features used by HTML.
+
+# XXX There should be a way to distinguish between PCDATA (parsed
+# character data -- the normal case), RCDATA (replaceable character
+# data -- only char and entity references and end tags are special)
+# and CDATA (character data -- only end tags are special).
+
+# sgmlop support added by fredrik@pythonware.com (April 6, 1998)
+
+import re
+import string
+import sys
+
+try:
+    from xml.parsers import sgmlop
+except ImportError:
+    sgmlop = None
+
+# standard entity defs
+
+ENTITYDEFS = {
+    'lt': '<',
+    'gt': '>',
+    'amp': '&',
+    'quot': '"',
+    'apos': '\''
+    }
+
+# SGML parser base class -- find tags and call handler functions.
+# Usage: p = SGMLParser(); p.feed(data); ...; p.close().
+# The dtd is defined by deriving a class which defines methods
+# with special names to handle tags: start_foo and end_foo to handle
+# <foo> and </foo>, respectively, or do_foo to handle <foo> by itself.
+# (Tags are converted to lower case for this purpose.)  The data
+# between tags is passed to the parser by calling self.handle_data()
+# with some data as argument (the data may be split up in arbutrary
+# chunks).  Entity references are passed by calling
+# self.handle_entityref() with the entity reference as argument.
+
+# --------------------------------------------------------------------
+# original re-based SGML parser
+
+interesting = re.compile('[&<]')
+incomplete = re.compile('&([a-zA-Z][a-zA-Z0-9]*|#[0-9]*)?|'
+                           '<([a-zA-Z][^<>]*|'
+                              '/([a-zA-Z][^<>]*)?|'
+                              '![^<>]*)?')
+
+entityref = re.compile('&([a-zA-Z][a-zA-Z0-9]*)[^a-zA-Z0-9]')
+charref = re.compile('&#([0-9]+)[^0-9]')
+
+starttagopen = re.compile('<[>a-zA-Z]')
+shorttagopen = re.compile('<[a-zA-Z][a-zA-Z0-9]*/')
+shorttag = re.compile('<([a-zA-Z][a-zA-Z0-9]*)/([^/]*)/')
+endtagopen = re.compile('</[<>a-zA-Z]')
+endbracket = re.compile('[<>]')
+special = re.compile('<![^<>]*>')
+commentopen = re.compile('<!--')
+commentclose = re.compile('--[ \t\n]*>')
+tagfind = re.compile('[a-zA-Z][a-zA-Z0-9]*')
+attrfind = re.compile(
+    '[ \t\n]*([a-zA-Z_][-.a-zA-Z_0-9]*)'
+    '([ \t\n]*=[ \t\n]*'
+    r'(\'[^\']*\'|"[^"]*"|[^ \t\n]*))?')
+
+
+class SlowSGMLParser:
+
+    # Interface -- initialize and reset this instance
+    def __init__(self, verbose=0):
+        self.verbose = verbose
+        self.reset()
+
+    # Interface -- reset this instance.  Loses all unprocessed data
+    def reset(self):
+        self.rawdata = ''
+        self.stack = []
+        self.lasttag = '???'
+        self.nomoretags = 0
+        self.literal = 0
+
+    # For derived classes only -- enter literal mode (CDATA) till EOF
+    def setnomoretags(self):
+        self.nomoretags = self.literal = 1
+
+    # For derived classes only -- enter literal mode (CDATA)
+    def setliteral(self, *args):
+        self.literal = 1
+
+    # Interface -- feed some data to the parser.  Call this as
+    # often as you want, with as little or as much text as you
+    # want (may include '\n').  (This just saves the text, all the
+    # processing is done by goahead().)
+    def feed(self, data):
+        self.rawdata = self.rawdata + data
+        self.goahead(0)
+
+    # Interface -- handle the remaining data
+    def close(self):
+        self.goahead(1)
+
+    # Internal -- handle data as far as reasonable.  May leave state
+    # and data to be processed by a subsequent call.  If 'end' is
+    # true, force handling all data as if followed by EOF marker.
+    def goahead(self, end):
+        rawdata = self.rawdata
+        i = 0
+        n = len(rawdata)
+        while i < n:
+            if self.nomoretags:
+                self.handle_data(rawdata[i:n])
+                i = n
+                break
+            match = interesting.search(rawdata, i)
+            if match: j = match.start(0)
+            else: j = n
+            if i < j: self.handle_data(rawdata[i:j])
+            i = j
+            if i == n: break
+            if rawdata[i] == '<':
+                if starttagopen.match(rawdata, i):
+                    if self.literal:
+                        self.handle_data(rawdata[i])
+                        i = i+1
+                        continue
+                    k = self.parse_starttag(i)
+                    if k < 0: break
+                    i = k
+                    continue
+                if endtagopen.match(rawdata, i):
+                    k = self.parse_endtag(i)
+                    if k < 0: break
+                    i =  k
+                    self.literal = 0
+                    continue
+                if commentopen.match(rawdata, i):
+                    if self.literal:
+                        self.handle_data(rawdata[i])
+                        i = i+1
+                        continue
+                    k = self.parse_comment(i)
+                    if k < 0: break
+                    i = i+k
+                    continue
+                match = special.match(rawdata, i)
+                if match:
+                    if self.literal:
+                        self.handle_data(rawdata[i])
+                        i = i+1
+                        continue
+                    i = match.end(0)
+                    continue
+            elif rawdata[i] == '&':
+                match = charref.match(rawdata, i)
+                if match:
+                    name = match.group(1)
+                    self.handle_charref(name)
+                    i = match.end(0)
+                    if rawdata[i-1] != ';': i = i-1
+                    continue
+                match = entityref.match(rawdata, i)
+                if match:
+                    name = match.group(1)
+                    self.handle_entityref(name)
+                    i = match.end(0)
+                    if rawdata[i-1] != ';': i = i-1
+                    continue
+            else:
+                raise RuntimeError, 'neither < nor & ??'
+            # We get here only if incomplete matches but
+            # nothing else
+            match = incomplete.match(rawdata, i)
+            if not match:
+                self.handle_data(rawdata[i])
+                i = i+1
+                continue
+            j = match.end(0)
+            if j == n:
+                break # Really incomplete
+            self.handle_data(rawdata[i:j])
+            i = j
+        # end while
+        if end and i < n:
+            self.handle_data(rawdata[i:n])
+            i = n
+        self.rawdata = rawdata[i:]
+        # XXX if end: check for empty stack
+
+    # Internal -- parse comment, return length or -1 if not terminated
+    def parse_comment(self, i):
+        rawdata = self.rawdata
+        if rawdata[i:i+4] <> '<!--':
+            raise RuntimeError, 'unexpected call to handle_comment'
+        match = commentclose.search(rawdata, i+4)
+        if not match:
+            return -1
+        j = match.start(0)
+        self.handle_comment(rawdata[i+4: j])
+        j = match.end(0)
+        return j-i
+
+    # Internal -- handle starttag, return length or -1 if not terminated
+    def parse_starttag(self, i):
+        rawdata = self.rawdata
+        if shorttagopen.match(rawdata, i):
+            # SGML shorthand: <tag/data/ == <tag>data</tag>
+            # XXX Can data contain &... (entity or char refs)?
+            # XXX Can data contain < or > (tag characters)?
+            # XXX Can there be whitespace before the first /?
+            match = shorttag.match(rawdata, i)
+            if not match:
+                return -1
+            tag, data = match.group(1, 2)
+            tag = string.lower(tag)
+            self.finish_shorttag(tag, data)
+            k = match.end(0)
+            return k
+        # XXX The following should skip matching quotes (' or ")
+        match = endbracket.search(rawdata, i+1)
+        if not match:
+            return -1
+        j = match.start(0)
+        # Now parse the data between i+1 and j into a tag and attrs
+        attrs = []
+        if rawdata[i:i+2] == '<>':
+            # SGML shorthand: <> == <last open tag seen>
+            k = j
+            tag = self.lasttag
+        else:
+            match = tagfind.match(rawdata, i+1)
+            if not match:
+                raise RuntimeError, 'unexpected call to parse_starttag'
+            k = match.end(0)
+            tag = string.lower(rawdata[i+1:k])
+            self.lasttag = tag
+        while k < j:
+            match = attrfind.match(rawdata, k)
+            if not match: break
+            attrname, rest, attrvalue = match.group(1, 2, 3)
+            if not rest:
+                attrvalue = attrname
+            elif attrvalue[:1] == '\'' == attrvalue[-1:] or \
+                 attrvalue[:1] == '"' == attrvalue[-1:]:
+                attrvalue = attrvalue[1:-1]
+            attrs.append((string.lower(attrname), attrvalue))
+            k = match.end(0)
+        if rawdata[j] == '>':
+            j = j+1
+        self.finish_starttag(tag, attrs)
+        return j
+
+    # Internal -- parse endtag
+    def parse_endtag(self, i):
+        rawdata = self.rawdata
+        match = endbracket.search(rawdata, i+1)
+        if not match:
+            return -1
+        j = match.start(0)
+        tag = string.lower(string.strip(rawdata[i+2:j]))
+        if rawdata[j] == '>':
+            j = j+1
+        self.finish_endtag(tag)
+        return j
+
+    # Internal -- finish parsing of <tag/data/ (same as <tag>data</tag>)
+    def finish_shorttag(self, tag, data):
+        self.finish_starttag(tag, [])
+        self.handle_data(data)
+        self.finish_endtag(tag)
+
+    # Internal -- finish processing of start tag
+    # Return -1 for unknown tag, 0 for open-only tag, 1 for balanced tag
+    def finish_starttag(self, tag, attrs):
+        try:
+            method = getattr(self, 'start_' + tag)
+        except AttributeError:
+            try:
+                method = getattr(self, 'do_' + tag)
+            except AttributeError:
+                self.unknown_starttag(tag, attrs)
+                return -1
+            else:
+                self.handle_starttag(tag, method, attrs)
+                return 0
+        else:
+            self.stack.append(tag)
+            self.handle_starttag(tag, method, attrs)
+            return 1
+
+    # Internal -- finish processing of end tag
+    def finish_endtag(self, tag):
+        if not tag:
+            found = len(self.stack) - 1
+            if found < 0:
+                self.unknown_endtag(tag)
+                return
+        else:
+            if tag not in self.stack:
+                try:
+                    method = getattr(self, 'end_' + tag)
+                except AttributeError:
+                    self.unknown_endtag(tag)
+                return
+            found = len(self.stack)
+            for i in range(found):
+                if self.stack[i] == tag: found = i
+        while len(self.stack) > found:
+            tag = self.stack[-1]
+            try:
+                method = getattr(self, 'end_' + tag)
+            except AttributeError:
+                method = None
+            if method:
+                self.handle_endtag(tag, method)
+            else:
+                self.unknown_endtag(tag)
+            del self.stack[-1]
+
+    # Overridable -- handle start tag
+    def handle_starttag(self, tag, method, attrs):
+        method(attrs)
+
+    # Overridable -- handle end tag
+    def handle_endtag(self, tag, method):
+        method()
+
+    # Example -- report an unbalanced </...> tag.
+    def report_unbalanced(self, tag):
+        if self.verbose:
+            print '*** Unbalanced </' + tag + '>'
+            print '*** Stack:', self.stack
+
+    # Example -- handle character reference, no need to override
+    def handle_charref(self, name):
+        try:
+            n = string.atoi(name)
+        except string.atoi_error:
+            self.unknown_charref(name)
+            return
+        if not 0 <= n <= 255:
+            self.unknown_charref(name)
+            return
+        self.handle_data(chr(n))
+
+    # Definition of entities -- derived classes may override
+    entitydefs = ENTITYDEFS
+
+    # Example -- handle entity reference, no need to override
+    def handle_entityref(self, name):
+        table = self.entitydefs
+        if table.has_key(name):
+            self.handle_data(table[name])
+        else:
+            self.unknown_entityref(name)
+            return
+
+    # Example -- handle data, should be overridden
+    def handle_data(self, data):
+        pass
+
+    # Example -- handle comment, could be overridden
+    def handle_comment(self, data):
+        pass
+
+    # To be overridden -- handlers for unknown objects
+    def unknown_starttag(self, tag, attrs): pass
+    def unknown_endtag(self, tag): pass
+    def unknown_charref(self, ref): pass
+    def unknown_entityref(self, ref): pass
+
+
+# --------------------------------------------------------------------
+# accelerated SGML parser
+
+class FastSGMLParser:
+
+    # Interface -- initialize and reset this instance
+    def __init__(self, verbose=0):
+        self.verbose = verbose
+        self.reset()
+
+    # Interface -- reset this instance.  Loses all unprocessed data
+    def reset(self):
+        self.rawdata = ''
+        self.stack = []
+        self.lasttag = '???'
+        self.nomoretags = 0
+        self.literal = 0
+        self.parser = sgmlop.SGMLParser()
+        self.feed = self.parser.feed
+        self.parser.register(self)
+
+    # For derived classes only -- enter literal mode (CDATA) till EOF
+    def setnomoretags(self):
+        self.nomoretags = self.literal = 1 # FIXME!
+
+    # For derived classes only -- enter literal mode (CDATA)
+    def setliteral(self, *args):
+        self.literal = 1 # FIXME!
+
+    # Interface -- feed some data to the parser.  Call this as
+    # often as you want, with as little or as much text as you
+    # want (may include '\n').
+    def feed(self, data): # overridden by reset
+        self.parser.feed(data)
+
+    # Interface -- handle the remaining data
+    def close(self):
+        try:
+            self.parser.close()
+        finally:
+            self.parser = None
+
+    # Internal -- finish parsing of <tag/data/ (same as <tag>data</tag>)
+    def finish_shorttag(self, tag, data):
+        self.finish_starttag(tag, [])
+        self.handle_data(data)
+        self.finish_endtag(tag)
+
+    # Internal -- finish processing of start tag
+    # Return -1 for unknown tag, 0 for open-only tag, 1 for balanced tag
+    def finish_starttag(self, tag, attrs):
+        # FIXME: should move this logic into sgmlop!
+        try:
+            method = getattr(self, 'start_' + tag)
+        except AttributeError:
+            try:
+                method = getattr(self, 'do_' + tag)
+            except AttributeError:
+                self.unknown_starttag(tag, attrs)
+                return -1
+            else:
+                self.handle_starttag(tag, method, attrs)
+                return 0
+        else:
+            self.stack.append(tag)
+            self.handle_starttag(tag, method, attrs)
+            return 1
+
+    # Internal -- finish processing of end tag
+    def finish_endtag(self, tag):
+        if not tag:
+            found = len(self.stack) - 1
+            if found < 0:
+                self.unknown_endtag(tag)
+                return
+        else:
+            if tag not in self.stack:
+                try:
+                    method = getattr(self, 'end_' + tag)
+                except AttributeError:
+                    self.unknown_endtag(tag)
+                return
+            found = len(self.stack)
+            for i in range(found):
+                if self.stack[i] == tag: found = i
+        while len(self.stack) > found:
+            tag = self.stack[-1]
+            try:
+                method = getattr(self, 'end_' + tag)
+            except AttributeError:
+                method = None
+            if method:
+                self.handle_endtag(tag, method)
+            else:
+                self.unknown_endtag(tag)
+            del self.stack[-1]
+
+    # Overridable -- handle start tag
+    def handle_starttag(self, tag, method, attrs):
+        method(attrs)
+
+    # Overridable -- handle end tag
+    def handle_endtag(self, tag, method):
+        method()
+
+    # Example -- report an unbalanced </...> tag.
+    def report_unbalanced(self, tag):
+        if self.verbose:
+            print '*** Unbalanced </' + tag + '>'
+            print '*** Stack:', self.stack
+
+    # Example -- handle character reference, no need to override
+    # def handle_charref(self, name):
+    #     pass
+
+    # Definition of entities -- derived classes may override
+    entitydefs = ENTITYDEFS
+
+    # Example -- handle entity reference, no need to override
+    def handle_entityref(self, name):
+        try:
+            data = self.entitydefs[name]
+        except KeyError:
+            self.unknown_entityref(name)
+        else:
+            self.handle_data(data)
+
+    # Example -- handle data, should be overridden
+    def handle_data(self, data):
+        pass
+
+    # Example -- handle comment, could be overridden
+    # def handle_comment(self, data):
+    #   pass
+
+    # To be overridden -- handlers for unknown objects
+    def unknown_starttag(self, tag, attrs): pass
+    def unknown_endtag(self, tag): pass
+    def unknown_charref(self, ref): pass
+    def unknown_entityref(self, ref): pass
+
+
+#sgmlop = None
+
+# pick a suitable parser
+if sgmlop:
+    SGMLParser = FastSGMLParser
+else:
+    SGMLParser = SlowSGMLParser
+
+# --------------------------------------------------------------------
+# test stuff
+
+class TestSGMLParser(SGMLParser):
+
+    def __init__(self, verbose=0):
+        self.testdata = ""
+        SGMLParser.__init__(self, verbose)
+
+    def handle_data(self, data):
+        self.testdata = self.testdata + data
+        if len(`self.testdata`) >= 70:
+            self.flush()
+
+    def flush(self):
+        data = self.testdata
+        if data:
+            self.testdata = ""
+            print 'data:', `data`
+
+    def handle_comment(self, data):
+        self.flush()
+        r = `data`
+        if len(r) > 68:
+            r = r[:32] + '...' + r[-32:]
+        print 'comment:', r
+
+    def unknown_starttag(self, tag, attrs):
+        self.flush()
+        if not attrs:
+            print 'start tag: <' + tag + '>'
+        else:
+            print 'start tag: <' + tag,
+            for name, value in attrs:
+                print name + '=' + '"' + value + '"',
+            print '>'
+
+    def unknown_endtag(self, tag):
+        self.flush()
+        print 'end tag: </' + tag + '>'
+
+    def unknown_entityref(self, ref):
+        self.flush()
+        print '*** unknown entity ref: &' + ref + ';'
+
+    def unknown_charref(self, ref):
+        self.flush()
+        print '*** unknown char ref: &#' + ref + ';'
+
+    def close(self):
+        SGMLParser.close(self)
+        self.flush()
+
+
+def test(args = None):
+    import sys
+
+    if not args:
+        args = sys.argv[1:]
+
+    if args and args[0] == '-s':
+        args = args[1:]
+        klass = SGMLParser
+    else:
+        klass = TestSGMLParser
+
+    if args:
+        file = args[0]
+    else:
+        file = 'test.html'
+
+    if file == '-':
+        f = sys.stdin
+    else:
+        try:
+            f = open(file, 'r')
+        except IOError, msg:
+            print file, ":", msg
+            sys.exit(1)
+
+    data = f.read()
+    if f is not sys.stdin:
+        f.close()
+
+    x = klass()
+    for c in data:
+        x.feed(c)
+    x.close()
+
+
+if __name__ == '__main__':
+    test()
diff -Nru clearsilver-0.9.7/python/neo_cgi.c clearsilver-0.9.8/python/neo_cgi.c
--- clearsilver-0.9.7/python/neo_cgi.c	Thu Sep 11 19:03:11 2003
+++ clearsilver-0.9.8/python/neo_cgi.c	Tue Mar 16 01:40:59 2004
@@ -258,13 +258,14 @@
   CGI *cgi = ((CGIObject *) self)->cgi;
   char *name, *value, *path = NULL, *domain = NULL, *time_str = NULL;
   int persist = 0;
+  int secure = 0;
   NEOERR *err;
-  static char *kwlist[] = {"name", "value", "path", "domain", "time_str", "persist", NULL};
+  static char *kwlist[] = {"name", "value", "path", "domain", "time_str", "persist", "secure", NULL};
 
-  if (!PyArg_ParseTupleAndKeywords(args, keywds, "ss|sssi:cookieSet()", kwlist, &name, &value, &path, &domain, &time_str, &persist))
+  if (!PyArg_ParseTupleAndKeywords(args, keywds, "ss|sssii:cookieSet()", kwlist, &name, &value, &path, &domain, &time_str, &persist, &secure))
     return NULL;
 
-  err = cgi_cookie_set (cgi, name, value, path, domain, time_str, persist);
+  err = cgi_cookie_set (cgi, name, value, path, domain, time_str, persist, secure);
   if (err) return p_neo_error (err);
   Py_INCREF(Py_None);
   return Py_None;
@@ -510,9 +511,10 @@
 
 
   buf = vsprintf_alloc(fmt, ap);
+  len = visprintf_alloc(&buf, fmt, ap);
+
   if (buf == NULL)
     return 0;
-  len = strlen(buf);
 
   str = PyString_FromStringAndSize (buf, len);
   free(buf);
diff -Nru clearsilver-0.9.7/rules.mk.in clearsilver-0.9.8/rules.mk.in
--- clearsilver-0.9.7/rules.mk.in	Mon Aug 11 14:37:09 2003
+++ clearsilver-0.9.8/rules.mk.in	Mon Jan  5 02:38:28 2004
@@ -83,6 +83,7 @@
 AR         = @AR@ cr
 RANLIB     = @RANLIB@
 DEP_LIBS   = $(DLIBS:-l%=$(LIB_DIR)lib%.a)
+DBI_LIBS   = -ldbi -ldl -lz
 LIBS       = @LIBS@
 LS         = /bin/ls
 XARGS      = xargs -i%
diff -Nru clearsilver-0.9.7/util/neo_misc.c clearsilver-0.9.8/util/neo_misc.c
--- clearsilver-0.9.7/util/neo_misc.c	Wed Apr  2 15:07:36 2003
+++ clearsilver-0.9.8/util/neo_misc.c	Mon Jan  5 02:34:55 2004
@@ -49,6 +49,25 @@
   va_end (ap);
 }
 
+static int LogLevel = 0;
+
+void ne_set_log (int level)
+{
+  LogLevel = level;
+}
+
+void ne_log (int level, char *fmt, ...)
+{
+  va_list ap;
+
+  if (LogLevel >= level)
+  {
+    va_start (ap, fmt);
+    ne_vwarn (fmt, ap);
+    va_end (ap);
+  }
+}
+
 UINT32 python_string_hash (const char *s)
 {
   int len=0;
diff -Nru clearsilver-0.9.7/util/neo_misc.h clearsilver-0.9.8/util/neo_misc.h
--- clearsilver-0.9.7/util/neo_misc.h	Tue Nov 11 18:11:52 2003
+++ clearsilver-0.9.8/util/neo_misc.h	Mon Jan  5 02:34:55 2004
@@ -103,6 +103,8 @@
 
 void ne_vwarn (char *fmt, va_list ap);
 void ne_warn (char *fmt, ...);
+void ne_set_log (int level);
+void ne_log (int level, char *fmt, ...);
 UINT32 python_string_hash (const char *s);
 UINT8 *ne_stream4 (UINT8  *dest, UINT32 num);
 UINT8 *ne_unstream4 (UINT32 *pnum, UINT8 *src);
diff -Nru clearsilver-0.9.7/util/neo_str.c clearsilver-0.9.8/util/neo_str.c
--- clearsilver-0.9.7/util/neo_str.c	Wed Oct  8 16:43:02 2003
+++ clearsilver-0.9.8/util/neo_str.c	Thu Dec  4 02:52:38 2003
@@ -269,36 +269,43 @@
 
 /* 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)
+int vnisprintf_alloc (char **buf, int start_size, char *fmt, va_list ap)
 {
-  char *b = NULL;
   int bl, size;
   va_list tmp;
 
+  *buf = NULL;
   size = start_size;
 
-  b = (char *) malloc (size * sizeof(char));
-  if (b == NULL) return NULL;
+  *buf = (char *) malloc (size * sizeof(char));
+  if (*buf == NULL) return 0;
   while (1)
   {
     va_copy(tmp, ap);
-    bl = vsnprintf (b, size, fmt, tmp);
+    bl = vsnprintf (*buf, size, fmt, tmp);
     if (bl > -1 && bl < size)
-      return b;
+      return bl;
     if (bl > -1)
       size = bl + 1;
     else
       size *= 2;
-    b = (char *) realloc (b, size * sizeof(char));
-    if (b == NULL) return NULL;
+    *buf = (char *) realloc (*buf, size * sizeof(char));
+    if (*buf == NULL) return 0;
   }
 }
 
+char *vnsprintf_alloc (int start_size, char *fmt, va_list ap)
+{
+  char *r;
+  vnisprintf_alloc(&r, start_size, fmt, ap);
+  return r;
+}
+
 /* 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)
+int visprintf_alloc (char **buf, char *fmt, va_list ap)
 {
-  char buf[4096];
+  char ibuf[4096];
   int bl, size;
   va_list tmp;
 
@@ -306,19 +313,43 @@
  * supposed to work at all */
   va_copy(tmp, ap);
   
-  size = sizeof (buf);
-  bl = vsnprintf (buf, sizeof (buf), fmt, tmp);
+  size = sizeof (ibuf);
+  bl = vsnprintf (ibuf, sizeof (ibuf), fmt, tmp);
   if (bl > -1 && bl < size)
-    return strdup (buf);
+  {
+    *buf = (char *) calloc(bl+1, sizeof(char));
+    if (*buf == NULL) return 0;
+    strncpy(*buf, ibuf, bl);
+    return bl;
+  }
 
   if (bl > -1)
     size = bl + 1;
   else
     size *= 2;
 
-  return vnsprintf_alloc(size, fmt, ap);
+  return vnisprintf_alloc(buf, size, fmt, ap);
 }
 
+char *vsprintf_alloc (char *fmt, va_list ap)
+{
+  char *r;
+  visprintf_alloc(&r, fmt, ap);
+  return r;
+}
+
+/* technically, sprintf's can have null values, so we need to be able to
+ * return a length also like real sprintf */
+int isprintf_alloc (char **buf, char *fmt, ...)
+{
+  va_list ap;
+  int r;
+
+  va_start (ap, fmt);
+  r = visprintf_alloc (buf, fmt, ap);
+  va_end (ap);
+  return r;
+}
 
 char *sprintf_alloc (char *fmt, ...)
 {
diff -Nru clearsilver-0.9.7/util/neo_str.h clearsilver-0.9.8/util/neo_str.h
--- clearsilver-0.9.7/util/neo_str.h	Wed Oct  8 16:43:02 2003
+++ clearsilver-0.9.8/util/neo_str.h	Thu Dec  4 02:52:38 2003
@@ -31,6 +31,12 @@
 char *vsprintf_alloc (char *fmt, va_list ap);
 char *vnsprintf_alloc (int start_size, char *fmt, va_list ap);
 
+/* Versions of the above which actually return a length, necessary if 
+ * you expect embedded NULLs */
+int vnisprintf_alloc (char **buf, int start_size, char *fmt, va_list ap);
+int visprintf_alloc (char **buf, char *fmt, va_list ap);
+int isprintf_alloc (char **buf, char *fmt, ...);
+
 typedef struct _string
 {
   UINT8 *buf;
