/*****************************************************************************
 * Core javascript functions used client side and server side
 *
 * Copyright: Neolane 2001-2009
 *****************************************************************************/
//[of]:Formatting

/*****************************************************************************
 * Formating helper functions 
 *****************************************************************************/
String.prototype.padLeft = function(character, len)
{
  var str = this
  while ( str.length < len )
    str = character + str
  return str
}

/** Convert a string to smartcase.
  *
  * @return the given string converted to smartcase. */
String.prototype.toSmartCase = function()
{
  var i, ch, n = this.length, strRes = "", nextInUpper = true
  for (i=0; i < n; i++)
  {
    ch   = this.charAt(i)
    strRes += nextInUpper ? ch.toUpperCase() : ch
    nextInUpper = !this.isAlphaNum(ch)
  }
  return strRes
}

/** Convert a string to an identifier.
  * 
  * - if the first character is a numeric value, an underscore is inserted before.
  * - if a character is not an ASCII 7 letter, it is remplaced by an underscore.
  *
  * @return a valid string identifier. */
String.prototype.toIdentifier = function()
{
  var i, ch, n = this.length, strRes = ""
  for (i=0; i < n; i++)
  {
    ch = this.charAt(i)
    if ( "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".indexOf(ch) == -1 )
      strRes += "_"
    else
      strRes += ch
  }
  
  if ( i > 0 && "0123456789".indexOf(strRes.charAt(0)) != -1 )
    return "_" + strRes
  return strRes
}

// Convert HTML reserved characters (& < > ") to their associated entities
String.prototype.encodeHtml = function()
{
  return this.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;")
}

/** Check if a character is a letter or digit.
  *
  * @ch character to test.
  * @return true or false. */
String.prototype.isAlphaNum = function(ch)
{
  return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞß".indexOf(ch) != -1
}

Number.prototype.padLeft = function(len)
{
  var str = this.toString();
  while ( str.length < len )
    str = '0' + str
  return str
}

/** Create a string from a Number using a known localized pattern.
  *
  * @options.pattern        Number Format Patterns, ex: '#,##0.## ¤'
  *                         (see http://www.unicode.org/reports/tr35/#Number_Format_Patterns).
  * @options.decimalSymbol  Decimal symbol (generaly ',' or '.', substitute for '.').
  * @options.groupSymbol    Group symbol (generaly ' ' or ',', substitute for ',' or ' ').
  * @options.symbol         Currency symbol (substitute for '¤').
  * @options.minusSign      Prefix used for negative numbers.
  * @options.plusSymbol     Prefix used for positive numbers.
  *
  * @exemple:
  * x.format({"pattern": "¤#,##0.00", "decimalSymbol": ",", "groupSymbol": " ", "symbol": "$"})
  *
  * @return a string according the given pattern. */
Number.prototype.format = function(options)
{
  if( isNaN(this) )
    return ""
  var stringValue = this.toString()
  var str = "", strDigit, strDecimal, strSign, strDigitPattern, strDecimalPattern

  if ( options.decimalSymbol == undefined )
    options.decimalSymbol = Format.settings.decimalSymbol
  if ( options.groupSymbol == undefined )
    options.groupSymbol = Format.settings.digitGroupingSymbol
  
  // split the pattern (digit / decimal)
  var iSepPos = options.pattern.indexOf('.')
  var iDecimalDigitCount = 0
  if ( iSepPos != -1 )
  {
    strDigitPattern    = options.pattern.substr(0, iSepPos)
    strDecimalPattern  = options.pattern.substr(iSepPos+1)
    
    var nDecimal = strDecimalPattern.length
    var pattern
    for (var i=0; i < nDecimal; i++)
    {
      pattern = strDecimalPattern.charAt(i)
      if ( pattern == '#' || pattern == '0' )
        iDecimalDigitCount++
      else
        break
    }
  }
  else
    strDigitPattern = options.pattern
  
  // split the number (digit / decimal)
  iSepPos = stringValue.indexOf('.')
  if ( iSepPos != -1 )
  {
    strDecimal  = stringValue.substr(iSepPos+1)      
    if( strDecimal.charAt(iDecimalDigitCount) != undefined && strDecimal.charAt(iDecimalDigitCount) >= 5 )
    {
      var roundedValue = this + Math.pow(10, -iDecimalDigitCount) * (this<0?-1:1)      
      // separator can be moved (99,5 -> 100,5), get new separator position
      iSepPos = stringValue.indexOf('.')      
      stringValue = roundedValue.toString()
      // separator can be moved (99,5 -> 100,5), get new separator position
      iSepPos = stringValue.indexOf('.')
      strDecimal  = stringValue.substr(iSepPos+1)      
    }
    strDigit    = stringValue.substr(0, iSepPos)
  }
  else
    strDigit = stringValue;
  
  // remove the sign if necessary
  if ( strDigit.charAt(0) == "-" )
  {
    strSign   = options.minusSign != undefined ? options.minusSign : '-'
    strDigit  = strDigit.substr(1);
  }
  else
    strSign   = options.plusSign != undefined ? options.plusSign : ''
        
  // process digit part
  var digit, pattern, iPatternPos = strDigitPattern.length
  var digitPatternStart
  var nDigit = strDigit.length
  for (var i=nDigit-1; i >= 0; i--)
  {
    digit   = strDigit.charAt(i)
    pattern = strDigitPattern.charAt(--iPatternPos)
    if ( pattern == ' ' || pattern == ',' )
    {
      str         = options.groupSymbol + str
      iPatternPos = strDigitPattern.length  // cycle on the pattern
      pattern     = strDigitPattern.charAt(--iPatternPos)
    }
    
    if ( pattern == '¤' )
    {
      str = options.symbol + str
      if ( iPatternPos > 0 && strDigitPattern.charAt(iPatternPos-1) == ' ' )
      {
        str = " " + str
        iPatternPos--
      }
      
      str = digit + str
    }
    else if ( pattern == '0' || pattern == '#' )
    {
      str = digit + str
      if ( digitPatternStart == undefined )
        digitPatternStart = iPatternPos+1
    }
      
    if ( iPatternPos == 0 )
      // start of pattern reached => cycling
      iPatternPos = digitPatternStart
  }
  
  // process decimals
  if ( strDecimalPattern != undefined )
  {
    str += options.decimalSymbol
    var nDecimal = strDecimalPattern.length
    for (var i=0; i < nDecimal; i++)
    {
      pattern = strDecimalPattern.charAt(i)
      if ( (pattern == '#' || pattern == '0') && strDecimal != undefined && i < strDecimal.length )
        str += strDecimal.charAt(i) 
      else if ( pattern == '0' )
        // 0 padding requested
        str += '0'
      else if ( pattern == ' ' )
        str += ' '
      else if ( pattern == '¤' )
        str += options.symbol
    }
  }
  
  str = strSign + str 
  if ( strDigitPattern.charAt(0) == '¤' )
    str = options.symbol + str

  return str
}

Date.prototype.getAmPmHour = function()
{
  var iHour = Math.floor(this.getHours())
  if( iHour > 12 )
    iHour -= 12
  else if( iHour < 1 )
    iHour = 12;
  return iHour;
}


var Format = {

  "language": null, /** current language */
  "settings": null, /** current settings */

  /** map of region settings */
  "REGION_SETTINGS" : 
  {
    "de": { "digitGroupingSymbol": ".", "decimalSymbol": ",", "shortDate": "%2D.%2M.%4Y", "longDate": "%A, %D. %B %4Y", "shortDateTime": "%2D.%2M.%4Y %2H:%2N", "AmPm": ["AM", "PM"],  // German
            "daysName": [ "Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"],
            "monthesName": [ "Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"],
            "quartersName": [ "1. Quartal", "2. Quartal", "3. Quartal", "4. Quartal"]},
    "en-us": { "digitGroupingSymbol": ",", "decimalSymbol": ".", "shortDate": "%2M/%2D/%4Y", "longDate": "%A, %D %B, %4Y", "shortDateTime": "%2M/%2D/%4Y %I:%2N %P", "AmPm": ["AM", "PM"]}, // English (United States)
    "en-gb": { "digitGroupingSymbol": ",", "decimalSymbol": ".", "shortDate": "%2D/%2M/%4Y", "longDate": "%A %D %B %4Y", "shortDateTime": "%2D/%2M/%4Y %2H:%2N", "AmPm": ["AM", "PM"],
            "daysName": ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], 
            "monthesName" : ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
            "quartersName" : ["1st quarter", "2nd quarter", "3rd quarter", "4th quarter"]},                                                                                              // English (United Kingdom)
    "ar": { "digitGroupingSymbol": ",", "decimalSymbol": ".", "shortDate": "%2D.%2M.%4Y", "longDate": "%A, %D. %B %4Y", "shortDateTime": "%2D/%2M/%4Y %2I:%2N %P", "AmPm": ["\u0635", "\u0645"]},            // Arabic
    "zh": { "digitGroupingSymbol": ",", "decimalSymbol": ".", "shortDate": "%2D.%2M.%4Y", "longDate": "%A, %D. %B %4Y", "shortDateTime": "%4Y-M-%D %P%I:%2N", "AmPm": ["\u4E0A\u5348", "\u4E0B\u5348"]},     // Chinese
    "ko": { "digitGroupingSymbol": ",", "decimalSymbol": ".", "shortDate": "%2D.%2M.%4Y", "longDate": "%A, %D. %B %4Y", "shortDateTime": "%4Y. %M. %D %P %I:%2N", "AmPm": ["\uC624\uC804", "\uC624\uD6C4"]}, // Korean
    "da": { "digitGroupingSymbol": ".", "decimalSymbol": ",", "shortDate": "%2D-%2M-%4Y", "longDate": "%D. %B %4Y", "shortDateTime": "%2D-%2M-%4Y %2H:%2N", "AmPm": ["AM", "PM"]},                           // Danish
    "es": { "digitGroupingSymbol": ".", "decimalSymbol": ",", "shortDate": "%2D/%2M/%4Y", "longDate": "%A, %D de %B de %4Y", "shortDateTime": "%2M-%2D-%4Y %2I:%2N %P", "AmPm": ["AM", "PM"]},               // Spanish
    "et": { "digitGroupingSymbol": " ", "decimalSymbol": ",", "shortDate": "%2D.%2M.%4Y", "longDate": "%D %B %4Y. a.", "shortDateTime": "%D.%2M.%4Y %H:%2N", "AmPm": ["AM", "PM"]},                          // Estonian
    "fi": { "digitGroupingSymbol": " ", "decimalSymbol": ",", "shortDate": "%D.%M.%4Y",   "longDate": "%D. %B %4Y", "shortDateTime": "%D.%M.%4Y %H:%2N", "AmPm": ["AM", "PM"]},                              // Finnish
    "fr-fr": { "digitGroupingSymbol": " ", "decimalSymbol": ",", "shortDate": "%2D/%2M/%4Y", "longDate": "%A %D %B %4Y", "shortDateTime": "%2D/%2M/%4Y %2H:%2N", "AmPm": ["AM", "PM"],
               "daysName": ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"], 
               "monthesName" : ["janvier", "février", "mars", "avril", "mai", "juin", "juillet", "août", "septembre", "octobre", "novembre", "décembre"],
               "quartersName" : ["1er trimestre", "2e trimestre", "3e trimestre", "4e trimestre"], "percentSeparator":" "},                                                                  // French
    "fr-be": { "digitGroupingSymbol": ".", "decimalSymbol": ",", "shortDate": "%2D/%2M/%4Y", "longDate": "%A %D %B %4Y", "shortDateTime": "%D/%2M/%4Y %H:%2N", "AmPm": ["AM", "PM"],
               "daysName": ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"], 
               "monthesName" : ["janvier", "février", "mars", "avril", "mai", "juin", "juillet", "août", "septembre", "octobre", "novembre", "décembre"],
               "quartersName" : ["1er trimestre", "2e trimestre", "3e trimestre", "4e trimestre"], "percentSeparator":" "},                                                                  // French
    "el": { "digitGroupingSymbol": ".", "decimalSymbol": ",", "shortDate": "%D/%M/%4Y",   "longDate": "%A, %D %B %4Y", "shortDateTime": "%D/%M/%4Y %I:%2N %P", "AmPm": ["\u03C0\u03BC", "\u03BC\u03BC"]}, // Greek
    "he": { "digitGroupingSymbol": ",", "decimalSymbol": ".", "shortDate": "%2D.%2M.%4Y", "longDate": "%A, %D. %B %4Y", "AmPm": ["AM", "PM"]},                                               // Hebrew
    "hu": { "digitGroupingSymbol": " ", "decimalSymbol": ",", "shortDate": "%4Y.%2M.%2D.", "longDate": "%4Y. %B %D.", "shortDateTime": "%4Y.%2M.%2D. %H:%2N", "AmPm" : ["DE", "DU"]},        // Hungarian
    "id": { "digitGroupingSymbol": ".", "decimalSymbol": ",", "shortDate": "%2D/%2M/%4Y", "longDate": "%D %B %4Y", "AmPm": ["AM", "PM"]},                                                    // Indonesian
    "ga": { "digitGroupingSymbol": ",", "decimalSymbol": ".", "shortDate": "%2D.%2M.%4Y", "longDate": "%A, %D. %B %4Y", "shortDateTime": "%4Y/%2M/%2D %2H:%2N", "AmPm": ["a.m.", "p.m."]},   // Irish
    "it-it": { "digitGroupingSymbol": ".", "decimalSymbol": ",", "shortDate": "%2D/%2M/%4Y", "longDate": "%A %D %B %4Y", "shortDateTime": "%2D/%2M/%4Y %H.%2N", "AmPm": ["AM", "PM"]},       // Italian (Italy)
    "it-ch": { "digitGroupingSymbol": ".", "decimalSymbol": ",", "shortDate": "%2D.%2M.%4Y", "longDate": "%A, %D. %B %4Y", "shortDateTime": "%2D/%2M/%4Y %H.%2N", "AmPm": ["AM", "PM"]},     // Italian (Switzerland) 
    "ja": { "digitGroupingSymbol": ",", "decimalSymbol": ".", "shortDate": "%2D.%2M.%4Y", "longDate": "%A, %D. %B %4Y", "shortDateTime": "%4Y/%2M/%2D H:%2N", "AmPm": ["\u5348\u524D", "\u5348\u5F8C"]}, // Japanese
    "lv": { "digitGroupingSymbol": " ", "decimalSymbol": ",", "shortDate": "%4Y.%2M.%2D.", "longDate": "%A, %4Y. gada %D. %B", "shortDateTime": "%4Y.%D.%M %2H:%2N", "AmPm": ["AM", "PM"]},  // Latvian
    "lt": { "digitGroupingSymbol": ".", "decimalSymbol": ",", "shortDate": "%4Y.%2M.%2D.", "longDate": "%4Y m. %B %D d.", "shortDateTime": "%4Y.%D.%M %2H:%2N", "AmPm": ["AM", "PM"]},       // Lithuanian
    "mt": { "digitGroupingSymbol": ",", "decimalSymbol": ".", "shortDate": "%2D/%2M/%4Y", "longDate": "%A, %D ta' %B %4Y", "shortDateTime": "%2D/%2M/%4Y %2H:%2N", "AmPm": ["QN", "WN"]},    // Maltese
    "nl-nl": { "digitGroupingSymbol": ".", "decimalSymbol": ",", "shortDate": "%2D-%2M-%4Y", "longDate": "%A %D %B %4Y", "shortDateTime": "%D-%M-%D %H:%2N", "AmPm": ["AM", "PM"]},          // Dutch (Netherlands)
    "nl-be": { "digitGroupingSymbol": ".", "decimalSymbol": ",", "shortDate": "%2D/%2M/%4Y", "longDate": "%A %D %B %4Y", "shortDateTime": "%D/%2M/%4Y %H:%2N", "AmPm": ["AM", "PM"]},        // Dutch (Belgium)
    "pl": { "digitGroupingSymbol": " ", "decimalSymbol": ",", "shortDate": "%4Y-%2M-%2D", "longDate": "%D %B %4Y", "shortDateTime": "%4Y-%2M-%2D %2H:%2N", "AmPm": ["AM", "PM"]},            // Polish
    "pt-pt": { "digitGroupingSymbol": ".", "decimalSymbol": ",", "shortDate": "%2D-%2M+%4Y", "longDate": "%A, %D de %B de %4Y", "shortDateTime": "%2D-%2M-%4Y %H:%2N", "AmPm": ["AM", "PM"]},// Portuguese (Potugual)
    "pt-br": { "digitGroupingSymbol": ".", "decimalSymbol": ",", "shortDate": "%D/%M/%4Y", "longDate": "%A, %D de %B de %4Y", "shortDateTime": "%2D/%2M/%4Y %2H:%2N", "AmPm": ["AM", "PM"]}, // Portuguese (Brazil)
    "ru": { "digitGroupingSymbol": " ", "decimalSymbol": ",", "shortDate": "%2D.%2M.%4Y", "longDate": "%D %B %4Y", "shortDateTime": "%2D.%2M.%4Y H:%2N", "AmPm": ["AM", "PM"]},              // Russian
    "sl": { "digitGroupingSymbol": ".", "decimalSymbol": ",", "shortDate": "%D.%M.%4Y", "longDate": "%D. %B %4Y", "shortDateTime": "%D.%M.y %H:%2N", "AmPm": ["AM", "PM"]},                  // Slovene
    "sk": { "digitGroupingSymbol": " ", "decimalSymbol": ",", "shortDate": "%D.%M.%4Y", "longDate": "%D. %B %4Y", "shortDateTime": "%D.%M.%4Y %H:%2N", "AmPm": ["AM", "PM"]},                // Slovak
    "sv-se": { "digitGroupingSymbol": " ", "decimalSymbol": ",", "shortDate": "%2D-%2M-%4Y", "longDate": "%A %D %B %4Y", "shortDateTime": "%4Y-%2M-%2D %2H:%2N", "AmPm": ["AM", "PM"]},      // Swedish (Sweden)
    "sv-fi": { "digitGroupingSymbol": " ", "decimalSymbol": ",", "shortDate": "%D.%M.%4Y", "longDate": "%A %D %B %4Y", "AmPm": ["AM", "PM"]},                                                // Swedish (finland)
    "cs": { "digitGroupingSymbol": " ", "decimalSymbol": ",", "shortDate": "%D.%M.%4Y", "longDate": "%D. %B %4Y", "shortDateTime": "%D.%M.%4Y %H:%2N", "AmPm": ["dop.", "odp."]},            // Czech
    "th": { "digitGroupingSymbol": ",", "decimalSymbol": ".", "shortDate": "%2D.%2M.%4Y", "longDate": "%A, %D. %B %4Y", "shortDateTime": "%M/%D/%4Y %I:%2N %P", 
            "AmPm" : ["\u0E01\u0E48\u0E2D\u0E19\u0E40\u0E17\u0E35\u0E48\u0E22\u0E07", "\u0E2B\u0E25\u0E31\u0E07\u0E40\u0E17\u0E35\u0E48\u0E22\u0E07"]},                                      // Thai
    "vi": { "digitGroupingSymbol": ".", "decimalSymbol": ",", "shortDate": "%2D.%2M.%4Y", "longDate": "%A, %D. %B %4Y", "shortDateTime": "%2H:%2N %2D/%2M/%4Y", "AmPm" : ["SA", "CH"]},      // Vietnamese
    "wa": { "digitGroupingSymbol": ".", "decimalSymbol": ",", "shortDate": "%2D.%2M.%4Y", "longDate": "%A, %D. %B %4Y", "AmPm": ["AM", "PM"]}                                                // Walloon
  },
  
  "getSettings": function()
  {
    // no language are define in this object
    // try to get it in document
    if( this.language == null && document && document.formatLanguage != null )
      this.setLanguage(document.formatLanguage)
  
    if( !this.settings )
      throw "No language defined"
      
    return this.settings
  },
  
  // iso-8601 date format used in opendocument and soap
  "ISO8601" : { "date": "%4Y-%2M-%2D", "dateTime": "%4Y-%2M-%2DT%2H:%2N:%2S" },
  
  /** Set language
    *
    * @in ISO 639-1 code of the language. */
  "setLanguage": function(language)
  {
    this.language = language.toLowerCase().replace('_', '-')
    if( document ) 
      document.formatLanguage = this.language
    this.settings = this.REGION_SETTINGS[this.language]
    if( this.settings == undefined )
    {
      for( var languageIndex in this.REGION_SETTINGS )
      {
        if( languageIndex.substr(0,2) == this.language.substr(0,2) )
        {
          if( document ) 
            document.formatLanguage = languageIndex
          this.settings = this.REGION_SETTINGS[languageIndex]
          break;
        }
      }
    }
    
    if( this.settings != null)
    {
      if( this.settings.daysName == null )
        this.settings.daysName = this.REGION_SETTINGS["en-gb"].daysName
      if( this.settings.monthesName  == null )
        this.settings.monthesName = this.REGION_SETTINGS["en-gb"].monthesName
      if( this.settings.quartersName  == null )
        this.settings.quartersName = this.REGION_SETTINGS["en-gb"].quartersName
      if( this.settings.digitGroupingSymbol == "." )
        this.settings.digitGroupingRegEx = /\./g
      else
        this.settings.digitGroupingRegEx = new RegExp(this.settings.digitGroupingSymbol, "g")
    }

    if( this.language.substr(0, 2) == 'fr' )
      this.columnSuffix = '\u00A0:'
    else
      this.columnSuffix = ':'
  },
  
  /** Set language from "Accept-Language" HTTP header
    *
    * @header: a string like "en-us,en;q=0.5". */
    "setLanguageFromHttpHeader": function(header)
    {
      var i, lang = header, remaining = header;
      while ( remaining.length > 0 )
      {
        i = remaining.indexOf(',')
        if ( i > 0 )
        {
          lang = remaining.substring(0, i)
          remaining = remaining.substring(i+1)
        }
          
        this.setLanguage(lang)
        if ( this.settings != undefined )
          break
      }
    },

/** Get the full month name of the given month depending of user language.
  *
  * @imonthIndex the month index.
  * @return a string depending of the current user language.*/
  "getMonthName" : function( imonthIndex )
  {
    if( this.getSettings().monthesName != null )
      return this.getSettings().monthesName[imonthIndex-1]
    else
      return imonthIndex
  },
  
  /** Format a date according to the region format.
    *
    * @date    date to format
    * @format  boolean (display date in long format) or string (format)
    * @return  a string */    
  "formatDate": function(date, format)
  {
    if( date == null )
      return ""
      
    var strDisplay 
    if( typeof format == "string" )
      strDisplay = format
    else if( format )
      strDisplay = this.getSettings().longDate
    else
      strDisplay = this.getSettings().shortDate

    strDisplay = strDisplay.replace(/%D/, date.getDate())
    strDisplay = strDisplay.replace(/%2D/, date.getDate().padLeft(2))
    strDisplay = strDisplay.replace(/%M/, date.getMonth()+1)
    strDisplay = strDisplay.replace(/%2M/, (date.getMonth()+1).padLeft(2))
    strDisplay = strDisplay.replace(/%Y/, date.getFullYear() % 100)
    strDisplay = strDisplay.replace(/%2Y/, (date.getFullYear() % 100).padLeft(2))
    strDisplay = strDisplay.replace(/%4Y/, date.getFullYear())

    // time
    strDisplay = strDisplay.replace(/%H/, date.getHours())
    strDisplay = strDisplay.replace(/%2H/, date.getHours().padLeft(2))
    strDisplay = strDisplay.replace(/%I/, date.getAmPmHour())
    strDisplay = strDisplay.replace(/%2I/, date.getAmPmHour().padLeft(2))
    strDisplay = strDisplay.replace(/%P/, this.getSettings().AmPm[Math.floor(date.getHours() / 12)])
    strDisplay = strDisplay.replace(/%N/, date.getMinutes())
    strDisplay = strDisplay.replace(/%2N/, date.getMinutes().padLeft(2))
    strDisplay = strDisplay.replace(/%S/, date.getSeconds())
    strDisplay = strDisplay.replace(/%2S/, date.getSeconds().padLeft(2))
    
    if( this.getSettings().daysName != null )
      strDisplay = strDisplay.replace(/%A/, this.getSettings().daysName[date.getDay()])
    else
      strDisplay = strDisplay.replace(/%A/, "")
    if( this.getSettings().monthesName != null )
      strDisplay = strDisplay.replace(/%B/, this.getSettings().monthesName[date.getMonth()])
    else
      strDisplay = strDisplay.replace(/%B/, date.getMonth()+1)
    if( this.getSettings().quartersName != null )
      strDisplay = strDisplay.replace(/%Q/, this.getSettings().quartersName[Math.floor(date.getMonth() / 3)])
    else
      strDisplay = strDisplay.replace(/%Q/, "")
    
    return strDisplay
  },

  /** Format a date+time according to the region format.
    *
    * @date    date+time to format
    * @format  boolean (display date in long format) or string (format)
    * @noSec   do not display the seconds
    * @return  a string */    
  "formatDateTime": function(date, format, noSec)
  {
    if (date == null)
      return ""
    if( !format && this.getSettings().shortDateTime )
      return this.formatDate(date, this.getSettings().shortDateTime )
    return this.formatDate(date, format) + " " + this.formatTime(date, noSec)
  },
  
  /** Format the time part of a date.
    *
    * @date    date to format
    * @noSec   do not display the seconds
    * @return  a string */    
  "formatTime": function(date, noSec)
  {
    if (date == null)
      return ""

    var t = date.getHours().padLeft(2) +':' + date.getMinutes().padLeft(2);
    if ( noSec != true )
       t += ':' + date.getSeconds().padLeft(2)
    return t
  },
  
  /** Format a time span given as a number of seconds.
    *
    * @timeSpan Number of seconds to format
    * @return   a string */    
  "formatTimeSpan": function(timeSpan)
  {
    if( timeSpan == null || isNaN(timeSpan) )
      return ""
    if( this.aTsUnits == undefined )
    {
      this.aTsUnits = ['s', 'mn', 'h', 'd', 'm', 'y']
      if( this.language.substr(0, 2) == 'fr' )
        this.aTsUnits = ['s', 'mn', 'h', 'j', 'm', 'a']
      Date.SEC_PER_MIN   = 60
      Date.SEC_PER_HOUR  = 3600
      Date.SEC_PER_DAY   = 86400
      Date.SEC_PER_MONTH = 86400 * 30
      Date.SEC_PER_YEAR  = 86400 * 30 * 12
    }

    var years  = Math.floor(timeSpan / Date.SEC_PER_YEAR)
    timeSpan   = timeSpan % Date.SEC_PER_YEAR
    var months = Math.floor(timeSpan / Date.SEC_PER_MONTH)
    timeSpan   = timeSpan % Date.SEC_PER_MONTH
    var days   = Math.floor(timeSpan / Date.SEC_PER_DAY)
    timeSpan   = timeSpan % Date.SEC_PER_DAY
    var hours  = Math.floor(timeSpan / Date.SEC_PER_HOUR)
    timeSpan   = timeSpan % Date.SEC_PER_HOUR
    var mins   = Math.floor(timeSpan / Date.SEC_PER_MIN)
    var secs   = Math.floor(timeSpan % Date.SEC_PER_MIN)

    var strResult = ''
    var iParts = 0
    var iMaxParts = 3
    if( years > 0 )
    {
      iParts++
      strResult += years + this.aTsUnits[5]
    }
    if( months > 0 )
    {
      iParts++
      strResult += (strResult.length == 0 ? '' : ' ') + months + this.aTsUnits[4]
    }
    if( days > 0 && iParts < iMaxParts )
    {
      iParts++
      strResult += (strResult.length == 0 ? '' : ' ') + days + this.aTsUnits[3]
    }
    if( hours > 0 && iParts < iMaxParts )
    {
      iParts++
      strResult += (strResult.length == 0 ? '' : ' ') + hours + this.aTsUnits[2]
    }
    if( mins > 0 && iParts < iMaxParts )
    {
      iParts++
      strResult += (strResult.length == 0 ? '' : ' ') + mins + this.aTsUnits[1]
    }
    if( iParts == 0 || (secs > 0 && iParts < iMaxParts) )
      strResult += (strResult.length == 0 ? '' : ' ') + secs + this.aTsUnits[0]
    return strResult
  },

  /** Get a date/time format suitable for the calendar compenent.
    * based on the short date region in the Format setting.
    */
  "getCalendarDateTimeFormat": function()
  {
    var strFormat = Format.settings.shortDate
    strFormat = strFormat.replace(/%D/, '%e')
    strFormat = strFormat.replace(/%2D/, '%d')
    strFormat = strFormat.replace(/%M/, '%m')
    strFormat = strFormat.replace(/%2M/, '%m')
    strFormat = strFormat.replace(/%Y/, '%y')
    strFormat = strFormat.replace(/%2Y/, '%y')
    strFormat = strFormat.replace(/%4Y/, '%Y')
    return strFormat
  },

  /** Parse a number according to the region format.
    *
    * @stringValue the string representation of the number.
    * @return      the number value, null if the string is not a number. */
  "parseNumber": function(stringValue)
  {
    var normalizedValue = stringValue.replace(this.getSettings().digitGroupingRegEx, "")
    if ( this.getSettings().decimalSymbol != "." )
      normalizedValue = normalizedValue.replace(this.getSettings().decimalSymbol, ".")
    
    var fValue = parseFloat(normalizedValue)
    return isNaN(fValue) ? null : fValue
  },
  
  /** Format a number according to the region format.
    * 
    * @value           the value to format.
    * @iDecimalPlaces  number of digits after the decimal point
    * @return          the string representation of the number according region format. */
  "formatNumber": function(value, iDecimalPlaces)
  {
    if( value == null )
      return value
    var stringValue = value.toString()
    var str = "", strDigit, strDecimal = null, strSign = null;
    var iSepPos = stringValue.indexOf('.');
    if ( iSepPos != -1 )
    {
      strDigit    = stringValue.substr(0, iSepPos);
      strDecimal  = stringValue.substr(iSepPos+1);      
    }
    else
      strDigit = stringValue;
      
    // remove the sign if necessary
    if ( strDigit.charAt(0) == "-" )
    {
      strSign = "-";
      strDigit = strDigit.substr(1);
    }
    else if ( strDigit.charAt(0) == "+" )
      strDigit = strDigit.substr(1);
          
    while ( strDigit.length > 3 )
    {
      str = this.getSettings().digitGroupingSymbol + strDigit.substr(strDigit.length-3) + str;
      strDigit = strDigit.substr(0, strDigit.length-3);
    }
    
    str = strDigit + str;
      
    if ( strDecimal != null || iDecimalPlaces != null)
    {
      if( strDecimal == null )
        strDecimal = "0"
      if( iDecimalPlaces != null )
      {
        if( strDecimal.length > iDecimalPlaces )
          strDecimal = strDecimal.substring(0,iDecimalPlaces)
        else( strDecimal.length < iDecimalPlaces ) 
          while ( strDecimal.length < iDecimalPlaces )
            strDecimal = strDecimal + "0"
      }
      if( strDecimal.length > 0 )
        str = str + this.getSettings().decimalSymbol + strDecimal;
    }
    if ( strSign != null )
      str = strSign + str;
      
    return str;
  },
  
  /** Multiply by 100 and format a number according to the region format.
    * 
    * @value           the value to format.
    * @iDecimalPlaces  number of digits after the decimal point
    * @return          the string representation of the number according region format. */  
  "formatPercent": function(value, iDecimalPlaces)
  {
    var sPcSmb = '%'
    if( Format.getSettings().percentSeparator )
      sPcSmb = Format.getSettings().percentSeparator + '%'
    return Format.formatNumber(value * 100, iDecimalPlaces) + sPcSmb;
  },
  
/** Parse a date string using the international format.
  *
  * International format is: 2004/01/29 20:05:17.000 
  * @in strDate the string to parse.
  * @return a date object. null if the string is empty. */
  "parseDateTimeInter": function(strDate)
  {
    if ( strDate == undefined || strDate == null || strDate.length == 0 )
      // undefined
      return null;
    var aDateTime = strDate.split(" ")
    var aDate     = aDateTime[0].split("/")
    var aTime = null
    if (aDateTime[1])
      aTime = aDateTime[1].split(":")
    if ( aTime && aTime.length > 0 )
      return new Date(aDate[0], aDate[1]-1, aDate[2], aTime[0], aTime[1], aTime[2])
    return new Date(aDate[0], aDate[1]-1, aDate[2])
  },
  "parseTime": function(strDate)
  {
    if ( strDate == undefined || strDate == null || strDate.length == 0 )
      // undefined
      return null;
    var aTime = strDate.split(":")
    return new Date(0, 0, 0, aTime[0], aTime[1], aTime[2])
  },
/** Format a date object to a string at the international format.
  *
  * @date a date object.
  * @return a string like '2007/10/8 11:19:31'. */  
  "formatDateTimeInter": function(date)
  {
    if ( date == undefined || date == null )
      return ""

    return date.getFullYear()
      + '/' + (date.getMonth()+1).padLeft(2)
      + '/' + date.getDate().padLeft(2)
      + ' ' + date.getHours().padLeft(2)
      + ':' + date.getMinutes().padLeft(2)
      + ':' + date.getSeconds().padLeft(2)
  },

/** Format a date object to a string at the international format at UTC.
  *
  * @date a date object.
  * @return a string. */  
  "formatDateTimeInterUTC": function(date, bUTC)
  {
    if ( date == undefined || date == null )
      return ""

    return date.getUTCFullYear()
      + '/' + (date.getUTCMonth()+1).padLeft(2)
      + '/' + date.getUTCDate().padLeft(2)
      + ' ' + date.getUTCHours().padLeft(2)
      + ':' + date.getUTCMinutes().padLeft(2)
      + ':' + date.getUTCSeconds().padLeft(2)
      + ( (bUTC) ? " UTC" : "" )
  },
  
/**
  * Format a date object to a string at the international format according to timezone offset
  * @date a date object.
  * @strOffset a string (+0230 for example)  If not provided, +0000 is used
  * @bUTC boolean to add "UTC <offset>" at the end of the return string
  * @return a string like '2007/10/08 11:19:31 UTC+0230' with timezone offset information  
**/
  "formatDateTimeInterWithTZ": function (date, strOffset, bUTC)
  {
    if ( date == undefined || date == null )
      return ""

    if( typeof strOffset != "undefined" )
      strOffset = strOffset.toString() // force string format

    if( typeof strOffset == "undefined" || strOffset.length == 0 || strOffset.length < 2 )
      strOffset = "+0000" // Default
      
    // Reformat the offset, it has to be formated like : +0000
    if( !strOffset.match(/^[+-]+/) )
      strOffset = "+" + strOffset

    if( strOffset.match(/^[+-]+\d{2}$/) ) //Something like "+02" need to be reformated (append "00" by default)
      strOffset += "00"
      
    // Conversion from "+0230" string to +2.5
    var strHourPart = strOffset.match(/[+-](\d{2})/)[1]
    var iMinutePart = parseInt(strOffset.match(/[+-]*\d{2}(.*)/)[1])
    var dMinutePart = 0.0
    if( iMinutePart != 0 )
      dMinutePart = iMinutePart / 60.0
    
    var dOffset = parseFloat(strHourPart) + dMinutePart
    if( strOffset.charAt(0) == "-" )
      dOffset *= -1

    // Compute UTC time from given date
    var lUTCTime = date.getTime()
    // Apply computed offset to given date
    var strResDate = Format.formatDateTimeInter(new Date(lUTCTime + (dOffset * 3600000)))
    
    if( bUTC )
      return strResDate + " UTC" + strOffset
    else
      return strResDate
  },
  
/** Parse a boolean string.
  *
  * @in str the string to parse.
  * @return true if the string equals "true" or "1". */  
  "parseBoolean": function(str)
  {
    if ( str == "true" || str == "1" )
      return true
    
    return false
  },

/** Format a boolean as a string.
  *
  * @b the boolean to format.
  * @return "true" or "false". */
  "formatBoolean": function(b)
  {
    return b ? "true" : "false"
  },
  
/** Substitute a value when a null value is encountered.
  *
  * @str is the string to test for a null value.
  * @replaceWith is the value returned if str is null.
  * @return replaceWith if str is null. */
  "nvl": function(str, replaceWith)
  {
    return str == null ? replaceWith : str
  },
  
/** Applying a datapolicy to a string.
  *
  * @datapolicy the name of the datapolicy.
  * @str        the string to process.
  * @param      additionnal parameters required by the datapolicy.
  * @return     the string after having processed the datapolicy. null if the datapolicy
  *             cannot be applied. */
  "checkDataPolicy": function(datapolicy, str, params)
  {
    if ( this.isEmptyString(str) )
      return ""
      
    switch ( datapolicy )
    {
      case "none":
        return str
        
      case "smartCase":
        return str.toSmartCase()
        
      case "upperCase":
        return str.toUpperCase()
        
      case "lowerCase":
        return str.toLowerCase()
        
      case "phone":
      {
        if ( params == undefined )
          // using the default regex
          params = "^\\(?(\\+?)\\s*\\(?(\\d)[\\.\\- ]*(\\d)\\)?[\\.\\- ]*"+
                   "(\\d)[\\.\\- ]*(\\d)?[\\.\\- ]*(\\d)?[\\.\\- ]*"+
                   "(\\d)?[\\.\\- ]*(\\d)?[\\.\\- ]*(\\d)?[\\.\\- ]*"+
                   "(\\d)?[\\.\\- ]*(\\d)?[\\.\\- ]*(\\d)?[\\.\\- ]*"+
                   "(\\d)?[\\.\\- ]*(\\d)?[\\.\\- ]*(\\d)?[\\.\\- ]*$"
        
        return str.match(params) == null ? null : str
      }
        
      case "email":
      {
        var aPart = str.split("@")
        if( aPart.length == 2 && aPart[0].match(/^[\S]+$/) && aPart[1].match(/^[a-zA-Z0-9-._]+[.][a-zA-Z]{2,4}$/) )
          return str.toLowerCase()
        return null
      }
      
      case "resIdentifier":
      case "identifier":
        return str.toIdentifier()
    }
  
    throw datapolicy + ": unknown datapolicy."  // ## PF: TO TRANSLATE
  },
  
/** Returns a copy of the string, with leading and trailing whitespace omitted.
  * 
  * @str the string to trim. */  
  "trim": function(str)
  {
    return str == undefined ? undefined : str.replace(/(^\s+)|(\s+$)/g, "")
  },
  
/** Check if the given string is empty or contains only space characters.
  *
  * @str the string to test.
  * @return true or false. */
  "isEmptyString": function(str)
  {
    return str == null || str == undefined || this.trim(str.toString()).length == 0
  }
}
//[cf]
//[of]:Hash

/*****************************************************************************
 * hash table implementation.
 *****************************************************************************/

/** Constructor
  *
  * Allow parameters to initialize the map. Parameters will be used 2 by 2 where
  * the fist is the key and the second is the value.
  *
  * @example:
  * 
  *   var hash = new Hash('key1, 'value1', 'key2', 'value2') */
function Hash()
{
	this.length = 0
	this.items = new Array()
	for (var i = 0; i < arguments.length; i += 2) 
	{
		if (typeof(arguments[i + 1]) != 'undefined') 
		{
			this.items[arguments[i]] = arguments[i + 1];
			this.length++;
		}
	}
}

/** Clear the hash table.
  *
  */
Hash.prototype.clear = function()
{
	this.length = 0
	this.items = new Array()
}

/** Remove an item from the hash table.
  *
  * @key the key of the item to remove.
  * @return the value of the item. undefined if the key is not found
  *         in the hash table. */
Hash.prototype.remove = function(key)
{
	var tmp_value = this.items[key]
	if ( tmp_value != 'undefined') 
	{
		this.length--
		delete this.items[key]
	}
   
	return tmp_value
}

/** Get an item.
  *
  * @key the key of the item to get.
  * @return the value of the item. undefined if the key is not found. */
Hash.prototype.get = function(key) 
{
	return this.items[key];
}

/** Remove an item from the hash table.
  *
  * @key    the key of the item to set.
  * @value  the value to set.
  * @return the value given as 'value' parameter. */
Hash.prototype.set = function(key, value)
{
	if ( typeof(value) != 'undefined') 
	{
		if ( typeof(this.items[key]) == 'undefined' )
			this.length++;

		this.items[key] = value;
	}
   
	return value;
}

/** Check if a key is in the hash.
  *
  * @key    the key of the item to set.
  * @return true or false. */
Hash.prototype.hasItem = function(key)
{
	return typeof(this.items[key]) != 'undefined';
}

//[cf]
//[of]:Vector
/*****************************************************************************
 * Vector 
 *****************************************************************************/
/** Constructor */ 
function XtkVector()
{
  this.inc  = 20;
  this.size = 0;
  this.data = null;
}

/** Remove all content from the vector */
XtkVector.prototype.clear = function()
{
  this.data = null;
  this.size = 0;
}

/** Increment the size of the vector */
XtkVector.prototype.resize = function()
{
  if ( this.data == null )
    this.data = new Array(this.inc);
  else
    this.data.concat(new Array(this.inc));
}

/** Sorts the elements in the entire vector using the fnCompare comparaison 
  * function. 
  * 
  * @in fnCompare a comparaison function. */
XtkVector.prototype.sort = function(fnCompare)
{
  if ( this.data != null )
  {
    if ( this.data.length != this.size )
      // ajust the array size before calling the javascript sort function
      // to avoid to sort void items
      this.data.splice(this.size, this.data.length-this.size);
    
    this.data.sort(fnCompare);
  }
}

/** Add an item to the vector.
  *
  * @in item the item to add.
  * @return the item added to the vector. */
XtkVector.prototype.add = function(item)
{
  if ( this.data == null || this.size == this.data.length )
    this.resize();
  
  this.data[this.size] = item;
  this.size++;
  return item;
}

/** Remove an item from the vector.
  *
  * @in item the item to remove.
  * @return the item removed from the vector. */
XtkVector.prototype.remove = function(item)
{
  this.removeFromIndex(this.indexOf(item));
  return item;
}

/** Search the index of the given item.
  * 
  * @in item the item to search.
  * @return the index of the item or -1 if the item has not been found. */
XtkVector.prototype.indexOf = function(item)
{
  var size  = this.size;
  var data  = this.data;
  for (var i=0; i < size; i++)
    if ( data[i] == item )
      return i;

  return -1;
}

/** Remove an item from its index.
  * 
  * @in index the index of the item to remove. */
XtkVector.prototype.removeFromIndex = function(index)
{
  if ( index == -1 )
    return;
  var size  = this.size;
  var data  = this.data;
  for (var i=index; i < size; i++)
    data[i] = data[i+1];
  
  data[this.size-1] = null;
  this.size--;
}

/** Get an item from its index.
  * 
  * @in index the index of the item. */
XtkVector.prototype.item = function(index)
{
  return this.data[index];
}

/** Convert vector contant as a string (using comma as separator) */
XtkVector.prototype.toString = function()
{
  var strResult = "";
  var size  = this.size;
  var data  = this.data;
  for(var i=0; i < size; i++)
    strResult += (i > 0) ? ',' + data[i] : data[i];
    
  return strResult;
}
//[cf]
//[of]:Helper functions
/*****************************************************************************
 * Helper functions
 *****************************************************************************/
function escapeJSString(strText) 
{
  if( !strText )
    return ""
  var str = strText.toString().replace(/'/g, '\\\'')
  str = str.replace(/"/g, '&quot;')
  return str
}

function escapeXtkString(str)
{
  return "'"+str.toString().replace(/'/g, "\\'")+"'"
}

function escapeSqlString(str)
{
  // escape '\' first, then double quotes
  return "'"+str.toString().replace(/\\/g, "\\\\").replace(/'/g, "''")+"'"
}

/** Convert XML reserved characters (& < > ") to their associated entities
  *
  * @strText the string to escape
  * @return the string escaped */
function escapeXmlString(strText)
{ 
  if( strText == null )
    return null;

  // force strText to be a string   
  strText = strText.toString()
  
  // Use reg exps to replace reserved characters
  strText = strText.replace(/&/g, "&amp;");
  strText = strText.replace(/"/g, "&quot;");
  strText = strText.replace(/</g, "&lt;");
  strText = strText.replace(/>/g, "&gt;");
  strText = strText.replace(/'/g, "&#39;");
  return strText;
}

function likeCond(str)
{
  return escapeXtkString(str + '%')
}

function folderLikeCond(str)
{
  return '$(homeDir) + ' + likeCond(str)
}

//---------------------------------------------------------------------------
// Utility function use to build fullName like a breadcrum
// Ex: /a/b/c => a > b > c
//---------------------------------------------------------------------------
function buildBreadCrum(strFullName)
{
  var iStart = 0
  if( strFullName.charAt(0) == '/' )
    iStart++
  var iEnd = strFullName.length-1
  if( strFullName.charAt(iEnd) == '/' )
    iEnd--

  return strFullName.substring(iStart, iEnd+1).replace(/\//g, " > ")
}

//[cf]
