/*
 * form-validation
 *
 * functions for validating string encoded data and forms.
 *
 * (c) Businessmart AG 2005
 * @author Andreas Geissel
 */

// ----------------------------------------------------------------------
/*
 * global definitions.
 */
var _vld =
{
  'zip_pattern'      : /*[S-ZIP]*/'^(.*)$'/*[E-ZIP]*/,
  'time_pattern'     : /*[S-TIME]*/'HH:MM'/*[E-TIME]*/,
  'date_pattern'     : /*[S-DATE]*/'dd.mm.yyyy'/*[E-DATE]*/,
  'currency_pattern' : /*[S-CURR]*/'^[\\-+]?\\d+,\\d\\d$'/*[S-CURR]*/,
  'soft_check'       : false,

  'error_color'      : "#ffa27d",
  'warn_color'       : "#ffe29d" 
};

// ----------------------------------------------------------------------
/**
 * set a string regex as pattern to check zip numbers.
 * @param aPattern string regex
 */
function setZipPattern(aPattern)
{
  if (aPattern && (aPattern.toString().length > 0))
  {
    _vld.zip_pattern = aPattern;
  }
}

// ----------------------------------------------------------------------
/**
 * @return zip pattern regex.
 */
function getZipPattern()
{
  return _vld.zip_pattern;
}

// ----------------------------------------------------------------------
/**
 * @param aPattern the regexp string to be used as
 *        currency pattern.
 */
function setCurrencyPattern(aPattern)
{
  if (aPattern && (aPattern.toString().length > 0))
  {
    _vld.currency_pattern = aPattern;
  }
}

// ----------------------------------------------------------------------
/**
 * @return the regexp string to be used as currency pattern.
 */
function getCurrencyPattern()
{
  return _vld.currency_pattern;
}

// ----------------------------------------------------------------------
/**
 * set a string color to use as background for invalid
 * input fields.
 * @param theErrorColor a color like #ffa27d
 */
function setErrorColor(aColor)
{
  if (aColor)
  {
    _vld.error_color = aColor;
  }
}

// ----------------------------------------------------------------------
/**
 * @return error background color for invalid input fields.
 */
function getErrorColor()
{
  return _vld.error_color;
}

// ----------------------------------------------------------------------
/**
 * set a string color to use as background for invalid
 * input fields.
 * @param theErrorColor a color like #ffa27d
 */
function setWarningColor(aColor)
{
  _vld.warn_color = aColor;
}

// ----------------------------------------------------------------------
/**
 * @return waring background color for invalid input fields.
 */
function getWarningColor()
{
  return _vld.warn_color;
}

// ----------------------------------------------------------------------
/**
 * set a string regex as pattern to check date pattern.
 * @param aPattern string regex
 */
function setDatePattern(aPattern)
{
  if (aPattern && (aPattern.toString().length > 0))
  {
    _vld.date_pattern = aPattern;
  }
}

// ----------------------------------------------------------------------
/**
 * @return date pattern regex.
 */
function getDatePattern()
{
  return _vld.date_pattern;
}

// ----------------------------------------------------------------------
/**
 * set a string regex as pattern to check time pattern.
 * @param aPattern string regex
 */
function setTimePattern(aPattern)
{
  if (aPattern && (aPattern.toString().length > 0))
  {
    _vld.time_pattern = aPattern;
  }
}


// ----------------------------------------------------------------------
/**
 * @return time pattern regex.
 */
function getTimePattern()
{
  return _vld.time_pattern;
}

// ----------------------------------------------------------------------
/**
 * @return soft check flag.
 */
function isSoftCheck()
{
  return _vld.soft_check;
}

// ----------------------------------------------------------------------
/**
 * set flag to activate or deactivate soft check.
 * @param theSoftCheckFlag
 */
function setSoftCheck(theSoftCheckFlag)
{
  _vld.soft_check = theSoftCheckFlag;
}

// ----------------------------------------------------------------------
/**
 * check if value is a currency.
 * @param aValue the value to be checked
 * @param aFormatString the format or null
 * @return true if value represents a currency
 */
function isCurrency(aValue, aFormatString)
{
  if (!isEmpty(aValue))
  {
    if (isEmpty(aFormatString))
    {
      aFormatString = getCurrencyPattern();
    }

    var numRegExp = new RegExp(aFormatString);
    return numRegExp.test(aValue);
  }
}

// ----------------------------------------------------------------------
/**
 * return true if the string parameter represents an integer.
 * @param aString a string that could represent an integer
 * @param aRangeString a range (format: lower..upper)
 * @return true if the parameter represents an integer
 */
function isInteger(aValue, aRangeString)
{
  if (!isEmpty(aValue) && aValue.toString().match(/^[\-\+]?\d+$/))
  {
    if (!isEmpty(aRangeString) && aRangeString.match(/(\d+)\.\.(\d+)/))
    {
      var min    = parseInt(RegExp.$1);
      var max    = parseInt(RegExp.$2);
      var intVal = parseInt(aValue);

      return (min <= intVal) && (intVal <= max);
    }
    return true;
  }

  return false;
}

// ----------------------------------------------------------------------
/**
 * return true if the string parameter represents a number.
 * @param aString a string that could represent a number
 * @return true if the parameter represents a number.
 */
function isNumber(aValue)
{
  return !isEmpty(aValue)
      && aValue.toString()
               .match(/^[\+\-]?[\d]*(\.[\d]*)?(\d[eE][+\-]?\d+)*$/);
}

// ----------------------------------------------------------------------
/**
 * check if date format is not empty.<b>
 * Otherwise return default date pattern
 * @see _vld.date_pattern
 * @see setDatePattern()
 */
function _checkDateFormat(aFormat)
{
  if (isEmpty(aFormat))
  {
    aFormat = getDatePattern();
  }
  return aFormat;
}

// ----------------------------------------------------------------------
/**
 * filter a part of a date string and return it as integer or null.
 * @param aDateString the string containing the date
 * @param aRegExp     the regexp to filter the part
 * @param aThresh     a threshhold (or null) to check the part
 * @param anOffset    an offset (or null) to be added to part when
 *                    threshold is greater than part.
 */
function _parseDatePart(aDateString, aRegExp, aThresh, anOffset)
{
  var part = null;

  if (aDateString.search(aRegExp) >= 0)
  {
    part = parseInt(RegExp.$1, 10);

    if ((aThresh != null) && (anOffset != null))
    {
      if (part < aThresh)
      {
        part += anOffset;
      }
    }
  }

  return part;
}

// ----------------------------------------------------------------------
/**
 * parse a string to find a time matching the given format.<b>
 * If format is null or empty the default format is used.
 * @see _vld.time_pattern
 * @param aString
 * @param aTimeFormat
 * @param aShortFormAllowFlag (option)
 * @return array of numbers
 */
function parseTimeString(aString, aFormatString, aShortFormAllowFlag)
{
  if (aFormatString == null)
  {
    aFormatString = getTimePattern();
  }

  var format   = aFormatString;
  var hPattern = aFormatString.replace(/^.*?([hH]).*?$/, '$1');
  var counter  = (aShortFormAllowFlag ? 0 : 1);
  do
  {
    var hourRE = _datetimeformat2regex(format, hPattern);
    var minRE  = _datetimeformat2regex(format, 'M');
    var secRE  = _datetimeformat2regex(format, 's');
    var hour   = _parseDatePart(aString, hourRE);
    var min    = _parseDatePart(aString, minRE);
    var sec    = _parseDatePart(aString, secRE);

    if ((hour != null) && (min != null))
    {
      if ((sec == null) || isNaN(sec))
      {
        sec = 0;
      }
      if (hPattern == 'h')
      {
        hour %= 12;
      }

      return new Array(hour, min, sec);
    }

    format = format.replace(/[\.\-:'"\/ ]/g, '')
                   .replace(/s/g, 's?');
  }
  while(counter++ < 1);

  return null;
}


// ----------------------------------------------------------------------
/**
 * try to parse and reformat an currency value.
 * @param aValue the value to parse
 * @param aFormatString the format to be use a regexp.
 * @return a string containing a currency value or null
 */
function parseCurrency(aValue, aFormatString)
{
  if (isEmpty(aFormatString))
  {
    aFormatString = getCurrencyPattern();
  }

  var format = aFormatString;
  var re = new RegExp(format);
  if (re.test(aValue))
  {
    return aValue;
  }

  format = aFormatString.replace(/^(.+?)(,|\\\.)(.+)$/, '($1)($2)?($3)');
  var delim = RegExp.$2.replace(/[^\.,]/g, '').charAt(0);
  re = new RegExp(format);
  if (re.test(aValue))
  {
    var pre  = RegExp.$1;
    var post = RegExp.$3;
    return pre + delim + post;
  }

  return null;
}

// ----------------------------------------------------------------------
/**
 * parse a string to find a date matching the
 * given format.
 * If format is null or empty the default format
 * is used.
 * @see _vld.date_pattern
 * @param aString
 * @param aFormatString
 * @param aShortFormAllowFlag (option)
 * @return Date
 */
function parseDateString(aString, aFormatString, aShortFormAllowFlag)
{
  aFormatString = _checkDateFormat(aFormatString);
  var counter   = (aShortFormAllowFlag ? 0 : 1);
  do
  {
    var dayRE     = _datetimeformat2regex(aFormatString, 'd');
    var monthRE   = _datetimeformat2regex(aFormatString, 'm');
    var yearRE    = _datetimeformat2regex(aFormatString, 'y');

    var day   = _parseDatePart(aString, dayRE);
    var month = _parseDatePart(aString, monthRE);
    var year  = _parseDatePart(aString, yearRE, 100, 2000);
    if ((day != null) && (month != null) && (year != null))
    {
      var date = new Date(year, month - 1, day);
      if ((day == date.getDate())
          && ((month - 1) == date.getMonth())
          && (year        == date.getFullYear()))
      {
        return date;
      }
    }
    aFormatString = aFormatString.replace(/[\.\-\/ ]/g, '');
  }
  while(counter++ < 1);

  return null;
}

// ----------------------------------------------------------------------
/**
 * return true if the string parameter represents a date.
 * @param aValue a string that could represent a date
 * @param theFormat
 * @return true if the parameter represents a date.
 */
function isDate(aValue, theFormat)
{
  return !isEmpty(aValue)
      && (parseDateString(aValue.toString(), theFormat) != null);
}

// ----------------------------------------------------------------------
/**
 * return true if the string parameter represents a time.
 * @param aValue a string that could represent a time
 * @param theFormat
 * @return true if the parameter represents a time.
 */
function isTime(aValue, theFormat)
{
  return !isEmpty(aValue)
      && (parseTimeString(aValue.toString(), theFormat) != null);
}

// ----------------------------------------------------------------------
/**
 * create a regex from a date or time format.
 * d - a day digit, m - a month digit, y - a year digit,
 * h - an hour digit (0-12), H an hour digit (0-23),
 * M - a minute digit,/ s - a second digit.
 * @param aFormat a string that represents a date or time format
 * @param aGroup  a character to define the digit group in braces.
 * @return a regex string.
 */
function _datetimeformat2regex(aFormat, aGroup)
{
  var regex = aFormat;
  regex = regex.replace(/([\.\/\\\-\(\)\[\]\{\}\^\$])/g, '\\$1');
  if (!isEmpty(aGroup))
  {
    regex = regex.replace(new RegExp('(' + aGroup + '+)'), '($1)');
  }
  regex = regex.replace(/dd/g, '(?:[0-2]?\\d|3[01])');
  regex = regex.replace(/mm|hh/g, '(?:0?\\d|1[0-2])');
  regex = regex.replace(/yyyy/g, '(?:\\d\\d)?\\d\\d');
  regex = regex.replace(/MM|ss/g, '[0-5]\\d');
  regex = regex.replace(/HH/g, '(?:[01]\\d|2[0-3])');
  regex = "^" + regex + "$";
  regex = new RegExp(regex);
  return regex;
}

// ----------------------------------------------------------------------
/**
/* return true if the string parameter represents a phone number.
 * @param aString a string that could represent a phone number.
 * @return true if the parameter represents a phone number
 */
function isPhone(aValue)
{
  return !isEmpty(aValue)
      && aValue.toString()
               .match(/^\(?[0\+]?\s?[\d\(\)]+(?:[\-\/]?\s?[\d\(\)]*)*\d\)?$/);
}

// ----------------------------------------------------------------------
/**
 * return true if the string parameter represents a name.
 * @param aString a string that could represent a name
 * @return true if the parameter represents a name.
 */
function isName(aString)
{
  if (!isEmpty(aString))
  {
    for (var ii = 0; ii < aString.length; ++ii)
    {
      var cc = aString.charAt(ii);
      if (cc.match(/[^\s\w\d&\.\/\-\+\'\*]|_/)
          && (cc.charCodeAt(0) <= 192))
      {
        return false;
      }
    }
    return true;
  }
  return false;
}

// ----------------------------------------------------------------------
/**
 * return true if the string parameter represents a forename.
 * @param aString a string that could represent a forename
 * @return true if the parameter represents a forename.
 */
function isForename(aString)
{

  if (!isEmpty(aString))
  {
    for (var ii = 0; ii < aString.length; ++ii)
    {
      var cc = aString.charAt(ii);
      if (!cc.match(/[^\s\W]/)
          && (cc.charCodeAt(0) <= 192))
      {
        return false;
      }
    }
    return true;
  }
  return false;
}

// ----------------------------------------------------------------------
/**
 * return true if the parameter string represents a business
 * partner number like BDE999999. If there is no role character
 * defined all possible characters A-Z are valid.
 * If there is no country defined all possible two-character
 *  permutations of A-Z are valid.
 * @param aString could represent an Internet address.
 * @param aRoleChar "", null or any character
 * @param aCounter, "", null or a two-character country (DE, AT, ...)
 * @param aPostfix to the regex
 */
function isBde(aString, aRoleChar, aCountry)
{
  if (!isEmpty(aString))
  {
    if (isEmpty(aRoleChar))
    {
      aRoleChar = "[A-Z]";
    }
    if (isEmpty(aCountry))
    {
      aCountry = "[A-Z][A-Z]";
    }
    var re = new RegExp('^' + aRoleChar + aCountry + "\\d{6}$");

    return aString.match(re);
  }
  return false;
}

// ----------------------------------------------------------------------
/**
 * return true if the string is a 13 digit ean code.
 * @param aString could be an EAN
 * @return true if param is an EAN
 */
function isEan(aString)
{
  if (!isEmpty(aString) && aString.match(/^\d{13}$/))
  {
    var sum = 0;
    for (var ii = 0; ii < aString.length - 1; ++ii)
    {
      var weigth = ii & 1 ? 3 : 1;
      var digit = parseInt(aString.charAt(ii));
      sum += (digit * weigth);
    }
    sum %= 10;
    sum = (10 - sum) % 10;
    if (parseInt(aString.charAt(12)) == sum)
    {
      return true;
    }
  }
  return false;
}

// ----------------------------------------------------------------------
/**
 * return true if the string is a number of 13 digits.
 * @param aString could be an ILN
 * @return true if param is an ILN
 */
function isIln(aString)
{
  return !isEmpty(aString)
      && aString.match(/^\d{13}$/);
}

// ----------------------------------------------------------------------
/**
 * return true if the parameter string represents an Internet
 * address (IP or DNS).
 * @param aString could represent an Internet address.
 * @param aPrefix to the regex
 * @param aPostfix to the regex
 */
function _isInetAddr(aString, aPrefix, aPostfix)
{
  if (!isEmpty(aString))
  {
    aString = aString.toLowerCase();
    var dnspart = "[\\w\\d]([\\w\\d\-]*[\\w\\d])?"
    var regex
      = new RegExp("^"
                   + aPrefix
                   + "(" + dnspart + "\\.)+"
                   + dnspart
                   + aPostfix
                   + "$");
    return aString.match(regex);
  }

  return false;
}

// ----------------------------------------------------------------------
/**
 * return true if the string parameter represents an e-mail addr.
 * @param aString a string that could represent an e-mail addr.
 * @return true if the parameter represents an e-mail addr.
 */
function isEMail(aString)
{
  return _isInetAddr(aString, ".+?@", "");
}

// ----------------------------------------------------------------------
/**
 * return true if the string parameter represents an e-mail addr.
 * @param aString a string that could represent an e-mail addr.
 * @return true if the parameter represents an e-mail addr.
 */
function isUrl(aString)
{
  return _isInetAddr(aString, "((https?|ftp):\\/\\/)?", "(:\\d+)?(\\/.*)?");
}

// ----------------------------------------------------------------------
/**
 * return true if the string parameter represents a zip code.
 * @param aString a string that could represent a zip code
 * @return true if the parameter represents a zip code.
 */
function isZip(aString, aZipPattern)
{
  if (isEmpty(aZipPattern))
  {
    aZipPattern = _vld.zip_pattern;
  }
  if (typeof aZipPattern == "string")
  {
    aZipPattern = new RegExp(aZipPattern);
  }

  return !isEmpty(aString)
      && aZipPattern.test(aString);
}

// ----------------------------------------------------------------------
/**
 * return true if the string parameter represents a name that
 * could appear in an address (like "51 xx street", "Musterstr. 17"
 * or "Stuttgart").
 * @param aString a string that could represent an address name
 * @return true if the parameter represents an address name.
 */
function isAddressName(aString)
{
  return !isEmpty(aString)
      && aString.match(/^\d*\s*.+?\s*\d*$/)
      && isName(aString.replace(/^\d*\s*(.+?)\.*\s*\d*$/, '$1'));
}

// ----------------------------------------------------------------------
/**
 * @param aValue
 * @return true if the value is null or empty.
 */
function isEmpty(aValue)
{
  return (aValue == null) || aValue.toString().match(/^\s*$/);
}

// ----------------------------------------------------------------------
/**
 * set the original border color of an element.
 * If there is none stored yet, the current color
 * is used as original one.
 * @param anElement the element to set the color of.
 */
function _restoreOriginalColor(anElement)
{
  if (anElement.oldBgColor == null)
  {
    anElement.oldBgColor = anElement.style.backgroundColor;
  }
  else
  {
    anElement.style.backgroundColor = anElement.oldBgColor;
  }
}

// ----------------------------------------------------------------------
/**
 * set an HTML element visible or invisible.
 * The id of the HTML element is calculated by the
 * form element's name followed by ""-validation-error".
 * If there is no element with such an id nothing
 * happens.
 * @param aFormElement the form element that's corresponding
 *        error element should be visible or invisible
 * @param theVisibility true if the element should be visible,
 *        false if the element should be invisible.
 */
function _setErrorVisible(aFormElement, theVisibility)
{
  var errorElmId   = aFormElement.name + "-validation-error";
  var errorElement = document.getElementById(errorElmId);

  if (errorElement != null)
  {
    var elementStyle = errorElement.style;

    if (theVisibility)
    {
      elementStyle.visibility = "visible";
      elementStyle.display    = "block";
    }
    else
    {
      elementStyle.visibility = "hidden";
      elementStyle.display    = "none";
    }
  }
}

// ----------------------------------------------------------------------
/**
 * call check function of a user-defined type.
 * The name of the check-function is determined
 * by the .funcname slot of the parameter object (2nd parameter),
 * and if there is parameter it is passed by the .param slot
 * of the 2nd parameter.
 * @param aString        the value of the form field
 * @param aParameterHash a hash object to carry additional
 *        data to the function
 * @return true if the check-function returns true or does not
 *         exist.
 */
function _isUserDefined(aString, aParameterHash)
{
  var result = true;
  if (aParameterHash.funcname)
  {
    var checkFunc = aParameterHash.funcname;
    var param     = aParameterHash.param;

    try
    {
        if (null != param)
        {
            result = eval(checkFunc + '("' + aString + '", "' + param + "\")");
        }
    }
    catch(ignored)
    {
      /* do nothing */
    }
  }

  return result;
}

// ----------------------------------------------------------------------
/**
 * check validity of a given value and a type identifier.
 * @param aValue the value to be checked
 * @param aType  the type the value should belong to
 * @param aParameterHash an assoc array (.param represents parameters)
 * @return true if the value is valid.
 */
function _isValid(aValue, aType, aParameterHash)
{
  switch(aType.toLowerCase())
  {
    case "integer":      return isInteger(aValue, aParameterHash.range);
    case "number":       return isNumber(aValue);
    case "currency":     return isCurrency(aValue, aParameterHash.param);
    case "e-mail":       return isEMail(aValue);
    case "url":          return isUrl(aValue);
    case "phone":        return isPhone(aValue);
    case "zip":          return isZip(aValue, aParameterHash.param);
    case "date":         return isDate(aValue, aParameterHash.param);
    case "time":         return isTime(aValue, aParameterHash.param);
    case "bde":          return isBde(aValue, aParameterHash.param, null);
    case "iln":          return isIln(aValue);
    case "ean":          return isEan(aValue);
	case "forename":	 return isForename(aValue);
    case "name":         //return isName(aValue);
    case "address-name": //return isAddressName(aValue);
    case "text":         return !isEmpty(aValue);
    case "user-def":     return _isUserDefined(aValue, aParameterHash);
  }
  return true; // unrecognized type are always valid
}

// ----------------------------------------------------------------------
/**
 * check validity of a given value and a type identifier.
 * @param anInputField the input field to be corrected
 * @param aType  the type the field should contain
 * @param aParameterHash an assoc array (.param represents parameters)
 * @return true if the value is valid.
 */
function _autocorrect(anInputField, aType, aParameterHash)
{
  var strval = anInputField.value;
  var format = aParameterHash.param ? aParameterHash.param : null;
  switch(aType.toLowerCase())
  {
    case "time":
      if (aParameterHash.autocorrect == 'true')
      {
        var time = parseTimeString(strval, format, true);
        if (time != null)
        {
          strval = formatTime(time, format);
        }
      }
      break;
    case "date":
      if (aParameterHash.autocorrect == 'true')
      {
        var date = parseDateString(strval, format, true);
        if (date != null)
        {
          strval = formatDate(date, format);
        }
      }
      break;
    case "currency":
      if (aParameterHash.autocorrect == 'true')
      {
        var curr = parseCurrency(strval, format);
        if (curr != null)
        {
          strval = curr;
        }
      }
      break;
    case "text": // falls thru
    case "bde":  // falls thru
    case "name":
      if (aParameterHash.autocorrect == 'lower')
      {
        strval = strval.toLowerCase();
      }
      else
      {
        strval = strval.toUpperCase();
      }
      break;
    default:
      break;
  }
  anInputField.value = strval;
}

// ----------------------------------------------------------------------
/**
 * parse a string and find parameters to a type.
 * Syntax: <tt>param=(bar),callback=(myCbFnc()),anyOther=(foo)</tt>
 * @param aParamString the string to be parsed
 * @param theParseResult assoc array to store parameters within.
 */
function _parseParameter(aParamString, theParseResult)
{
  if ((aParamString != null)
      && (aParamString.length > 0)
      && (theParseResult != null))
  {
    var array   = aParamString.split(",");
    var parseRe = /^\s*(.+?)=\(\s*(.+?)\s*\)\s*$/;

    for (var ii = 0; ii < array.length; ++ii)
    {
      var reResult = parseRe.exec(array[ii]);
      if (reResult != null)
      {
        theParseResult[reResult[1]] = reResult[2];
      }
    }
  }
}

// ----------------------------------------------------------------------
/**
 * parse a value and split it into an array by a given delimiter
 * or semicolon.
 * If theListFlag is false the resulting array
 * contains one element which is the value itself.
 * @param aValue to split
 * @param theListFlag true: split aValue, false: don't split
 * @param theParamHash hash of parameters (could contain delim)
 * @return an array of values
 */
function _value2array(aValue, theListFlag, theParamHash)
{
  var valArr;
  if (theListFlag)
  {
    var delim = theParamHash.delim ? theParamHash.delim : ';';
    if (delim.toLowerCase() == 'comma')
    {
      delim = ',';
    }
    valArr = aValue.split(delim);
    for (var ii = 0; ii < valArr.length; ++ii)
    {
      valArr[ii] = valArr[ii].replace(/^\s*/, '').replace(/\s*$/, '');
    }
  }
  else
  {
    valArr = new Array(aValue);
  }

  return valArr;
}

// ----------------------------------------------------------------------
/**
 * parse a type descriptor (could be the value of accept attribute).
 * @param theTypeDescriptorString
 * @return
 */
function _parseType(theTypeDescriptorString)
{
  var regex
    = /(?:(required|desired)\-)?(?:(list\-of)\-)?(.+?)(?:\{(.+?)\})?$/i;
  var result   = regex.exec(theTypeDescriptorString.replace(/[\n\r]+/g, ""));
  var required = (result[1] == 'required');
  var desired  = (result[1] == 'desired');
  var list     = (result[2] == 'list-of');
  var type     = result[3];

  var typeParameters      = new Object(); // assoc array
  typeParameters.param    = null;
  typeParameters.callback = null;

  _parseParameter(result[4], typeParameters);

  var typeDescr =
  {
    'type'       : type,
    'required'   : required,
    'desired'    : desired,
    'parameters' : typeParameters,
    'list'       : list
  };
  return typeDescr;
}

// ----------------------------------------------------------------------
/**
 * check validity of a given value and a type identifier.
 * @param anInputField the form field to be checked.
 * @return "ok" if the value is valid, "error" if the value is invalid,
 *         "warning" if the value is desired but not set.
 */
function _check(anInputField)
{
  var type = _findType(anInputField);
  if (!isEmpty(type))
  {
    var typeDescr = _parseType(type);

    if (!isEmpty(anInputField.value)
        && (typeDescr.parameters.autocorrect != null))
    {
      _autocorrect(anInputField, typeDescr.type, typeDescr.parameters);
    }

    if (!isEmpty(anInputField.value) || typeDescr.required)
    {
      var valArr = _value2array(anInputField.value,
                                typeDescr.list,
                                typeDescr.parameters);
      for (var ii = 0; ii < valArr.length; ++ii)
      {
        if (!_isValid(valArr[ii], typeDescr.type, typeDescr.parameters))
        {
          return "error";
        }
      }
    }
    else if (typeDescr.desired)
    {
      try
      {
        if ((typeDescr.parameters.callback != null)
            && eval(typeDescr.parameters.callback
                    + '("' + anInputField.value + '")'))
        {
          return "warn";
        }
      }
      catch (ignored)
      {
        // do nothing -> "ok"
      }
    }
  }

  return "ok";
}

// ----------------------------------------------------------------------
/**
 * try to find type information of the given HTML element.
 * If the element.type name begins with "select" the type
 * is required-user-def.
 * @param anElement the element to find the type of
 * @return accepted type or null
 */
function _findType(anElement)
{
  var type = anElement.accept;
  if (anElement.type.match(/^select/i))
  {
    type = 'required-user-def{funcname=(checkSelectBox_'
         + anElement.name + ')}';
  }

  return type;
}


// ----------------------------------------------------------------------
/**
 * format an integer to a given length.
 * @param anInt    the integer to foramt
 * @param theWidth the number of digits
 */
function _formatNDigit(anInt, theWidth)
{
  var result = '';
  for (var ii = 0; ii < theWidth; ++ii)
  {
    result += '0';
  }
  result += anInt;
  var len = result.length;
  return result.substr(len - theWidth, theWidth);
}

// ----------------------------------------------------------------------
/**
 * formate a date object by a format string
 * @param aDate the date object to be formatted
 * @param aFormatString (or null) the format, if null
 *        default format is used
 */
function formatDate(aDate, aFormatString)
{
  var dateStr = _checkDateFormat(aFormatString);
  var day     = _formatNDigit(aDate.getDate(),      2);
  var month   = _formatNDigit(aDate.getMonth() + 1, 2);
  var year    = _formatNDigit(aDate.getFullYear(),  4);

  dateStr = dateStr.replace(/dd/i,   day)
                   .replace(/mm/i,   month)
                   .replace(/yyyy/i, year);

  return dateStr;
}

// ----------------------------------------------------------------------
/**
 * formate a time array by a format string
 * @param aTime the date object to be formatted
 * @param aFormatString (or null) the format, if null
 *        default format is used
 */
function formatTime(aTime, aFormatString)
{
  if (aFormatString == null)
  {
    aFormatString = getTimePattern();
  }

  var hour = aTime[0];
  var min  = aTime[1];
  var sec  = aTime[2];

  var timeStr = aFormatString.replace(/hh|HH/, _formatNDigit(hour, 2))
                             .replace(/MM/, _formatNDigit(min, 2))
                             .replace(/ss/, _formatNDigit(sec, 2));
  return timeStr;
}

// ----------------------------------------------------------------------
/**
 * check if the content of an element is valid.
 * @param anElement the element to be checked
 * @return true if content is correct.
 */
function checkElement(anElement)
{
  _restoreOriginalColor(anElement);
  _setErrorVisible(anElement, false);

  var errlevel = _check(anElement);
  if (errlevel != 'ok')
  {
    switch (errlevel)
    {
      case 'error':
        anElement.style.backgroundColor = _vld.error_color;
        break;
      case 'warn':
        anElement.style.backgroundColor = _vld.warn_color;
        break;
    }
    _setErrorVisible(anElement, true);
    return false;
  }
  return true;
}

// ----------------------------------------------------------------------
/**
 * check all registerd input fields.
 * If all are valid set action address to the given URL
 * and submit the form.
 * If a field is invalid it is marked red and gets
 * the focus.
 * <pre>
 * &lt;form action="/the/action" onsubmit="return checkAcceptedTypes(this)" ...>
 *   &lt;input type="???" value="???" name="NNN" accept="integer" /&gt;
 * &lt;/form>
 * </pre>
 */
function checkAcceptedTypes(aForm)
{
  var error = 0;
  _setErrorVisible(aForm, false);
  for (var ii = 0; ii < aForm.elements.length; ++ii)
  {
    var element = aForm.elements[ii];
    if (!checkElement(element))
    {
      if (++error == 1)
      {
        element.focus();
        if (element.type.match(/^text/i))
        {
          element.select();
        }
      }
    }
  }

  if (error > 0)
  {
    _setErrorVisible(aForm, true);
    if (!isSoftCheck())
    {
      return false;
    }
  }

  return true;
}

// ----------------------------------------------------------------------
/**
 * check the accepted types and submit the form if
 * all entry fields are ok.
 * @see checkAcceptedTypes(aForm)
 * @param aForm
 * @return the result of checkAcceptedTypes().
 */
function checkAndSubmit(aForm)
{
  if ((aForm != null) && checkAcceptedTypes(aForm))
  {
    aForm.submit();
    return true;
  }
  return false;
}

// ----------------------------------------------------------------------
/**
 * add onChange handlers to each text input field that has
 * an accepted type.
 * @param aForm the form that fields should be modified.<b>
 */
function addOnChangeHandlers(aForm, anAfterHookFcn)
{
  var checkFcn;
  if (anAfterHookFcn != null)
  {
    checkFcn = function()
               {
                 if (checkElement(this))
                 {
                   anAfterHookFcn(this);
                 }
               };
  }
  else
  {
    checkFcn = function() { checkElement(this); };
  }

  for (var ii = 0; ii < aForm.elements.length; ++ii)
  {
    var element = aForm.elements[ii];
    if (!isEmpty(element.accept) && (_parseType(element.accept) != null))
    {
      if (element.onchange == null)
      {
        element.setAttribute("onchange", checkFcn); // makes mozilla happy
        element.onchange = checkFcn; // makes ie happy
      }
    }
  }
}

// ----------------------------------------------------------------------
/**
 *
 */
function findAllSoftCheckForms(anAfterHookFcn)
{
  var forms = document.getElementsByTagName('form');
  for (var ii = 0; ii < forms.length; ++ii)
  {
    if (!isEmpty(forms[ii].id) && (forms[ii].id.match(/^SOFT\-/)))
    {
      addOnChangeHandlers(forms[ii], anAfterHookFcn);
    }
  }
}
