*** TMP *** Fix decimalformat


Change-Id: Ie449e202f0ed0f1f7fa9c5527990df271190f4f0
diff --git a/org.eclipse.scout.rt.ui.html.test/src/test/js/scout/text/DecimalFormatSpec.js b/org.eclipse.scout.rt.ui.html.test/src/test/js/scout/text/DecimalFormatSpec.js
index 70e0dc6..1535a46 100644
--- a/org.eclipse.scout.rt.ui.html.test/src/test/js/scout/text/DecimalFormatSpec.js
+++ b/org.eclipse.scout.rt.ui.html.test/src/test/js/scout/text/DecimalFormatSpec.js
@@ -278,7 +278,6 @@
       expect(decimalFormat.format(-12345.6789)).toBe('M12~345!67');
 
       decimalFormat.allAfter = 0;
-      decimalFormat.pattern = '#,##0';
       expect(decimalFormat.format(-1000.1234)).toBe('M1~000');
       expect(decimalFormat.format(1000.1234)).toBe('1~001');
     });
diff --git a/org.eclipse.scout.rt.ui.html/src/main/js/scout/form/fields/numberfield/NumberField.js b/org.eclipse.scout.rt.ui.html/src/main/js/scout/form/fields/numberfield/NumberField.js
index a3b5ea1..5350660 100644
--- a/org.eclipse.scout.rt.ui.html/src/main/js/scout/form/fields/numberfield/NumberField.js
+++ b/org.eclipse.scout.rt.ui.html/src/main/js/scout/form/fields/numberfield/NumberField.js
@@ -61,10 +61,10 @@
   scout.NumberField.parent.prototype.acceptInput.call(this, whileTyping);
 };
 
-scout.NumberField.prototype.parse = function() {
+scout.NumberField.prototype.parse = function(displayText) {
   var number = null;
   try {
-    number = this.decimalFormat.parse(this.displayText);
+    number = this.decimalFormat.parse(scout.nvl(displayText, this.displayText));
   } catch(e) {
     // catch Error thrown when number isNaN
   }
@@ -75,10 +75,7 @@
   var input = this.$field.val();
   if (input) {
     // Convert to JS number format (remove groupingChar, replace decimalSeparatorChar with '.')
-    input = input
-      .replace(new RegExp('[' + this.decimalFormat.groupingChar + ']', 'g'), '')
-      .replace(new RegExp('[' + this.decimalFormat.decimalSeparatorChar + ']', 'g'), '.')
-      .replace(/\s/g, '');
+    input = this.decimalFormat.normalizeString(input);
 
     // if only math symbols are in the input string...
     if (this.calc.isFormula(input)) {
@@ -94,3 +91,11 @@
     }
   }
 };
+
+scout.NumberField.prototype._parseValue = function(displayText) {
+  return this.parse(displayText);
+};
+
+scout.NumberField.prototype._formatValue = function(value) {
+  return this.decimalFormat.format(value, false);
+};
diff --git a/org.eclipse.scout.rt.ui.html/src/main/js/scout/text/DecimalFormat.js b/org.eclipse.scout.rt.ui.html/src/main/js/scout/text/DecimalFormat.js
index d685fca..0d515a0 100644
--- a/org.eclipse.scout.rt.ui.html/src/main/js/scout/text/DecimalFormat.js
+++ b/org.eclipse.scout.rt.ui.html/src/main/js/scout/text/DecimalFormat.js
@@ -20,10 +20,11 @@
  */
 scout.DecimalFormat = function(locale, decimalFormatConfiguration) {
   // format function will use these (defaults)
-  this.positivePrefix = '';
-  this.positiveSuffix = '';
-  this.negativePrefix = locale.decimalFormatSymbols.minusSign;
-  this.negativeSuffix = '';
+  this.positivePrefix;
+  this.positiveSuffix;
+  this.negativePrefix;
+  this.negativeSuffix;
+  this.minusSign = locale.decimalFormatSymbols.minusSign;
   this.groupingChar = locale.decimalFormatSymbols.groupingSeparator;
   this.groupLength = 0;
   this.decimalSeparatorChar = locale.decimalFormatSymbols.decimalSeparator;
@@ -40,45 +41,67 @@
   // Check if there are separate subpatterns for positive and negative numbers ("PositivePattern;NegativePattern")
   var split = this.pattern.split(SYMBOLS.patternSeparator);
   // Use the first subpattern as positive prefix/suffix
-  var positivePrefixAndSuffix = findPrefixAndSuffix(split[0]);
-  this.positivePrefix = positivePrefixAndSuffix.prefix;
-  this.positiveSuffix = positivePrefixAndSuffix.suffix;
+  var positivePatternParts = scout.DecimalFormat.findPatternParts(split[0]);
+  this.positivePrefix = positivePatternParts.prefix;
+  this.positiveSuffix = positivePatternParts.suffix;
+  this.numberPattern = positivePatternParts.number;
   if (split.length > 1) {
     // Yes, there is a negative subpattern
-    var negativePrefixAndSuffix = findPrefixAndSuffix(split[1]);
-    this.negativePrefix = negativePrefixAndSuffix.prefix;
-    this.negativeSuffix = negativePrefixAndSuffix.suffix;
-    // from now on, only look at the positive subpattern
-    this.pattern = split[0];
+    var negativePatternParts = scout.DecimalFormat.findPatternParts(split[1]);
+    this.negativePrefix = negativePatternParts.prefix;
+    this.negativeSuffix = negativePatternParts.suffix;
+    if (negativePatternParts.prefixMinusMask.indexOf(SYMBOLS.minusSign) !== -1) {
+      this.minusSignInNegativePrefix = true;
+    }
+    if (negativePatternParts.suffixMinusMask.indexOf(SYMBOLS.minusSign) !== -1) {
+      this.minusSignInNegativeSuffix = true;
+    }
+    // "number" part is ignored, positive and negative number pattern are the same
   } else {
     // No, there is no negative subpattern, so the positive prefix/suffix are used for both positive and negative numbers.
+    this.negativePrefix = this.positivePrefix;
+    this.negativeSuffix = this.positiveSuffix;
     // Check if there is a minus sign in the prefix/suffix.
-    if (this.positivePrefix.indexOf(SYMBOLS.minusSign) !== -1 || this.positiveSuffix.indexOf(SYMBOLS.minusSign) !== -1) {
-      // Yes, there is a minus sign in the prefix/suffix. Use this a negativePrefix/Suffix and remove the minus sign from the posistivePrefix/Suffix.
-      this.negativePrefix = this.positivePrefix.replace(SYMBOLS.minusSign, locale.decimalFormatSymbols.minusSign);
-      this.negativeSuffix = this.positiveSuffix.replace(SYMBOLS.minusSign, locale.decimalFormatSymbols.minusSign);
-      this.positivePrefix = this.positivePrefix.replace(SYMBOLS.minusSign, '');
-      this.positiveSuffix = this.positiveSuffix.replace(SYMBOLS.minusSign, '');
-    } else {
-      // No, there is no minus sign in the prefix/suffix. Therefore, use the default negativePrefix/Suffix, but append the positivePrefix/Suffix
-      this.negativePrefix = this.positivePrefix + this.negativePrefix;
-      this.negativeSuffix = this.negativeSuffix + this.positiveSuffix;
+    var prefixMinusSignIndex = positivePatternParts.prefixMinusMask.indexOf(SYMBOLS.minusSign);
+    if (prefixMinusSignIndex !== -1) {
+      // Yes, there is a minus sign in the prefix. Use this a negativePrefix and remove the minus sign from the posistivePrefix.
+      while (prefixMinusSignIndex !== -1) {
+        this.positivePrefix = replaceCharAt(this.positivePrefix, prefixMinusSignIndex, '');
+        this.negativePrefix = replaceCharAt(this.negativePrefix, prefixMinusSignIndex, this.minusSign);
+        prefixMinusSignIndex = positivePatternParts.prefixMinusMask.indexOf(SYMBOLS.minusSign, prefixMinusSignIndex + 1);
+      }
+      this.minusSignInNegativePrefix = true;
+    }
+    var suffixMinusSignIndex = positivePatternParts.suffixMinusMask.indexOf(SYMBOLS.minusSign);
+    if (suffixMinusSignIndex !== -1) {
+      // Yes, there is a minus sign in the suffix. Use this a negativeSuffix and remove the minus sign from the posistiveSuffix.
+      while (suffixMinusSignIndex !== -1) {
+        this.positiveSuffix = replaceCharAt(this.positiveSuffix, suffixMinusSignIndex, '');
+        this.negativeSuffix = replaceCharAt(this.negativePrefix, suffixMinusSignIndex, this.minusSign);
+        suffixMinusSignIndex = positivePatternParts.suffixMinusMask.indexOf(SYMBOLS.minusSign, suffixMinusSignIndex + 1);
+      }
+      this.minusSignInNegativeSuffix = true;
+    }
+    if (!this.minusSignInNegativePrefix && !this.minusSignInNegativeSuffix) {
+      // No, there is no minus sign in the prefix/suffix. Therefore, automatically prepend the minus sign to the prefix.
+      this.negativePrefix = this.minusSign + this.positivePrefix;
+      this.minusSignInNegativePrefix = true;
     }
   }
 
   // find group length
-  var posDecimalSeparator = this.pattern.indexOf(SYMBOLS.decimalSeparator);
+  var posDecimalSeparator = this.numberPattern.indexOf(SYMBOLS.decimalSeparator);
   if (posDecimalSeparator === -1) {
-    posDecimalSeparator = this.pattern.length; // assume decimal separator at end
+    posDecimalSeparator = this.numberPattern.length; // assume decimal separator at end
   }
-  var posGroupingSeparator = this.pattern.lastIndexOf(SYMBOLS.groupingSeparator, posDecimalSeparator); // only search before decimal separator
+  var posGroupingSeparator = this.numberPattern.lastIndexOf(SYMBOLS.groupingSeparator, posDecimalSeparator); // only search before decimal separator
   if (posGroupingSeparator > 0) {
     this.groupLength = posDecimalSeparator - posGroupingSeparator - 1;
   }
-  this.pattern = this.pattern.replace(new RegExp('[' + SYMBOLS.groupingSeparator + ']', 'g'), '');
+  this.numberPattern = this.numberPattern.replace(new RegExp('[' + SYMBOLS.groupingSeparator + ']', 'g'), '');
 
   // split on decimal point
-  split = this.pattern.split(SYMBOLS.decimalSeparator);
+  split = this.numberPattern.split(SYMBOLS.decimalSeparator);
 
   // find digits before and after decimal point
   this.zeroBefore = scout.strings.count(split[0], SYMBOLS.zeroDigit);
@@ -87,29 +110,10 @@
     this.allAfter = this.zeroAfter + scout.strings.count(split[1], SYMBOLS.digit);
   }
 
-  // Returns an object with the properties 'prefix' and 'suffix', which contain all characters
-  // before or after any 'digit-like' character in the given pattern string.
-  function findPrefixAndSuffix(pattern) {
-    var result = {
-      prefix: '',
-      suffix: ''
-    };
-    // Find prefix (anything before the first 'digit-like' character)
-    var digitLikeCharacters = SYMBOLS.digit + SYMBOLS.zeroDigit + SYMBOLS.decimalSeparator + SYMBOLS.groupingSeparator;
-    var r = new RegExp('^(.*?)[' + digitLikeCharacters + '].*$');
-    var matches = r.exec(pattern);
-    if (matches !== null) {
-      // Ignore single quotes (for special, quoted characters - e.g. Java quotes percentage sign like '%')
-      result.prefix = matches[1].replace(new RegExp('\'([^\']+)\'', 'g'), '$1');
-    }
-    // Find suffix (anything before the first 'digit-like' character)
-    r = new RegExp('^.*[' + digitLikeCharacters + '](.*?)$');
-    matches = r.exec(pattern);
-    if (matches !== null) {
-      // Ignore single quotes (for special, quoted characters - e.g. Java quotes percentage sign like '%')
-      result.suffix = matches[1].replace(new RegExp('\'([^\']+)\'', 'g'), '$1');
-    }
-    return result;
+  // ----- Helper functions -----
+
+  function replaceCharAt(s, pos, replacement) {
+    return s.substring(0, pos) + replacement + s.substring(pos + 1);
   }
 };
 
@@ -121,10 +125,7 @@
   if (scout.strings.empty(numberString)) {
     return null;
   }
-  var pureNumber = numberString
-    .replace(new RegExp('[' + this.groupingChar + ']', 'g'), '')
-    .replace(new RegExp('[' + this.decimalSeparatorChar + ']', 'g'), '.')
-    .replace(/\s/g, '');
+  var pureNumber = this.normalizeString(numberString);
   var number = Number(pureNumber);
   if (isNaN(number)) {
     throw new Error(numberString + ' is not a number (NaN)');
@@ -132,6 +133,27 @@
   return number;
 };
 
+scout.DecimalFormat.prototype.normalizeString = function(numberString) {
+  if (scout.strings.empty(numberString)) {
+    return '';
+  }
+  var negativePrefixRegEx = new RegExp('^' + scout.strings.quote(this.negativePrefix));
+  var negativeSuffixRegEx = new RegExp(scout.strings.quote(this.negativeSuffix) + '$');
+  var minus = '';
+  if ((this.minusSignInNegativePrefix && negativePrefixRegEx.test(numberString)) ||
+      (this.minusSignInNegativeSuffix && negativeSuffixRegEx.test(numberString))) {
+    minus = '-';
+  }
+  return minus + numberString
+    .replace(new RegExp('^' + scout.strings.quote(this.positivePrefix)), '')
+    .replace(new RegExp(scout.strings.quote(this.positiveSuffix) + '$'), '')
+    .replace(negativePrefixRegEx, '')
+    .replace(negativeSuffixRegEx, '')
+    .replace(new RegExp('[' + this.groupingChar + ']', 'g'), '')
+    .replace(new RegExp('[' + this.decimalSeparatorChar + ']', 'g'), '.')
+    .replace(/\s/g, '');
+};
+
 scout.DecimalFormat.prototype.format = function(number, applyMultiplier) {
   applyMultiplier = scout.nvl(applyMultiplier, true);
   if (number === null || number === undefined) {
@@ -217,3 +239,68 @@
   }
   return new scout.DecimalFormat(locale, format);
 };
+
+/**
+ * Returns an object with the properties 'prefix', 'number' and 'suffix'. Number contains
+ * the part of the pattern that consists of 'digit-like' characters. Prefix and suffix
+ * contain the literal part before and after the number part, respectively. Single quotes
+ * that can be used to escape characters are removed from the result.
+ */
+scout.DecimalFormat.findPatternParts = function(pattern) {
+  var result = {
+    prefix: '',
+    prefixMinusMask: '',
+    number: '',
+    suffix: '',
+    suffixMinusMask: ''
+  };
+  var SYMBOLS = scout.DecimalFormat.PATTERN_SYMBOLS;
+  // Pattern that matches digit-like characters
+  var r = new RegExp('[' + SYMBOLS.digit + SYMBOLS.zeroDigit + SYMBOLS.decimalSeparator + SYMBOLS.groupingSeparator + ']');
+  var escape = false;
+  var scope = 'PREFIX';
+  for (var i = 0; i < pattern.length; i++) {
+    var ch = pattern.charAt(i);
+    if (scope === 'PREFIX') {
+      // prefix
+      if (ch === '\'') { // toggle escape
+        if (escape && pattern.charAt(i - 1) === '\'') { // two consecutive ' are equal to one literal '
+          result.prefix += '\'';
+          result.prefixMinusMask += ' ';
+        }
+        escape = !escape;
+        continue;
+      } else if (!escape && r.test(ch)) { // digit-like character, belongs to 'number' part
+        scope = 'NUMBER';
+      } else { // part of prefix
+        result.prefix += ch;
+        result.prefixMinusMask += (ch === SYMBOLS.minusSign && !escape ? '-' : ' ');
+        continue;
+      }
+    }
+    if (scope === 'NUMBER') {
+      // number
+      if (r.test(ch)) { // digit-like character
+        result.number += ch;
+        continue;
+      } else { // number is finished, belongs to suffix
+        scope = 'SUFFIX';
+      }
+    }
+    if (scope === 'SUFFIX') {
+      // suffix
+      if (ch === '\'') { // toggle escape
+        if (escape && pattern.charAt(i - 1) === '\'') { // two consecutive ' are equal to one literal '
+          result.suffix += '\'';
+          result.suffixMinusMask += ' ';
+        }
+        escape = !escape;
+        continue;
+      } else { // part of suffix
+        result.suffix += ch;
+        result.suffixMinusMask += (ch === SYMBOLS.minusSign && !escape ? '-' : ' ');
+      }
+    }
+  }
+  return result;
+};