Index: /other-projects/realistic-books/trunk/CASCADE-MAKE.sh
===================================================================
--- /other-projects/realistic-books/trunk/CASCADE-MAKE.sh	(revision 21014)
+++ /other-projects/realistic-books/trunk/CASCADE-MAKE.sh	(revision 21014)
@@ -0,0 +1,58 @@
+#!/bin/bash
+
+source setup.bash silent
+source cascade-make/lib/cascade-lib.bash
+
+
+# Ensure that the necessary directories exist within the 'installed' area
+
+for d in include lib ; do
+  if [ ! -e "$RBHOME_INSTALLED/$d" ] ; then
+    echo "Creating $RBHOME_INSTALLED/$d"
+    mkdir -p "$RBHOME_INSTALLED/$d"
+  fi
+done
+
+
+for d in packages ; do
+  echo "Running $d/CASCADE-MAKE.sh $*"
+
+  (cd $d ; ./CASCADE-MAKE.sh $*)
+
+  if [ $? != 0 ] ; then
+    echo "Error encountered running $d/CASCADE-MAKE.sh"
+    exit 1
+  fi
+done
+
+if [ $install = "1" ] ; then
+
+  if [ ! -e "$RBHOME_INSTALLED/bin/script" ] ; then
+    echo "Creating $RBHOME_INSTALLED/bin/script"
+    mkdir "$RBHOME_INSTALLED/bin/script"
+  fi
+
+  /bin/cp src/pdf2realbook.pl "$RBHOME_INSTALLED/bin/script/."
+  chmod a+x "$RBHOME_INSTALLED/bin/script/pdf2realbook.pl"
+
+  # Configure CGI files
+
+  rbfile="cgi-bin/rbsite.cfg"
+  echo "Setting rbhome to $REALISTIC_BOOKS_HOME in $rbfile"
+  cat "$rbfile.in" \
+    | sed "s@\*\*RBHOME\*\*@$REALISTIC_BOOKS_HOME@g" \
+    > "$rbfile"
+
+  perlbin=`which perl 2>/dev/null`
+  if [ $? == "0" ] ; then
+    cgifile="cgi-bin/make-realbook.cgi"
+    echo "Setting PERLBIN to $perlbin in $cgifile"
+    cat "$cgifile.in" \
+      | sed "s@\*\*PERLBIN\*\*@$perlbin@g" \
+      > "$cgifile"
+  else
+    echo "Unable to find perl on PATH.  Failed to set PERLBIN in $cgifile"
+  fi 
+
+fi
+
Index: /other-projects/realistic-books/trunk/apache-server.bat
===================================================================
--- /other-projects/realistic-books/trunk/apache-server.bat	(revision 21014)
+++ /other-projects/realistic-books/trunk/apache-server.bat	(revision 21014)
@@ -0,0 +1,6 @@
+@echo off
+
+call setup.bat
+start "Apache Web Server" /MIN httpd
+sleep 2
+start http://localhost:8282/greenstone/cgi-bin/library.cgi
Index: /other-projects/realistic-books/trunk/bin/script/checkwritability.bat
===================================================================
--- /other-projects/realistic-books/trunk/bin/script/checkwritability.bat	(revision 21014)
+++ /other-projects/realistic-books/trunk/bin/script/checkwritability.bat	(revision 21014)
@@ -0,0 +1,75 @@
+@echo off
+
+if "%REALISTIC_BOOKS_HOME%" == "" goto EnvNotSet
+
+REM test if we have write permission on the greenstone install directory
+echo.
+echo Checking if REALISTIC_BOOKS_HOME is writable...
+echo This is a temporary file. It is safe to delete it. > "%REALISTIC_BOOKS_HOME%\books\testing.tmp"
+if not exist "%REALISTIC_BOOKS_HOME%\books\testing.tmp" goto CantWrite
+del "%REALISTIC_BOOKS_HOME%\books\testing.tmp"
+echo REALISTIC_BOOKS_HOME has write permission for the current user.
+echo.
+goto TheEnd
+
+:CantWrite
+echo.
+echo.
+echo.
+echo -----------------------------------
+
+if "%RBLANG%" == "en" echo  WARNING: CANNOT WRITE TO REALISTIC_BOOKS_HOME
+if "%RBLANG%" == "es" echo  ATENCIÃN: NO PUEDE ESCRIBIR EN EL DIRECTORIO RAÃZ DE GREENSTONE.
+if "%RBLANG%" == "fr" echo  AVERTISSEMENT: ECRIRE IMPOSSIBLE DANS LE DOSSIER D'ACCUEIL DE GREENSTONE
+if "%RBLANG%" == "ru" echo  ÐÐÐÐÐÐÐÐ: ÐÐÐÐÐÐÐÐÐÐ ÐÐ¡Ð£Ð©ÐÐ¡Ð¢ÐÐÐ¢Ð¬ ÐÐÐÐÐ¡Ð¬ Ð ÐÐÐÐÐšÐÐ®Ð® ÐÐÐÐÐ£ GREENSTONE
+
+echo -----------------------------------
+If "%RBLANG%" == "en" echo Greenstone needs write permission for the Greenstone home folder,
+if "%RBLANG%" == "en" echo which is %REALISTIC_BOOKS_HOME%,
+if "%RBLANG%" == "en" echo but right now it does not.
+if "%RBLANG%" == "en" echo Please grant "Full Control" for this folder (and all subfolders)  
+if "%RBLANG%" == "en" echo to the current user (%username%) and try again.
+
+if "%RBLANG%" == "es" echo Greenstone necesita permiso de escritura en el directorio:
+if "%RBLANG%" == "es" echo %REALISTIC_BOOKS_HOME%,
+if "%RBLANG%" == "es" echo pero no lo tiene.
+if "%RBLANG%" == "es" echo Por favor, concÃ©dale âControl completoâ al usuario actual (%username%), 
+if "%RBLANG%" == "es" echo y vuelva a intentarlo.
+
+if "%RBLANG%" == "fr" echo Greenstone nÃ©cessite une autorisation d'Ã©criture dans le dossier 
+if "%RBLANG%" == "fr" echo principal de Greenstone: %REALISTIC_BOOKS_HOME%,
+if "%RBLANG%" == "fr" echo mais pour l'instant cette autorisation n'existe pas.
+if "%RBLANG%" == "fr" echo Autorisez le Â«Â ContrÃŽle TotalÂ Â» sur ce dossier (et sur tous les sous-dossiers)
+if "%RBLANG%" == "fr" echo pour l'utilisateur courant (%username%) puis re-essayez.
+
+if "%RBLANG%" == "ru" echo Greenstone ÑÑÐµÐ±ÑÐµÑÑÑ ÑÐ°Ð·ÑÐµÑÐµÐœÐžÐµ ÐœÐ° Ð·Ð°Ð¿ÐžÑÑ Ð² ÐŽÐŸÐŒÐ°ÑÐœÑÑ Ð¿Ð°Ð¿ÐºÑ %REALISTIC_BOOKS_HOME%,
+if "%RBLANG%" == "ru" echo Ð² ÐŽÐ°ÐœÐœÑÐ¹ ÐŒÐŸÐŒÐµÐœÑ Ð·Ð°Ð¿ÐžÑÑ Ð² ÐŽÐŸÐŒÐ°ÑÐœÑÑ Ð¿Ð°Ð¿ÐºÑ Ð·Ð°Ð¿ÑÐµÑÐµÐœÐ°.
+if "%RBLANG%" == "ru" echo ÐÐŸÐ¶Ð°Ð»ÑÐ¹ÑÑÐ° ÑÑÑÐ°ÐœÐŸÐ²ÐžÑÐµ ÐÐŸÐ»ÐœÑÐ¹ ÐŽÐŸÑÑÑÐ¿ ÐŽÐ»Ñ Ð¿Ð°Ð¿ÐºÐž (Ðž Ð²ÑÐµÑ
+ Ð²Ð»ÐŸÐ¶ÐµÐœÐœÑÑ
+ Ð¿Ð°Ð¿ÐŸÐº)
+if "%RBLANG%" == "ru" echo ÐŽÐ»Ñ ÑÐµÐºÑÑÐµÐ³ÐŸ Ð¿ÐŸÐ»ÑÐ·ÐŸÐ²Ð°ÑÐµÐ»Ñ (%username%) Ðž Ð¿ÐŸÐ¿ÑÐŸÐ±ÑÐ¹ÑÐµ Ð·Ð°ÐœÐŸÐ²ÐŸ.
+
+echo.
+
+if "%RBLANG%" == "en" echo (Alternatively, re-install Greenstone to a location where you have Full
+if "%RBLANG%" == "en" echo Control already, such as a your home folder or 'My Documents'.)
+
+if "%RBLANG%" == "es" echo (Alternativamente, reinstale Greenstone en una localizaciÃ³n en la que tenga âControl completoâ, 
+if "%RBLANG%" == "es" echo como su directorio raÃ­z o âMis documentosâ).
+
+if "%RBLANG%" == "fr" echo (Alternativement, re-installez Greenstone Ã  l'emplacement oÃ¹ vous avez dÃ©jÃ  le Â«Â ContrÃŽle TotalÂ Â», 
+if "%RBLANG%" == "fr" echo comme votre dossier d'accueil ou  "Mes Documents".)
+
+if "%RBLANG%" == "ru" echo (ÐÐžÐ±ÐŸ, Ð¿ÐµÑÐµÑÑÑÐ°ÐœÐŸÐ²ÐžÑÐµ Greenstone Ð² ÑÑ Ð¿Ð°Ð¿ÐºÑ, Ðº ÐºÐŸÑÐŸÑÐŸÐ¹ ÐÑ ÑÐ¶Ðµ ÐžÐŒÐµÐµÑÐµ Ð¿ÐŸÐ»ÐœÑÐ¹ ÐŽÐŸÑÑÑÐ¿, 
+if "%RBLANG%" == "ru" echo ÐœÐ°Ð¿ÑÐžÐŒÐµÑ Ð² ÑÐ²ÐŸÑ ÐŽÐŸÐŒÐ°ÑÐœÑÑ Ð¿Ð°Ð¿ÐºÑ ÐžÐ»Ðž Ð² Ð¿Ð°Ð¿ÐºÑ "ÐÐŸÐž ÐŽÐŸÐºÑÐŒÐµÐœÑÑ".)
+
+echo.
+echo.
+echo.
+echo.
+goto TheEnd
+
+:EnvNotSet
+echo REALISTIC_BOOKS_HOME not set
+
+:TheEnd
Index: /other-projects/realistic-books/trunk/books/modelcol/RealisticBook.js
===================================================================
--- /other-projects/realistic-books/trunk/books/modelcol/RealisticBook.js	(revision 21014)
+++ /other-projects/realistic-books/trunk/books/modelcol/RealisticBook.js	(revision 21014)
@@ -0,0 +1,228 @@
+/*
+# A component of the Realistic Book software
+# from the New Zealand Digital Library Project at the 
+# University of Waikato, New Zealand.
+#
+# Copyright (C) 2007 New Zealand Digital Library Project
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+/*****************************************************************
+Realistic Book application
+	Detect Flash Player 8.0.24 or more plugin 
+	Specify the XHTML input and image cover URL to program
+	Embed the program to the div tag with id = bookdiv
+
+by Veronica Liesaputra --- 1 August 2007
+******************************************************************/
+
+var doc_url;//XHTML url
+var img_cover;//image cover url
+
+//check internet browser type
+var isIE  = (navigator.appVersion.indexOf("MSIE") != -1) ? true : false;
+var isWin = (navigator.appVersion.toLowerCase().indexOf("win") != -1) ? true : false;
+var isOpera = (navigator.userAgent.indexOf("Opera") != -1) ? true : false;
+var doc_width = -1;
+var doc_height = -1;
+var doc_style = "";
+
+//These functions will be called by the program
+function getDocURL() { return doc_url; }
+function getImgCover() { return img_cover; }
+function getStageW() { return doc_width; }
+function getStageH() { return doc_height; }
+function setDocumentTitle(doc_title) { document.title = doc_title; }
+function getStyleFile() { return doc_style; }
+
+//set the style of the book
+function setBookStyle(s) {doc_style = s;}
+//set the size of the book
+function setBookSize(dw,dh) {doc_width = dw; doc_height = dh;}
+
+//overload function
+function embedBookProgram(args)
+{
+	if (args.length == 0)
+		alert("Please type the URL for the XHTML input");
+	else
+	{
+		var xhtml_url = args[0];
+		var height = -1;
+		var width = -1;
+		var image_url = "";
+		if (args.length > 1) 
+		{
+			if (isNaN(args[1]) == true)
+				image_url = args[1];
+			else
+				height = args[1];
+		}
+		
+		if (args.length > 2)
+			width = args[2];
+	
+		embedProgram(xhtml_url,image_url,height,width);
+	} 
+}
+
+/*
+	If user has Flash player 8.0.24 or more,
+		Embed the program to your HTML file inside the div tag with id = bookdiv
+		Set the doc_url, img_cover, stage_width and stage_height value
+	Otherwise, 
+		Show user the links to get the flash player
+*/
+function embedProgram(xhtml_url, image_url, stage_height, stage_width)
+{
+    var flash_plug_html = "";
+    var flash_width = "100%";
+    if (stage_width != -1)
+      	flash_width = "" + stage_width;
+    var flash_height = "100%";
+    if (stage_height != -1)
+	flash_height = "" + stage_height;
+	
+    if (detectFlashPlayerVersion(8,0,24) == false)
+    {
+	flash_plug_html += 'This content requires the Adobe Flash Player 8.0.24 or greater.';
+	flash_plug_html += '<a href=http://www.macromedia.com/go/getflash/>Get Flash</a>';
+    }
+    else
+    {
+    	doc_url = escape(''+xhtml_url);//just in case user forgot the quote
+    	img_cover = escape(''+image_url);
+
+	//embed inside object tag for IE
+    	flash_plug_html += '<OBJECT align="middle" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" \n';
+	flash_plug_html += '  codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=8,0,0,0" \n';
+    	flash_plug_html += '  height="' + flash_height + '" id="Book" swLiveConnect="true" align="middle"\n';
+	flash_plug_html += '  width="' + flash_width + '">\n';
+	//embed inside embed tag for netscape, mozilla
+    	flash_plug_html += '<PARAM name="allowScriptAccess" value="always" />\n';
+    	flash_plug_html += '<PARAM name="movie" value="RealisticBook.swf';
+	flash_plug_html += '?src_image=' + img_cover;
+	flash_plug_html += '&amp;stageW=' + doc_width;
+	flash_plug_html += '&amp;stageH=' + doc_height;
+	flash_plug_html += '&amp;doc_url=' + doc_url;
+	flash_plug_html += '" />\n';
+	flash_plug_html += '<PARAM name="quality" value="high" />\n';
+	flash_plug_html += '<PARAM name="bgcolor" value="#FFFFFF" />\n';
+	flash_plug_html += '<EMBED align="middle" \n';
+	flash_plug_html += '      allowScriptAccess="always" swLiveConnect="true" \n';
+	flash_plug_html += '      bgcolor="#FFFFFF" height="' + flash_height + '" name="Book" \n';
+	flash_plug_html += '      pluginspage="http://www.macromedia.com/go/getflashplayer" \n';
+	flash_plug_html += '      quality="high" \n';
+	flash_plug_html += '      src=\'RealisticBook.swf';
+	flash_plug_html += '?src_image=' + img_cover;
+	flash_plug_html += '&amp;stageW=' + doc_width;
+	flash_plug_html += '&amp;stageH=' + doc_height;
+	flash_plug_html += '&amp;doc_url=' + doc_url;
+	flash_plug_html += '\'\n'; 
+	flash_plug_html += '      type="application/x-shockwave-flash" width="' + flash_width + '" />\n';
+	flash_plug_html += '</OBJECT>\n';
+    }
+
+    var flash_div = document.getElementById('bookdiv');
+    flash_div.innerHTML = flash_plug_html;
+}
+
+function detectFlashPlayerVersion(reqMajor,reqMinor,reqRevision)
+{
+	if (navigator.plugins != null && navigator.plugins.length > 0) 
+	{
+		if (navigator.plugins["Shockwave Flash 2.0"] || navigator.plugins["Shockwave Flash"]) 
+		{
+			var swVer2 = navigator.plugins["Shockwave Flash 2.0"] ? " 2.0" : "";
+			var flashDescription = navigator.plugins["Shockwave Flash" + swVer2].description;
+			var descArray = flashDescription.split(" ");
+			var tempArrayMajor = descArray[2].split(".");			
+			var versionMajor = tempArrayMajor[0];
+			var versionMinor = tempArrayMajor[1];
+			var versionRevision = descArray[3];
+			if (versionRevision == "") 
+			{
+				versionRevision = descArray[4];
+			}
+			if (versionRevision[0] == "d") 
+			{
+				versionRevision = versionRevision.substring(1);
+			}
+			else if (versionRevision[0] == "r") 
+			{
+				versionRevision = versionRevision.substring(1);
+				if (versionRevision.indexOf("d") > 0) 
+				{
+					versionRevision = versionRevision.substring(0, versionRevision.indexOf("d"));
+				}
+			}
+
+			var isGood = isGoodPlugin(reqMajor,reqMinor,reqRevision,versionMajor,versionMinor,versionRevision);
+			return isGood;
+		}
+	}
+	else if ( isIE && isWin && !isOpera ) 
+	{
+		var version;
+		var axo;
+		var e;
+
+		try 
+		{
+			// version will be set for 7.X or greater players
+			axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7");
+			version = axo.GetVariable("$version");
+
+			// Given "WIN 2,0,0,11"
+			var tempArray = version.split(" "); 	// ["WIN", "2,0,0,11"]
+			var tempString = tempArray[1];		// "2,0,0,11"
+			var versionArray = tempString.split(",");	// ['2', '0', '0', '11']
+
+			var isGood = isGoodPlugin(reqMajor,reqMinor,reqRevision,versionArray[0],versionArray[1],versionArray[2]);
+			return isGood;
+		} 
+		catch (e) {}
+	}
+
+	return false;
+}
+
+/*
+	Check whether the current flash player plugin passed the required version
+*/
+function isGoodPlugin(reqMajor,reqMinor,reqRevision,currMajor,currMinor,currRevision)
+{
+	if (currMajor > parseFloat(reqMajor)) 
+	{
+		return true;
+	} 
+	else if (currMajor == parseFloat(reqMajor)) 
+	{
+		if (currMinor > parseFloat(reqMinor))
+		{
+			return true;
+		}
+		else if (currMinor == parseFloat(reqMinor)) 
+		{
+			if (currRevision >= parseFloat(reqRevision))
+			{		
+				return true;
+			}
+		}
+	}
+	
+	return false;
+}
Index: /other-projects/realistic-books/trunk/books/modelcol/index.html
===================================================================
--- /other-projects/realistic-books/trunk/books/modelcol/index.html	(revision 21014)
+++ /other-projects/realistic-books/trunk/books/modelcol/index.html	(revision 21014)
@@ -0,0 +1,23 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
+<title>Realistic Book</title>
+<script src="RealisticBook.js" language="javascript"></script>
+</head>
+<body>
+<!-- You have to have this div tag with id = bookdiv in your HTML file!! This is where the program will be embedded -->
+<div id="bookdiv"></div>
+<script type="text/javascript">
+ 	embedBookProgram(new Array('pages.htm'));
+
+	/*
+	   	If you have front_image_cover_URL then it can be set as second
+		parameter in the above line
+		For example: embedBookProgram(new Array("CHIBook.htm","cover.png"));
+	*/
+
+
+</script>
+</body>
+</html>
+
Index: /other-projects/realistic-books/trunk/cascade-make/lib/cascade-lib.bash
===================================================================
--- /other-projects/realistic-books/trunk/cascade-make/lib/cascade-lib.bash	(revision 21014)
+++ /other-projects/realistic-books/trunk/cascade-make/lib/cascade-lib.bash	(revision 21014)
@@ -0,0 +1,181 @@
+
+run_untar()
+{
+  local package=$1
+  local version=$2
+  local ext=$3
+
+  tar_args="";
+
+  if [ $ext = ".tar.gz" ] ; then
+    tar_args="xvzf"
+  elif [ $ext = ".tgz" ] ; then
+    tar_args="xvzf"
+  elif [ $ext = ".tar.bz2" ] ; then
+    tar_args="xvjf"
+  elif [ $ext = ".tar.bz" ] ; then
+    tar_args="xvjf"
+  elif [ $ext = ".tar" ] ; then
+    tar_args="xvf"
+  else 
+    echo "Warning: Unrecognized extension: $ext"
+    echo "Assuming tarred, gzipped file"
+    tar_args="xvjf"
+  fi
+
+
+  echo tar $tar_args $package$version$ext 
+  tar $tar_args $package$version$ext
+
+  if [ $? != 0 ] ; then
+    echo "      Error encountered running *untar* stage of $progname"
+    exit 1
+  fi
+}
+
+opt_run_tarclean()
+{
+  local type=$1
+  local package=$2
+  local version=$3
+
+  if [ $type = "1" ] ; then
+
+    local dir=$package$version
+    if [ -d $dir ] ; then
+      echo /bin/rm -rf $dir
+      /bin/rm -rf $dir
+
+      if [ $? != 0 ] ; then
+        echo "      Error encountered running *tarclean* stage of $progname"
+        exit 1
+      fi
+    else 
+      echo "Unable to find directory $dir"
+    fi
+  fi
+}
+
+opt_run_untar()
+{
+  local force_untar=$1
+  local auto_untar=$2
+  local package=$3
+  local version=$4
+
+  local ext="";
+
+  if [ ! -z "$5" ] ; then
+    ext=$5
+  else 
+    ext=".tar.gz"
+  fi
+
+  if [ $force_untar = "1" ] ; then
+    run_untar $package $version $ext
+  elif [ $auto_config = "1" ] ; then
+    if [ -d $package$version ] ; then
+      echo "Found untarred version of $package$version$ext => no need to untar"
+    else
+      run_untar $package $version $ext
+    fi
+  fi
+}
+
+
+opt_run_configure()
+{
+  local force_config=$1
+  local auto_config=$2
+  local package=$3
+  local version=$4
+  local prefix=$5
+  local opt_args="";
+
+  if [ ! -z "$6" ] ; then
+    opt_args=$6
+  fi
+
+  if [ $force_config = "1" ] ; then
+    echo "[pushd $package$version]"
+    ( cd $package$version ; \
+      echo ./configure --prefix="$prefix" $opt_args ; \
+      ./configure --prefix="$prefix" $opt_args )
+    if [ $? != 0 ] ; then
+        echo "      Error encountered running *configure* stage of $progname"
+        exit 1
+    fi
+    echo "[popd]"
+  else
+    if [ $auto_config = "1" ] ; then
+      echo "Found top-level for ${progname%.*} => no need to run ./configure"
+    fi
+  fi
+}
+
+
+opt_run_make()
+{
+  local type=$1
+  local package=$2
+  local version=$3
+  local opt_target=""
+
+  if [ ! -z "$4" ] ; then
+    opt_target=$4
+  fi
+
+  if [ $type = "1" ] ; then
+    ( cd $package$version ; \
+      make $opt_target)
+
+    if [ $? != 0 ] ; then
+        echo "      Error encountered running *make $target* stage of $progname"
+        exit 1
+    fi
+  fi
+}
+
+
+
+force_untar=0
+auto_untar=0
+
+force_config=0
+auto_config=0
+
+compile=0
+install=0
+clean=0
+distclean=0
+tarclean=0
+
+if [ $# -gt 0 ] ; then
+  for cmd in $* ; do
+    echo $cmd
+    if   [ $cmd = "untar" ] ;     then force_untar=1
+    elif [ $cmd = "configure" ] ; then force_config=1
+    elif [ $cmd = "compile" ]   ; then compile=1
+    elif [ $cmd = "install" ]   ; then install=1  
+    elif [ $cmd = "clean" ]     ; then clean=1  
+    elif [ $cmd = "distclean" ] ; then distclean=1
+    elif [ $cmd = "tarclean" ] ;  then tarclean=1
+    fi
+  done
+else
+  # defaults
+  auto_untar=1
+  auto_config=1
+  compile=1
+  install=1
+  clean=0
+  distclean=0
+  tarclean=0
+fi
+
+if [ $auto_config = "1" ] ; then
+  if [ ! -e $package$version/Makefile ] ; then
+    force_config=1
+  fi
+fi
+
Index: /other-projects/realistic-books/trunk/cgi-bin/CGI.pm
===================================================================
--- /other-projects/realistic-books/trunk/cgi-bin/CGI.pm	(revision 21014)
+++ /other-projects/realistic-books/trunk/cgi-bin/CGI.pm	(revision 21014)
@@ -0,0 +1,6697 @@
+package CGI;
+require 5.004;
+use Carp 'croak';
+
+# See the bottom of this file for the POD documentation.  Search for the
+# string '=head'.
+
+# You can run this file through either pod2man or pod2html to produce pretty
+# documentation in manual or html file format (these utilities are part of the
+# Perl 5 distribution).
+
+# Copyright 1995-1998 Lincoln D. Stein.  All rights reserved.
+# It may be used and modified freely, but I do request that this copyright
+# notice remain attached to the file.  You may modify this module as you 
+# wish, but if you redistribute a modified version, please attach a note
+# listing the modifications you have made.
+
+# The most recent version and complete docs are available at:
+#   http://stein.cshl.org/WWW/software/CGI/
+
+$CGI::revision = '$Id: CGI.pm 7767 2004-07-19 05:28:53Z davidb $';
+$CGI::VERSION='2.81';
+
+# HARD-CODED LOCATION FOR FILE UPLOAD TEMPORARY FILES.
+# UNCOMMENT THIS ONLY IF YOU KNOW WHAT YOU'RE DOING.
+# $CGITempFile::TMPDIRECTORY = '/usr/tmp';
+use CGI::Util qw(rearrange make_attributes unescape escape expires);
+
+#use constant XHTML_DTD => ['-//W3C//DTD XHTML Basic 1.0//EN',
+#                           'http://www.w3.org/TR/xhtml-basic/xhtml-basic10.dtd'];
+
+use constant XHTML_DTD => ['-//W3C//DTD XHTML 1.0 Transitional//EN',
+                           'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'];
+
+# >>>>> Here are some globals that you might want to adjust <<<<<<
+sub initialize_globals {
+    # Set this to 1 to enable copious autoloader debugging messages
+    $AUTOLOAD_DEBUG = 0;
+
+    # Set this to 1 to generate XTML-compatible output
+    $XHTML = 1;
+
+    # Change this to the preferred DTD to print in start_html()
+    # or use default_dtd('text of DTD to use');
+    $DEFAULT_DTD = [ '-//W3C//DTD HTML 4.01 Transitional//EN',
+		     'http://www.w3.org/TR/html4/loose.dtd' ] ;
+
+    # Set this to 1 to enable NOSTICKY scripts
+    # or: 
+    #    1) use CGI qw(-nosticky)
+    #    2) $CGI::nosticky(1)
+    $NOSTICKY = 0;
+
+    # Set this to 1 to enable NPH scripts
+    # or: 
+    #    1) use CGI qw(-nph)
+    #    2) CGI::nph(1)
+    #    3) print header(-nph=>1)
+    $NPH = 0;
+
+    # Set this to 1 to enable debugging from @ARGV
+    # Set to 2 to enable debugging from STDIN
+    $DEBUG = 1;
+
+    # Set this to 1 to make the temporary files created
+    # during file uploads safe from prying eyes
+    # or do...
+    #    1) use CGI qw(:private_tempfiles)
+    #    2) CGI::private_tempfiles(1);
+    $PRIVATE_TEMPFILES = 0;
+
+    # Set this to a positive value to limit the size of a POSTing
+    # to a certain number of bytes:
+    $POST_MAX = -1;
+
+    # Change this to 1 to disable uploads entirely:
+    $DISABLE_UPLOADS = 0;
+
+    # Automatically determined -- don't change
+    $EBCDIC = 0;
+
+    # Change this to 1 to suppress redundant HTTP headers
+    $HEADERS_ONCE = 0;
+
+    # separate the name=value pairs by semicolons rather than ampersands
+    $USE_PARAM_SEMICOLONS = 1;
+
+    # Do not include undefined params parsed from query string
+    # use CGI qw(-no_undef_params);
+    $NO_UNDEF_PARAMS = 0;
+
+    # Other globals that you shouldn't worry about.
+    undef $Q;
+    $BEEN_THERE = 0;
+    undef @QUERY_PARAM;
+    undef %EXPORT;
+    undef $QUERY_CHARSET;
+    undef %QUERY_FIELDNAMES;
+
+    # prevent complaints by mod_perl
+    1;
+}
+
+# ------------------ START OF THE LIBRARY ------------
+
+# make mod_perlhappy
+initialize_globals();
+
+# FIGURE OUT THE OS WE'RE RUNNING UNDER
+# Some systems support the $^O variable.  If not
+# available then require() the Config library
+unless ($OS) {
+    unless ($OS = $^O) {
+	require Config;
+	$OS = $Config::Config{'osname'};
+    }
+}
+if ($OS =~ /^MSWin/i) {
+  $OS = 'WINDOWS';
+} elsif ($OS =~ /^VMS/i) {
+  $OS = 'VMS';
+} elsif ($OS =~ /^dos/i) {
+  $OS = 'DOS';
+} elsif ($OS =~ /^MacOS/i) {
+    $OS = 'MACINTOSH';
+} elsif ($OS =~ /^os2/i) {
+    $OS = 'OS2';
+} elsif ($OS =~ /^epoc/i) {
+    $OS = 'EPOC';
+} else {
+    $OS = 'UNIX';
+}
+
+# Some OS logic.  Binary mode enabled on DOS, NT and VMS
+$needs_binmode = $OS=~/^(WINDOWS|DOS|OS2|MSWin)/;
+
+# This is the default class for the CGI object to use when all else fails.
+$DefaultClass = 'CGI' unless defined $CGI::DefaultClass;
+
+# This is where to look for autoloaded routines.
+$AutoloadClass = $DefaultClass unless defined $CGI::AutoloadClass;
+
+# The path separator is a slash, backslash or semicolon, depending
+# on the paltform.
+$SL = {
+       UNIX=>'/', OS2=>'\\', EPOC=>'/',
+       WINDOWS=>'\\', DOS=>'\\', MACINTOSH=>':', VMS=>'/'
+    }->{$OS};
+
+# This no longer seems to be necessary
+# Turn on NPH scripts by default when running under IIS server!
+# $NPH++ if defined($ENV{'SERVER_SOFTWARE'}) && $ENV{'SERVER_SOFTWARE'}=~/IIS/;
+$IIS++ if defined($ENV{'SERVER_SOFTWARE'}) && $ENV{'SERVER_SOFTWARE'}=~/IIS/;
+
+# Turn on special checking for Doug MacEachern's modperl
+if (exists $ENV{'GATEWAY_INTERFACE'} 
+    && 
+    ($MOD_PERL = $ENV{'GATEWAY_INTERFACE'} =~ /^CGI-Perl\//))
+{
+    $| = 1;
+    require Apache;
+}
+# Turn on special checking for ActiveState's PerlEx
+$PERLEX++ if defined($ENV{'GATEWAY_INTERFACE'}) && $ENV{'GATEWAY_INTERFACE'} =~ /^CGI-PerlEx/;
+
+# Define the CRLF sequence.  I can't use a simple "\r\n" because the meaning
+# of "\n" is different on different OS's (sometimes it generates CRLF, sometimes LF
+# and sometimes CR).  The most popular VMS web server
+# doesn't accept CRLF -- instead it wants a LR.  EBCDIC machines don't
+# use ASCII, so \015\012 means something different.  I find this all 
+# really annoying.
+$EBCDIC = "\t" ne "\011";
+if ($OS eq 'VMS') {
+  $CRLF = "\n";
+} elsif ($EBCDIC) {
+  $CRLF= "\r\n";
+} else {
+  $CRLF = "\015\012";
+}
+
+if ($needs_binmode) {
+    $CGI::DefaultClass->binmode(main::STDOUT);
+    $CGI::DefaultClass->binmode(main::STDIN);
+    $CGI::DefaultClass->binmode(main::STDERR);
+}
+
+%EXPORT_TAGS = (
+		':html2'=>['h1'..'h6',qw/p br hr ol ul li dl dt dd menu code var strong em
+			   tt u i b blockquote pre img a address cite samp dfn html head
+			   base body Link nextid title meta kbd start_html end_html
+			   input Select option comment charset escapeHTML/],
+		':html3'=>[qw/div table caption th td TR Tr sup Sub strike applet Param 
+			   embed basefont style span layer ilayer font frameset frame script small big/],
+                ':html4'=>[qw/abbr acronym bdo col colgroup del fieldset iframe
+                            ins label legend noframes noscript object optgroup Q 
+                            thead tbody tfoot/], 
+		':netscape'=>[qw/blink fontsize center/],
+		':form'=>[qw/textfield textarea filefield password_field hidden checkbox checkbox_group 
+			  submit reset defaults radio_group popup_menu button autoEscape
+			  scrolling_list image_button start_form end_form startform endform
+			  start_multipart_form end_multipart_form isindex tmpFileName uploadInfo URL_ENCODED MULTIPART/],
+		':cgi'=>[qw/param upload path_info path_translated url self_url script_name cookie Dump
+			 raw_cookie request_method query_string Accept user_agent remote_host content_type
+			 remote_addr referer server_name server_software server_port server_protocol
+			 virtual_host remote_ident auth_type http
+			 save_parameters restore_parameters param_fetch
+			 remote_user user_name header redirect import_names put 
+			 Delete Delete_all url_param cgi_error/],
+		':ssl' => [qw/https/],
+		':imagemap' => [qw/Area Map/],
+		':cgi-lib' => [qw/ReadParse PrintHeader HtmlTop HtmlBot SplitParam Vars/],
+		':html' => [qw/:html2 :html3 :html4 :netscape/],
+		':standard' => [qw/:html2 :html3 :html4 :form :cgi/],
+		':push' => [qw/multipart_init multipart_start multipart_end multipart_final/],
+		':all' => [qw/:html2 :html3 :netscape :form :cgi :internal :html4/]
+		);
+
+# to import symbols into caller
+sub import {
+    my $self = shift;
+
+# This causes modules to clash.  
+    undef %EXPORT_OK;
+    undef %EXPORT;
+
+    $self->_setup_symbols(@_);
+    my ($callpack, $callfile, $callline) = caller;
+
+    # To allow overriding, search through the packages
+    # Till we find one in which the correct subroutine is defined.
+    my @packages = ($self,@{"$self\:\:ISA"});
+    foreach $sym (keys %EXPORT) {
+	my $pck;
+	my $def = ${"$self\:\:AutoloadClass"} || $DefaultClass;
+	foreach $pck (@packages) {
+	    if (defined(&{"$pck\:\:$sym"})) {
+		$def = $pck;
+		last;
+	    }
+	}
+	*{"${callpack}::$sym"} = \&{"$def\:\:$sym"};
+    }
+}
+
+sub compile {
+    my $pack = shift;
+    $pack->_setup_symbols('-compile',@_);
+}
+
+sub expand_tags {
+    my($tag) = @_;
+    return ("start_$1","end_$1") if $tag=~/^(?:\*|start_|end_)(.+)/;
+    my(@r);
+    return ($tag) unless $EXPORT_TAGS{$tag};
+    foreach (@{$EXPORT_TAGS{$tag}}) {
+	push(@r,&expand_tags($_));
+    }
+    return @r;
+}
+
+#### Method: new
+# The new routine.  This will check the current environment
+# for an existing query string, and initialize itself, if so.
+####
+sub new {
+    my($class,$initializer) = @_;
+    my $self = {};
+
+    bless $self,ref $class || $class || $DefaultClass;
+    if ($MOD_PERL && defined Apache->request) {
+      Apache->request->register_cleanup(\&CGI::_reset_globals);
+      undef $NPH;
+    }
+    $self->_reset_globals if $PERLEX;
+    $self->init($initializer);
+    return $self;
+}
+
+# We provide a DESTROY method so that the autoloader
+# doesn't bother trying to find it.
+sub DESTROY { }
+
+#### Method: param
+# Returns the value(s)of a named parameter.
+# If invoked in a list context, returns the
+# entire list.  Otherwise returns the first
+# member of the list.
+# If name is not provided, return a list of all
+# the known parameters names available.
+# If more than one argument is provided, the
+# second and subsequent arguments are used to
+# set the value of the parameter.
+####
+sub param {
+    my($self,@p) = self_or_default(@_);
+    return $self->all_parameters unless @p;
+    my($name,$value,@other);
+
+    # For compatibility between old calling style and use_named_parameters() style, 
+    # we have to special case for a single parameter present.
+    if (@p > 1) {
+	($name,$value,@other) = rearrange([NAME,[DEFAULT,VALUE,VALUES]],@p);
+	my(@values);
+
+	if (substr($p[0],0,1) eq '-') {
+	    @values = defined($value) ? (ref($value) && ref($value) eq 'ARRAY' ? @{$value} : $value) : ();
+	} else {
+	    foreach ($value,@other) {
+		push(@values,$_) if defined($_);
+	    }
+	}
+	# If values is provided, then we set it.
+	if (@values) {
+	    $self->add_parameter($name);
+	    $self->{$name}=[@values];
+	}
+    } else {
+	$name = $p[0];
+    }
+
+    return unless defined($name) && $self->{$name};
+    return wantarray ? @{$self->{$name}} : $self->{$name}->[0];
+}
+
+sub self_or_default {
+    return @_ if defined($_[0]) && (!ref($_[0])) &&($_[0] eq 'CGI');
+    unless (defined($_[0]) && 
+	    (ref($_[0]) eq 'CGI' || UNIVERSAL::isa($_[0],'CGI')) # slightly optimized for common case
+	    ) {
+	$Q = $CGI::DefaultClass->new unless defined($Q);
+	unshift(@_,$Q);
+    }
+    return wantarray ? @_ : $Q;
+}
+
+sub self_or_CGI {
+    local $^W=0;                # prevent a warning
+    if (defined($_[0]) &&
+	(substr(ref($_[0]),0,3) eq 'CGI' 
+	 || UNIVERSAL::isa($_[0],'CGI'))) {
+	return @_;
+    } else {
+	return ($DefaultClass,@_);
+    }
+}
+
+########################################
+# THESE METHODS ARE MORE OR LESS PRIVATE
+# GO TO THE __DATA__ SECTION TO SEE MORE
+# PUBLIC METHODS
+########################################
+
+# Initialize the query object from the environment.
+# If a parameter list is found, this object will be set
+# to an associative array in which parameter names are keys
+# and the values are stored as lists
+# If a keyword list is found, this method creates a bogus
+# parameter list with the single parameter 'keywords'.
+
+sub init {
+    my($self,$initializer) = @_;
+    my($query_string,$meth,$content_length,$fh,@lines) = ('','','','');
+    local($/) = "\n";
+
+    # if we get called more than once, we want to initialize
+    # ourselves from the original query (which may be gone
+    # if it was read from STDIN originally.)
+    if (defined(@QUERY_PARAM) && !defined($initializer)) {
+	foreach (@QUERY_PARAM) {
+	    $self->param('-name'=>$_,'-value'=>$QUERY_PARAM{$_});
+	}
+	$self->charset($QUERY_CHARSET);
+	$self->{'.fieldnames'} = {%QUERY_FIELDNAMES};
+	return;
+    }
+
+    $meth=$ENV{'REQUEST_METHOD'} if defined($ENV{'REQUEST_METHOD'});
+    $content_length = defined($ENV{'CONTENT_LENGTH'}) ? $ENV{'CONTENT_LENGTH'} : 0;
+
+    $fh = to_filehandle($initializer) if $initializer;
+
+    # set charset to the safe ISO-8859-1
+    $self->charset('ISO-8859-1');
+
+  METHOD: {
+
+      # avoid unreasonably large postings
+      if (($POST_MAX > 0) && ($content_length > $POST_MAX)) {
+	  $self->cgi_error("413 Request entity too large");
+	  last METHOD;
+      }
+
+      # Process multipart postings, but only if the initializer is
+      # not defined.
+      if ($meth eq 'POST'
+	  && defined($ENV{'CONTENT_TYPE'})
+	  && $ENV{'CONTENT_TYPE'}=~m|^multipart/form-data|
+	  && !defined($initializer)
+	  ) {
+	  my($boundary) = $ENV{'CONTENT_TYPE'} =~ /boundary=\"?([^\";,]+)\"?/;
+	  $self->read_multipart($boundary,$content_length);
+	  last METHOD;
+      } 
+
+      # If initializer is defined, then read parameters
+      # from it.
+      if (defined($initializer)) {
+	  if (UNIVERSAL::isa($initializer,'CGI')) {
+	      $query_string = $initializer->query_string;
+	      last METHOD;
+	  }
+	  if (ref($initializer) && ref($initializer) eq 'HASH') {
+	      foreach (keys %$initializer) {
+		  $self->param('-name'=>$_,'-value'=>$initializer->{$_});
+	      }
+	      last METHOD;
+	  }
+	  
+	  if (defined($fh) && ($fh ne '')) {
+	      while (<$fh>) {
+		  chomp;
+		  last if /^=/;
+		  push(@lines,$_);
+	      }
+	      # massage back into standard format
+	      if ("@lines" =~ /=/) {
+		  $query_string=join("&",@lines);
+	      } else {
+		  $query_string=join("+",@lines);
+	      }
+	      last METHOD;
+	  }
+
+	  # last chance -- treat it as a string
+	  $initializer = $$initializer if ref($initializer) eq 'SCALAR';
+	  $query_string = $initializer;
+
+	  last METHOD;
+      }
+
+      # If method is GET or HEAD, fetch the query from
+      # the environment.
+      if ($meth=~/^(GET|HEAD)$/) {
+	  if ($MOD_PERL) {
+	      $query_string = Apache->request->args;
+	  } else {
+	      $query_string = $ENV{'QUERY_STRING'} if defined $ENV{'QUERY_STRING'};
+	      $query_string ||= $ENV{'REDIRECT_QUERY_STRING'} if defined $ENV{'REDIRECT_QUERY_STRING'};
+	  }
+	  last METHOD;
+      }
+
+      if ($meth eq 'POST') {
+	  $self->read_from_client(\*STDIN,\$query_string,$content_length,0)
+	      if $content_length > 0;
+	  # Some people want to have their cake and eat it too!
+	  # Uncomment this line to have the contents of the query string
+	  # APPENDED to the POST data.
+	  # $query_string .= (length($query_string) ? '&' : '') . $ENV{'QUERY_STRING'} if defined $ENV{'QUERY_STRING'};
+	  last METHOD;
+      }
+
+      # If $meth is not of GET, POST or HEAD, assume we're being debugged offline.
+      # Check the command line and then the standard input for data.
+      # We use the shellwords package in order to behave the way that
+      # UN*X programmers expect.
+      $query_string = read_from_cmdline() if $DEBUG;
+  }
+
+    # We now have the query string in hand.  We do slightly
+    # different things for keyword lists and parameter lists.
+    if (defined $query_string && length $query_string) {
+	if ($query_string =~ /[&=;]/) {
+	    $self->parse_params($query_string);
+	} else {
+	    $self->add_parameter('keywords');
+	    $self->{'keywords'} = [$self->parse_keywordlist($query_string)];
+	}
+    }
+
+    # Special case.  Erase everything if there is a field named
+    # .defaults.
+    if ($self->param('.defaults')) {
+	undef %{$self};
+    }
+
+    # Associative array containing our defined fieldnames
+    $self->{'.fieldnames'} = {};
+    foreach ($self->param('.cgifields')) {
+	$self->{'.fieldnames'}->{$_}++;
+    }
+    
+    # Clear out our default submission button flag if present
+    $self->delete('.submit');
+    $self->delete('.cgifields');
+
+    $self->save_request unless $initializer;
+}
+
+# FUNCTIONS TO OVERRIDE:
+# Turn a string into a filehandle
+sub to_filehandle {
+    my $thingy = shift;
+    return undef unless $thingy;
+    return $thingy if UNIVERSAL::isa($thingy,'GLOB');
+    return $thingy if UNIVERSAL::isa($thingy,'FileHandle');
+    if (!ref($thingy)) {
+	my $caller = 1;
+	while (my $package = caller($caller++)) {
+	    my($tmp) = $thingy=~/[\':]/ ? $thingy : "$package\:\:$thingy"; 
+	    return $tmp if defined(fileno($tmp));
+	}
+    }
+    return undef;
+}
+
+# send output to the browser
+sub put {
+    my($self,@p) = self_or_default(@_);
+    $self->print(@p);
+}
+
+# print to standard output (for overriding in mod_perl)
+sub print {
+    shift;
+    CORE::print(@_);
+}
+
+# get/set last cgi_error
+sub cgi_error {
+    my ($self,$err) = self_or_default(@_);
+    $self->{'.cgi_error'} = $err if defined $err;
+    return $self->{'.cgi_error'};
+}
+
+sub save_request {
+    my($self) = @_;
+    # We're going to play with the package globals now so that if we get called
+    # again, we initialize ourselves in exactly the same way.  This allows
+    # us to have several of these objects.
+    @QUERY_PARAM = $self->param; # save list of parameters
+    foreach (@QUERY_PARAM) {
+      next unless defined $_;
+      $QUERY_PARAM{$_}=$self->{$_};
+    }
+    $QUERY_CHARSET = $self->charset;
+    %QUERY_FIELDNAMES = %{$self->{'.fieldnames'}};
+}
+
+sub parse_params {
+    my($self,$tosplit) = @_;
+    my(@pairs) = split(/[&;]/,$tosplit);
+    my($param,$value);
+    foreach (@pairs) {
+	($param,$value) = split('=',$_,2);
+	next unless defined $param;
+	next if $NO_UNDEF_PARAMS and not defined $value;
+	$value = '' unless defined $value;
+	$param = unescape($param);
+	$value = unescape($value);
+	$self->add_parameter($param);
+	push (@{$self->{$param}},$value);
+    }
+}
+
+sub add_parameter {
+    my($self,$param)=@_;
+    return unless defined $param;
+    push (@{$self->{'.parameters'}},$param) 
+	unless defined($self->{$param});
+}
+
+sub all_parameters {
+    my $self = shift;
+    return () unless defined($self) && $self->{'.parameters'};
+    return () unless @{$self->{'.parameters'}};
+    return @{$self->{'.parameters'}};
+}
+
+# put a filehandle into binary mode (DOS)
+sub binmode {
+    CORE::binmode($_[1]);
+}
+
+sub _make_tag_func {
+    my ($self,$tagname) = @_;
+    my $func = qq(
+	sub $tagname {
+            shift if \$_[0] && 
+                    (ref(\$_[0]) &&
+                     (substr(ref(\$_[0]),0,3) eq 'CGI' ||
+                    UNIVERSAL::isa(\$_[0],'CGI')));
+	    my(\$attr) = '';
+	    if (ref(\$_[0]) && ref(\$_[0]) eq 'HASH') {
+		my(\@attr) = make_attributes(shift()||undef,1);
+		\$attr = " \@attr" if \@attr;
+	    }
+	);
+    if ($tagname=~/start_(\w+)/i) {
+	$func .= qq! return "<\L$1\E\$attr>";} !;
+    } elsif ($tagname=~/end_(\w+)/i) {
+	$func .= qq! return "<\L/$1\E>"; } !;
+    } else {
+	$func .= qq#
+	    return \$XHTML ? "\L<$tagname\E\$attr />" : "\L<$tagname\E\$attr>" unless \@_;
+	    my(\$tag,\$untag) = ("\L<$tagname\E\$attr>","\L</$tagname>\E");
+	    my \@result = map { "\$tag\$_\$untag" } 
+                              (ref(\$_[0]) eq 'ARRAY') ? \@{\$_[0]} : "\@_";
+	    return "\@result";
+            }#;
+    }
+return $func;
+}
+
+sub AUTOLOAD {
+    print STDERR "CGI::AUTOLOAD for $AUTOLOAD\n" if $CGI::AUTOLOAD_DEBUG;
+    my $func = &_compile;
+    goto &$func;
+}
+
+sub _compile {
+    my($func) = $AUTOLOAD;
+    my($pack,$func_name);
+    {
+	local($1,$2); # this fixes an obscure variable suicide problem.
+	$func=~/(.+)::([^:]+)$/;
+	($pack,$func_name) = ($1,$2);
+	$pack=~s/::SUPER$//;	# fix another obscure problem
+	$pack = ${"$pack\:\:AutoloadClass"} || $CGI::DefaultClass
+	    unless defined(${"$pack\:\:AUTOLOADED_ROUTINES"});
+
+        my($sub) = \%{"$pack\:\:SUBS"};
+        unless (%$sub) {
+	   my($auto) = \${"$pack\:\:AUTOLOADED_ROUTINES"};
+	   eval "package $pack; $$auto";
+	   croak("$AUTOLOAD: $@") if $@;
+           $$auto = '';  # Free the unneeded storage (but don't undef it!!!)
+       }
+       my($code) = $sub->{$func_name};
+
+       $code = "sub $AUTOLOAD { }" if (!$code and $func_name eq 'DESTROY');
+       if (!$code) {
+	   (my $base = $func_name) =~ s/^(start_|end_)//i;
+	   if ($EXPORT{':any'} || 
+	       $EXPORT{'-any'} ||
+	       $EXPORT{$base} || 
+	       (%EXPORT_OK || grep(++$EXPORT_OK{$_},&expand_tags(':html')))
+	           && $EXPORT_OK{$base}) {
+	       $code = $CGI::DefaultClass->_make_tag_func($func_name);
+	   }
+       }
+       croak("Undefined subroutine $AUTOLOAD\n") unless $code;
+       eval "package $pack; $code";
+       if ($@) {
+	   $@ =~ s/ at .*\n//;
+	   croak("$AUTOLOAD: $@");
+       }
+    }       
+    CORE::delete($sub->{$func_name});  #free storage
+    return "$pack\:\:$func_name";
+}
+
+sub _selected {
+  my $self = shift;
+  my $value = shift;
+  return '' unless $value;
+  return $XHTML ? qq( selected="selected") : qq( selected);
+}
+
+sub _checked {
+  my $self = shift;
+  my $value = shift;
+  return '' unless $value;
+  return $XHTML ? qq( checked="checked") : qq( checked);
+}
+
+sub _reset_globals { initialize_globals(); }
+
+sub _setup_symbols {
+    my $self = shift;
+    my $compile = 0;
+
+    # to avoid reexporting unwanted variables
+    undef %EXPORT;
+
+    foreach (@_) {
+	$HEADERS_ONCE++,         next if /^[:-]unique_headers$/;
+	$NPH++,                  next if /^[:-]nph$/;
+	$NOSTICKY++,             next if /^[:-]nosticky$/;
+	$DEBUG=0,                next if /^[:-]no_?[Dd]ebug$/;
+	$DEBUG=2,                next if /^[:-][Dd]ebug$/;
+	$USE_PARAM_SEMICOLONS++, next if /^[:-]newstyle_urls$/;
+	$XHTML++,                next if /^[:-]xhtml$/;
+	$XHTML=0,                next if /^[:-]no_?xhtml$/;
+	$USE_PARAM_SEMICOLONS=0, next if /^[:-]oldstyle_urls$/;
+	$PRIVATE_TEMPFILES++,    next if /^[:-]private_tempfiles$/;
+	$EXPORT{$_}++,           next if /^[:-]any$/;
+	$compile++,              next if /^[:-]compile$/;
+	$NO_UNDEF_PARAMS++,      next if /^[:-]no_undef_params$/;
+	
+	# This is probably extremely evil code -- to be deleted some day.
+	if (/^[-]autoload$/) {
+	    my($pkg) = caller(1);
+	    *{"${pkg}::AUTOLOAD"} = sub { 
+		my($routine) = $AUTOLOAD;
+		$routine =~ s/^.*::/CGI::/;
+		&$routine;
+	    };
+	    next;
+	}
+
+	foreach (&expand_tags($_)) {
+	    tr/a-zA-Z0-9_//cd;  # don't allow weird function names
+	    $EXPORT{$_}++;
+	}
+    }
+    _compile_all(keys %EXPORT) if $compile;
+}
+
+sub charset {
+  my ($self,$charset) = self_or_default(@_);
+  $self->{'.charset'} = $charset if defined $charset;
+  $self->{'.charset'};
+}
+
+###############################################################################
+################# THESE FUNCTIONS ARE AUTOLOADED ON DEMAND ####################
+###############################################################################
+$AUTOLOADED_ROUTINES = '';      # get rid of -w warning
+$AUTOLOADED_ROUTINES=<<'END_OF_AUTOLOAD';
+
+%SUBS = (
+
+'URL_ENCODED'=> <<'END_OF_FUNC',
+sub URL_ENCODED { 'application/x-www-form-urlencoded'; }
+END_OF_FUNC
+
+'MULTIPART' => <<'END_OF_FUNC',
+sub MULTIPART {  'multipart/form-data'; }
+END_OF_FUNC
+
+'SERVER_PUSH' => <<'END_OF_FUNC',
+sub SERVER_PUSH { 'multipart/x-mixed-replace;boundary="' . shift() . '"'; }
+END_OF_FUNC
+
+'new_MultipartBuffer' => <<'END_OF_FUNC',
+# Create a new multipart buffer
+sub new_MultipartBuffer {
+    my($self,$boundary,$length,$filehandle) = @_;
+    return MultipartBuffer->new($self,$boundary,$length,$filehandle);
+}
+END_OF_FUNC
+
+'read_from_client' => <<'END_OF_FUNC',
+# Read data from a file handle
+sub read_from_client {
+    my($self, $fh, $buff, $len, $offset) = @_;
+    local $^W=0;                # prevent a warning
+    return undef unless defined($fh);
+    return read($fh, $$buff, $len, $offset);
+}
+END_OF_FUNC
+
+'delete' => <<'END_OF_FUNC',
+#### Method: delete
+# Deletes the named parameter entirely.
+####
+sub delete {
+    my($self,@p) = self_or_default(@_);
+    my($name) = rearrange([NAME],@p);
+    CORE::delete $self->{$name};
+    CORE::delete $self->{'.fieldnames'}->{$name};
+    @{$self->{'.parameters'}}=grep($_ ne $name,$self->param());
+    return wantarray ? () : undef;
+}
+END_OF_FUNC
+
+#### Method: import_names
+# Import all parameters into the given namespace.
+# Assumes namespace 'Q' if not specified
+####
+'import_names' => <<'END_OF_FUNC',
+sub import_names {
+    my($self,$namespace,$delete) = self_or_default(@_);
+    $namespace = 'Q' unless defined($namespace);
+    die "Can't import names into \"main\"\n" if \%{"${namespace}::"} == \%::;
+    if ($delete || $MOD_PERL || exists $ENV{'FCGI_ROLE'}) {
+	# can anyone find an easier way to do this?
+	foreach (keys %{"${namespace}::"}) {
+	    local *symbol = "${namespace}::${_}";
+	    undef $symbol;
+	    undef @symbol;
+	    undef %symbol;
+	}
+    }
+    my($param,@value,$var);
+    foreach $param ($self->param) {
+	# protect against silly names
+	($var = $param)=~tr/a-zA-Z0-9_/_/c;
+	$var =~ s/^(?=\d)/_/;
+	local *symbol = "${namespace}::$var";
+	@value = $self->param($param);
+	@symbol = @value;
+	$symbol = $value[0];
+    }
+}
+END_OF_FUNC
+
+#### Method: keywords
+# Keywords acts a bit differently.  Calling it in a list context
+# returns the list of keywords.  
+# Calling it in a scalar context gives you the size of the list.
+####
+'keywords' => <<'END_OF_FUNC',
+sub keywords {
+    my($self,@values) = self_or_default(@_);
+    # If values is provided, then we set it.
+    $self->{'keywords'}=[@values] if @values;
+    my(@result) = defined($self->{'keywords'}) ? @{$self->{'keywords'}} : ();
+    @result;
+}
+END_OF_FUNC
+
+# These are some tie() interfaces for compatibility
+# with Steve Brenner's cgi-lib.pl routines
+'Vars' => <<'END_OF_FUNC',
+sub Vars {
+    my $q = shift;
+    my %in;
+    tie(%in,CGI,$q);
+    return %in if wantarray;
+    return \%in;
+}
+END_OF_FUNC
+
+# These are some tie() interfaces for compatibility
+# with Steve Brenner's cgi-lib.pl routines
+'ReadParse' => <<'END_OF_FUNC',
+sub ReadParse {
+    local(*in);
+    if (@_) {
+	*in = $_[0];
+    } else {
+	my $pkg = caller();
+	*in=*{"${pkg}::in"};
+    }
+    tie(%in,CGI);
+    return scalar(keys %in);
+}
+END_OF_FUNC
+
+'PrintHeader' => <<'END_OF_FUNC',
+sub PrintHeader {
+    my($self) = self_or_default(@_);
+    return $self->header();
+}
+END_OF_FUNC
+
+'HtmlTop' => <<'END_OF_FUNC',
+sub HtmlTop {
+    my($self,@p) = self_or_default(@_);
+    return $self->start_html(@p);
+}
+END_OF_FUNC
+
+'HtmlBot' => <<'END_OF_FUNC',
+sub HtmlBot {
+    my($self,@p) = self_or_default(@_);
+    return $self->end_html(@p);
+}
+END_OF_FUNC
+
+'SplitParam' => <<'END_OF_FUNC',
+sub SplitParam {
+    my ($param) = @_;
+    my (@params) = split ("\0", $param);
+    return (wantarray ? @params : $params[0]);
+}
+END_OF_FUNC
+
+'MethGet' => <<'END_OF_FUNC',
+sub MethGet {
+    return request_method() eq 'GET';
+}
+END_OF_FUNC
+
+'MethPost' => <<'END_OF_FUNC',
+sub MethPost {
+    return request_method() eq 'POST';
+}
+END_OF_FUNC
+
+'TIEHASH' => <<'END_OF_FUNC',
+sub TIEHASH { 
+    return $_[1] if defined $_[1];
+    return $Q ||= new shift;
+}
+END_OF_FUNC
+
+'STORE' => <<'END_OF_FUNC',
+sub STORE {
+    my $self = shift;
+    my $tag  = shift;
+    my $vals = shift;
+    my @vals = index($vals,"\0")!=-1 ? split("\0",$vals) : $vals;
+    $self->param(-name=>$tag,-value=>\@vals);
+}
+END_OF_FUNC
+
+'FETCH' => <<'END_OF_FUNC',
+sub FETCH {
+    return $_[0] if $_[1] eq 'CGI';
+    return undef unless defined $_[0]->param($_[1]);
+    return join("\0",$_[0]->param($_[1]));
+}
+END_OF_FUNC
+
+'FIRSTKEY' => <<'END_OF_FUNC',
+sub FIRSTKEY {
+    $_[0]->{'.iterator'}=0;
+    $_[0]->{'.parameters'}->[$_[0]->{'.iterator'}++];
+}
+END_OF_FUNC
+
+'NEXTKEY' => <<'END_OF_FUNC',
+sub NEXTKEY {
+    $_[0]->{'.parameters'}->[$_[0]->{'.iterator'}++];
+}
+END_OF_FUNC
+
+'EXISTS' => <<'END_OF_FUNC',
+sub EXISTS {
+    exists $_[0]->{$_[1]};
+}
+END_OF_FUNC
+
+'DELETE' => <<'END_OF_FUNC',
+sub DELETE {
+    $_[0]->delete($_[1]);
+}
+END_OF_FUNC
+
+'CLEAR' => <<'END_OF_FUNC',
+sub CLEAR {
+    %{$_[0]}=();
+}
+####
+END_OF_FUNC
+
+####
+# Append a new value to an existing query
+####
+'append' => <<'EOF',
+sub append {
+    my($self,@p) = @_;
+    my($name,$value) = rearrange([NAME,[VALUE,VALUES]],@p);
+    my(@values) = defined($value) ? (ref($value) ? @{$value} : $value) : ();
+    if (@values) {
+	$self->add_parameter($name);
+	push(@{$self->{$name}},@values);
+    }
+    return $self->param($name);
+}
+EOF
+
+#### Method: delete_all
+# Delete all parameters
+####
+'delete_all' => <<'EOF',
+sub delete_all {
+    my($self) = self_or_default(@_);
+    undef %{$self};
+}
+EOF
+
+'Delete' => <<'EOF',
+sub Delete {
+    my($self,@p) = self_or_default(@_);
+    $self->delete(@p);
+}
+EOF
+
+'Delete_all' => <<'EOF',
+sub Delete_all {
+    my($self,@p) = self_or_default(@_);
+    $self->delete_all(@p);
+}
+EOF
+
+#### Method: autoescape
+# If you want to turn off the autoescaping features,
+# call this method with undef as the argument
+'autoEscape' => <<'END_OF_FUNC',
+sub autoEscape {
+    my($self,$escape) = self_or_default(@_);
+    $self->{'dontescape'}=!$escape;
+}
+END_OF_FUNC
+
+
+#### Method: version
+# Return the current version
+####
+'version' => <<'END_OF_FUNC',
+sub version {
+    return $VERSION;
+}
+END_OF_FUNC
+
+#### Method: url_param
+# Return a parameter in the QUERY_STRING, regardless of
+# whether this was a POST or a GET
+####
+'url_param' => <<'END_OF_FUNC',
+sub url_param {
+    my ($self,@p) = self_or_default(@_);
+    my $name = shift(@p);
+    return undef unless exists($ENV{QUERY_STRING});
+    unless (exists($self->{'.url_param'})) {
+	$self->{'.url_param'}={}; # empty hash
+	if ($ENV{QUERY_STRING} =~ /=/) {
+	    my(@pairs) = split(/[&;]/,$ENV{QUERY_STRING});
+	    my($param,$value);
+	    foreach (@pairs) {
+		($param,$value) = split('=',$_,2);
+		$param = unescape($param);
+		$value = unescape($value);
+		push(@{$self->{'.url_param'}->{$param}},$value);
+	    }
+	} else {
+	    $self->{'.url_param'}->{'keywords'} = [$self->parse_keywordlist($ENV{QUERY_STRING})];
+	}
+    }
+    return keys %{$self->{'.url_param'}} unless defined($name);
+    return () unless $self->{'.url_param'}->{$name};
+    return wantarray ? @{$self->{'.url_param'}->{$name}}
+                     : $self->{'.url_param'}->{$name}->[0];
+}
+END_OF_FUNC
+
+#### Method: Dump
+# Returns a string in which all the known parameter/value 
+# pairs are represented as nested lists, mainly for the purposes 
+# of debugging.
+####
+'Dump' => <<'END_OF_FUNC',
+sub Dump {
+    my($self) = self_or_default(@_);
+    my($param,$value,@result);
+    return '<ul></ul>' unless $self->param;
+    push(@result,"<ul>");
+    foreach $param ($self->param) {
+	my($name)=$self->escapeHTML($param);
+	push(@result,"<li><strong>$param</strong>");
+	push(@result,"<ul>");
+	foreach $value ($self->param($param)) {
+	    $value = $self->escapeHTML($value);
+            $value =~ s/\n/<br>\n/g;
+	    push(@result,"<li>$value");
+	}
+	push(@result,"</ul>");
+    }
+    push(@result,"</ul>");
+    return join("\n",@result);
+}
+END_OF_FUNC
+
+#### Method as_string
+#
+# synonym for "dump"
+####
+'as_string' => <<'END_OF_FUNC',
+sub as_string {
+    &Dump(@_);
+}
+END_OF_FUNC
+
+#### Method: save
+# Write values out to a filehandle in such a way that they can
+# be reinitialized by the filehandle form of the new() method
+####
+'save' => <<'END_OF_FUNC',
+sub save {
+    my($self,$filehandle) = self_or_default(@_);
+    $filehandle = to_filehandle($filehandle);
+    my($param);
+    local($,) = '';  # set print field separator back to a sane value
+    local($\) = '';  # set output line separator to a sane value
+    foreach $param ($self->param) {
+	my($escaped_param) = escape($param);
+	my($value);
+	foreach $value ($self->param($param)) {
+	    print $filehandle "$escaped_param=",escape("$value"),"\n";
+	}
+    }
+    foreach (keys %{$self->{'.fieldnames'}}) {
+          print $filehandle ".cgifields=",escape("$_"),"\n";
+    }
+    print $filehandle "=\n";    # end of record
+}
+END_OF_FUNC
+
+
+#### Method: save_parameters
+# An alias for save() that is a better name for exportation.
+# Only intended to be used with the function (non-OO) interface.
+####
+'save_parameters' => <<'END_OF_FUNC',
+sub save_parameters {
+    my $fh = shift;
+    return save(to_filehandle($fh));
+}
+END_OF_FUNC
+
+#### Method: restore_parameters
+# A way to restore CGI parameters from an initializer.
+# Only intended to be used with the function (non-OO) interface.
+####
+'restore_parameters' => <<'END_OF_FUNC',
+sub restore_parameters {
+    $Q = $CGI::DefaultClass->new(@_);
+}
+END_OF_FUNC
+
+#### Method: multipart_init
+# Return a Content-Type: style header for server-push
+# This has to be NPH on most web servers, and it is advisable to set $| = 1
+#
+# Many thanks to Ed Jordan <ed@fidalgo.net> for this
+# contribution, updated by Andrew Benham (adsb@bigfoot.com)
+####
+'multipart_init' => <<'END_OF_FUNC',
+sub multipart_init {
+    my($self,@p) = self_or_default(@_);
+    my($boundary,@other) = rearrange([BOUNDARY],@p);
+    $boundary = $boundary || '------- =_aaaaaaaaaa0';
+    $self->{'separator'} = "$CRLF--$boundary$CRLF";
+    $self->{'final_separator'} = "$CRLF--$boundary--$CRLF";
+    $type = SERVER_PUSH($boundary);
+    return $self->header(
+	-nph => 1,
+	-type => $type,
+	(map { split "=", $_, 2 } @other),
+    ) . "WARNING: YOUR BROWSER DOESN'T SUPPORT THIS SERVER-PUSH TECHNOLOGY." . $self->multipart_end;
+}
+END_OF_FUNC
+
+
+#### Method: multipart_start
+# Return a Content-Type: style header for server-push, start of section
+#
+# Many thanks to Ed Jordan <ed@fidalgo.net> for this
+# contribution, updated by Andrew Benham (adsb@bigfoot.com)
+####
+'multipart_start' => <<'END_OF_FUNC',
+sub multipart_start {
+    my(@header);
+    my($self,@p) = self_or_default(@_);
+    my($type,@other) = rearrange([TYPE],@p);
+    $type = $type || 'text/html';
+    push(@header,"Content-Type: $type");
+
+    # rearrange() was designed for the HTML portion, so we
+    # need to fix it up a little.
+    foreach (@other) {
+        next unless my($header,$value) = /([^\s=]+)=\"?(.+?)\"?$/;
+	($_ = $header) =~ s/^(\w)(.*)/$1 . lc ($2) . ': '.$self->unescapeHTML($value)/e;
+    }
+    push(@header,@other);
+    my $header = join($CRLF,@header)."${CRLF}${CRLF}";
+    return $header;
+}
+END_OF_FUNC
+
+
+#### Method: multipart_end
+# Return a MIME boundary separator for server-push, end of section
+#
+# Many thanks to Ed Jordan <ed@fidalgo.net> for this
+# contribution
+####
+'multipart_end' => <<'END_OF_FUNC',
+sub multipart_end {
+    my($self,@p) = self_or_default(@_);
+    return $self->{'separator'};
+}
+END_OF_FUNC
+
+
+#### Method: multipart_final
+# Return a MIME boundary separator for server-push, end of all sections
+#
+# Contributed by Andrew Benham (adsb@bigfoot.com)
+####
+'multipart_final' => <<'END_OF_FUNC',
+sub multipart_final {
+    my($self,@p) = self_or_default(@_);
+    return $self->{'final_separator'} . "WARNING: YOUR BROWSER DOESN'T SUPPORT THIS SERVER-PUSH TECHNOLOGY." . $CRLF;
+}
+END_OF_FUNC
+
+
+#### Method: header
+# Return a Content-Type: style header
+#
+####
+'header' => <<'END_OF_FUNC',
+sub header {
+    my($self,@p) = self_or_default(@_);
+    my(@header);
+
+    return undef if $self->{'.header_printed'}++ and $HEADERS_ONCE;
+
+    my($type,$status,$cookie,$target,$expires,$nph,$charset,$attachment,@other) = 
+	rearrange([['TYPE','CONTENT_TYPE','CONTENT-TYPE'],
+			    'STATUS',['COOKIE','COOKIES'],'TARGET',
+                            'EXPIRES','NPH','CHARSET',
+                            'ATTACHMENT'],@p);
+
+    $nph     ||= $NPH;
+    if (defined $charset) {
+      $self->charset($charset);
+    } else {
+      $charset = $self->charset;
+    }
+
+    # rearrange() was designed for the HTML portion, so we
+    # need to fix it up a little.
+    foreach (@other) {
+        next unless my($header,$value) = /([^\s=]+)=\"?(.+?)\"?$/;
+	($_ = $header) =~ s/^(\w)(.*)/$1 . lc ($2) . ': '.$self->unescapeHTML($value)/e;
+        $header = ucfirst($header);
+    }
+
+    $type ||= 'text/html' unless defined($type);
+    $type .= "; charset=$charset" if $type ne '' and $type =~ m!^text/! and $type !~ /\bcharset\b/;
+
+    # Maybe future compatibility.  Maybe not.
+    my $protocol = $ENV{SERVER_PROTOCOL} || 'HTTP/1.0';
+    push(@header,$protocol . ' ' . ($status || '200 OK')) if $nph;
+    push(@header,"Server: " . &server_software()) if $nph;
+
+    push(@header,"Status: $status") if $status;
+    push(@header,"Window-Target: $target") if $target;
+    # push all the cookies -- there may be several
+    if ($cookie) {
+	my(@cookie) = ref($cookie) && ref($cookie) eq 'ARRAY' ? @{$cookie} : $cookie;
+	foreach (@cookie) {
+            my $cs = UNIVERSAL::isa($_,'CGI::Cookie') ? $_->as_string : $_;
+	    push(@header,"Set-Cookie: $cs") if $cs ne '';
+	}
+    }
+    # if the user indicates an expiration time, then we need
+    # both an Expires and a Date header (so that the browser is
+    # uses OUR clock)
+    push(@header,"Expires: " . expires($expires,'http'))
+	if $expires;
+    push(@header,"Date: " . expires(0,'http')) if $expires || $cookie || $nph;
+    push(@header,"Pragma: no-cache") if $self->cache();
+    push(@header,"Content-Disposition: attachment; filename=\"$attachment\"") if $attachment;
+    push(@header,map {ucfirst $_} @other);
+    push(@header,"Content-Type: $type") if $type ne '';
+
+    my $header = join($CRLF,@header)."${CRLF}${CRLF}";
+    if ($MOD_PERL and not $nph) {
+	my $r = Apache->request;
+	$r->send_cgi_header($header);
+	return '';
+    }
+    return $header;
+}
+END_OF_FUNC
+
+
+#### Method: cache
+# Control whether header() will produce the no-cache
+# Pragma directive.
+####
+'cache' => <<'END_OF_FUNC',
+sub cache {
+    my($self,$new_value) = self_or_default(@_);
+    $new_value = '' unless $new_value;
+    if ($new_value ne '') {
+	$self->{'cache'} = $new_value;
+    }
+    return $self->{'cache'};
+}
+END_OF_FUNC
+
+
+#### Method: redirect
+# Return a Location: style header
+#
+####
+'redirect' => <<'END_OF_FUNC',
+sub redirect {
+    my($self,@p) = self_or_default(@_);
+    my($url,$target,$cookie,$nph,@other) = rearrange([[LOCATION,URI,URL],TARGET,COOKIE,NPH],@p);
+    $url ||= $self->self_url;
+    my(@o);
+    foreach (@other) { tr/\"//d; push(@o,split("=",$_,2)); }
+    unshift(@o,
+	 '-Status'=>'302 Moved',
+	 '-Location'=>$url,
+	 '-nph'=>$nph);
+    unshift(@o,'-Target'=>$target) if $target;
+    unshift(@o,'-Cookie'=>$cookie) if $cookie;
+    unshift(@o,'-Type'=>'');
+    return $self->header(@o);
+}
+END_OF_FUNC
+
+
+#### Method: start_html
+# Canned HTML header
+#
+# Parameters:
+# $title -> (optional) The title for this HTML document (-title)
+# $author -> (optional) e-mail address of the author (-author)
+# $base -> (optional) if set to true, will enter the BASE address of this document
+#          for resolving relative references (-base) 
+# $xbase -> (optional) alternative base at some remote location (-xbase)
+# $target -> (optional) target window to load all links into (-target)
+# $script -> (option) Javascript code (-script)
+# $no_script -> (option) Javascript <noscript> tag (-noscript)
+# $meta -> (optional) Meta information tags
+# $head -> (optional) any other elements you'd like to incorporate into the <head> tag
+#           (a scalar or array ref)
+# $style -> (optional) reference to an external style sheet
+# @other -> (optional) any other named parameters you'd like to incorporate into
+#           the <body> tag.
+####
+'start_html' => <<'END_OF_FUNC',
+sub start_html {
+    my($self,@p) = &self_or_default(@_);
+    my($title,$author,$base,$xbase,$script,$noscript,
+        $target,$meta,$head,$style,$dtd,$lang,$encoding,@other) = 
+	rearrange([TITLE,AUTHOR,BASE,XBASE,SCRIPT,NOSCRIPT,TARGET,META,HEAD,STYLE,DTD,LANG,ENCODING],@p);
+
+    $encoding = 'iso-8859-1' unless defined $encoding;
+
+    # strangely enough, the title needs to be escaped as HTML
+    # while the author needs to be escaped as a URL
+    $title = $self->escapeHTML($title || 'Untitled Document');
+    $author = $self->escape($author);
+    $lang ||= 'en-US';
+    my(@result,$xml_dtd);
+    if ($dtd) {
+        if (defined(ref($dtd)) and (ref($dtd) eq 'ARRAY')) {
+            $dtd = $DEFAULT_DTD unless $dtd->[0] =~ m|^-//|;
+        } else {
+            $dtd = $DEFAULT_DTD unless $dtd =~ m|^-//|;
+        }
+    } else {
+        $dtd = $XHTML ? XHTML_DTD : $DEFAULT_DTD;
+    }
+
+    $xml_dtd++ if ref($dtd) eq 'ARRAY' && $dtd->[0] =~ /\bXHTML\b/i;
+    $xml_dtd++ if ref($dtd) eq '' && $dtd =~ /\bXHTML\b/i;
+    push @result,qq(<?xml version="1.0" encoding="$encoding"?>) if $xml_dtd; 
+
+    if (ref($dtd) && ref($dtd) eq 'ARRAY') {
+        push(@result,qq(<!DOCTYPE html\n\tPUBLIC "$dtd->[0]"\n\t "$dtd->[1]">));
+    } else {
+        push(@result,qq(<!DOCTYPE html\n\tPUBLIC "$dtd">));
+    }
+    push(@result,$XHTML ? qq(<html xmlns="http://www.w3.org/1999/xhtml" lang="$lang"><head><title>$title</title>)
+                        : qq(<html lang="$lang"><head><title>$title</title>));
+	if (defined $author) {
+    push(@result,$XHTML ? "<link rev=\"made\" href=\"mailto:$author\" />"
+								: "<link rev=\"made\" href=\"mailto:$author\">");
+	}
+
+    if ($base || $xbase || $target) {
+	my $href = $xbase || $self->url('-path'=>1);
+	my $t = $target ? qq/ target="$target"/ : '';
+	push(@result,$XHTML ? qq(<base href="$href"$t />) : qq(<base href="$href"$t>));
+    }
+
+    if ($meta && ref($meta) && (ref($meta) eq 'HASH')) {
+	foreach (keys %$meta) { push(@result,$XHTML ? qq(<meta name="$_" content="$meta->{$_}" />) 
+			: qq(<meta name="$_" content="$meta->{$_}">)); }
+    }
+
+    push(@result,ref($head) ? @$head : $head) if $head;
+
+    # handle the infrequently-used -style and -script parameters
+    push(@result,$self->_style($style)) if defined $style;
+    push(@result,$self->_script($script)) if defined $script;
+
+    # handle -noscript parameter
+    push(@result,<<END) if $noscript;
+<noscript>
+$noscript
+</noscript>
+END
+    ;
+    my($other) = @other ? " @other" : '';
+    push(@result,"</head><body$other>");
+    return join("\n",@result);
+}
+END_OF_FUNC
+
+### Method: _style
+# internal method for generating a CSS style section
+####
+'_style' => <<'END_OF_FUNC',
+sub _style {
+    my ($self,$style) = @_;
+    my (@result);
+    my $type = 'text/css';
+
+    my $cdata_start = $XHTML ? "\n<!--/* <![CDATA[ */" : "\n<!-- ";
+    my $cdata_end   = $XHTML ? "\n/* ]]> */-->\n" : " -->\n";
+
+    if (ref($style)) {
+     my($src,$code,$stype,@other) =
+         rearrange([SRC,CODE,TYPE],
+                    '-foo'=>'bar', # a trick to allow the '-' to be omitted
+                    ref($style) eq 'ARRAY' ? @$style : %$style);
+     $type = $stype if $stype;
+     if (ref($src) eq "ARRAY") # Check to see if the $src variable is an array reference
+     { # If it is, push a LINK tag for each one.
+       foreach $src (@$src)
+       {
+         push(@result,$XHTML ? qq(<link rel="stylesheet" type="$type" href="$src" />)
+                             : qq(<link rel="stylesheet" type="$type" href="$src">)) if $src;
+       }
+     }
+     else
+     { # Otherwise, push the single -src, if it exists.
+       push(@result,$XHTML ? qq(<link rel="stylesheet" type="$type" href="$src" />)
+                           : qq(<link rel="stylesheet" type="$type" href="$src">)
+            ) if $src;
+      }
+     push(@result,style({'type'=>$type},"$cdata_start\n$code\n$cdata_end")) if $code;
+    } else {
+     push(@result,style({'type'=>$type},"$cdata_start\n$style\n$cdata_end"));
+    }
+    @result;
+}
+END_OF_FUNC
+
+'_script' => <<'END_OF_FUNC',
+sub _script {
+    my ($self,$script) = @_;
+    my (@result);
+
+    my (@scripts) = ref($script) eq 'ARRAY' ? @$script : ($script);
+    foreach $script (@scripts) {
+	my($src,$code,$language);
+	if (ref($script)) { # script is a hash
+	    ($src,$code,$language, $type) =
+		rearrange([SRC,CODE,LANGUAGE,TYPE],
+				 '-foo'=>'bar',	# a trick to allow the '-' to be omitted
+				 ref($script) eq 'ARRAY' ? @$script : %$script);
+            # User may not have specified language
+            $language ||= 'JavaScript';
+            unless (defined $type) {
+                $type = lc $language;
+                # strip '1.2' from 'javascript1.2'
+                $type =~ s/^(\D+).*$/text\/$1/;
+            }
+	} else {
+	    ($src,$code,$language, $type) = ('',$script,'JavaScript', 'text/javascript');
+	}
+
+    my $comment = '//';  # javascript by default
+    $comment = '#' if $type=~/perl|tcl/i;
+    $comment = "'" if $type=~/vbscript/i;
+
+    my $cdata_start  =  "\n<!-- Hide script\n";
+    $cdata_start    .= "$comment<![CDATA[\n"  if $XHTML; 
+    my $cdata_end    = $XHTML ? "\n$comment]]>" : $comment;
+    $cdata_end      .= " End script hiding -->\n";
+
+	my(@satts);
+	push(@satts,'src'=>$src) if $src;
+	push(@satts,'language'=>$language);
+        push(@satts,'type'=>$type);
+	$code = "$cdata_start$code$cdata_end" if defined $code;
+	push(@result,script({@satts},$code || ''));
+    }
+    @result;
+}
+END_OF_FUNC
+
+#### Method: end_html
+# End an HTML document.
+# Trivial method for completeness.  Just returns "</body>"
+####
+'end_html' => <<'END_OF_FUNC',
+sub end_html {
+    return "</body></html>";
+}
+END_OF_FUNC
+
+
+################################
+# METHODS USED IN BUILDING FORMS
+################################
+
+#### Method: isindex
+# Just prints out the isindex tag.
+# Parameters:
+#  $action -> optional URL of script to run
+# Returns:
+#   A string containing a <ISINDEX> tag
+'isindex' => <<'END_OF_FUNC',
+sub isindex {
+    my($self,@p) = self_or_default(@_);
+    my($action,@other) = rearrange([ACTION],@p);
+    $action = qq/action="$action"/ if $action;
+    my($other) = @other ? " @other" : '';
+    return $XHTML ? "<isindex $action$other />" : "<isindex $action$other>";
+}
+END_OF_FUNC
+
+
+#### Method: startform
+# Start a form
+# Parameters:
+#   $method -> optional submission method to use (GET or POST)
+#   $action -> optional URL of script to run
+#   $enctype ->encoding to use (URL_ENCODED or MULTIPART)
+'startform' => <<'END_OF_FUNC',
+sub startform {
+    my($self,@p) = self_or_default(@_);
+
+    my($method,$action,$enctype,@other) = 
+	rearrange([METHOD,ACTION,ENCTYPE],@p);
+
+    $method = lc($method) || 'post';
+    $enctype = $enctype || &URL_ENCODED;
+    unless (defined $action) {
+       $action = $self->url(-absolute=>1,-path=>1);
+       $action .= "?$ENV{QUERY_STRING}" if $ENV{QUERY_STRING};
+    }
+    $action = qq(action="$action");
+    my($other) = @other ? " @other" : '';
+    $self->{'.parametersToAdd'}={};
+    return qq/<form method="$method" $action enctype="$enctype"$other>\n/;
+}
+END_OF_FUNC
+
+
+#### Method: start_form
+# synonym for startform
+'start_form' => <<'END_OF_FUNC',
+sub start_form {
+    &startform;
+}
+END_OF_FUNC
+
+'end_multipart_form' => <<'END_OF_FUNC',
+sub end_multipart_form {
+    &endform;
+}
+END_OF_FUNC
+
+#### Method: start_multipart_form
+# synonym for startform
+'start_multipart_form' => <<'END_OF_FUNC',
+sub start_multipart_form {
+    my($self,@p) = self_or_default(@_);
+    if (defined($param[0]) && substr($param[0],0,1) eq '-') {
+	my(%p) = @p;
+	$p{'-enctype'}=&MULTIPART;
+	return $self->startform(%p);
+    } else {
+	my($method,$action,@other) = 
+	    rearrange([METHOD,ACTION],@p);
+	return $self->startform($method,$action,&MULTIPART,@other);
+    }
+}
+END_OF_FUNC
+
+
+#### Method: endform
+# End a form
+'endform' => <<'END_OF_FUNC',
+sub endform {
+    my($self,@p) = self_or_default(@_);    
+    if ( $NOSTICKY ) {
+    return wantarray ? ("</form>") : "\n</form>";
+    } else {
+    return wantarray ? ($self->get_fields,"</form>") : 
+                        $self->get_fields ."\n</form>";
+    }
+}
+END_OF_FUNC
+
+
+#### Method: end_form
+# synonym for endform
+'end_form' => <<'END_OF_FUNC',
+sub end_form {
+    &endform;
+}
+END_OF_FUNC
+
+
+'_textfield' => <<'END_OF_FUNC',
+sub _textfield {
+    my($self,$tag,@p) = self_or_default(@_);
+    my($name,$default,$size,$maxlength,$override,@other) = 
+	rearrange([NAME,[DEFAULT,VALUE],SIZE,MAXLENGTH,[OVERRIDE,FORCE]],@p);
+
+    my $current = $override ? $default : 
+	(defined($self->param($name)) ? $self->param($name) : $default);
+
+    $current = defined($current) ? $self->escapeHTML($current,1) : '';
+    $name = defined($name) ? $self->escapeHTML($name) : '';
+    my($s) = defined($size) ? qq/ size="$size"/ : '';
+    my($m) = defined($maxlength) ? qq/ maxlength="$maxlength"/ : '';
+    my($other) = @other ? " @other" : '';
+    # this entered at cristy's request to fix problems with file upload fields
+    # and WebTV -- not sure it won't break stuff
+    my($value) = $current ne '' ? qq(value="$current") : '';
+    return $XHTML ? qq(<input type="$tag" name="$name" $value$s$m$other />) 
+                  : qq(<input type="$tag" name="$name" $value$s$m$other>);
+}
+END_OF_FUNC
+
+#### Method: textfield
+# Parameters:
+#   $name -> Name of the text field
+#   $default -> Optional default value of the field if not
+#                already defined.
+#   $size ->  Optional width of field in characaters.
+#   $maxlength -> Optional maximum number of characters.
+# Returns:
+#   A string containing a <INPUT TYPE="text"> field
+#
+'textfield' => <<'END_OF_FUNC',
+sub textfield {
+    my($self,@p) = self_or_default(@_);
+    $self->_textfield('text',@p);
+}
+END_OF_FUNC
+
+
+#### Method: filefield
+# Parameters:
+#   $name -> Name of the file upload field
+#   $size ->  Optional width of field in characaters.
+#   $maxlength -> Optional maximum number of characters.
+# Returns:
+#   A string containing a <INPUT TYPE="text"> field
+#
+'filefield' => <<'END_OF_FUNC',
+sub filefield {
+    my($self,@p) = self_or_default(@_);
+    $self->_textfield('file',@p);
+}
+END_OF_FUNC
+
+
+#### Method: password
+# Create a "secret password" entry field
+# Parameters:
+#   $name -> Name of the field
+#   $default -> Optional default value of the field if not
+#                already defined.
+#   $size ->  Optional width of field in characters.
+#   $maxlength -> Optional maximum characters that can be entered.
+# Returns:
+#   A string containing a <INPUT TYPE="password"> field
+#
+'password_field' => <<'END_OF_FUNC',
+sub password_field {
+    my ($self,@p) = self_or_default(@_);
+    $self->_textfield('password',@p);
+}
+END_OF_FUNC
+
+#### Method: textarea
+# Parameters:
+#   $name -> Name of the text field
+#   $default -> Optional default value of the field if not
+#                already defined.
+#   $rows ->  Optional number of rows in text area
+#   $columns -> Optional number of columns in text area
+# Returns:
+#   A string containing a <textarea></textarea> tag
+#
+'textarea' => <<'END_OF_FUNC',
+sub textarea {
+    my($self,@p) = self_or_default(@_);
+    
+    my($name,$default,$rows,$cols,$override,@other) =
+	rearrange([NAME,[DEFAULT,VALUE],ROWS,[COLS,COLUMNS],[OVERRIDE,FORCE]],@p);
+
+    my($current)= $override ? $default :
+	(defined($self->param($name)) ? $self->param($name) : $default);
+
+    $name = defined($name) ? $self->escapeHTML($name) : '';
+    $current = defined($current) ? $self->escapeHTML($current) : '';
+    my($r) = $rows ? qq/ rows="$rows"/ : '';
+    my($c) = $cols ? qq/ cols="$cols"/ : '';
+    my($other) = @other ? " @other" : '';
+    return qq{<textarea name="$name"$r$c$other>$current</textarea>};
+}
+END_OF_FUNC
+
+
+#### Method: button
+# Create a javascript button.
+# Parameters:
+#   $name ->  (optional) Name for the button. (-name)
+#   $value -> (optional) Value of the button when selected (and visible name) (-value)
+#   $onclick -> (optional) Text of the JavaScript to run when the button is
+#                clicked.
+# Returns:
+#   A string containing a <INPUT TYPE="button"> tag
+####
+'button' => <<'END_OF_FUNC',
+sub button {
+    my($self,@p) = self_or_default(@_);
+
+    my($label,$value,$script,@other) = rearrange([NAME,[VALUE,LABEL],
+							 [ONCLICK,SCRIPT]],@p);
+
+    $label=$self->escapeHTML($label);
+    $value=$self->escapeHTML($value,1);
+    $script=$self->escapeHTML($script);
+
+    my($name) = '';
+    $name = qq/ name="$label"/ if $label;
+    $value = $value || $label;
+    my($val) = '';
+    $val = qq/ value="$value"/ if $value;
+    $script = qq/ onclick="$script"/ if $script;
+    my($other) = @other ? " @other" : '';
+    return $XHTML ? qq(<input type="button"$name$val$script$other />)
+                  : qq(<input type="button"$name$val$script$other>);
+}
+END_OF_FUNC
+
+
+#### Method: submit
+# Create a "submit query" button.
+# Parameters:
+#   $name ->  (optional) Name for the button.
+#   $value -> (optional) Value of the button when selected (also doubles as label).
+#   $label -> (optional) Label printed on the button(also doubles as the value).
+# Returns:
+#   A string containing a <INPUT TYPE="submit"> tag
+####
+'submit' => <<'END_OF_FUNC',
+sub submit {
+    my($self,@p) = self_or_default(@_);
+
+    my($label,$value,@other) = rearrange([NAME,[VALUE,LABEL]],@p);
+
+    $label=$self->escapeHTML($label);
+    $value=$self->escapeHTML($value,1);
+
+    my($name) = ' name=".submit"' unless $NOSTICKY;
+    $name = qq/ name="$label"/ if defined($label);
+    $value = defined($value) ? $value : $label;
+    my($val) = '';
+    $val = qq/ value="$value"/ if defined($value);
+    my($other) = @other ? " @other" : '';
+    return $XHTML ? qq(<input type="submit"$name$val$other />)
+                  : qq(<input type="submit"$name$val$other>);
+}
+END_OF_FUNC
+
+
+#### Method: reset
+# Create a "reset" button.
+# Parameters:
+#   $name -> (optional) Name for the button.
+# Returns:
+#   A string containing a <INPUT TYPE="reset"> tag
+####
+'reset' => <<'END_OF_FUNC',
+sub reset {
+    my($self,@p) = self_or_default(@_);
+    my($label,@other) = rearrange([NAME],@p);
+    $label=$self->escapeHTML($label);
+    my($value) = defined($label) ? qq/ value="$label"/ : '';
+    my($other) = @other ? " @other" : '';
+    return $XHTML ? qq(<input type="reset"$value$other />)
+                  : qq(<input type="reset"$value$other>);
+}
+END_OF_FUNC
+
+
+#### Method: defaults
+# Create a "defaults" button.
+# Parameters:
+#   $name -> (optional) Name for the button.
+# Returns:
+#   A string containing a <INPUT TYPE="submit" NAME=".defaults"> tag
+#
+# Note: this button has a special meaning to the initialization script,
+# and tells it to ERASE the current query string so that your defaults
+# are used again!
+####
+'defaults' => <<'END_OF_FUNC',
+sub defaults {
+    my($self,@p) = self_or_default(@_);
+
+    my($label,@other) = rearrange([[NAME,VALUE]],@p);
+
+    $label=$self->escapeHTML($label,1);
+    $label = $label || "Defaults";
+    my($value) = qq/ value="$label"/;
+    my($other) = @other ? " @other" : '';
+    return $XHTML ? qq(<input type="submit" name=".defaults"$value$other />)
+                  : qq/<input type="submit" NAME=".defaults"$value$other>/;
+}
+END_OF_FUNC
+
+
+#### Method: comment
+# Create an HTML <!-- comment -->
+# Parameters: a string
+'comment' => <<'END_OF_FUNC',
+sub comment {
+    my($self,@p) = self_or_CGI(@_);
+    return "<!-- @p -->";
+}
+END_OF_FUNC
+
+#### Method: checkbox
+# Create a checkbox that is not logically linked to any others.
+# The field value is "on" when the button is checked.
+# Parameters:
+#   $name -> Name of the checkbox
+#   $checked -> (optional) turned on by default if true
+#   $value -> (optional) value of the checkbox, 'on' by default
+#   $label -> (optional) a user-readable label printed next to the box.
+#             Otherwise the checkbox name is used.
+# Returns:
+#   A string containing a <INPUT TYPE="checkbox"> field
+####
+'checkbox' => <<'END_OF_FUNC',
+sub checkbox {
+    my($self,@p) = self_or_default(@_);
+
+    my($name,$checked,$value,$label,$override,@other) = 
+	rearrange([NAME,[CHECKED,SELECTED,ON],VALUE,LABEL,[OVERRIDE,FORCE]],@p);
+    
+    $value = defined $value ? $value : 'on';
+
+    if (!$override && ($self->{'.fieldnames'}->{$name} || 
+		       defined $self->param($name))) {
+	$checked = grep($_ eq $value,$self->param($name)) ? $self->_checked(1) : '';
+    } else {
+	$checked = $self->_checked($checked);
+    }
+    my($the_label) = defined $label ? $label : $name;
+    $name = $self->escapeHTML($name);
+    $value = $self->escapeHTML($value,1);
+    $the_label = $self->escapeHTML($the_label);
+    my($other) = @other ? " @other" : '';
+    $self->register_parameter($name);
+    return $XHTML ? qq{<input type="checkbox" name="$name" value="$value"$checked$other />$the_label}
+                  : qq{<input type="checkbox" name="$name" value="$value"$checked$other>$the_label};
+}
+END_OF_FUNC
+
+
+#### Method: checkbox_group
+# Create a list of logically-linked checkboxes.
+# Parameters:
+#   $name -> Common name for all the check boxes
+#   $values -> A pointer to a regular array containing the
+#             values for each checkbox in the group.
+#   $defaults -> (optional)
+#             1. If a pointer to a regular array of checkbox values,
+#             then this will be used to decide which
+#             checkboxes to turn on by default.
+#             2. If a scalar, will be assumed to hold the
+#             value of a single checkbox in the group to turn on. 
+#   $linebreak -> (optional) Set to true to place linebreaks
+#             between the buttons.
+#   $labels -> (optional)
+#             A pointer to an associative array of labels to print next to each checkbox
+#             in the form $label{'value'}="Long explanatory label".
+#             Otherwise the provided values are used as the labels.
+# Returns:
+#   An ARRAY containing a series of <INPUT TYPE="checkbox"> fields
+####
+'checkbox_group' => <<'END_OF_FUNC',
+sub checkbox_group {
+    my($self,@p) = self_or_default(@_);
+
+    my($name,$values,$defaults,$linebreak,$labels,$rows,$columns,
+       $rowheaders,$colheaders,$override,$nolabels,@other) =
+	rearrange([NAME,[VALUES,VALUE],[DEFAULTS,DEFAULT],
+			  LINEBREAK,LABELS,ROWS,[COLUMNS,COLS],
+			  ROWHEADERS,COLHEADERS,
+			  [OVERRIDE,FORCE],NOLABELS],@p);
+
+    my($checked,$break,$result,$label);
+
+    my(%checked) = $self->previous_or_default($name,$defaults,$override);
+
+	if ($linebreak) {
+    $break = $XHTML ? "<br />" : "<br>";
+	}
+	else {
+	$break = '';
+	}
+    $name=$self->escapeHTML($name);
+
+    # Create the elements
+    my(@elements,@values);
+
+    @values = $self->_set_values_and_labels($values,\$labels,$name);
+
+    my($other) = @other ? " @other" : '';
+    foreach (@values) {
+	$checked = $self->_checked($checked{$_});
+	$label = '';
+	unless (defined($nolabels) && $nolabels) {
+	    $label = $_;
+	    $label = $labels->{$_} if defined($labels) && defined($labels->{$_});
+	    $label = $self->escapeHTML($label);
+	}
+	$_ = $self->escapeHTML($_,1);
+	push(@elements,$XHTML ? qq(<input type="checkbox" name="$name" value="$_"$checked$other />${label}${break})
+                              : qq/<input type="checkbox" name="$name" value="$_"$checked$other>${label}${break}/);
+    }
+    $self->register_parameter($name);
+    return wantarray ? @elements : join(' ',@elements)            
+        unless defined($columns) || defined($rows);
+    return _tableize($rows,$columns,$rowheaders,$colheaders,@elements);
+}
+END_OF_FUNC
+
+# Escape HTML -- used internally
+'escapeHTML' => <<'END_OF_FUNC',
+sub escapeHTML {
+         # hack to work around  earlier hacks
+         push @_,$_[0] if @_==1 && $_[0] eq 'CGI';
+         my ($self,$toencode,$newlinestoo) = CGI::self_or_default(@_);
+         return undef unless defined($toencode);
+         return $toencode if ref($self) && $self->{'dontescape'};
+         $toencode =~ s{&}{&amp;}gso;
+         $toencode =~ s{<}{&lt;}gso;
+         $toencode =~ s{>}{&gt;}gso;
+         $toencode =~ s{"}{&quot;}gso;
+         my $latin = uc $self->{'.charset'} eq 'ISO-8859-1' ||
+                     uc $self->{'.charset'} eq 'WINDOWS-1252';
+         if ($latin) {  # bug in some browsers
+                $toencode =~ s{'}{&#39;}gso;
+                $toencode =~ s{\x8b}{&#139;}gso;
+                $toencode =~ s{\x9b}{&#155;}gso;
+                if (defined $newlinestoo && $newlinestoo) {
+                     $toencode =~ s{\012}{&#10;}gso;
+                     $toencode =~ s{\015}{&#13;}gso;
+                }
+         }
+         return $toencode;
+}
+END_OF_FUNC
+
+# unescape HTML -- used internally
+'unescapeHTML' => <<'END_OF_FUNC',
+sub unescapeHTML {
+    my ($self,$string) = CGI::self_or_default(@_);
+    return undef unless defined($string);
+    my $latin = defined $self->{'.charset'} ? $self->{'.charset'} =~ /^(ISO-8859-1|WINDOWS-1252)$/i
+                                            : 1;
+    # thanks to Randal Schwartz for the correct solution to this one
+    $string=~ s[&(.*?);]{
+	local $_ = $1;
+	/^amp$/i	? "&" :
+	/^quot$/i	? '"' :
+        /^gt$/i		? ">" :
+	/^lt$/i		? "<" :
+	/^#(\d+)$/ && $latin	     ? chr($1) :
+	/^#x([0-9a-f]+)$/i && $latin ? chr(hex($1)) :
+	$_
+	}gex;
+    return $string;
+}
+END_OF_FUNC
+
+# Internal procedure - don't use
+'_tableize' => <<'END_OF_FUNC',
+sub _tableize {
+    my($rows,$columns,$rowheaders,$colheaders,@elements) = @_;
+    $rowheaders = [] unless defined $rowheaders;
+    $colheaders = [] unless defined $colheaders;
+    my($result);
+
+    if (defined($columns)) {
+	$rows = int(0.99 + @elements/$columns) unless defined($rows);
+    }
+    if (defined($rows)) {
+	$columns = int(0.99 + @elements/$rows) unless defined($columns);
+    }
+    
+    # rearrange into a pretty table
+    $result = "<table>";
+    my($row,$column);
+    unshift(@$colheaders,'') if @$colheaders && @$rowheaders;
+    $result .= "<tr>" if @{$colheaders};
+    foreach (@{$colheaders}) {
+	$result .= "<th>$_</th>";
+    }
+    for ($row=0;$row<$rows;$row++) {
+	$result .= "<tr>";
+	$result .= "<th>$rowheaders->[$row]</th>" if @$rowheaders;
+	for ($column=0;$column<$columns;$column++) {
+	    $result .= "<td>" . $elements[$column*$rows + $row] . "</td>"
+		if defined($elements[$column*$rows + $row]);
+	}
+	$result .= "</tr>";
+    }
+    $result .= "</table>";
+    return $result;
+}
+END_OF_FUNC
+
+
+#### Method: radio_group
+# Create a list of logically-linked radio buttons.
+# Parameters:
+#   $name -> Common name for all the buttons.
+#   $values -> A pointer to a regular array containing the
+#             values for each button in the group.
+#   $default -> (optional) Value of the button to turn on by default.  Pass '-'
+#               to turn _nothing_ on.
+#   $linebreak -> (optional) Set to true to place linebreaks
+#             between the buttons.
+#   $labels -> (optional)
+#             A pointer to an associative array of labels to print next to each checkbox
+#             in the form $label{'value'}="Long explanatory label".
+#             Otherwise the provided values are used as the labels.
+# Returns:
+#   An ARRAY containing a series of <INPUT TYPE="radio"> fields
+####
+'radio_group' => <<'END_OF_FUNC',
+sub radio_group {
+    my($self,@p) = self_or_default(@_);
+
+    my($name,$values,$default,$linebreak,$labels,
+       $rows,$columns,$rowheaders,$colheaders,$override,$nolabels,@other) =
+	rearrange([NAME,[VALUES,VALUE],DEFAULT,LINEBREAK,LABELS,
+			  ROWS,[COLUMNS,COLS],
+			  ROWHEADERS,COLHEADERS,
+			  [OVERRIDE,FORCE],NOLABELS],@p);
+    my($result,$checked);
+
+    if (!$override && defined($self->param($name))) {
+	$checked = $self->param($name);
+    } else {
+	$checked = $default;
+    }
+    my(@elements,@values);
+    @values = $self->_set_values_and_labels($values,\$labels,$name);
+
+    # If no check array is specified, check the first by default
+    $checked = $values[0] unless defined($checked) && $checked ne '';
+    $name=$self->escapeHTML($name);
+
+    my($other) = @other ? " @other" : '';
+    foreach (@values) {
+	my($checkit) = $checked eq $_ ? qq/ checked="checked"/ : '';
+	my($break);
+	if ($linebreak) {
+          $break = $XHTML ? "<br />" : "<br>";
+	}
+	else {
+	  $break = '';
+	}
+	my($label)='';
+	unless (defined($nolabels) && $nolabels) {
+	    $label = $_;
+	    $label = $labels->{$_} if defined($labels) && defined($labels->{$_});
+	    $label = $self->escapeHTML($label,1);
+	}
+	$_=$self->escapeHTML($_);
+	push(@elements,$XHTML ? qq(<input type="radio" name="$name" value="$_"$checkit$other />${label}${break})
+                              : qq/<input type="radio" name="$name" value="$_"$checkit$other>${label}${break}/);
+    }
+    $self->register_parameter($name);
+    return wantarray ? @elements : join(' ',@elements) 
+           unless defined($columns) || defined($rows);
+    return _tableize($rows,$columns,$rowheaders,$colheaders,@elements);
+}
+END_OF_FUNC
+
+
+#### Method: popup_menu
+# Create a popup menu.
+# Parameters:
+#   $name -> Name for all the menu
+#   $values -> A pointer to a regular array containing the
+#             text of each menu item.
+#   $default -> (optional) Default item to display
+#   $labels -> (optional)
+#             A pointer to an associative array of labels to print next to each checkbox
+#             in the form $label{'value'}="Long explanatory label".
+#             Otherwise the provided values are used as the labels.
+# Returns:
+#   A string containing the definition of a popup menu.
+####
+'popup_menu' => <<'END_OF_FUNC',
+sub popup_menu {
+    my($self,@p) = self_or_default(@_);
+
+    my($name,$values,$default,$labels,$override,@other) =
+	rearrange([NAME,[VALUES,VALUE],[DEFAULT,DEFAULTS],LABELS,[OVERRIDE,FORCE]],@p);
+    my($result,$selected);
+
+    if (!$override && defined($self->param($name))) {
+	$selected = $self->param($name);
+    } else {
+	$selected = $default;
+    }
+    $name=$self->escapeHTML($name);
+    my($other) = @other ? " @other" : '';
+
+    my(@values);
+    @values = $self->_set_values_and_labels($values,\$labels,$name);
+
+    $result = qq/<select name="$name"$other>\n/;
+    foreach (@values) {
+	my($selectit) = defined($selected) ? $self->_selected($selected eq $_) : '';
+	my($label) = $_;
+	$label = $labels->{$_} if defined($labels) && defined($labels->{$_});
+	my($value) = $self->escapeHTML($_);
+	$label=$self->escapeHTML($label,1);
+	$result .= "<option$selectit value=\"$value\">$label</option>\n";
+    }
+
+    $result .= "</select>";
+    return $result;
+}
+END_OF_FUNC
+
+
+#### Method: scrolling_list
+# Create a scrolling list.
+# Parameters:
+#   $name -> name for the list
+#   $values -> A pointer to a regular array containing the
+#             values for each option line in the list.
+#   $defaults -> (optional)
+#             1. If a pointer to a regular array of options,
+#             then this will be used to decide which
+#             lines to turn on by default.
+#             2. Otherwise holds the value of the single line to turn on.
+#   $size -> (optional) Size of the list.
+#   $multiple -> (optional) If set, allow multiple selections.
+#   $labels -> (optional)
+#             A pointer to an associative array of labels to print next to each checkbox
+#             in the form $label{'value'}="Long explanatory label".
+#             Otherwise the provided values are used as the labels.
+# Returns:
+#   A string containing the definition of a scrolling list.
+####
+'scrolling_list' => <<'END_OF_FUNC',
+sub scrolling_list {
+    my($self,@p) = self_or_default(@_);
+    my($name,$values,$defaults,$size,$multiple,$labels,$override,@other)
+	= rearrange([NAME,[VALUES,VALUE],[DEFAULTS,DEFAULT],
+			    SIZE,MULTIPLE,LABELS,[OVERRIDE,FORCE]],@p);
+
+    my($result,@values);
+    @values = $self->_set_values_and_labels($values,\$labels,$name);
+
+    $size = $size || scalar(@values);
+
+    my(%selected) = $self->previous_or_default($name,$defaults,$override);
+    my($is_multiple) = $multiple ? qq/ multiple="multiple"/ : '';
+    my($has_size) = $size ? qq/ size="$size"/: '';
+    my($other) = @other ? " @other" : '';
+
+    $name=$self->escapeHTML($name);
+    $result = qq/<select name="$name"$has_size$is_multiple$other>\n/;
+    foreach (@values) {
+	my($selectit) = $self->_selected($selected{$_});
+	my($label) = $_;
+	$label = $labels->{$_} if defined($labels) && defined($labels->{$_});
+	$label=$self->escapeHTML($label);
+	my($value)=$self->escapeHTML($_,1);
+	$result .= "<option$selectit value=\"$value\">$label</option>\n";
+    }
+    $result .= "</select>";
+    $self->register_parameter($name);
+    return $result;
+}
+END_OF_FUNC
+
+
+#### Method: hidden
+# Parameters:
+#   $name -> Name of the hidden field
+#   @default -> (optional) Initial values of field (may be an array)
+#      or
+#   $default->[initial values of field]
+# Returns:
+#   A string containing a <INPUT TYPE="hidden" NAME="name" VALUE="value">
+####
+'hidden' => <<'END_OF_FUNC',
+sub hidden {
+    my($self,@p) = self_or_default(@_);
+
+    # this is the one place where we departed from our standard
+    # calling scheme, so we have to special-case (darn)
+    my(@result,@value);
+    my($name,$default,$override,@other) = 
+	rearrange([NAME,[DEFAULT,VALUE,VALUES],[OVERRIDE,FORCE]],@p);
+
+    my $do_override = 0;
+    if ( ref($p[0]) || substr($p[0],0,1) eq '-') {
+	@value = ref($default) ? @{$default} : $default;
+	$do_override = $override;
+    } else {
+	foreach ($default,$override,@other) {
+	    push(@value,$_) if defined($_);
+	}
+    }
+
+    # use previous values if override is not set
+    my @prev = $self->param($name);
+    @value = @prev if !$do_override && @prev;
+
+    $name=$self->escapeHTML($name);
+    foreach (@value) {
+	$_ = defined($_) ? $self->escapeHTML($_,1) : '';
+	push @result,$XHTML ? qq(<input type="hidden" name="$name" value="$_" />)
+                            : qq(<input type="hidden" name="$name" value="$_">);
+    }
+    return wantarray ? @result : join('',@result);
+}
+END_OF_FUNC
+
+
+#### Method: image_button
+# Parameters:
+#   $name -> Name of the button
+#   $src ->  URL of the image source
+#   $align -> Alignment style (TOP, BOTTOM or MIDDLE)
+# Returns:
+#   A string containing a <INPUT TYPE="image" NAME="name" SRC="url" ALIGN="alignment">
+####
+'image_button' => <<'END_OF_FUNC',
+sub image_button {
+    my($self,@p) = self_or_default(@_);
+
+    my($name,$src,$alignment,@other) =
+	rearrange([NAME,SRC,ALIGN],@p);
+
+    my($align) = $alignment ? " align=\U\"$alignment\"" : '';
+    my($other) = @other ? " @other" : '';
+    $name=$self->escapeHTML($name);
+    return $XHTML ? qq(<input type="image" name="$name" src="$src"$align$other />)
+                  : qq/<input type="image" name="$name" src="$src"$align$other>/;
+}
+END_OF_FUNC
+
+
+#### Method: self_url
+# Returns a URL containing the current script and all its
+# param/value pairs arranged as a query.  You can use this
+# to create a link that, when selected, will reinvoke the
+# script with all its state information preserved.
+####
+'self_url' => <<'END_OF_FUNC',
+sub self_url {
+    my($self,@p) = self_or_default(@_);
+    return $self->url('-path_info'=>1,'-query'=>1,'-full'=>1,@p);
+}
+END_OF_FUNC
+
+
+# This is provided as a synonym to self_url() for people unfortunate
+# enough to have incorporated it into their programs already!
+'state' => <<'END_OF_FUNC',
+sub state {
+    &self_url;
+}
+END_OF_FUNC
+
+
+#### Method: url
+# Like self_url, but doesn't return the query string part of
+# the URL.
+####
+'url' => <<'END_OF_FUNC',
+sub url {
+    my($self,@p) = self_or_default(@_);
+    my ($relative,$absolute,$full,$path_info,$query,$base) = 
+	rearrange(['RELATIVE','ABSOLUTE','FULL',['PATH','PATH_INFO'],['QUERY','QUERY_STRING'],'BASE'],@p);
+    my $url;
+    $full++      if $base || !($relative || $absolute);
+
+    my $path = $self->path_info;
+    my $script_name = $self->script_name;
+
+    # for compatibility with Apache's MultiViews
+    if (exists($ENV{REQUEST_URI})) {
+        my $index;
+	$script_name = $ENV{REQUEST_URI};
+        $script_name =~ s/\?.+$//;   # strip query string
+        # and path
+        if (exists($ENV{PATH_INFO})) {
+           (my $encoded_path = $ENV{PATH_INFO}) =~ s/([^a-zA-Z0-9_.%;&?\/\\:+=~-])/sprintf("%%%02X",ord($1))/eg;
+           $script_name      =~ s/$encoded_path$//i;
+         }
+    }
+
+    if ($full) {
+	my $protocol = $self->protocol();
+	$url = "$protocol://";
+	my $vh = http('host');
+	if ($vh) {
+	    $url .= $vh;
+	} else {
+	    $url .= server_name();
+	    my $port = $self->server_port;
+	    $url .= ":" . $port
+		unless (lc($protocol) eq 'http' && $port == 80)
+		    || (lc($protocol) eq 'https' && $port == 443);
+	}
+        return $url if $base;
+	$url .= $script_name;
+    } elsif ($relative) {
+	($url) = $script_name =~ m!([^/]+)$!;
+    } elsif ($absolute) {
+	$url = $script_name;
+    }
+
+    $url .= $path if $path_info and defined $path;
+    $url .= "?" . $self->query_string if $query and $self->query_string;
+    $url = '' unless defined $url;
+    $url =~ s/([^a-zA-Z0-9_.%;&?\/\\:+=~-])/sprintf("%%%02X",ord($1))/eg;
+    return $url;
+}
+
+END_OF_FUNC
+
+#### Method: cookie
+# Set or read a cookie from the specified name.
+# Cookie can then be passed to header().
+# Usual rules apply to the stickiness of -value.
+#  Parameters:
+#   -name -> name for this cookie (optional)
+#   -value -> value of this cookie (scalar, array or hash) 
+#   -path -> paths for which this cookie is valid (optional)
+#   -domain -> internet domain in which this cookie is valid (optional)
+#   -secure -> if true, cookie only passed through secure channel (optional)
+#   -expires -> expiry date in format Wdy, DD-Mon-YYYY HH:MM:SS GMT (optional)
+####
+'cookie' => <<'END_OF_FUNC',
+sub cookie {
+    my($self,@p) = self_or_default(@_);
+    my($name,$value,$path,$domain,$secure,$expires) =
+	rearrange([NAME,[VALUE,VALUES],PATH,DOMAIN,SECURE,EXPIRES],@p);
+
+    require CGI::Cookie;
+
+    # if no value is supplied, then we retrieve the
+    # value of the cookie, if any.  For efficiency, we cache the parsed
+    # cookies in our state variables.
+    unless ( defined($value) ) {
+	$self->{'.cookies'} = CGI::Cookie->fetch
+	    unless $self->{'.cookies'};
+
+	# If no name is supplied, then retrieve the names of all our cookies.
+	return () unless $self->{'.cookies'};
+	return keys %{$self->{'.cookies'}} unless $name;
+	return () unless $self->{'.cookies'}->{$name};
+	return $self->{'.cookies'}->{$name}->value if defined($name) && $name ne '';
+    }
+
+    # If we get here, we're creating a new cookie
+    return undef unless defined($name) && $name ne '';	# this is an error
+
+    my @param;
+    push(@param,'-name'=>$name);
+    push(@param,'-value'=>$value);
+    push(@param,'-domain'=>$domain) if $domain;
+    push(@param,'-path'=>$path) if $path;
+    push(@param,'-expires'=>$expires) if $expires;
+    push(@param,'-secure'=>$secure) if $secure;
+
+    return new CGI::Cookie(@param);
+}
+END_OF_FUNC
+
+'parse_keywordlist' => <<'END_OF_FUNC',
+sub parse_keywordlist {
+    my($self,$tosplit) = @_;
+    $tosplit = unescape($tosplit); # unescape the keywords
+    $tosplit=~tr/+/ /;          # pluses to spaces
+    my(@keywords) = split(/\s+/,$tosplit);
+    return @keywords;
+}
+END_OF_FUNC
+
+'param_fetch' => <<'END_OF_FUNC',
+sub param_fetch {
+    my($self,@p) = self_or_default(@_);
+    my($name) = rearrange([NAME],@p);
+    unless (exists($self->{$name})) {
+	$self->add_parameter($name);
+	$self->{$name} = [];
+    }
+    
+    return $self->{$name};
+}
+END_OF_FUNC
+
+###############################################
+# OTHER INFORMATION PROVIDED BY THE ENVIRONMENT
+###############################################
+
+#### Method: path_info
+# Return the extra virtual path information provided
+# after the URL (if any)
+####
+'path_info' => <<'END_OF_FUNC',
+sub path_info {
+    my ($self,$info) = self_or_default(@_);
+    if (defined($info)) {
+	$info = "/$info" if $info ne '' &&  substr($info,0,1) ne '/';
+	$self->{'.path_info'} = $info;
+    } elsif (! defined($self->{'.path_info'}) ) {
+	$self->{'.path_info'} = defined($ENV{'PATH_INFO'}) ? 
+	    $ENV{'PATH_INFO'} : '';
+
+	# hack to fix broken path info in IIS
+	$self->{'.path_info'} =~ s/^\Q$ENV{'SCRIPT_NAME'}\E// if $IIS;
+
+    }
+    return $self->{'.path_info'};
+}
+END_OF_FUNC
+
+
+#### Method: request_method
+# Returns 'POST', 'GET', 'PUT' or 'HEAD'
+####
+'request_method' => <<'END_OF_FUNC',
+sub request_method {
+    return $ENV{'REQUEST_METHOD'};
+}
+END_OF_FUNC
+
+#### Method: content_type
+# Returns the content_type string
+####
+'content_type' => <<'END_OF_FUNC',
+sub content_type {
+    return $ENV{'CONTENT_TYPE'};
+}
+END_OF_FUNC
+
+#### Method: path_translated
+# Return the physical path information provided
+# by the URL (if any)
+####
+'path_translated' => <<'END_OF_FUNC',
+sub path_translated {
+    return $ENV{'PATH_TRANSLATED'};
+}
+END_OF_FUNC
+
+
+#### Method: query_string
+# Synthesize a query string from our current
+# parameters
+####
+'query_string' => <<'END_OF_FUNC',
+sub query_string {
+    my($self) = self_or_default(@_);
+    my($param,$value,@pairs);
+    foreach $param ($self->param) {
+	my($eparam) = escape($param);
+	foreach $value ($self->param($param)) {
+	    $value = escape($value);
+            next unless defined $value;
+	    push(@pairs,"$eparam=$value");
+	}
+    }
+    foreach (keys %{$self->{'.fieldnames'}}) {
+      push(@pairs,".cgifields=".escape("$_"));
+    }
+    return join($USE_PARAM_SEMICOLONS ? ';' : '&',@pairs);
+}
+END_OF_FUNC
+
+
+#### Method: accept
+# Without parameters, returns an array of the
+# MIME types the browser accepts.
+# With a single parameter equal to a MIME
+# type, will return undef if the browser won't
+# accept it, 1 if the browser accepts it but
+# doesn't give a preference, or a floating point
+# value between 0.0 and 1.0 if the browser
+# declares a quantitative score for it.
+# This handles MIME type globs correctly.
+####
+'Accept' => <<'END_OF_FUNC',
+sub Accept {
+    my($self,$search) = self_or_CGI(@_);
+    my(%prefs,$type,$pref,$pat);
+    
+    my(@accept) = split(',',$self->http('accept'));
+
+    foreach (@accept) {
+	($pref) = /q=(\d\.\d+|\d+)/;
+	($type) = m#(\S+/[^;]+)#;
+	next unless $type;
+	$prefs{$type}=$pref || 1;
+    }
+
+    return keys %prefs unless $search;
+    
+    # if a search type is provided, we may need to
+    # perform a pattern matching operation.
+    # The MIME types use a glob mechanism, which
+    # is easily translated into a perl pattern match
+
+    # First return the preference for directly supported
+    # types:
+    return $prefs{$search} if $prefs{$search};
+
+    # Didn't get it, so try pattern matching.
+    foreach (keys %prefs) {
+	next unless /\*/;       # not a pattern match
+	($pat = $_) =~ s/([^\w*])/\\$1/g; # escape meta characters
+	$pat =~ s/\*/.*/g; # turn it into a pattern
+	return $prefs{$_} if $search=~/$pat/;
+    }
+}
+END_OF_FUNC
+
+
+#### Method: user_agent
+# If called with no parameters, returns the user agent.
+# If called with one parameter, does a pattern match (case
+# insensitive) on the user agent.
+####
+'user_agent' => <<'END_OF_FUNC',
+sub user_agent {
+    my($self,$match)=self_or_CGI(@_);
+    return $self->http('user_agent') unless $match;
+    return $self->http('user_agent') =~ /$match/i;
+}
+END_OF_FUNC
+
+
+#### Method: raw_cookie
+# Returns the magic cookies for the session.
+# The cookies are not parsed or altered in any way, i.e.
+# cookies are returned exactly as given in the HTTP
+# headers.  If a cookie name is given, only that cookie's
+# value is returned, otherwise the entire raw cookie
+# is returned.
+####
+'raw_cookie' => <<'END_OF_FUNC',
+sub raw_cookie {
+    my($self,$key) = self_or_CGI(@_);
+
+    require CGI::Cookie;
+
+    if (defined($key)) {
+	$self->{'.raw_cookies'} = CGI::Cookie->raw_fetch
+	    unless $self->{'.raw_cookies'};
+
+	return () unless $self->{'.raw_cookies'};
+	return () unless $self->{'.raw_cookies'}->{$key};
+	return $self->{'.raw_cookies'}->{$key};
+    }
+    return $self->http('cookie') || $ENV{'COOKIE'} || '';
+}
+END_OF_FUNC
+
+#### Method: virtual_host
+# Return the name of the virtual_host, which
+# is not always the same as the server
+######
+'virtual_host' => <<'END_OF_FUNC',
+sub virtual_host {
+    my $vh = http('host') || server_name();
+    $vh =~ s/:\d+$//;		# get rid of port number
+    return $vh;
+}
+END_OF_FUNC
+
+#### Method: remote_host
+# Return the name of the remote host, or its IP
+# address if unavailable.  If this variable isn't
+# defined, it returns "localhost" for debugging
+# purposes.
+####
+'remote_host' => <<'END_OF_FUNC',
+sub remote_host {
+    return $ENV{'REMOTE_HOST'} || $ENV{'REMOTE_ADDR'} 
+    || 'localhost';
+}
+END_OF_FUNC
+
+
+#### Method: remote_addr
+# Return the IP addr of the remote host.
+####
+'remote_addr' => <<'END_OF_FUNC',
+sub remote_addr {
+    return $ENV{'REMOTE_ADDR'} || '127.0.0.1';
+}
+END_OF_FUNC
+
+
+#### Method: script_name
+# Return the partial URL to this script for
+# self-referencing scripts.  Also see
+# self_url(), which returns a URL with all state information
+# preserved.
+####
+'script_name' => <<'END_OF_FUNC',
+sub script_name {
+    return $ENV{'SCRIPT_NAME'} if defined($ENV{'SCRIPT_NAME'});
+    # These are for debugging
+    return "/$0" unless $0=~/^\//;
+    return $0;
+}
+END_OF_FUNC
+
+
+#### Method: referer
+# Return the HTTP_REFERER: useful for generating
+# a GO BACK button.
+####
+'referer' => <<'END_OF_FUNC',
+sub referer {
+    my($self) = self_or_CGI(@_);
+    return $self->http('referer');
+}
+END_OF_FUNC
+
+
+#### Method: server_name
+# Return the name of the server
+####
+'server_name' => <<'END_OF_FUNC',
+sub server_name {
+    return $ENV{'SERVER_NAME'} || 'localhost';
+}
+END_OF_FUNC
+
+#### Method: server_software
+# Return the name of the server software
+####
+'server_software' => <<'END_OF_FUNC',
+sub server_software {
+    return $ENV{'SERVER_SOFTWARE'} || 'cmdline';
+}
+END_OF_FUNC
+
+#### Method: server_port
+# Return the tcp/ip port the server is running on
+####
+'server_port' => <<'END_OF_FUNC',
+sub server_port {
+    return $ENV{'SERVER_PORT'} || 80; # for debugging
+}
+END_OF_FUNC
+
+#### Method: server_protocol
+# Return the protocol (usually HTTP/1.0)
+####
+'server_protocol' => <<'END_OF_FUNC',
+sub server_protocol {
+    return $ENV{'SERVER_PROTOCOL'} || 'HTTP/1.0'; # for debugging
+}
+END_OF_FUNC
+
+#### Method: http
+# Return the value of an HTTP variable, or
+# the list of variables if none provided
+####
+'http' => <<'END_OF_FUNC',
+sub http {
+    my ($self,$parameter) = self_or_CGI(@_);
+    return $ENV{$parameter} if $parameter=~/^HTTP/;
+    $parameter =~ tr/-/_/;
+    return $ENV{"HTTP_\U$parameter\E"} if $parameter;
+    my(@p);
+    foreach (keys %ENV) {
+	push(@p,$_) if /^HTTP/;
+    }
+    return @p;
+}
+END_OF_FUNC
+
+#### Method: https
+# Return the value of HTTPS
+####
+'https' => <<'END_OF_FUNC',
+sub https {
+    local($^W)=0;
+    my ($self,$parameter) = self_or_CGI(@_);
+    return $ENV{HTTPS} unless $parameter;
+    return $ENV{$parameter} if $parameter=~/^HTTPS/;
+    $parameter =~ tr/-/_/;
+    return $ENV{"HTTPS_\U$parameter\E"} if $parameter;
+    my(@p);
+    foreach (keys %ENV) {
+	push(@p,$_) if /^HTTPS/;
+    }
+    return @p;
+}
+END_OF_FUNC
+
+#### Method: protocol
+# Return the protocol (http or https currently)
+####
+'protocol' => <<'END_OF_FUNC',
+sub protocol {
+    local($^W)=0;
+    my $self = shift;
+    return 'https' if uc($self->https()) eq 'ON'; 
+    return 'https' if $self->server_port == 443;
+    my $prot = $self->server_protocol;
+    my($protocol,$version) = split('/',$prot);
+    return "\L$protocol\E";
+}
+END_OF_FUNC
+
+#### Method: remote_ident
+# Return the identity of the remote user
+# (but only if his host is running identd)
+####
+'remote_ident' => <<'END_OF_FUNC',
+sub remote_ident {
+    return $ENV{'REMOTE_IDENT'};
+}
+END_OF_FUNC
+
+
+#### Method: auth_type
+# Return the type of use verification/authorization in use, if any.
+####
+'auth_type' => <<'END_OF_FUNC',
+sub auth_type {
+    return $ENV{'AUTH_TYPE'};
+}
+END_OF_FUNC
+
+
+#### Method: remote_user
+# Return the authorization name used for user
+# verification.
+####
+'remote_user' => <<'END_OF_FUNC',
+sub remote_user {
+    return $ENV{'REMOTE_USER'};
+}
+END_OF_FUNC
+
+
+#### Method: user_name
+# Try to return the remote user's name by hook or by
+# crook
+####
+'user_name' => <<'END_OF_FUNC',
+sub user_name {
+    my ($self) = self_or_CGI(@_);
+    return $self->http('from') || $ENV{'REMOTE_IDENT'} || $ENV{'REMOTE_USER'};
+}
+END_OF_FUNC
+
+#### Method: nosticky
+# Set or return the NOSTICKY global flag
+####
+'nosticky' => <<'END_OF_FUNC',
+sub nosticky {
+    my ($self,$param) = self_or_CGI(@_);
+    $CGI::NOSTICKY = $param if defined($param);
+    return $CGI::NOSTICKY;
+}
+END_OF_FUNC
+
+#### Method: nph
+# Set or return the NPH global flag
+####
+'nph' => <<'END_OF_FUNC',
+sub nph {
+    my ($self,$param) = self_or_CGI(@_);
+    $CGI::NPH = $param if defined($param);
+    return $CGI::NPH;
+}
+END_OF_FUNC
+
+#### Method: private_tempfiles
+# Set or return the private_tempfiles global flag
+####
+'private_tempfiles' => <<'END_OF_FUNC',
+sub private_tempfiles {
+    my ($self,$param) = self_or_CGI(@_);
+    $CGI::PRIVATE_TEMPFILES = $param if defined($param);
+    return $CGI::PRIVATE_TEMPFILES;
+}
+END_OF_FUNC
+
+#### Method: default_dtd
+# Set or return the default_dtd global
+####
+'default_dtd' => <<'END_OF_FUNC',
+sub default_dtd {
+    my ($self,$param,$param2) = self_or_CGI(@_);
+    if (defined $param2 && defined $param) {
+        $CGI::DEFAULT_DTD = [ $param, $param2 ];
+    } elsif (defined $param) {
+        $CGI::DEFAULT_DTD = $param;
+    }
+    return $CGI::DEFAULT_DTD;
+}
+END_OF_FUNC
+
+# -------------- really private subroutines -----------------
+'previous_or_default' => <<'END_OF_FUNC',
+sub previous_or_default {
+    my($self,$name,$defaults,$override) = @_;
+    my(%selected);
+
+    if (!$override && ($self->{'.fieldnames'}->{$name} || 
+		       defined($self->param($name)) ) ) {
+	grep($selected{$_}++,$self->param($name));
+    } elsif (defined($defaults) && ref($defaults) && 
+	     (ref($defaults) eq 'ARRAY')) {
+	grep($selected{$_}++,@{$defaults});
+    } else {
+	$selected{$defaults}++ if defined($defaults);
+    }
+
+    return %selected;
+}
+END_OF_FUNC
+
+'register_parameter' => <<'END_OF_FUNC',
+sub register_parameter {
+    my($self,$param) = @_;
+    $self->{'.parametersToAdd'}->{$param}++;
+}
+END_OF_FUNC
+
+'get_fields' => <<'END_OF_FUNC',
+sub get_fields {
+    my($self) = @_;
+    return $self->CGI::hidden('-name'=>'.cgifields',
+			      '-values'=>[keys %{$self->{'.parametersToAdd'}}],
+			      '-override'=>1);
+}
+END_OF_FUNC
+
+'read_from_cmdline' => <<'END_OF_FUNC',
+sub read_from_cmdline {
+    my($input,@words);
+    my($query_string);
+    if ($DEBUG && @ARGV) {
+	@words = @ARGV;
+    } elsif ($DEBUG > 1) {
+	require "shellwords.pl";
+	print STDERR "(offline mode: enter name=value pairs on standard input)\n";
+	chomp(@lines = <STDIN>); # remove newlines
+	$input = join(" ",@lines);
+	@words = &shellwords($input);    
+    }
+    foreach (@words) {
+	s/\\=/%3D/g;
+	s/\\&/%26/g;	    
+    }
+
+    if ("@words"=~/=/) {
+	$query_string = join('&',@words);
+    } else {
+	$query_string = join('+',@words);
+    }
+    return $query_string;
+}
+END_OF_FUNC
+
+#####
+# subroutine: read_multipart
+#
+# Read multipart data and store it into our parameters.
+# An interesting feature is that if any of the parts is a file, we
+# create a temporary file and open up a filehandle on it so that the
+# caller can read from it if necessary.
+#####
+'read_multipart' => <<'END_OF_FUNC',
+sub read_multipart {
+    my($self,$boundary,$length,$filehandle) = @_;
+
+    my($buffer) = $self->new_MultipartBuffer($boundary,$length,$filehandle);
+    return unless $buffer;
+    my(%header,$body);
+    my $filenumber = 0;
+    while (!$buffer->eof) {
+	%header = $buffer->readHeader;
+
+	unless (%header) {
+	    $self->cgi_error("400 Bad request (malformed multipart POST)");
+	    return;
+	}
+
+	my($param)= $header{'Content-Disposition'}=~/ name="?([^\";]*)"?/;
+
+	# Bug:  Netscape doesn't escape quotation marks in file names!!!
+	my($filename) = $header{'Content-Disposition'}=~/ filename="?([^\"]*)"?/;
+
+	# add this parameter to our list
+	$self->add_parameter($param);
+
+	# If no filename specified, then just read the data and assign it
+	# to our parameter list.
+	if ( !defined($filename) || $filename eq '' ) {
+	    my($value) = $buffer->readBody;
+	    push(@{$self->{$param}},$value);
+	    next;
+	}
+
+	my ($tmpfile,$tmp,$filehandle);
+      UPLOADS: {
+	  # If we get here, then we are dealing with a potentially large
+	  # uploaded form.  Save the data to a temporary file, then open
+	  # the file for reading.
+
+	  # skip the file if uploads disabled
+	  if ($DISABLE_UPLOADS) {
+	      while (defined($data = $buffer->read)) { }
+	      last UPLOADS;
+	  }
+
+	  # choose a relatively unpredictable tmpfile sequence number
+          my $seqno = unpack("%16C*",join('',localtime,values %ENV));
+          for (my $cnt=10;$cnt>0;$cnt--) {
+	    next unless $tmpfile = new CGITempFile($seqno);
+	    $tmp = $tmpfile->as_string;
+	    last if defined($filehandle = Fh->new($filename,$tmp,$PRIVATE_TEMPFILES));
+            $seqno += int rand(100);
+          }
+          die "CGI open of tmpfile: $!\n" unless defined $filehandle;
+	  $CGI::DefaultClass->binmode($filehandle) if $CGI::needs_binmode;
+
+	  my ($data);
+	  local($\) = '';
+	  while (defined($data = $buffer->read)) {
+	      print $filehandle $data;
+	  }
+
+	  # back up to beginning of file
+	  seek($filehandle,0,0);
+	  $CGI::DefaultClass->binmode($filehandle) if $CGI::needs_binmode;
+
+	  # Save some information about the uploaded file where we can get
+	  # at it later.
+	  $self->{'.tmpfiles'}->{fileno($filehandle)}= {
+	      name => $tmpfile,
+	      info => {%header},
+	  };
+	  push(@{$self->{$param}},$filehandle);
+      }
+    }
+}
+END_OF_FUNC
+
+'upload' =><<'END_OF_FUNC',
+sub upload {
+    my($self,$param_name) = self_or_default(@_);
+    my @param = grep(ref && fileno($_), $self->param($param_name));
+    return unless @param;
+    return wantarray ? @param : $param[0];
+}
+END_OF_FUNC
+
+'tmpFileName' => <<'END_OF_FUNC',
+sub tmpFileName {
+    my($self,$filename) = self_or_default(@_);
+    return $self->{'.tmpfiles'}->{fileno($filename)}->{name} ?
+	$self->{'.tmpfiles'}->{fileno($filename)}->{name}->as_string
+	    : '';
+}
+END_OF_FUNC
+
+'uploadInfo' => <<'END_OF_FUNC',
+sub uploadInfo {
+    my($self,$filename) = self_or_default(@_);
+    return $self->{'.tmpfiles'}->{fileno($filename)}->{info};
+}
+END_OF_FUNC
+
+# internal routine, don't use
+'_set_values_and_labels' => <<'END_OF_FUNC',
+sub _set_values_and_labels {
+    my $self = shift;
+    my ($v,$l,$n) = @_;
+    $$l = $v if ref($v) eq 'HASH' && !ref($$l);
+    return $self->param($n) if !defined($v);
+    return $v if !ref($v);
+    return ref($v) eq 'HASH' ? keys %$v : @$v;
+}
+END_OF_FUNC
+
+'_compile_all' => <<'END_OF_FUNC',
+sub _compile_all {
+    foreach (@_) {
+	next if defined(&$_);
+	$AUTOLOAD = "CGI::$_";
+	_compile();
+    }
+}
+END_OF_FUNC
+
+);
+END_OF_AUTOLOAD
+;
+
+#########################################################
+# Globals and stubs for other packages that we use.
+#########################################################
+
+################### Fh -- lightweight filehandle ###############
+package Fh;
+use overload 
+    '""'  => \&asString,
+    'cmp' => \&compare,
+    'fallback'=>1;
+
+$FH='fh00000';
+
+*Fh::AUTOLOAD = \&CGI::AUTOLOAD;
+
+$AUTOLOADED_ROUTINES = '';      # prevent -w error
+$AUTOLOADED_ROUTINES=<<'END_OF_AUTOLOAD';
+%SUBS =  (
+'asString' => <<'END_OF_FUNC',
+sub asString {
+    my $self = shift;
+    # get rid of package name
+    (my $i = $$self) =~ s/^\*(\w+::fh\d{5})+//; 
+    $i =~ s/%(..)/ chr(hex($1)) /eg;
+    return $i;
+# BEGIN DEAD CODE
+# This was an extremely clever patch that allowed "use strict refs".
+# Unfortunately it relied on another bug that caused leaky file descriptors.
+# The underlying bug has been fixed, so this no longer works.  However
+# "strict refs" still works for some reason.
+#    my $self = shift;
+#    return ${*{$self}{SCALAR}};
+# END DEAD CODE
+}
+END_OF_FUNC
+
+'compare' => <<'END_OF_FUNC',
+sub compare {
+    my $self = shift;
+    my $value = shift;
+    return "$self" cmp $value;
+}
+END_OF_FUNC
+
+'new'  => <<'END_OF_FUNC',
+sub new {
+    my($pack,$name,$file,$delete) = @_;
+    require Fcntl unless defined &Fcntl::O_RDWR;
+    (my $safename = $name) =~ s/([':%])/ sprintf '%%%02X', ord $1 /eg;
+    my $fv = ++$FH . $safename;
+    my $ref = \*{"Fh::$fv"};
+    sysopen($ref,$file,Fcntl::O_RDWR()|Fcntl::O_CREAT()|Fcntl::O_EXCL(),0600) || return;
+    unlink($file) if $delete;
+    CORE::delete $Fh::{$fv};
+    return bless $ref,$pack;
+}
+END_OF_FUNC
+
+'DESTROY'  => <<'END_OF_FUNC',
+sub DESTROY {
+    my $self = shift;
+    close $self;
+}
+END_OF_FUNC
+
+);
+END_OF_AUTOLOAD
+
+######################## MultipartBuffer ####################
+package MultipartBuffer;
+
+# how many bytes to read at a time.  We use
+# a 4K buffer by default.
+$INITIAL_FILLUNIT = 1024 * 4;
+$TIMEOUT = 240*60;       # 4 hour timeout for big files
+$SPIN_LOOP_MAX = 2000;  # bug fix for some Netscape servers
+$CRLF=$CGI::CRLF;
+
+#reuse the autoload function
+*MultipartBuffer::AUTOLOAD = \&CGI::AUTOLOAD;
+
+# avoid autoloader warnings
+sub DESTROY {}
+
+###############################################################################
+################# THESE FUNCTIONS ARE AUTOLOADED ON DEMAND ####################
+###############################################################################
+$AUTOLOADED_ROUTINES = '';      # prevent -w error
+$AUTOLOADED_ROUTINES=<<'END_OF_AUTOLOAD';
+%SUBS =  (
+
+'new' => <<'END_OF_FUNC',
+sub new {
+    my($package,$interface,$boundary,$length,$filehandle) = @_;
+    $FILLUNIT = $INITIAL_FILLUNIT;
+    my $IN;
+    if ($filehandle) {
+	my($package) = caller;
+	# force into caller's package if necessary
+	$IN = $filehandle=~/[':]/ ? $filehandle : "$package\:\:$filehandle"; 
+    }
+    $IN = "main::STDIN" unless $IN;
+
+    $CGI::DefaultClass->binmode($IN) if $CGI::needs_binmode;
+    
+    # If the user types garbage into the file upload field,
+    # then Netscape passes NOTHING to the server (not good).
+    # We may hang on this read in that case. So we implement
+    # a read timeout.  If nothing is ready to read
+    # by then, we return.
+
+    # Netscape seems to be a little bit unreliable
+    # about providing boundary strings.
+    my $boundary_read = 0;
+    if ($boundary) {
+
+	# Under the MIME spec, the boundary consists of the 
+	# characters "--" PLUS the Boundary string
+
+	# BUG: IE 3.01 on the Macintosh uses just the boundary -- not
+	# the two extra hyphens.  We do a special case here on the user-agent!!!!
+	$boundary = "--$boundary" unless CGI::user_agent('MSIE\s+3\.0[12];\s*Mac|DreamPassport');
+
+    } else { # otherwise we find it ourselves
+	my($old);
+	($old,$/) = ($/,$CRLF); # read a CRLF-delimited line
+	$boundary = <$IN>;      # BUG: This won't work correctly under mod_perl
+	$length -= length($boundary);
+	chomp($boundary);               # remove the CRLF
+	$/ = $old;                      # restore old line separator
+        $boundary_read++;
+    }
+
+    my $self = {LENGTH=>$length,
+		BOUNDARY=>$boundary,
+		IN=>$IN,
+		INTERFACE=>$interface,
+		BUFFER=>'',
+	    };
+
+    $FILLUNIT = length($boundary)
+	if length($boundary) > $FILLUNIT;
+
+    my $retval = bless $self,ref $package || $package;
+
+    # Read the preamble and the topmost (boundary) line plus the CRLF.
+    unless ($boundary_read) {
+      while ($self->read(0)) { }
+    }
+    die "Malformed multipart POST\n" if $self->eof;
+
+    return $retval;
+}
+END_OF_FUNC
+
+'readHeader' => <<'END_OF_FUNC',
+sub readHeader {
+    my($self) = @_;
+    my($end);
+    my($ok) = 0;
+    my($bad) = 0;
+
+    local($CRLF) = "\015\012" if $CGI::OS eq 'VMS';
+
+    do {
+	$self->fillBuffer($FILLUNIT);
+	$ok++ if ($end = index($self->{BUFFER},"${CRLF}${CRLF}")) >= 0;
+	$ok++ if $self->{BUFFER} eq '';
+	$bad++ if !$ok && $self->{LENGTH} <= 0;
+	# this was a bad idea
+	# $FILLUNIT *= 2 if length($self->{BUFFER}) >= $FILLUNIT; 
+    } until $ok || $bad;
+    return () if $bad;
+
+    my($header) = substr($self->{BUFFER},0,$end+2);
+    substr($self->{BUFFER},0,$end+4) = '';
+    my %return;
+
+    
+    # See RFC 2045 Appendix A and RFC 822 sections 3.4.8
+    #   (Folding Long Header Fields), 3.4.3 (Comments)
+    #   and 3.4.5 (Quoted-Strings).
+
+    my $token = '[-\w!\#$%&\'*+.^_\`|{}~]';
+    $header=~s/$CRLF\s+/ /og;		# merge continuation lines
+    while ($header=~/($token+):\s+([^$CRLF]*)/mgox) {
+	my ($field_name,$field_value) = ($1,$2); # avoid taintedness
+	$field_name =~ s/\b(\w)/uc($1)/eg; #canonicalize
+	$return{$field_name}=$field_value;
+    }
+    return %return;
+}
+END_OF_FUNC
+
+# This reads and returns the body as a single scalar value.
+'readBody' => <<'END_OF_FUNC',
+sub readBody {
+    my($self) = @_;
+    my($data);
+    my($returnval)='';
+    while (defined($data = $self->read)) {
+	$returnval .= $data;
+    }
+    return $returnval;
+}
+END_OF_FUNC
+
+# This will read $bytes or until the boundary is hit, whichever happens
+# first.  After the boundary is hit, we return undef.  The next read will
+# skip over the boundary and begin reading again;
+'read' => <<'END_OF_FUNC',
+sub read {
+    my($self,$bytes) = @_;
+
+    # default number of bytes to read
+    $bytes = $bytes || $FILLUNIT;       
+
+    # Fill up our internal buffer in such a way that the boundary
+    # is never split between reads.
+    $self->fillBuffer($bytes);
+
+    # Find the boundary in the buffer (it may not be there).
+    my $start = index($self->{BUFFER},$self->{BOUNDARY});
+    # protect against malformed multipart POST operations
+    die "Malformed multipart POST\n" unless ($start >= 0) || ($self->{LENGTH} > 0);
+
+    # If the boundary begins the data, then skip past it
+    # and return undef.
+    if ($start == 0) {
+
+	# clear us out completely if we've hit the last boundary.
+	if (index($self->{BUFFER},"$self->{BOUNDARY}--")==0) {
+	    $self->{BUFFER}='';
+	    $self->{LENGTH}=0;
+	    return undef;
+	}
+
+	# just remove the boundary.
+	substr($self->{BUFFER},0,length($self->{BOUNDARY}))='';
+        $self->{BUFFER} =~ s/^\012\015?//;
+	return undef;
+    }
+
+    my $bytesToReturn;    
+    if ($start > 0) {           # read up to the boundary
+	$bytesToReturn = $start > $bytes ? $bytes : $start;
+    } else {    # read the requested number of bytes
+	# leave enough bytes in the buffer to allow us to read
+	# the boundary.  Thanks to Kevin Hendrick for finding
+	# this one.
+	$bytesToReturn = $bytes - (length($self->{BOUNDARY})+1);
+    }
+
+    my $returnval=substr($self->{BUFFER},0,$bytesToReturn);
+    substr($self->{BUFFER},0,$bytesToReturn)='';
+    
+    # If we hit the boundary, remove the CRLF from the end.
+    return (($start > 0) && ($start <= $bytes)) 
+           ? substr($returnval,0,-2) : $returnval;
+}
+END_OF_FUNC
+
+
+# This fills up our internal buffer in such a way that the
+# boundary is never split between reads
+'fillBuffer' => <<'END_OF_FUNC',
+sub fillBuffer {
+    my($self,$bytes) = @_;
+    return unless $self->{LENGTH};
+
+    my($boundaryLength) = length($self->{BOUNDARY});
+    my($bufferLength) = length($self->{BUFFER});
+    my($bytesToRead) = $bytes - $bufferLength + $boundaryLength + 2;
+    $bytesToRead = $self->{LENGTH} if $self->{LENGTH} < $bytesToRead;
+
+    # Try to read some data.  We may hang here if the browser is screwed up.  
+    my $bytesRead = $self->{INTERFACE}->read_from_client($self->{IN},
+							 \$self->{BUFFER},
+							 $bytesToRead,
+							 $bufferLength);
+    $self->{BUFFER} = '' unless defined $self->{BUFFER};
+
+    # An apparent bug in the Apache server causes the read()
+    # to return zero bytes repeatedly without blocking if the
+    # remote user aborts during a file transfer.  I don't know how
+    # they manage this, but the workaround is to abort if we get
+    # more than SPIN_LOOP_MAX consecutive zero reads.
+    if ($bytesRead == 0) {
+	die  "CGI.pm: Server closed socket during multipart read (client aborted?).\n"
+	    if ($self->{ZERO_LOOP_COUNTER}++ >= $SPIN_LOOP_MAX);
+    } else {
+	$self->{ZERO_LOOP_COUNTER}=0;
+    }
+
+    $self->{LENGTH} -= $bytesRead;
+}
+END_OF_FUNC
+
+
+# Return true when we've finished reading
+'eof' => <<'END_OF_FUNC'
+sub eof {
+    my($self) = @_;
+    return 1 if (length($self->{BUFFER}) == 0)
+		 && ($self->{LENGTH} <= 0);
+    undef;
+}
+END_OF_FUNC
+
+);
+END_OF_AUTOLOAD
+
+####################################################################################
+################################## TEMPORARY FILES #################################
+####################################################################################
+package CGITempFile;
+
+$SL = $CGI::SL;
+$MAC = $CGI::OS eq 'MACINTOSH';
+my ($vol) = $MAC ? MacPerl::Volumes() =~ /:(.*)/ : "";
+unless ($TMPDIRECTORY) {
+    @TEMP=("${SL}usr${SL}tmp","${SL}var${SL}tmp",
+	   "C:${SL}temp","${SL}tmp","${SL}temp",
+	   "${vol}${SL}Temporary Items",
+           "${SL}WWW_ROOT", "${SL}SYS\$SCRATCH",
+	   "C:${SL}system${SL}temp");
+    unshift(@TEMP,$ENV{'TMPDIR'}) if exists $ENV{'TMPDIR'};
+
+    # this feature was supposed to provide per-user tmpfiles, but
+    # it is problematic.
+    #    unshift(@TEMP,(getpwuid($<))[7].'/tmp') if $CGI::OS eq 'UNIX';
+    # Rob: getpwuid() is unfortunately UNIX specific. On brain dead OS'es this
+    #    : can generate a 'getpwuid() not implemented' exception, even though
+    #    : it's never called.  Found under DOS/Win with the DJGPP perl port.
+    #    : Refer to getpwuid() only at run-time if we're fortunate and have  UNIX.
+    # unshift(@TEMP,(eval {(getpwuid($>))[7]}).'/tmp') if $CGI::OS eq 'UNIX' and $> != 0;
+
+    foreach (@TEMP) {
+	do {$TMPDIRECTORY = $_; last} if -d $_ && -w _;
+    }
+}
+
+$TMPDIRECTORY  = $MAC ? "" : "." unless $TMPDIRECTORY;
+$MAXTRIES = 5000;
+
+# cute feature, but overload implementation broke it
+# %OVERLOAD = ('""'=>'as_string');
+*CGITempFile::AUTOLOAD = \&CGI::AUTOLOAD;
+
+sub DESTROY {
+    my($self) = @_;
+    unlink $$self;              # get rid of the file
+}
+
+###############################################################################
+################# THESE FUNCTIONS ARE AUTOLOADED ON DEMAND ####################
+###############################################################################
+$AUTOLOADED_ROUTINES = '';      # prevent -w error
+$AUTOLOADED_ROUTINES=<<'END_OF_AUTOLOAD';
+%SUBS = (
+
+'new' => <<'END_OF_FUNC',
+sub new {
+    my($package,$sequence) = @_;
+    my $filename;
+    for (my $i = 0; $i < $MAXTRIES; $i++) {
+	last if ! -f ($filename = sprintf("${TMPDIRECTORY}${SL}CGItemp%d",$sequence++));
+    }
+    # untaint the darn thing
+    return unless $filename =~ m!^([a-zA-Z0-9_ '":/.\$\\-]+)$!;
+    $filename = $1;
+    return bless \$filename;
+}
+END_OF_FUNC
+
+'as_string' => <<'END_OF_FUNC'
+sub as_string {
+    my($self) = @_;
+    return $$self;
+}
+END_OF_FUNC
+
+);
+END_OF_AUTOLOAD
+
+package CGI;
+
+# We get a whole bunch of warnings about "possibly uninitialized variables"
+# when running with the -w switch.  Touch them all once to get rid of the
+# warnings.  This is ugly and I hate it.
+if ($^W) {
+    $CGI::CGI = '';
+    $CGI::CGI=<<EOF;
+    $CGI::VERSION;
+    $MultipartBuffer::SPIN_LOOP_MAX;
+    $MultipartBuffer::CRLF;
+    $MultipartBuffer::TIMEOUT;
+    $MultipartBuffer::INITIAL_FILLUNIT;
+EOF
+    ;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+CGI - Simple Common Gateway Interface Class
+
+=head1 SYNOPSIS
+
+  # CGI script that creates a fill-out form
+  # and echoes back its values.
+
+  use CGI qw/:standard/;
+  print header,
+        start_html('A Simple Example'),
+        h1('A Simple Example'),
+        start_form,
+        "What's your name? ",textfield('name'),p,
+        "What's the combination?", p,
+        checkbox_group(-name=>'words',
+		       -values=>['eenie','meenie','minie','moe'],
+		       -defaults=>['eenie','minie']), p,
+        "What's your favorite color? ",
+        popup_menu(-name=>'color',
+	           -values=>['red','green','blue','chartreuse']),p,
+        submit,
+        end_form,
+        hr;
+
+   if (param()) {
+       print "Your name is",em(param('name')),p,
+	     "The keywords are: ",em(join(", ",param('words'))),p,
+	     "Your favorite color is ",em(param('color')),
+	     hr;
+   }
+
+=head1 ABSTRACT
+
+This perl library uses perl5 objects to make it easy to create Web
+fill-out forms and parse their contents.  This package defines CGI
+objects, entities that contain the values of the current query string
+and other state variables.  Using a CGI object's methods, you can
+examine keywords and parameters passed to your script, and create
+forms whose initial values are taken from the current query (thereby
+preserving state information).  The module provides shortcut functions
+that produce boilerplate HTML, reducing typing and coding errors. It
+also provides functionality for some of the more advanced features of
+CGI scripting, including support for file uploads, cookies, cascading
+style sheets, server push, and frames.
+
+CGI.pm also provides a simple function-oriented programming style for
+those who don't need its object-oriented features.
+
+The current version of CGI.pm is available at
+
+  http://www.genome.wi.mit.edu/ftp/pub/software/WWW/cgi_docs.html
+  ftp://ftp-genome.wi.mit.edu/pub/software/WWW/
+
+=head1 DESCRIPTION
+
+=head2 PROGRAMMING STYLE
+
+There are two styles of programming with CGI.pm, an object-oriented
+style and a function-oriented style.  In the object-oriented style you
+create one or more CGI objects and then use object methods to create
+the various elements of the page.  Each CGI object starts out with the
+list of named parameters that were passed to your CGI script by the
+server.  You can modify the objects, save them to a file or database
+and recreate them.  Because each object corresponds to the "state" of
+the CGI script, and because each object's parameter list is
+independent of the others, this allows you to save the state of the
+script and restore it later.
+
+For example, using the object oriented style, here is how you create
+a simple "Hello World" HTML page:
+
+   #!/usr/local/bin/perl -w
+   use CGI;                             # load CGI routines
+   $q = new CGI;                        # create new CGI object
+   print $q->header,                    # create the HTTP header
+         $q->start_html('hello world'), # start the HTML
+         $q->h1('hello world'),         # level 1 header
+         $q->end_html;                  # end the HTML
+
+In the function-oriented style, there is one default CGI object that
+you rarely deal with directly.  Instead you just call functions to
+retrieve CGI parameters, create HTML tags, manage cookies, and so
+on.  This provides you with a cleaner programming interface, but
+limits you to using one CGI object at a time.  The following example
+prints the same page, but uses the function-oriented interface.
+The main differences are that we now need to import a set of functions
+into our name space (usually the "standard" functions), and we don't
+need to create the CGI object.
+
+   #!/usr/local/bin/perl
+   use CGI qw/:standard/;           # load standard CGI routines
+   print header,                    # create the HTTP header
+         start_html('hello world'), # start the HTML
+         h1('hello world'),         # level 1 header
+         end_html;                  # end the HTML
+
+The examples in this document mainly use the object-oriented style.
+See HOW TO IMPORT FUNCTIONS for important information on
+function-oriented programming in CGI.pm
+
+=head2 CALLING CGI.PM ROUTINES
+
+Most CGI.pm routines accept several arguments, sometimes as many as 20
+optional ones!  To simplify this interface, all routines use a named
+argument calling style that looks like this:
+
+   print $q->header(-type=>'image/gif',-expires=>'+3d');
+
+Each argument name is preceded by a dash.  Neither case nor order
+matters in the argument list.  -type, -Type, and -TYPE are all
+acceptable.  In fact, only the first argument needs to begin with a
+dash.  If a dash is present in the first argument, CGI.pm assumes
+dashes for the subsequent ones.
+
+Several routines are commonly called with just one argument.  In the
+case of these routines you can provide the single argument without an
+argument name.  header() happens to be one of these routines.  In this
+case, the single argument is the document type.
+
+   print $q->header('text/html');
+
+Other such routines are documented below.
+
+Sometimes named arguments expect a scalar, sometimes a reference to an
+array, and sometimes a reference to a hash.  Often, you can pass any
+type of argument and the routine will do whatever is most appropriate.
+For example, the param() routine is used to set a CGI parameter to a
+single or a multi-valued value.  The two cases are shown below:
+
+   $q->param(-name=>'veggie',-value=>'tomato');
+   $q->param(-name=>'veggie',-value=>['tomato','tomahto','potato','potahto']);
+
+A large number of routines in CGI.pm actually aren't specifically
+defined in the module, but are generated automatically as needed.
+These are the "HTML shortcuts," routines that generate HTML tags for
+use in dynamically-generated pages.  HTML tags have both attributes
+(the attribute="value" pairs within the tag itself) and contents (the
+part between the opening and closing pairs.)  To distinguish between
+attributes and contents, CGI.pm uses the convention of passing HTML
+attributes as a hash reference as the first argument, and the
+contents, if any, as any subsequent arguments.  It works out like
+this:
+
+   Code                           Generated HTML
+   ----                           --------------
+   h1()                           <h1>
+   h1('some','contents');         <h1>some contents</h1>
+   h1({-align=>left});            <h1 ALIGN="LEFT">
+   h1({-align=>left},'contents'); <h1 ALIGN="LEFT">contents</h1>
+
+HTML tags are described in more detail later.  
+
+Many newcomers to CGI.pm are puzzled by the difference between the
+calling conventions for the HTML shortcuts, which require curly braces
+around the HTML tag attributes, and the calling conventions for other
+routines, which manage to generate attributes without the curly
+brackets.  Don't be confused.  As a convenience the curly braces are
+optional in all but the HTML shortcuts.  If you like, you can use
+curly braces when calling any routine that takes named arguments.  For
+example:
+
+   print $q->header( {-type=>'image/gif',-expires=>'+3d'} );
+
+If you use the B<-w> switch, you will be warned that some CGI.pm argument
+names conflict with built-in Perl functions.  The most frequent of
+these is the -values argument, used to create multi-valued menus,
+radio button clusters and the like.  To get around this warning, you
+have several choices:
+
+=over 4
+
+=item 1.
+
+Use another name for the argument, if one is available. 
+For example, -value is an alias for -values.
+
+=item 2.
+
+Change the capitalization, e.g. -Values
+
+=item 3.
+
+Put quotes around the argument name, e.g. '-values'
+
+=back
+
+Many routines will do something useful with a named argument that it
+doesn't recognize.  For example, you can produce non-standard HTTP
+header fields by providing them as named arguments:
+
+  print $q->header(-type  =>  'text/html',
+                   -cost  =>  'Three smackers',
+                   -annoyance_level => 'high',
+                   -complaints_to   => 'bit bucket');
+
+This will produce the following nonstandard HTTP header:
+
+   HTTP/1.0 200 OK
+   Cost: Three smackers
+   Annoyance-level: high
+   Complaints-to: bit bucket
+   Content-type: text/html
+
+Notice the way that underscores are translated automatically into
+hyphens.  HTML-generating routines perform a different type of
+translation. 
+
+This feature allows you to keep up with the rapidly changing HTTP and
+HTML "standards".
+
+=head2 CREATING A NEW QUERY OBJECT (OBJECT-ORIENTED STYLE):
+
+     $query = new CGI;
+
+This will parse the input (from both POST and GET methods) and store
+it into a perl5 object called $query.  
+
+=head2 CREATING A NEW QUERY OBJECT FROM AN INPUT FILE
+
+     $query = new CGI(INPUTFILE);
+
+If you provide a file handle to the new() method, it will read
+parameters from the file (or STDIN, or whatever).  The file can be in
+any of the forms describing below under debugging (i.e. a series of
+newline delimited TAG=VALUE pairs will work).  Conveniently, this type
+of file is created by the save() method (see below).  Multiple records
+can be saved and restored.
+
+Perl purists will be pleased to know that this syntax accepts
+references to file handles, or even references to filehandle globs,
+which is the "official" way to pass a filehandle:
+
+    $query = new CGI(\*STDIN);
+
+You can also initialize the CGI object with a FileHandle or IO::File
+object.
+
+If you are using the function-oriented interface and want to
+initialize CGI state from a file handle, the way to do this is with
+B<restore_parameters()>.  This will (re)initialize the
+default CGI object from the indicated file handle.
+
+    open (IN,"test.in") || die;
+    restore_parameters(IN);
+    close IN;
+
+You can also initialize the query object from an associative array
+reference:
+
+    $query = new CGI( {'dinosaur'=>'barney',
+		       'song'=>'I love you',
+		       'friends'=>[qw/Jessica George Nancy/]}
+		    );
+
+or from a properly formatted, URL-escaped query string:
+
+    $query = new CGI('dinosaur=barney&color=purple');
+
+or from a previously existing CGI object (currently this clones the
+parameter list, but none of the other object-specific fields, such as
+autoescaping):
+
+    $old_query = new CGI;
+    $new_query = new CGI($old_query);
+
+To create an empty query, initialize it from an empty string or hash:
+
+   $empty_query = new CGI("");
+
+       -or-
+
+   $empty_query = new CGI({});
+
+=head2 FETCHING A LIST OF KEYWORDS FROM THE QUERY:
+
+     @keywords = $query->keywords
+
+If the script was invoked as the result of an <ISINDEX> search, the
+parsed keywords can be obtained as an array using the keywords() method.
+
+=head2 FETCHING THE NAMES OF ALL THE PARAMETERS PASSED TO YOUR SCRIPT:
+
+     @names = $query->param
+
+If the script was invoked with a parameter list
+(e.g. "name1=value1&name2=value2&name3=value3"), the param() method
+will return the parameter names as a list.  If the script was invoked
+as an <ISINDEX> script and contains a string without ampersands
+(e.g. "value1+value2+value3") , there will be a single parameter named
+"keywords" containing the "+"-delimited keywords.
+
+NOTE: As of version 1.5, the array of parameter names returned will
+be in the same order as they were submitted by the browser.
+Usually this order is the same as the order in which the 
+parameters are defined in the form (however, this isn't part
+of the spec, and so isn't guaranteed).
+
+=head2 FETCHING THE VALUE OR VALUES OF A SINGLE NAMED PARAMETER:
+
+    @values = $query->param('foo');
+
+	      -or-
+
+    $value = $query->param('foo');
+
+Pass the param() method a single argument to fetch the value of the
+named parameter. If the parameter is multivalued (e.g. from multiple
+selections in a scrolling list), you can ask to receive an array.  Otherwise
+the method will return a single value.
+
+If a value is not given in the query string, as in the queries
+"name1=&name2=" or "name1&name2", it will be returned as an empty
+string.  This feature is new in 2.63.
+
+=head2 SETTING THE VALUE(S) OF A NAMED PARAMETER:
+
+    $query->param('foo','an','array','of','values');
+
+This sets the value for the named parameter 'foo' to an array of
+values.  This is one way to change the value of a field AFTER
+the script has been invoked once before.  (Another way is with
+the -override parameter accepted by all methods that generate
+form elements.)
+
+param() also recognizes a named parameter style of calling described
+in more detail later:
+
+    $query->param(-name=>'foo',-values=>['an','array','of','values']);
+
+			      -or-
+
+    $query->param(-name=>'foo',-value=>'the value');
+
+=head2 APPENDING ADDITIONAL VALUES TO A NAMED PARAMETER:
+
+   $query->append(-name=>'foo',-values=>['yet','more','values']);
+
+This adds a value or list of values to the named parameter.  The
+values are appended to the end of the parameter if it already exists.
+Otherwise the parameter is created.  Note that this method only
+recognizes the named argument calling syntax.
+
+=head2 IMPORTING ALL PARAMETERS INTO A NAMESPACE:
+
+   $query->import_names('R');
+
+This creates a series of variables in the 'R' namespace.  For example,
+$R::foo, @R:foo.  For keyword lists, a variable @R::keywords will appear.
+If no namespace is given, this method will assume 'Q'.
+WARNING:  don't import anything into 'main'; this is a major security
+risk!!!!
+
+In older versions, this method was called B<import()>.  As of version 2.20, 
+this name has been removed completely to avoid conflict with the built-in
+Perl module B<import> operator.
+
+=head2 DELETING A PARAMETER COMPLETELY:
+
+    $query->delete('foo');
+
+This completely clears a parameter.  It sometimes useful for
+resetting parameters that you don't want passed down between
+script invocations.
+
+If you are using the function call interface, use "Delete()" instead
+to avoid conflicts with Perl's built-in delete operator.
+
+=head2 DELETING ALL PARAMETERS:
+
+   $query->delete_all();
+
+This clears the CGI object completely.  It might be useful to ensure
+that all the defaults are taken when you create a fill-out form.
+
+Use Delete_all() instead if you are using the function call interface.
+
+=head2 DIRECT ACCESS TO THE PARAMETER LIST:
+
+   $q->param_fetch('address')->[1] = '1313 Mockingbird Lane';
+   unshift @{$q->param_fetch(-name=>'address')},'George Munster';
+
+If you need access to the parameter list in a way that isn't covered
+by the methods above, you can obtain a direct reference to it by
+calling the B<param_fetch()> method with the name of the .  This
+will return an array reference to the named parameters, which you then
+can manipulate in any way you like.
+
+You can also use a named argument style using the B<-name> argument.
+
+=head2 FETCHING THE PARAMETER LIST AS A HASH:
+
+    $params = $q->Vars;
+    print $params->{'address'};
+    @foo = split("\0",$params->{'foo'});
+    %params = $q->Vars;
+
+    use CGI ':cgi-lib';
+    $params = Vars;
+
+Many people want to fetch the entire parameter list as a hash in which
+the keys are the names of the CGI parameters, and the values are the
+parameters' values.  The Vars() method does this.  Called in a scalar
+context, it returns the parameter list as a tied hash reference.
+Changing a key changes the value of the parameter in the underlying
+CGI parameter list.  Called in a list context, it returns the
+parameter list as an ordinary hash.  This allows you to read the
+contents of the parameter list, but not to change it.
+
+When using this, the thing you must watch out for are multivalued CGI
+parameters.  Because a hash cannot distinguish between scalar and
+list context, multivalued parameters will be returned as a packed
+string, separated by the "\0" (null) character.  You must split this
+packed string in order to get at the individual values.  This is the
+convention introduced long ago by Steve Brenner in his cgi-lib.pl
+module for Perl version 4.
+
+If you wish to use Vars() as a function, import the I<:cgi-lib> set of
+function calls (also see the section on CGI-LIB compatibility).
+
+=head2 SAVING THE STATE OF THE SCRIPT TO A FILE:
+
+    $query->save(FILEHANDLE)
+
+This will write the current state of the form to the provided
+filehandle.  You can read it back in by providing a filehandle
+to the new() method.  Note that the filehandle can be a file, a pipe,
+or whatever!
+
+The format of the saved file is:
+
+	NAME1=VALUE1
+	NAME1=VALUE1'
+	NAME2=VALUE2
+	NAME3=VALUE3
+	=
+
+Both name and value are URL escaped.  Multi-valued CGI parameters are
+represented as repeated names.  A session record is delimited by a
+single = symbol.  You can write out multiple records and read them
+back in with several calls to B<new>.  You can do this across several
+sessions by opening the file in append mode, allowing you to create
+primitive guest books, or to keep a history of users' queries.  Here's
+a short example of creating multiple session records:
+
+   use CGI;
+
+   open (OUT,">>test.out") || die;
+   $records = 5;
+   foreach (0..$records) {
+       my $q = new CGI;
+       $q->param(-name=>'counter',-value=>$_);
+       $q->save(OUT);
+   }
+   close OUT;
+
+   # reopen for reading
+   open (IN,"test.out") || die;
+   while (!eof(IN)) {
+       my $q = new CGI(IN);
+       print $q->param('counter'),"\n";
+   }
+
+The file format used for save/restore is identical to that used by the
+Whitehead Genome Center's data exchange format "Boulderio", and can be
+manipulated and even databased using Boulderio utilities.  See
+
+  http://stein.cshl.org/boulder/
+
+for further details.
+
+If you wish to use this method from the function-oriented (non-OO)
+interface, the exported name for this method is B<save_parameters()>.
+
+=head2 RETRIEVING CGI ERRORS
+
+Errors can occur while processing user input, particularly when
+processing uploaded files.  When these errors occur, CGI will stop
+processing and return an empty parameter list.  You can test for
+the existence and nature of errors using the I<cgi_error()> function.
+The error messages are formatted as HTTP status codes. You can either
+incorporate the error text into an HTML page, or use it as the value
+of the HTTP status:
+
+    my $error = $q->cgi_error;
+    if ($error) {
+	print $q->header(-status=>$error),
+	      $q->start_html('Problems'),
+              $q->h2('Request not processed'),
+	      $q->strong($error);
+        exit 0;
+    }
+
+When using the function-oriented interface (see the next section),
+errors may only occur the first time you call I<param()>. Be ready
+for this!
+
+=head2 USING THE FUNCTION-ORIENTED INTERFACE
+
+To use the function-oriented interface, you must specify which CGI.pm
+routines or sets of routines to import into your script's namespace.
+There is a small overhead associated with this importation, but it
+isn't much.
+
+   use CGI <list of methods>;
+
+The listed methods will be imported into the current package; you can
+call them directly without creating a CGI object first.  This example
+shows how to import the B<param()> and B<header()>
+methods, and then use them directly:
+
+   use CGI 'param','header';
+   print header('text/plain');
+   $zipcode = param('zipcode');
+
+More frequently, you'll import common sets of functions by referring
+to the groups by name.  All function sets are preceded with a ":"
+character as in ":html3" (for tags defined in the HTML 3 standard).
+
+Here is a list of the function sets you can import:
+
+=over 4
+
+=item B<:cgi>
+
+Import all CGI-handling methods, such as B<param()>, B<path_info()>
+and the like.
+
+=item B<:form>
+
+Import all fill-out form generating methods, such as B<textfield()>.
+
+=item B<:html2>
+
+Import all methods that generate HTML 2.0 standard elements.
+
+=item B<:html3>
+
+Import all methods that generate HTML 3.0 elements (such as
+<table>, <super> and <sub>).
+
+=item B<:html4>
+
+Import all methods that generate HTML 4 elements (such as
+<abbrev>, <acronym> and <thead>).
+
+=item B<:netscape>
+
+Import all methods that generate Netscape-specific HTML extensions.
+
+=item B<:html>
+
+Import all HTML-generating shortcuts (i.e. 'html2' + 'html3' +
+'netscape')...
+
+=item B<:standard>
+
+Import "standard" features, 'html2', 'html3', 'html4', 'form' and 'cgi'.
+
+=item B<:all>
+
+Import all the available methods.  For the full list, see the CGI.pm
+code, where the variable %EXPORT_TAGS is defined.
+
+=back
+
+If you import a function name that is not part of CGI.pm, the module
+will treat it as a new HTML tag and generate the appropriate
+subroutine.  You can then use it like any other HTML tag.  This is to
+provide for the rapidly-evolving HTML "standard."  For example, say
+Microsoft comes out with a new tag called <gradient> (which causes the
+user's desktop to be flooded with a rotating gradient fill until his
+machine reboots).  You don't need to wait for a new version of CGI.pm
+to start using it immediately:
+
+   use CGI qw/:standard :html3 gradient/;
+   print gradient({-start=>'red',-end=>'blue'});
+
+Note that in the interests of execution speed CGI.pm does B<not> use
+the standard L<Exporter> syntax for specifying load symbols.  This may
+change in the future.
+
+If you import any of the state-maintaining CGI or form-generating
+methods, a default CGI object will be created and initialized
+automatically the first time you use any of the methods that require
+one to be present.  This includes B<param()>, B<textfield()>,
+B<submit()> and the like.  (If you need direct access to the CGI
+object, you can find it in the global variable B<$CGI::Q>).  By
+importing CGI.pm methods, you can create visually elegant scripts:
+
+   use CGI qw/:standard/;
+   print 
+       header,
+       start_html('Simple Script'),
+       h1('Simple Script'),
+       start_form,
+       "What's your name? ",textfield('name'),p,
+       "What's the combination?",
+       checkbox_group(-name=>'words',
+		      -values=>['eenie','meenie','minie','moe'],
+		      -defaults=>['eenie','moe']),p,
+       "What's your favorite color?",
+       popup_menu(-name=>'color',
+		  -values=>['red','green','blue','chartreuse']),p,
+       submit,
+       end_form,
+       hr,"\n";
+
+    if (param) {
+       print 
+	   "Your name is ",em(param('name')),p,
+	   "The keywords are: ",em(join(", ",param('words'))),p,
+	   "Your favorite color is ",em(param('color')),".\n";
+    }
+    print end_html;
+
+=head2 PRAGMAS
+
+In addition to the function sets, there are a number of pragmas that
+you can import.  Pragmas, which are always preceded by a hyphen,
+change the way that CGI.pm functions in various ways.  Pragmas,
+function sets, and individual functions can all be imported in the
+same use() line.  For example, the following use statement imports the
+standard set of functions and enables debugging mode (pragma
+-debug):
+
+   use CGI qw/:standard -debug/;
+
+The current list of pragmas is as follows:
+
+=over 4
+
+=item -any
+
+When you I<use CGI -any>, then any method that the query object
+doesn't recognize will be interpreted as a new HTML tag.  This allows
+you to support the next I<ad hoc> Netscape or Microsoft HTML
+extension.  This lets you go wild with new and unsupported tags:
+
+   use CGI qw(-any);
+   $q=new CGI;
+   print $q->gradient({speed=>'fast',start=>'red',end=>'blue'});
+
+Since using <cite>any</cite> causes any mistyped method name
+to be interpreted as an HTML tag, use it with care or not at
+all.
+
+=item -compile
+
+This causes the indicated autoloaded methods to be compiled up front,
+rather than deferred to later.  This is useful for scripts that run
+for an extended period of time under FastCGI or mod_perl, and for
+those destined to be crunched by Malcom Beattie's Perl compiler.  Use
+it in conjunction with the methods or method families you plan to use.
+
+   use CGI qw(-compile :standard :html3);
+
+or even
+
+   use CGI qw(-compile :all);
+
+Note that using the -compile pragma in this way will always have
+the effect of importing the compiled functions into the current
+namespace.  If you want to compile without importing use the
+compile() method instead (see below).
+
+=item -nosticky
+
+This makes CGI.pm not generating the hidden fields .submit
+and .cgifields. It is very useful if you don't want to
+have the hidden fields appear in the querystring in a GET method.
+For example, a search script generated this way will have
+a very nice url with search parameters for bookmarking.
+
+=item -no_undef_params
+
+This keeps CGI.pm from including undef params in the parameter list.
+
+=item -no_xhtml
+
+By default, CGI.pm versions 2.69 and higher emit XHTML
+(http://www.w3.org/TR/xhtml1/).  The -no_xhtml pragma disables this
+feature.  Thanks to Michalis Kabrianis <kabrianis@hellug.gr> for this
+feature.
+
+=item -nph
+
+This makes CGI.pm produce a header appropriate for an NPH (no
+parsed header) script.  You may need to do other things as well
+to tell the server that the script is NPH.  See the discussion
+of NPH scripts below.
+
+=item -newstyle_urls
+
+Separate the name=value pairs in CGI parameter query strings with
+semicolons rather than ampersands.  For example:
+
+   ?name=fred;age=24;favorite_color=3
+
+Semicolon-delimited query strings are always accepted, but will not be
+emitted by self_url() and query_string() unless the -newstyle_urls
+pragma is specified.
+
+This became the default in version 2.64.
+
+=item -oldstyle_urls
+
+Separate the name=value pairs in CGI parameter query strings with
+ampersands rather than semicolons.  This is no longer the default.
+
+=item -autoload
+
+This overrides the autoloader so that any function in your program
+that is not recognized is referred to CGI.pm for possible evaluation.
+This allows you to use all the CGI.pm functions without adding them to
+your symbol table, which is of concern for mod_perl users who are
+worried about memory consumption.  I<Warning:> when
+I<-autoload> is in effect, you cannot use "poetry mode"
+(functions without the parenthesis).  Use I<hr()> rather
+than I<hr>, or add something like I<use subs qw/hr p header/> 
+to the top of your script.
+
+=item -no_debug
+
+This turns off the command-line processing features.  If you want to
+run a CGI.pm script from the command line to produce HTML, and you
+don't want it to read CGI parameters from the command line or STDIN,
+then use this pragma:
+
+   use CGI qw(-no_debug :standard);
+
+=item -debug
+
+This turns on full debugging.  In addition to reading CGI arguments
+from the command-line processing, CGI.pm will pause and try to read
+arguments from STDIN, producing the message "(offline mode: enter
+name=value pairs on standard input)" features.
+
+See the section on debugging for more details.
+
+=item -private_tempfiles
+
+CGI.pm can process uploaded file. Ordinarily it spools the uploaded
+file to a temporary directory, then deletes the file when done.
+However, this opens the risk of eavesdropping as described in the file
+upload section.  Another CGI script author could peek at this data
+during the upload, even if it is confidential information. On Unix
+systems, the -private_tempfiles pragma will cause the temporary file
+to be unlinked as soon as it is opened and before any data is written
+into it, reducing, but not eliminating the risk of eavesdropping
+(there is still a potential race condition).  To make life harder for
+the attacker, the program chooses tempfile names by calculating a 32
+bit checksum of the incoming HTTP headers.
+
+To ensure that the temporary file cannot be read by other CGI scripts,
+use suEXEC or a CGI wrapper program to run your script.  The temporary
+file is created with mode 0600 (neither world nor group readable).
+
+The temporary directory is selected using the following algorithm:
+
+    1. if the current user (e.g. "nobody") has a directory named
+    "tmp" in its home directory, use that (Unix systems only).
+
+    2. if the environment variable TMPDIR exists, use the location
+    indicated.
+
+    3. Otherwise try the locations /usr/tmp, /var/tmp, C:\temp,
+    /tmp, /temp, ::Temporary Items, and \WWW_ROOT.
+
+Each of these locations is checked that it is a directory and is
+writable.  If not, the algorithm tries the next choice.
+
+=back
+
+=head2 SPECIAL FORMS FOR IMPORTING HTML-TAG FUNCTIONS
+
+Many of the methods generate HTML tags.  As described below, tag
+functions automatically generate both the opening and closing tags.
+For example:
+
+  print h1('Level 1 Header');
+
+produces
+
+  <h1>Level 1 Header</h1>
+
+There will be some times when you want to produce the start and end
+tags yourself.  In this case, you can use the form start_I<tag_name>
+and end_I<tag_name>, as in:
+
+  print start_h1,'Level 1 Header',end_h1;
+
+With a few exceptions (described below), start_I<tag_name> and
+end_I<tag_name> functions are not generated automatically when you
+I<use CGI>.  However, you can specify the tags you want to generate
+I<start/end> functions for by putting an asterisk in front of their
+name, or, alternatively, requesting either "start_I<tag_name>" or
+"end_I<tag_name>" in the import list.
+
+Example:
+
+  use CGI qw/:standard *table start_ul/;
+
+In this example, the following functions are generated in addition to
+the standard ones:
+
+=over 4
+
+=item 1. start_table() (generates a <table> tag)
+
+=item 2. end_table() (generates a </table> tag)
+
+=item 3. start_ul() (generates a <ul> tag)
+
+=item 4. end_ul() (generates a </ul> tag)
+
+=back
+
+=head1 GENERATING DYNAMIC DOCUMENTS
+
+Most of CGI.pm's functions deal with creating documents on the fly.
+Generally you will produce the HTTP header first, followed by the
+document itself.  CGI.pm provides functions for generating HTTP
+headers of various types as well as for generating HTML.  For creating
+GIF images, see the GD.pm module.
+
+Each of these functions produces a fragment of HTML or HTTP which you
+can print out directly so that it displays in the browser window,
+append to a string, or save to a file for later use.
+
+=head2 CREATING A STANDARD HTTP HEADER:
+
+Normally the first thing you will do in any CGI script is print out an
+HTTP header.  This tells the browser what type of document to expect,
+and gives other optional information, such as the language, expiration
+date, and whether to cache the document.  The header can also be
+manipulated for special purposes, such as server push and pay per view
+pages.
+
+	print $query->header;
+
+	     -or-
+
+	print $query->header('image/gif');
+
+	     -or-
+
+	print $query->header('text/html','204 No response');
+
+	     -or-
+
+	print $query->header(-type=>'image/gif',
+			     -nph=>1,
+			     -status=>'402 Payment required',
+			     -expires=>'+3d',
+			     -cookie=>$cookie,
+                             -charset=>'utf-7',
+                             -attachment=>'foo.gif',
+			     -Cost=>'$2.00');
+
+header() returns the Content-type: header.  You can provide your own
+MIME type if you choose, otherwise it defaults to text/html.  An
+optional second parameter specifies the status code and a human-readable
+message.  For example, you can specify 204, "No response" to create a
+script that tells the browser to do nothing at all.
+
+The last example shows the named argument style for passing arguments
+to the CGI methods using named parameters.  Recognized parameters are
+B<-type>, B<-status>, B<-expires>, and B<-cookie>.  Any other named
+parameters will be stripped of their initial hyphens and turned into
+header fields, allowing you to specify any HTTP header you desire.
+Internal underscores will be turned into hyphens:
+
+    print $query->header(-Content_length=>3002);
+
+Most browsers will not cache the output from CGI scripts.  Every time
+the browser reloads the page, the script is invoked anew.  You can
+change this behavior with the B<-expires> parameter.  When you specify
+an absolute or relative expiration interval with this parameter, some
+browsers and proxy servers will cache the script's output until the
+indicated expiration date.  The following forms are all valid for the
+-expires field:
+
+	+30s                              30 seconds from now
+	+10m                              ten minutes from now
+	+1h                               one hour from now
+	-1d                               yesterday (i.e. "ASAP!")
+	now                               immediately
+	+3M                               in three months
+	+10y                              in ten years time
+	Thursday, 25-Apr-1999 00:40:33 GMT  at the indicated time & date
+
+The B<-cookie> parameter generates a header that tells the browser to provide
+a "magic cookie" during all subsequent transactions with your script.
+Netscape cookies have a special format that includes interesting attributes
+such as expiration time.  Use the cookie() method to create and retrieve
+session cookies.
+
+The B<-nph> parameter, if set to a true value, will issue the correct
+headers to work with an NPH (no-parse-header) script.  This is important
+to use with certain servers that expect all their scripts to be NPH.
+
+The B<-charset> parameter can be used to control the character set
+sent to the browser.  If not provided, defaults to ISO-8859-1.  As a
+side effect, this sets the charset() method as well.
+
+The B<-attachment> parameter can be used to turn the page into an
+attachment.  Instead of displaying the page, some browsers will prompt
+the user to save it to disk.  The value of the argument is the
+suggested name for the saved file.  In order for this to work, you may
+have to set the B<-type> to "application/octet-stream".
+
+=head2 GENERATING A REDIRECTION HEADER
+
+   print $query->redirect('http://somewhere.else/in/movie/land');
+
+Sometimes you don't want to produce a document yourself, but simply
+redirect the browser elsewhere, perhaps choosing a URL based on the
+time of day or the identity of the user.  
+
+The redirect() function redirects the browser to a different URL.  If
+you use redirection like this, you should B<not> print out a header as
+well.
+
+One hint I can offer is that relative links may not work correctly
+when you generate a redirection to another document on your site.
+This is due to a well-intentioned optimization that some servers use.
+The solution to this is to use the full URL (including the http: part)
+of the document you are redirecting to.
+
+You can also use named arguments:
+
+    print $query->redirect(-uri=>'http://somewhere.else/in/movie/land',
+			   -nph=>1);
+
+The B<-nph> parameter, if set to a true value, will issue the correct
+headers to work with an NPH (no-parse-header) script.  This is important
+to use with certain servers, such as Microsoft Internet Explorer, which
+expect all their scripts to be NPH.
+
+=head2 CREATING THE HTML DOCUMENT HEADER
+
+   print $query->start_html(-title=>'Secrets of the Pyramids',
+			    -author=>'fred@capricorn.org',
+			    -base=>'true',
+			    -target=>'_blank',
+			    -meta=>{'keywords'=>'pharaoh secret mummy',
+				    'copyright'=>'copyright 1996 King Tut'},
+			    -style=>{'src'=>'/styles/style1.css'},
+			    -BGCOLOR=>'blue');
+
+After creating the HTTP header, most CGI scripts will start writing
+out an HTML document.  The start_html() routine creates the top of the
+page, along with a lot of optional information that controls the
+page's appearance and behavior.
+
+This method returns a canned HTML header and the opening <body> tag.
+All parameters are optional.  In the named parameter form, recognized
+parameters are -title, -author, -base, -xbase, -dtd, -lang and -target
+(see below for the explanation).  Any additional parameters you
+provide, such as the Netscape unofficial BGCOLOR attribute, are added
+to the <body> tag.  Additional parameters must be proceeded by a
+hyphen.
+
+The argument B<-xbase> allows you to provide an HREF for the <base> tag
+different from the current location, as in
+
+    -xbase=>"http://home.mcom.com/"
+
+All relative links will be interpreted relative to this tag.
+
+The argument B<-target> allows you to provide a default target frame
+for all the links and fill-out forms on the page.  B<This is a
+non-standard HTTP feature which only works with Netscape browsers!>
+See the Netscape documentation on frames for details of how to
+manipulate this.
+
+    -target=>"answer_window"
+
+All relative links will be interpreted relative to this tag.
+You add arbitrary meta information to the header with the B<-meta>
+argument.  This argument expects a reference to an associative array
+containing name/value pairs of meta information.  These will be turned
+into a series of header <meta> tags that look something like this:
+
+    <meta name="keywords" content="pharaoh secret mummy">
+    <meta name="description" content="copyright 1996 King Tut">
+
+To create an HTTP-EQUIV type of <meta> tag, use B<-head>, described
+below.
+
+The B<-style> argument is used to incorporate cascading stylesheets
+into your code.  See the section on CASCADING STYLESHEETS for more
+information.
+
+The B<-lang> argument is used to incorporate a language attribute into
+the <html> tag.  The default if not specified is "en-US" for US
+English.  For example:
+
+    print $q->start_html(-lang=>'fr-CA');
+
+The B<-encoding> argument can be used to specify the character set for
+XHTML.  It defaults to iso-8859-1 if not specified.
+
+You can place other arbitrary HTML elements to the <head> section with the
+B<-head> tag.  For example, to place the rarely-used <link> element in the
+head section, use this:
+
+    print start_html(-head=>Link({-rel=>'next',
+		                  -href=>'http://www.capricorn.com/s2.html'}));
+
+To incorporate multiple HTML elements into the <head> section, just pass an
+array reference:
+
+    print start_html(-head=>[ 
+                             Link({-rel=>'next',
+				   -href=>'http://www.capricorn.com/s2.html'}),
+		             Link({-rel=>'previous',
+				   -href=>'http://www.capricorn.com/s1.html'})
+			     ]
+		     );
+
+And here's how to create an HTTP-EQUIV <meta> tag:
+
+      print start_html(-head=>meta({-http_equiv => 'Content-Type',
+                                    -content    => 'text/html'}))
+
+
+JAVASCRIPTING: The B<-script>, B<-noScript>, B<-onLoad>,
+B<-onMouseOver>, B<-onMouseOut> and B<-onUnload> parameters are used
+to add Netscape JavaScript calls to your pages.  B<-script> should
+point to a block of text containing JavaScript function definitions.
+This block will be placed within a <script> block inside the HTML (not
+HTTP) header.  The block is placed in the header in order to give your
+page a fighting chance of having all its JavaScript functions in place
+even if the user presses the stop button before the page has loaded
+completely.  CGI.pm attempts to format the script in such a way that
+JavaScript-naive browsers will not choke on the code: unfortunately
+there are some browsers, such as Chimera for Unix, that get confused
+by it nevertheless.
+
+The B<-onLoad> and B<-onUnload> parameters point to fragments of JavaScript
+code to execute when the page is respectively opened and closed by the
+browser.  Usually these parameters are calls to functions defined in the
+B<-script> field:
+
+      $query = new CGI;
+      print $query->header;
+      $JSCRIPT=<<END;
+      // Ask a silly question
+      function riddle_me_this() {
+	 var r = prompt("What walks on four legs in the morning, " +
+		       "two legs in the afternoon, " +
+		       "and three legs in the evening?");
+	 response(r);
+      }
+      // Get a silly answer
+      function response(answer) {
+	 if (answer == "man")
+	    alert("Right you are!");
+	 else
+	    alert("Wrong!  Guess again.");
+      }
+      END
+      print $query->start_html(-title=>'The Riddle of the Sphinx',
+			       -script=>$JSCRIPT);
+
+Use the B<-noScript> parameter to pass some HTML text that will be displayed on 
+browsers that do not have JavaScript (or browsers where JavaScript is turned
+off).
+
+Netscape 3.0 recognizes several attributes of the <script> tag,
+including LANGUAGE and SRC.  The latter is particularly interesting,
+as it allows you to keep the JavaScript code in a file or CGI script
+rather than cluttering up each page with the source.  To use these
+attributes pass a HASH reference in the B<-script> parameter containing
+one or more of -language, -src, or -code:
+
+    print $q->start_html(-title=>'The Riddle of the Sphinx',
+			 -script=>{-language=>'JAVASCRIPT',
+                                   -src=>'/javascript/sphinx.js'}
+			 );
+
+    print $q->(-title=>'The Riddle of the Sphinx',
+	       -script=>{-language=>'PERLSCRIPT',
+			 -code=>'print "hello world!\n;"'}
+	       );
+
+
+A final feature allows you to incorporate multiple <script> sections into the
+header.  Just pass the list of script sections as an array reference.
+this allows you to specify different source files for different dialects
+of JavaScript.  Example:     
+
+     print $q->start_html(-title=>'The Riddle of the Sphinx',
+                          -script=>[
+                                    { -language => 'JavaScript1.0',
+                                      -src      => '/javascript/utilities10.js'
+                                    },
+                                    { -language => 'JavaScript1.1',
+                                      -src      => '/javascript/utilities11.js'
+                                    },
+                                    { -language => 'JavaScript1.2',
+                                      -src      => '/javascript/utilities12.js'
+                                    },
+                                    { -language => 'JavaScript28.2',
+                                      -src      => '/javascript/utilities219.js'
+                                    }
+                                 ]
+                             );
+
+If this looks a bit extreme, take my advice and stick with straight CGI scripting.  
+
+See
+
+   http://home.netscape.com/eng/mozilla/2.0/handbook/javascript/
+
+for more information about JavaScript.
+
+The old-style positional parameters are as follows:
+
+=over 4
+
+=item B<Parameters:>
+
+=item 1.
+
+The title
+
+=item 2.
+
+The author's e-mail address (will create a <link rev="MADE"> tag if present
+
+=item 3.
+
+A 'true' flag if you want to include a <base> tag in the header.  This
+helps resolve relative addresses to absolute ones when the document is moved, 
+but makes the document hierarchy non-portable.  Use with care!
+
+=item 4, 5, 6...
+
+Any other parameters you want to include in the <body> tag.  This is a good
+place to put Netscape extensions, such as colors and wallpaper patterns.
+
+=back
+
+=head2 ENDING THE HTML DOCUMENT:
+
+	print $query->end_html
+
+This ends an HTML document by printing the </body></html> tags.
+
+=head2 CREATING A SELF-REFERENCING URL THAT PRESERVES STATE INFORMATION:
+
+    $myself = $query->self_url;
+    print q(<a href="$myself">I'm talking to myself.</a>);
+
+self_url() will return a URL, that, when selected, will reinvoke
+this script with all its state information intact.  This is most
+useful when you want to jump around within the document using
+internal anchors but you don't want to disrupt the current contents
+of the form(s).  Something like this will do the trick.
+
+     $myself = $query->self_url;
+     print "<a href=$myself#table1>See table 1</a>";
+     print "<a href=$myself#table2>See table 2</a>";
+     print "<a href=$myself#yourself>See for yourself</a>";
+
+If you want more control over what's returned, using the B<url()>
+method instead.
+
+You can also retrieve the unprocessed query string with query_string():
+
+    $the_string = $query->query_string;
+
+=head2 OBTAINING THE SCRIPT'S URL
+
+    $full_url      = $query->url();
+    $full_url      = $query->url(-full=>1);  #alternative syntax
+    $relative_url  = $query->url(-relative=>1);
+    $absolute_url  = $query->url(-absolute=>1);
+    $url_with_path = $query->url(-path_info=>1);
+    $url_with_path_and_query = $query->url(-path_info=>1,-query=>1);
+    $netloc        = $query->url(-base => 1);
+
+B<url()> returns the script's URL in a variety of formats.  Called
+without any arguments, it returns the full form of the URL, including
+host name and port number
+
+    http://your.host.com/path/to/script.cgi
+
+You can modify this format with the following named arguments:
+
+=over 4
+
+=item B<-absolute>
+
+If true, produce an absolute URL, e.g.
+
+    /path/to/script.cgi
+
+=item B<-relative>
+
+Produce a relative URL.  This is useful if you want to reinvoke your
+script with different parameters. For example:
+
+    script.cgi
+
+=item B<-full>
+
+Produce the full URL, exactly as if called without any arguments.
+This overrides the -relative and -absolute arguments.
+
+=item B<-path> (B<-path_info>)
+
+Append the additional path information to the URL.  This can be
+combined with B<-full>, B<-absolute> or B<-relative>.  B<-path_info>
+is provided as a synonym.
+
+=item B<-query> (B<-query_string>)
+
+Append the query string to the URL.  This can be combined with
+B<-full>, B<-absolute> or B<-relative>.  B<-query_string> is provided
+as a synonym.
+
+=item B<-base>
+
+Generate just the protocol and net location, as in http://www.foo.com:8000
+
+=back
+
+=head2 MIXING POST AND URL PARAMETERS
+
+   $color = $query-&gt;url_param('color');
+
+It is possible for a script to receive CGI parameters in the URL as
+well as in the fill-out form by creating a form that POSTs to a URL
+containing a query string (a "?" mark followed by arguments).  The
+B<param()> method will always return the contents of the POSTed
+fill-out form, ignoring the URL's query string.  To retrieve URL
+parameters, call the B<url_param()> method.  Use it in the same way as
+B<param()>.  The main difference is that it allows you to read the
+parameters, but not set them.
+
+
+Under no circumstances will the contents of the URL query string
+interfere with similarly-named CGI parameters in POSTed forms.  If you
+try to mix a URL query string with a form submitted with the GET
+method, the results will not be what you expect.
+
+=head1 CREATING STANDARD HTML ELEMENTS:
+
+CGI.pm defines general HTML shortcut methods for most, if not all of
+the HTML 3 and HTML 4 tags.  HTML shortcuts are named after a single
+HTML element and return a fragment of HTML text that you can then
+print or manipulate as you like.  Each shortcut returns a fragment of
+HTML code that you can append to a string, save to a file, or, most
+commonly, print out so that it displays in the browser window.
+
+This example shows how to use the HTML methods:
+
+   $q = new CGI;
+   print $q->blockquote(
+		     "Many years ago on the island of",
+		     $q->a({href=>"http://crete.org/"},"Crete"),
+		     "there lived a Minotaur named",
+		     $q->strong("Fred."),
+		    ),
+       $q->hr;
+
+This results in the following HTML code (extra newlines have been
+added for readability):
+
+   <blockquote>
+   Many years ago on the island of
+   <a href="http://crete.org/">Crete</a> there lived
+   a minotaur named <strong>Fred.</strong> 
+   </blockquote>
+   <hr>
+
+If you find the syntax for calling the HTML shortcuts awkward, you can
+import them into your namespace and dispense with the object syntax
+completely (see the next section for more details):
+
+   use CGI ':standard';
+   print blockquote(
+      "Many years ago on the island of",
+      a({href=>"http://crete.org/"},"Crete"),
+      "there lived a minotaur named",
+      strong("Fred."),
+      ),
+      hr;
+
+=head2 PROVIDING ARGUMENTS TO HTML SHORTCUTS
+
+The HTML methods will accept zero, one or multiple arguments.  If you
+provide no arguments, you get a single tag:
+
+   print hr;  	#  <hr>
+
+If you provide one or more string arguments, they are concatenated
+together with spaces and placed between opening and closing tags:
+
+   print h1("Chapter","1"); # <h1>Chapter 1</h1>"
+
+If the first argument is an associative array reference, then the keys
+and values of the associative array become the HTML tag's attributes:
+
+   print a({-href=>'fred.html',-target=>'_new'},
+      "Open a new frame");
+
+	    <a href="fred.html",target="_new">Open a new frame</a>
+
+You may dispense with the dashes in front of the attribute names if
+you prefer:
+
+   print img {src=>'fred.gif',align=>'LEFT'};
+
+	   <img align="LEFT" src="fred.gif">
+
+Sometimes an HTML tag attribute has no argument.  For example, ordered
+lists can be marked as COMPACT.  The syntax for this is an argument
+that points to an undef string:
+
+   print ol({compact=>undef},li('one'),li('two'),li('three'));
+
+Prior to CGI.pm version 2.41, providing an empty ('') string as an
+attribute argument was the same as providing undef.  However, this has
+changed in order to accommodate those who want to create tags of the form
+<img alt="">.  The difference is shown in these two pieces of code:
+
+     CODE                   RESULT
+     img({alt=>undef})      <img alt>
+     img({alt=>''})         <img alt="">
+
+=head2 THE DISTRIBUTIVE PROPERTY OF HTML SHORTCUTS
+
+One of the cool features of the HTML shortcuts is that they are
+distributive.  If you give them an argument consisting of a
+B<reference> to a list, the tag will be distributed across each
+element of the list.  For example, here's one way to make an ordered
+list:
+
+   print ul(
+             li({-type=>'disc'},['Sneezy','Doc','Sleepy','Happy'])
+           );
+
+This example will result in HTML output that looks like this:
+
+   <ul>
+     <li type="disc">Sneezy</li>
+     <li type="disc">Doc</li>
+     <li type="disc">Sleepy</li>
+     <li type="disc">Happy</li>
+   </ul>
+
+This is extremely useful for creating tables.  For example:
+
+   print table({-border=>undef},
+           caption('When Should You Eat Your Vegetables?'),
+           Tr({-align=>CENTER,-valign=>TOP},
+           [
+              th(['Vegetable', 'Breakfast','Lunch','Dinner']),
+              td(['Tomatoes' , 'no', 'yes', 'yes']),
+              td(['Broccoli' , 'no', 'no',  'yes']),
+              td(['Onions'   , 'yes','yes', 'yes'])
+           ]
+           )
+        );
+
+=head2 HTML SHORTCUTS AND LIST INTERPOLATION
+
+Consider this bit of code:
+
+   print blockquote(em('Hi'),'mom!'));
+
+It will ordinarily return the string that you probably expect, namely:
+
+   <blockquote><em>Hi</em> mom!</blockquote>
+
+Note the space between the element "Hi" and the element "mom!".
+CGI.pm puts the extra space there using array interpolation, which is
+controlled by the magic $" variable.  Sometimes this extra space is
+not what you want, for example, when you are trying to align a series
+of images.  In this case, you can simply change the value of $" to an
+empty string.
+
+   {
+      local($") = '';
+      print blockquote(em('Hi'),'mom!'));
+    }
+
+I suggest you put the code in a block as shown here.  Otherwise the
+change to $" will affect all subsequent code until you explicitly
+reset it.
+
+=head2 NON-STANDARD HTML SHORTCUTS
+
+A few HTML tags don't follow the standard pattern for various
+reasons.  
+
+B<comment()> generates an HTML comment (<!-- comment -->).  Call it
+like
+
+    print comment('here is my comment');
+
+Because of conflicts with built-in Perl functions, the following functions
+begin with initial caps:
+
+    Select
+    Tr
+    Link
+    Delete
+    Accept
+    Sub
+
+In addition, start_html(), end_html(), start_form(), end_form(),
+start_multipart_form() and all the fill-out form tags are special.
+See their respective sections.
+
+=head2 AUTOESCAPING HTML
+
+By default, all HTML that is emitted by the form-generating functions
+is passed through a function called escapeHTML():
+
+=over 4
+
+=item $escaped_string = escapeHTML("unescaped string");
+
+Escape HTML formatting characters in a string.
+
+=back
+
+Provided that you have specified a character set of ISO-8859-1 (the
+default), the standard HTML escaping rules will be used.  The "<"
+character becomes "&lt;", ">" becomes "&gt;", "&" becomes "&amp;", and
+the quote character becomes "&quot;".  In addition, the hexadecimal
+0x8b and 0x9b characters, which many windows-based browsers interpret
+as the left and right angle-bracket characters, are replaced by their
+numeric HTML entities ("&#139" and "&#155;").  If you manually change
+the charset, either by calling the charset() method explicitly or by
+passing a -charset argument to header(), then B<all> characters will
+be replaced by their numeric entities, since CGI.pm has no lookup
+table for all the possible encodings.
+
+The automatic escaping does not apply to other shortcuts, such as
+h1().  You should call escapeHTML() yourself on untrusted data in
+order to protect your pages against nasty tricks that people may enter
+into guestbooks, etc..  To change the character set, use charset().
+To turn autoescaping off completely, use autoescape():
+
+=over 4
+
+=item $charset = charset([$charset]);
+
+Get or set the current character set.
+
+=item $flag = autoEscape([$flag]);
+
+Get or set the value of the autoescape flag.
+
+=back
+
+=head2 PRETTY-PRINTING HTML
+
+By default, all the HTML produced by these functions comes out as one
+long line without carriage returns or indentation. This is yuck, but
+it does reduce the size of the documents by 10-20%.  To get
+pretty-printed output, please use L<CGI::Pretty>, a subclass
+contributed by Brian Paulsen.
+
+=head1 CREATING FILL-OUT FORMS:
+
+I<General note>  The various form-creating methods all return strings
+to the caller, containing the tag or tags that will create the requested
+form element.  You are responsible for actually printing out these strings.
+It's set up this way so that you can place formatting tags
+around the form elements.
+
+I<Another note> The default values that you specify for the forms are only
+used the B<first> time the script is invoked (when there is no query
+string).  On subsequent invocations of the script (when there is a query
+string), the former values are used even if they are blank.  
+
+If you want to change the value of a field from its previous value, you have two
+choices:
+
+(1) call the param() method to set it.
+
+(2) use the -override (alias -force) parameter (a new feature in version 2.15).
+This forces the default value to be used, regardless of the previous value:
+
+   print $query->textfield(-name=>'field_name',
+			   -default=>'starting value',
+			   -override=>1,
+			   -size=>50,
+			   -maxlength=>80);
+
+I<Yet another note> By default, the text and labels of form elements are
+escaped according to HTML rules.  This means that you can safely use
+"<CLICK ME>" as the label for a button.  However, it also interferes with
+your ability to incorporate special HTML character sequences, such as &Aacute;,
+into your fields.  If you wish to turn off automatic escaping, call the
+autoEscape() method with a false value immediately after creating the CGI object:
+
+   $query = new CGI;
+   $query->autoEscape(undef);
+
+=head2 CREATING AN ISINDEX TAG
+
+   print $query->isindex(-action=>$action);
+
+	 -or-
+
+   print $query->isindex($action);
+
+Prints out an <isindex> tag.  Not very exciting.  The parameter
+-action specifies the URL of the script to process the query.  The
+default is to process the query with the current script.
+
+=head2 STARTING AND ENDING A FORM
+
+    print $query->start_form(-method=>$method,
+			    -action=>$action,
+			    -enctype=>$encoding);
+      <... various form stuff ...>
+    print $query->endform;
+
+	-or-
+
+    print $query->start_form($method,$action,$encoding);
+      <... various form stuff ...>
+    print $query->endform;
+
+start_form() will return a <form> tag with the optional method,
+action and form encoding that you specify.  The defaults are:
+
+    method: POST
+    action: this script
+    enctype: application/x-www-form-urlencoded
+
+endform() returns the closing </form> tag.  
+
+Start_form()'s enctype argument tells the browser how to package the various
+fields of the form before sending the form to the server.  Two
+values are possible:
+
+B<Note:> This method was previously named startform(), and startform()
+is still recognized as an alias.
+
+=over 4
+
+=item B<application/x-www-form-urlencoded>
+
+This is the older type of encoding used by all browsers prior to
+Netscape 2.0.  It is compatible with many CGI scripts and is
+suitable for short fields containing text data.  For your
+convenience, CGI.pm stores the name of this encoding
+type in B<&CGI::URL_ENCODED>.
+
+=item B<multipart/form-data>
+
+This is the newer type of encoding introduced by Netscape 2.0.
+It is suitable for forms that contain very large fields or that
+are intended for transferring binary data.  Most importantly,
+it enables the "file upload" feature of Netscape 2.0 forms.  For
+your convenience, CGI.pm stores the name of this encoding type
+in B<&CGI::MULTIPART>
+
+Forms that use this type of encoding are not easily interpreted
+by CGI scripts unless they use CGI.pm or another library designed
+to handle them.
+
+=back
+
+For compatibility, the start_form() method uses the older form of
+encoding by default.  If you want to use the newer form of encoding
+by default, you can call B<start_multipart_form()> instead of
+B<start_form()>.
+
+JAVASCRIPTING: The B<-name> and B<-onSubmit> parameters are provided
+for use with JavaScript.  The -name parameter gives the
+form a name so that it can be identified and manipulated by
+JavaScript functions.  -onSubmit should point to a JavaScript
+function that will be executed just before the form is submitted to your
+server.  You can use this opportunity to check the contents of the form 
+for consistency and completeness.  If you find something wrong, you
+can put up an alert box or maybe fix things up yourself.  You can 
+abort the submission by returning false from this function.  
+
+Usually the bulk of JavaScript functions are defined in a <script>
+block in the HTML header and -onSubmit points to one of these function
+call.  See start_html() for details.
+
+=head2 CREATING A TEXT FIELD
+
+    print $query->textfield(-name=>'field_name',
+			    -default=>'starting value',
+			    -size=>50,
+			    -maxlength=>80);
+	-or-
+
+    print $query->textfield('field_name','starting value',50,80);
+
+textfield() will return a text input field.  
+
+=over 4
+
+=item B<Parameters>
+
+=item 1.
+
+The first parameter is the required name for the field (-name).  
+
+=item 2.
+
+The optional second parameter is the default starting value for the field
+contents (-default).  
+
+=item 3.
+
+The optional third parameter is the size of the field in
+      characters (-size).
+
+=item 4.
+
+The optional fourth parameter is the maximum number of characters the
+      field will accept (-maxlength).
+
+=back
+
+As with all these methods, the field will be initialized with its 
+previous contents from earlier invocations of the script.
+When the form is processed, the value of the text field can be
+retrieved with:
+
+       $value = $query->param('foo');
+
+If you want to reset it from its initial value after the script has been
+called once, you can do so like this:
+
+       $query->param('foo',"I'm taking over this value!");
+
+NEW AS OF VERSION 2.15: If you don't want the field to take on its previous
+value, you can force its current value by using the -override (alias -force)
+parameter:
+
+    print $query->textfield(-name=>'field_name',
+			    -default=>'starting value',
+			    -override=>1,
+			    -size=>50,
+			    -maxlength=>80);
+
+JAVASCRIPTING: You can also provide B<-onChange>, B<-onFocus>,
+B<-onBlur>, B<-onMouseOver>, B<-onMouseOut> and B<-onSelect>
+parameters to register JavaScript event handlers.  The onChange
+handler will be called whenever the user changes the contents of the
+text field.  You can do text validation if you like.  onFocus and
+onBlur are called respectively when the insertion point moves into and
+out of the text field.  onSelect is called when the user changes the
+portion of the text that is selected.
+
+=head2 CREATING A BIG TEXT FIELD
+
+   print $query->textarea(-name=>'foo',
+			  -default=>'starting value',
+			  -rows=>10,
+			  -columns=>50);
+
+	-or
+
+   print $query->textarea('foo','starting value',10,50);
+
+textarea() is just like textfield, but it allows you to specify
+rows and columns for a multiline text entry box.  You can provide
+a starting value for the field, which can be long and contain
+multiple lines.
+
+JAVASCRIPTING: The B<-onChange>, B<-onFocus>, B<-onBlur> ,
+B<-onMouseOver>, B<-onMouseOut>, and B<-onSelect> parameters are
+recognized.  See textfield().
+
+=head2 CREATING A PASSWORD FIELD
+
+   print $query->password_field(-name=>'secret',
+				-value=>'starting value',
+				-size=>50,
+				-maxlength=>80);
+	-or-
+
+   print $query->password_field('secret','starting value',50,80);
+
+password_field() is identical to textfield(), except that its contents 
+will be starred out on the web page.
+
+JAVASCRIPTING: The B<-onChange>, B<-onFocus>, B<-onBlur>,
+B<-onMouseOver>, B<-onMouseOut> and B<-onSelect> parameters are
+recognized.  See textfield().
+
+=head2 CREATING A FILE UPLOAD FIELD
+
+    print $query->filefield(-name=>'uploaded_file',
+			    -default=>'starting value',
+			    -size=>50,
+			    -maxlength=>80);
+	-or-
+
+    print $query->filefield('uploaded_file','starting value',50,80);
+
+filefield() will return a file upload field for Netscape 2.0 browsers.
+In order to take full advantage of this I<you must use the new 
+multipart encoding scheme> for the form.  You can do this either
+by calling B<start_form()> with an encoding type of B<&CGI::MULTIPART>,
+or by calling the new method B<start_multipart_form()> instead of
+vanilla B<start_form()>.
+
+=over 4
+
+=item B<Parameters>
+
+=item 1.
+
+The first parameter is the required name for the field (-name).  
+
+=item 2.
+
+The optional second parameter is the starting value for the field contents
+to be used as the default file name (-default).
+
+For security reasons, browsers don't pay any attention to this field,
+and so the starting value will always be blank.  Worse, the field
+loses its "sticky" behavior and forgets its previous contents.  The
+starting value field is called for in the HTML specification, however,
+and possibly some browser will eventually provide support for it.
+
+=item 3.
+
+The optional third parameter is the size of the field in
+characters (-size).
+
+=item 4.
+
+The optional fourth parameter is the maximum number of characters the
+field will accept (-maxlength).
+
+=back
+
+When the form is processed, you can retrieve the entered filename
+by calling param():
+
+       $filename = $query->param('uploaded_file');
+
+Different browsers will return slightly different things for the
+name.  Some browsers return the filename only.  Others return the full
+path to the file, using the path conventions of the user's machine.
+Regardless, the name returned is always the name of the file on the
+I<user's> machine, and is unrelated to the name of the temporary file
+that CGI.pm creates during upload spooling (see below).
+
+The filename returned is also a file handle.  You can read the contents
+of the file using standard Perl file reading calls:
+
+	# Read a text file and print it out
+	while (<$filename>) {
+	   print;
+	}
+
+	# Copy a binary file to somewhere safe
+	open (OUTFILE,">>/usr/local/web/users/feedback");
+	while ($bytesread=read($filename,$buffer,1024)) {
+	   print OUTFILE $buffer;
+	}
+
+However, there are problems with the dual nature of the upload fields.
+If you C<use strict>, then Perl will complain when you try to use a
+string as a filehandle.  You can get around this by placing the file
+reading code in a block containing the C<no strict> pragma.  More
+seriously, it is possible for the remote user to type garbage into the
+upload field, in which case what you get from param() is not a
+filehandle at all, but a string.
+
+To be safe, use the I<upload()> function (new in version 2.47).  When
+called with the name of an upload field, I<upload()> returns a
+filehandle, or undef if the parameter is not a valid filehandle.
+
+     $fh = $query->upload('uploaded_file');
+     while (<$fh>) {
+	   print;
+     }
+
+In an array context, upload() will return an array of filehandles.
+This makes it possible to create forms that use the same name for
+multiple upload fields.
+
+This is the recommended idiom.
+
+When a file is uploaded the browser usually sends along some
+information along with it in the format of headers.  The information
+usually includes the MIME content type.  Future browsers may send
+other information as well (such as modification date and size). To
+retrieve this information, call uploadInfo().  It returns a reference to
+an associative array containing all the document headers.
+
+       $filename = $query->param('uploaded_file');
+       $type = $query->uploadInfo($filename)->{'Content-Type'};
+       unless ($type eq 'text/html') {
+	  die "HTML FILES ONLY!";
+       }
+
+If you are using a machine that recognizes "text" and "binary" data
+modes, be sure to understand when and how to use them (see the Camel book).  
+Otherwise you may find that binary files are corrupted during file
+uploads.
+
+There are occasionally problems involving parsing the uploaded file.
+This usually happens when the user presses "Stop" before the upload is
+finished.  In this case, CGI.pm will return undef for the name of the
+uploaded file and set I<cgi_error()> to the string "400 Bad request
+(malformed multipart POST)".  This error message is designed so that
+you can incorporate it into a status code to be sent to the browser.
+Example:
+
+   $file = $query->upload('uploaded_file');
+   if (!$file && $query->cgi_error) {
+      print $query->header(-status=>$query->cgi_error);
+      exit 0;
+   }
+
+You are free to create a custom HTML page to complain about the error,
+if you wish.
+
+If you are using CGI.pm on a Windows platform and find that binary
+files get slightly larger when uploaded but that text files remain the
+same, then you have forgotten to activate binary mode on the output
+filehandle.  Be sure to call binmode() on any handle that you create
+to write the uploaded file to disk.
+
+JAVASCRIPTING: The B<-onChange>, B<-onFocus>, B<-onBlur>,
+B<-onMouseOver>, B<-onMouseOut> and B<-onSelect> parameters are
+recognized.  See textfield() for details.
+
+=head2 CREATING A POPUP MENU
+
+   print $query->popup_menu('menu_name',
+			    ['eenie','meenie','minie'],
+			    'meenie');
+
+      -or-
+
+   %labels = ('eenie'=>'your first choice',
+	      'meenie'=>'your second choice',
+	      'minie'=>'your third choice');
+   print $query->popup_menu('menu_name',
+			    ['eenie','meenie','minie'],
+			    'meenie',\%labels);
+
+	-or (named parameter style)-
+
+   print $query->popup_menu(-name=>'menu_name',
+			    -values=>['eenie','meenie','minie'],
+			    -default=>'meenie',
+			    -labels=>\%labels);
+
+popup_menu() creates a menu.
+
+=over 4
+
+=item 1.
+
+The required first argument is the menu's name (-name).
+
+=item 2.
+
+The required second argument (-values) is an array B<reference>
+containing the list of menu items in the menu.  You can pass the
+method an anonymous array, as shown in the example, or a reference to
+a named array, such as "\@foo".
+
+=item 3.
+
+The optional third parameter (-default) is the name of the default
+menu choice.  If not specified, the first item will be the default.
+The values of the previous choice will be maintained across queries.
+
+=item 4.
+
+The optional fourth parameter (-labels) is provided for people who
+want to use different values for the user-visible label inside the
+popup menu nd the value returned to your script.  It's a pointer to an
+associative array relating menu values to user-visible labels.  If you
+leave this parameter blank, the menu values will be displayed by
+default.  (You can also leave a label undefined if you want to).
+
+=back
+
+When the form is processed, the selected value of the popup menu can
+be retrieved using:
+
+      $popup_menu_value = $query->param('menu_name');
+
+JAVASCRIPTING: popup_menu() recognizes the following event handlers:
+B<-onChange>, B<-onFocus>, B<-onMouseOver>, B<-onMouseOut>, and
+B<-onBlur>.  See the textfield() section for details on when these
+handlers are called.
+
+=head2 CREATING A SCROLLING LIST
+
+   print $query->scrolling_list('list_name',
+				['eenie','meenie','minie','moe'],
+				['eenie','moe'],5,'true');
+      -or-
+
+   print $query->scrolling_list('list_name',
+				['eenie','meenie','minie','moe'],
+				['eenie','moe'],5,'true',
+				\%labels);
+
+	-or-
+
+   print $query->scrolling_list(-name=>'list_name',
+				-values=>['eenie','meenie','minie','moe'],
+				-default=>['eenie','moe'],
+				-size=>5,
+				-multiple=>'true',
+				-labels=>\%labels);
+
+scrolling_list() creates a scrolling list.  
+
+=over 4
+
+=item B<Parameters:>
+
+=item 1.
+
+The first and second arguments are the list name (-name) and values
+(-values).  As in the popup menu, the second argument should be an
+array reference.
+
+=item 2.
+
+The optional third argument (-default) can be either a reference to a
+list containing the values to be selected by default, or can be a
+single value to select.  If this argument is missing or undefined,
+then nothing is selected when the list first appears.  In the named
+parameter version, you can use the synonym "-defaults" for this
+parameter.
+
+=item 3.
+
+The optional fourth argument is the size of the list (-size).
+
+=item 4.
+
+The optional fifth argument can be set to true to allow multiple
+simultaneous selections (-multiple).  Otherwise only one selection
+will be allowed at a time.
+
+=item 5.
+
+The optional sixth argument is a pointer to an associative array
+containing long user-visible labels for the list items (-labels).
+If not provided, the values will be displayed.
+
+When this form is processed, all selected list items will be returned as
+a list under the parameter name 'list_name'.  The values of the
+selected items can be retrieved with:
+
+      @selected = $query->param('list_name');
+
+=back
+
+JAVASCRIPTING: scrolling_list() recognizes the following event
+handlers: B<-onChange>, B<-onFocus>, B<-onMouseOver>, B<-onMouseOut>
+and B<-onBlur>.  See textfield() for the description of when these
+handlers are called.
+
+=head2 CREATING A GROUP OF RELATED CHECKBOXES
+
+   print $query->checkbox_group(-name=>'group_name',
+				-values=>['eenie','meenie','minie','moe'],
+				-default=>['eenie','moe'],
+				-linebreak=>'true',
+				-labels=>\%labels);
+
+   print $query->checkbox_group('group_name',
+				['eenie','meenie','minie','moe'],
+				['eenie','moe'],'true',\%labels);
+
+   HTML3-COMPATIBLE BROWSERS ONLY:
+
+   print $query->checkbox_group(-name=>'group_name',
+				-values=>['eenie','meenie','minie','moe'],
+				-rows=2,-columns=>2);
+
+
+checkbox_group() creates a list of checkboxes that are related
+by the same name.
+
+=over 4
+
+=item B<Parameters:>
+
+=item 1.
+
+The first and second arguments are the checkbox name and values,
+respectively (-name and -values).  As in the popup menu, the second
+argument should be an array reference.  These values are used for the
+user-readable labels printed next to the checkboxes as well as for the
+values passed to your script in the query string.
+
+=item 2.
+
+The optional third argument (-default) can be either a reference to a
+list containing the values to be checked by default, or can be a
+single value to checked.  If this argument is missing or undefined,
+then nothing is selected when the list first appears.
+
+=item 3.
+
+The optional fourth argument (-linebreak) can be set to true to place
+line breaks between the checkboxes so that they appear as a vertical
+list.  Otherwise, they will be strung together on a horizontal line.
+
+=item 4.
+
+The optional fifth argument is a pointer to an associative array
+relating the checkbox values to the user-visible labels that will
+be printed next to them (-labels).  If not provided, the values will
+be used as the default.
+
+=item 5.
+
+B<HTML3-compatible browsers> (such as Netscape) can take advantage of
+the optional parameters B<-rows>, and B<-columns>.  These parameters
+cause checkbox_group() to return an HTML3 compatible table containing
+the checkbox group formatted with the specified number of rows and
+columns.  You can provide just the -columns parameter if you wish;
+checkbox_group will calculate the correct number of rows for you.
+
+To include row and column headings in the returned table, you
+can use the B<-rowheaders> and B<-colheaders> parameters.  Both
+of these accept a pointer to an array of headings to use.
+The headings are just decorative.  They don't reorganize the
+interpretation of the checkboxes -- they're still a single named
+unit.
+
+=back
+
+When the form is processed, all checked boxes will be returned as
+a list under the parameter name 'group_name'.  The values of the
+"on" checkboxes can be retrieved with:
+
+      @turned_on = $query->param('group_name');
+
+The value returned by checkbox_group() is actually an array of button
+elements.  You can capture them and use them within tables, lists,
+or in other creative ways:
+
+    @h = $query->checkbox_group(-name=>'group_name',-values=>\@values);
+    &use_in_creative_way(@h);
+
+JAVASCRIPTING: checkbox_group() recognizes the B<-onClick>
+parameter.  This specifies a JavaScript code fragment or
+function call to be executed every time the user clicks on
+any of the buttons in the group.  You can retrieve the identity
+of the particular button clicked on using the "this" variable.
+
+=head2 CREATING A STANDALONE CHECKBOX
+
+    print $query->checkbox(-name=>'checkbox_name',
+			   -checked=>1,
+			   -value=>'ON',
+			   -label=>'CLICK ME');
+
+	-or-
+
+    print $query->checkbox('checkbox_name','checked','ON','CLICK ME');
+
+checkbox() is used to create an isolated checkbox that isn't logically
+related to any others.
+
+=over 4
+
+=item B<Parameters:>
+
+=item 1.
+
+The first parameter is the required name for the checkbox (-name).  It
+will also be used for the user-readable label printed next to the
+checkbox.
+
+=item 2.
+
+The optional second parameter (-checked) specifies that the checkbox
+is turned on by default.  Synonyms are -selected and -on.
+
+=item 3.
+
+The optional third parameter (-value) specifies the value of the
+checkbox when it is checked.  If not provided, the word "on" is
+assumed.
+
+=item 4.
+
+The optional fourth parameter (-label) is the user-readable label to
+be attached to the checkbox.  If not provided, the checkbox name is
+used.
+
+=back
+
+The value of the checkbox can be retrieved using:
+
+    $turned_on = $query->param('checkbox_name');
+
+JAVASCRIPTING: checkbox() recognizes the B<-onClick>
+parameter.  See checkbox_group() for further details.
+
+=head2 CREATING A RADIO BUTTON GROUP
+
+   print $query->radio_group(-name=>'group_name',
+			     -values=>['eenie','meenie','minie'],
+			     -default=>'meenie',
+			     -linebreak=>'true',
+			     -labels=>\%labels);
+
+	-or-
+
+   print $query->radio_group('group_name',['eenie','meenie','minie'],
+					  'meenie','true',\%labels);
+
+
+   HTML3-COMPATIBLE BROWSERS ONLY:
+
+   print $query->radio_group(-name=>'group_name',
+			     -values=>['eenie','meenie','minie','moe'],
+			     -rows=2,-columns=>2);
+
+radio_group() creates a set of logically-related radio buttons
+(turning one member of the group on turns the others off)
+
+=over 4
+
+=item B<Parameters:>
+
+=item 1.
+
+The first argument is the name of the group and is required (-name).
+
+=item 2.
+
+The second argument (-values) is the list of values for the radio
+buttons.  The values and the labels that appear on the page are
+identical.  Pass an array I<reference> in the second argument, either
+using an anonymous array, as shown, or by referencing a named array as
+in "\@foo".
+
+=item 3.
+
+The optional third parameter (-default) is the name of the default
+button to turn on. If not specified, the first item will be the
+default.  You can provide a nonexistent button name, such as "-" to
+start up with no buttons selected.
+
+=item 4.
+
+The optional fourth parameter (-linebreak) can be set to 'true' to put
+line breaks between the buttons, creating a vertical list.
+
+=item 5.
+
+The optional fifth parameter (-labels) is a pointer to an associative
+array relating the radio button values to user-visible labels to be
+used in the display.  If not provided, the values themselves are
+displayed.
+
+=item 6.
+
+B<HTML3-compatible browsers> (such as Netscape) can take advantage 
+of the optional 
+parameters B<-rows>, and B<-columns>.  These parameters cause
+radio_group() to return an HTML3 compatible table containing
+the radio group formatted with the specified number of rows
+and columns.  You can provide just the -columns parameter if you
+wish; radio_group will calculate the correct number of rows
+for you.
+
+To include row and column headings in the returned table, you
+can use the B<-rowheader> and B<-colheader> parameters.  Both
+of these accept a pointer to an array of headings to use.
+The headings are just decorative.  They don't reorganize the
+interpretation of the radio buttons -- they're still a single named
+unit.
+
+=back
+
+When the form is processed, the selected radio button can
+be retrieved using:
+
+      $which_radio_button = $query->param('group_name');
+
+The value returned by radio_group() is actually an array of button
+elements.  You can capture them and use them within tables, lists,
+or in other creative ways:
+
+    @h = $query->radio_group(-name=>'group_name',-values=>\@values);
+    &use_in_creative_way(@h);
+
+=head2 CREATING A SUBMIT BUTTON 
+
+   print $query->submit(-name=>'button_name',
+			-value=>'value');
+
+	-or-
+
+   print $query->submit('button_name','value');
+
+submit() will create the query submission button.  Every form
+should have one of these.
+
+=over 4
+
+=item B<Parameters:>
+
+=item 1.
+
+The first argument (-name) is optional.  You can give the button a
+name if you have several submission buttons in your form and you want
+to distinguish between them.  The name will also be used as the
+user-visible label.  Be aware that a few older browsers don't deal with this correctly and
+B<never> send back a value from a button.
+
+=item 2.
+
+The second argument (-value) is also optional.  This gives the button
+a value that will be passed to your script in the query string.
+
+=back
+
+You can figure out which button was pressed by using different
+values for each one:
+
+     $which_one = $query->param('button_name');
+
+JAVASCRIPTING: radio_group() recognizes the B<-onClick>
+parameter.  See checkbox_group() for further details.
+
+=head2 CREATING A RESET BUTTON
+
+   print $query->reset
+
+reset() creates the "reset" button.  Note that it restores the
+form to its value from the last time the script was called, 
+NOT necessarily to the defaults.
+
+Note that this conflicts with the Perl reset() built-in.  Use
+CORE::reset() to get the original reset function.
+
+=head2 CREATING A DEFAULT BUTTON
+
+   print $query->defaults('button_label')
+
+defaults() creates a button that, when invoked, will cause the
+form to be completely reset to its defaults, wiping out all the
+changes the user ever made.
+
+=head2 CREATING A HIDDEN FIELD
+
+	print $query->hidden(-name=>'hidden_name',
+			     -default=>['value1','value2'...]);
+
+		-or-
+
+	print $query->hidden('hidden_name','value1','value2'...);
+
+hidden() produces a text field that can't be seen by the user.  It
+is useful for passing state variable information from one invocation
+of the script to the next.
+
+=over 4
+
+=item B<Parameters:>
+
+=item 1.
+
+The first argument is required and specifies the name of this
+field (-name).
+
+=item 2.  
+
+The second argument is also required and specifies its value
+(-default).  In the named parameter style of calling, you can provide
+a single value here or a reference to a whole list
+
+=back
+
+Fetch the value of a hidden field this way:
+
+     $hidden_value = $query->param('hidden_name');
+
+Note, that just like all the other form elements, the value of a
+hidden field is "sticky".  If you want to replace a hidden field with
+some other values after the script has been called once you'll have to
+do it manually:
+
+     $query->param('hidden_name','new','values','here');
+
+=head2 CREATING A CLICKABLE IMAGE BUTTON
+
+     print $query->image_button(-name=>'button_name',
+				-src=>'/source/URL',
+				-align=>'MIDDLE');      
+
+	-or-
+
+     print $query->image_button('button_name','/source/URL','MIDDLE');
+
+image_button() produces a clickable image.  When it's clicked on the
+position of the click is returned to your script as "button_name.x"
+and "button_name.y", where "button_name" is the name you've assigned
+to it.
+
+JAVASCRIPTING: image_button() recognizes the B<-onClick>
+parameter.  See checkbox_group() for further details.
+
+=over 4
+
+=item B<Parameters:>
+
+=item 1.
+
+The first argument (-name) is required and specifies the name of this
+field.
+
+=item 2.
+
+The second argument (-src) is also required and specifies the URL
+
+=item 3.
+
+The third option (-align, optional) is an alignment type, and may be
+TOP, BOTTOM or MIDDLE
+
+=back
+
+Fetch the value of the button this way:
+     $x = $query->param('button_name.x');
+     $y = $query->param('button_name.y');
+
+=head2 CREATING A JAVASCRIPT ACTION BUTTON
+
+     print $query->button(-name=>'button_name',
+			  -value=>'user visible label',
+			  -onClick=>"do_something()");
+
+	-or-
+
+     print $query->button('button_name',"do_something()");
+
+button() produces a button that is compatible with Netscape 2.0's
+JavaScript.  When it's pressed the fragment of JavaScript code
+pointed to by the B<-onClick> parameter will be executed.  On
+non-Netscape browsers this form element will probably not even
+display.
+
+=head1 HTTP COOKIES
+
+Netscape browsers versions 1.1 and higher, and all versions of
+Internet Explorer, support a so-called "cookie" designed to help
+maintain state within a browser session.  CGI.pm has several methods
+that support cookies.
+
+A cookie is a name=value pair much like the named parameters in a CGI
+query string.  CGI scripts create one or more cookies and send
+them to the browser in the HTTP header.  The browser maintains a list
+of cookies that belong to a particular Web server, and returns them
+to the CGI script during subsequent interactions.
+
+In addition to the required name=value pair, each cookie has several
+optional attributes:
+
+=over 4
+
+=item 1. an expiration time
+
+This is a time/date string (in a special GMT format) that indicates
+when a cookie expires.  The cookie will be saved and returned to your
+script until this expiration date is reached if the user exits
+the browser and restarts it.  If an expiration date isn't specified, the cookie
+will remain active until the user quits the browser.
+
+=item 2. a domain
+
+This is a partial or complete domain name for which the cookie is 
+valid.  The browser will return the cookie to any host that matches
+the partial domain name.  For example, if you specify a domain name
+of ".capricorn.com", then the browser will return the cookie to
+Web servers running on any of the machines "www.capricorn.com", 
+"www2.capricorn.com", "feckless.capricorn.com", etc.  Domain names
+must contain at least two periods to prevent attempts to match
+on top level domains like ".edu".  If no domain is specified, then
+the browser will only return the cookie to servers on the host the
+cookie originated from.
+
+=item 3. a path
+
+If you provide a cookie path attribute, the browser will check it
+against your script's URL before returning the cookie.  For example,
+if you specify the path "/cgi-bin", then the cookie will be returned
+to each of the scripts "/cgi-bin/tally.pl", "/cgi-bin/order.pl",
+and "/cgi-bin/customer_service/complain.pl", but not to the script
+"/cgi-private/site_admin.pl".  By default, path is set to "/", which
+causes the cookie to be sent to any CGI script on your site.
+
+=item 4. a "secure" flag
+
+If the "secure" attribute is set, the cookie will only be sent to your
+script if the CGI request is occurring on a secure channel, such as SSL.
+
+=back
+
+The interface to HTTP cookies is the B<cookie()> method:
+
+    $cookie = $query->cookie(-name=>'sessionID',
+			     -value=>'xyzzy',
+			     -expires=>'+1h',
+			     -path=>'/cgi-bin/database',
+			     -domain=>'.capricorn.org',
+			     -secure=>1);
+    print $query->header(-cookie=>$cookie);
+
+B<cookie()> creates a new cookie.  Its parameters include:
+
+=over 4
+
+=item B<-name>
+
+The name of the cookie (required).  This can be any string at all.
+Although browsers limit their cookie names to non-whitespace
+alphanumeric characters, CGI.pm removes this restriction by escaping
+and unescaping cookies behind the scenes.
+
+=item B<-value>
+
+The value of the cookie.  This can be any scalar value,
+array reference, or even associative array reference.  For example,
+you can store an entire associative array into a cookie this way:
+
+	$cookie=$query->cookie(-name=>'family information',
+			       -value=>\%childrens_ages);
+
+=item B<-path>
+
+The optional partial path for which this cookie will be valid, as described
+above.
+
+=item B<-domain>
+
+The optional partial domain for which this cookie will be valid, as described
+above.
+
+=item B<-expires>
+
+The optional expiration date for this cookie.  The format is as described 
+in the section on the B<header()> method:
+
+	"+1h"  one hour from now
+
+=item B<-secure>
+
+If set to true, this cookie will only be used within a secure
+SSL session.
+
+=back
+
+The cookie created by cookie() must be incorporated into the HTTP
+header within the string returned by the header() method:
+
+	print $query->header(-cookie=>$my_cookie);
+
+To create multiple cookies, give header() an array reference:
+
+	$cookie1 = $query->cookie(-name=>'riddle_name',
+				  -value=>"The Sphynx's Question");
+	$cookie2 = $query->cookie(-name=>'answers',
+				  -value=>\%answers);
+	print $query->header(-cookie=>[$cookie1,$cookie2]);
+
+To retrieve a cookie, request it by name by calling cookie() method
+without the B<-value> parameter:
+
+	use CGI;
+	$query = new CGI;
+	$riddle = $query->cookie('riddle_name');
+        %answers = $query->cookie('answers');
+
+Cookies created with a single scalar value, such as the "riddle_name"
+cookie, will be returned in that form.  Cookies with array and hash
+values can also be retrieved.
+
+The cookie and CGI namespaces are separate.  If you have a parameter
+named 'answers' and a cookie named 'answers', the values retrieved by
+param() and cookie() are independent of each other.  However, it's
+simple to turn a CGI parameter into a cookie, and vice-versa:
+
+   # turn a CGI parameter into a cookie
+   $c=$q->cookie(-name=>'answers',-value=>[$q->param('answers')]);
+   # vice-versa
+   $q->param(-name=>'answers',-value=>[$q->cookie('answers')]);
+
+See the B<cookie.cgi> example script for some ideas on how to use
+cookies effectively.
+
+=head1 WORKING WITH FRAMES
+
+It's possible for CGI.pm scripts to write into several browser panels
+and windows using the HTML 4 frame mechanism.  There are three
+techniques for defining new frames programmatically:
+
+=over 4
+
+=item 1. Create a <Frameset> document
+
+After writing out the HTTP header, instead of creating a standard
+HTML document using the start_html() call, create a <frameset> 
+document that defines the frames on the page.  Specify your script(s)
+(with appropriate parameters) as the SRC for each of the frames.
+
+There is no specific support for creating <frameset> sections 
+in CGI.pm, but the HTML is very simple to write.  See the frame
+documentation in Netscape's home pages for details 
+
+  http://home.netscape.com/assist/net_sites/frames.html
+
+=item 2. Specify the destination for the document in the HTTP header
+
+You may provide a B<-target> parameter to the header() method:
+
+    print $q->header(-target=>'ResultsWindow');
+
+This will tell the browser to load the output of your script into the
+frame named "ResultsWindow".  If a frame of that name doesn't already
+exist, the browser will pop up a new window and load your script's
+document into that.  There are a number of magic names that you can
+use for targets.  See the frame documents on Netscape's home pages for
+details.
+
+=item 3. Specify the destination for the document in the <form> tag
+
+You can specify the frame to load in the FORM tag itself.  With
+CGI.pm it looks like this:
+
+    print $q->start_form(-target=>'ResultsWindow');
+
+When your script is reinvoked by the form, its output will be loaded
+into the frame named "ResultsWindow".  If one doesn't already exist
+a new window will be created.
+
+=back
+
+The script "frameset.cgi" in the examples directory shows one way to
+create pages in which the fill-out form and the response live in
+side-by-side frames.
+
+=head1 LIMITED SUPPORT FOR CASCADING STYLE SHEETS
+
+CGI.pm has limited support for HTML3's cascading style sheets (css).
+To incorporate a stylesheet into your document, pass the
+start_html() method a B<-style> parameter.  The value of this
+parameter may be a scalar, in which case it is incorporated directly
+into a <style> section, or it may be a hash reference.  In the latter
+case you should provide the hash with one or more of B<-src> or
+B<-code>.  B<-src> points to a URL where an externally-defined
+stylesheet can be found.  B<-code> points to a scalar value to be
+incorporated into a <style> section.  Style definitions in B<-code>
+override similarly-named ones in B<-src>, hence the name "cascading."
+
+You may also specify the type of the stylesheet by adding the optional
+B<-type> parameter to the hash pointed to by B<-style>.  If not
+specified, the style defaults to 'text/css'.
+
+To refer to a style within the body of your document, add the
+B<-class> parameter to any HTML element:
+
+    print h1({-class=>'Fancy'},'Welcome to the Party');
+
+Or define styles on the fly with the B<-style> parameter:
+
+    print h1({-style=>'Color: red;'},'Welcome to Hell');
+
+You may also use the new B<span()> element to apply a style to a
+section of text:
+
+    print span({-style=>'Color: red;'},
+	       h1('Welcome to Hell'),
+	       "Where did that handbasket get to?"
+	       );
+
+Note that you must import the ":html3" definitions to have the
+B<span()> method available.  Here's a quick and dirty example of using
+CSS's.  See the CSS specification at
+http://www.w3.org/pub/WWW/TR/Wd-css-1.html for more information.
+
+    use CGI qw/:standard :html3/;
+
+    #here's a stylesheet incorporated directly into the page
+    $newStyle=<<END;
+    <!-- 
+    P.Tip {
+	margin-right: 50pt;
+	margin-left: 50pt;
+        color: red;
+    }
+    P.Alert {
+	font-size: 30pt;
+        font-family: sans-serif;
+      color: red;
+    }
+    -->
+    END
+    print header();
+    print start_html( -title=>'CGI with Style',
+		      -style=>{-src=>'http://www.capricorn.com/style/st1.css',
+		               -code=>$newStyle}
+	             );
+    print h1('CGI with Style'),
+          p({-class=>'Tip'},
+	    "Better read the cascading style sheet spec before playing with this!"),
+          span({-style=>'color: magenta'},
+	       "Look Mom, no hands!",
+	       p(),
+	       "Whooo wee!"
+	       );
+    print end_html;
+
+Pass an array reference to B<-style> in order to incorporate multiple
+stylesheets into your document.
+
+=head1 DEBUGGING
+
+If you are running the script from the command line or in the perl
+debugger, you can pass the script a list of keywords or
+parameter=value pairs on the command line or from standard input (you
+don't have to worry about tricking your script into reading from
+environment variables).  You can pass keywords like this:
+
+    your_script.pl keyword1 keyword2 keyword3
+
+or this:
+
+   your_script.pl keyword1+keyword2+keyword3
+
+or this:
+
+    your_script.pl name1=value1 name2=value2
+
+or this:
+
+    your_script.pl name1=value1&name2=value2
+
+To turn off this feature, use the -no_debug pragma.
+
+To test the POST method, you may enable full debugging with the -debug
+pragma.  This will allow you to feed newline-delimited name=value
+pairs to the script on standard input.
+
+When debugging, you can use quotes and backslashes to escape 
+characters in the familiar shell manner, letting you place
+spaces and other funny characters in your parameter=value
+pairs:
+
+   your_script.pl "name1='I am a long value'" "name2=two\ words"
+
+=head2 DUMPING OUT ALL THE NAME/VALUE PAIRS
+
+The Dump() method produces a string consisting of all the query's
+name/value pairs formatted nicely as a nested list.  This is useful
+for debugging purposes:
+
+    print $query->Dump
+
+
+Produces something that looks like:
+
+    <ul>
+    <li>name1
+	<ul>
+	<li>value1
+	<li>value2
+	</ul>
+    <li>name2
+	<ul>
+	<li>value1
+	</ul>
+    </ul>
+
+As a shortcut, you can interpolate the entire CGI object into a string
+and it will be replaced with the a nice HTML dump shown above:
+
+    $query=new CGI;
+    print "<h2>Current Values</h2> $query\n";
+
+=head1 FETCHING ENVIRONMENT VARIABLES
+
+Some of the more useful environment variables can be fetched
+through this interface.  The methods are as follows:
+
+=over 4
+
+=item B<Accept()>
+
+Return a list of MIME types that the remote browser accepts. If you
+give this method a single argument corresponding to a MIME type, as in
+$query->Accept('text/html'), it will return a floating point value
+corresponding to the browser's preference for this type from 0.0
+(don't want) to 1.0.  Glob types (e.g. text/*) in the browser's accept
+list are handled correctly.
+
+Note that the capitalization changed between version 2.43 and 2.44 in
+order to avoid conflict with Perl's accept() function.
+
+=item B<raw_cookie()>
+
+Returns the HTTP_COOKIE variable, an HTTP extension implemented by
+Netscape browsers version 1.1 and higher, and all versions of Internet
+Explorer.  Cookies have a special format, and this method call just
+returns the raw form (?cookie dough).  See cookie() for ways of
+setting and retrieving cooked cookies.
+
+Called with no parameters, raw_cookie() returns the packed cookie
+structure.  You can separate it into individual cookies by splitting
+on the character sequence "; ".  Called with the name of a cookie,
+retrieves the B<unescaped> form of the cookie.  You can use the
+regular cookie() method to get the names, or use the raw_fetch()
+method from the CGI::Cookie module.
+
+=item B<user_agent()>
+
+Returns the HTTP_USER_AGENT variable.  If you give
+this method a single argument, it will attempt to
+pattern match on it, allowing you to do something
+like $query->user_agent(netscape);
+
+=item B<path_info()>
+
+Returns additional path information from the script URL.
+E.G. fetching /cgi-bin/your_script/additional/stuff will result in
+$query->path_info() returning "/additional/stuff".
+
+NOTE: The Microsoft Internet Information Server
+is broken with respect to additional path information.  If
+you use the Perl DLL library, the IIS server will attempt to
+execute the additional path information as a Perl script.
+If you use the ordinary file associations mapping, the
+path information will be present in the environment, 
+but incorrect.  The best thing to do is to avoid using additional
+path information in CGI scripts destined for use with IIS.
+
+=item B<path_translated()>
+
+As per path_info() but returns the additional
+path information translated into a physical path, e.g.
+"/usr/local/etc/httpd/htdocs/additional/stuff".
+
+The Microsoft IIS is broken with respect to the translated
+path as well.
+
+=item B<remote_host()>
+
+Returns either the remote host name or IP address.
+if the former is unavailable.
+
+=item B<script_name()>
+
+Return the script name as a partial URL, for self-refering
+scripts.
+
+=item B<referer()>
+
+Return the URL of the page the browser was viewing
+prior to fetching your script.  Not available for all
+browsers.
+
+=item B<auth_type ()>
+
+Return the authorization/verification method in use for this
+script, if any.
+
+=item B<server_name ()>
+
+Returns the name of the server, usually the machine's host
+name.
+
+=item B<virtual_host ()>
+
+When using virtual hosts, returns the name of the host that
+the browser attempted to contact
+
+=item B<server_port ()>
+
+Return the port that the server is listening on.
+
+=item B<server_software ()>
+
+Returns the server software and version number.
+
+=item B<remote_user ()>
+
+Return the authorization/verification name used for user
+verification, if this script is protected.
+
+=item B<user_name ()>
+
+Attempt to obtain the remote user's name, using a variety of different
+techniques.  This only works with older browsers such as Mosaic.
+Newer browsers do not report the user name for privacy reasons!
+
+=item B<request_method()>
+
+Returns the method used to access your script, usually
+one of 'POST', 'GET' or 'HEAD'.
+
+=item B<content_type()>
+
+Returns the content_type of data submitted in a POST, generally 
+multipart/form-data or application/x-www-form-urlencoded
+
+=item B<http()>
+
+Called with no arguments returns the list of HTTP environment
+variables, including such things as HTTP_USER_AGENT,
+HTTP_ACCEPT_LANGUAGE, and HTTP_ACCEPT_CHARSET, corresponding to the
+like-named HTTP header fields in the request.  Called with the name of
+an HTTP header field, returns its value.  Capitalization and the use
+of hyphens versus underscores are not significant.
+
+For example, all three of these examples are equivalent:
+
+   $requested_language = $q->http('Accept-language');
+   $requested_language = $q->http('Accept_language');
+   $requested_language = $q->http('HTTP_ACCEPT_LANGUAGE');
+
+=item B<https()>
+
+The same as I<http()>, but operates on the HTTPS environment variables
+present when the SSL protocol is in effect.  Can be used to determine
+whether SSL is turned on.
+
+=back
+
+=head1 USING NPH SCRIPTS
+
+NPH, or "no-parsed-header", scripts bypass the server completely by
+sending the complete HTTP header directly to the browser.  This has
+slight performance benefits, but is of most use for taking advantage
+of HTTP extensions that are not directly supported by your server,
+such as server push and PICS headers.
+
+Servers use a variety of conventions for designating CGI scripts as
+NPH.  Many Unix servers look at the beginning of the script's name for
+the prefix "nph-".  The Macintosh WebSTAR server and Microsoft's
+Internet Information Server, in contrast, try to decide whether a
+program is an NPH script by examining the first line of script output.
+
+
+CGI.pm supports NPH scripts with a special NPH mode.  When in this
+mode, CGI.pm will output the necessary extra header information when
+the header() and redirect() methods are
+called.
+
+The Microsoft Internet Information Server requires NPH mode.  As of
+version 2.30, CGI.pm will automatically detect when the script is
+running under IIS and put itself into this mode.  You do not need to
+do this manually, although it won't hurt anything if you do.  However,
+note that if you have applied Service Pack 6, much of the
+functionality of NPH scripts, including the ability to redirect while
+setting a cookie, b<do not work at all> on IIS without a special patch
+from Microsoft.  See
+http://support.microsoft.com/support/kb/articles/Q280/3/41.ASP:
+Non-Parsed Headers Stripped From CGI Applications That Have nph-
+Prefix in Name.
+
+=over 4
+
+=item In the B<use> statement 
+
+Simply add the "-nph" pragmato the list of symbols to be imported into
+your script:
+
+      use CGI qw(:standard -nph)
+
+=item By calling the B<nph()> method:
+
+Call B<nph()> with a non-zero parameter at any point after using CGI.pm in your program.
+
+      CGI->nph(1)
+
+=item By using B<-nph> parameters
+
+in the B<header()> and B<redirect()>  statements:
+
+      print $q->header(-nph=>1);
+
+=back
+
+=head1 Server Push
+
+CGI.pm provides four simple functions for producing multipart
+documents of the type needed to implement server push.  These
+functions were graciously provided by Ed Jordan <ed@fidalgo.net>.  To
+import these into your namespace, you must import the ":push" set.
+You are also advised to put the script into NPH mode and to set $| to
+1 to avoid buffering problems.
+
+Here is a simple script that demonstrates server push:
+
+  #!/usr/local/bin/perl
+  use CGI qw/:push -nph/;
+  $| = 1;
+  print multipart_init(-boundary=>'----here we go!');
+  foreach (0 .. 4) {
+      print multipart_start(-type=>'text/plain'),
+            "The current time is ",scalar(localtime),"\n";
+      if ($_ < 4) {
+              print multipart_end;
+      } else {
+              print multipart_final;
+      }
+      sleep 1;
+  }
+
+This script initializes server push by calling B<multipart_init()>.
+It then enters a loop in which it begins a new multipart section by
+calling B<multipart_start()>, prints the current local time,
+and ends a multipart section with B<multipart_end()>.  It then sleeps
+a second, and begins again. On the final iteration, it ends the
+multipart section with B<multipart_final()> rather than with
+B<multipart_end()>.
+
+=over 4
+
+=item multipart_init()
+
+  multipart_init(-boundary=>$boundary);
+
+Initialize the multipart system.  The -boundary argument specifies
+what MIME boundary string to use to separate parts of the document.
+If not provided, CGI.pm chooses a reasonable boundary for you.
+
+=item multipart_start()
+
+  multipart_start(-type=>$type)
+
+Start a new part of the multipart document using the specified MIME
+type.  If not specified, text/html is assumed.
+
+=item multipart_end()
+
+  multipart_end()
+
+End a part.  You must remember to call multipart_end() once for each
+multipart_start(), except at the end of the last part of the multipart
+document when multipart_final() should be called instead of multipart_end().
+
+=item multipart_final()
+
+  multipart_final()
+
+End all parts.  You should call multipart_final() rather than
+multipart_end() at the end of the last part of the multipart document.
+
+=back
+
+Users interested in server push applications should also have a look
+at the CGI::Push module.
+
+Only Netscape Navigator supports server push.  Internet Explorer
+browsers do not.
+
+=head1 Avoiding Denial of Service Attacks
+
+A potential problem with CGI.pm is that, by default, it attempts to
+process form POSTings no matter how large they are.  A wily hacker
+could attack your site by sending a CGI script a huge POST of many
+megabytes.  CGI.pm will attempt to read the entire POST into a
+variable, growing hugely in size until it runs out of memory.  While
+the script attempts to allocate the memory the system may slow down
+dramatically.  This is a form of denial of service attack.
+
+Another possible attack is for the remote user to force CGI.pm to
+accept a huge file upload.  CGI.pm will accept the upload and store it
+in a temporary directory even if your script doesn't expect to receive
+an uploaded file.  CGI.pm will delete the file automatically when it
+terminates, but in the meantime the remote user may have filled up the
+server's disk space, causing problems for other programs.
+
+The best way to avoid denial of service attacks is to limit the amount
+of memory, CPU time and disk space that CGI scripts can use.  Some Web
+servers come with built-in facilities to accomplish this. In other
+cases, you can use the shell I<limit> or I<ulimit>
+commands to put ceilings on CGI resource usage.
+
+
+CGI.pm also has some simple built-in protections against denial of
+service attacks, but you must activate them before you can use them.
+These take the form of two global variables in the CGI name space:
+
+=over 4
+
+=item B<$CGI::POST_MAX>
+
+If set to a non-negative integer, this variable puts a ceiling
+on the size of POSTings, in bytes.  If CGI.pm detects a POST
+that is greater than the ceiling, it will immediately exit with an error
+message.  This value will affect both ordinary POSTs and
+multipart POSTs, meaning that it limits the maximum size of file
+uploads as well.  You should set this to a reasonably high
+value, such as 1 megabyte.
+
+=item B<$CGI::DISABLE_UPLOADS>
+
+If set to a non-zero value, this will disable file uploads
+completely.  Other fill-out form values will work as usual.
+
+=back
+
+You can use these variables in either of two ways.
+
+=over 4
+
+=item B<1. On a script-by-script basis>
+
+Set the variable at the top of the script, right after the "use" statement:
+
+    use CGI qw/:standard/;
+    use CGI::Carp 'fatalsToBrowser';
+    $CGI::POST_MAX=1024 * 100;  # max 100K posts
+    $CGI::DISABLE_UPLOADS = 1;  # no uploads
+
+=item B<2. Globally for all scripts>
+
+Open up CGI.pm, find the definitions for $POST_MAX and 
+$DISABLE_UPLOADS, and set them to the desired values.  You'll 
+find them towards the top of the file in a subroutine named 
+initialize_globals().
+
+=back
+
+An attempt to send a POST larger than $POST_MAX bytes will cause
+I<param()> to return an empty CGI parameter list.  You can test for
+this event by checking I<cgi_error()>, either after you create the CGI
+object or, if you are using the function-oriented interface, call
+<param()> for the first time.  If the POST was intercepted, then
+cgi_error() will return the message "413 POST too large".
+
+This error message is actually defined by the HTTP protocol, and is
+designed to be returned to the browser as the CGI script's status
+ code.  For example:
+
+   $uploaded_file = param('upload');
+   if (!$uploaded_file && cgi_error()) {
+      print header(-status=>cgi_error());
+      exit 0;
+   }
+
+However it isn't clear that any browser currently knows what to do
+with this status code.  It might be better just to create an
+HTML page that warns the user of the problem.
+
+=head1 COMPATIBILITY WITH CGI-LIB.PL
+
+To make it easier to port existing programs that use cgi-lib.pl the
+compatibility routine "ReadParse" is provided.  Porting is simple:
+
+OLD VERSION
+    require "cgi-lib.pl";
+    &ReadParse;
+    print "The value of the antique is $in{antique}.\n";
+
+NEW VERSION
+    use CGI;
+    CGI::ReadParse
+    print "The value of the antique is $in{antique}.\n";
+
+CGI.pm's ReadParse() routine creates a tied variable named %in,
+which can be accessed to obtain the query variables.  Like
+ReadParse, you can also provide your own variable.  Infrequently
+used features of ReadParse, such as the creation of @in and $in 
+variables, are not supported.
+
+Once you use ReadParse, you can retrieve the query object itself
+this way:
+
+    $q = $in{CGI};
+    print $q->textfield(-name=>'wow',
+			-value=>'does this really work?');
+
+This allows you to start using the more interesting features
+of CGI.pm without rewriting your old scripts from scratch.
+
+=head1 AUTHOR INFORMATION
+
+Copyright 1995-1998, Lincoln D. Stein.  All rights reserved.  
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+Address bug reports and comments to: lstein@cshl.org.  When sending
+bug reports, please provide the version of CGI.pm, the version of
+Perl, the name and version of your Web server, and the name and
+version of the operating system you are using.  If the problem is even
+remotely browser dependent, please provide information about the
+affected browers as well.
+
+=head1 CREDITS
+
+Thanks very much to:
+
+=over 4
+
+=item Matt Heffron (heffron@falstaff.css.beckman.com)
+
+=item James Taylor (james.taylor@srs.gov)
+
+=item Scott Anguish <sanguish@digifix.com>
+
+=item Mike Jewell (mlj3u@virginia.edu)
+
+=item Timothy Shimmin (tes@kbs.citri.edu.au)
+
+=item Joergen Haegg (jh@axis.se)
+
+=item Laurent Delfosse (delfosse@delfosse.com)
+
+=item Richard Resnick (applepi1@aol.com)
+
+=item Craig Bishop (csb@barwonwater.vic.gov.au)
+
+=item Tony Curtis (tc@vcpc.univie.ac.at)
+
+=item Tim Bunce (Tim.Bunce@ig.co.uk)
+
+=item Tom Christiansen (tchrist@convex.com)
+
+=item Andreas Koenig (k@franz.ww.TU-Berlin.DE)
+
+=item Tim MacKenzie (Tim.MacKenzie@fulcrum.com.au)
+
+=item Kevin B. Hendricks (kbhend@dogwood.tyler.wm.edu)
+
+=item Stephen Dahmen (joyfire@inxpress.net)
+
+=item Ed Jordan (ed@fidalgo.net)
+
+=item David Alan Pisoni (david@cnation.com)
+
+=item Doug MacEachern (dougm@opengroup.org)
+
+=item Robin Houston (robin@oneworld.org)
+
+=item ...and many many more...
+
+for suggestions and bug fixes.
+
+=back
+
+=head1 A COMPLETE EXAMPLE OF A SIMPLE FORM-BASED SCRIPT
+
+
+	#!/usr/local/bin/perl
+
+	use CGI;
+
+	$query = new CGI;
+
+	print $query->header;
+	print $query->start_html("Example CGI.pm Form");
+	print "<h1> Example CGI.pm Form</h1>\n";
+	&print_prompt($query);
+	&do_work($query);
+	&print_tail;
+	print $query->end_html;
+
+	sub print_prompt {
+	   my($query) = @_;
+
+	   print $query->start_form;
+	   print "<em>What's your name?</em><br>";
+	   print $query->textfield('name');
+	   print $query->checkbox('Not my real name');
+
+	   print "<p><em>Where can you find English Sparrows?</em><br>";
+	   print $query->checkbox_group(
+				 -name=>'Sparrow locations',
+				 -values=>[England,France,Spain,Asia,Hoboken],
+				 -linebreak=>'yes',
+				 -defaults=>[England,Asia]);
+
+	   print "<p><em>How far can they fly?</em><br>",
+		$query->radio_group(
+			-name=>'how far',
+			-values=>['10 ft','1 mile','10 miles','real far'],
+			-default=>'1 mile');
+
+	   print "<p><em>What's your favorite color?</em>  ";
+	   print $query->popup_menu(-name=>'Color',
+				    -values=>['black','brown','red','yellow'],
+				    -default=>'red');
+
+	   print $query->hidden('Reference','Monty Python and the Holy Grail');
+
+	   print "<p><em>What have you got there?</em><br>";
+	   print $query->scrolling_list(
+			 -name=>'possessions',
+			 -values=>['A Coconut','A Grail','An Icon',
+				   'A Sword','A Ticket'],
+			 -size=>5,
+			 -multiple=>'true');
+
+	   print "<p><em>Any parting comments?</em><br>";
+	   print $query->textarea(-name=>'Comments',
+				  -rows=>10,
+				  -columns=>50);
+
+	   print "<p>",$query->reset;
+	   print $query->submit('Action','Shout');
+	   print $query->submit('Action','Scream');
+	   print $query->endform;
+	   print "<hr>\n";
+	}
+
+	sub do_work {
+	   my($query) = @_;
+	   my(@values,$key);
+
+	   print "<h2>Here are the current settings in this form</h2>";
+
+	   foreach $key ($query->param) {
+	      print "<strong>$key</strong> -> ";
+	      @values = $query->param($key);
+	      print join(", ",@values),"<br>\n";
+	  }
+	}
+
+	sub print_tail {
+	   print <<END;
+	<hr>
+	<address>Lincoln D. Stein</address><br>
+	<a href="/">Home Page</a>
+	END
+	}
+
+=head1 BUGS
+
+This module has grown large and monolithic.  Furthermore it's doing many
+things, such as handling URLs, parsing CGI input, writing HTML, etc., that
+are also done in the LWP modules. It should be discarded in favor of
+the CGI::* modules, but somehow I continue to work on it.
+
+Note that the code is truly contorted in order to avoid spurious
+warnings when programs are run with the B<-w> switch.
+
+=head1 SEE ALSO
+
+L<CGI::Carp>, L<CGI::Fast>, L<CGI::Pretty>
+
+=cut
+
Index: /other-projects/realistic-books/trunk/cgi-bin/make-realbook.cgi.in
===================================================================
--- /other-projects/realistic-books/trunk/cgi-bin/make-realbook.cgi.in	(revision 21014)
+++ /other-projects/realistic-books/trunk/cgi-bin/make-realbook.cgi.in	(revision 21014)
@@ -0,0 +1,121 @@
+#!**PERLBIN** -w
+# Need to specify the full path of Perl above
+
+
+use strict;
+use File::Basename; 
+
+no strict 'subs';
+no strict 'refs'; # allow filehandles to be variables and viceversa
+
+
+# Set this to 1 to work around IIS 6 craziness
+my $iis6_mode = 0;
+
+
+# IIS 6: for some reason, IIS runs this script with the working directory set to the Greenstone
+#   directory rather than the cgi-bin directory, causing lots of stuff to fail
+if ($iis6_mode)
+{
+    # Change into cgi-bin directory
+    chdir("cgi");
+}
+
+
+# We use require and an eval here (instead of "use") to catch any errors loading the module (for IIS)
+eval("require \"rbCGI.pm\"");
+if ($@)
+{
+    print STDOUT "Content-type:text/plain\n\n";
+    print STDOUT "ERROR: $@\n";
+    exit 0;
+}
+
+
+
+sub main
+{
+    my $rb_cgi = new rbCGI();
+
+    $rb_cgi->{'xml'} = 0;
+
+    $rb_cgi->setup_rb();
+
+    my $rbhome = $ENV{'REALISTIC_BOOKS_HOME'};
+    $rb_cgi->checked_chdir($rbhome);
+
+    
+    my $action = $rb_cgi->param("a");
+    
+    my $pdf_filename = $rb_cgi->param("upload");
+    #$rb_cgi->generate_message("action = $action, pdf file = $pdf_filename");
+    #exit 0;
+
+
+    if ($action eq "import-pdf") {
+	my $pdf_filename = $rb_cgi->param("upload");
+
+	my ($fname, $fpath, $ext) = fileparse ( $pdf_filename, '\..*' );
+	$pdf_filename = "$fname$ext";
+	$pdf_filename =~ tr/ /_/;
+
+	my $full_pdf_filename = &util::filename_cat($rbhome,"pdfs",$pdf_filename);
+
+	my $upload_filehandle = $rb_cgi->upload("upload");
+
+	##open (UPLOADFILE, ">$rbhome/pdfs/$pdf_filename") 
+	open (UPLOADFILE, ">full_pdf_filename") 
+	    || $rb_cgi->generate_error("$!");
+	binmode UPLOADFILE;
+	
+	while ( <$upload_filehandle> ){
+	    print UPLOADFILE;
+	}
+	
+	close UPLOADFILE;
+
+	my $old_handle = select (STDOUT); 
+	$| = 1; # perform flush after each write to STDOUT 
+
+	##my $cmd = "perl -S pdf2realbook.pl \"$rbhome/pdfs/$pdf_filename\"";
+	my $cmd = "perl -S pdf2realbook.pl \"$full_pdf_filename\"";
+	if (open(PIN,"$cmd|")) {
+
+	    print STDOUT "Content-type:text/html\n\n";
+	    print STDOUT "<p>Processing ...</p>";
+
+	    my $line;
+	    while (defined ($line=<PIN>)) {
+		next if ($line =~ m/^Realistic book generated/);
+		chomp $line;		
+		print STDOUT "$line<br>\n";
+	    }
+
+	    close(PIN);
+	}
+	else {
+	    $rb_cgi->generate_error("Failed to cmd: $cmd");
+	}
+
+	select ($old_handle); 
+
+	print STDOUT "Done\n";
+	my $viewbookurl = $rb_cgi->param("viewbookurl");
+	my $viewthisbookurl = "$viewbookurl/$fname/index.html";
+
+	print STDOUT "<p><a href=\"$viewthisbookurl\">View this Realistic Book</a></p>\n";
+	print STDOUT "<p><a href=\"$viewbookurl\">View Realistic Library</a></p>\n";
+
+
+	##$rb_cgi->generate_message("Success: $output");
+    
+    }
+    else {
+	$rb_cgi->generate_error("Unrecognized action");
+    }
+}
+
+
+
+
+&main();
Index: /other-projects/realistic-books/trunk/cgi-bin/rbCGI.pm
===================================================================
--- /other-projects/realistic-books/trunk/cgi-bin/rbCGI.pm	(revision 21014)
+++ /other-projects/realistic-books/trunk/cgi-bin/rbCGI.pm	(revision 21014)
@@ -0,0 +1,271 @@
+package rbCGI;
+
+
+use strict; 
+no strict 'subs';
+no strict 'refs'; # allow filehandles to be variables and viceversa
+
+use CGI;
+use Cwd;
+use MIME::Base64;
+
+@rbCGI::ISA = ( 'CGI' ); 
+
+sub new {
+    my $class = shift @_;
+    
+    my $self = new CGI;
+
+    $self->{'site_filename'} = "rbsite.cfg";
+    
+    return bless $self, $class;
+}
+
+
+sub parse_cgi_args
+{
+    my $self = shift @_;
+    my $xml = (defined $self->param("xml")) ? 1 : 0;
+
+    $self->{'xml'} = $xml;
+
+    my @var_names = $self->param;
+    my @arg_list = ();
+    foreach my $n ( @var_names ) {
+	my $arg = "$n=";
+	my $val =  $self->param($n);
+	$arg .= $val if (defined $val);
+	push(@arg_list,$arg);
+    }
+	
+    $self->{'args'} = join("&",@arg_list);
+}
+
+
+sub clean_param
+{
+    my $self = shift @_;
+    my ($param) = @_;
+
+    my $val = $self->SUPER::param($param);
+    $val =~ s/[\r\n]+$// if (defined $val);
+
+    return $val;
+}
+
+sub safe_val
+{
+    my $self = shift @_;
+    my ($val) = @_;
+
+    # ensure only alpha-numeric plus a few other special chars remain
+
+    $val =~ s/[^[:alnum:]@\.\/\- :_]//g if (defined $val); 
+
+    return $val;
+}
+
+sub generate_message
+{
+    my $self = shift @_;
+    my ($message) = @_;
+    
+    print STDOUT "Content-type:text/plain\n\n$message";
+
+}
+
+sub generate_error
+{
+    my $self = shift @_;
+    my ($mess) = @_;
+    
+    my $xml = $self->{'xml'};
+
+    my $full_mess;
+    my $args = $self->{'args'};
+
+    if ($xml) {
+	# Make $args XML safe
+	my $args_xml_safe = $args;
+	$args_xml_safe =~ s/&/&amp;/g;
+
+	$full_mess =  "<Error>\n";
+	$full_mess .= "  $mess\n";
+	$full_mess .= "  CGI args were: $args_xml_safe\n";
+	$full_mess .= "</Error>\n";
+    }
+    else {
+	$full_mess = "ERROR: $mess\n  ($args)\n";
+    }
+
+    $self->generate_message($full_mess);
+
+    die $full_mess;
+}
+
+sub generate_warning
+{
+    my $self = shift @_;
+    my ($mess) = @_;
+    
+    my $xml = $self->{'xml'};
+
+    my $full_mess;
+    my $args = $self->{'args'};
+
+    if ($xml) {
+	# Make $args XML safe
+	my $args_xml_safe = $args;
+	$args_xml_safe =~ s/&/&amp;/g;
+
+	$full_mess =  "<Warning>\n";
+	$full_mess .= "  $mess\n";
+	$full_mess .= "  CGI args were: $args_xml_safe\n";
+	$full_mess .= "</Warning>\n";
+    }
+    else {
+	$full_mess = "Warning: $mess ($args)\n";
+    }
+
+    $self->generate_message($full_mess);
+
+    print STDERR $full_mess;
+}
+
+
+sub generate_ok_message
+{
+    my $self = shift @_;
+    my ($mess) = @_;
+    
+    my $xml = $self->{'xml'};
+
+    my $full_mess;
+
+    if ($xml) {
+	$full_mess =  "<Accepted>\n";
+	$full_mess .= "  $mess\n";
+	$full_mess .= "</Accepted>\n";
+    }
+    else {
+	$full_mess = "$mess\n";
+    }
+ 
+    $self->generate_message($full_mess);
+}
+
+
+
+sub get_config_info {
+    my $self = shift @_;
+    my ($infotype, $optional) = @_;
+
+    my $site_filename = $self->{'site_filename'};
+    open (FILEIN, "<$site_filename") 
+	|| $self->generate_error("Could not open $site_filename");
+
+    my $config_content = "";
+    while(defined (my $line = <FILEIN>)) {
+	$config_content .= $line;
+    }
+    close(FILEIN);
+
+    my ($loc) = ($config_content =~ m/^$infotype\s+((\".+\")|(\S+))\s*\n/m);
+    $loc =~ s/\"//g if defined $loc;
+
+    if ((!defined $loc) || ($loc =~ m/^\s*$/)) {
+	if((!defined $optional) || (!$optional)) {
+	    $self->generate_error("$infotype is not set in $site_filename");
+	}
+    }
+
+    return $loc;
+}
+
+
+sub get_rb_home {
+    my $self = shift @_;
+    
+    if (defined $self->{'rbhome'}) {
+	return $self->{'rbhome'};
+    }
+
+    my $rbhome = $self->get_config_info("rbhome");
+
+    $rbhome =~ s/(\/|\\)$//; # remove trailing slash
+
+    $self->{'rbhome'} = $rbhome;
+
+    return $rbhome;
+}
+
+sub get_rb_os {
+    my $self = shift @_;
+    
+    my $os = $^O;
+
+    if ($os =~ m/linux/i) {
+	return "linux";
+    }
+    elsif ($os =~ m/mswin/i) {
+	return "windows";
+    }
+    elsif ($os =~ m/macos/i) {
+	return "darwin";
+    }
+    else {
+	# return as is.
+	return $os;
+    }
+}
+
+
+sub setup_rb {
+    my $self = shift @_;
+    my $optional = 1; # ignore absence of specified properties in rbsite.cfg if not found
+
+    my $rbhome = $self->get_rb_home();
+    my $rbos = $self->get_rb_os();
+    $ENV{'REALISTIC_BOOKS_HOME'} = $rbhome;
+    $ENV{'RBOS'} = $rbos;
+    $ENV{'GSDLOS'} = $rbos;
+
+    require util;
+
+    my $rb_bin_script = &util::filename_cat($rbhome,"bin","script");
+    &util::envvar_append("PATH",$rb_bin_script);
+    
+    my $rb_bin_os = &util::filename_cat($rbhome,"bin",$rbos);
+    &util::envvar_append("PATH",$rb_bin_os);
+
+
+    my $rb_bin_perl = &util::filename_cat($rbhome,"bin",$rbos,"perl","bin");
+    &util::envvar_append("PATH",$rb_bin_perl);
+    
+}
+
+
+sub checked_chdir
+{
+    my $self = shift @_;
+    my ($dir) = @_;
+
+    if (!-e $dir) {
+	$self->generate_error("Directory '$dir' does not exist");
+    }
+
+    chdir $dir
+	|| $self->generate_error("Unable to change to directory: $dir");
+}
+
+
+sub decode {
+    my ($self, $text) = @_;
+    $text =~ s/\+/ /g;
+    $text = &MIME::Base64::decode_base64($text);
+
+    return $text;
+}
+
+1;
+
Index: /other-projects/realistic-books/trunk/cgi-bin/rbsite.cfg.in
===================================================================
--- /other-projects/realistic-books/trunk/cgi-bin/rbsite.cfg.in	(revision 21014)
+++ /other-projects/realistic-books/trunk/cgi-bin/rbsite.cfg.in	(revision 21014)
@@ -0,0 +1,2 @@
+
+rbhome    **RBHOME**
Index: /other-projects/realistic-books/trunk/cgi-bin/util.pm
===================================================================
--- /other-projects/realistic-books/trunk/cgi-bin/util.pm	(revision 21014)
+++ /other-projects/realistic-books/trunk/cgi-bin/util.pm	(revision 21014)
@@ -0,0 +1,1085 @@
+###########################################################################
+#
+# util.pm -- various useful utilities
+# A component of the Greenstone digital library software
+# from the New Zealand Digital Library Project at the 
+# University of Waikato, New Zealand.
+#
+# Copyright (C) 1999 New Zealand Digital Library Project
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+###########################################################################
+
+package util;
+
+use File::Copy;
+use File::Basename;
+
+use strict;
+
+
+# removes files (but not directories)
+sub rm {
+    my (@files) = @_;
+
+    my @filefiles = ();
+
+    # make sure the files we want to delete exist 
+    # and are regular files
+    foreach my $file (@files) {
+	if (!-e $file) {
+	    print STDERR "util::rm $file does not exist\n";
+	} elsif ((!-f $file) && (!-l $file)) {
+	    print STDERR "util::rm $file is not a regular (or symbolic) file\n";
+	} else {
+	    push (@filefiles, $file);
+	}
+    }
+    
+    # remove the files
+    my $numremoved = unlink @filefiles;
+
+    # check to make sure all of them were removed
+    if ($numremoved != scalar(@filefiles)) {
+	print STDERR "util::rm Not all files were removed\n";
+    }
+}
+
+
+
+# recursive removal
+sub filtered_rm_r {
+    my ($files,$file_accept_re,$file_reject_re) = @_;
+
+    my @files_array = (ref $files eq "ARRAY") ? @$files : ($files);
+
+    # recursively remove the files
+    foreach my $file (@files_array) {
+	$file =~ s/[\/\\]+$//; # remove trailing slashes
+	
+	if (!-e $file) {
+	    print STDERR "util::filtered_rm_r $file does not exist\n";
+
+	} elsif ((-d $file) && (!-l $file)) { # don't recurse down symbolic link
+	    # get the contents of this directory
+	    if (!opendir (INDIR, $file)) {
+		print STDERR "util::filtered_rm_r could not open directory $file\n";
+	    } else {
+		my @filedir = grep (!/^\.\.?$/, readdir (INDIR));
+		closedir (INDIR);
+				
+		# remove all the files in this directory
+		map {$_="$file/$_";} @filedir;
+		&filtered_rm_r (\@filedir,$file_accept_re,$file_reject_re);
+
+		if (!defined $file_accept_re && !defined $file_reject_re) {
+		    # remove this directory
+		    if (!rmdir $file) {
+			print STDERR "util::filtered_rm_r couldn't remove directory $file\n";
+		    }
+		}
+	    }
+	} else {
+	    next if (defined $file_reject_re && ($file =~ m/$file_reject_re/));
+
+	    if ((!defined $file_accept_re) || ($file =~ m/$file_accept_re/)) {
+		# remove this file	
+		&rm ($file);
+	    }
+	}
+    }
+}
+
+
+# recursive removal
+sub rm_r {
+    my (@files) = @_;
+    
+    # use the more general (but reterospectively written function
+    # filtered_rm_r function()
+
+    filtered_rm_r(\@files,undef,undef); # no accept or reject expressions
+}
+
+
+
+
+# moves a file or a group of files
+sub mv {
+    my $dest = pop (@_);
+    my (@srcfiles) = @_;
+
+    # remove trailing slashes from source and destination files
+    $dest =~ s/[\\\/]+$//;
+    map {$_ =~ s/[\\\/]+$//;} @srcfiles;
+
+    # a few sanity checks
+    if (scalar (@srcfiles) == 0) {
+	print STDERR "util::mv no destination directory given\n";
+	return;
+    } elsif ((scalar (@srcfiles) > 1) && (!-d $dest)) {
+	print STDERR "util::mv if multiple source files are given the ".
+	    "destination must be a directory\n";
+	return;
+    }
+
+    # move the files
+    foreach my $file (@srcfiles) {
+	my $tempdest = $dest;
+	if (-d $tempdest) {
+	    my ($filename) = $file =~ /([^\\\/]+)$/;
+	    $tempdest .= "/$filename";
+	}
+	if (!-e $file) {
+	    print STDERR "util::mv $file does not exist\n";
+	} else {
+	    rename ($file, $tempdest);
+	}
+    }
+}
+
+
+# copies a file or a group of files
+sub cp {
+    my $dest = pop (@_);
+    my (@srcfiles) = @_;
+
+    # remove trailing slashes from source and destination files
+    $dest =~ s/[\\\/]+$//;
+    map {$_ =~ s/[\\\/]+$//;} @srcfiles;
+
+    # a few sanity checks
+    if (scalar (@srcfiles) == 0) {
+	print STDERR "util::cp no destination directory given\n";
+	return;
+    } elsif ((scalar (@srcfiles) > 1) && (!-d $dest)) {
+	print STDERR "util::cp if multiple source files are given the ".
+	    "destination must be a directory\n";
+	return;
+    }
+
+    # copy the files
+    foreach my $file (@srcfiles) {
+	my $tempdest = $dest;
+	if (-d $tempdest) {
+	    my ($filename) = $file =~ /([^\\\/]+)$/;
+	    $tempdest .= "/$filename";
+	}
+	if (!-e $file) {
+	    print STDERR "util::cp $file does not exist\n";
+	} elsif (!-f $file) {
+	    print STDERR "util::cp $file is not a plain file\n";
+	} else {
+	    &File::Copy::copy ($file, $tempdest);
+	}
+    }
+}
+
+
+
+# recursively copies a file or group of files
+# syntax: cp_r (sourcefiles, destination directory)
+# destination must be a directory - to copy one file to
+# another use cp instead
+sub cp_r {
+    my $dest = pop (@_);
+    my (@srcfiles) = @_;
+
+    # a few sanity checks
+    if (scalar (@srcfiles) == 0) {
+	print STDERR "util::cp_r no destination directory given\n";
+	return;
+    } elsif (-f $dest) {
+	print STDERR "util::cp_r destination must be a directory\n";
+	return;
+    }
+    
+    # create destination directory if it doesn't exist already
+    if (! -d $dest) {
+	my $store_umask = umask(0002);
+	mkdir ($dest, 0777);
+	umask($store_umask);
+    } 
+
+    # copy the files
+    foreach my $file (@srcfiles) {
+
+	if (!-e $file) {
+	    print STDERR "util::cp_r $file does not exist\n";
+
+	} elsif (-d $file) {
+	    # make the new directory
+	    my ($filename) = $file =~ /([^\\\/]*)$/;
+	    $dest = &util::filename_cat ($dest, $filename);
+	    my $store_umask = umask(0002);
+	    mkdir ($dest, 0777);
+	    umask($store_umask);
+
+	    # get the contents of this directory
+	    if (!opendir (INDIR, $file)) {
+		print STDERR "util::cp_r could not open directory $file\n";
+	    } else {
+		my @filedir = readdir (INDIR);
+		closedir (INDIR);
+		foreach my $f (@filedir) {
+		    next if $f =~ /^\.\.?$/;
+		    # copy all the files in this directory
+		    my $ff = &util::filename_cat ($file, $f); 
+		    &cp_r ($ff, $dest);
+		}
+	    }
+
+	} else {
+	    &cp($file, $dest);
+	}
+    }
+}
+
+# copies a directory and its contents, excluding subdirectories, into a new directory
+sub cp_r_toplevel {
+    my $dest = pop (@_);
+    my (@srcfiles) = @_;
+
+    # a few sanity checks
+    if (scalar (@srcfiles) == 0) {
+	print STDERR "util::cp_r no destination directory given\n";
+	return;
+    } elsif (-f $dest) {
+	print STDERR "util::cp_r destination must be a directory\n";
+	return;
+    }
+    
+    # create destination directory if it doesn't exist already
+    if (! -d $dest) {
+	my $store_umask = umask(0002);
+	mkdir ($dest, 0777);
+	umask($store_umask);
+    } 
+
+    # copy the files
+    foreach my $file (@srcfiles) {
+
+	if (!-e $file) {
+	    print STDERR "util::cp_r $file does not exist\n";
+
+	} elsif (-d $file) {
+	    # make the new directory
+	    my ($filename) = $file =~ /([^\\\/]*)$/;
+	    $dest = &util::filename_cat ($dest, $filename);
+	    my $store_umask = umask(0002);
+	    mkdir ($dest, 0777);
+	    umask($store_umask);
+
+	    # get the contents of this directory
+	    if (!opendir (INDIR, $file)) {
+		print STDERR "util::cp_r could not open directory $file\n";
+	    } else {
+		my @filedir = readdir (INDIR);
+		closedir (INDIR);
+		foreach my $f (@filedir) {
+		    next if $f =~ /^\.\.?$/;
+		    
+		    # copy all the files in this directory, but not directories
+		    my $ff = &util::filename_cat ($file, $f); 
+		    if (-f $ff) {
+			&cp($ff, $dest);
+			#&cp_r ($ff, $dest);
+		    }
+		}
+	    }
+
+	} else {
+	    &cp($file, $dest);
+	}
+    }
+}
+
+sub mk_dir {
+    my ($dir) = @_;
+
+    my $store_umask = umask(0002);
+    my $mkdir_ok = mkdir ($dir, 0777);
+    umask($store_umask);
+    
+    if (!$mkdir_ok) 
+    {
+	print STDERR "util::mk_dir could not create directory $dir\n";
+	return;
+    }
+}
+
+# in case anyone cares - I did some testing (using perls Benchmark module)
+# on this subroutine against File::Path::mkpath (). mk_all_dir() is apparently
+# slightly faster (surprisingly) - Stefan.
+sub mk_all_dir {
+    my ($dir) = @_;
+
+    # use / for the directory separator, remove duplicate and
+    # trailing slashes
+    $dir=~s/[\\\/]+/\//g; 
+    $dir=~s/[\\\/]+$//;
+
+    # make sure the cache directory exists
+    my $dirsofar = "";
+    my $first = 1;
+    foreach my $dirname (split ("/", $dir)) {
+	$dirsofar .= "/" unless $first;
+	$first = 0;
+
+	$dirsofar .= $dirname;
+
+	next if $dirname =~ /^(|[a-z]:)$/i;
+	if (!-e $dirsofar)
+	    {
+		my $store_umask = umask(0002);
+		my $mkdir_ok = mkdir ($dirsofar, 0777);
+		umask($store_umask);
+		if (!$mkdir_ok)
+		{
+		    print STDERR "util::mk_all_dir could not create directory $dirsofar\n";
+		    return;
+		}
+	    }
+    }
+}
+
+# make hard link to file if supported by OS, otherwise copy the file
+sub hard_link {
+    my ($src, $dest, $verbosity) = @_;
+
+    # remove trailing slashes from source and destination files
+    $src =~ s/[\\\/]+$//;
+    $dest =~ s/[\\\/]+$//;
+
+    # a few sanity checks
+    if (-e $dest) {
+	# destination file already exists
+	return;
+    }
+    elsif (!-e $src) {
+	print STDERR "util::hard_link source file $src does not exist\n";
+	return 1;
+    }
+    elsif (-d $src) {
+	print STDERR "util::hard_link source $src is a directory\n";
+	return 1;
+    }
+
+    my $dest_dir = &File::Basename::dirname($dest);
+    mk_all_dir($dest_dir) if (!-e $dest_dir);
+
+    # link not supported on windows 9x
+    if (($ENV{'GSDLOS'} =~ /^windows$/i) && (Win32::FsType() !~ /^ntfs$/i)) {
+	&File::Copy::copy ($src, $dest);
+
+    } elsif (!link($src, $dest)) {
+	if ((!defined $verbosity) || ($verbosity>2)) {
+	    print STDERR "util::hard_link: unable to create hard link. ";
+	    print STDERR " Copying file: $src -> $dest\n";
+	}
+	&File::Copy::copy ($src, $dest);
+    }
+    return 0;
+}
+
+# make soft link to file if supported by OS, otherwise copy file
+sub soft_link {
+    my ($src, $dest, $ensure_paths_absolute) = @_;
+
+    # remove trailing slashes from source and destination files
+    $src =~ s/[\\\/]+$//;
+    $dest =~ s/[\\\/]+$//;
+
+    # Ensure file paths are absolute IF requested to do so 
+    # Soft_linking didn't work for relative paths
+    if(defined $ensure_paths_absolute && $ensure_paths_absolute) {
+	# We need to ensure that the src file is the absolute path 
+	# See http://perldoc.perl.org/File/Spec.html
+	if(!File::Spec->file_name_is_absolute( $src ))  { # it's relative
+	    $src = File::Spec->rel2abs($src); # make absolute
+	}
+	# Might as well ensure that the destination file's absolute path is used
+	if(!File::Spec->file_name_is_absolute( $dest )) {
+	    $dest = File::Spec->rel2abs($dest); # make absolute
+	}
+    }
+
+    # a few sanity checks
+    if (!-e $src) {
+	print STDERR "util::soft_link source file $src does not exist\n";
+	return 0;
+    }
+
+    my $dest_dir = &File::Basename::dirname($dest);
+    mk_all_dir($dest_dir) if (!-e $dest_dir);
+
+    if ($ENV{'GSDLOS'} =~ /^windows$/i) {
+	# symlink not supported on windows
+	&File::Copy::copy ($src, $dest);
+
+    } elsif (!eval {symlink($src, $dest)}) {
+	print STDERR "util::soft_link: unable to create soft link.\n";
+	return 0;
+    }
+
+    return 1;
+}
+
+
+
+
+# updates a copy of a directory in some other part of the filesystem
+# verbosity settings are: 0=low, 1=normal, 2=high
+# both $fromdir and $todir should be absolute paths
+sub cachedir {
+    my ($fromdir, $todir, $verbosity) = @_;
+    $verbosity = 1 unless defined $verbosity;
+
+    # use / for the directory separator, remove duplicate and
+    # trailing slashes
+    $fromdir=~s/[\\\/]+/\//g; 
+    $fromdir=~s/[\\\/]+$//;
+    $todir=~s/[\\\/]+/\//g; 
+    $todir=~s/[\\\/]+$//;
+
+    &mk_all_dir ($todir);
+
+    # get the directories in ascending order
+    if (!opendir (FROMDIR, $fromdir)) {
+	print STDERR "util::cachedir could not read directory $fromdir\n";
+	return;
+    }
+    my @fromdir = grep (!/^\.\.?$/, sort(readdir (FROMDIR)));
+    closedir (FROMDIR);
+
+    if (!opendir (TODIR, $todir)) {
+	print STDERR "util::cacedir could not read directory $todir\n";
+	return;
+    }
+    my @todir = grep (!/^\.\.?$/, sort(readdir (TODIR)));
+    closedir (TODIR);
+
+    my $fromi = 0;
+    my $toi = 0;
+		    
+    while ($fromi < scalar(@fromdir) || $toi < scalar(@todir)) {
+#	print "fromi: $fromi toi: $toi\n";
+
+	# see if we should delete a file/directory
+	# this should happen if the file/directory
+	# is not in the from list or if its a different
+	# size, or has an older timestamp
+	if ($toi < scalar(@todir)) {
+	    if (($fromi >= scalar(@fromdir)) ||
+		($todir[$toi] lt $fromdir[$fromi] || 
+		 ($todir[$toi] eq $fromdir[$fromi] && 
+		  &differentfiles("$fromdir/$fromdir[$fromi]","$todir/$todir[$toi]",
+				  $verbosity)))) {
+
+		# the files are different
+		&rm_r("$todir/$todir[$toi]");
+		splice(@todir, $toi, 1); # $toi stays the same
+
+	    } elsif ($todir[$toi] eq $fromdir[$fromi]) {
+		# the files are the same
+		# if it is a directory, check its contents
+		if (-d "$todir/$todir[$toi]") {
+		    &cachedir ("$fromdir/$fromdir[$fromi]",
+			       "$todir/$todir[$toi]", $verbosity);
+		}
+
+		$toi++;
+		$fromi++;
+		next;
+	    }
+	}
+  
+	# see if we should insert a file/directory
+	# we should insert a file/directory if there
+	# is no tofiles left or if the tofile does not exist
+	if ($fromi < scalar(@fromdir) && ($toi >= scalar(@todir) || 
+					  $todir[$toi] gt $fromdir[$fromi])) {
+	    &cp_r ("$fromdir/$fromdir[$fromi]", "$todir/$fromdir[$fromi]");
+	    splice (@todir, $toi, 0, $fromdir[$fromi]);
+
+	    $toi++;
+	    $fromi++;
+	}
+    }
+}
+
+# this function returns -1 if either file is not found
+# assumes that $file1 and $file2 are absolute file names or
+# in the current directory
+# $file2 is allowed to be newer than $file1
+sub differentfiles {
+    my ($file1, $file2, $verbosity) = @_;
+    $verbosity = 1 unless defined $verbosity;
+
+    $file1 =~ s/\/+$//;
+    $file2 =~ s/\/+$//;
+    
+    my ($file1name) = $file1 =~ /\/([^\/]*)$/;
+    my ($file2name) = $file2 =~ /\/([^\/]*)$/;
+
+    return -1 unless (-e $file1 && -e $file2);
+    if ($file1name ne $file2name) {
+	print STDERR "filenames are not the same\n" if ($verbosity >= 2);
+	return 1;
+    }
+
+    my @file1stat = stat ($file1);
+    my @file2stat = stat ($file2);
+
+    if (-d $file1) {
+	if (! -d $file2) {
+	    print STDERR "one file is a directory\n" if ($verbosity >= 2);
+	    return 1;
+	}
+	return 0;
+    }
+
+    # both must be regular files
+    unless (-f $file1 && -f $file2) {
+	print STDERR "one file is not a regular file\n" if ($verbosity >= 2);
+	return 1;
+    }
+
+    # the size of the files must be the same
+    if ($file1stat[7] != $file2stat[7]) {
+	print STDERR "different sized files\n" if ($verbosity >= 2);
+	return 1;
+    }
+
+    # the second file cannot be older than the first
+    if ($file1stat[9] > $file2stat[9]) {
+	print STDERR "file is older\n" if ($verbosity >= 2);
+	return 1;
+    }
+
+    return 0;
+}
+
+
+sub get_tmp_filename 
+{
+    my $file_ext = shift(@_) || undef;
+
+    my $opt_dot_file_ext = (defined $file_ext) ? ".$file_ext" : "";
+
+    my $tmpdir = filename_cat($ENV{'GSDLHOME'}, "tmp");
+    &mk_all_dir ($tmpdir) unless -e $tmpdir;
+
+    my $count = 1000;
+    my $rand = int(rand $count);
+    my $full_tmp_filename = &filename_cat($tmpdir, "F$rand$opt_dot_file_ext");
+
+    while (-e $full_tmp_filename) {
+	$rand = int(rand $count);
+	$full_tmp_filename = &filename_cat($tmpdir, "F$rand$opt_dot_file_ext");
+	$count++;
+    }
+    
+    return $full_tmp_filename;
+}
+
+sub filename_to_regex {
+    my $filename = shift (@_);
+
+    # need to put single backslash back to double so that regex works
+    if ($ENV{'GSDLOS'} =~ /^windows$/i) {
+	$filename =~ s/\\/\\\\/g;
+    }
+    return $filename;
+}
+
+sub filename_cat {
+    my $first_file = shift(@_);
+    my (@filenames) = @_;
+
+#   Useful for debugging 
+#     -- might make sense to call caller(0) rather than (1)??
+#   my ($cpackage,$cfilename,$cline,$csubr,$chas_args,$cwantarray) = caller(1);
+#   print STDERR "Calling method; $cfilename:$cline $cpackage->$csubr\n";
+   
+    # If first_file is not null or empty, then add it back into the list
+    if (defined $first_file && $first_file =~ /\S/) {
+	unshift(@filenames, $first_file);
+    }
+
+    my $filename = join("/", @filenames);
+
+    # remove duplicate slashes and remove the last slash
+    if ($ENV{'GSDLOS'} =~ /^windows$/i) {
+	$filename =~ s/[\\\/]+/\\/g;
+    } else {
+	$filename =~ s/[\/]+/\//g; 
+	# DB: want a filename abc\de.html to remain like this
+    }
+    $filename =~ s/[\\\/]$//;
+
+    return $filename;
+}
+
+
+sub envvar_prepend {
+    my ($var,$val) = @_;
+
+    # do not prepend any value/path that's already in the environment variable
+    if ($ENV{'GSDLOS'} =~ /^windows$/i) 
+    {
+	my $escaped_val = $val;
+	$escaped_val =~ s/\\/\\\\/g; # escape any Windows backslashes for upcoming regex
+	if($ENV{$var} !~ m/$escaped_val/) { 
+	    $ENV{$var} = "$val;".$ENV{$var};
+	}
+    }
+    else {
+	if($ENV{$var} !~ m/$val/) { 
+	    $ENV{$var} = "$val:".$ENV{$var};
+	}
+    }
+}
+
+sub envvar_append {
+    my ($var,$val) = @_;
+
+    # do not append any value/path that's already in the environment variable
+    if ($ENV{'GSDLOS'} =~ /^windows$/i) 
+    {
+	my $escaped_val = $val;
+	$escaped_val =~ s/\\/\\\\/g; # escape any Windows backslashes for upcoming regex
+	if($ENV{$var} !~ m/$escaped_val/) { 
+	    $ENV{$var} .= ";$val";
+	}
+    }
+    else {
+	if($ENV{$var} !~ m/$val/) { 
+	    $ENV{$var} .= ":$val";
+	}
+    }    
+}
+
+
+# splits a filename into a prefix and a tail extension using the tail_re, or 
+# if that fails, splits on the file_extension . (dot) 
+sub get_prefix_and_tail_by_regex {
+
+    my ($filename,$tail_re) = @_;
+    
+    my ($file_prefix,$file_ext) = ($filename =~ m/^(.*?)($tail_re)$/);
+    if ((!defined $file_prefix) || (!defined $file_ext)) {
+	($file_prefix,$file_ext) = ($filename =~ m/^(.*)(\..*?)$/);
+    }
+
+    return ($file_prefix,$file_ext);
+}
+
+# get full path and file only path from a base_dir (which may be empty) and 
+# file (which may contain directories)
+sub get_full_filenames {
+    my ($base_dir, $file) = @_;
+    
+    my $filename_full_path = $file;
+    # add on directory if present
+    $filename_full_path = &util::filename_cat ($base_dir, $file) if $base_dir =~ /\S/;
+    
+    my $filename_no_path = $file;
+
+    # remove directory if present
+    $filename_no_path =~ s/^.*[\/\\]//;
+    return ($filename_full_path, $filename_no_path);
+}
+
+# returns the path of a file without the filename -- ie. the directory the file is in
+sub filename_head {
+    my $filename = shift(@_);
+
+    if ($ENV{'GSDLOS'} =~ /^windows$/i) {
+	$filename =~ s/[^\\\\]*$//;
+    }
+    else {
+	$filename =~ s/[^\\\/]*$//;
+    }
+
+    return $filename;
+}
+
+
+# returns 1 if filename1 and filename2 point to the same
+# file or directory
+sub filenames_equal {
+    my ($filename1, $filename2) = @_;
+
+    # use filename_cat to clean up trailing slashes and 
+    # multiple slashes
+    $filename1 = filename_cat ($filename1);
+    $filename2 = filename_cat ($filename2);
+
+    # filenames not case sensitive on windows
+    if ($ENV{'GSDLOS'} =~ /^windows$/i) {
+	$filename1 =~ tr/[A-Z]/[a-z]/;
+	$filename2 =~ tr/[A-Z]/[a-z]/;
+    }
+    return 1 if $filename1 eq $filename2;
+    return 0;
+}
+
+sub filename_within_collection
+{
+    my ($filename) = @_;
+
+    my $collect_dir = $ENV{'GSDLCOLLECTDIR'};
+    
+    if (defined $collect_dir) {
+	my $dirsep = &util::get_dirsep();
+	if ($collect_dir !~ m/$dirsep$/) {
+	    $collect_dir .= $dirsep;
+	}
+	
+	$collect_dir =~ s/\\/\\\\/g; # escape DOS style file separator
+	
+	# if from within GSDLCOLLECTDIR, then remove directory prefix
+	# so source_filename is realative to it.  This is done to aid
+	# portability, i.e. the collection can be moved to somewhere
+	# else on the file system and the archives directory will still
+	# work.  This is needed, for example in the applet version of
+	# GLI where GSDLHOME/collect on the server will be different to
+	# the collect directory of the remove user.  Of course,
+	# GSDLCOLLECTDIR subsequently needs to be put back on to turn
+	# it back into a full pathname.
+	
+	if ($filename =~ /^$collect_dir(.*)$/) {
+	    $filename = $1;
+	}
+    }
+    
+    return $filename;
+}
+
+sub filename_is_absolute
+{
+    my ($filename) = @_;
+
+    if ($ENV{'GSDLOS'} =~ /^windows$/i) {
+	return ($filename =~ m/^(\w:)?\\/);
+    }
+    else {
+	return ($filename =~ m/^\//);
+    }
+}
+
+
+## @method make_absolute()
+#
+#  Ensure the given file path is absolute in respect to the given base path.
+#
+#  @param  $base_dir A string denoting the base path the given dir must be
+#                    absolute to.
+#  @param  $dir The directory to be made absolute as a string. Note that the
+#               dir may already be absolute, in which case it will remain
+#               unchanged.
+#  @return The now absolute form of the directory as a string.
+#
+#  @author John Thompson, DL Consulting Ltd.
+#  @copy 2006 DL Consulting Ltd.
+#
+#used in buildcol.pl, doesn't work for all cases --kjdon
+sub make_absolute {
+    
+    my ($base_dir, $dir) = @_;
+###    print STDERR "dir = $dir\n";
+    $dir =~ s/[\\\/]+/\//g;
+    $dir = $base_dir . "/$dir" unless ($dir =~ m|^(\w:)?/|); 
+    $dir =~ s|^/tmp_mnt||;
+    1 while($dir =~ s|/[^/]*/\.\./|/|g);
+    $dir =~ s|/[.][.]?/|/|g;
+    $dir =~ tr|/|/|s;
+###    print STDERR "dir = $dir\n";
+    
+    return $dir;
+}
+## make_absolute() ##
+
+sub get_dirsep {
+
+    if ($ENV{'GSDLOS'} =~ /^windows$/i) {
+	return "\\";
+    } else {
+	return "\/";
+    }
+}
+
+sub get_os_dirsep {
+
+    if ($ENV{'GSDLOS'} =~ /^windows$/i) {
+	return "\\\\";
+    } else {
+	return "\\\/";
+    }
+}
+
+sub get_re_dirsep {
+
+    return "\\\\|\\\/";
+}
+
+
+sub get_dirsep_tail {
+    my ($filename) = @_;
+    
+    # returns last part of directory or filename
+    # On unix e.g. a/b.d => b.d
+    #              a/b/c => c
+
+    my $dirsep = get_re_dirsep();
+    my @dirs = split (/$dirsep/, $filename);
+    my $tail = pop @dirs;
+
+    # - caused problems under windows
+    #my ($tail) = ($filename =~ m/^(?:.*?$dirsep)?(.*?)$/); 
+
+    return $tail;
+}
+
+
+# if this is running on windows we want binaries to end in
+# .exe, otherwise they don't have to end in any extension
+sub get_os_exe {
+    return ".exe" if $ENV{'GSDLOS'} =~ /^windows$/i;
+    return "";
+}
+
+
+# test to see whether this is a big or little endian machine
+sub is_little_endian
+{
+    # To determine the name of the operating system, the variable $^O is a cheap alternative to pulling it out of the Config module;
+    # If it is a Macintosh machine (i.e. the Darwin operating system), regardless if it's running on the IBM power-pc cpu or the x86 Intel-based chip with a power-pc emulator running on top of it, it's big-endian
+    # Otherwise, it's little endian
+
+    #return 0 if $^O =~ /^darwin$/i;
+    #return 0 if $ENV{'GSDLOS'} =~ /^darwin$/i;
+    
+    # Going back to stating exactly whether the machine is little endian
+    # or big endian, without any special case for Macs. Since for rata it comes
+    # back with little endian and for shuttle with bigendian.
+    return (ord(substr(pack("s",1), 0, 1)) == 1);
+}
+
+
+# will return the collection name if successful, "" otherwise
+sub use_collection {
+    my ($collection, $collectdir) = @_;
+
+    if (!defined $collectdir || $collectdir eq "") {
+	$collectdir = &filename_cat ($ENV{'GSDLHOME'}, "collect");
+    }
+
+    # get and check the collection
+    if (!defined($collection) || $collection eq "") {
+	if (defined $ENV{'GSDLCOLLECTION'}) {
+	    $collection = $ENV{'GSDLCOLLECTION'};
+	} else {
+	    print STDOUT "No collection specified\n";
+	    return "";
+	}
+    }
+    
+    if ($collection eq "modelcol") {
+	print STDOUT "You can't use modelcol.\n";
+	return "";
+    }
+
+    # make sure the environment variables GSDLCOLLECTION and GSDLCOLLECTDIR
+    # are defined
+    $ENV{'GSDLCOLLECTION'} = $collection;
+    $ENV{'GSDLCOLLECTDIR'} = &filename_cat ($collectdir, $collection);
+
+    # make sure this collection exists
+    if (!-e $ENV{'GSDLCOLLECTDIR'}) {
+	print STDOUT "Invalid collection ($collection).\n";
+	return "";
+    }
+
+    # everything is ready to go
+    return $collection;
+}
+
+
+
+
+# will return the collection name if successful, "" otherwise.  
+# Like use_collection (above) but for greenstone 3 (taking account of site level)
+
+sub use_site_collection {
+    my ($site, $collection, $collectdir) = @_;
+
+    if (!defined $collectdir || $collectdir eq "") {
+	die "GSDL3HOME not set.\n" unless defined $ENV{'GSDL3HOME'};
+	$collectdir = &filename_cat ($ENV{'GSDL3HOME'}, "sites", $site, "collect");
+    }
+
+    # collectdir explicitly set by this point (using $site variable if required).
+    # Can call "old" gsdl2 use_collection now.
+
+    return use_collection($collection,$collectdir);
+}
+
+
+
+sub locate_config_file
+{
+    my ($file) = @_;
+
+    my $locations = locate_config_files($file);
+
+    return shift @$locations; # returns undef if 'locations' is empty
+}
+
+
+sub locate_config_files
+{
+    my ($file) = @_;
+
+    my @locations = ();
+
+    if (-e $file) {
+	# Clearly specified (most likely full filename)
+	# No need to hunt in 'etc' directories, return value unchanged
+	push(@locations,$file);
+    }
+    else {
+	# Check for collection specific one before looking in global GSDL 'etc'
+	if (defined $ENV{'GSDLCOLLECTDIR'} && $ENV{'GSDLCOLLECTDIR'} ne "") {
+	    my $test_collect_etc_filename 
+		= &util::filename_cat($ENV{'GSDLCOLLECTDIR'},"etc", $file);
+	    
+	    if (-e $test_collect_etc_filename) {
+		push(@locations,$test_collect_etc_filename);
+	    }
+	}
+	my $test_main_etc_filename 
+	    = &util::filename_cat($ENV{'GSDLHOME'},"etc", $file);
+	if (-e $test_main_etc_filename) {
+	    push(@locations,$test_main_etc_filename);
+	}
+    }
+
+    return \@locations;
+}
+
+
+sub hyperlink_text
+{
+    my ($text) = @_;
+    
+    $text =~ s/(http:\/\/[^\s]+)/<a href=\"$1\">$1<\/a>/mg;
+    $text =~ s/(^|\s+)(www\.(\w|\.)+)/<a href=\"http:\/\/$2\">$2<\/a>/mg;
+
+    return $text;
+}
+
+
+# A method to check if a directory is empty (note that an empty directory still has non-zero size!!!) 
+# Code is from http://episteme.arstechnica.com/eve/forums/a/tpc/f/6330927813/m/436007700831
+sub is_dir_empty
+{
+    my ($path) = @_;
+    opendir DIR, $path;
+    while(my $entry = readdir DIR) {
+        next if($entry =~ /^\.\.?$/);
+        closedir DIR;
+        return 0;
+    }
+    closedir DIR;
+    return 1;
+}
+
+# Returns the given filename converted using either URL encoding or base64
+# encoding, as specified by $rename_method. If the given filename has no suffix
+# (if it is just the tailname), then $no_suffix should be some defined value. 
+sub rename_file {
+    my ($filename, $rename_method, $no_suffix)  = @_;
+
+    if(!$filename) { # undefined or empty string
+	return $filename;
+    }
+
+    # Replace spaces with underscore.
+    # Do this first else it can go wrong below when getting tailname
+    $filename =~ s/ /_/g;
+
+    my ($tailname,$dirname,$suffix); 
+    if($no_suffix) { # given a tailname, no suffix
+	($tailname,$dirname) = File::Basename::fileparse($filename);
+    } 
+    else {
+	($tailname,$dirname,$suffix) = File::Basename::fileparse($filename, "\\.(?:[^\\.]+?)\$");
+    }
+    $suffix = "" if !$suffix;
+
+    if (!$rename_method) {
+	print STDERR "WARNING: no file renaming method specified. Defaulting to using URL encoding...\n";
+	# Debugging information
+	my ($cpackage,$cfilename,$cline,$csubr,$chas_args,$cwantarray) = caller(1);
+	print STDERR "Calling method: $cfilename:$cline $cpackage->$csubr\n";
+    } elsif($rename_method eq "none") {
+	return $filename; # would have already been renamed
+    }
+
+    if (!$rename_method || $rename_method eq "url") {
+	$tailname = &unicode::url_encode($tailname);
+    }
+    elsif ($rename_method eq "base64") {
+	$tailname = &unicode::base64_encode($tailname);
+	$tailname =~ s/\s*//sg;      # for some reason it adds spaces not just at end but also in middle
+    }
+
+    $filename = "$tailname$suffix";
+    $filename = "$dirname$filename" if ($dirname ne "./" && $dirname ne ".\\");
+
+    return $filename;
+}
+
+# makes sure that the file has a gdb extension
+sub rename_gdbm_file {
+    my ($filename_no_ext) = @_;
+
+    my $new_filename = "$filename_no_ext.gdb";
+    return if (-f $new_filename); # if gdb file exists, don't need to do anything
+    # try ldb
+    my $old_filename = "$filename_no_ext.ldb";
+    
+    if (-f $old_filename) {
+	print STDERR "Renaming $old_filename to $new_filename\n";
+	rename ($old_filename, $new_filename)
+	    || print STDERR "Rename failed: $!\n";
+	return;
+    }
+    # try bdb
+    $old_filename = "$filename_no_ext.bdb";
+    if (-f $old_filename) {
+	print STDERR "Renaming $old_filename to $new_filename\n";	
+	rename ($old_filename, $new_filename)
+	    || print STDERR "Rename failed: $!\n";
+	return;
+    }
+}
+
+
+
+1;
Index: /other-projects/realistic-books/trunk/devel.sh
===================================================================
--- /other-projects/realistic-books/trunk/devel.sh	(revision 21014)
+++ /other-projects/realistic-books/trunk/devel.sh	(revision 21014)
@@ -0,0 +1,27 @@
+
+
+if [ -z $REALISTIC_BOOKS_HOME ] ; then
+  source setup.bash
+fi
+
+
+if [ ! -z $CFLAGS ] ; then
+  if [ "x$1" != "xsilent" ] ; then
+    echo "Warning: redefining CFLAGS"
+  fi
+fi
+export CFLAGS=-I$RBHOME_INSTALLED/include
+
+if [ ! -z $LDFLAGS ] ; then
+  if [ "x$1" != "xsilent" ] ; then
+    echo "Warning: redefining LDFLAGS"
+  fi
+fi
+
+export LDFLAGS=-L$RBHOME_INSTALLED/lib
+
+if [ "x$1" != "xsilent" ] ; then
+  echo "*****"
+  echo "* Your environment is now set up to *compile* Realistic Books"
+  echo "****"
+fi
Index: /other-projects/realistic-books/trunk/packages/CASCADE-MAKE.sh
===================================================================
--- /other-projects/realistic-books/trunk/packages/CASCADE-MAKE.sh	(revision 21014)
+++ /other-projects/realistic-books/trunk/packages/CASCADE-MAKE.sh	(revision 21014)
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+
+for d in LIBJPEG FREETYPE SWFTOOLS ; do
+  echo "    Running CASCADE-MAKE/$d.sh $*"
+
+  ./CASCADE-MAKE/$d.sh $*
+
+  if [ $? != 0 ] ; then
+    echo "  Error encountered running CASCADE-MAKE/$d.sh"
+    exit 1
+  fi
+done
Index: /other-projects/realistic-books/trunk/packages/CASCADE-MAKE/FREETYPE.sh
===================================================================
--- /other-projects/realistic-books/trunk/packages/CASCADE-MAKE/FREETYPE.sh	(revision 21014)
+++ /other-projects/realistic-books/trunk/packages/CASCADE-MAKE/FREETYPE.sh	(revision 21014)
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+package=freetype
+version=-2.3.9
+
+progname=$0
+
+(cd .. ; \
+ source setup.bash silent; )
+ 
+source $REALISTIC_BOOKS_HOME/cascade-make/lib/cascade-lib.bash
+
+prefix=$RBHOME_INSTALLED
+
+opt_run_untar $force_untar $auto_untar $package $version
+opt_run_configure $force_config $auto_config $package $version "$prefix"
+
+opt_run_make $compile   $package $version
+opt_run_make $install   $package $version "install"
+opt_run_make $clean     $package $version "clean"
+opt_run_make $distclean $package $version "distclean"
+
+opt_run_tarclean $tarclean $package $version
+
+
+
Index: /other-projects/realistic-books/trunk/packages/CASCADE-MAKE/LIBJPEG.sh
===================================================================
--- /other-projects/realistic-books/trunk/packages/CASCADE-MAKE/LIBJPEG.sh	(revision 21014)
+++ /other-projects/realistic-books/trunk/packages/CASCADE-MAKE/LIBJPEG.sh	(revision 21014)
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+package=jpeg
+version=-6b
+
+progname=$0
+
+(cd .. ; \
+ source setup.bash silent; )
+ 
+source $REALISTIC_BOOKS_HOME/cascade-make/lib/cascade-lib.bash
+
+prefix=$RBHOME_INSTALLED
+
+
+opt_run_untar $force_untar $auto_untar $package $version
+opt_run_configure $force_config $auto_config $package $version "$prefix" \
+        --enable-static
+
+opt_run_make $compile   $package $version
+opt_run_make $install   $package $version "install-lib"
+opt_run_make $clean     $package $version "clean"
+opt_run_make $distclean $package $version "distclean"
+
+opt_run_tarclean $tarclean $package $version
+
Index: /other-projects/realistic-books/trunk/packages/CASCADE-MAKE/SWFTOOLS.sh
===================================================================
--- /other-projects/realistic-books/trunk/packages/CASCADE-MAKE/SWFTOOLS.sh	(revision 21014)
+++ /other-projects/realistic-books/trunk/packages/CASCADE-MAKE/SWFTOOLS.sh	(revision 21014)
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+package=swftools
+version=-0.9.0
+
+progname=$0
+
+(cd .. ; \
+ source setup.bash silent; )
+ 
+source $REALISTIC_BOOKS_HOME/cascade-make/lib/cascade-lib.bash
+
+prefix=$RBHOME_INSTALLED
+
+opt_run_untar $force_untar $auto_untar $package $version
+opt_run_configure $force_config $auto_config $package $version "$prefix"
+
+opt_run_make $compile   $package $version
+opt_run_make $install   $package $version "install"
+opt_run_make $clean     $package $version "clean"
+opt_run_make $distclean $package $version "distclean"
+
+opt_run_tarclean $tarclean $package $version
+
+
+
Index: /other-projects/realistic-books/trunk/setup.bash
===================================================================
--- /other-projects/realistic-books/trunk/setup.bash	(revision 21014)
+++ /other-projects/realistic-books/trunk/setup.bash	(revision 21014)
@@ -0,0 +1,70 @@
+
+
+function testSource(){
+
+  if test "$0" != "`echo $0 | sed s/setup\.bash//`" ; then
+  # if $0 contains "setup.bash" we've been run... $0 is shellname if sourced.
+  # One exception is zsh has an option to set it temporarily to the script name
+    if test -z "$ZSH_NAME" ; then
+    # we aren't using zsh
+     rb_not_sourced=true
+   fi 
+  fi
+
+  if test -n "$rb_not_sourced" ; then
+     echo "  Error: Make sure you source this script, not execute it. Eg:"
+     echo "    $ source setup.bash"
+     echo "  or"
+     echo "    $ . setup.bash"
+     echo "  not"
+     echo "    $ ./setup.bash"
+     unset rb_not_sourced
+     exit 1
+  fi
+
+  if test ! -f setup.bash ; then
+    echo "You must source the script from within the Realistic Books home directory"
+    exit 1
+  fi
+
+}
+
+testSource
+
+if [ -z $REALISTIC_BOOKS_HOME ] ; then
+  export REALISTIC_BOOKS_HOME=`pwd`
+
+  RBOS=`uname -s | tr '[A-Z]' '[a-z]'`
+
+  # check for running bash under cygwin
+  if test "`echo $RBOS | sed 's/cygwin//'`" != "$RBOS" ; then
+    RBOS=windows
+  fi
+  export RBOS
+
+
+  export RBHOME_INSTALLED=$REALISTIC_BOOKS_HOME/$RBOS
+
+  export PATH=$RBHOME_INSTALLED/bin:$RBHOME_INSTALLED/bin/script:$PATH
+
+  export LD_LIBRARY_PATH=$RBHOME_INSTALLED/lib
+  export DYLD_LIBRARY_PATH=$RBHOME_INSTALLED/lib
+
+  if [ "x$1" != "xsilent" ] ; then
+    echo "*****"
+    echo "* Your environment is now set up to run Realistic Books"
+    echo "****"
+  fi
+else
+  if [ "x$1" != "xsilent" ] ; then
+    echo "*****"
+    echo "* Your environment is already set up to run Realistic Books"
+    echo "****"
+  fi
+fi
+
+
+if [ -e devel.sh ] ; then
+  source devel.sh $*
+fi
+
Index: /other-projects/realistic-books/trunk/setup.bat
===================================================================
--- /other-projects/realistic-books/trunk/setup.bat	(revision 21014)
+++ /other-projects/realistic-books/trunk/setup.bat	(revision 21014)
@@ -0,0 +1,87 @@
+@echo off
+pushd "%CD%"
+CD /D "%~dp0"
+set RBLANG=en
+
+
+echo Realistic Books Software
+echo (C) 2008, New Zealand Digital Library Project
+echo.
+echo.
+echo.
+
+if "%OS%" == "Windows_NT" goto WinNT
+if "%OS%" == "" goto Win95
+if "%GSDLLANG%" == "en" echo Setup failed - your PATH has not been set
+if "%GSDLLANG%" == "es" echo No se pudo realizar la configuraciÂ¢n - no se ha establecido la RUTA.
+if "%GSDLLANG%" == "fr" echo EchÂc de l'installation - votre variable PATH n'a pas ÂtÂ ajustÂe
+if "%GSDLLANG%" == "ru" echo ÂÃ¡Ã¢Â Â­Â®Â¢ÂªÂ  Â­Â¥ Ã£Â€Â Â«Â Ã¡Ã¬ - ÂÂÂÂ Â­Â¥ Â¡Ã«Â« Ã£Ã¡Ã¢Â Â­Â®Â¢Â«Â¥Â­
+
+goto End
+
+:WinNT
+set REALISTIC_BOOKS_HOME=%CD%
+set RBOS=windows
+
+
+if "%RB_CP_SET%" == "yes" goto Success
+set PATH=%REALISTIC_BOOKS_HOME%\bin\windows\perl\bin;%REALISTIC_BOOKS_HOME%\bin\windows;%REALISTIC_BOOKS_HOME%\bin\script;%PATH%
+set RB_CP_SET=yes
+goto Success
+
+:Win95
+if "%1" == "SetEnv" goto Win95Env
+REM We'll invoke a second copy of the command processor to make
+REM sure there's enough environment space
+COMMAND /E:2048 /K %0 SetEnv
+goto End
+
+:Win95Env
+set REALISTIC_BOOKS_HOME=%CD%
+set RBOS=windows
+
+
+if "%RB_CP_SET%" == "yes" goto Success
+set PATH="%REALISTIC_BOOKS_HOME%\bin\windows\perl\bin";"%REALISTIC_BOOKS_HOME%\bin\windows";"%REALISTIC_BOOKS_HOME%\bin\script";"%PATH%"
+set RB_CP_SET=yes
+goto Success
+
+
+:Success
+if "%RBLANG%" == "en" echo.
+if "%RBLANG%" == "en" echo Your environment has successfully been set up to run RealisticBooks.
+if "%RBLANG%" == "en" echo Note that these settings will only have effect within this MS-DOS
+if "%RBLANG%" == "en" echo session. You will therefore need to rerun setup.bat if you want
+if "%RBLANG%" == "en" echo to run RealisticBooks programs from a different MS-DOS session.
+if "%RBLANG%" == "en" echo.
+
+if "%RBLANG%" == "es" echo.
+if "%RBLANG%" == "es" echo Su ambiente ha sido configurado para correr los programas RealisticBooks.
+if "%RBLANG%" == "es" echo Recuerde que estos ajustes Â£nicamente tendrÂ n efecto dentro de esta sesiÂ¢n
+if "%RBLANG%" == "es" echo MS-DOS. Por lo tanto deberÂ  ejecutar nuevamente setup.bat si desea
+if "%RBLANG%" == "es" echo correr los programas de RealisticBooks desde una sesiÂ¢n MS-DOS diferente.
+if "%RBLANG%" == "es" echo.
+
+if "%RBLANG%" == "fr" echo.
+if "%RBLANG%" == "fr" echo Votre environnement a ÂtÂ configuÂre avec succÂs pour exÂcuter RealisticBooks
+if "%RBLANG%" == "fr" echo Notez que ces paramÂtrages n'auront d'effet que dans cette session MS-DOS.
+if "%RBLANG%" == "fr" echo Vous devrez par consÂquent rÂexÂcuter setup.bat si vous voulez faire
+if "%RBLANG%" == "fr" echo lancer des programmes RealisticBooks dans une autre session MS-DOS.
+if "%RBLANG%" == "fr" echo.
+
+if "%RBLANG%" == "ru" echo.
+if "%RBLANG%" == "ru" echo ÂÂ ÃšÂ¥ Â®ÂªÃ Ã£ÂŠÂ¥Â­ÂšÂ¥ Â¡Ã«Â«Â® Ã£Ã¡Â¯Â¥ÃšÂ­Â® Â­Â Ã¡Ã¢Ã Â®Â¥Â­Â®, Ã§Ã¢Â®Â¡Ã« Ã£Ã¡Ã¢Â Â­Â®Â¢ÂšÃ¢Ã¬ RealisticBooks ÂÂ¡Ã Â Ã¢ÂšÃ¢Â¥
+if "%RBLANG%" == "ru" echo Â¢Â­ÂšÂ¬Â Â­ÂšÂ¥, Ã§Ã¢Â® Ã­Ã¢Âš Â­Â Â§Â­Â Ã§Â¥Â­ÂšÃ¯ Â¡Ã£Â€Ã£Ã¢ Ã¢Â®Â«Ã¬ÂªÂ® ÂšÂ¬Â¥Ã¢Ã¬ Ã­Ã€Ã€Â¥ÂªÃ¢ Â¢ Â¯Ã Â¥Â€Â¥Â«Â Ã¥ Ã­Ã¢Â®Â£Â® MS DOS
+if "%RBLANG%" == "ru" echo Ã¡Â¥Ã¡Ã¡ÂšÃ¯. ÂÃ« Â¡Ã£Â€Â¥Ã¢Â¥ Â¯Â®Ã­Ã¢Â®Â¬Ã£ Â€Â®Â«ÂŠÂ­Ã« Â¯Â®Â¢Ã¢Â®Ã Â­Â® Ã£Â¯Ã Â Â¢Â«Ã¯Ã¢Ã¬ setup.bat, Â¥Ã¡Â«Âš ÂÃ« Ã¥Â®Ã¢ÂšÃ¢Â¥
+if "%RBLANG%" == "ru" echo Ã£Â¯Ã Â Â¢Â«Ã¯Ã¢Ã¬ Â¯Ã Â®Â£Ã Â Â¬Â¬Â Â¬Âš ÂÂ¥Â«Ã±Â­Ã«Ã¥ ÂšÂ§Â¢Â¥Ã ÂŠÂ¥Â­Â­Ã«Ã¥ Â¯Â®Ã Â®Â€ Â®Ã¢ Ã Â Â§Â«ÂšÃ§Â­Â®Â© Ã¡Â¥Ã¡Ã¡ÂšÂš MS DOS.
+if "%RBLANG%" == "ru" echo.
+
+:End
+
+:: test writability of REALISTIC_BOOKS_HOME
+@call "%REALISTIC_BOOKS_HOME%\bin\script\checkwritability.bat"
+popd
+
+
+
+
Index: /other-projects/realistic-books/trunk/src/RealisticBook.js
===================================================================
--- /other-projects/realistic-books/trunk/src/RealisticBook.js	(revision 21014)
+++ /other-projects/realistic-books/trunk/src/RealisticBook.js	(revision 21014)
@@ -0,0 +1,317 @@
+/*
+# A component of the Realistic Book software
+# from the New Zealand Digital Library Project at the 
+# University of Waikato, New Zealand.
+#
+# Copyright (C) 2007 New Zealand Digital Library Project
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+/*****************************************************************
+Realistic Book application
+	Detect Flash Player 8.0.24 or more plugin 
+	Specify the XHTML input and image cover URL to program
+	Embed the program to the div tag with id = bookdiv
+
+by Veronica Liesaputra --- 1 August 2007
+******************************************************************/
+
+var doc_url;// XHTML url
+var img_cover;// image cover url
+var anno_url;// XHTML annotation url
+
+//check internet browser type
+var isIE  = (navigator.appVersion.indexOf("MSIE") != -1) ? true : false;
+var isWin = (navigator.appVersion.toLowerCase().indexOf("win") != -1) ? true : false;
+var isOpera = (navigator.userAgent.indexOf("Opera") != -1) ? true : false;
+var doc_width = -1;
+var doc_height = -1;
+var doc_style = "";
+var init_page = "";
+var http_request = false;
+
+//These functions will be called by the program
+function getDocURL() { return doc_url; }
+function getImgCover() { return img_cover; }
+function getStageW() { return doc_width; }
+function getStageH() { return doc_height; }
+function setDocumentTitle(doc_title) { document.title = doc_title; }
+function getStyleFile() { return doc_style; }
+function getStartOpenPage() { return init_page; }
+function getAnnoURL() { return anno_url; }
+
+//set the page where the book will be open to
+function setOpenPage(num) { init_page = num; }
+//set the style of the book
+function setBookStyle(s) {doc_style = s;}
+//set the size of the book
+function setBookSize(dw,dh) {doc_width = dw; doc_height = dh;}
+
+//overload function
+function embedBookProgram(args)
+{
+	if (init_page.length == 0) {
+		var loc = window.location.href;
+		var idx = loc.lastIndexOf("#");
+		if (idx != -1) {
+			var aftr = loc.substring(idx + 1);
+			if ((aftr.indexOf(".") == -1) && (aftr.indexOf("?") == -1))
+				init_page = aftr;
+		}
+	}
+	if (args.length == 0)
+		alert("Please type the URL for the XHTML input");
+	else
+	{
+		var size = [-1, -1];
+		var file_url = ["","",""];
+		var slen = 0;
+		var surl = 0;
+		for (var i = 0; i < args.length; i++) {
+			if (isNaN(args[i]) == true) {
+				file_url[surl] = args[i];
+				surl++;
+			}  else {
+				size[slen] = args[i];
+				slen++;
+			}
+		}
+		
+		embedProgram(file_url[0],file_url[1],size[0],size[1],file_url[2]);
+	} 
+}
+
+/*
+ * If user has Flash player 8.0.24 or more, Embed the program to your HTML file
+ * inside the div tag with id = bookdiv Set the doc_url, img_cover, anno_url, stage_width
+ * and stage_height value Otherwise, Show user the links to get the flash player
+ */
+function embedProgram(xhtml_url, image_url, stage_height, stage_width,note_url)
+{
+	var flash_plug_html = "";
+	var flash_width = "100%";
+	if (stage_width != -1)
+		flash_width = "" + stage_width;
+	var flash_height = "100%";
+	if (stage_height != -1)
+		flash_height = "" + stage_height;
+
+	if (detectFlashPlayerVersion(8,0,24) == false)
+	{
+		flash_plug_html += 'This content requires the Adobe Flash Player 8.0.24 or greater.';
+		flash_plug_html += '<a href=http://www.macromedia.com/go/getflash/>Get Flash</a>';
+	}
+	else
+	{
+		doc_url = escape(''+xhtml_url);// just in case user forgot the quote
+		img_cover = escape(''+image_url);
+		if (note_url == "") {
+			var ind = xhtml_url.lastIndexOf(".");
+			if (ind != -1) {
+				note_url = xhtml_url.substring(0, ind) + "_annotation" + xhtml_url.substring(ind, xhtml_url.length);
+			}	
+		}
+		anno_url = escape(''+note_url);
+
+		// embed inside object tag for IE
+		flash_plug_html += '<OBJECT align="middle" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" \n';
+		flash_plug_html += '  codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=8,0,0,0" \n';
+		flash_plug_html += '  height="' + flash_height + '" id="Book" swLiveConnect="true" align="middle"\n';
+		flash_plug_html += '  width="' + flash_width + '">\n';
+		// embed inside embed tag for netscape, mozilla
+		flash_plug_html += '<PARAM name="allowScriptAccess" value="always" />\n';
+		flash_plug_html += '<PARAM name="movie" value="RealisticBook.swf';
+		flash_plug_html += '?src_image=' + img_cover;
+		flash_plug_html += '&amp;stageW=' + doc_width;
+		flash_plug_html += '&amp;stageH=' + doc_height;
+		flash_plug_html += '&amp;doc_url=' + doc_url;
+		flash_plug_html += '&amp;anno_url=' + anno_url;
+		flash_plug_html += '" />\n';
+		flash_plug_html += '<PARAM name="quality" value="high" />\n';
+		flash_plug_html += '<PARAM name="bgcolor" value="#FFFFFF" />\n';
+		flash_plug_html += '<EMBED align="middle" \n';
+		flash_plug_html += '      allowScriptAccess="always" swLiveConnect="true" \n';
+		flash_plug_html += '      bgcolor="#FFFFFF" height="' + flash_height + '" name="Book" \n';
+		flash_plug_html += '      pluginspage="http://www.macromedia.com/go/getflashplayer" \n';
+		flash_plug_html += '      quality="high" \n';
+		flash_plug_html += '      src=\'RealisticBook.swf';
+		flash_plug_html += '?src_image=' + img_cover;
+		flash_plug_html += '&amp;stageW=' + doc_width;
+		flash_plug_html += '&amp;stageH=' + doc_height;
+		flash_plug_html += '&amp;doc_url=' + doc_url;
+		flash_plug_html += '&amp;anno_url=' + anno_url;
+		flash_plug_html += '\'\n'; 
+		flash_plug_html += '      type="application/x-shockwave-flash" width="' + flash_width + '" />\n';
+		flash_plug_html += '</OBJECT>\n';
+	}
+
+	var flash_div = document.getElementById('bookdiv');
+	flash_div.innerHTML = flash_plug_html;
+}
+
+function detectFlashPlayerVersion(reqMajor,reqMinor,reqRevision)
+{
+	if (navigator.plugins != null && navigator.plugins.length > 0) 
+	{
+		if (navigator.plugins["Shockwave Flash 2.0"] || navigator.plugins["Shockwave Flash"]) 
+		{
+			var swVer2 = navigator.plugins["Shockwave Flash 2.0"] ? " 2.0" : "";
+			var flashDescription = navigator.plugins["Shockwave Flash" + swVer2].description;
+			var descArray = flashDescription.split(" ");
+			var tempArrayMajor = descArray[2].split(".");			
+			var versionMajor = tempArrayMajor[0];
+			var versionMinor = tempArrayMajor[1];
+			var versionRevision = descArray[3];
+			if (versionRevision == "") 
+			{
+				versionRevision = descArray[4];
+			}
+			if (versionRevision[0] == "d") 
+			{
+				versionRevision = versionRevision.substring(1);
+			}
+			else if (versionRevision[0] == "r") 
+			{
+				versionRevision = versionRevision.substring(1);
+				if (versionRevision.indexOf("d") > 0) 
+				{
+					versionRevision = versionRevision.substring(0, versionRevision.indexOf("d"));
+				}
+			}
+
+			var isGood = isGoodPlugin(reqMajor,reqMinor,reqRevision,versionMajor,versionMinor,versionRevision);
+			return isGood;
+		}
+	}
+	else if ( isIE && isWin && !isOpera ) 
+	{
+		var version;
+		var axo;
+		var e;
+
+		try 
+		{
+			// version will be set for 7.X or greater players
+			axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7");
+			version = axo.GetVariable("$version");
+
+			// Given "WIN 2,0,0,11"
+			var tempArray = version.split(" "); 	// ["WIN", "2,0,0,11"]
+			var tempString = tempArray[1];		// "2,0,0,11"
+			var versionArray = tempString.split(",");	// ['2', '0', '0', '11']
+
+			var isGood = isGoodPlugin(reqMajor,reqMinor,reqRevision,versionArray[0],versionArray[1],versionArray[2]);
+			return isGood;
+		} 
+		catch (e) {}
+	}
+
+	return false;
+}
+
+/*
+ * Check whether the current flash player plugin passed the required version
+ */
+function isGoodPlugin(reqMajor,reqMinor,reqRevision,currMajor,currMinor,currRevision)
+{
+	if (currMajor > parseFloat(reqMajor)) 
+	{
+		return true;
+	} 
+	else if (currMajor == parseFloat(reqMajor)) 
+	{
+		if (currMinor > parseFloat(reqMinor))
+		{
+			return true;
+		}
+		else if (currMinor == parseFloat(reqMinor)) 
+		{
+			if (currRevision >= parseFloat(reqRevision))
+			{		
+				return true;
+			}
+		}
+	}
+
+	return false;
+}
+
+function makePOSTRequest(url, parameters) {
+	http_request = false;
+	if (window.XMLHttpRequest) { // Mozilla, Safari,...
+		http_request = new XMLHttpRequest();
+		if (http_request.overrideMimeType) {
+			// set type accordingly to anticipated content type
+			// http_request.overrideMimeType('text/xml');
+			http_request.overrideMimeType('text/html');
+		}
+	} else if (window.ActiveXObject) { // IE
+		try {
+			http_request = new ActiveXObject("Msxml2.XMLHTTP");
+		} catch (e) {
+			try {
+				http_request = new ActiveXObject("Microsoft.XMLHTTP");
+			} catch (e) {}
+		}
+	}
+	if (!http_request) {
+		alert('Cannot create XMLHTTP instance');
+		return false;
+	}
+
+	http_request.onreadystatechange = sendStatus;
+	http_request.open('POST', url, true);
+	http_request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
+	http_request.setRequestHeader("Content-length", parameters.length);
+	http_request.setRequestHeader("Connection", "close");
+	http_request.send(parameters);
+}
+
+function sendStatus() {
+	if (http_request.readyState == 4) {
+		if (http_request.status == 200) {
+			alert(http_request.responseText);
+		}
+		else {
+			alert('There was a problem with the request.');
+		}
+	}
+}
+
+function saveText(msg) {
+	var postStr = 'msgFile=' + escape(encodeURIComponent(doc_url)); 
+	postStr += '&msgText=' + escape(encodeURIComponent(msg));
+	makePOSTRequest('send.php',postStr);
+}
+
+function saveNote(msg) {
+	var postStr = 'msgFile=' + escape(encodeURIComponent(anno_url)); 
+	postStr += '&msgText=' + escape(encodeURIComponent(msg));
+	makePOSTRequest('send.php',postStr);
+}
+
+window.onbeforeunload = saveBook;
+function saveBook() {
+	var flashBook = document["Book"];
+	if (navigator.appName.indexOf("Microsoft") != -1) {
+        	flashBook = window["Book"];
+    	}
+   	
+	if (flashBook != null) {
+		flashBook.saveBookNotes();
+		//flashBook.saveBookTexts();
+	}
+}
Index: /other-projects/realistic-books/trunk/src/checkwritability.bat
===================================================================
--- /other-projects/realistic-books/trunk/src/checkwritability.bat	(revision 21014)
+++ /other-projects/realistic-books/trunk/src/checkwritability.bat	(revision 21014)
@@ -0,0 +1,75 @@
+@echo off
+
+if "%REALISTIC_BOOKS_HOME%" == "" goto EnvNotSet
+
+REM test if we have write permission on the greenstone install directory
+echo.
+echo Checking if REALISTIC_BOOKS_HOME is writable...
+echo This is a temporary file. It is safe to delete it. > "%REALISTIC_BOOKS_HOME%\books\testing.tmp"
+if not exist "%REALISTIC_BOOKS_HOME%\books\testing.tmp" goto CantWrite
+del "%REALISTIC_BOOKS_HOME%\books\testing.tmp"
+echo REALISTIC_BOOKS_HOME has write permission for the current user.
+echo.
+goto TheEnd
+
+:CantWrite
+echo.
+echo.
+echo.
+echo -----------------------------------
+
+if "%RBLANG%" == "en" echo  WARNING: CANNOT WRITE TO REALISTIC_BOOKS_HOME
+if "%RBLANG%" == "es" echo  ATENCIÃN: NO PUEDE ESCRIBIR EN EL DIRECTORIO RAÃZ DE GREENSTONE.
+if "%RBLANG%" == "fr" echo  AVERTISSEMENT: ECRIRE IMPOSSIBLE DANS LE DOSSIER D'ACCUEIL DE GREENSTONE
+if "%RBLANG%" == "ru" echo  ÐÐÐÐÐÐÐÐ: ÐÐÐÐÐÐÐÐÐÐ ÐÐ¡Ð£Ð©ÐÐ¡Ð¢ÐÐÐ¢Ð¬ ÐÐÐÐÐ¡Ð¬ Ð ÐÐÐÐÐšÐÐ®Ð® ÐÐÐÐÐ£ GREENSTONE
+
+echo -----------------------------------
+If "%RBLANG%" == "en" echo Greenstone needs write permission for the Greenstone home folder,
+if "%RBLANG%" == "en" echo which is %REALISTIC_BOOKS_HOME%,
+if "%RBLANG%" == "en" echo but right now it does not.
+if "%RBLANG%" == "en" echo Please grant "Full Control" for this folder (and all subfolders)  
+if "%RBLANG%" == "en" echo to the current user (%username%) and try again.
+
+if "%RBLANG%" == "es" echo Greenstone necesita permiso de escritura en el directorio:
+if "%RBLANG%" == "es" echo %REALISTIC_BOOKS_HOME%,
+if "%RBLANG%" == "es" echo pero no lo tiene.
+if "%RBLANG%" == "es" echo Por favor, concÃ©dale âControl completoâ al usuario actual (%username%), 
+if "%RBLANG%" == "es" echo y vuelva a intentarlo.
+
+if "%RBLANG%" == "fr" echo Greenstone nÃ©cessite une autorisation d'Ã©criture dans le dossier 
+if "%RBLANG%" == "fr" echo principal de Greenstone: %REALISTIC_BOOKS_HOME%,
+if "%RBLANG%" == "fr" echo mais pour l'instant cette autorisation n'existe pas.
+if "%RBLANG%" == "fr" echo Autorisez le Â«Â ContrÃŽle TotalÂ Â» sur ce dossier (et sur tous les sous-dossiers)
+if "%RBLANG%" == "fr" echo pour l'utilisateur courant (%username%) puis re-essayez.
+
+if "%RBLANG%" == "ru" echo Greenstone ÑÑÐµÐ±ÑÐµÑÑÑ ÑÐ°Ð·ÑÐµÑÐµÐœÐžÐµ ÐœÐ° Ð·Ð°Ð¿ÐžÑÑ Ð² ÐŽÐŸÐŒÐ°ÑÐœÑÑ Ð¿Ð°Ð¿ÐºÑ %REALISTIC_BOOKS_HOME%,
+if "%RBLANG%" == "ru" echo Ð² ÐŽÐ°ÐœÐœÑÐ¹ ÐŒÐŸÐŒÐµÐœÑ Ð·Ð°Ð¿ÐžÑÑ Ð² ÐŽÐŸÐŒÐ°ÑÐœÑÑ Ð¿Ð°Ð¿ÐºÑ Ð·Ð°Ð¿ÑÐµÑÐµÐœÐ°.
+if "%RBLANG%" == "ru" echo ÐÐŸÐ¶Ð°Ð»ÑÐ¹ÑÑÐ° ÑÑÑÐ°ÐœÐŸÐ²ÐžÑÐµ ÐÐŸÐ»ÐœÑÐ¹ ÐŽÐŸÑÑÑÐ¿ ÐŽÐ»Ñ Ð¿Ð°Ð¿ÐºÐž (Ðž Ð²ÑÐµÑ
+ Ð²Ð»ÐŸÐ¶ÐµÐœÐœÑÑ
+ Ð¿Ð°Ð¿ÐŸÐº)
+if "%RBLANG%" == "ru" echo ÐŽÐ»Ñ ÑÐµÐºÑÑÐµÐ³ÐŸ Ð¿ÐŸÐ»ÑÐ·ÐŸÐ²Ð°ÑÐµÐ»Ñ (%username%) Ðž Ð¿ÐŸÐ¿ÑÐŸÐ±ÑÐ¹ÑÐµ Ð·Ð°ÐœÐŸÐ²ÐŸ.
+
+echo.
+
+if "%RBLANG%" == "en" echo (Alternatively, re-install Greenstone to a location where you have Full
+if "%RBLANG%" == "en" echo Control already, such as a your home folder or 'My Documents'.)
+
+if "%RBLANG%" == "es" echo (Alternativamente, reinstale Greenstone en una localizaciÃ³n en la que tenga âControl completoâ, 
+if "%RBLANG%" == "es" echo como su directorio raÃ­z o âMis documentosâ).
+
+if "%RBLANG%" == "fr" echo (Alternativement, re-installez Greenstone Ã  l'emplacement oÃ¹ vous avez dÃ©jÃ  le Â«Â ContrÃŽle TotalÂ Â», 
+if "%RBLANG%" == "fr" echo comme votre dossier d'accueil ou  "Mes Documents".)
+
+if "%RBLANG%" == "ru" echo (ÐÐžÐ±ÐŸ, Ð¿ÐµÑÐµÑÑÑÐ°ÐœÐŸÐ²ÐžÑÐµ Greenstone Ð² ÑÑ Ð¿Ð°Ð¿ÐºÑ, Ðº ÐºÐŸÑÐŸÑÐŸÐ¹ ÐÑ ÑÐ¶Ðµ ÐžÐŒÐµÐµÑÐµ Ð¿ÐŸÐ»ÐœÑÐ¹ ÐŽÐŸÑÑÑÐ¿, 
+if "%RBLANG%" == "ru" echo ÐœÐ°Ð¿ÑÐžÐŒÐµÑ Ð² ÑÐ²ÐŸÑ ÐŽÐŸÐŒÐ°ÑÐœÑÑ Ð¿Ð°Ð¿ÐºÑ ÐžÐ»Ðž Ð² Ð¿Ð°Ð¿ÐºÑ "ÐÐŸÐž ÐŽÐŸÐºÑÐŒÐµÐœÑÑ".)
+
+echo.
+echo.
+echo.
+echo.
+goto TheEnd
+
+:EnvNotSet
+echo REALISTIC_BOOKS_HOME not set
+
+:TheEnd
Index: /other-projects/realistic-books/trunk/src/pdf2image.pl
===================================================================
--- /other-projects/realistic-books/trunk/src/pdf2image.pl	(revision 21014)
+++ /other-projects/realistic-books/trunk/src/pdf2image.pl	(revision 21014)
@@ -0,0 +1,175 @@
+#
+# A component of the Realistic Book software
+# from the New Zealand Digital Library Project at the 
+# University of Waikato, New Zealand.
+#
+# Copyright (C) 2007 New Zealand Digital Library Project
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+
+#*****************************************************************
+#Realistic Book application
+#	Converting each PDF page into a SWF file. Also produce
+#	the XHTML input file for the Realistic book program
+#
+#by Veronica Liesaputra --- 1 August 2007
+#*****************************************************************/
+use warnings; use strict;
+
+   my $err = 0;
+   if ($#ARGV < 0) {
+     $err = 1;
+   }
+
+    my $newIndex = 0;
+    my $newStructure = 0;
+    my $filename = "";
+    my $j = 0;
+    while (($j <= $#ARGV) && ($err == 0)) {
+			if ($ARGV[$j] =~ m/^\-noindex$/i) {
+				$newIndex = 1;
+			} else {
+				if ($ARGV[$j] =~ m/^\-nostructure$/i) {
+					$newStructure = 1;
+				} else {
+					if ($ARGV[$j] =~ m/^\-swfupdate$/i) {
+					   $newIndex = 1;
+					   $newStructure = 1;
+                                        } else {
+					   if ($ARGV[$j] =~ m/^\-/) {
+						print STDERR "Unknown option: $ARGV[$j]\n\n";
+                                                $err = 1;
+					   } else {
+						$filename = $ARGV[$j];
+					   }
+					}
+				}
+			}
+			
+			$j = $j + 1;
+		}
+   
+    # exit from the application and show the usage message
+    if ($err == 1) {
+	print STDERR "USAGE: perl pdf2image.pl [-options] pdf_filename\n\n";
+    	print STDERR "Options:\n";
+        print STDERR "-swfupdate \t Only convert each page of the pdf file.\n";
+	print STDERR "           \t Do not generate new index.html and book structure files.\n";
+    	print STDERR "-noindex \t Do not generate new index.html file.\n";
+    	print STDERR "-nostructure \t Do not generate new book structure file.\n";
+        die("\n");
+    }
+
+    # check that file exists
+    if (-d $filename) {
+      die("$filename is a directory! Please specify the file extension. \n");
+    }
+    if (-f $filename) {
+    } else {
+      die("Cannot find $filename! \n");
+    }
+
+    # check that pdf2swf exists
+    my $progname = `pdf2swf --version`;
+    if (defined $progname && $progname =~ m/swftools/i) {
+    } else {
+      die("\nPlease make sure that pdf2swf is located in the same location as this script.\nIf you are in Linux/Macintosh, please install swftools in SWFToolsPackage.\n");
+    }
+
+    # create folder for swf files
+    my $folder_name = $filename;
+    $folder_name=~ s/.pdf//i;
+    mkdir($folder_name);
+
+    # convert each page of pdf into swf and get its page size
+    my $width = 0;
+    my $height = 0;
+    my $page_input = "";
+    my @output_lines = split(/\n/,`pdf2swf -I $filename`);# get info of each pages in pdf
+    foreach my $line (@output_lines) {
+      if ($line =~ m/^page=(\d+)\s+width=(\d+\.?\d*)\s+height=(\d+\.?\d*)$/i) {
+         # get the smallest width and height
+         if (($width == 0) || ($2 < $width)) {
+            $width = $2;
+         }
+         if (($height == 0) || ($3 < $height)) {
+            $height = $3;
+         }
+
+         # convert to swf
+         my $i = $1;
+				 my $backFile = "Page_$i.swf";
+				 my $frontFile = "$folder_name/";
+				 my $output_file = "$frontFile$backFile";
+				 my $imgfile = `pdf2swf -p $i -s insertstop -s zoom=100 -s bboxvars $filename -o $output_file`;
+				 
+				 print STDOUT "creating page $i\n";
+				 
+				 $page_input = "$page_input<newpage filename=\"Page_$i\" />\n";
+      }
+   }
+
+   if ($newStructure == 0) {
+     # get page size
+     my $page_size = "x$height";
+     $page_size = "$width$page_size";
+
+
+     # create the book structure file
+     my $html_input = "<HTML>\n<title>$folder_name</title>\n<Description>\n";
+     $html_input = "$html_input<!-- Please do not remove Filename and PageFolder metadata -->\n";
+     $html_input = "$html_input<Metadata name=\"Filename\">$folder_name</Metadata>\n";
+     $html_input = "$html_input<Metadata name=\"PageFolder\">$folder_name</Metadata>\n";
+     $html_input = "$html_input<Metadata name=\"StartNumbering\">1</Metadata>\n";
+     $html_input = "$html_input<Metadata name=\"PageSize\">$page_size</Metadata>\n";
+     $html_input = "$html_input</Description>\n";
+     $html_input = "$html_input$page_input";
+     $html_input = "$html_input</HTML>\n";
+    
+     my $html_file = "$folder_name.htm";
+     open (PROD, ">$html_file") || die("Error Writing to File: $html_file $!");
+     print PROD $html_input;
+     close (PROD) || die("Error Closing File: $html_file $!");
+     print STDOUT "creating $html_file\n";
+   }
+
+   if ($newIndex == 0) {
+     # creating index.html
+     my $index_input = "<html>\n<head>\n";
+     $index_input = "$index_input<title>$folder_name</title>\n";
+     $index_input = "$index_input<script src='RealisticBook.js' language='javascript'></script>\n</head>\n";
+     $index_input = "$index_input<body>\n";
+     $index_input = "$index_input<!-- You have to have this div tag with id = bookdiv in your HTML file!! This is where the program will be embedded -->\n";
+     $index_input = "$index_input<div id='bookdiv'></div>\n";
+     $index_input = "$index_input<script type='text/javascript'>\n";
+     $index_input = "$index_input<!-- \n";
+     $index_input = "$index_input embedBookProgram(new Array('$folder_name.htm'));\n";
+     $index_input = "$index_input// -->\n";
+     $index_input = "$index_input</script>\n";
+     $index_input = "$index_input<noscript>\n";
+     $index_input = "$index_input This content requires JavaScript and Adobe Flash Player.\n";
+     $index_input = "$index_input <a href='http://www.macromedia.com/go/getflash/'>Get Flash</a>\n";
+     $index_input = "$index_input</noscript>\n</body>\n</html>\n";
+
+     my $index_file = "index.html";
+     open (PROD, ">$index_file") || die("Error Writing to File: $index_file $!");
+     print PROD $index_input;
+     close (PROD) || die("Error Closing File: $index_file $!");
+     print STDOUT "creating $index_file\n";
+   }
+
+   print STDOUT "Done!\n";
+
Index: /other-projects/realistic-books/trunk/src/pdf2realbook.pl
===================================================================
--- /other-projects/realistic-books/trunk/src/pdf2realbook.pl	(revision 21014)
+++ /other-projects/realistic-books/trunk/src/pdf2realbook.pl	(revision 21014)
@@ -0,0 +1,241 @@
+#!/usr/bin/perl -w
+#
+# A component of the Realistic Book software
+# from the New Zealand Digital Library Project at the 
+# University of Waikato, New Zealand.
+#
+# Copyright (C) 2007 New Zealand Digital Library Project
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+
+#*****************************************************************
+#Realistic Book application
+#	Converting each PDF page into a SWF file. Also produce
+#	the XHTML input file for the Realistic book program
+#
+#by Veronica Liesaputra --- 1 August 2007
+#*****************************************************************/
+
+use warnings; 
+use strict;
+
+use File::Basename;
+use File::Spec::Functions;
+use File::Copy;
+
+BEGIN {
+    die "REALISTIC_BOOKS_HOME not set. Have you sourced setup.bash?\n" 
+	unless defined $ENV{'REALISTIC_BOOKS_HOME'};
+    die "RBOS not set. Have you sourced setup.bash?\n" 
+	unless defined $ENV{'RBOS'};
+}
+
+
+sub print_usage
+{
+    my ($full_progname) = @_;
+
+    my $progname = basename($full_progname);
+
+    print STDERR "\nUsage: $progname pdf_filename\n";
+    print STDERR "or     perl -S $progname pdf_filename\n\n";
+
+    exit(-1);
+}
+
+sub check_pdf2swf
+{
+    my $output = `pdf2swf --version`;
+
+    if (!defined $output || ($output !~ m/swftools/i)) {
+	print STDERR "Unable to find pdf2swf: $!\n";
+	print STDERR "Have you sourced setup.bash (Linux/MacOS)?\n";
+	print STDERR "Have you run     setup.bat  (Windows)?\n";
+	exit(-1);
+    }
+}
+
+sub check_file
+{
+    my ($filename) = @_;
+
+    if (-d $filename) {
+	print STDERR "Error: $filename is a directory.\n";
+	print STDERR "       Please specify the PDF file as input.\n";
+	exit(-1);
+    }
+
+    if (!-e $filename) {
+	print STDERR "Error: Unable to find $filename\n";
+	exit(-1);
+    }
+}
+
+sub copy_model_col
+{
+    my ($folder_name) = @_;
+
+    #--
+    # Copy modelcol content to new book folder
+    # --
+
+    mkdir($folder_name);
+    my $modelcol_dir = catfile($ENV{'REALISTIC_BOOKS_HOME'},"books","modelcol");
+    opendir(DIR, $modelcol_dir) 
+	|| die "Cannot open directory $modelcol_dir: $!";
+
+    my @model_files = grep { -f catfile($modelcol_dir,$_) } readdir(DIR);
+    closedir DIR;
+
+    foreach my $f ( @model_files) {
+	my $src_filename = catfile($modelcol_dir,$f);
+	my $dst_filename = catfile($folder_name,$f);
+	copy($src_filename,$dst_filename);
+    }
+}
+
+sub pdf2swf_info
+{
+    my ($filename) = @_;
+
+    my $cmd = "pdf2swf --info \"$filename\"";
+    if ($ENV{'RBOS'} =~ /^windows$/i) {
+	$cmd .= " 2>nul";
+    }
+    else {
+	$cmd .= " 2>/dev/null";
+    }
+    
+
+    my @output_lines = split(/\n/,`$cmd`);
+
+    my $curr_page    = 0;
+    my $total_width  = 0;
+    my $total_height = 0;
+
+    my $double_check = 0;
+    foreach my $line (@output_lines) {
+	next if ($line =~ m/^$/);
+
+	if ($line =~ m/^page=(\d+)\s+width=(\d+\.?\d*)\s+height=(\d+\.?\d*)$/) {
+	    $curr_page = $1;
+	    my $width = $2;
+	    my $height = $3;
+	    $total_width += $width;
+	    $total_height += $height;
+	}
+	$double_check++;
+    }
+
+    #my $last_line = pop(@output_lines);
+
+    ## Watch out for trailing \n
+    #$last_line = pop(@output_lines) if ($last_line =~ m/^$/);
+
+    #my ($num_pages) = ($last_line =~ m/^page=(\d+)/);
+    my $num_pages = $curr_page;
+
+    if ($double_check != $num_pages) {
+	print STDERR "Warning: inconsistency found in page count: $double_check vs $num_pages\n";
+	print STDERR "Setting to $double_check\n";
+	$num_pages = $double_check;
+    }
+
+    my $avg_width  = $total_width / $num_pages;
+    my $avg_height = $total_height / $num_pages;
+
+    return ($num_pages,$avg_width,$avg_height);
+
+}
+
+    
+sub generate_html
+{
+    my ($full_folder_name,$folder_root,$filename) = @_;
+
+    my ($num_pages,$width,$height) = pdf2swf_info($filename);
+
+    print STDOUT "Total page: $num_pages\n";
+
+    my $html_input = "<HTML>\n<title>$folder_root</title>\n<Description>\n";
+    $html_input .= "<!-- Please do not remove Filename and PageFolder metadata -->\n";
+    $html_input .= "  <Metadata name=\"Filename\">$folder_root</Metadata>\n";
+    $html_input .= "  <Metadata name=\"PageFolder\">.</Metadata>\n";
+    $html_input .= "  <Metadata name=\"StartNumbering\">1</Metadata>\n";
+    $html_input .= "  <Metadata name=\"pagedim\">${width}x${height}</Metadata>\n";
+    $html_input .= "</Description>\n";
+
+    for (my $i=1; $i<=$num_pages; $i++) {
+
+	my $backFile = "Page_$i.swf";
+	my $frontFile = "$full_folder_name";
+	my $output_file = catfile($frontFile,$backFile);
+	my $cmd = "pdf2swf -p $i -s insertstop -s zoom=100 \"$filename\" -o \"$output_file\"";
+	## $cmd .= " 2>1";
+
+	my $imgfile = `$cmd`;
+	
+	print STDOUT "creating page $i\n";
+	
+	$html_input = "$html_input<newpage filename=\"Page_$i\" />\n";
+    }
+
+    $html_input = "$html_input</HTML>\n";
+
+    return $html_input;
+}
+
+
+sub main
+{
+    my ($argc,@argv) = @_;
+
+    my $full_progname = $0;
+
+    if ($argc < 0) {
+	print_usage($full_progname);
+    }
+
+    check_pdf2swf();
+
+    my $filename = $argv[0];
+    check_file($filename);
+
+    # print STDERR "Ignore the message called RegOpenKeyEx failed\n";
+
+    my($file_root, $fulldir, $file_ext) = fileparse($filename,qr/\.[^.]*/);
+
+    my $full_folder_name = catfile($ENV{'REALISTIC_BOOKS_HOME'},"books",$file_root);
+
+    copy_model_col($full_folder_name);
+
+    my $html_input = generate_html($full_folder_name,$file_root,$filename);
+
+    # Write out HTML file
+    my $html_file = catfile($full_folder_name,"pages.htm");
+    open (PROD, ">$html_file") || die("Error Writing to File: $html_file $!");
+    print STDERR "Creating $html_file\n";
+
+    print PROD $html_input;
+    close (PROD) || die("Error Closing File: $html_file $!");
+
+
+    print STDOUT "Realistic book generated in $full_folder_name\n";
+
+}
+
+
+&main(scalar(@ARGV),@ARGV);
Index: /other-projects/realistic-books/trunk/src/send.php
===================================================================
--- /other-projects/realistic-books/trunk/src/send.php	(revision 21014)
+++ /other-projects/realistic-books/trunk/src/send.php	(revision 21014)
@@ -0,0 +1,9 @@
+<?php
+$msg = stripslashes(urldecode($_POST["msgText"]));
+$fname = stripslashes(urldecode($_POST["msgFile"]));
+$file = fopen($fname,"w");
+if (!$file) die ("cannot open the file");
+fwrite($file, $msg);
+fclose($file);
+echo "The book has been successfully been saved";	
+?>
