/* dCGISession A class that mangages CGI operations when dBASE is used  to
				create Web Applets. The current version supports only CGI.

	New Methods (some inspired by Ken Mayer):
		errorMessage	-- streams out a message on a page that has already started to display
		streamTitle	 -- stream out the title at the top of the page <h1>
		streamSubTitle	-- stream out the title at the top of the page <h3>
		streamDetail	-- streams out individual lines of text and/or html
		streamFormBegin -- steams out the <FORM> code with the cgi definition you specify for what to do
						 with the form when it's submitted
		streamFormEnd	 -- streams out the end tag for a form
		streamText		-- streams out an HTML entryfield
		streamPassword	-- streams out an HTML entryfield with a password mask
		streamRadio	 -- streams out an HTML radio button
		streamCheckbox	-- streams out an HTML checkbox
		streamReset	 -- streams out an HTML "Reset" button
		streamSubmit	-- streams out an HTML "Submit" button
		streamTextArea	-- streams out an HTML Editor control
		streamSelectBegin -- streams out an HTML Combobox or Listbox (see details in documentation for this method)
		streamOption	-- stream out elements used in Select
		streamSelectEnd -- end tag for a select object
		streamHidden	-- special object used to pass values in standard Name/Value pairs to an application when a 
						 form is posted, which do not have any UI (i.e., the user cannot see/interact with them!)
		streamResponsiveHeader -- streams out responsive header including css and javascript for a responsive web page
		streamResponsiveSetup -- streams out css and javascript for a responsive web page
		streamLogo		-- streams out logo (default is dBASE)
		streamWebVars	-- streams out Web data passed to CGI program
		modSQLQuery	 -- modifies Existing SQL string to add a restriction clause (required) and to localize the data table (optional)

	Revised Methods:
		streamBody -- streams out the BODY tag, but with some parameters not in the original ...
		sorryPage	-- modified to use the methods defined here, and passing colors for the title ...
		errorPage	-- modified to handle passing colors through for the title ...
		
	Existing Methods:		
		streamHeader -- streams out a header for CGI output
		LoadArrayFromFields -- Load field info from dataset
		loadDataModuleFromArray -- Load arrary info to update dataset
		PassDataThrough -- Passes through all received CGI data to the next form

	Usage:
		To use this class:

		set procedure to webclass.cc additive
		oCGI = new CGISession()

		// set default colors (this is pretty garish, and it's
		// just an example):
		oCGI.BackgroundColor := "black"
		oCGI.BackgroundImage := "myimage.gif"
		oCGI.TextColor		 := "green"
		oCGI.LinkColor		 := "red"
		oCGI.vLinkColor		:= "lightblue"
		oCGI.aLinkColor		:= "navy"
		oCGI.TitleBackColor	:= "white"
		oCGI.TitleTextColor	:= "black"
		// NOTE: these colors can also use the RGB
		// color format: "#000000" == black
		//				 "#00FF00" == green
		//	etc. You need to look up the colors
		//	you want for that. I like using names,
		//	but not all colors have names ...

		// From there, use the class as you would any other ...
		// You could also subclass this class to create your
		// own, and set the colors there, or override any
		// of the classes shown here.

	NOTE: When building your executable, you will need to be
		 sure to include the webclass.co in the executable. 

	Author:		A.A.Katz, author
				v.1.0 11/27/1999
				v.1.1 01/12/2000 (supports Win 98 STDOUT)
				v.1.2 05/24/2001 Loop with timeout added to STDIN read
								__asian__ build OEMFormat method added
				v.1.3 08/24/2001 loadArrayFromFields method revised
				v.1.4 09/04/2001 Chinese, Korean added to OEMFormat method #ifdef __asian
				v.1.5 01/31/2002 'AUTOINC' string comparisons standardized
				v.3.0 08/28/2016 Updated for dBASE PLUS 11				v.3.1 02/14/2017 Updated for dm-web-example-6.prg

	Rights:		You have the right to freely include this code in any compiled
					dBASE Web application. You do not have the right to reproduce
				or publish this source code without the explicit written
				permission of dBASE, LLC., Binghampton, NY
				(c) 1999-2000 dBASE Inc.
				(c) 2012-2016 dBase LLC.

	Platform:	This code works only with Visual dBASE 7.5 or later.
				If this code is used, you do not need to use VdB_CGI.exe
				as required with earlier versions. Your Visual dBASE
				applications may be called directly by the Web Server.
*/


Class CGISession of assocArray
	////// Constructor code

	this.nTimeout = 45			 // STDIN read timeout in seconds #####

	// create default color and background image settings here
	// that will affect the whole HTML document and any document
	// using this instance of the class:

	// NOTE: colors must be in either text format (i.e., "BLUE" )
	//		or in hexidecimal (RGB) with the "#" in front of
	//		them ...

	this.defaultLogo = "/images/dBaseLogo.png" // default logo image for streamed and generated static html pages

	// These are used by the streamBody() method if
	// no values are passed:
	this.BackgroundImage = "" // image that is displayed over the whole document
	this.BackgroundColor = "#F1FAFE" // default background color of an HTML document (Web Wizard base color)
	this.TextColor	   = "BLACK" // default color of text on a page
	this.LinkColor	   = "BLUE" // default link color (default is blue)
	this.VLinkColor	  = "" // default Visited link color (one that the user has clicked on -- default is purple)
	this.ALinkColor	  = "" // default color of link that is 'active' 

	// These are used by the streamTitle() method if 
	// no values are passed:
	this.TitleBackColor  = "" 
	this.TitleTextColor  = "" 

	////// Set dBASE environment commands. Web apps are non-GUI

	SET CENTURY ON
	SET TALK OFF


	////// Method:		Connect //////////////////////////////////////////////////
	////// Purpose		Connects by StdIn and StdOut to Web server ///////////////
	//////			reads in data, and then parses out name/value pairs //////
	//////				and creates a new element of this array for each. ////////

	Function Connect

		// Set default WebMaster EMail address

		this["WebMasterEMail"] = ""

		// Read incoming data into this array as name/value pairs
		// Ex: this["City] = "Binghampton", where "City" is the name
		// and "Binghampton" is the value

		this.loadArrayFromCGI()

		////// Open output stream back to Web Server
		Try

			this.fOut = new file()
			this.fOut.Open("StdOut", "RA")  // open the StdOut output stream

		Catch (exception e)

			Quit 							// Might as well quit, can't send response

		endTry


	////// Method:  LoadArrayFromGGI //////////////////////////////////////
	////// Purpose: Loads existing assoc array with data from  ////////////
	//////  		   Web Server from a previous page////////////////////////
	////// Params:  None //////////////////////////////////////////////////

	/* Note: This code connects to the Web Server and captures the incoming
			data sent when the HTML form was submitted. This Associative
			Array, fortunately, mimics the Web's Name/Value pairs.
			Example: this["City"] = "Binghampton", where "City" is the name and
			"Binghampton" is the value.
	*/

	Function LoadArrayFromCGI

		// First, look in environment to see if the page was submitted via
		// Post or Get

		cMethod = upper(getEnv("REQUEST_METHOD"))

		if cMethod = 'POST'	 // if the submit method was "Post" (Recommended!!!)
							 // get length of incoming data string
			nLen = val(getEnv("CONTENT_LENGTH"))
			nCnt = 0	// Initialize bytes read counter #####

			Try

				fIn = new file()		// Create new instance of File Object
				fIn.Open("StdIn", "RA")  // open the StdIn input stream
				cEnv = ""				// Initialize read buffer
				cStart = time()		  // Save start time
				// Loop until all bytes are read or timeout occurs
				do while nCnt < nLen
					cEnv += fIn.Read(nLen - nCnt)  // Read additional bytes
					nCnt := len ( cEnv )  // Update byte counter
					// Compute seconds elapsed, compensated for date rollover
					if this.nTimeout < IIf(elapsed(time(), cStart) < 0,;
						elapsed(time(), cStart) + 86400,;
						elapsed(time(), cStart))
						exit	// Exit if timeout value exceeded
					endIf
				endDo

			Catch (exception e)

				Quit	// Might as well quit, can't open connection

			endTry

			 // Note: StdIn and StdOut cannot be close()'ed.

		else

			cEnv = getEnv("QUERY_STRING") // Get data from environment

		endif

		/* Note: Name/Value pairs are sent in a form something like this:
			City=Binghampton&State=NY&Country=USA  with "&" as delimeter
			between named pairs.

		Note: Punctuation and spaces are sent using special characters.
			Furthermore, the Internet uses the International character
			standard: ANSI while dBASE uses its own internal character
			standard: OEM. The OEMFormat() method handles all
			conversions to dBASE-usable character strings automatically.
		*/

		////// Break up CGI input string into
		////// Name/Value pairs

		try

			do while at("=",cEnv) > 0   // Do while there are still pairs

				cName  = ''			 // init name/value vars
				cValue = ''

				if at("&",cEnv) > 0	  // If there is more than one pair

					// Grab the pair before first "&'
					cEnvPair = this.OEMFormat(substr(cEnv,1,at("&",cEnv)-1))

					// Strip this pair off of cEnv
					// in preparation for next loop
					cEnv = substr(cEnv,at("&",cEnv)+1)

				else // This must be the last pair

					cEnvPair = this.OEMFormat(cEnv) // grab remainder
					cEnv = ''			 // Nothing left to parse

				endif

				////// separate name and value
				cName = substr(cEnvPair,1,at('=',cEnvPair)-1)+""
				cValue = substr(cEnvPair, at('=',cEnvPair)+1)+""

				////// if name/value pair exists, append into this array
				if len(trim(cName)) >0 //

					this[cName] = cValue  // update or add to array

				endif

				////// Add in HTTP Variables
				this["QUERY_STRING"] = getEnv("QUERY_STRING") // Get data from environment
				this["DOCUMENT_ROOT"] = getEnv("DOCUMENT_ROOT") // 	The root directory of your server
				this["HTTP_COOKIE"] = getEnv("HTTP_COOKIE") // 	The visitor's cookie, if one is set
				this["HTTP_HOST"] = getEnv("HTTP_HOST") // 	The hostname of your server
				this["HTTP_REFERER"] = getEnv("HTTP_REFERER") // 	The URL of the page that called your script
				this["HTTP_USER_AGENT"] = getEnv("HTTP_USER_AGENT") // 	The browser type of the visitor
				this["HTTPS"] = getEnv("HTTPS") // 	"on" if the script is being called through a secure server
				this["PATH"] = getEnv("PATH") // 	The system path your server is running under
				this["REMOTE_ADDR"] = getEnv("REMOTE_ADDR") // 	The IP address of the visitor
				this["REMOTE_HOST"] = getEnv("REMOTE_HOST") // 	The hostname of the visitor (if your server has reverse-name-lookups on; otherwise this is the IP address again)
				this["REMOTE_PORT"] = getEnv("REMOTE_PORT") // 	The port the visitor is connected to on the web server
				this["REMOTE_USER"] = getEnv("REMOTE_USER") // 	The visitor's username (for .htaccess-protected pages)
				this["REQUEST_URI"] = getEnv("REQUEST_URI") // 	The interpreted pathname of the requested document or CGI (relative to the document root)
				this["SCRIPT_FILENAME"] = getEnv("SCRIPT_FILENAME") // 	The full pathname of the current CGI
				this["SCRIPT_NAME"] = getEnv("SCRIPT_NAME") // 	The interpreted pathname of the current CGI (relative to the document root)
				this["SERVER_ADMIN"] = getEnv("SERVER_ADMIN") // 	The email address for your server's webmaster
				this["SERVER_NAME"] = getEnv("SERVER_NAME") // 	Your server's fully qualified domain name (e.g. www.cgi101.com)
				this["SERVER_PORT"] = getEnv("SERVER_PORT") // 	The port number your server is listening on
				this["SERVER_SOFTWARE"] = getEnv("SERVER_SOFTWARE") // 	The server software you're using (such as Apache 1.3)

			enddo

		catch (exception e)

			this.errorPage(e)

		endtry

		return true

	////// Method:  streamWebVars //////////////////////////////////////
	////// Purpose: displays the web variables sent to CGI program ////
	////// Params:  None //////////////////////////////////////////////////

	/* Note: This code connects to the Web Server and captures the incoming
			data sent when the HTML form was submitted. This Associative
			Array, fortunately, mimics the Web's Name/Value pairs.
			Example: this["City"] = "Binghampton", where "City" is the name and
			"Binghampton" is the value.
	*/

	Function streamWebVars

		this.fOut.Puts('<p>')
		cKey = this.firstKey // Get first key in AssocArray of Web Variables passed in
		for nElements = 1 to this.count()
			this.fOut.Puts('Key :: '+cKey+' = '+this[ cKey ]+'<br>')
			cKey := this.nextKey( cKey ) // Get next key value 
		endfor
		oCGI.fOut.Puts('</p>')
	 
	////// Method 	OEMFormat /////////////////////////////////////////////////
	////// Purpose 	Transforms CGI escape chars to OEM/////////////////////////
	////// Param: 	cStr 	Character strings sent by Web browser ///////////////
	//////					in ANSI format ////////////////////////////////////////////

	/* Note: Strings come in over the internet in ANSI format and must be
			converted to dBASE OEM Format before use. Punctuation is
			sent in %nn hexadecimal format and spaces to plus signs (+).
			This method does all the conversion necessary to convert from
			ANSI CGI to dBASE OEM.
			Example: Alan's New Class, in ANSI CGI is
					 Alan%27s+New+Class
	*/

   #ifdef __asian__

	Function OEMFormat(cStr) Protect // For internal use only
	// this version [May.2000] supports Shift-JIS double-byte Japanese

		// remove "Pluses" used as space keepers in CGI
		do while at("+",cStr) > 0
			cStr = substr(cStr,1,at("+",cStr)-1) + " "+;
					substr(cStr,at("+",cStr)+1)
		enddo

		// add in "CRs" sent as escape characters
		do while at("%0D%0A",cStr) > 0
			cStr = substr(cStr,1,at("%0D%0A",cStr)-1) +chr(13)+;
					substr(cStr,at("%0D%0A",cStr)+3)
		enddo

		// convert escape characters back to oem chars
		// non-ascii and Asian chars likely encoded as '%nn' per each byte
		// loop as long as '%' exists in string

		nLeft = 1
		do while at("%",substr(cStr,nLeft) ) > 0
		 nPos = at("%",substr(cStr,nLeft))+nLeft-1

		 nLeft = nPos+1
		 if nPos <= len(cStr)-2

			jumpAhead = 3
			// convertincoming encoding to character
			char1 = chr( htoi(substr(cStr,nPos+1,2)) )
			validCharacter = true

			// is char1 a lead byte of a double-byte character?
			// Japanese Shift-JIS lead bytes are only in the following range

			// convert nn to decimal
			chrCode = htoi(substr(cStr,nPos+1,2))

			isLeadByte = false
			if '932' $ _app.charset // Japanese Shift-JIS
									 // lead-byte range (non-Contiguous)
									 //	81h - 9Fh and then E0h - FCh
				isLeadByte = ( chrCode >= 129 .and. chrCode <= 159 ) .or.;
							 ( chrCode >= 224 .and. chrCode <= 239 )

			elseif '936' $ _app.charset //  Simplified Chinese
										//  lead byte range A1h - FEh
				isLeadByte = ( chrCode >= 161 .and. chrCode <= 254 )

			elseif '949' $ _app.charset // Korean
										// leadByte range 81h - FEh
				isLeadByte = ( chrCode >= 129 .and. chrCode <= 254 )

			elseif '950' $ _app.charset // Traditional Chinese
										// lead byte range A1h - FEh
				isLeadByte = ( chrCode >= 161 .and. chrCode <= 254 )

			endif

			if isLeadByte
				// we have a lead byte
				// must grab the trail byte, and then convert to character

				// trail byte is not _always_ %nn encoded
				// as CGI passes them thru as is,
				// when they happen to match a lower-case ASCII codePoint
				if substr( cStr, nPos+3, 1 ) = "%"
					// trail byte is %nn encoded
					char1 = chr( htoi( substr(cStr,nPos+1,2) + substr( cStr, nPos+4, 2 )))
					jumpAhead = 6
				else
					// trail byte is plain text
					char1 = chr( htoi( substr(cStr,nPos+1,2) + itoh( asc( substr( cStr, nPos+3, 1 ))) ))
					jumpAhead = 4
				endif
				validCharacter = iskanji( char1 )
			endif

			// throw away character if invalid
			if validCharacter
				cStr = left(cStr,nPos-1)+ char1 +;
					substr(cStr,nPos+jumpAhead)
			else
				cStr = left(cStr,nPos-1)+ substr(cStr,nPos+3)
			endif
		endif
	enddo

	return cStr

   #else

	Function OEMFormat(cStr) Protect // For internal use only

		// remove "Pluses" used as space keepers in CGI
		do while at("+",cStr) > 0
			cStr = substr(cStr,1,at("+",cStr)-1) + " "+;
				substr(cStr,at("+",cStr)+1)
		enddo

		// add in "CRs" sent as escape characters
		do while at("%0D%0A",cStr) > 0
			cStr = substr(cStr,1,at("%0D%0A",cStr)-1) +chr(13)+;
				substr(cStr,at("%0D%0A",cStr)+3)
		enddo

		// convert escape characters back to oem chars
		nLeft = 1
		do while at("%",substr(cStr,nLeft) ) > 0
			nPos = at("%",substr(cStr,nLeft))+nLeft-1
			nLeft = nPos+1
			if nPos <= len(cStr)-2
				cStr = left(cStr,nPos-1)+ chr(htoi(substr(cStr,nPos+1,2)))+;
					substr(cStr,nPos+3)
			endif
		enddo

		return cStr

   #endif


	////// Method: 	SetWebMasterAddress //////////////////////////////////
	////// Purpose:	Sets mailto: address used in Error page //////////////
	////// Param:	cWebMasterEMail (ex: webmaster@my.server) ////////////

	Function SetWebMasterAddress(cMailAddress)
		if not empty(cMailAddress)
			this["WebMasterEMail"] = cMailAddress
		endif
		return true


	/////////////////////// Output Methods //////////////////////////////////

	// -----------------------------------------------------------------
	// Streaming of HTML Documents -- I am modifying some of the 
	// original code in WebClass.cc so I have to copy the code
	// itself to here. In addition, I am giving a bit more flexibility
	// in the appearance and such ... this allows you the ability
	// to customize a bit easier ...
	// -----------------------------------------------------------------

	// -----------------------------------------------------------------
	// streamTitle
	// -----------------------------------------------------------------
	Function streamTitle( cTitle, cBackColor, cTextColor )
		/*
			streamTitle is used to stream out a title that works
			like the one in the primary webclass for the sorryPage
			and errorPage methods ... it's simply done with
			the use of a table and setting the background for
			the cell in the table. However, this allows you to
			pass a parameter for the two colors (if you wish),
			and if you wish, you can set a default color here
		*/
		// if no value passed, use default ...
		cBackColor = iif( empty( cBackColor ), this.TitleBackColor, cBackColor )
		cTextColor = iif( empty( cTextColor ), this.TitleTextColor, cTextColor )

		this.streamDetail([ ])		this.streamDetail( [	<!-- Start of streamTitle -->] )		// Title:
		this.streamDetail([	<div class="container">])
		this.streamDetail([		<div class="row">])
		if not empty( cBackColor )
			sColor = '<span style="background-color:'+cBackColor+';'
		if not empty ( cTextColor)
			sColor += 'color:'+cTextColor
		endif
		sColor += '">'
		this.streamDetail([			<div align="center">]+sColor+[<h1>]+cTitle+[</h1></span></div>])
		else
			this.streamDetail([			<div align="center"><h1>]+cTitle+[</h1></div>])
		endif
		this.streamDetail([		</div>])
		this.streamDetail([	</div>])

		// Blank Line:
		this.streamDetail( [	<!-- End of streamTitle -->] )
		this.streamDetail([ ])	// end of method: streamTitle

	// -----------------------------------------------------------------
	// streamSubTitle
	// -----------------------------------------------------------------
	Function streamSubTitle( cTitle, cBackColor, cTextColor )
		/*
			streamSubTitle is used to stream out a 2nd title that works
			like streamTitle.
		*/
		// if no value passed, use default ...
		cBackColor = iif( empty( cBackColor ), this.TitleBackColor, cBackColor )
		cTextColor = iif( empty( cTextColor ), this.TitleTextColor, cTextColor )

		this.streamDetail([ ])		this.streamDetail( [	<!-- Start of streamSubTitle -->] )		// SubTitle:
		this.streamDetail([	<div class="container">])
		this.streamDetail([		<div class="row">])
		if not empty( cBackColor )
			sColor = '<span style="background-color:'+cBackColor+';'
			if not empty ( cTextColor)
				sColor += 'color:'+cTextColor
			endif
			sColor += '">'
			this.streamDetail([			<div align="center">]+sColor+[<h3>]+cTitle+[</h3></span></div>])
		else
			this.streamDetail([			<div align="center"><h3>]+cTitle+[</h3></div>])
		endif
		this.streamDetail([		</div>])
		this.streamDetail([	</div>])

		// Blank Line:
		this.streamDetail( [	<!-- End of streamSubTitle -->] )
		this.streamDetail([ ])	// end of method: streamTitle

	// -----------------------------------------------------------------
	// streamDetail - streams out HTML line of code
	// -----------------------------------------------------------------
	Function streamDetail( cString )

		// cString is the string you want streamed out -- it can
		// contain HTML ...
		// NOTE: This should be called after the streamHeader() and streamBody() methods are called ...
		
		this.fOut.Puts( ""+cString )

	// end of method: streamDetail

	// -----------------------------------------------------------------
	// Revised methods of the CGISession class in Webclass.cc
	// -----------------------------------------------------------------

	// -----------------------------------------------------------------
	// streamBody - streams out the body tag and adds responsive code
	// -----------------------------------------------------------------
	Function streamBody(cBackImage, cBackColor, cTextColor, cLinkColor, cVLinkColor, cALinkColor)

		/*
			This streams out the HTML body tag used at the top
			of an HTML document ... however, note that you can 
			pass the colors that you want to display in your
			HTML document ... all settings for a page get set here.
			Note that the colors must use either standard named
			colors (e.g., "BLUE", "LIGHTBLUE", etc.) or standard
			hexidecimal colors with the "#" in front of them (e.g.,
			"#999999" which is white) 

			If the color parameters are left out, the default is a
			white page with black text, etc.

			Parameters:
				cBackImage -- background image used on the form
				cBackColor -- background color (under the image, if any)
				cTextColor -- text color -- this is important for text
								on top of a background color ...
				cLinkColor -- default color of links
				cVLinkColor -- Viewed links (once that have been clicked on
								by a user)
				cAlinkColor -- Active link (if the cursor is over the link
								and the mouse button is pressed ...)
		*/

		// note that you can set defaults in the constructor code:
		cBackImage  = iif(empty(cBackImage),  this.BackgroundImage, cBackImage )
		cBackColor  = iif(empty(cBackColor),  this.BackgroundColor, cBackColor )
		cTextColor  = iif(empty(cTextColor),  this.TextColor,	   cTextColor )
		cLinkColor  = iif(empty(cLinkColor),  this.LinkColor,	   cLinkColor )
		cVLinkColor = iif(empty(cVLinkColor), this.VLinkColor,	  cVLinkColor )
		cALinkColor = iif(empty(cALinkColor), this.ALinkColor,	  cALinkColor )

		cOut = ''
		if not empty( cBackImage )
			// Strip out path to extract filename
			if at('\',cBackImage) > 0
				cBackImage = substr(cBackImage,rat('\',cBackImage)+1)
			endif
			// Strip out path alias to extract filename
			if at(':',cBackImage) > 0
				cBackImage = substr(cBackImage,rat('\',cBackImage)+1)
			endif		 
			cOut += [ background: url(]+cBackImage+[);]
		endif
		if not empty( cBackColor )
			cOut += [ background-color:]+this.HTMLColor(cBackColor)+[;]
		endif
		if not empty( cTextColor )
			cOut += [ color:]+cTextColor+[;]
		endif
		if not empty( cLinkColor )
			cOut += [ a:link: {color:]+cTextColor+[;}]
		endif
		if not empty( cVLinkColor )
			cOut += [ a:visited {color:]+cVLinkColor+[;}]
		endif
		if not empty( cALinkColor )
			cOut += [ a:active {color:]+cALinkColor+[;}]
		endif

		if not empty ( cOut)
			cBodyOut = [<body style="]+cOut+[">]
		else 
			cBodyOut = [<body>]
		endif
		this.streamDetail( cBodyOut )
		this.streamDetail([<!-- End of streamBody -->])
		this.streamResponsiveSetup()

	// end of method: streamBody

	// -----------------------------------------------------------------
	// streamFormBegin - streams out the form tag
	// -----------------------------------------------------------------
	Function streamFormBegin( cCGI )
		/*
			The form tag needs to know what to do when
			the user submits it, hence the "cCGI" parameter ... 
			if this is left out, the form will not actually
			do anything if you click the submit button ... it will
			work, but ...

			Your CGI/Action ought to be something like:
			 "/cgi-bin/myprog.exe" 
		*/
		cOut = [<form method="POST" ]
		if pCount() => 1
			cOut += [action="]+cCGI+["]
		endif
		cOut += [>]

		this.streamDetail( cOut )
		this.streamDetail([<!-- End of streamFormBegin -->])
	// end of method: streamFormBegin

	// -----------------------------------------------------------------
	// streamFormEnd - streams out the end form tag
	// -----------------------------------------------------------------
	Function streamFormEnd
		this.streamDetail( "</form>" )
		this.streamDetail( [<!-- End of streamFormEnd -->] )
	// end of method: streamFormEnd

	// -----------------------------------------------------------------
	// streamText - streams out Form controls:
	// -----------------------------------------------------------------
	Function streamText( cName, nSize, cValue, nMaxLength )
		/*
		The 'text' control for HTML forms is
		the same as an entryfield ...
		Parameters:
			cName is the name -- if you want this to match a field,
				make sure you use *exact* case every time you 
				reference this, as the case is vital to the way 
				the web classes work (they are subclassed from 
				an Associative Array, and it is case sensitive).
			nSize is the width of the entryfield on the form --
				the size is character based ...
			cValue is the value you want to display -- could
				be the contents of the field
			nMaxLength is optional, and is the maximum amount
				of data you want to allow to be entered.
		*/
		cOut = [<input type="TEXT" name="]+cName+["]
		if pCount() > 1
			cOut+= [ size=]+nSize
		endif
		if pCount() > 2
			cOut+= [ value="]+cValue+["]
		endif
		if pCount() > 3
			cOut+= [ maxlength=]+nMaxLength
		endif
		cOut+= [>]
		this.streamDetail( cOut )
		this.streamDetail( [<!-- End of streamText -->] )
	// end of method: streamText

	// -----------------------------------------------------------------
	// streamPassword - streams out password form controls
	// -----------------------------------------------------------------
	Function streamPassword( cName, nSize, cValue, nMaxLength )
		/*
			the password control in HTML is exactly the same
			as the text control, except that a password mask
			is displayed for each character entered, rather
			than displaying the character.

			The parameters for this control are exactly the
			same as the text control -- see comments for
			this above.
		*/
		cOut = [<input type="PASSWORD" name="]+cName+["]
		if pCount() > 1
			cOut+= [ size=]+nSize
		endif
		if pCount() > 2
			cOut+= [ value="]+cValue+["]
		endif
		if pCount() > 3
			cOut+= [ maxlength=]+nMaxLength
		endif
		cOut+= [>]
		this.streamDetail( cOut )
		this.streamDetail( [<!-- End of streamPassword -->] )
	// end of method: streamPassword

	// -----------------------------------------------------------------
	// streamRadio - streams out radio button form controls
	// -----------------------------------------------------------------
	Function streamRadio( cName, cValue, cText, lChecked )
		/*
		 radio controls in HTML use multiple controls
		 just like in dBASE. In HTML they are grouped
		 by the NAME property (cName parameter)
		 The VALUE is returned for whichever radiobutton
		 was checked when the HTML form was submitted ...

		 Parameters:
			 cName --	as text control
			 cValue --	value returned for radiobutton selected
			 cText --	text that appears to the right of the 
						 radiobutton
			 lChecked -- is this radiobutton defaulting to 
						 a 'selected' state?
		*/
		cOut = [<input type="RADIO" name="]+cName+["]
		cOut+= [ value="]+cValue+["]
		if pCount() > 3 and lChecked 
			cOut+= [ checked]
		endif
		cOut+= [>]
		if pCount() > 2
			cOut+= cText
		endif
		this.streamDetail( cOut )
		this.streamDetail( [<!-- End of streamRadio -->] )
	// end of method: streamRadio

	// -----------------------------------------------------------------
	// streamCheckbox - streams out checkbox form controls
	// -----------------------------------------------------------------
	Function streamCheckbox( cName, cValue, cText, lChecked )
		/*
			the Checkbox works similar to a dBASE checkbox,
			the 'value' is actually the text assigned to
			the checkbox. Note that the HTML checkbox 
			returns the value property, not a true or false
			property ... when checking your return values
			you would want to check for the value -- i.e.,
			if you pass a value property to this method
			of "A Test", and a name of "MyCheckbox", 
			if the checkbox is checked, the value returned is:
				 oCGI["MyCheckbox"] = "A Test"
			OTHERWISE, the name is not returned at all -- 
			so your code would need to check for the existance
			of the key:
				 if oCGI.isKey( "MyCheckbox" )
					// it was checked and the value was passed on
				 endif
			Parameters:
			 cName --	as text control
			 cValue --	value returned if checkbox was checked
			 cText --	text that appears to the right of the 
						 Checkbox
			 lChecked -- is this checkbox defaulting to a 'checked' 
						 state?
		*/
		cOut = [<input type="CHECKBOX" name="]+cName+["]
		cOut+= [ value="]+cValue+["]
		if pCount() > 3 and lChecked 
			cOut+= [ checked]
		endif
		cOut+= [>]
		if pCount() > 2
			cOut+= cText
		endif
		this.streamDetail( cOut )
		this.streamDetail( [<!-- End of streamCheckbox -->] )
	// end of method: streamCheckbox

	// -----------------------------------------------------------------
	// streamReset - streams out RESET button form controls
	// -----------------------------------------------------------------
	Function streamReset( cText )
		/*
		 The reset button simply clears out the values in
		 an HTML form ...
		 Parameters: cText -- text displayed on the button
							  this is optional ...
		*/
		cOut = [<input type="RESET"]
		if pCount() => 1 // anything beyond 1 is ignored
			cOut+= [ value="]+cText+["]
		endif
		cOut+= [>]
		this.streamDetail( cOut )
		this.streamDetail( [<!-- End of streamReset -->] )
	// end of method: streamReset

	// -----------------------------------------------------------------
	// streamSubmit - streams out SUBMIT button form controls
	// -----------------------------------------------------------------
	Function streamSubmit( cText )
		/*
		 The submit button submits the contents of the form 
		 Parameters: cText -- text displayed on the button
							  this is optional ...
		*/
		cOut = [<input type="SUBMIT"]
		if pCount() => 1 // anything beyond 1 is ignored
			cOut+= [ value="]+cText+["]
		endif
		cOut+= [>]
		this.streamDetail( cOut )
		this.streamDetail( [<!-- End of streamSubmit -->] )
	// end of method: streamSubmit

	// -----------------------------------------------------------------
	// streamTextArea - streams out TextArea form controls
	// -----------------------------------------------------------------
	Function streamTextArea( cName, nRows, nCols, cValue )
		/*
			A textArea control in HTML is like an editor control
			In HTML this one actualy has two tags -- the second
			is used after the default value ...

			Parameters:
				cText -- as other controls except the buttons
						 reset/submit
				nRows -- this is the number of rows to take up
						 on a form -- it does not limit the
						 input, just the display
				nCols -- same as nRows -- how many columns to use.
				cValue -- optional, this is a default value if
						 adding data, or the contents of the field
						 you want to display ...
		*/
		cOut = [<textarea name="]+cName+["]
		if pCount() > 1
			cOut+= [ rows=]+nRows
		endif
		if pCount() > 2
			cOut+= [ cols=]+nCols
		endif
		cOut+= [>]
		this.streamDetail( cOut )
		if pCount() > 3
			this.streamDetail( cValue )
		endif
		cOut = "</textarea>"
		this.streamDetail( cOut )
		this.streamDetail( [<!-- End of streamTextArea -->] )
	// end of method: streamTextArea

	// --------------------------------------------------
	// The select control is interesting and different
	// from all the other HTML controls ... takes
	// some extra work. For our purposes, it's easier
	// to break it out into parts, allowing you to define
	// your SELECT in as much detail as you need ...
	// ex:
	//	 oCGI.streamSelectBegin( "MyList" )
	//	 oCGI.streamOption( "Option1", "Display this", true )
	//	 oCGI.streamOption( "Option2", "Display another" )
	//	 oCGI.streamSelectEnd()
	// --------------------------------------------------
	Function streamSelectBegin( cName, nSize, lMultiple )
		/*
			This method starts the Select object, but you must
			also call the streamOption method for each item
			you wish to appear in your SELECT, and when done
			you must call the streamSelectEnd() method to
			stuff the </SELECT> tag into the form ... (without
			that tag your form is hosed!)

			Parameters:
			 cName -- see other controls
			 nSize -- if size is zero, or omitted, 
					  you will get a combobox. If
					  the size is larger than that,
					  you will get a listbox ...
			 lMultiple -- allow multiple selections?
					  NOTE: If you select this, you 
					  will, by definition, get a listbox,
					  not a combobox ...
		*/
		cOut = [	<select name="]+cName+["]
		if pCount() > 1
			if nSize > 1
				cOut+= [ size=]+nSize
			endif
		endif
		if pCount() > 2 and lMultiple
			cOut+= [ multiple]
		endif
		cOut+= [>]
		this.streamDetail( cOut )
		this.streamDetail( [	<!-- End of streamSelectBegin -->] )
	// end of method: streamSelectBegin

	// -----------------------------------------------------------------
	// streamOption - streams out OPTION form controls
	// -----------------------------------------------------------------
	Function streamOption( cValue, cText, lSelected )
		/*
			This method streams out the individual options that
			are needed for your select control.

			cValue -- is the value returned when the form is submitted
					if this option is selected (required)
			cText  -- is the text displayed (it can be different from
					the value) (required)
			lSelected -- is this selected when the form is displayed 
					the first time?  (optional)
		*/
		cOut = [<option value="]+cValue+["]
		if pCount() > 2 and lSelected
			cOut+= [ selected]
		endif
		cOut+= [>]
		this.streamDetail( cOut )
		this.streamDetail( cText )
		this.streamDetail( [	<!-- End of streamOption -->] )
	// end of method: streamOption

	// -----------------------------------------------------------------
	// streamSelectEnd - streams out Select end form controls
	// -----------------------------------------------------------------
	Function streamSelectEnd
		// a simple method, this just makes sure we have
		// the end tag for the select option -- without it,
		// we have a messed up form ...
		this.streamDetail( "	</select>" )
		this.streamDetail( [	<!-- End of streamSelectEnd -->] )
	// end of method: streamSelectEnd()

	// -----------------------------------------------------------------
	// streamHidden - streams out Hidden form controls
	// -----------------------------------------------------------------
	Function streamHidden( cName, cValue )
		/*
		 The 'Hidden' control for HTML forms is
		 special -- the user never sees it, but it
		 can be used to pass persistant values
		 from one HTML form to another program;
		 etc.

		 Parameters:
			 cName is the name -- if you want this to match a field,
				make sure you use *exact* case every time you 
				reference this, as the case is vital to the way 
				the web classes work (they are subclassed from 
				an Associative Array, and it is case sensitive).
			 cValue is the value you want to 'Hide' -- could
				be the contents of the field
		*/
		cOut = [<input type="HIDDEN" name="]+cName+["]
		cOut+= [ value="]+cValue+[">]
		this.streamDetail( cOut )
		this.streamDetail( [<!-- End of streamHidden -->] )
	// end of method: streamHidden

	// -----------------------------------------------------------------
	// errorMessage - create error message text stream out to current page
	// -----------------------------------------------------------------
	Function errorMessage( oErr, cTitleBackColor, cTitleTextColor )
		/*
			errorMessage was created after discovering that
			in some cases, an error caught by a try/catch
			will attempt to create a new page in the already
			streamed page, which causes one of two problems:
			 1) It just plain looks wrong (the information
				streamed out in the streamHeader() messes things 
				up)
			 2) The second body tag causes the browser to not 
				display anything at all ...

			The purpose of this method then, is to display an error
			after your header and body tags have been streamed
			out to your user. 

			I copied code from sorryPage() and errorPage()
			where it seemed appropriate ...

			Use: 
			 After your form's header/body have been streamed
			 out, wrap the rest of your program in a try/catch
			 and when an error occurs, pass it to *this* method
			 instead ...

			The parameters are:
			oErr: The error object generated. 
			cTitleBackColor: this is the background of the title
				 displayed across the top of the page --
				 if none is given, this method will use
				 the defaults set for the class.
			cTitleTextColor: this is the color of the text used
				 in the title at the top of the page ... as
				 above.
		*/

		// make sure we have something besides a null for
		// the streamTitle() method:
		cTitleBackColor = iif( empty( cTitleBackColor), "", cTitleBackColor )
		cTitleTextColor = iif( empty( cTitleTextColor), "", cTitleTextColor )

		aError = new array()	// Create array for messages
		with (aError)
			add('File: '+oErr.Filename)	 // Add file/line and message
			add('Line No:'+oErr.LineNo)
			add(''+oErr.Message)
		endwith

		cRecover = 'Contact the WebMaster'

		// stream title -- using the title, it will be big and bold
		// and grab someone's attention
		this.streamTitle( "Error!", cTitleBackColor, cTitleTextColor )

		this.streamDetail( '<p style="text-align:center;">' )
		////// Traverse the message array
		for n = 1 to aLen(aError,1)
			this.streamDetail( aError[n] )
			this.streamDetail( '<br />' )
		next
		this.streamDetail( '</p>' )

		// stream out the recover message:
		this.streamDetail( '<p style="text-align:center;">'+cRecover+'</p>' )

		// stream the end of the HTML document
		this.streamFooter()

		// stop the application here
		quit
	// end of method: errorMessage

	// -----------------------------------------------------------------
	// errorPage - streams out default error page with error message text
	// -----------------------------------------------------------------
	Function errorPage( oErr, cTitleBackColor, cTitleTextColor )
		/*
			errorPage is modified in the same way that
			sorryPage is below ...

			The parameters are:
			oErr: The error object generated. 
			cTitleBackColor: this is the background of the title
				 displayed across the top of the page --
				 if none is given, this method will use
				 the defaults set for the class.
			cTitleTextColor: this is the color of the text used
				 in the title at the top of the page ... as
				 above.

			Note: this uses the sorryPage() method to display
			the error ...
		*/

		// make sure we have something besides a 'null' to pass across
		// to sorryPage
		cTitleBackColor = iif( empty( cTitleBackColor), "", cTitleBackColor )
		cTitleTextColor = iif( empty( cTitleTextColor), "", cTitleTextColor )

		aError = new array()				// Create array for messages

		with (aError)
			add('File: '+oErr.Filename)	 // Add file/line and message
			add('Line No:'+oErr.LineNo)
			add(''+oErr.Message)
		endwith

		cRecover = 'Contact the WebMaster'
		// Call sorryPage() to display  
		this.sorryPage(aError,'An error ocurred on the server',cRecover,;
						cTitleBackColor, cTitleTextColor )

	return true
	// end of method: errorPage

	// -----------------------------------------------------------------
	// sorryPage - streams out warning page with message text
	// -----------------------------------------------------------------
	Function sorryPage(cMsg, cSubTtl, cRcvr, cTitleBackColor, cTitleTextColor )
		/*
			SorryPage is modified to use other methods here ...
			The parameters are:
			cMsg: this is the message you want to display
				for the user -- note that it can be an array --
				the message can then have multiple lines, and
				so on, and the message can have HTML embedded in it
			cSubTtl: this is the subtitle -- a title under the
				"sorry" heading
			cRcvr: this is the recovery message -- it explains
				 to the user what to do to recover from the
				 error ...
			cTitleBackColor: this is the background of the title
				 displayed across the top of the page --
				 if none is given, this method will use
				 the defaults set for the class.
			cTitleTextColor: this is the color of the text used
				 in the title at the top of the page ... as
				 above.
		*/
  
		aMsg = iif(empty(cMsg),'',cMsg)

		if type('aMsg') = 'C'	// if message is not array,
			aMsg = new array()	// convert to a one-element array
			aMsg.add(cMsg)
		endif
		// Make private versions for type()
		cRecover =  iif(empty(cRcvr),'Press Back button and try again.',cRcvr)  
		cSubtitle = iif(empty(cSubTtl),'',cSubTtl)

		/////// stream out header
		this.streamHeader('Sorry!')

		////// Body tag (starts body of page)  
		this.streamBody()

		// stream out the title:
		cTitleBackColor = iif( empty( cTitleBackColor), this.TitleBackColor, cTitleBackColor )
		cTitleTextColor = iif( empty( cTitleTextColor), this.TitleTextColor, cTitleTextColor )
		this.streamTitle( "Sorry!", cTitleBackColor, cTitleTextColor )

		////// Subtitle
		if not empty( cSubTitle )
			this.streamSubTitle( cSubTitle, cTitleBackColor, cTitleTextColor )
		endif

		////// Traverse the message array
		cMsgStr = '<p style="text-align:center">'
		for n = 1 to aLen(aMsg,1)
			cMsgStr += aMsg[n]
			cMsgStr += '<br />'
		next
		cMsgStr += '</p>'
		this.streamDetail( '<p style="text-align:center">' + cMsgStr + '</p>' )

		////// If recovery instructions exist (Contact Webmaster, Press Back
		////// 		Button, etc.), stream it here
		if not empty(cRecover)
			this.streamDetail( '<p style="text-align:center">' + cRecover + '</p>' )
		endif

		// and stream out the end of the HTML document
		this.streamFooter()

		// if we output this page, the mini-application is done: 
		quit

	// end of method: SorryPage


	////// Method: 	streamHeader //////////////////////////////////////////////
	////// Purpose		Streams the CGI and HTML headers required  ////////////////
	//////				to send a response page back through the server ///////////
	////// Param:		Title - the page title desired ////////////////////////////

	Function streamHeader(Title)
		cTitle = iif(empty(Title),'Response Page',Title)

		this.streamDetail('Content-type: text/html')
		this.streamDetail('')
		this.streamDetail('<HTML>')
		this.streamDetail('')
		this.streamDetail('<HEAD>')
		this.streamDetail('	<META HTTP-EQUIV="Content-Type" CONTENT="text/html">')
		this.streamDetail('	<TITLE>'+cTitle+'</TITLE>')
		this.streamDetail('</HEAD>')
		this.streamDetail('')

		return true
	// End of streamHeader

	////// Method: streamFooter /////////////////////////////////////////////////
	//////		 Streams out closing tags for header/title block for //////////
	//////		 HTML Response page sent back to user//////////////////////////

	Function streamFooter

		this.streamDetail('')
		this.streamDetail('	</body>')
		this.streamDetail('</HTML>')

		return true
	// End of streamFooter


	////// Method: 	streamResponsiveHeader //////////////////////////////////////////////
	////// Purpose		Streams the CGI and HTML headers required  ////////////////
	//////				to send a response page back through the server ///////////
	////// Param:		Title - the page title desired ////////////////////////////

	Function streamResponsiveHeader(Title)
		cTitle = iif(empty(Title),'Response Page',Title)

		this.streamDetail('<!DOCTYPE html>')
		this.streamDetail('<html lang="en">')
		this.streamDetail('<head>')
		this.streamDetail('	  <meta charset="utf-8">')
		this.streamDetail('	  <title>'+cTitle+'</title>')
		this.streamDetail('  <meta name="generator" content="dBASE PLUS 11 Web Wizard">')
		this.streamDetail('  <meta name="description" content="HTML output generated by dBASE PLUS 11 Web Wizard">')
		this.streamDetail('  <meta name="author" content="dBase LLC">')
		this.streamDetail('  <meta name="viewport" content="width=device-width, initial-scale=1">')
		this.streamDetail('  <link href="//fonts.googleapis.com/css?family=Raleway:400,300,600" rel="stylesheet" type="text/css">')
		this.streamDetail('  <link rel="stylesheet" href="/css/normalize.css">')
		this.streamDetail('  <link rel="stylesheet" href="/css/skeleton.css">')
		this.streamDetail('  <link rel="icon" type="image/x-icon" href="/images/favicon.ico">')
		this.streamDetail('</head>')
		this.streamDetail('')

		return true
	// End of streamResponsiveHeader


	////// Method: streamResponsiveSetup /////////////////////////////////////////////////
	//////		 Streams out javascript to load resonsive CSS files must be placed below BODY tag //////////
	//////		 when you can't have use streamResponsiveHeader, i.e. in a CGI program  //////////
	//////		 HTML Response page sent back to user //////////////////////////

	Function streamResponsiveSetup

		this.streamDetail(" ")		this.streamDetail( [	<!-- Start of streamResponsiveSetup -->] )		this.streamDetail(" ")
		this.streamDetail([	<script type="text/javascript">])
		this.streamDetail([		function loadCSS(filename){])
		this.streamDetail([			var file = document.createElement("link");])
		this.streamDetail([			file.setAttribute("rel", "stylesheet");])
		this.streamDetail([			file.setAttribute("type", "text/css");])
		this.streamDetail([			file.setAttribute("href", filename);])
		this.streamDetail([			document.head.appendChild(file);])
		this.streamDetail("			}")
		this.streamDetail([		loadCSS("css/normalize.css");])
		this.streamDetail([		loadCSS("css/skeleton.css");])
		this.streamDetail([	</script>])
		this.streamDetail(" ")
		
		this.streamDetail( [	<!-- End of streamResponsiveSetup -->] )
		this.streamDetail(" ")		return true
	// End of streamResponsiveSetup


	////// Method: streamLogo /////////////////////////////////////////////////
	//////		 Streams out logo for page  //////////
	//////		 HTML Response page sent back to user //////////////////////////

	Function streamLogo(cLogo,cAlign)
	
		cLogo = iif(empty(cLogo), this.defaultLogo, cLogo)
		cAlign = iif(empty(cAlign), 'center',cAlign)

		////// Setup Logo
		this.streamDetail([ ])		this.streamDetail( [	<!-- Start of streamLogo -->] )		this.streamDetail([ ])
		this.streamDetail([	<div class="container">])
		this.streamDetail([		<div class="row">])
		this.streamDetail([			<div align="]+cAlign+["><img style="height: auto; width: 100%;" src="]+cLogo+["></div>])
		this.streamDetail([		</div>])
		this.streamDetail([	</div>])
		this.streamDetail([ ])

		this.streamDetail( [	<!-- End of streamLogo -->] )
		this.streamDetail([ ])		return true
	// End of streamLogo

	////// Method: streamResponsiveCenter /////////////////////////////////////////////////
	//////		 Streams out single block of text in center of page	//////////
	//////		 HTML Response page sent back to user //////////////////////////

	Function streamResponsiveCenter(fText)
	
		////// Setup responsive block to center
		this.streamDetail([ ])		this.streamDetail( [	<!-- Start of streamResponsiveCenter -->] )		this.streamDetail([ ])
		this.streamDetail([	<div class="container">])
		this.streamDetail([		<div class="row">])
		this.streamDetail([			<div align="center">]+fText+[</div>])
		this.streamDetail([		</div>])
		this.streamDetail([	</div>])
		this.streamDetail([ ])

		this.streamDetail( [   <!-- End of streamResponsiveCenter -->] )
		this.streamDetail([ ])		return true
	// End of streamResponsiveCenter

	/////////// Method:  HTMLColor ////////////////////////////////////
	/////////// Purpose: converts VdB 7 Color to HTML Color ///////////
	//////////			 ex: 0x010203 to #030201 //////////////////////
	/////////// Param:   cColorStr - color expression /////////////////

	 Function HTMLColor(cColorStr)
		 if substr(cColorStr,1,2) = '0x'
			return '#'+substr(cColorStr,7,2)+;
					 substr(cColorStr,5,2)+;
					 substr(cColorStr,3,2)
		 endif
		 return cColorStr

	 //
	 // Simple function to take an existing SQL query and add a 
	 // restriction clause, i.e. "where orderid > 5" to it and 
	 // return it to the caller
	 //
	Function modSQLQuery (oQuery, oClause, oLocal)

		Local returnString
		Local remainingString
		Local queryElements
		Local elementLocations
		Local singleTableName
		Local stopLocation
		Local startLocation

		returnString = ''

		if empty(oQuery)
			returnString += 'Error: SQL Query is missing'
			return returnString
		endif

		if empty(oQuery)
			returnString += 'Error: SQL Query restriction clause is missing'
			return returnString
		endif

		remainingString = oQuery
		queryElements = new AssocArray( )
		elementLocations = new AssocArray( )

		// Check for sections starting
		elementLocations ['ORDERBY'] = rat('ORDER BY',upper(oQuery))
		elementLocations ['HAVING'] = rat('HAVING',upper(oQuery))
		elementLocations ['GROUPBY'] = rat('GROUP BY',upper(oQuery))
		elementLocations ['WHERE'] = rat('WHERE',upper(oQuery))
		elementLocations ['FROM'] = rat('FROM',upper(oQuery))
		elementLocations ['SELECT'] = rat('SELECT',upper(oQuery))

		// process sections
		queryElements ['ORDERBY'] = ''
		if elementLocations ['ORDERBY'] > 0
			queryElements ['ORDERBY'] = substr(oQuery, elementLocations ['ORDERBY'])
			// ? queryElements ['ORDERBY']
			remainingString = substr (oQuery, 1, elementLocations ['ORDERBY']-1)
		endif

		queryElements ['HAVING'] = ''
		if elementLocations ['HAVING'] > 0
			queryElements ['HAVING'] = substr(remainingString, elementLocations ['HAVING'])
			// ? queryElements ['HAVING']
			remainingString = substr (remainingString, 1, elementLocations ['HAVING']-1)
		endif

		queryElements ['GROUPBY'] = ''
		if elementLocations ['GROUPBY'] > 0
			queryElements ['GROUPBY'] = substr(remainingString, elementLocations ['GROUPBY'])
			// ? queryElements ['GROUPBY']
			remainingString = substr (remainingString, 1, elementLocations ['GROUPBY']-1)
		endif

		queryElements ['WHERE'] = ''
		if elementLocations ['WHERE'] > 0
			queryElements ['WHERE'] = substr(remainingString, elementLocations ['WHERE'])
			// ? queryElements ['WHERE']
			remainingString = substr (remainingString, 1, elementLocations ['WHERE']-1)
		endif

		queryElements ['FROM'] = ''
		if elementLocations ['FROM'] > 0
			queryElements ['FROM'] = substr(remainingString, elementLocations ['FROM'])
			// ? queryElements ['FROM']
			remainingString = substr (remainingString, 1, elementLocations ['FROM']-1)
		endif

		queryElements ['SELECT'] = ''
		if elementLocations ['SELECT'] > 0
			queryElements ['SELECT'] = substr(remainingString, elementLocations ['SELECT'])
			// ? queryElements ['SELECT']
		endif

		// return error if NOT Select statement or doesn't have FROM clause
		if elementLocations ['SELECT'] = 0
			returnString += 'Error: Not a SQL SELECT query'
			return returnString
		endif

		if elementLocations ['FROM'] = 0
			returnString += 'Error: SQL Query missing valid FROM clause'
			return returnString
		endif

		// add restricition to either HAVING clause or WHERE
		if elementLocations ['GROUPBY'] > 0 
			if elementLocations ['HAVING'] > 0
				queryElements ['HAVING'] += ' and ' + oClause + ' '
				// ? queryElements ['HAVING']
			else
				queryElements ['HAVING'] += ' HAVING ' + oClause + ' '
				// ? queryElements ['HAVING']
			endif
		elseif elementLocations ['WHERE'] > 0 
			queryElements ['WHERE'] += ' and ' + oClause + ' '
			// ? queryElements ['WHERE']
		else
			queryElements ['WHERE'] += ' WHERE ' + oClause + ' '
			// ? queryElements ['WHERE']
		endif

		// localize table if desired
		if oLocal
			if elementLocations ['WHERE'] > 0
				stopLocation = elementLocations ['WHERE'] - 2
			elseif elementLocations ['GROUPBY'] > 0
				stopLocation = elementLocations ['GROUPBY'] - 2
			elseif elementLocations ['ORDERBY'] > 0
				stopLocation = elementLocations ['ORDERBY'] - 2
			else
				stopLocation = LEN(oQuery)
			endif

			startLocation = elementLocations ['FROM']+6 // take away the first single or double quote around the db string
			singleTableName = substr(oQuery, startLocation, stopLocation - startLocation)
			// ? singleTableName
			singleTableName = substr(singleTableName,rat('\',singleTableName)+1)
			// reconstruct the FROM clause
			queryElements ['FROM'] = 'FROM "' + singleTableName + '" '
		endif
	
		// reset elementLocation for WHERE or HAVING clauses that have been modified
		elementLocations ['WHERE'] = len (queryElements ['WHERE'])
		elementLocations ['HAVING'] = len (queryElements ['HAVING'])

		// recontruct the entire revised SQL query and return it
		returnString = queryElements ['SELECT'] + queryElements ['FROM']
		if elementLocations ['WHERE'] > 0 
			returnString += queryElements ['WHERE']
		endif
		if elementLocations ['GROUPBY'] > 0 
			returnString += queryElements ['GROUPBY']
		endif
		if elementLocations ['HAVING'] > 0 
			returnString += queryElements ['HAVING']
		endif
		if elementLocations ['ORDERBY'] > 0 
			returnString += queryElements ['ORDERBY']
		endif

		return returnString

		// end of function modSQLQuery


	////// Method: PassDataThrough ///////////////////////////////////////////////
	////// 		  Passes through all received CGI data to the next form /////////

	/* Note: This method is used to "chain" Web pages together. For example,
		let's assume that you have an order page with a product lookup. Web pages
		can't go "back" unless the user clicks the Back button on the browser.
		So you -emulate- Windows-style behavior by calling the Product Lookup,
		then  a -new- copy of the invoice form with the lookup product code
		defaulted in.

		To get the data from the first Invoice Form, through the lookup form to
		the next version of the Invoice form, you have to pass along -every single
		bit of data that came in from the first form - to the Product Lookup form
		and then as controls to the final Invoice form.

		This method creates "hiddens" in the current HTML response page for all
		valid elements of this array. That data will then be sent automatically
		by the Web Browser to the next Web applet called from the page that
		carries the "pass through data". This method automatically generates the
		"hidden" HTML code required to pass a whole form of data to the next...
		and the next... etc.

		Usage: Call this method any time after streamBody() and before
	 			streamFooter()
	*/

	Function PassDataThrough

	 cKey  = this.firstKey

	 for n = 1 to this.count()

		this.fOut.Puts('<input type="HIDDEN" name="'+cKey+'" value="'+this[cKey]+'">')

		cKey = this.nextKey(cKey)

	 next



	////////////////////////// Database Classes //////////////////////////////////

	////// Method: 	LoadArrayFromFields //////////////////////////////////
	////// Purpose:	Loads existing assoc array with data from fields ///
	//////  				in a rowset passed as a param //////////////////////
	////// Params:		rowsetFields = query.rowset.fields object ref //////


	/* Note: All data methods in this class are based on name-matching.
				The HTML component name that sent the data must be an
			identical match to the name of the field, including CASE.
			In other words, name your city Entryfield CITY if the field
			in your table is named CITY! If you do this unfailingly, you
			can totally automate CGI reads into table fields and
			table fields out to HTML response pages.
	*/

	Function LoadArrayFromFields(rowsetFields)

		private rf, cKey
		local n

		rf = rowsetFields  // make "private" version of param

		cKey = this.FirstKey

		for n = 1 to this.count() // traverse this array

			// see if there's a field that matches the array index key
			// and see if it's empty or not.

			// if field exists
			if type('rf[cKey]') = 'O'

				// if field is not empty
				if empty(rf[cKey].value)

					this[cKey] = ""

				else

					// figure out its type and store it as value to array
					if rf[cKey].type = 'AUTOINC' or ;
						rf[cKey].type = 'NUMERIC'  or ;
						rf[cKey].type = 'LONG' or ;
						rf[cKey].type = 'INTEGER'  or ;
						rf[cKey].type = 'DOUBLE' or ;
						rf[cKey].type = 'FLOAT'
						this[cKey] = str(rf[cKey].value,;
									 rf[cKey].length,;
									 rf[cKey].decimalLength)
					elseif rf[cKey].type = 'DATE'
						this[cKey] = dtoc(value)
					elseif rf[cKey].type = 'LOGICAL'
						this[cKey] = iif(value,'TRUE','FALSE')
					elseif rf[cKey].type = 'BINARY' or ;
						rf[cKey].type = 'OLE'
						// do nothing
					else // char or memo
						this[cKey] = rf[cKey].value
					endif
				endif
			endif
			cKey = this.nextKey(cKey)
		next
		return true


	////// Method 	LoadFieldsFromArray ///////////////////////////////////////
	////// Purpose: 	load data from assoc array into rowset fields /////////////
	////// Params: 	rowset.fields /////////////////////////////////////////////

	/*		Note: This method may be called over and over again for any number
			of queries or rows within a query. It is, however, indiscriminate.
			If you run this for more than one rowset, it will attempt to
			update each and every matching field each time you run it.
			This may present a problem if you have two tables and you only
			want to update certain fields in one table and certain fields
			in the other.

			The field-related methods of this Web Class assume either:

				1.) Only one table is in use or
			2.) Each table has unique field names or
			3.) It's OK if more than one table has the same
				 field name and gets the same data.

			If you need to exercize care in opdating fields in one rowset
			but not in another, (for example customer->Custno and not
			invoice->Custno), we recommend you use the subclass:
			WebDMDClass.cc,, which lets you indicate both query and fieldname,
			not just fieldname as this method does. This method should
			accomodate most circumstances.
	*/

	Function loadFieldsFromArray(rowsetFields,bAppend)

		bAppend = iif(empty(bAppend),false,true)

		rf = rowsetFields  // Create private instance of param

		////// If bAppend, make new row
		if bAppend
			rf.parent.beginAppend()
		endif

		////// Establish first key
		cKey = this.firstKey

		///// Traverse the array
		for n = 1 to this.count()

			if type('rf[cKey]') = 'O'  // if field exists
				with (rf[ckey])		 // with (field)
					if type  = "LOGICAL"   // convert text to type and store
						value = iif(upper(this[cKey]) ='TRUE' or ;
								upper(this[cKey]) = 'Y',true,false)
					elseif type = "NUMERIC" or ;
						type = "INTEGER" or ;
						type = "DOUBLE" or ;
						type = "FLOAT" or ;
						type = "LONG"
						value = val(this[cKey])
					elseif type = "DATE"
						value = ctod(this[cKey])
					elseif type = "AUTOINC" or ;
						type = 'TIMESTAMP' or ;
						type = 'BINARY' or ;
						type = 'OLE'
					// do nothing!! Autoincrement is automatic
					// and the others not supported in HTML
					else // Char or memo
						value = this[cKey]
					endif
				endwith
			endif
			////// Go to next key
			cKey = this.nextKey(cKey)
		next

		if not rf.parent.endOfSet // if not end of set
			rf.parent.save()		 // save row
		endif

		return true



	////// Method: loadDatamoduleFromArray ////////////////////////////
	//////		 loads data in this array into the rowset.fields ////
	//////		 of the datamodule
	////// Param:	oDataMod - a reference to a DataModule object ///////
	////// 			bAppend - create new row?

	Function loadDataModuleFromArray(oDataMod,bAppend)

		if empty(oDataMod)
			return false // no datamodule to work with
		endif

		oD = oDataMod  // "Privatize param"  for use in macros

					 // ensure default of bAppand
		bAppend = iif(empty(bAppend),false,bAppend)

		////// This array will track which queries have already
		////// been appended so we don't create extra rows  or
		////// save rows unneccessarily

		aAppends = new array()

		////// Traverse this array of field/value pairs and update
		////// rowset fields with data

		cKey = this.firstKey  // init first key


		For n = 1 to this.Count()  // traverse entire array

			if at('*@',cKey) = 0	// if not a datamod pair
				cKey = this.nextKey(cKey)
				loop	// do next one
			endif

			////// extract query name
			cQuery = substr(cKey,1,at("*@",cKey)-1)


			////// Prepare macro to test query
			cStr = 'oD.'+cQuery

			////// See if query exists
			Try
				oQuery = &cStr.
			catch (exception e)
				// this query does not exist!
				cKey = this.nextKey(cKey)
				loop
			endTry


			if type('oQuery.rowset') # 'O'
				// Rowset not valid???
				cKey = this.nextKey(cKey)
				loop
			endif

			////// See if this query has been appended or edited
			////// if not, append row if bAppend = true
			if aAppends.scan(upper(cQuery))  = 0
				aAppends.add(upper(cQuery)) // add to query array
				if bAppend
					oQuery.rowset.beginAppend()
				endif
			endif

			////// extract field name
			cField = substr(cKey,at("*@",cKey)+2)

			if type('oQuery.rowset.fields[cField]') # 'O' // if no such field exists
				// loop and try the next
				cKey = this.nextKey(cKey)
				loop
			endif

			with (oQuery.rowset.fields[cField])		 // with (field)
				////// Convert from string to type and update field value
				if type 	= "LOGICAL"
					value  = ;
					iif(upper(this[cKey]) = 'TRUE' or upper(this[cKey]) = 'Y',true,false)
				elseif type = "NUMERIC" or ;
					type = "INTEGER" or ;
					type = "LONG" or ;
					type = "DOUBLE" or ;
					type = "FLOAT"
					value = val(this[cKey])
				elseif type = "DATE"
					value = ctod(this[cKey])
				elseif type = 'AUTOINC' or ;
					type = 'TIMESTAMP'  or ;
					type = 'BINARY' or ;
					type = 'OLE'
					// do nothing, not supported in HTML
				else 	// char or memo
					value = this[cKey]
				endif
			endwith
			cKey = this.nextKey(cKey)
		Next

		////// Traverse all queries and save rows
		for n = 1 to alen(aAppends,1)
			cQuery = aAppends[n]
			cStr = 'oD.'+cQuery  // Prepare string for macro
			try
				oQuery = &cStr.   // Get reference for this query
			catch (exception e)
				loop			  // if fail, go to next
			endTry
			if not oQuery.rowset.endOfSet
				oQuery.rowset.save() // save row.
			endif
		next

EndClass

