function ASPen_ClientLibraryRelease()
{
	// I just bumpted this from 1.0 to 1.1.  It's not used by anyone, however.
	return '1.1';
}

// Called by a page to validate a form.
// formObj: The DOM form object.
// nElements: Number of elements in the element metadata list.
// aParams: A string containing ASCII characters that represent LIB_? parameters for the field
// aLabel: The field's english caption
// aName: The offical HTML NAME of the field in the DOM.
function ASPen_ValidateForm( formObj, nElements, aParams, aLabel, aName )
{
	// alert(nElements);
	
	// The following metadata describes validation regular expressions.
	//     LIB		= the internal ASCII character representing LIB_?.
	//     re		= the regular expression that is evaluated if the LIB character is present for the form field.
	//	   message	= the string that is appended to the alert dialog message if the validation fails.
	// To add a new regular expression, add a new 'set' to the end of the sets.
	var nbrPatterns = 0
	var aPatterns = new Array();
    aPatterns[nbrPatterns  ] = new Object();
	aPatterns[nbrPatterns  ].LIB		= /e/;		// LIB_EMAIL(e)
	aPatterns[nbrPatterns  ].re			= /^([0-9a-z_&.+-]+!)*[0-9a-z_&.+-]+@(([0-9a-z]([0-9a-z-]*[0-9a-z])?\.)+[a-z]{2,3}|([0-9]{1,3}\.){3}[0-9]{1,3})$/i;
	aPatterns[nbrPatterns++].message	= ' must be a valid email address';

	//aPatterns[nbrPatterns  ] = new Object();
	//aPatterns[nbrPatterns  ].LIB		= /s/;		// LIB_STATE(s)
	//aPatterns[nbrPatterns  ].re			= /^(A[AEKLPRSZ]|C[AOT]|DC|DE|FL|FM|GA|GU|HI|I[ADLN]|KS|KY|LA|M[ADEHINOPST]|N[CDEHJMVY]|O[HKR]|P[ARW]|RI|SC|SD|T[NTX]|UT|V[AIT]|W[AIVY])$/i;
	//aPatterns[nbrPatterns++].message	= ' must be a valid state abbreviation';

	// HELP: overriden by next set for a while until I can fix this one... see comment on .message member.
	//aPatterns[nbrPatterns  ] = new Object();
	//aPatterns[nbrPatterns  ].LIB		= /5/;		// LIB_ZIP5(5)
	//aPatterns[nbrPatterns  ].re			= /^d(5)([-]?d(4))?$/;
	//aPatterns[nbrPatterns  ].message	= ' must be a valid zip code';   // palvin: BUG/HELP: REMOVED ++ can't get past zip.  The re is broken or something so for now I just commented it out (ooops).

	//aPatterns[nbrPatterns  ] = new Object();
	//aPatterns[nbrPatterns  ].LIB		= /p/;		// LIB_PHONE(o)
	//aPatterns[nbrPatterns  ].re			= /^\(?\d{3}\)?\D?\d{3}\D?\d{4}$/;
	//aPatterns[nbrPatterns++].message	= ' must be a valid area code and phone number';

	var bOkay = true;			// innocent until proven guilty
	var i, b, sMessage, sLast;
	sMessage = '';				// alert message string that is appended to.
	sLast = '';					// the value of the PREVIOUS form element (used to see if passwords identical)

	// iterate only the elements in the element metadata list, which is probably shorter than the real number of elements on the page.
	for (i=1; i<=nElements; i++)
	{
		//alert( 'name=' + aName[i] + ' label=' + aLabel[i] + ' params=' + aParams[i] + ' type=' + formObj[aName[i]].type );
		// DNW alert( ' value=' + ckValue(formObj[aName[i]]) );  WHERE DID chValue go?

		// The following tests for an internal error.
		if ( formObj[aName[i]].type == null ) // "undefined"
		{
			alert('INTERNAL ERROR: Two form fields (' + aName[i] + ') have the same field NAME.');
			sMessage += 'internal error\n';
		}
    	
		// This section tests INPUT TYPE=TEXT or TYPE=PASSWORD fields
		if (( /text/.test(formObj[aName[i]].type) ) || ( /password/.test(formObj[aName[i]].type) ) || ( /file/.test(formObj[aName[i]].type) ))
		{
			// Manual LIB_REQUIRED(*) check for making sure a required field was entered.
			if ( /\*/.test(aParams[i]) && !formObj[aName[i]].value )
			{
				sMessage += aLabel[i] + ' is a required field.\n';
			}
			// Manual LIB_CONFIRM(c) check for making sure two passwords are identical.
			else if ( /c/.test(aParams[i]) && formObj[aName[i]].value != sLast )
			{
				sMessage += aLabel[i] + ' must match ' + aLabel[i-1] + '.\n';
			}
			else
			{
				// Manual LIB_NBR(#) check for making sure value entered is a number.
				// Only test if a value was entered in the field.
				if ( formObj[aName[i]].value.length && /\#/.test(aParams[i]) && !formObj[aName[i]].value.match(/[0-9]/) )
				{
					sMessage += aLabel[i] + ' must be a number.\n';
				}
				
				//Check date text box for a valid date
				if ( formObj[aName[i]].value.length && /D/.test(aParams[i]) && !chkdate(formObj[aName[i]].value))
				{
					sMessage += aLabel[i] + ' is a bad date.\n';
				}

				// At this point all of the manual checks have been performed.
				// Now, iterate over all of the regular expressions defined at the top of this file.
				// Only test if a value was entered in the field.
				for (var j=0; j<nbrPatterns; j++)
				{
					// alert( 'i=' + i + ' j=' + j + ' (test: ' + aPatterns[j].message + ') RE: params=' + aParams[i] + ' value=' + formObj[aName[i]].value + ' FIRST='+aPatterns[j].LIB.test(aParams[i]) + ' SECOND='+aPatterns[j].re.test(formObj[aName[i]].value) );

					if ( formObj[aName[i]].value.length && aPatterns[j].LIB.test(aParams[i]) && !aPatterns[j].re.test(formObj[aName[i]].value) )
					{
						sMessage += aLabel[i] + aPatterns[j].message + '.\n';
					}
				}
			}

			// Save the current value for the next iteration (used for password verification)
			sLast = formObj[aName[i]].value;
		}
		// This section tests INPUT TYPE=SELECT fields
		else if ( /select/.test(formObj[aName[i]].type) )
		{
			// Manual LIB_REQUIRED(*) check for making sure the user selected an item other than the first item (which usually says "Select a State" or something).
			if ( /\*/.test(aParams[i]) && formObj[aName[i]].selectedIndex == 0 )
			{
				sMessage += aLabel[i] + ' is a required field.\n';
			}
		}
	}

	// Only raise an error if failed validation messages were appeneded to the sMessage string.

	if ( sMessage.length )
	{
		alert( 'Please correct the following problems:\n\n' + sMessage );
		return 0;
	}
	else
	{
		return 1;
	}
}