/*****************************************************************************
 * Core javascript functions used client side and server side
 *
 * Copyright: Neolane 2001-2011
 *****************************************************************************/
//[of]:Formatting

/*****************************************************************************
 * Formating helper functions 
 *****************************************************************************/
String.prototype.padLeft = function(character, len)
{
  var str = this
  while ( str.length < len )
    str = character + str
  return str
}

String.prototype.startsWith = function(strCmp)
{
  return (this.match("^"+strCmp)==strCmp)
}

/** 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.toLowerCase()
    
    // next char should be in upper case if this one is neither a number nor a digit
    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
}

String.prototype.canonize = function()
{
  var accentMap = {
    " ":"_", "'":"_", "-":"_", "ç":"c",
    "à":"a", "â":"a", "ä":"a",
    "é":"e", "ë":"e", "è":"e", "ê":"e",
    "î":"i", "ï":"i",
    "ô":"o", "ö":"o",
    "û":"u", "ü":"u", "ù":"u",
    "/":"_"
  }

  return this.toString().replace(/([\s'\-çàâäéëèêîïôöûüù\/])/g, function(m) { return accentMap[m]; })
}

// Convert HTML reserved characters (& < > ") to their associated entities
String.prototype.encodeHtml = function()
{
  if (this == undefined)
    return ""

  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 ( ch.toUpperCase() != ch.toLowerCase() ) || Format.isDigit(ch)
}

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(bIsUTC)
{
  var iHour = Math.floor(bIsUTC ? this.getUTCHours() : this.getHours())
  if( iHour > 12 )
    iHour -= 12
  else if( iHour < 1 )
    iHour = 12;
  return iHour;
}

/**
 *   Copyright Mike Samuel
 *  
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * Original source code is here:
 *   http://json-sans-eval.googlecode.com/svn/trunk/src/json_sans_eval.js
 * Removed comments and modified indentation
 * 
 * @author Mike Samuel <mikesamuel@gmail.com>
 * Creates the JSON.parse function if it is not defined
 * @param {string} json per RFC 4627
 * @param {function (this:Object, string, *):*} opt_reviver optional function
 *     that reworks JSON objects post-parse per Chapter 15.12 of EcmaScript3.1.
 *     If supplied, the function is called with a string key, and a value.
 *     The value is the property of 'this'.  The reviver should return
 *     the value to use in its place.  So if dates were serialized as
 *     {@code { "type": "Date", "time": 1234 }}, then a reviver might look like
 *     {@code
 *     function (key, value) {
 *       if (value && typeof value === 'object' && 'Date' === value.type) {
 *         return new Date(value.time);
 *       } else {
 *         return value;
 *       }
 *     }}.
 *     If the reviver returns {@code undefined} then the property named by key
 *     will be deleted from its container.
 *     {@code this} is bound to the object containing the specified property.
 * @return {Object|Array}
 */
var JSON;
if( !JSON )
{
  JSON = {parse:(function ()
  {
    var number = '(?:-?\\b(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\\b)';
    var oneChar = '(?:[^\\0-\\x08\\x0a-\\x1f\"\\\\]|\\\\(?:[\"/\\\\bfnrt]|u[0-9A-Fa-f]{4}))';
    var string = '(?:\"' + oneChar + '*\")';
    var jsonToken = new RegExp('(?:false|true|null|[\\{\\}\\[\\]]|' + number + '|' + string + ')', 'g');
    var escapeSequence = new RegExp('\\\\(?:([^u])|u(.{4}))', 'g');
    var escapes = {'"': '"', '/': '/', '\\': '\\', 'b': '\b', 'f': '\f', 'n': '\n', 'r': '\r', 't': '\t'};
    function unescapeOne(_, ch, hex)
    {
      return ch ? escapes[ch] : String.fromCharCode(parseInt(hex, 16));
    }

    var EMPTY_STRING = new String('');
    var SLASH = '\\';

    var firstTokenCtors = { '{': Object, '[': Array };

    var hop = Object.hasOwnProperty;

    return function(json, opt_reviver)
    {
      var result, toks = json.match(jsonToken);
      var tok = toks[0];
      var topLevelPrimitive = false;
      if ('{' === tok)
        result = {};
      else if ('[' === tok)
        result = [];
      else
      {
        result = [];
        topLevelPrimitive = true;
      }

      var key, stack = [result];
      for( var i = 1 - topLevelPrimitive, n = toks.length; i < n; ++i )
      {
        tok = toks[i];
        var cont;
        switch (tok.charCodeAt(0))
        {
          default:
            cont = stack[0];
            cont[key || cont.length] = +(tok);
            key = void 0;
            break;
          case 0x22:  // '"'
            tok = tok.substring(1, tok.length - 1);
            if (tok.indexOf(SLASH) !== -1) {
              tok = tok.replace(escapeSequence, unescapeOne);
            }
            cont = stack[0];
            if (!key) {
              if (cont instanceof Array) {
                key = cont.length;
              } else {
                key = tok || EMPTY_STRING;
                break;
              }
            }
            cont[key] = tok;
            key = void 0;
            break;
          case 0x5b:  // '['
            cont = stack[0];
            stack.unshift(cont[key || cont.length] = []);
            key = void 0;
            break;
          case 0x5d:  // ']'
            stack.shift();
            break;
          case 0x66:  // 'f'
            cont = stack[0];
            cont[key || cont.length] = false;
            key = void 0;
            break;
          case 0x6e:  // 'n'
            cont = stack[0];
            cont[key || cont.length] = null;
            key = void 0;
            break;
          case 0x74:  // 't'
            cont = stack[0];
            cont[key || cont.length] = true;
            key = void 0;
            break;
          case 0x7b:  // '{'
            cont = stack[0];
            stack.unshift(cont[key || cont.length] = {});
            key = void 0;
            break;
          case 0x7d:  // '}'
            stack.shift();
            break;
        }
      }
      if (topLevelPrimitive)
      {
        if (stack.length !== 1)
          throw new Error();
        result = result[0];
      }
      else
      {
        if (stack.length)
          throw new Error();
      }

      if (opt_reviver)
      {
        var walk = function (holder, key)
        {
          var value = holder[key];
          if( value && typeof value === 'object' )
          {
            var toDelete = null;
            for (var k in value)
            {
              if (hop.call(value, k) && value !== holder)
              {
                var v = walk(value, k);
                if (v !== void 0)
                  value[k] = v;
                else
                {
                  if (!toDelete)
                    toDelete = [];
                  toDelete.push(k);
                }
              }
            }
            if (toDelete)
              for (var i = toDelete.length; --i >= 0;)
                delete value[toDelete[i]];
          }
          return opt_reviver.call(holder, key, value);
        };
        result = walk({ '': result }, '');
      }
      return result;
    };
    })()
  };
}

/**
 * The list of the periods for a given timezone.
 * The initialization can trigger an error if the tz is invalid or inexistent.
 */
function Timezone(strName)
{
  this.m_strName = strName;
  this.m_vRange = [];
  this.load();
}

/** 
 * @param oDate:Date The date which TZ offset is to be computed
 * @param strTimezone:string (optional) the name of the timezone.
 *  If not provided uses the user timezone if any
 * @return If no offset could be applied return the same oDate 
 * instance as the one passed in parameter, else a new Dtae instance
 * with the proper offset applied.
 */
Timezone.offset = function(oDate, strTimezone)
{
  if( !oDate || !(oDate instanceof Date) )
    return oDate
  
  // Uses the user context 
  if( !strTimezone )
  {  
    try
    {
      Timezone.g_tzUser = Timezone.g_tzUser || new Timezone()
      return Timezone.g_tzUser.offset(oDate)
    }
    catch( e )
    {
      return oDate
    }
  }
  else if( typeof strTimezone == "string" )
  {
    Timezone.g_tzRangeMap = Timezone.g_tzRangeMap || {}
    try
    {
      Timezone.g_tzRangeMap[strTimezone] = Timezone.g_tzRangeMap[strTimezone] || new Timezone(strTimezone)
      return Timezone.g_tzRangeMap[strTimezone].offset(oDate)
    }
    catch( e )
    {
      return oDate
    }
  }
  else
    return oDate
}

/**
 * Adds the TZ offset of the given date.
 * @param oDate:Date the date to have its time moved to the right hour of the timezone
 *    If no timezone can be found, oDate is left unchanged.
 */
Timezone.prototype.offset = function(oDate, bReverse)
{
  // dichotomic search
  var iSearched = oDate.getTime();
  var iHigh = this.m_vRange.length;
  var iLow = -1;
  var found = undefined;
  while(iHigh - iLow > 1)
  {
    var iMid = Math.floor((iHigh + iLow) / 2);
    if( this.m_vRange[iMid].startDate > iSearched)
      iHigh = iMid;
    else if( this.m_vRange[iMid].startDate == iSearched || iMid+1 >= this.m_vRange.length || this.m_vRange[iMid+1].startDate > iSearched )
    {
      found = this.m_vRange[iMid];
      break;
    }    
    else
      iLow = iMid;
  }
  if( !found )
    return oDate;
  
  if( bReverse )
    return new Date(oDate.getTime() - found.offset);
    
  return new Date(oDate.getTime() + found.offset);
}

/**
 *
 */
Timezone.prototype.load = function()
{
  try
  {
    // Is it possible to make an HttpRequest to the hosting server
    if( typeof window !== "undefined" &&
        (typeof window.XMLHttpRequest === "function" || typeof window.ActiveXObject === "function") )
    {
      this.fetchAsClient();
    }
    // Is it possible to make the request locally
    else if( typeof application !== "undefined" &&
             typeof application.getTimezoneJson === "function" )
    {
      this.m_vRange = JSON.parse(application.getTimezoneJson(this.m_strName || ""));
    }
    else
    {
      this.m_vRange = [];
    }
  }
  catch( e )
  {
    this.m_vRange = [];
  }
  this.m_vRange.sort(function(a, b) {return a.startDate - b.startDate});
}

/**
 * Fetches the timezone via an AJAX methodology.
 */
Timezone.prototype.fetchAsClient = function()
{
  // use HTTP to talk to the SOAP server
  //------------------------------------
  if( window.XMLHttpRequest )  // native XMLHttpRequest object
  {
    var request = new XMLHttpRequest(); // Let the excn escape
  }
  else if( window.ActiveXObject ) // IE/Windows ActiveX version
  { 
    try
    {
      var request = new ActiveXObject("Msxml2.XMLHTTP");
    }
    catch( e )
    {
      var request = new ActiveXObject("Microsoft.XMLHTTP"); // Let the excn escape
    }
  }

  var serverUrl = window.location.protocol + "//" + window.location.host + "/xtk/zoneJson.jssp";
  if( this.m_strName )
    serverUrl += "?name=" + this.m_strName;
  
  // Send as synchronous (asynch set to false)
  request.open("GET", serverUrl, false);

  // We add quotes for microsoft compatibility ... 
  request.setRequestHeader('If-Modified-Since', 'Wed, 15 Nov 1995 00:00:00 GMT');
  request.send();
  
  if( request.status != 200 )
    throw request.statusText;
  
  data = JSON.parse(request.responseText);
  data = data.data;
  
  this.m_vRange = data;
}

var Format = {

  // Locale: current locale, always in lower case with dashes (no underscores)
  "locale"       : null,
  // Language: current language, always in lower case
  "language"     : null,
  "settings"     : null, /** current settings */

  /** map of region settings */
  "REGION_SETTINGS" : 
  {
    // German
    "de": { "digitGroupingSymbol": ".", "decimalSymbol": ",", "shortDate": "%2D.%2M.%4Y", "longDate": "%A, %D. %B %4Y", "time": "%2H:%2N", "shortDateTime": "%2D.%2M.%4Y %2H:%2N", "AmPm": ["AM", "PM"],  
            "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"]},

    // English (United States)
    "en-us": { "digitGroupingSymbol": ",", "decimalSymbol": ".", "shortDate": "%2M/%2D/%4Y", "longDate": "%A, %D %B, %4Y", "time": "%I:%2N %P", "shortDateTime": "%2M/%2D/%4Y %I:%2N %P", "AmPm": ["AM", "PM"]}, 
    // English (United Kingdom)
    "en-gb": { "digitGroupingSymbol": ",", "decimalSymbol": ".", "shortDate": "%2D/%2M/%4Y", "longDate": "%A %D %B %4Y", "time": "%2H:%2N",  "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"]},
    // Arabic
    "ar": { "digitGroupingSymbol": ",", "decimalSymbol": ".", "shortDate": "%2D/%2M/%4Y", "longDate": "%A, %D. %B %4Y", "time": "%2I:%2N %P", "shortDateTime": "%2D/%2M/%4Y %2I:%2N %P", "AmPm": ["\u0635", "\u0645"]},
    // Chinese
    "zh": { "digitGroupingSymbol": ",", "decimalSymbol": ".", "shortDate": "%4Y-M-%D",    "longDate": "%A, %D. %B %4Y", "time": "%P%I:%2N", "shortDateTime": "%4Y-M-%D %P%I:%2N", "AmPm": ["\u4E0A\u5348", "\u4E0B\u5348"]},
    // Korean
    "ko": { "digitGroupingSymbol": ",", "decimalSymbol": ".", "shortDate": "%2D.%2M.%4Y", "longDate": "%A, %D. %B %4Y", "time": "%I:%2N", "shortDateTime": "%4Y. %M. %D %P %I:%2N", "AmPm": ["\uC624\uC804", "\uC624\uD6C4"]},
    // Danish
    "da": { "digitGroupingSymbol": ".", "decimalSymbol": ",", "shortDate": "%2D-%2M-%4Y", "longDate": "%D. %B %4Y", "time": "%2H:%2N", "shortDateTime": "%2D-%2M-%4Y %2H:%2N", "AmPm": ["AM", "PM"]},
    // Spanish
    "es": { "digitGroupingSymbol": ".", "decimalSymbol": ",", "shortDate": "%2D/%2M/%4Y", "longDate": "%A, %D de %B de %4Y", "time": "%2H:%2N", "shortDateTime": "%2M-%2D-%4Y %2I:%2N %P", "AmPm": ["AM", "PM"]},
    // Estonian
    "et": { "digitGroupingSymbol": " ", "decimalSymbol": ",", "shortDate": "%D.%2M.%4Y",  "longDate": "%D %B %4Y. a.", "time": "%H:%2N", "shortDateTime": "%D.%2M.%4Y %H:%2N", "AmPm": ["AM", "PM"]},
    // Finnish
    "fi": { "digitGroupingSymbol": " ", "decimalSymbol": ",", "shortDate": "%D.%M.%4Y",   "longDate": "%D. %B %4Y", "shortDateTime": "%D.%M.%4Y %H:%2N", "AmPm": ["AM", "PM"]},
    // French (France)
    "fr-fr": { "digitGroupingSymbol": " ", "decimalSymbol": ",", "shortDate": "%2D/%2M/%4Y", "longDate": "%A %D %B %4Y", "time": "%2H:%2N", "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 (Belgique)
    "fr-be": { "digitGroupingSymbol": ".", "decimalSymbol": ",", "shortDate": "%D/%2M/%4Y", "longDate": "%A %D %B %4Y", "time": "%H:%2N", "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":" "},
    // Greek
    "el": { "digitGroupingSymbol": ".", "decimalSymbol": ",", "shortDate": "%D/%M/%4Y",   "longDate": "%A, %D %B %4Y", "time": "%I:%2N %P", "shortDateTime": "%D/%M/%4Y %I:%2N %P", "AmPm": ["\u03C0\u03BC", "\u03BC\u03BC"]},
    // Hebrew
    "he": { "digitGroupingSymbol": ",", "decimalSymbol": ".", "shortDate": "%2D.%2M.%4Y", "longDate": "%A, %D. %B %4Y", "AmPm": ["AM", "PM"]}, 
    // Hungarian
    "hu": { "digitGroupingSymbol": " ", "decimalSymbol": ",", "shortDate": "%4Y.%2M.%2D.", "longDate": "%4Y. %B %D.", "time": "%H:%2N", "shortDateTime": "%4Y.%2M.%2D. %H:%2N", "AmPm" : ["DE", "DU"]},
    // Indonesian
    "id": { "digitGroupingSymbol": ".", "decimalSymbol": ",", "shortDate": "%2D/%2M/%4Y", "longDate": "%D %B %4Y", "time": "%2H:%2N", "AmPm": ["AM", "PM"]},
    // Irish
    "ga": { "digitGroupingSymbol": ",", "decimalSymbol": ".", "shortDate": "%4Y/%2M/%2D", "longDate": "%A, %D. %B %4Y", "time": "%2H:%2N", "shortDateTime": "%4Y/%2M/%2D %2H:%2N", "AmPm": ["a.m.", "p.m."]},
    // Italian (Italy)
    "it-it": { "digitGroupingSymbol": ".", "decimalSymbol": ",", "shortDate": "%2D/%2M/%4Y", "longDate": "%A %D %B %4Y", "time": "%H.%2N", "shortDateTime": "%2D/%2M/%4Y %H.%2N", "AmPm": ["AM", "PM"]},
    // Italian (Switzerland)
    "it-ch": { "digitGroupingSymbol": ".", "decimalSymbol": ",", "shortDate": "%2D/%2M/%4Y", "longDate": "%A, %D. %B %4Y", "time": "%H.%2N", "shortDateTime": "%2D/%2M/%4Y %H.%2N", "AmPm": ["AM", "PM"]},
    // Japanese
    "ja": { "digitGroupingSymbol": ",", "decimalSymbol": ".", "shortDate": "%4Y/%2M/%2D", "longDate": "%A, %D. %B %4Y", "time": "%H:%2N", "shortDateTime": "%4Y/%2M/%2D. %H:%2N", "AmPm": ["\u5348\u524D", "\u5348\u5F8C"]}, 
    // Latvian
    "lv": { "digitGroupingSymbol": " ", "decimalSymbol": ",", "shortDate": "%4Y.%2M.%2D.", "longDate": "%A, %4Y. gada %D. %B", "time": "%2H:%2N", "shortDateTime": "%4Y.%2M.%2D. %2H:%2N", "AmPm": ["AM", "PM"]},
    // Lithuanian
    "lt": { "digitGroupingSymbol": ".", "decimalSymbol": ",", "shortDate": "%4Y.%D.%M.", "longDate": "%4Y m. %B %D d.", "time": "%2H:%2N", "shortDateTime": "%4Y.%D.%M %2H:%2N", "AmPm": ["AM", "PM"]},
    // Maltese
    "mt": { "digitGroupingSymbol": ",", "decimalSymbol": ".", "shortDate": "%2D/%2M/%4Y", "longDate": "%A, %D ta' %B %4Y", "time": "%2H:%2N", "shortDateTime": "%2D/%2M/%4Y %2H:%2N", "AmPm": ["QN", "WN"]},
    // Dutch (Netherlands)
    "nl-nl": { "digitGroupingSymbol": ".", "decimalSymbol": ",", "shortDate": "%D-%M-%4Y", "longDate": "%A %D %B %4Y", "time": "%H:%2N", "shortDateTime": "%D-%M-%4Y %H:%2N", "AmPm": ["AM", "PM"]},
    // Dutch (Belgium)
    "nl-be": { "digitGroupingSymbol": ".", "decimalSymbol": ",", "shortDate": "%D/%2M/%4Y", "longDate": "%A %D %B %4Y", "time": "%H:%2N", "shortDateTime": "%D/%2M/%4Y %H:%2N", "AmPm": ["AM", "PM"]},
    // Norwegian (Norway,Nynorsk) - form http://www.localeplanet.com/java/no-NO-NY/
    "no": { "digitGroupingSymbol": " ", "decimalSymbol": ",", "shortDate": "%2D.%2M.%4Y", "longDate": "%A %D. %B %4Y", "time": "%2H:%2N", "shortDateTime": "%2D.%2M.%4Y %2H:%2N", "AmPm": ["AM", "PM"],
            "daysName": ["sundag", "måndag", "tysdag", "onsdag", "torsdag", "fredag", "laurdag"],
            "monthesName" : ["januar", "februar", "mars", "april", "mai", "juni", "juli", "august", "september", "oktober", "november", "desember"],
            "percentSeparator":" "},
    // Polish
    "pl": { "digitGroupingSymbol": " ", "decimalSymbol": ",", "shortDate": "%4Y-%2M-%2D", "longDate": "%D %B %4Y", "time": "%2H:%2N", "shortDateTime": "%4Y-%2M-%2D %2H:%2N", "AmPm": ["AM", "PM"]},
    // Portuguese (Potugual)
    "pt-pt": { "digitGroupingSymbol": ".", "decimalSymbol": ",", "shortDate": "%2D-%2M-%4Y", "longDate": "%A, %D de %B de %4Y", "time": "%H:%2N", "shortDateTime": "%2D-%2M-%4Y %H:%2N", "AmPm": ["AM", "PM"]}, 
    // Portuguese (Brazil)
    "pt-br": { "digitGroupingSymbol": ".", "decimalSymbol": ",", "shortDate": "%2D/%2M/%4Y", "longDate": "%A, %D de %B de %4Y", "time": "%2H:%2N", "shortDateTime": "%2D/%2M/%4Y %2H:%2N", "AmPm": ["AM", "PM"]}, 
    // Russian
    "ru": { "digitGroupingSymbol": " ", "decimalSymbol": ",", "shortDate": "%2D.%2M.%4Y", "longDate": "%D %B %4Y",  "time": "%H:%2N", "shortDateTime": "%2D.%2M.%4Y %H:%2N", "AmPm": ["AM", "PM"]},
    // Slovene
    "sl": { "digitGroupingSymbol": ".", "decimalSymbol": ",", "shortDate": "%D.%M.%4Y", "longDate": "%D. %B %4Y",  "time": "%H:%2N", "shortDateTime": "%D.%M.%4Y %H:%2N", "AmPm": ["AM", "PM"]},
    // Slovak
    "sk": { "digitGroupingSymbol": " ", "decimalSymbol": ",", "shortDate": "%D.%M.%4Y", "longDate": "%D. %B %4Y", "time": "%H:%2N",  "shortDateTime": "%D.%M.%4Y %H:%2N", "AmPm": ["AM", "PM"]},
    // Swedish (Sweden)
    "sv-se": { "digitGroupingSymbol": " ", "decimalSymbol": ",", "shortDate": "%4Y-%2M-%2D", "longDate": "%A %D %B %4Y", "time": "%2H:%2N", "shortDateTime": "%4Y-%2M-%2D %2H:%2N", "AmPm": ["AM", "PM"]},
    // Swedish (finland)
    "sv-fi": { "digitGroupingSymbol": " ", "decimalSymbol": ",", "shortDate": "%D.%M.%4Y", "longDate": "%A %D %B %4Y", "AmPm": ["AM", "PM"]},
    // Czech
    "cs": { "digitGroupingSymbol": " ", "decimalSymbol": ",", "shortDate": "%D.%M.%4Y", "longDate": "%D. %B %4Y",  "time": "%H:%2N", 
            "shortDateTime": "%D.%M.%4Y %H:%2N", "AmPm": ["dop.", "odp."]},
    // Thai
    "th": { "digitGroupingSymbol": ",", "decimalSymbol": ".", "shortDate": "%M/%D/%4Y", "longDate": "%A, %D. %B %4Y", "time": "%I:%2N %P", "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"]},
    // Vietnamese            
    "vi": { "digitGroupingSymbol": ".", "decimalSymbol": ",", "shortDate": "%2D/%2M/%4Y", "longDate": "%A, %D. %B %4Y", "time": "%2H:%2N", "shortDateTime": "%2H:%2N %2D/%2M/%4Y", "AmPm" : ["SA", "CH"]},
    // Walloon
    "wa": { "digitGroupingSymbol": ".", "decimalSymbol": ",", "shortDate": "%2D.%2M.%4Y", "longDate": "%A, %D. %B %4Y", "AmPm": ["AM", "PM"], "time": "%H:%2N", "shortDateTime": "%D/%2M/%4Y %H:%2N"}
  },
  
  "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" // Shouldn't happen if setLanguage was called.
      
    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 the attribute "locale" and "language" (deduced from the locale and 
    * following the code ISO 639-1).
    *
    * @in locale or language.
    * @return true if the language had a region setting, false if it defaulted */
  "setLanguage": function(strLocale)
  {
    if( !strLocale )
      return false
    
    // The locale and language are always converted to lower case
    this.locale = strLocale.toLowerCase().replace('_', '-')
    this.language = this.locale.substr(0,2)
    
    var strFormatLanguage
    
    // Try to get the region settings from the locale, the language, a locale 
    // from the same language, or if all failed, from english.
    this.settings = this.REGION_SETTINGS[this.locale]
    var strDefaultRegion = "en-gb"
    var bGotSettings = true
    
    if( this.settings )
      strFormatLanguage = this.locale
    else
    {
      this.settings = this.REGION_SETTINGS[this.language]
      if( this.settings )
        strFormatLanguage = this.language
      else
      {
          // Try to find a locale from the same language in the region settings
        for( var languageIndex in this.REGION_SETTINGS )
        {
          if( languageIndex.substr(0,2) == this.language )
          {
            this.settings = this.REGION_SETTINGS[languageIndex]
            strFormatLanguage = languageIndex
            break;
          }
        }
        
        if( !this.settings )
        {
          this.settings = this.REGION_SETTINGS[strDefaultRegion]
          bGotSettings = false
          strFormatLanguage = strDefaultRegion
        }
      }
    }
    
    if( strFormatLanguage && document )
      document.formatLanguage = strFormatLanguage

    if( this.settings != null)
    {
      // if a seetings is not defined in this locale, get the default one
      var settingsPart = [ "daysName", "monthesName", "quartersName", "digitGroupingSymbol", "decimalSymbol",
                           "shortDate", "longDate", "time", "shortDateTime", "AmPm"]
      for( var i = 0 ; i < settingsPart.length ; i++ )
      {
        if( this.settings[settingsPart[i]] == null )
          this.settings[settingsPart[i]] = this.REGION_SETTINGS[strDefaultRegion][settingsPart[i]]
      }

      if( this.settings.digitGroupingSymbol == "." )
        this.settings.digitGroupingRegEx = /\./g
      else
        this.settings.digitGroupingRegEx = new RegExp(this.settings.digitGroupingSymbol, "g")
    }

    if( this.language == 'fr' )
      this.columnSuffix = '\u00A0:'
    else
      this.columnSuffix = ':'
    
    return bGotSettings
  },
  
  "getLocale" : function()
  {
    var aLocalePart = this.locale.split('-')
    if( aLocalePart.length == 1 )
    {
      var defaultCountry = ""
      if( aLocalePart[0] == "en" )
        defaultCountry = "US" 
      else 
        defaultCountry = aLocalePart[0].toUpperCase();
      return aLocalePart[0] + "_" + defaultCountry
    }
    if( aLocalePart.length == 2 )
      return aLocalePart[0] + "_" + aLocalePart[1].toUpperCase()
    return this.locale
  },
  
  /** Set language from "Accept-Language" HTTP header
    *
    * @header: a string like "en-us,en;q=0.5,fr".
    * @validityCheck: optionnal callback to overide the default check */
  "setLanguageFromHttpHeader": function(header, validityCheck)
  {
    var aLocalesPerQuality = []
    var aData = header.split(/[;,=]/)
    var aLocales = []
    var aLength = aData.length
    for( var i = 0; i < aLength; ++i ) {
      var aDatum = Format.trim(aData[i])
      if( aDatum == "q" ) {
        if( aLocales.length > 0 ) {
          var iQuality = Format.parseInt(aData[i+1]) || 1
          aLocalesPerQuality.push({quality: iQuality, locales: aLocales})
          aLocales = []
        }
        ++i // bypass value following the q token
      }
      else if( aDatum )
        aLocales.push(aDatum)
    }
    if( aLocales.length > 0 )
      aLocalesPerQuality.push({quality: 1, locales: aLocales})
    
    aLocalesPerQuality = aLocalesPerQuality.sort(function(a,b) { return a.quality < b.quality })
    
    var bUseValidityCallback = typeof validityCheck == "function"
    for( var i = 0; i < aLocalesPerQuality.length; ++i )
    {
      var aLocalesForQuality = aLocalesPerQuality[i].locales
      for( var j = 0; j < aLocalesForQuality.length; ++j )
      {
        // We call setLanguage even when we use the validityCheck callback as
        // we need its side effects (set locale and language)
        var bValid = this.setLanguage(aLocalesForQuality[j])
        if( bUseValidityCallback )
          bValid = validityCheck(Format.locale, Format.language)
        if( bValid )
          return true
      }
    }
    
    return false
  },

/** 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

    var iDay      = date.getDate()
    var iMonth    = date.getMonth() + 1
    var iFullYear = date.getFullYear()
    var iWeekDay  = date.getDay()

    strDisplay = strDisplay.replace(/%D/ , iDay)
    strDisplay = strDisplay.replace(/%2D/, iDay.padLeft(2))
    strDisplay = strDisplay.replace(/%M/ , iMonth)
    strDisplay = strDisplay.replace(/%2M/, iMonth.padLeft(2))
    strDisplay = strDisplay.replace(/%Y/ , iFullYear % 100)
    strDisplay = strDisplay.replace(/%2Y/, (iFullYear % 100).padLeft(2))
    strDisplay = strDisplay.replace(/%4Y/, iFullYear)
    
    if( this.getSettings().daysName != null )
      strDisplay = strDisplay.replace(/%A/, this.getSettings().daysName[iWeekDay])
    else
      strDisplay = strDisplay.replace(/%A/, "")
      
    if( this.getSettings().monthesName != null )
      strDisplay = strDisplay.replace(/%B/, this.getSettings().monthesName[iMonth-1])
    else
      strDisplay = strDisplay.replace(/%B/, iMonth)
      
    if( this.getSettings().quartersName != null )
      strDisplay = strDisplay.replace(/%Q/, this.getSettings().quartersName[Math.floor((iMonth-1) / 3)])
    else
      strDisplay = strDisplay.replace(/%Q/, "")
    
    return strDisplay
  },
  workingTimeZone: null,

  /** Format a date+time according to the region format.
    *
    * @date    date+time to format
    * @format  boolean (display date in long format) or string (format)
    * @bSec    display the seconds
    * @return  a string */    
  "formatDateTime": function(date, format, bSec)
  {
    if( date == null || isNaN(date.getTime()) )
      return ""
      
    if( typeof format == "undefined" )
      format = this.getSettings().shortDateTime

    var strDisplay
    if( typeof format == "string" )
      strDisplay = format
    else if( format )
      strDisplay = this.getSettings().longDate
    else
      strDisplay = this.getSettings().shortDate

    if( bSec == true )
      strDisplay = strDisplay.replace(/(.)(%2N)/, "$1$2$1%2S")

    // Time zone handling    
    var offsetDate
    if( Format.workingTimeZone != null && typeof Format.workingTimeZone.offset == "function" )
      offsetDate = Format.workingTimeZone.offset(date)
    else
      offsetDate = Timezone.offset(date)

    if( offsetDate == date ) // No offset was applied
    {
      var iDay      = date.getDate()
      var iMonth    = date.getMonth() + 1
      var iFullYear = date.getFullYear()
      var iHours    = date.getHours()
      var iMinutes  = date.getMinutes()
      var iSeconds  = date.getSeconds()
      var iAmPm     = date.getAmPmHour()
      var iWeekDay  = date.getDay()
    }
    else // A new date object was returned => offset was applied
    {
      var iDay      = offsetDate.getUTCDate()
      var iMonth    = offsetDate.getUTCMonth() + 1
      var iFullYear = offsetDate.getUTCFullYear()
      var iHours    = offsetDate.getUTCHours()
      var iMinutes  = offsetDate.getUTCMinutes()
      var iSeconds  = offsetDate.getUTCSeconds()
      var iAmPm     = offsetDate.getAmPmHour(true)
      var iWeekDay  = offsetDate.getUTCDay()
    }
      
    strDisplay = strDisplay.replace(/%D/ , iDay)
    strDisplay = strDisplay.replace(/%2D/, iDay.padLeft(2))
    strDisplay = strDisplay.replace(/%M/ , iMonth)
    strDisplay = strDisplay.replace(/%2M/, iMonth.padLeft(2))
    strDisplay = strDisplay.replace(/%Y/ , iFullYear % 100)
    strDisplay = strDisplay.replace(/%2Y/, (iFullYear % 100).padLeft(2))
    strDisplay = strDisplay.replace(/%4Y/, iFullYear)

    // time
    strDisplay = strDisplay.replace(/%H/ , iHours)
    strDisplay = strDisplay.replace(/%2H/, iHours.padLeft(2))
    strDisplay = strDisplay.replace(/%I/ , iAmPm)
    strDisplay = strDisplay.replace(/%2I/, iAmPm.padLeft(2))
    strDisplay = strDisplay.replace(/%P/ , this.getSettings().AmPm[Math.floor(iHours / 12)])
    strDisplay = strDisplay.replace(/%N/ , iMinutes)
    strDisplay = strDisplay.replace(/%2N/, iMinutes.padLeft(2))
    strDisplay = strDisplay.replace(/%S/ , iSeconds)
    strDisplay = strDisplay.replace(/%2S/, iSeconds.padLeft(2))

    if( this.getSettings().daysName != null )
      strDisplay = strDisplay.replace(/%A/, this.getSettings().daysName[iWeekDay])
    else
      strDisplay = strDisplay.replace(/%A/, "")
    if( this.getSettings().monthesName != null )
      strDisplay = strDisplay.replace(/%B/, this.getSettings().monthesName[iMonth-1])
    else
      strDisplay = strDisplay.replace(/%B/, iMonth)
    if( this.getSettings().quartersName != null )
      strDisplay = strDisplay.replace(/%Q/, this.getSettings().quartersName[Math.floor((iMonth-1) / 3)])
    else
      strDisplay = strDisplay.replace(/%Q/, "")

    return strDisplay
  },
  
  /** 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 component.
    * 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
          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 res = /^([0-9]+)[-/]([0-9]+)[-/]([0-9]+)$/.exec(strDate)
    if( res )
    {
      var year   = parseInt(res[1], 10)
      var month  = parseInt(res[2], 10) - 1
      var day    = parseInt(res[3], 10)
      return new Date(year, month, day)
    }
    
    res = /^([0-9]+)[-/]([0-9]+)[-/]([0-9]+)[ T]([0-9]+):([0-9]+):([0-9]+)(\.[0-9]+)?(Z?)$/.exec(strDate)
    if( res )
    {
      var year   = parseInt(res[1], 10)
      var month  = parseInt(res[2], 10) - 1
      var day    = parseInt(res[3], 10)
      var hour   = parseInt(res[4], 10)
      var minute = parseInt(res[5], 10)
      var second = parseInt(res[6], 10)
      var utc    = res[8] == "Z"
      
      if( utc )
      {
        var date = new Date()
        date.setUTCFullYear(year)
        date.setUTCMonth(month, day)
        date.setUTCHours(hour)
        date.setUTCMinutes(minute)
        date.setUTCSeconds(second)
        return date
      }
      else
      {
        return new Date(year, month, day, hour, minute, second)
      }
    }
    
    return null
  },
  
  "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 at UTC.
  *
  * @date a date object.
  * @return a string. */
  "toISO8601": function(date)
  {
    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)
      + "Z"
  },
/** Shortcut to toISO8601 ***for compatibility purposes***
  *
  * @date a date object.
  * @return a string like '2007/10/8 11:19:31'. */
  "formatDateTimeInter": function(date)
  {
    var strDate = this.toISO8601(date)
    // Old formatting
    strDate = strDate.replace(/-/g, "/").slice(0, -1)
    return strDate
  },

/** Shortcut to toISO8601 ***for compatibility purposes***
  *
  * @date a date object.
  * @return a string. */  
  "formatDateTimeInterUTC": function(date, bUTC)
  {
    var strDate = this.toISO8601(date)
    // Old formatting
    var strSuffix = bUTC ? " UTC" : ""
    strDate = strDate.replace(/-/g, "/").replace(/Z$/, strSuffix)
    return strDate
  },
  
/**
  * 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.toISO8601(new Date(lUTCTime + (dOffset * 3600000)))
    
    if( bUTC )
      return strResDate + " UTC" + strOffset
    else
      return strResDate
  },
  
/** Parse an integral string.
  *
  * @in str the string to parse.
  * @return empty string if the string was empty, the parsed integer otherwise. */
  "parseInt": function(str)
  {
    return this.isEmptyString(str) ? "" : parseInt(str, 10)
  },

/** Parse a floating string.
  *
  * @in str the string to parse.
  * @return empty string if the string was empty, the parsed float otherwise. */
  "parseFloat": function(str)
  {
    return this.isEmptyString(str) ? "" : parseFloat(str)
  },

/** 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(/^([\w-]+\.)+[\w-]+$/) )
          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
  },

/** Check if a character is a digit (using ranges coming from PERL's sources).
  *
  * @ch character to test.
  * @return true or false. */
  "isDigit": function(ch)
  {
    var iCode = ch.charCodeAt(0)
    return ( 0x0030 <= iCode && iCode <= 0x0039 )
        || ( 0x0660 <= iCode && iCode <= 0x0669 )
        || ( 0x06F0 <= iCode && iCode <= 0x06F9 )
        || ( 0x0966 <= iCode && iCode <= 0x096F )
        || ( 0x09E6 <= iCode && iCode <= 0x09EF )
        || ( 0x0A66 <= iCode && iCode <= 0x0A6F )
        || ( 0x0AE6 <= iCode && iCode <= 0x0AEF )
        || ( 0x0B66 <= iCode && iCode <= 0x0B6F )
        || ( 0x0BE6 <= iCode && iCode <= 0x0BEF )
        || ( 0x0C66 <= iCode && iCode <= 0x0C6F )
        || ( 0x0CE6 <= iCode && iCode <= 0x0CEF )
        || ( 0x0D66 <= iCode && iCode <= 0x0D6F )
        || ( 0x0E50 <= iCode && iCode <= 0x0E59 )
        || ( 0x0ED0 <= iCode && iCode <= 0x0ED9 )
        || ( 0x0F20 <= iCode && iCode <= 0x0F29 )
        || ( 0x1040 <= iCode && iCode <= 0x1049 )
        || ( 0x17E0 <= iCode && iCode <= 0x17E9 )
        || ( 0x1810 <= iCode && iCode <= 0x1819 )
        || ( 0x1946 <= iCode && iCode <= 0x194F )
        || ( 0x19D0 <= iCode && iCode <= 0x19D9 )
        || ( 0xFF10 <= iCode && iCode <= 0xFF19 )
        || ( 0x104A0 <= iCode && iCode <= 0x104A9 )
        || ( 0x1D7CE <= iCode && iCode <= 0x1D7FF )
  }
}
//[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( typeof strText === "undefined" || strText === null )
    return ""
  var str = strText.toString().replace(/'/g, '\\\'')
  str = str.replace(/"/g, '&quot;')
  return str
}

function escapeXtkString(strText)
{
  if( typeof strText === "undefined" || strText === null )
    return "''"
  
  return "'" + strText.toString().replace(/\\/g, "\\\\").replace(/'/g, "\\'") + "'"
}

function escapeSqlString(strText)
{
  if( typeof strText === "undefined" || strText === null )
    return "''"

  // escape '\' first, then double quotes
  return "'" + strText.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( typeof strText === "undefined" || 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)
{
  if( strFullName==undefined )
    return '';
  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, " > ")
}

//---------------------------------------------------------------------------
// Truncate string value if needed
//---------------------------------------------------------------------------
function truncateStr(sValue, iCharCount, bNoEscape)
{
  var strRes = sValue
  if( sValue.length > iCharCount )
    strRes = sValue.substr(0, iCharCount/2)+ "..."+sValue.substr(sValue.length-(iCharCount/2), sValue.length)

  if (booleanValue(bNoEscape))
    return strRes
  return escapeXmlString(strRes)
}

//---------------------------------------------------------------------------
// Utility function for boolean value
//---------------------------------------------------------------------------
function booleanValue(bValue)
{
  bValue = String(bValue)
  if (bValue == "true" || bValue == "1")
    return true
  return false
}

//---------------------------------------------------------------------------
// Utilitiy function use to parse xtk img 
// Ex xtk:test.png => /xtk/test.png
//---------------------------------------------------------------------------
function ParseXtkImg(strImg)
{
  if (strImg == null || strImg == "" || strImg == undefined)
    return "/xtk/img/defaultimg.png"

  return "/"+strImg.toString().replace(/:/, "/img/")
}

//---------------------------------------------------------------------------
// Date helpers function for client side
//---------------------------------------------------------------------------
function formatLocalDate(date)
{
  return Format.formatDate(Format.parseDateTimeInter(date))
}
function formatLocalDateTime(date)
{
  return Format.formatDateTime(Format.parseDateTimeInter(date))
}
function getDatePart(date, datePart)
{
  switch( datePart )
  {
    case "H4":
    case "H2":
      return date.getHours().padLeft(2)
    case "MI":
      return date.getMinutes().padLeft(2)
    case "DD":
      return date.getDate().padLeft(2)
    case "MM":
    case "MN":
      return (date.getMonth()+1).padLeft(2)
    case "Y2":
      return date.getYear().padLeft(2)
    case "Y4":
      return date.getFullYear()
  }
  return ""
}

Date.prototype.setDatePart = function(datePart, strNewValuePart)
{
  var iNewValue = parseInt(strNewValuePart, 10)
  switch( datePart )
  {
    case "H4":
    case "H2":
      this.setHours(iNewValue); break;
    case "MI":
      this.setMinutes(iNewValue); break;
    case "DD":
      this.setDate(iNewValue); break;
    case "MM":
    case "MN":
      this.setMonth(iNewValue-1); break;
    case "Y2":
      this.setYear(iNewValue); break;
    case "Y4":
      this.setFullYear(iNewValue)
  }
}

//---------------------------------------------------------------------------
// Return the url from a view depending on whether we are in the console or
// not and on the view definition. This function works on the client and on 
// the server side
// @param elView : view as defined in the navtrees
// @param bConsole : called from the console ?
// @param bEscapeAmpersands : should this function escape the & in the url ?
// @param bDontUseParam : don't add view params
//---------------------------------------------------------------------------
function urlFromView(elView, bConsole, bEscapeAmpersands, bDontUseParam)
{
  var strUrl = "#"
  var strAmpersand = bEscapeAmpersands ? "&amp;" : "&"
  
  try
  {
    // Client compatibility (elView type is xml only on the server side)
    if( typeof elView != "xml" )
    {
      // IE doesn't allow us to add functions to the element, so we encapsulate it
      elView = { elView : elView }
      elView.attribute = function(strName)
      {
        return E4XStyleAttribute(this.elView, strName)
      }
    }

    if( bConsole )
    {
      if( booleanValue(elView.attribute("noDock")) && elView.attribute("url").length() > 0 )
        // Open url to display directly the edition form
        strUrl = elView.attribute("url").toString()
      else if( elView.attribute("editForm").length() > 0 )
      {
        // Edition of form
        strUrl = "xtk://open/?form=" + elView.attribute("editForm")
        if( elView.attribute("editSchema").length() > 0 )
          strUrl += strAmpersand + "schema=" + elView.attribute("editSchema")
        if( !bDontUseParam && elView.attribute("editParam").length() > 0 )
          strUrl += strAmpersand + elView.attribute("editParam")
      }
      else
        // Navigation view
        strUrl = "xtk://navigate/?view=" + elView.attribute("name")
    }
    else
    {
      // Preference : url > web app > report
      var strViewUrl = elView.attribute("url").toString()
      if( strViewUrl != "" && strViewUrl[0] == "'" )
      {
        // Try evaluating the string 
        strViewUrl = eval(strViewUrl)
        // Make sure we still have a valid string afterwards
        strViewUrl = strViewUrl ? strViewUrl.toString() : ""
      }
      if( strViewUrl != "" && !strViewUrl.match("^xtk://") )
        strUrl = strViewUrl
      else if( elView.attribute("webApp").length() > 0 )
        strUrl = "/webApp/" + elView.attribute("webApp")
               + "?__userConfig=" + elView.attribute("name")
      else if( elView.attribute("report").length() > 0 )
        strUrl = "/report/" + elView.attribute("report") + "?__userConfig=" + elView.attribute("name")
      else
      {
        // we don't support it
        strUrl = null
      }
      
      if (strUrl && elView.attribute("parameters").toString())
        // Complete url with extra parameters
        strUrl += "&" + elView.attribute("parameters").toString()
    }
  }
  catch(e) {} // Stay silent
  
  return strUrl
}

//[cf]

//---------------------------------------------------------------------------
// Compute the domain on which the cookie has to be set
// Same logic as GetCookieDomain() in nlredir.cpp.
//
// @param host : the host name (e.g. t.nl.neolane.com)
//---------------------------------------------------------------------------
function getCookieDomain(strHost)
{
  var strDomain = strHost;
  var astrSubDomains = strDomain.split(".");
  if( astrSubDomains.length > 1 )
    strDomain = astrSubDomains.slice(1).join(".");
    
  return strDomain;
}

