@ui5/webcomponents-localization 0.0.0-47cc17a26 → 0.0.0-49bade48d
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +293 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/DateFormat.d.ts +23 -0
- package/dist/DateFormat.js +16 -0
- package/dist/DateFormat.js.map +1 -1
- package/dist/Locale.d.ts +5 -0
- package/dist/Locale.js +7 -0
- package/dist/Locale.js.map +1 -0
- package/dist/NumberFormat.d.ts +5 -0
- package/dist/NumberFormat.js +7 -0
- package/dist/NumberFormat.js.map +1 -0
- package/dist/dates/convertMonthNumbersToMonthNames.d.ts +2 -2
- package/dist/dates/convertMonthNumbersToMonthNames.js.map +1 -1
- package/dist/generated/assets/cldr/Unicode-Data-Files-LICENSE.txt +36 -24
- package/dist/generated/assets/cldr/ar.json +624 -354
- package/dist/generated/assets/cldr/ar_EG.json +624 -354
- package/dist/generated/assets/cldr/ar_SA.json +625 -355
- package/dist/generated/assets/cldr/bg.json +339 -215
- package/dist/generated/assets/cldr/ca.json +633 -509
- package/dist/generated/assets/cldr/cnr.json +960 -653
- package/dist/generated/assets/cldr/cs.json +343 -531
- package/dist/generated/assets/cldr/cy.json +684 -1050
- package/dist/generated/assets/cldr/da.json +354 -231
- package/dist/generated/assets/cldr/de.json +458 -330
- package/dist/generated/assets/cldr/de_AT.json +459 -331
- package/dist/generated/assets/cldr/de_CH.json +522 -386
- package/dist/generated/assets/cldr/el.json +317 -152
- package/dist/generated/assets/cldr/el_CY.json +317 -152
- package/dist/generated/assets/cldr/en.json +606 -253
- package/dist/generated/assets/cldr/en_AU.json +830 -376
- package/dist/generated/assets/cldr/en_GB.json +696 -251
- package/dist/generated/assets/cldr/en_HK.json +544 -203
- package/dist/generated/assets/cldr/en_IE.json +533 -192
- package/dist/generated/assets/cldr/en_IN.json +820 -296
- package/dist/generated/assets/cldr/en_NZ.json +534 -193
- package/dist/generated/assets/cldr/en_PG.json +509 -168
- package/dist/generated/assets/cldr/en_SG.json +526 -185
- package/dist/generated/assets/cldr/en_ZA.json +529 -186
- package/dist/generated/assets/cldr/es.json +355 -230
- package/dist/generated/assets/cldr/es_AR.json +444 -288
- package/dist/generated/assets/cldr/es_BO.json +419 -264
- package/dist/generated/assets/cldr/es_CL.json +455 -300
- package/dist/generated/assets/cldr/es_CO.json +412 -257
- package/dist/generated/assets/cldr/es_MX.json +515 -352
- package/dist/generated/assets/cldr/es_PE.json +397 -234
- package/dist/generated/assets/cldr/es_UY.json +424 -269
- package/dist/generated/assets/cldr/es_VE.json +398 -243
- package/dist/generated/assets/cldr/et.json +390 -233
- package/dist/generated/assets/cldr/fa.json +510 -271
- package/dist/generated/assets/cldr/fi.json +497 -251
- package/dist/generated/assets/cldr/fr.json +339 -106
- package/dist/generated/assets/cldr/fr_BE.json +341 -108
- package/dist/generated/assets/cldr/fr_CA.json +652 -397
- package/dist/generated/assets/cldr/fr_CH.json +342 -109
- package/dist/generated/assets/cldr/fr_LU.json +339 -106
- package/dist/generated/assets/cldr/he.json +726 -904
- package/dist/generated/assets/cldr/hi.json +529 -288
- package/dist/generated/assets/cldr/hr.json +392 -383
- package/dist/generated/assets/cldr/hu.json +459 -335
- package/dist/generated/assets/cldr/id.json +379 -128
- package/dist/generated/assets/cldr/it.json +420 -301
- package/dist/generated/assets/cldr/it_CH.json +426 -307
- package/dist/generated/assets/cldr/ja.json +332 -74
- package/dist/generated/assets/cldr/kk.json +540 -240
- package/dist/generated/assets/cldr/ko.json +371 -115
- package/dist/generated/assets/cldr/lt.json +428 -545
- package/dist/generated/assets/cldr/lv.json +415 -413
- package/dist/generated/assets/cldr/mk.json +691 -559
- package/dist/generated/assets/cldr/ms.json +395 -136
- package/dist/generated/assets/cldr/nb.json +355 -234
- package/dist/generated/assets/cldr/nl.json +357 -251
- package/dist/generated/assets/cldr/nl_BE.json +428 -322
- package/dist/generated/assets/cldr/pl.json +333 -459
- package/dist/generated/assets/cldr/pt.json +419 -298
- package/dist/generated/assets/cldr/pt_PT.json +362 -231
- package/dist/generated/assets/cldr/ro.json +369 -397
- package/dist/generated/assets/cldr/ru.json +599 -368
- package/dist/generated/assets/cldr/ru_UA.json +598 -367
- package/dist/generated/assets/cldr/sk.json +343 -517
- package/dist/generated/assets/cldr/sl.json +477 -640
- package/dist/generated/assets/cldr/sr.json +478 -531
- package/dist/generated/assets/cldr/sr_Latn.json +631 -684
- package/dist/generated/assets/cldr/sv.json +395 -258
- package/dist/generated/assets/cldr/th.json +452 -199
- package/dist/generated/assets/cldr/tr.json +391 -238
- package/dist/generated/assets/cldr/uk.json +696 -342
- package/dist/generated/assets/cldr/vi.json +836 -487
- package/dist/generated/assets/cldr/zh_CN.json +358 -104
- package/dist/generated/assets/cldr/zh_HK.json +366 -113
- package/dist/generated/assets/cldr/zh_SG.json +371 -118
- package/dist/generated/assets/cldr/zh_TW.json +480 -238
- package/dist/getCachedLocaleDataInstance.js +2 -0
- package/dist/getCachedLocaleDataInstance.js.map +1 -1
- package/dist/locale/getLocaleData.js +2 -0
- package/dist/locale/getLocaleData.js.map +1 -1
- package/dist/sap/base/Event.js +2 -2
- package/dist/sap/base/Eventing.js +4 -3
- package/dist/sap/base/assert.js +1 -1
- package/dist/sap/base/config/MemoryConfigurationProvider.js +1 -1
- package/dist/sap/base/future.js +12 -0
- package/dist/sap/base/i18n/Formatting.d.ts +5 -0
- package/dist/sap/base/i18n/Formatting.js +7 -0
- package/dist/sap/base/i18n/Formatting.js.map +1 -1
- package/dist/sap/base/i18n/LanguageTag.js +18 -12
- package/dist/sap/base/i18n/Localization.d.ts +3 -0
- package/dist/sap/base/i18n/Localization.js +5 -0
- package/dist/sap/base/i18n/Localization.js.map +1 -1
- package/dist/sap/base/i18n/ResourceBundle.js +20 -0
- package/dist/sap/base/i18n/date/CalendarType.js +9 -8
- package/dist/sap/base/i18n/date/CalendarWeekNumbering.js +9 -8
- package/dist/sap/base/i18n/date/TimezoneUtils.js +170 -18
- package/dist/sap/base/i18n/date/_EnumHelper.js +37 -0
- package/dist/sap/base/strings/camelize.js +1 -1
- package/dist/sap/base/strings/formatMessage.js +6 -1
- package/dist/sap/base/util/Properties.js +12 -0
- package/dist/sap/base/util/Version.js +19 -13
- package/dist/sap/base/util/_merge.js +1 -1
- package/dist/sap/base/util/array/uniqueSort.js +1 -1
- package/dist/sap/base/util/deepClone.js +2 -2
- package/dist/sap/base/util/deepEqual.js +1 -1
- package/dist/sap/base/util/deepExtend.js +60 -0
- package/dist/sap/base/util/extend.js +1 -1
- package/dist/sap/base/util/fetch.js +8 -0
- package/dist/sap/base/util/isEmptyObject.js +1 -1
- package/dist/sap/base/util/isPlainObject.js +1 -1
- package/dist/sap/base/util/merge.js +57 -0
- package/dist/sap/base/util/mixedFetch.js +3 -0
- package/dist/sap/base/util/now.js +7 -13
- package/dist/sap/base/util/uid.js +1 -1
- package/dist/sap/ui/Device.js +27 -0
- package/dist/sap/ui/Global.js +1 -0
- package/dist/sap/ui/VersionInfo.js +14 -0
- package/dist/sap/ui/base/DataType.js +63 -12
- package/dist/sap/ui/base/DesignTime.js +11 -0
- package/dist/sap/ui/base/EventProvider.js +21 -0
- package/dist/sap/ui/base/Metadata.js +45 -10
- package/dist/sap/ui/base/Object.js +2 -2
- package/dist/sap/ui/base/OwnStatics.js +6 -0
- package/dist/sap/ui/base/SyncPromise.js +98 -0
- package/dist/sap/ui/core/AnimationMode.js +42 -0
- package/dist/sap/ui/core/CalendarType.js +2 -2
- package/dist/sap/ui/core/ControlBehavior.js +11 -0
- package/dist/sap/ui/core/Lib.js +41 -0
- package/dist/sap/ui/core/Locale.js +9 -9
- package/dist/sap/ui/core/LocaleData.js +498 -333
- package/dist/sap/ui/core/Theming.js +25 -525
- package/dist/sap/ui/core/_UrlResolver.js +11 -0
- package/dist/sap/ui/core/date/Buddhist.js +2 -2
- package/dist/sap/ui/core/date/CalendarUtils.js +14 -12
- package/dist/sap/ui/core/date/CalendarWeekNumbering.js +2 -2
- package/dist/sap/ui/core/date/Gregorian.js +2 -2
- package/dist/sap/ui/core/date/Islamic.js +2 -2
- package/dist/sap/ui/core/date/Japanese.js +2 -2
- package/dist/sap/ui/core/date/Persian.js +2 -2
- package/dist/sap/ui/core/date/UI5Date.js +80 -32
- package/dist/sap/ui/core/date/UniversalDate.js +83 -101
- package/dist/sap/ui/core/format/DateFormat.js +70 -73
- package/dist/sap/ui/core/format/FormatUtils.js +30 -0
- package/dist/sap/ui/core/format/NumberFormat.js +3164 -0
- package/dist/sap/ui/core/format/TimezoneUtil.js +80 -11
- package/dist/sap/ui/core/getCompatibilityVersion.js +7 -0
- package/dist/sap/ui/core/theming/ThemeHelper.js +14 -0
- package/dist/sap/ui/security/Security.js +12 -0
- package/dist/sap/ui/util/XMLHelper.js +11 -0
- package/dist/sap/ui/util/_URL.js +1 -0
- package/dist/ui5loader-autoconfig.js +1 -0
- package/package-scripts.cjs +1 -1
- package/package.json +9 -11
- package/used-modules.txt +7 -1
|
@@ -0,0 +1,3164 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* OpenUI5
|
|
3
|
+
* (c) Copyright 2026 SAP SE or an SAP affiliate company.
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
|
|
5
|
+
*/
|
|
6
|
+
// Provides class sap.ui.core.format.NumberFormat
|
|
7
|
+
import Formatting from "../../../base/i18n/Formatting.js";
|
|
8
|
+
import Localization from "../../../base/i18n/Localization.js";
|
|
9
|
+
import BaseObject from "../../base/Object.js";
|
|
10
|
+
import Locale from "../Locale.js";
|
|
11
|
+
import LocaleData from "../LocaleData.js";
|
|
12
|
+
import Supportability from "../Supportability.js";
|
|
13
|
+
import FormatUtils from "./FormatUtils.js";
|
|
14
|
+
import Log from "../../../base/Log.js";
|
|
15
|
+
import assert from "../../../base/assert.js";
|
|
16
|
+
import extend from "../../../base/util/extend.js";
|
|
17
|
+
/**
|
|
18
|
+
* Format classes
|
|
19
|
+
*
|
|
20
|
+
* @namespace
|
|
21
|
+
* @name sap.ui.core.format
|
|
22
|
+
* @public
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Constructor for NumberFormat - must not be used: To get a NumberFormat instance, please use getInstance, getFloatInstance or getIntegerInstance.
|
|
27
|
+
*
|
|
28
|
+
* @class
|
|
29
|
+
* The NumberFormat is a static class for formatting and parsing numeric values according
|
|
30
|
+
* to a set of format options.
|
|
31
|
+
*
|
|
32
|
+
* @public
|
|
33
|
+
* @hideconstructor
|
|
34
|
+
* @alias sap.ui.core.format.NumberFormat
|
|
35
|
+
* @extends sap.ui.base.Object
|
|
36
|
+
*/
|
|
37
|
+
var NumberFormat = BaseObject.extend("sap.ui.core.format.NumberFormat", /** @lends sap.ui.core.format.NumberFormat.prototype */{
|
|
38
|
+
constructor: function (oFormatOptions) {
|
|
39
|
+
// Do not use the constructor
|
|
40
|
+
throw new Error();
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Regex for replacing the number part of a decimal or currency pattern
|
|
45
|
+
const rNumberPattern = /[0#.,]+/;
|
|
46
|
+
const rAllWhiteSpaces = /\s/g;
|
|
47
|
+
// Regex for checking whether the last character belongs to the Unicode General Category L (letter)
|
|
48
|
+
const rEndsWithLetter = /\p{L}$/u;
|
|
49
|
+
// Regex for checking whether the first character belongs to the Unicode General Category L (letter)
|
|
50
|
+
const rStartsWithLetter = /^\p{L}/u;
|
|
51
|
+
// splits a currency pattern into following matching groups:
|
|
52
|
+
// 0: complete match
|
|
53
|
+
// 1: the optional number pattern in front of the currency placeholder
|
|
54
|
+
// 2: the characters between the number pattern in front of the currency placeholder and the currency placeholder
|
|
55
|
+
// 3: the currency placeholder
|
|
56
|
+
// 4: the characters between the currency placeholder and the number pattern after the currency placeholder
|
|
57
|
+
// 5: the optional number pattern after the currency placeholder
|
|
58
|
+
const rSplitCurrencyPattern = /([0#.,]*)([^0#.,]*)(¤)([^0#.,]*)([0#.,]*)/;
|
|
59
|
+
// Regex for checking if a number has leading zeros
|
|
60
|
+
const rLeadingZeros = /^(-?)0+(\d)/;
|
|
61
|
+
// Regex for matching the number placeholder in pattern
|
|
62
|
+
const rNumPlaceHolder = /0+(\.0+)?/;
|
|
63
|
+
// Regex for checking that the given string only consists of '0' characters
|
|
64
|
+
const rOnlyZeros = /^0+$/;
|
|
65
|
+
// A regular expresssion that can be used to remove a leading "-" from a number representing zero,
|
|
66
|
+
// e.g. "-0", or "-0.00"; $1 contains the number without the leading "-"
|
|
67
|
+
const rRemoveMinusFromZero = /^-(0(?:.0+)?)$/;
|
|
68
|
+
// A regular expression that can be used to remove trailing zeros from a number
|
|
69
|
+
const rTrailingZeros = /0+$/;
|
|
70
|
+
// A regular expression that can be used to remove all RTL characters
|
|
71
|
+
// see: https://www.unicode.org/reports/tr44/#Bidi_Class_Values (Explicit Formatting Types)
|
|
72
|
+
const rAllRTLCharacters = /[\u061c\u200e\u200f\u202a\u202b\u202c]/g;
|
|
73
|
+
// A regular expression that can be used to remove the left-to-right mark character
|
|
74
|
+
const rLeftToRightMark = /\u200e/;
|
|
75
|
+
// Array of all available power of tens for short formats, max: 100 000 000 000 000
|
|
76
|
+
const aPowerOfTens = [10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000, 100000000000, 1000000000000, 10000000000000, 100000000000000];
|
|
77
|
+
|
|
78
|
+
/*
|
|
79
|
+
* Is used to validate existing grouping separators.
|
|
80
|
+
* e.g. yyy.yyy.yyy -> /^\d+(?:\.?\d{3})*\.?\d{3}$/
|
|
81
|
+
*/
|
|
82
|
+
var getGroupingRegExp = function (groupingSeparator, groupingSize, groupingBaseSize) {
|
|
83
|
+
var sGroupingEscaped = quote(groupingSeparator);
|
|
84
|
+
return new RegExp("^\\d+" + "(?:" + sGroupingEscaped + "?" + "\\d{" + groupingSize + "}" + ")*" + "" + sGroupingEscaped + "?" + "\\d{" + groupingBaseSize + "}" + "$");
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Internal enumeration to differentiate number types
|
|
89
|
+
*/
|
|
90
|
+
var mNumberType = {
|
|
91
|
+
INTEGER: "integer",
|
|
92
|
+
FLOAT: "float",
|
|
93
|
+
CURRENCY: "currency",
|
|
94
|
+
UNIT: "unit",
|
|
95
|
+
PERCENT: "percent"
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Specifies a rounding behavior for numerical operations capable of discarding precision. Each rounding mode in
|
|
100
|
+
* this object indicates how the least significant returned digits of rounded result are to be calculated.
|
|
101
|
+
*
|
|
102
|
+
* @public
|
|
103
|
+
* @enum {string}
|
|
104
|
+
* @alias sap.ui.core.format.NumberFormat.RoundingMode
|
|
105
|
+
*/
|
|
106
|
+
var mRoundingMode = {
|
|
107
|
+
/**
|
|
108
|
+
* Rounding mode to round towards negative infinity; examples of rounding results to one fractional digit: 0.51
|
|
109
|
+
* is rounded to 0.5, and -0.51 is rounded to -0.6.
|
|
110
|
+
* @public
|
|
111
|
+
* @type {string}
|
|
112
|
+
*/
|
|
113
|
+
FLOOR: "FLOOR",
|
|
114
|
+
/**
|
|
115
|
+
* Rounding mode to round towards positive infinity; examples of rounding results to one fractional digit: 0.51
|
|
116
|
+
* is rounded to 0.6, and -0.51 is rounded to -0.5.
|
|
117
|
+
* @public
|
|
118
|
+
* @type {string}
|
|
119
|
+
*/
|
|
120
|
+
CEILING: "CEILING",
|
|
121
|
+
/**
|
|
122
|
+
* Rounding mode to round towards zero; examples of rounding results to one fractional digit: 0.59 is rounded to
|
|
123
|
+
* 0.5, and -0.59 is rounded to -0.5.
|
|
124
|
+
* @public
|
|
125
|
+
* @type {string}
|
|
126
|
+
*/
|
|
127
|
+
TOWARDS_ZERO: "TOWARDS_ZERO",
|
|
128
|
+
/**
|
|
129
|
+
* Rounding mode to round away from zero; examples of rounding results to one fractional digit: 0.51 is rounded
|
|
130
|
+
* to 0.6, and -0.51 is rounded to -0.6.
|
|
131
|
+
* @public
|
|
132
|
+
* @type {string}
|
|
133
|
+
*/
|
|
134
|
+
AWAY_FROM_ZERO: "AWAY_FROM_ZERO",
|
|
135
|
+
/**
|
|
136
|
+
* Rounding mode to round towards the nearest neighbor, unless both neighbors are equidistant, in which case
|
|
137
|
+
* round towards negative infinity; examples of rounding results to one fractional digit: 0.54 or 0.46 are
|
|
138
|
+
* rounded to 0.5, -0.54 or -0.46 are rounded to -0.5, 0.55 is rounded to 0.5, and -0.55 is rounded to -0.6.
|
|
139
|
+
* @public
|
|
140
|
+
* @type {string}
|
|
141
|
+
*/
|
|
142
|
+
HALF_FLOOR: "HALF_FLOOR",
|
|
143
|
+
/**
|
|
144
|
+
* Rounding mode to round towards the nearest neighbor, unless both neighbors are equidistant, in which case
|
|
145
|
+
* round towards positive infinity; examples of rounding results to one fractional digit: 0.54 or 0.46 are
|
|
146
|
+
* rounded to 0.5, -0.54 or -0.46 are rounded to -0.5, 0.55 is rounded to 0.6, and -0.55 is rounded to -0.5.
|
|
147
|
+
* @public
|
|
148
|
+
* @type {string}
|
|
149
|
+
*/
|
|
150
|
+
HALF_CEILING: "HALF_CEILING",
|
|
151
|
+
/**
|
|
152
|
+
* Rounding mode to round towards the nearest neighbor, unless both neighbors are equidistant, in which case
|
|
153
|
+
* round towards zero; examples of rounding results to one fractional digit: 0.54 or 0.46 are rounded to 0.5,
|
|
154
|
+
* -0.54 or -0.46 are rounded to -0.5, 0.55 is rounded to 0.5, and -0.55 is rounded to -0.5.
|
|
155
|
+
* @public
|
|
156
|
+
* @type {string}
|
|
157
|
+
*/
|
|
158
|
+
HALF_TOWARDS_ZERO: "HALF_TOWARDS_ZERO",
|
|
159
|
+
/**
|
|
160
|
+
* Rounding mode to round towards the nearest neighbor unless, both neighbors are equidistant, in which case
|
|
161
|
+
* round away from zero; examples of rounding results to one fractional digit: 0.54 or 0.46 are rounded to 0.5,
|
|
162
|
+
* -0.54 or -0.46 are rounded to -0.5, 0.55 is rounded to 0.6, and -0.55 is rounded to -0.6.
|
|
163
|
+
* @public
|
|
164
|
+
* @type {string}
|
|
165
|
+
*/
|
|
166
|
+
HALF_AWAY_FROM_ZERO: "HALF_AWAY_FROM_ZERO"
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Adds the summand given as a number to an decimal given as a string.
|
|
171
|
+
*
|
|
172
|
+
* @param {string} sDecimal A positive or negative decimal number as string
|
|
173
|
+
* @param {int} iSummand An integer between -9 and 9 to be added to the given decimal number
|
|
174
|
+
* @returns {string} The sum of the two numbers as a string
|
|
175
|
+
*
|
|
176
|
+
* @private
|
|
177
|
+
*/
|
|
178
|
+
NumberFormat.add = function (sDecimal, iSummand) {
|
|
179
|
+
const aParts = sDecimal.split(".");
|
|
180
|
+
let sInteger = aParts[0];
|
|
181
|
+
const sFractionPart = aParts[1];
|
|
182
|
+
const bNegative = sInteger[0] === "-";
|
|
183
|
+
if (bNegative) {
|
|
184
|
+
sInteger = sInteger.slice(1);
|
|
185
|
+
iSummand = -iSummand;
|
|
186
|
+
}
|
|
187
|
+
const aDigits = sInteger.split("").map(Number);
|
|
188
|
+
const iLastIndex = aDigits.length - 1;
|
|
189
|
+
aDigits[iLastIndex] += iSummand;
|
|
190
|
+
for (let i = iLastIndex; i >= 0; i -= 1) {
|
|
191
|
+
if (aDigits[i] >= 10) {
|
|
192
|
+
aDigits[i] = aDigits[i] % 10;
|
|
193
|
+
if (i === 0) {
|
|
194
|
+
aDigits.unshift(1);
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
aDigits[i - 1] += 1;
|
|
198
|
+
} else if (aDigits[i] < 0 && i > 0) {
|
|
199
|
+
aDigits[i] = 10 + aDigits[i];
|
|
200
|
+
aDigits[i - 1] -= 1;
|
|
201
|
+
if (i === 1 && aDigits[0] === 0) {
|
|
202
|
+
aDigits.shift();
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
} else {
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (bNegative) {
|
|
210
|
+
aDigits[0] = -aDigits[0];
|
|
211
|
+
}
|
|
212
|
+
let sResult = aDigits.join("");
|
|
213
|
+
if (!sFractionPart) {
|
|
214
|
+
return sResult;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// If sResult is 0, the sign may be lost and has to be restored, e.g. "-5.123" + 5 => -5 + 5 = 0 => "-0.123"
|
|
218
|
+
sResult = sResult === "0" && bNegative ? "-0" : sResult;
|
|
219
|
+
const sResultSign = sResult[0] === "-" ? "-" : "";
|
|
220
|
+
// If both signs are equal, the fraction part can simply be appended
|
|
221
|
+
if (bNegative === !!sResultSign) {
|
|
222
|
+
return sResult + "." + sFractionPart;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// If the signs are different, aDigits contains only one digit which is different from zero; to compute the
|
|
226
|
+
// result, the result sign has to be kept, the integer part is the absolute sResult reduced by one, and the
|
|
227
|
+
// fractional part is (1 - fractional part), e.g. "2.123" - 5 => 2 - 5 = -3 => sign = "-", integer part is
|
|
228
|
+
// |-3| - 1 = 2 and fractional part is 1 - 0.123 = 0.877 without the leading "0." => "-2.877"
|
|
229
|
+
const aFractionDigits = sFractionPart.split("").map(Number);
|
|
230
|
+
for (let i = aFractionDigits.length - 1; i >= 0; i -= 1) {
|
|
231
|
+
aFractionDigits[i] = 10 - aFractionDigits[i];
|
|
232
|
+
if (i > 0) {
|
|
233
|
+
aFractionDigits[i - 1] += 1;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return sResultSign + (Math.abs(aDigits[0]) - 1) + "." + aFractionDigits.join("");
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Derives the maximum number of possible decimals from the given format option's <code>maxFractionDigits</code>
|
|
241
|
+
* and <code>decimals</code> properties.
|
|
242
|
+
*
|
|
243
|
+
* If <code>decimals</code> and <code>maxFractionDigits</code> are >= 0, then the minimum of
|
|
244
|
+
* <code>maxFractionDigits</code> and <code>decimals</code> is returned, - otherwise
|
|
245
|
+
* <code>decimals</code> is returned.
|
|
246
|
+
*
|
|
247
|
+
* @param {object} oFormatOptions
|
|
248
|
+
* @param {int} [oFormatOptions.decimals]
|
|
249
|
+
* The number of decimal digits
|
|
250
|
+
* @param {int} [oFormatOptions.maxFractionDigits]
|
|
251
|
+
* The maximum number of decimal digits
|
|
252
|
+
* @returns {int}
|
|
253
|
+
* The maximum decimals to be used
|
|
254
|
+
*
|
|
255
|
+
* @private
|
|
256
|
+
* @static
|
|
257
|
+
*/
|
|
258
|
+
NumberFormat.getMaximumDecimals = function ({
|
|
259
|
+
decimals,
|
|
260
|
+
maxFractionDigits
|
|
261
|
+
}) {
|
|
262
|
+
if (maxFractionDigits >= 0 && decimals > 0 && maxFractionDigits < decimals) {
|
|
263
|
+
return maxFractionDigits;
|
|
264
|
+
}
|
|
265
|
+
return decimals;
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Rounds the given number up to the smallest integer greater than or equal to the given number.
|
|
270
|
+
*
|
|
271
|
+
* @param {number|string} vNumber
|
|
272
|
+
* The number to be rounded up; it has at least one digit in front of the decimal point in case of type "string"
|
|
273
|
+
* @returns {number|string}
|
|
274
|
+
* The smallest integer greater than or equal to the given number; the returned type is the same as the type of
|
|
275
|
+
* the given number
|
|
276
|
+
*/
|
|
277
|
+
function ceil(vNumber) {
|
|
278
|
+
if (typeof vNumber === "number") {
|
|
279
|
+
return Math.ceil(vNumber);
|
|
280
|
+
}
|
|
281
|
+
const [sIntegerPart, sFractionPart = "0"] = vNumber.split(".");
|
|
282
|
+
return rOnlyZeros.test(sFractionPart) || sIntegerPart[0] === "-" ? sIntegerPart : NumberFormat.add(sIntegerPart, 1);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Rounds the given number down to the largest integer less than or equal to the given number.
|
|
287
|
+
*
|
|
288
|
+
* @param {number|string} vNumber
|
|
289
|
+
* The number to be rounded down; it has at least one digit in front of the decimal point in case of type "string"
|
|
290
|
+
* @returns {number|string}
|
|
291
|
+
* The largest integer less than or equal to the given number; the returned type is the same as the type of the
|
|
292
|
+
* given number
|
|
293
|
+
*/
|
|
294
|
+
function floor(vNumber) {
|
|
295
|
+
if (typeof vNumber === "number") {
|
|
296
|
+
return Math.floor(vNumber);
|
|
297
|
+
}
|
|
298
|
+
const [sIntegerPart, sFractionPart = "0"] = vNumber.split(".");
|
|
299
|
+
return rOnlyZeros.test(sFractionPart) || sIntegerPart[0] !== "-" ? sIntegerPart : NumberFormat.add(sIntegerPart, -1);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Adds 0.5 to or subtracts 0.5 from the given number.
|
|
304
|
+
*
|
|
305
|
+
* @param {number|string} vNumber
|
|
306
|
+
* The number to be increased or decreased by 0.5
|
|
307
|
+
* @param {boolean} bIncrease
|
|
308
|
+
* Whether to increase the number by 0.5; otherwise the number is decreased by 0.5
|
|
309
|
+
* @returns {number|string}
|
|
310
|
+
* The number increased or decreased by 0.5; the returned type is the same as the type of the given number
|
|
311
|
+
*/
|
|
312
|
+
function increaseOrDecreaseByHalf(vNumber, bIncrease) {
|
|
313
|
+
if (typeof vNumber === "number") {
|
|
314
|
+
return bIncrease ? vNumber + 0.5 : vNumber - 0.5;
|
|
315
|
+
}
|
|
316
|
+
vNumber = NumberFormat._shiftDecimalPoint(vNumber, 1);
|
|
317
|
+
vNumber = NumberFormat.add(vNumber, bIncrease ? 5 : -5);
|
|
318
|
+
return NumberFormat._shiftDecimalPoint(vNumber, -1);
|
|
319
|
+
}
|
|
320
|
+
const mRoundingFunction = {
|
|
321
|
+
[mRoundingMode.FLOOR]: floor,
|
|
322
|
+
[mRoundingMode.CEILING]: ceil,
|
|
323
|
+
[mRoundingMode.TOWARDS_ZERO]: vNumber => vNumber > 0 ? floor(vNumber) : ceil(vNumber),
|
|
324
|
+
[mRoundingMode.AWAY_FROM_ZERO]: vNumber => vNumber > 0 ? ceil(vNumber) : floor(vNumber),
|
|
325
|
+
[mRoundingMode.HALF_TOWARDS_ZERO]: vNumber => {
|
|
326
|
+
const bPositive = vNumber > 0;
|
|
327
|
+
vNumber = increaseOrDecreaseByHalf(vNumber, !bPositive);
|
|
328
|
+
return bPositive ? ceil(vNumber) : floor(vNumber);
|
|
329
|
+
},
|
|
330
|
+
[mRoundingMode.HALF_AWAY_FROM_ZERO]: vNumber => {
|
|
331
|
+
const bPositive = vNumber > 0;
|
|
332
|
+
vNumber = increaseOrDecreaseByHalf(vNumber, bPositive);
|
|
333
|
+
return bPositive ? floor(vNumber) : ceil(vNumber);
|
|
334
|
+
},
|
|
335
|
+
[mRoundingMode.HALF_FLOOR]: vNumber => ceil(increaseOrDecreaseByHalf(vNumber, false)),
|
|
336
|
+
[mRoundingMode.HALF_CEILING]: vNumber => floor(increaseOrDecreaseByHalf(vNumber, true))
|
|
337
|
+
};
|
|
338
|
+
NumberFormat.RoundingMode = mRoundingMode;
|
|
339
|
+
|
|
340
|
+
/*
|
|
341
|
+
* Default format options for Integer
|
|
342
|
+
*/
|
|
343
|
+
NumberFormat.oDefaultIntegerFormat = {
|
|
344
|
+
minIntegerDigits: 1,
|
|
345
|
+
maxIntegerDigits: 99,
|
|
346
|
+
minFractionDigits: 0,
|
|
347
|
+
maxFractionDigits: 0,
|
|
348
|
+
strictGroupingValidation: false,
|
|
349
|
+
groupingEnabled: false,
|
|
350
|
+
groupingSize: 3,
|
|
351
|
+
groupingSeparator: ",",
|
|
352
|
+
decimalSeparator: ".",
|
|
353
|
+
plusSign: "+",
|
|
354
|
+
minusSign: "-",
|
|
355
|
+
isInteger: true,
|
|
356
|
+
type: mNumberType.INTEGER,
|
|
357
|
+
showMeasure: false,
|
|
358
|
+
style: "standard",
|
|
359
|
+
showNumber: true,
|
|
360
|
+
parseAsString: false,
|
|
361
|
+
preserveDecimals: false,
|
|
362
|
+
roundingMode: NumberFormat.RoundingMode.TOWARDS_ZERO,
|
|
363
|
+
emptyString: NaN,
|
|
364
|
+
showScale: true
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
/*
|
|
368
|
+
* Default format options for Float
|
|
369
|
+
*/
|
|
370
|
+
NumberFormat.oDefaultFloatFormat = {
|
|
371
|
+
minIntegerDigits: 1,
|
|
372
|
+
maxIntegerDigits: 99,
|
|
373
|
+
minFractionDigits: 0,
|
|
374
|
+
maxFractionDigits: 99,
|
|
375
|
+
strictGroupingValidation: false,
|
|
376
|
+
groupingEnabled: true,
|
|
377
|
+
groupingSize: 3,
|
|
378
|
+
groupingSeparator: ",",
|
|
379
|
+
decimalSeparator: ".",
|
|
380
|
+
plusSign: "+",
|
|
381
|
+
minusSign: "-",
|
|
382
|
+
isInteger: false,
|
|
383
|
+
type: mNumberType.FLOAT,
|
|
384
|
+
showMeasure: false,
|
|
385
|
+
style: "standard",
|
|
386
|
+
showNumber: true,
|
|
387
|
+
parseAsString: false,
|
|
388
|
+
preserveDecimals: false,
|
|
389
|
+
roundingMode: NumberFormat.RoundingMode.HALF_AWAY_FROM_ZERO,
|
|
390
|
+
emptyString: NaN,
|
|
391
|
+
showScale: true
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
/*
|
|
395
|
+
* Default format options for Percent
|
|
396
|
+
*/
|
|
397
|
+
NumberFormat.oDefaultPercentFormat = {
|
|
398
|
+
minIntegerDigits: 1,
|
|
399
|
+
maxIntegerDigits: 99,
|
|
400
|
+
minFractionDigits: 0,
|
|
401
|
+
maxFractionDigits: 99,
|
|
402
|
+
strictGroupingValidation: false,
|
|
403
|
+
groupingEnabled: true,
|
|
404
|
+
groupingSize: 3,
|
|
405
|
+
groupingSeparator: ",",
|
|
406
|
+
decimalSeparator: ".",
|
|
407
|
+
plusSign: "+",
|
|
408
|
+
minusSign: "-",
|
|
409
|
+
percentSign: "%",
|
|
410
|
+
isInteger: false,
|
|
411
|
+
type: mNumberType.PERCENT,
|
|
412
|
+
showMeasure: false,
|
|
413
|
+
style: "standard",
|
|
414
|
+
showNumber: true,
|
|
415
|
+
parseAsString: false,
|
|
416
|
+
preserveDecimals: false,
|
|
417
|
+
roundingMode: NumberFormat.RoundingMode.HALF_AWAY_FROM_ZERO,
|
|
418
|
+
emptyString: NaN,
|
|
419
|
+
showScale: true
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
/*
|
|
423
|
+
* Default format options for Currency
|
|
424
|
+
* @name sap.ui.core.format.NumberFormat.oDefaultCurrencyFormat
|
|
425
|
+
*/
|
|
426
|
+
NumberFormat.oDefaultCurrencyFormat = {
|
|
427
|
+
minIntegerDigits: 1,
|
|
428
|
+
maxIntegerDigits: 99,
|
|
429
|
+
// the default value for min/maxFractionDigits is defined in oLocaleData.getCurrencyDigits
|
|
430
|
+
// they need to be left undefined here in order to detect whether they are set from outside
|
|
431
|
+
strictGroupingValidation: false,
|
|
432
|
+
groupingEnabled: true,
|
|
433
|
+
groupingSize: 3,
|
|
434
|
+
groupingSeparator: ",",
|
|
435
|
+
decimalSeparator: ".",
|
|
436
|
+
plusSign: "+",
|
|
437
|
+
minusSign: "-",
|
|
438
|
+
isInteger: false,
|
|
439
|
+
type: mNumberType.CURRENCY,
|
|
440
|
+
showMeasure: true,
|
|
441
|
+
currencyCode: true,
|
|
442
|
+
currencyContext: 'standard',
|
|
443
|
+
style: "standard",
|
|
444
|
+
showNumber: true,
|
|
445
|
+
customCurrencies: undefined,
|
|
446
|
+
parseAsString: false,
|
|
447
|
+
preserveDecimals: false,
|
|
448
|
+
roundingMode: NumberFormat.RoundingMode.HALF_AWAY_FROM_ZERO,
|
|
449
|
+
emptyString: NaN,
|
|
450
|
+
showScale: true,
|
|
451
|
+
// The 'precision' format option is ignored because the number of decimals shouldn't
|
|
452
|
+
// depend on the number of integer part of a number
|
|
453
|
+
ignorePrecision: true
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
/*
|
|
457
|
+
* Default format options for Unit (type is CLDR)
|
|
458
|
+
* @name sap.ui.core.format.NumberFormat.oDefaultUnitFormat
|
|
459
|
+
*/
|
|
460
|
+
NumberFormat.oDefaultUnitFormat = {
|
|
461
|
+
minIntegerDigits: 1,
|
|
462
|
+
maxIntegerDigits: 99,
|
|
463
|
+
strictGroupingValidation: false,
|
|
464
|
+
groupingEnabled: true,
|
|
465
|
+
groupingSize: 3,
|
|
466
|
+
groupingSeparator: ",",
|
|
467
|
+
decimalSeparator: ".",
|
|
468
|
+
plusSign: "+",
|
|
469
|
+
minusSign: "-",
|
|
470
|
+
isInteger: false,
|
|
471
|
+
type: mNumberType.UNIT,
|
|
472
|
+
showMeasure: true,
|
|
473
|
+
style: "standard",
|
|
474
|
+
showNumber: true,
|
|
475
|
+
customUnits: undefined,
|
|
476
|
+
allowedUnits: undefined,
|
|
477
|
+
parseAsString: false,
|
|
478
|
+
preserveDecimals: false,
|
|
479
|
+
roundingMode: NumberFormat.RoundingMode.HALF_AWAY_FROM_ZERO,
|
|
480
|
+
emptyString: NaN,
|
|
481
|
+
showScale: true
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Checks the given format options if decimal padding is allowed and if the decimal padding value is supported.
|
|
486
|
+
*
|
|
487
|
+
* @param {object} [oFormatOptions]
|
|
488
|
+
* The format options to be checked
|
|
489
|
+
* @param {int} [oFormatOptions.decimalPadding]
|
|
490
|
+
* The format option decimal padding
|
|
491
|
+
* @param {boolean} [oFormatOptions.showMeasure]
|
|
492
|
+
* The format option show measure
|
|
493
|
+
* @param {string} [oFormatOptions.style]
|
|
494
|
+
* The format option style
|
|
495
|
+
* @param {boolean} [bDecimalPaddingSupported=true]
|
|
496
|
+
* Whether decimal padding is supported
|
|
497
|
+
* @param {boolean} [bShowMeasureMustBeFalse=false]
|
|
498
|
+
* Whether the format option <code>showMeasure</code> must be set to false
|
|
499
|
+
* @throws {Error} If decimal padding cannot be used or the given decimal padding value is not supported
|
|
500
|
+
*
|
|
501
|
+
* @private
|
|
502
|
+
* @static
|
|
503
|
+
*/
|
|
504
|
+
NumberFormat.checkDecimalPadding = function (oFormatOptions, bDecimalPaddingSupported = true, bShowMeasureMustBeFalse = false) {
|
|
505
|
+
const iDecimalPadding = oFormatOptions?.decimalPadding;
|
|
506
|
+
if (!bDecimalPaddingSupported && iDecimalPadding !== undefined) {
|
|
507
|
+
throw new Error("Unsupported format option: 'decimalPadding' cannot be used with an integer or percent" + " instance of sap.ui.core.format.NumberFormat");
|
|
508
|
+
}
|
|
509
|
+
if (bShowMeasureMustBeFalse && iDecimalPadding && oFormatOptions?.showMeasure !== false) {
|
|
510
|
+
throw new Error("The format option 'decimalPadding' can only be used if the format option" + " 'showMeasure' is set to false");
|
|
511
|
+
}
|
|
512
|
+
if (iDecimalPadding < 1) {
|
|
513
|
+
throw new Error("The format option 'decimalPadding' must have a value greater than 0");
|
|
514
|
+
}
|
|
515
|
+
if (iDecimalPadding > 0 && (oFormatOptions?.style === "short" || oFormatOptions?.style === "long")) {
|
|
516
|
+
throw new Error("The format option 'decimalPadding' can only be used if the format option" + " 'style' is not set to 'short' or 'long'");
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* An alias for {@link #getFloatInstance}.
|
|
522
|
+
*
|
|
523
|
+
* @param {object} [oFormatOptions] Object which defines the format options. See the documentation of
|
|
524
|
+
* {@link #getFloatInstance} for the parameters
|
|
525
|
+
* @param {sap.ui.core.Locale} [oLocale] Locale to get the formatter for
|
|
526
|
+
* @return {sap.ui.core.format.NumberFormat} float instance of the NumberFormat
|
|
527
|
+
*
|
|
528
|
+
*/
|
|
529
|
+
NumberFormat.getInstance = function (oFormatOptions, oLocale) {
|
|
530
|
+
return this.getFloatInstance(oFormatOptions, oLocale);
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Get a float instance of the NumberFormat, which can be used for formatting.
|
|
535
|
+
*
|
|
536
|
+
* <p>
|
|
537
|
+
* This instance has HALF_AWAY_FROM_ZERO set as default rounding mode.
|
|
538
|
+
* Please set the roundingMode property in oFormatOptions to change the
|
|
539
|
+
* default value.
|
|
540
|
+
* </p>
|
|
541
|
+
*
|
|
542
|
+
* The following example shows how grouping is done:
|
|
543
|
+
* <pre>
|
|
544
|
+
* var oFormat = NumberFormat.getFloatInstance({
|
|
545
|
+
* "groupingEnabled": true, // grouping is enabled
|
|
546
|
+
* "groupingSeparator": '.', // grouping separator is '.'
|
|
547
|
+
* "groupingSize": 3, // the amount of digits to be grouped (here: thousand)
|
|
548
|
+
* "decimalSeparator": "," // the decimal separator must be different from the grouping separator
|
|
549
|
+
* });
|
|
550
|
+
*
|
|
551
|
+
* oFormat.format(1234.56); // "1.234,56"
|
|
552
|
+
* </pre>
|
|
553
|
+
*
|
|
554
|
+
* @param {object} [oFormatOptions] The option object, which supports the following parameters.
|
|
555
|
+
* If no options are given, default values according to the type and locale settings are used.
|
|
556
|
+
* @param {int} [oFormatOptions.decimalPadding]
|
|
557
|
+
* The target length of places after the decimal separator; if the number has fewer decimal places than given in
|
|
558
|
+
* this option, it is padded with whitespaces at the end up to the target length. An additional whitespace
|
|
559
|
+
* character for the decimal separator is added for a number without any decimals.
|
|
560
|
+
* <b>Note:</b> This format option is only allowed if the following conditions apply:
|
|
561
|
+
* <ul>
|
|
562
|
+
* <li>It has a value greater than 0.</li>
|
|
563
|
+
* <li>The <code>oFormatOptions.style</code> format option is <b>not</b> set to <code>"short"</code> or
|
|
564
|
+
* <code>"long"</code>.</li>
|
|
565
|
+
* </ul>
|
|
566
|
+
* @param {int} [oFormatOptions.decimals] defines the number of decimal digits
|
|
567
|
+
* @param {string} [oFormatOptions.decimalSeparator] defines the character used as decimal separator.
|
|
568
|
+
* Note: <code>decimalSeparator</code> must always be different from <code>groupingSeparator</code>.
|
|
569
|
+
* @param {null|number|string} [oFormatOptions.emptyString=NaN] since 1.30.0 defines what an empty string
|
|
570
|
+
* is parsed as, and what is formatted as an empty string. The allowed values are "" (empty string),
|
|
571
|
+
* NaN, <code>null</code>, or 0.
|
|
572
|
+
* The 'format' and 'parse' functions are done in a symmetric way. For example, when this
|
|
573
|
+
* parameter is set to NaN, an empty string is parsed as NaN, and NaN is formatted as an empty
|
|
574
|
+
* string.
|
|
575
|
+
* @param {int} [oFormatOptions.groupingBaseSize=3] defines the grouping base size in digits if
|
|
576
|
+
* it is different from the grouping size (e.g. Indian grouping)
|
|
577
|
+
* @param {boolean} [oFormatOptions.groupingEnabled=true] defines whether grouping is enabled
|
|
578
|
+
* (grouping separators are shown).
|
|
579
|
+
* <b>Note:</b> Grouping is disabled if the <code>groupingSize</code> format option is set to
|
|
580
|
+
* a non-positive value.
|
|
581
|
+
* @param {string} [oFormatOptions.groupingSeparator] defines the character used as grouping separator.
|
|
582
|
+
* Note: <code>groupingSeparator</code> must always be different from <code>decimalSeparator</code>.
|
|
583
|
+
* @param {int} [oFormatOptions.groupingSize=3] defines the grouping size in digits; the default
|
|
584
|
+
* is <code>3</code>.
|
|
585
|
+
* <b>Note:</b> If this format option is set to a non-positive value, grouping will be disabled entirely.
|
|
586
|
+
* @param {int} [oFormatOptions.maxFractionDigits=99] defines the maximum number of decimal digits
|
|
587
|
+
* @param {int} [oFormatOptions.maxIntegerDigits=99] defines the maximum number of non-decimal digits.
|
|
588
|
+
* If the number exceeds this maximum, e.g. 1e+120, "?" characters are shown instead of digits.
|
|
589
|
+
* @param {int} [oFormatOptions.minFractionDigits=0] defines the minimal number of decimal digits
|
|
590
|
+
* @param {int} [oFormatOptions.minIntegerDigits=1] defines the minimal number of non-decimal digits
|
|
591
|
+
* @param {string} [oFormatOptions.minusSign] defines the used minus symbol
|
|
592
|
+
* @param {boolean} [oFormatOptions.parseAsString=false] since 1.28.2 defines whether to output
|
|
593
|
+
* the string from the parse function in order to keep the precision for big numbers. Numbers
|
|
594
|
+
* in scientific notation are parsed back to standard notation. For example, "5e-3" is parsed
|
|
595
|
+
* to "0.005".
|
|
596
|
+
* @param {string} [oFormatOptions.pattern] CLDR number pattern which is used to format the number
|
|
597
|
+
* @param {string} [oFormatOptions.plusSign] defines the used plus symbol
|
|
598
|
+
* @param {int} [oFormatOptions.precision] The maximum number of digits in the formatted representation of a number;
|
|
599
|
+
* if the <code>precision</code> is less than the overall length of the number, its fractional part is truncated
|
|
600
|
+
* through rounding. As the <code>precision</code> only affects the rounding of a number, its integer part can
|
|
601
|
+
* retain more digits than defined by this parameter.
|
|
602
|
+
* <b>Example:</b> With a <code>precision</code> of 2, <code>234.567</code> is formatted to <code>235</code>.
|
|
603
|
+
* <b>Note:</b> The formatted output may differ depending on locale.
|
|
604
|
+
* @param {boolean} [oFormatOptions.preserveDecimals=false] Whether {@link #format} preserves
|
|
605
|
+
* decimal digits except trailing zeros in case there are more decimals than the
|
|
606
|
+
* <code>maxFractionDigits</code> format option allows.
|
|
607
|
+
* If decimals are not preserved, the formatted number is rounded to <code>maxFractionDigits</code>.
|
|
608
|
+
* @param {sap.ui.core.format.NumberFormat.RoundingMode} [oFormatOptions.roundingMode=HALF_AWAY_FROM_ZERO]
|
|
609
|
+
* Specifies the rounding behavior for discarding the digits after the maximum fraction digits
|
|
610
|
+
* defined by <code>maxFractionDigits</code>.
|
|
611
|
+
* This can be assigned
|
|
612
|
+
* <ul>
|
|
613
|
+
* <li>by value in {@link sap.ui.core.format.NumberFormat.RoundingMode RoundingMode},</li>
|
|
614
|
+
* <li>via a function that is used for rounding the number and takes two parameters: the number itself, and the
|
|
615
|
+
* number of decimal digits that should be reserved. <b>Using a function is deprecated since 1.121.0</b>;
|
|
616
|
+
* string based numbers are not rounded via this custom function.</li>
|
|
617
|
+
* </ul>
|
|
618
|
+
* @param {int} [oFormatOptions.shortDecimals] defines the number of decimal in the shortened format string. If this isn't specified, the 'decimals' options is used
|
|
619
|
+
* @param {int} [oFormatOptions.shortLimit] only use short number formatting for values above this limit
|
|
620
|
+
* @param {int} [oFormatOptions.shortRefNumber] since 1.40 specifies a number from which the scale factor for 'short' or 'long' style format is generated. The generated scale factor is
|
|
621
|
+
* used for all numbers which are formatted with this format instance. This option has effect only when the option 'style' is set to 'short' or 'long'. This option is by default set
|
|
622
|
+
* with <code>undefined</code> which means the scale factor is selected automatically for each number being formatted.
|
|
623
|
+
* @param {boolean} [oFormatOptions.showScale=true] since 1.40 specifies whether the scale factor is shown in the formatted number. This option takes effect only when the 'style' options is set to either 'short' or 'long'.
|
|
624
|
+
* @param {boolean} [oFormatOptions.strictGroupingValidation=false] whether the positions of grouping separators are validated. Space characters used as grouping separators are not validated.
|
|
625
|
+
* @param {string} [oFormatOptions.style=standard] defines the style of format. Valid values are
|
|
626
|
+
* 'short, 'long' or 'standard' (based on the CLDR decimalFormat). When set to 'short' or 'long',
|
|
627
|
+
* numbers are formatted into compact forms. When this option is set, the default value of the
|
|
628
|
+
* 'precision' option is set to 2. This can be changed by setting either min/maxFractionDigits,
|
|
629
|
+
* decimals, shortDecimals, or the 'precision' option itself.
|
|
630
|
+
* @param {sap.ui.core.Locale} [oLocale]
|
|
631
|
+
* The locale to get the formatter for; if no locale is given, a locale for the currently configured language is
|
|
632
|
+
* used; see {@link module:sap/base/i18n/Formatting.getLanguageTag Formatting.getLanguageTag}
|
|
633
|
+
* @return {sap.ui.core.format.NumberFormat} float instance of the NumberFormat
|
|
634
|
+
* @throws {Error} If the <code>oFormatOptions.decimalPadding</code> is set but is not allowed
|
|
635
|
+
* @static
|
|
636
|
+
* @public
|
|
637
|
+
*/
|
|
638
|
+
NumberFormat.getFloatInstance = function (oFormatOptions, oLocale) {
|
|
639
|
+
NumberFormat.checkDecimalPadding(oFormatOptions);
|
|
640
|
+
const oFormat = NumberFormat.createInstance(oFormatOptions, oLocale);
|
|
641
|
+
const oLocaleFormatOptions = oFormat.getLocaleFormatOptions(mNumberType.FLOAT);
|
|
642
|
+
oFormat.oFormatOptions = extend({}, this.oDefaultFloatFormat, oLocaleFormatOptions, oFormat.oOriginalFormatOptions);
|
|
643
|
+
oFormat.checkGroupingFormatOptions();
|
|
644
|
+
return oFormat;
|
|
645
|
+
};
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* Get an integer instance of the NumberFormat, which can be used for formatting.
|
|
649
|
+
*
|
|
650
|
+
* <p>
|
|
651
|
+
* This instance has TOWARDS_ZERO set as default rounding mode.
|
|
652
|
+
* Please set the roundingMode property in oFormatOptions to change the
|
|
653
|
+
* default value.
|
|
654
|
+
* </p>
|
|
655
|
+
*
|
|
656
|
+
* The following example shows how grouping is done:
|
|
657
|
+
* <pre>
|
|
658
|
+
* var oFormat = NumberFormat.getIntegerInstance({
|
|
659
|
+
* "groupingEnabled": true, // grouping is enabled
|
|
660
|
+
* "groupingSeparator": '.', // grouping separator is '.'
|
|
661
|
+
* "groupingSize": 3 // the amount of digits to be grouped (here: thousand)
|
|
662
|
+
* });
|
|
663
|
+
*
|
|
664
|
+
* oFormat.format(1234); // "1.234"
|
|
665
|
+
* </pre>
|
|
666
|
+
*
|
|
667
|
+
* @param {object} [oFormatOptions] The option object, which supports the following parameters.
|
|
668
|
+
* If no options are given, default values according to the type and locale settings are used.
|
|
669
|
+
* @param {int} [oFormatOptions.decimalPadding] Not supported.
|
|
670
|
+
* @param {int} [oFormatOptions.decimals] defines the number of decimal digits
|
|
671
|
+
* @param {string} [oFormatOptions.decimalSeparator] defines the character used as decimal separator.
|
|
672
|
+
* Note: <code>decimalSeparator</code> must always be different from <code>groupingSeparator</code>.
|
|
673
|
+
* @param {null|number|string} [oFormatOptions.emptyString=NaN] since 1.30.0 defines what an empty string
|
|
674
|
+
* is parsed as, and what is formatted as an empty string. The allowed values are "" (empty string)
|
|
675
|
+
* NaN, <code>null</code>, or 0.
|
|
676
|
+
* The 'format' and 'parse' functions are done in a symmetric way. For example, when this
|
|
677
|
+
* parameter is set to NaN, an empty string is parsed as NaN, and NaN is formatted as an empty
|
|
678
|
+
* string.
|
|
679
|
+
* @param {int} [oFormatOptions.groupingBaseSize=3] defines the grouping base size in digits if
|
|
680
|
+
* it is different from the grouping size (e.g. Indian grouping)
|
|
681
|
+
* @param {boolean} [oFormatOptions.groupingEnabled=false] defines whether grouping is enabled
|
|
682
|
+
* (grouping separators are shown).
|
|
683
|
+
* <b>Note:</b> Grouping is disabled if the <code>groupingSize</code> format option is set to
|
|
684
|
+
* a non-positive value.
|
|
685
|
+
* @param {string} [oFormatOptions.groupingSeparator] defines the character used as grouping separator.
|
|
686
|
+
* Note: <code>groupingSeparator</code> must always be different from <code>decimalSeparator</code>.
|
|
687
|
+
* @param {int} [oFormatOptions.groupingSize=3] defines the grouping size in digits; the default
|
|
688
|
+
* is <code>3</code>.
|
|
689
|
+
* <b>Note:</b> If this format option is set to a non-positive value, grouping will be disabled entirely.
|
|
690
|
+
* @param {int} [oFormatOptions.maxFractionDigits=0] defines the maximum number of decimal digits
|
|
691
|
+
* @param {int} [oFormatOptions.maxIntegerDigits=99] defines the maximum number of non-decimal digits.
|
|
692
|
+
* If the number exceeds this maximum, e.g. 1e+120, "?" characters are shown instead of digits.
|
|
693
|
+
* @param {int} [oFormatOptions.minFractionDigits=0] defines the minimal number of decimal digits
|
|
694
|
+
* @param {int} [oFormatOptions.minIntegerDigits=1] defines the minimal number of non-decimal digits
|
|
695
|
+
* @param {string} [oFormatOptions.minusSign] defines the used minus symbol
|
|
696
|
+
* @param {boolean} [oFormatOptions.parseAsString=false] since 1.28.2 defines whether to output
|
|
697
|
+
* the string from the parse function in order to keep the precision for big numbers. Numbers
|
|
698
|
+
* in scientific notation are parsed back to standard notation. For example, "5e+3" is parsed
|
|
699
|
+
* to "5000".
|
|
700
|
+
* @param {string} [oFormatOptions.pattern] CLDR number pattern which is used to format the number
|
|
701
|
+
* @param {string} [oFormatOptions.plusSign] defines the used plus symbol
|
|
702
|
+
* @param {int} [oFormatOptions.precision] <b>Note:</b> Only considered if the number format leads to a
|
|
703
|
+
* representation with decimal places, e.g. if the option <code>style: "short"</code> is set.
|
|
704
|
+
* The maximum number of digits in the formatted representation of a number; if the <code>precision</code> is
|
|
705
|
+
* less than the overall length of the number, its fractional part is truncated through rounding.
|
|
706
|
+
* As the <code>precision</code> only affects the rounding of a number, its integer part can retain more digits
|
|
707
|
+
* than defined by this parameter.
|
|
708
|
+
* <b>Example:</b> With a <code>precision</code> of 2 and <code>style: "short"</code>,
|
|
709
|
+
* <code>234567</code> is formatted to <code>"235K"</code>.
|
|
710
|
+
* <b>Note:</b> The formatted output may differ depending on locale.
|
|
711
|
+
* @param {boolean} [oFormatOptions.preserveDecimals=false] Whether {@link #format} preserves
|
|
712
|
+
* decimal digits except trailing zeros in case there are more decimals than the
|
|
713
|
+
* <code>maxFractionDigits</code> format option allows.
|
|
714
|
+
* If decimals are not preserved, the formatted number is rounded to <code>maxFractionDigits</code>.
|
|
715
|
+
* @param {sap.ui.core.format.NumberFormat.RoundingMode} [oFormatOptions.roundingMode=TOWARDS_ZERO]
|
|
716
|
+
* Specifies the rounding behavior for discarding the digits after the maximum fraction digits
|
|
717
|
+
* defined by <code>maxFractionDigits</code>.
|
|
718
|
+
* This can be assigned
|
|
719
|
+
* <ul>
|
|
720
|
+
* <li>by value in {@link sap.ui.core.format.NumberFormat.RoundingMode RoundingMode},</li>
|
|
721
|
+
* <li>via a function that is used for rounding the number and takes two parameters: the number itself, and the
|
|
722
|
+
* number of decimal digits that should be reserved. <b>Using a function is deprecated since 1.121.0</b>;
|
|
723
|
+
* string based numbers are not rounded via this custom function.</li>
|
|
724
|
+
* </ul>
|
|
725
|
+
* @param {int} [oFormatOptions.shortDecimals] defines the number of decimal in the shortened format string. If this isn't specified, the 'decimals' options is used
|
|
726
|
+
* @param {int} [oFormatOptions.shortLimit] only use short number formatting for values above this limit
|
|
727
|
+
* @param {int} [oFormatOptions.shortRefNumber] since 1.40 specifies a number from which the scale factor for 'short' or 'long' style format is generated. The generated scale factor is
|
|
728
|
+
* used for all numbers which are formatted with this format instance. This option has effect only when the option 'style' is set to 'short' or 'long'. This option is by default set
|
|
729
|
+
* with <code>undefined</code> which means the scale factor is selected automatically for each number being formatted.
|
|
730
|
+
* @param {boolean} [oFormatOptions.showScale=true] since 1.40 specifies whether the scale factor is shown in the formatted number. This option takes effect only when the 'style' options is set to either 'short' or 'long'.
|
|
731
|
+
* @param {boolean} [oFormatOptions.strictGroupingValidation=false] whether the positions of grouping separators are validated. Space characters used as grouping separators are not validated.
|
|
732
|
+
* @param {string} [oFormatOptions.style=standard] defines the style of format. Valid values are
|
|
733
|
+
* 'short, 'long' or 'standard' (based on the CLDR decimalFormat). When set to 'short' or 'long',
|
|
734
|
+
* numbers are formatted into compact forms. When this option is set, the default value of the
|
|
735
|
+
* 'precision' option is set to 2. This can be changed by setting either min/maxFractionDigits,
|
|
736
|
+
* decimals, shortDecimals, or the 'precision' option itself.
|
|
737
|
+
* @param {sap.ui.core.Locale} [oLocale]
|
|
738
|
+
* The locale to get the formatter for; if no locale is given, a locale for the currently configured language is
|
|
739
|
+
* used; see {@link module:sap/base/i18n/Formatting.getLanguageTag Formatting.getLanguageTag}
|
|
740
|
+
* @return {sap.ui.core.format.NumberFormat} integer instance of the NumberFormat
|
|
741
|
+
* @throws {Error} If the <code>oFormatOptions.decimalPadding</code> format option is provided
|
|
742
|
+
* @static
|
|
743
|
+
* @public
|
|
744
|
+
*/
|
|
745
|
+
NumberFormat.getIntegerInstance = function (oFormatOptions, oLocale) {
|
|
746
|
+
NumberFormat.checkDecimalPadding(oFormatOptions, false);
|
|
747
|
+
const oFormat = NumberFormat.createInstance(oFormatOptions, oLocale);
|
|
748
|
+
const oLocaleFormatOptions = oFormat.getLocaleFormatOptions(mNumberType.INTEGER);
|
|
749
|
+
oFormat.oFormatOptions = extend({}, NumberFormat.oDefaultIntegerFormat, oLocaleFormatOptions, oFormat.oOriginalFormatOptions);
|
|
750
|
+
oFormat.checkGroupingFormatOptions();
|
|
751
|
+
return oFormat;
|
|
752
|
+
};
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* Get a currency instance of the NumberFormat, which can be used for formatting.
|
|
756
|
+
*
|
|
757
|
+
* <p>
|
|
758
|
+
* This instance has HALF_AWAY_FROM_ZERO set as default rounding mode.
|
|
759
|
+
* Please set the roundingMode property in oFormatOptions to change the
|
|
760
|
+
* default value.
|
|
761
|
+
* </p>
|
|
762
|
+
*
|
|
763
|
+
* The currency instance supports locally defined custom currency exclusive to the created instance.
|
|
764
|
+
* The following example shows how to use custom currencies (e.g. for Bitcoins):
|
|
765
|
+
* <pre>
|
|
766
|
+
* var oFormat = NumberFormat.getCurrencyInstance({
|
|
767
|
+
* "currencyCode": false,
|
|
768
|
+
* "customCurrencies": {
|
|
769
|
+
* "BTC": {
|
|
770
|
+
* "symbol": "\u0243",
|
|
771
|
+
* "decimals": 3
|
|
772
|
+
* }
|
|
773
|
+
* }
|
|
774
|
+
* });
|
|
775
|
+
*
|
|
776
|
+
* oFormat.format(123.4567, "BTC"); // "Ƀ 123.457"
|
|
777
|
+
* </pre>
|
|
778
|
+
*
|
|
779
|
+
* As an alternative to using a fixed <code>symbol</code> for your custom currencies, you can also provide an ISO-Code.
|
|
780
|
+
* The provided ISO-Code will be used to look up the currency symbol in the global configuration,
|
|
781
|
+
* either defined in the CLDR or custom defined on the Format Settings (see
|
|
782
|
+
* {@link module:sap/base/i18n/Formatting.setCustomCurrencies Formatting.setCustomCurrencies},
|
|
783
|
+
* {@link module:sap/base/i18n/Formatting.addCustomCurrencies Formatting.addCustomCurrencies}).
|
|
784
|
+
*
|
|
785
|
+
* If no symbol is given at all, the custom currency key is used for formatting.
|
|
786
|
+
*
|
|
787
|
+
* <pre>
|
|
788
|
+
* var oFormat = NumberFormat.getCurrencyInstance({
|
|
789
|
+
* "currencyCode": false,
|
|
790
|
+
* "customCurrencies": {
|
|
791
|
+
* "MyDollar": {
|
|
792
|
+
* "isoCode": "USD",
|
|
793
|
+
* "decimals": 3
|
|
794
|
+
* },
|
|
795
|
+
* "Bitcoin": {
|
|
796
|
+
* "decimals": 2
|
|
797
|
+
* }
|
|
798
|
+
* }
|
|
799
|
+
* });
|
|
800
|
+
*
|
|
801
|
+
* // symbol looked up from global configuration
|
|
802
|
+
* oFormat.format(123.4567, "MyDollar"); // "$123.457"
|
|
803
|
+
*
|
|
804
|
+
* // no symbol available, custom currency key is rendered
|
|
805
|
+
* oFormat.format(777.888, "Bitcoin"); // "Bitcoin 777.89"
|
|
806
|
+
* </pre>
|
|
807
|
+
*
|
|
808
|
+
* @param {object} [oFormatOptions] The option object, which supports the following parameters.
|
|
809
|
+
* If no options are given, default values according to the type and locale settings are used.
|
|
810
|
+
* @param {boolean} [oFormatOptions.currencyCode=true] defines whether the currency is shown as
|
|
811
|
+
* a code in currency format. The currency symbol is displayed when this option is set to
|
|
812
|
+
* <code>false</code> and a symbol has been defined for the given currency code.
|
|
813
|
+
* @param {string} [oFormatOptions.currencyContext=standard] can be set either to 'standard'
|
|
814
|
+
* (the default value) or to 'accounting' for an accounting-specific currency display
|
|
815
|
+
* @param {Object<string,object>} [oFormatOptions.customCurrencies] defines a set of custom currencies exclusive to this NumberFormat instance.
|
|
816
|
+
* Custom currencies must not only consist of digits.
|
|
817
|
+
* If custom currencies are defined on the instance, no other currencies can be formatted and parsed by this instance.
|
|
818
|
+
* Globally available custom currencies can be added via the global configuration.
|
|
819
|
+
* See the above examples.
|
|
820
|
+
* See also {@link module:sap/base/i18n/Formatting.setCustomCurrencies Formatting.setCustomCurrencies} and
|
|
821
|
+
* {@link module:sap/base/i18n/Formatting.addCustomCurrencies Formatting.addCustomCurrencies}.
|
|
822
|
+
* @param {int} [oFormatOptions.decimalPadding]
|
|
823
|
+
* The target length of places after the decimal separator; if the number has fewer decimal places than given in
|
|
824
|
+
* this option, it is padded with whitespaces at the end up to the target length. An additional whitespace
|
|
825
|
+
* character for the decimal separator is added for a number without any decimals.
|
|
826
|
+
* <b>Note:</b> This format option is only allowed if the following conditions apply:
|
|
827
|
+
* <ul>
|
|
828
|
+
* <li>It has a value greater than 0.</li>
|
|
829
|
+
* <li>The <code>FormatOptions.showMeasure</code> format option is set to <code>false</code>.</li>
|
|
830
|
+
* <li>The <code>oFormatOptions.style</code> format option is <b>not</b> set to <code>"short"</code> or
|
|
831
|
+
* <code>"long"</code>.</li>
|
|
832
|
+
* </ul>
|
|
833
|
+
* @param {int} [oFormatOptions.decimals] defines the number of decimal digits
|
|
834
|
+
* @param {string} [oFormatOptions.decimalSeparator] defines the character used as decimal separator.
|
|
835
|
+
* Note: <code>decimalSeparator</code> must always be different from <code>groupingSeparator</code>.
|
|
836
|
+
* @param {null|number|string} [oFormatOptions.emptyString=NaN] since 1.30.0 defines what an empty string
|
|
837
|
+
* is parsed as, and what is formatted as an empty string. The allowed values are "" (empty string),
|
|
838
|
+
* NaN, <code>null</code>, or 0.
|
|
839
|
+
* The 'format' and 'parse' functions are done in a symmetric way. For example, when this
|
|
840
|
+
* parameter is set to NaN, an empty string is parsed as [NaN, undefined], and NaN is
|
|
841
|
+
* formatted as an empty string.
|
|
842
|
+
* @param {int} [oFormatOptions.groupingBaseSize=3] defines the grouping base size in digits if
|
|
843
|
+
* it is different from the grouping size (e.g. Indian grouping)
|
|
844
|
+
* @param {boolean} [oFormatOptions.groupingEnabled=true] defines whether grouping is enabled
|
|
845
|
+
* (grouping separators are shown).
|
|
846
|
+
* <b>Note:</b> Grouping is disabled if the <code>groupingSize</code> format option is set to
|
|
847
|
+
* a non-positive value.
|
|
848
|
+
* @param {string} [oFormatOptions.groupingSeparator] defines the character used as grouping separator.
|
|
849
|
+
* Note: <code>groupingSeparator</code> must always be different from <code>decimalSeparator</code>.
|
|
850
|
+
* @param {int} [oFormatOptions.groupingSize=3] defines the grouping size in digits; the default
|
|
851
|
+
* is <code>3</code>.
|
|
852
|
+
* <b>Note:</b> If this format option is set to a non-positive value, grouping will be disabled entirely.
|
|
853
|
+
* @param {int} [oFormatOptions.maxFractionDigits=99] defines the maximum number of decimal digits
|
|
854
|
+
* @param {int} [oFormatOptions.maxIntegerDigits=99] defines the maximum number of non-decimal digits.
|
|
855
|
+
* If the number exceeds this maximum, e.g. 1e+120, "?" characters are shown instead of digits.
|
|
856
|
+
* @param {int} [oFormatOptions.minFractionDigits=0] Deprecated as of 1.130; this format option does not have
|
|
857
|
+
* an effect on currency formats since decimals can always be determined, either through the given format options,
|
|
858
|
+
* custom currencies or the CLDR
|
|
859
|
+
* @param {int} [oFormatOptions.minIntegerDigits=1] defines the minimal number of non-decimal digits
|
|
860
|
+
* @param {string} [oFormatOptions.minusSign] defines the used minus symbol
|
|
861
|
+
* @param {boolean} [oFormatOptions.parseAsString=false] since 1.28.2 defines whether to output
|
|
862
|
+
* the string from the parse function in order to keep the precision for big numbers. Numbers
|
|
863
|
+
* in scientific notation are parsed back to standard notation. For example, "5e-3" is parsed
|
|
864
|
+
* to "0.005".
|
|
865
|
+
* @param {string} [oFormatOptions.pattern] CLDR number pattern which is used to format the number
|
|
866
|
+
* @param {string} [oFormatOptions.plusSign] defines the used plus symbol
|
|
867
|
+
* @param {boolean} [oFormatOptions.preserveDecimals=false] Whether {@link #format} preserves
|
|
868
|
+
* decimal digits except trailing zeros in case there are more decimals than the
|
|
869
|
+
* <code>maxFractionDigits</code> format option allows.
|
|
870
|
+
* If decimals are not preserved, the formatted number is rounded to <code>maxFractionDigits</code>.
|
|
871
|
+
* @param {sap.ui.core.format.NumberFormat.RoundingMode} [oFormatOptions.roundingMode=HALF_AWAY_FROM_ZERO]
|
|
872
|
+
* Specifies the rounding behavior for discarding the digits after the maximum fraction digits
|
|
873
|
+
* defined by <code>maxFractionDigits</code>.
|
|
874
|
+
* This can be assigned
|
|
875
|
+
* <ul>
|
|
876
|
+
* <li>by value in {@link sap.ui.core.format.NumberFormat.RoundingMode RoundingMode},</li>
|
|
877
|
+
* <li>via a function that is used for rounding the number and takes two parameters: the number itself, and the
|
|
878
|
+
* number of decimal digits that should be reserved. <b>Using a function is deprecated since 1.121.0</b>;
|
|
879
|
+
* string based numbers are not rounded via this custom function.</li>
|
|
880
|
+
* </ul>
|
|
881
|
+
* @param {int} [oFormatOptions.shortDecimals] defines the number of decimal in the shortened format string. If this isn't specified, the 'decimals' options is used
|
|
882
|
+
* @param {int} [oFormatOptions.shortLimit] only use short number formatting for values above this limit
|
|
883
|
+
* @param {int} [oFormatOptions.shortRefNumber] since 1.40 specifies a number from which the scale factor for 'short' or 'long' style format is generated. The generated scale factor is
|
|
884
|
+
* used for all numbers which are formatted with this format instance. This option has effect only when the option 'style' is set to 'short' or 'long'. This option is by default set
|
|
885
|
+
* with <code>undefined</code> which means the scale factor is selected automatically for each number being formatted.
|
|
886
|
+
* @param {boolean} [oFormatOptions.showMeasure=true] defines whether the currency code/symbol is shown in the formatted string,
|
|
887
|
+
* e.g. true: "1.00 EUR", false: "1.00" for locale "en"
|
|
888
|
+
* If both <code>showMeasure</code> and <code>showNumber</code> are false, an empty string is returned
|
|
889
|
+
* @param {boolean} [oFormatOptions.showNumber=true] defines whether the number is shown as part of the result string,
|
|
890
|
+
* e.g. 1 EUR for locale "en"
|
|
891
|
+
* <code>NumberFormat.getCurrencyInstance({showNumber:true}).format(1, "EUR"); // "1.00 EUR"</code>
|
|
892
|
+
* <code>NumberFormat.getCurrencyInstance({showNumber:false}).format(1, "EUR"); // "EUR"</code>
|
|
893
|
+
* If both <code>showMeasure</code> and <code>showNumber</code> are false, an empty string is returned
|
|
894
|
+
* @param {boolean} [oFormatOptions.showScale=true] since 1.40 specifies whether the scale factor is shown in the formatted number.
|
|
895
|
+
* This option takes effect only when the 'style' options is set to either 'short' or 'long'.
|
|
896
|
+
* @param {boolean} [oFormatOptions.strictGroupingValidation=false] whether the positions of grouping separators are validated. Space characters used as grouping separators are not validated.
|
|
897
|
+
* @param {string} [oFormatOptions.style=standard] defines the style of format. Valid values are
|
|
898
|
+
* 'short, 'long' or 'standard' (based on the CLDR decimalFormat). When set to 'short' or 'long',
|
|
899
|
+
* numbers are formatted into the 'short' form only. When this option is set, the default value of the
|
|
900
|
+
* 'precision' option is set to 2. This can be changed by setting either min/maxFractionDigits,
|
|
901
|
+
* decimals, shortDecimals, or the 'precision' option itself.
|
|
902
|
+
* @param {boolean} [oFormatOptions.trailingCurrencyCode] overrides the global configuration
|
|
903
|
+
* value {@link module:sap/base/i18n/Formatting.getTrailingCurrencyCode Formatting.getTrailingCurrencyCode},
|
|
904
|
+
* which has a default value of <code>true</>.
|
|
905
|
+
* This is ignored if <code>oFormatOptions.currencyCode</code> is set to <code>false</code>,
|
|
906
|
+
* or if <code>oFormatOptions.pattern</code> is supplied.
|
|
907
|
+
* @param {sap.ui.core.Locale} [oLocale]
|
|
908
|
+
* The locale to get the formatter for; if no locale is given, a locale for the currently configured language is
|
|
909
|
+
* used; see {@link module:sap/base/i18n/Formatting.getLanguageTag Formatting.getLanguageTag}
|
|
910
|
+
* @return {sap.ui.core.format.NumberFormat} currency instance of the NumberFormat
|
|
911
|
+
* @throws {Error} If the <code>oFormatOptions.decimalPadding</code> is set but is not allowed
|
|
912
|
+
* @static
|
|
913
|
+
* @public
|
|
914
|
+
*/
|
|
915
|
+
NumberFormat.getCurrencyInstance = function (oFormatOptions, oLocale) {
|
|
916
|
+
NumberFormat.checkDecimalPadding(oFormatOptions, true, true);
|
|
917
|
+
const oFormat = NumberFormat.createInstance(oFormatOptions, oLocale);
|
|
918
|
+
const oLocaleFormatOptions = oFormat.getLocaleFormatOptions(mNumberType.CURRENCY);
|
|
919
|
+
oFormat.oFormatOptions = extend({}, NumberFormat.oDefaultCurrencyFormat, oLocaleFormatOptions, oFormat.oOriginalFormatOptions);
|
|
920
|
+
|
|
921
|
+
// The format option "trailingCurrencyCode" is influenced by other options, such as pattern, currencyCode,
|
|
922
|
+
// global config
|
|
923
|
+
// Therefore set it manually without modifying the original oFormatOptions.
|
|
924
|
+
// E.g. the "pattern" option would overwrite this option, even if the "trailingCurrencyCode" option is set
|
|
925
|
+
// oFormatOptions.pattern = "###"
|
|
926
|
+
// oFormatOptions.trailingCurrencyCode = true
|
|
927
|
+
// ->
|
|
928
|
+
// oFormatOptions.trailingCurrencyCode = false
|
|
929
|
+
oFormat.oFormatOptions.trailingCurrencyCode = oFormat.showTrailingCurrencyCode();
|
|
930
|
+
oFormat._defineCustomCurrencySymbols();
|
|
931
|
+
if (oFormat.oFormatOptions.style === "long") {
|
|
932
|
+
oFormat.oFormatOptions.style = "short";
|
|
933
|
+
}
|
|
934
|
+
oFormat.checkGroupingFormatOptions();
|
|
935
|
+
return oFormat;
|
|
936
|
+
};
|
|
937
|
+
|
|
938
|
+
/**
|
|
939
|
+
* Get a unit instance of the NumberFormat, which can be used for formatting units.
|
|
940
|
+
*
|
|
941
|
+
* <p>
|
|
942
|
+
* This instance has HALF_AWAY_FROM_ZERO set as default rounding mode.
|
|
943
|
+
* Please set the roundingMode property in oFormatOptions to change the
|
|
944
|
+
* default value.
|
|
945
|
+
* </p>
|
|
946
|
+
*
|
|
947
|
+
* @param {object} [oFormatOptions] The option object, which supports the following parameters.
|
|
948
|
+
* If no options are given, default values according to the type and locale settings are used.
|
|
949
|
+
* @param {array} [oFormatOptions.allowedUnits] defines the allowed units for formatting and parsing, e.g. ["size-meter", "volume-liter", ...]
|
|
950
|
+
* @param {Object<string,object>} [oFormatOptions.customUnits] defines a set of custom units, e.g.
|
|
951
|
+
* {"electric-inductance": {
|
|
952
|
+
* "displayName": "henry",
|
|
953
|
+
* "unitPattern-count-one": "{0} H",
|
|
954
|
+
* "unitPattern-count-other": "{0} H",
|
|
955
|
+
* "perUnitPattern": "{0}/H",
|
|
956
|
+
* "decimals": 2,
|
|
957
|
+
* "precision": 4
|
|
958
|
+
* }}
|
|
959
|
+
* @param {int} [oFormatOptions.decimalPadding]
|
|
960
|
+
* The target length of places after the decimal separator; if the number has fewer decimal places than given in
|
|
961
|
+
* this option, it is padded with whitespaces at the end up to the target length. An additional whitespace
|
|
962
|
+
* character for the decimal separator is added for a number without any decimals.
|
|
963
|
+
* <b>Note:</b> This format option is only allowed if the following conditions apply:
|
|
964
|
+
* <ul>
|
|
965
|
+
* <li>It has a value greater than 0.</li>
|
|
966
|
+
* <li>The <code>FormatOptions.showMeasure</code> format option is set to <code>false</code>.</li>
|
|
967
|
+
* <li>The <code>oFormatOptions.style</code> format option is <b>not</b> set to <code>"short"</code> or
|
|
968
|
+
* <code>"long"</code>.</li>
|
|
969
|
+
* </ul>
|
|
970
|
+
* @param {int} [oFormatOptions.decimals] defines the number of decimal digits
|
|
971
|
+
* @param {string} [oFormatOptions.decimalSeparator] defines the character used as decimal separator.
|
|
972
|
+
* Note: <code>decimalSeparator</code> must always be different from <code>groupingSeparator</code>.
|
|
973
|
+
* @param {null|number|string} [oFormatOptions.emptyString=NaN] since 1.30.0 defines what an empty string
|
|
974
|
+
* is parsed as, and what is formatted as an empty string. The allowed values are "" (empty string),
|
|
975
|
+
* NaN, <code>null</code>, or 0.
|
|
976
|
+
* The 'format' and 'parse' functions are done in a symmetric way. For example, when this
|
|
977
|
+
* parameter is set to NaN, an empty string is parsed as [NaN, undefined], and NaN is
|
|
978
|
+
* formatted as an empty string.
|
|
979
|
+
* @param {int} [oFormatOptions.groupingBaseSize=3] defines the grouping base size in digits if
|
|
980
|
+
* it is different from the grouping size (e.g. Indian grouping)
|
|
981
|
+
* @param {boolean} [oFormatOptions.groupingEnabled=true] defines whether grouping is enabled
|
|
982
|
+
* (grouping separators are shown).
|
|
983
|
+
* <b>Note:</b> Grouping is disabled if the <code>groupingSize</code> format option is set to
|
|
984
|
+
* a non-positive value.
|
|
985
|
+
* @param {string} [oFormatOptions.groupingSeparator] defines the character used as grouping separator.
|
|
986
|
+
* Note: <code>groupingSeparator</code> must always be different from <code>decimalSeparator</code>.
|
|
987
|
+
* @param {int} [oFormatOptions.groupingSize=3] defines the grouping size in digits; the default
|
|
988
|
+
* is <code>3</code>.
|
|
989
|
+
* <b>Note:</b> If this format option is set to a non-positive value, grouping will be disabled entirely.
|
|
990
|
+
* @param {int} [oFormatOptions.maxFractionDigits=99] defines the maximum number of decimal digits
|
|
991
|
+
* @param {int} [oFormatOptions.maxIntegerDigits=99] defines the maximum number of non-decimal digits.
|
|
992
|
+
* If the number exceeds this maximum, e.g. 1e+120, "?" characters are shown instead of digits.
|
|
993
|
+
* @param {int} [oFormatOptions.minFractionDigits=0] defines the minimal number of decimal digits
|
|
994
|
+
* @param {int} [oFormatOptions.minIntegerDigits=1] defines the minimal number of non-decimal digits
|
|
995
|
+
* @param {string} [oFormatOptions.minusSign] defines the used minus symbol
|
|
996
|
+
* @param {boolean} [oFormatOptions.parseAsString=false] since 1.28.2 defines whether to output
|
|
997
|
+
* the string from the parse function in order to keep the precision for big numbers. Numbers
|
|
998
|
+
* in scientific notation are parsed back to standard notation. For example, "5e-3" is parsed
|
|
999
|
+
* to "0.005".
|
|
1000
|
+
* @param {string} [oFormatOptions.pattern] CLDR number pattern which is used to format the number
|
|
1001
|
+
* @param {string} [oFormatOptions.plusSign] defines the used plus symbol
|
|
1002
|
+
* @param {int} [oFormatOptions.precision] The maximum number of digits in the formatted representation of a number;
|
|
1003
|
+
* if the <code>precision</code> is less than the overall length of the number, its fractional part is truncated
|
|
1004
|
+
* through rounding. As the <code>precision</code> only affects the rounding of a number, its integer part can
|
|
1005
|
+
* retain more digits than defined by this parameter.
|
|
1006
|
+
* <b>Example:</b> With a <code>precision</code> of 2, the parameters <code>"234.567", "mass-kilogram"</code> are
|
|
1007
|
+
* formatted to <code>"235 kg"</code>.
|
|
1008
|
+
* <b>Note:</b> The formatted output may differ depending on locale.
|
|
1009
|
+
* @param {boolean} [oFormatOptions.preserveDecimals=false] Whether {@link #format} preserves
|
|
1010
|
+
* decimal digits except trailing zeros in case there are more decimals than the
|
|
1011
|
+
* <code>maxFractionDigits</code> format option allows.
|
|
1012
|
+
* If decimals are not preserved, the formatted number is rounded to <code>maxFractionDigits</code>.
|
|
1013
|
+
* @param {sap.ui.core.format.NumberFormat.RoundingMode} [oFormatOptions.roundingMode=HALF_AWAY_FROM_ZERO]
|
|
1014
|
+
* Specifies the rounding behavior for discarding the digits after the maximum fraction digits
|
|
1015
|
+
* defined by <code>maxFractionDigits</code>.
|
|
1016
|
+
* This can be assigned
|
|
1017
|
+
* <ul>
|
|
1018
|
+
* <li>by value in {@link sap.ui.core.format.NumberFormat.RoundingMode RoundingMode},</li>
|
|
1019
|
+
* <li>via a function that is used for rounding the number and takes two parameters: the number itself, and the
|
|
1020
|
+
* number of decimal digits that should be reserved. <b>Using a function is deprecated since 1.121.0</b>;
|
|
1021
|
+
* string based numbers are not rounded via this custom function.</li>
|
|
1022
|
+
* </ul>
|
|
1023
|
+
* @param {int} [oFormatOptions.shortDecimals] defines the number of decimals in the shortened
|
|
1024
|
+
* format string. If this option isn't specified, the 'decimals' option is used instead.
|
|
1025
|
+
* @param {int} [oFormatOptions.shortLimit] defines a limit above which only short number formatting is used
|
|
1026
|
+
* @param {int} [oFormatOptions.shortRefNumber] since 1.40 specifies a number from which the
|
|
1027
|
+
* scale factor for the 'short' or 'long' style format is generated. The generated scale
|
|
1028
|
+
* factor is used for all numbers which are formatted with this format instance. This option
|
|
1029
|
+
* only takes effect when the 'style' option is set to 'short' or 'long'. This option is
|
|
1030
|
+
* set to <code>undefined</code> by default, which means that the scale factor is selected
|
|
1031
|
+
* automatically for each number being formatted.
|
|
1032
|
+
* @param {boolean} [oFormatOptions.showMeasure=true] defines whether the unit of measure is shown in the formatted string,
|
|
1033
|
+
* e.g. for input 1 and "duration-day" true: "1 day", false: "1".
|
|
1034
|
+
* If both <code>showMeasure</code> and <code>showNumber</code> are false, an empty string is returned
|
|
1035
|
+
* @param {boolean} [oFormatOptions.showNumber=true] defines whether the number is shown as part of the result string,
|
|
1036
|
+
* e.g. 1 day for locale "en"
|
|
1037
|
+
* <code>NumberFormat.getUnitInstance({showNumber:true}).format(1, "duration-day"); // "1 day"</code>
|
|
1038
|
+
* <code>NumberFormat.getUnitInstance({showNumber:false}).format(1, "duration-day"); // "day"</code>
|
|
1039
|
+
* e.g. 2 days for locale "en"
|
|
1040
|
+
* <code>NumberFormat.getUnitInstance({showNumber:true}).format(2, "duration-day"); // "2 days"</code>
|
|
1041
|
+
* <code>NumberFormat.getUnitInstance({showNumber:false}).format(2, "duration-day"); // "days"</code>
|
|
1042
|
+
* If both <code>showMeasure</code> and <code>showNumber</code> are false, an empty string is returned
|
|
1043
|
+
* @param {boolean} [oFormatOptions.showScale=true] since 1.40 specifies whether the scale factor is shown in the formatted number. This option takes effect only when the 'style' options is set to either 'short' or 'long'.
|
|
1044
|
+
* @param {boolean} [oFormatOptions.strictGroupingValidation=false] whether the positions of grouping separators are validated. Space characters used as grouping separators are not validated.
|
|
1045
|
+
* @param {string} [oFormatOptions.style=standard] defines the style of format. Valid values are
|
|
1046
|
+
* 'short, 'long' or 'standard' (based on the CLDR decimalFormat). When set to 'short' or 'long',
|
|
1047
|
+
* numbers are formatted into compact forms. When this option is set, the default value of the
|
|
1048
|
+
* 'precision' option is set to 2. This can be changed by setting either min/maxFractionDigits,
|
|
1049
|
+
* decimals, shortDecimals, or the 'precision' option itself.
|
|
1050
|
+
* @param {sap.ui.core.Locale} [oLocale]
|
|
1051
|
+
* The locale to get the formatter for; if no locale is given, a locale for the currently configured language is
|
|
1052
|
+
* used; see {@link module:sap/base/i18n/Formatting.getLanguageTag Formatting.getLanguageTag}
|
|
1053
|
+
* @return {sap.ui.core.format.NumberFormat} unit instance of the NumberFormat
|
|
1054
|
+
* @throws {Error} If the <code>oFormatOptions.decimalPadding</code> is set but is not allowed
|
|
1055
|
+
* @static
|
|
1056
|
+
* @public
|
|
1057
|
+
*/
|
|
1058
|
+
NumberFormat.getUnitInstance = function (oFormatOptions, oLocale) {
|
|
1059
|
+
NumberFormat.checkDecimalPadding(oFormatOptions, true, true);
|
|
1060
|
+
const oFormat = NumberFormat.createInstance(oFormatOptions, oLocale);
|
|
1061
|
+
const oLocaleFormatOptions = oFormat.getLocaleFormatOptions(mNumberType.UNIT);
|
|
1062
|
+
oFormat.oFormatOptions = extend({}, NumberFormat.oDefaultUnitFormat, oLocaleFormatOptions, oFormat.oOriginalFormatOptions);
|
|
1063
|
+
oFormat.checkGroupingFormatOptions();
|
|
1064
|
+
return oFormat;
|
|
1065
|
+
};
|
|
1066
|
+
|
|
1067
|
+
/**
|
|
1068
|
+
* Get a percent instance of the NumberFormat, which can be used for formatting.
|
|
1069
|
+
*
|
|
1070
|
+
* <p>
|
|
1071
|
+
* This instance has HALF_AWAY_FROM_ZERO set as default rounding mode.
|
|
1072
|
+
* Please set the roundingMode property in oFormatOptions to change the
|
|
1073
|
+
* default value.
|
|
1074
|
+
* </p>
|
|
1075
|
+
*
|
|
1076
|
+
* @param {object} [oFormatOptions] The option object, which supports the following parameters.
|
|
1077
|
+
* If no options are given, default values according to the type and locale settings are used.
|
|
1078
|
+
* @param {int} [oFormatOptions.decimalPadding] Not supported.
|
|
1079
|
+
* @param {int} [oFormatOptions.decimals] defines the number of decimal digits
|
|
1080
|
+
* @param {string} [oFormatOptions.decimalSeparator] defines the character used as decimal separator.
|
|
1081
|
+
* Note: <code>decimalSeparator</code> must always be different from <code>groupingSeparator</code>.
|
|
1082
|
+
* @param {null|number|string} [oFormatOptions.emptyString=NaN] since 1.30.0 defines what an empty string
|
|
1083
|
+
* is parsed as, and what is formatted as an empty string. The allowed values are "" (empty string),
|
|
1084
|
+
* NaN, <code>null</code>, or 0.
|
|
1085
|
+
* The 'format' and 'parse' functions are done in a symmetric way. For example, when this
|
|
1086
|
+
* parameter is set to NaN, an empty string is parsed as NaN, and NaN is formatted as an empty
|
|
1087
|
+
* string.
|
|
1088
|
+
* @param {int} [oFormatOptions.groupingBaseSize=3] defines the grouping base size in digits if
|
|
1089
|
+
* it is different from the grouping size (e.g. Indian grouping)
|
|
1090
|
+
* @param {boolean} [oFormatOptions.groupingEnabled=true] defines whether grouping is enabled
|
|
1091
|
+
* (grouping separators are shown).
|
|
1092
|
+
* <b>Note:</b> Grouping is disabled if the <code>groupingSize</code> format option is set to
|
|
1093
|
+
* a non-positive value.
|
|
1094
|
+
* @param {string} [oFormatOptions.groupingSeparator] defines the character used as grouping separator.
|
|
1095
|
+
* Note: <code>groupingSeparator</code> must always be different from <code>decimalSeparator</code>.
|
|
1096
|
+
* @param {int} [oFormatOptions.groupingSize=3] defines the grouping size in digits; the default
|
|
1097
|
+
* is <code>3</code>.
|
|
1098
|
+
* <b>Note:</b> If this format option is set to a non-positive value, grouping will be disabled entirely.
|
|
1099
|
+
* @param {int} [oFormatOptions.maxFractionDigits=99] defines the maximum number of decimal digits
|
|
1100
|
+
* @param {int} [oFormatOptions.maxIntegerDigits=99] defines the maximum number of non-decimal digits.
|
|
1101
|
+
* If the number exceeds this maximum, e.g. 1e+120, "?" characters are shown instead of digits.
|
|
1102
|
+
* @param {int} [oFormatOptions.minFractionDigits=0] defines the minimal number of decimal digits
|
|
1103
|
+
* @param {int} [oFormatOptions.minIntegerDigits=1] defines the minimal number of non-decimal digits
|
|
1104
|
+
* @param {string} [oFormatOptions.minusSign] defines the used minus symbol
|
|
1105
|
+
* @param {boolean} [oFormatOptions.parseAsString=false] since 1.28.2 defines whether to output
|
|
1106
|
+
* the string from the parse function in order to keep the precision for big numbers. Numbers
|
|
1107
|
+
* in scientific notation are parsed back to standard notation. For example, "5e-3" is parsed
|
|
1108
|
+
* to "0.005".
|
|
1109
|
+
* @param {string} [oFormatOptions.pattern] CLDR number pattern which is used to format the number
|
|
1110
|
+
* @param {string} [oFormatOptions.percentSign] defines the used percent symbol
|
|
1111
|
+
* @param {string} [oFormatOptions.plusSign] defines the used plus symbol
|
|
1112
|
+
* @param {int} [oFormatOptions.precision] The maximum number of digits in the formatted representation of a number;
|
|
1113
|
+
* if the <code>precision</code> is less than the overall length of the number, its fractional part is truncated
|
|
1114
|
+
* through rounding. As the <code>precision</code> only affects the rounding of a number, its integer part
|
|
1115
|
+
* can retain more digits than defined by this parameter.
|
|
1116
|
+
* <b>Example:</b> With a <code>precision</code> of 2, <code>234.567</code> is formatted to
|
|
1117
|
+
* <code>"23,457%"</code>.
|
|
1118
|
+
* <b>Note:</b> The formatted output may differ depending on locale.
|
|
1119
|
+
* @param {boolean} [oFormatOptions.preserveDecimals=false] Whether {@link #format} preserves
|
|
1120
|
+
* decimal digits except trailing zeros in case there are more decimals than the
|
|
1121
|
+
* <code>maxFractionDigits</code> format option allows.
|
|
1122
|
+
* If decimals are not preserved, the formatted number is rounded to <code>maxFractionDigits</code>.
|
|
1123
|
+
* @param {sap.ui.core.format.NumberFormat.RoundingMode} [oFormatOptions.roundingMode=HALF_AWAY_FROM_ZERO]
|
|
1124
|
+
* Specifies the rounding behavior for discarding the digits after the maximum fraction digits
|
|
1125
|
+
* defined by <code>maxFractionDigits</code>.
|
|
1126
|
+
* This can be assigned
|
|
1127
|
+
* <ul>
|
|
1128
|
+
* <li>by value in {@link sap.ui.core.format.NumberFormat.RoundingMode RoundingMode},</li>
|
|
1129
|
+
* <li>via a function that is used for rounding the number and takes two parameters: the number itself, and the
|
|
1130
|
+
* number of decimal digits that should be reserved. <b>Using a function is deprecated since 1.121.0</b>;
|
|
1131
|
+
* string based numbers are not rounded via this custom function.</li>
|
|
1132
|
+
* </ul>
|
|
1133
|
+
* @param {int} [oFormatOptions.shortDecimals] defines the number of decimal in the shortened format string. If this isn't specified, the 'decimals' options is used
|
|
1134
|
+
* @param {int} [oFormatOptions.shortLimit] only use short number formatting for values above this limit
|
|
1135
|
+
* @param {int} [oFormatOptions.shortRefNumber] since 1.40 specifies a number from which the scale factor for 'short' or 'long' style format is generated. The generated scale factor is
|
|
1136
|
+
* used for all numbers which are formatted with this format instance. This option has effect only when the option 'style' is set to 'short' or 'long'. This option is by default set
|
|
1137
|
+
* with <code>undefined</code> which means the scale factor is selected automatically for each number being formatted.
|
|
1138
|
+
* @param {boolean} [oFormatOptions.showScale=true] since 1.40 specifies whether the scale factor is shown in the formatted number. This option takes effect only when the 'style' options is set to either 'short' or 'long'.
|
|
1139
|
+
* @param {boolean} [oFormatOptions.strictGroupingValidation=false] whether the positions of grouping separators are validated. Space characters used as grouping separators are not validated.
|
|
1140
|
+
* @param {string} [oFormatOptions.style=standard] defines the style of format. Valid values are
|
|
1141
|
+
* 'short, 'long' or 'standard' (based on the CLDR decimalFormat). When set to 'short' or 'long',
|
|
1142
|
+
* numbers are formatted into compact forms. When this option is set, the default value of the
|
|
1143
|
+
* 'precision' option is set to 2. This can be changed by setting either min/maxFractionDigits,
|
|
1144
|
+
* decimals, shortDecimals, or the 'precision' option itself.
|
|
1145
|
+
* @param {sap.ui.core.Locale} [oLocale]
|
|
1146
|
+
* The locale to get the formatter for; if no locale is given, a locale for the currently configured language is
|
|
1147
|
+
* used; see {@link module:sap/base/i18n/Formatting.getLanguageTag Formatting.getLanguageTag}
|
|
1148
|
+
* @return {sap.ui.core.format.NumberFormat} percentage instance of the NumberFormat
|
|
1149
|
+
* @throws {Error} If the <code>oFormatOptions.decimalPadding</code> format option is provided
|
|
1150
|
+
* @static
|
|
1151
|
+
* @public
|
|
1152
|
+
*/
|
|
1153
|
+
NumberFormat.getPercentInstance = function (oFormatOptions, oLocale) {
|
|
1154
|
+
NumberFormat.checkDecimalPadding(oFormatOptions, false);
|
|
1155
|
+
const oFormat = NumberFormat.createInstance(oFormatOptions, oLocale);
|
|
1156
|
+
const oLocaleFormatOptions = oFormat.getLocaleFormatOptions(mNumberType.PERCENT);
|
|
1157
|
+
oFormat.oFormatOptions = extend({}, NumberFormat.oDefaultPercentFormat, oLocaleFormatOptions, oFormat.oOriginalFormatOptions);
|
|
1158
|
+
oFormat.checkGroupingFormatOptions();
|
|
1159
|
+
return oFormat;
|
|
1160
|
+
};
|
|
1161
|
+
|
|
1162
|
+
/**
|
|
1163
|
+
* Create an instance of the NumberFormat.
|
|
1164
|
+
*
|
|
1165
|
+
* @param {object} [oFormatOptions] Object which defines the format options
|
|
1166
|
+
* @return {sap.ui.core.format.NumberFormat} integer instance of the NumberFormat
|
|
1167
|
+
* @static
|
|
1168
|
+
* @private
|
|
1169
|
+
*/
|
|
1170
|
+
NumberFormat.createInstance = function (oFormatOptions, oLocale) {
|
|
1171
|
+
var oFormat = Object.create(this.prototype),
|
|
1172
|
+
oPatternOptions;
|
|
1173
|
+
if (oFormatOptions instanceof Locale) {
|
|
1174
|
+
oLocale = oFormatOptions;
|
|
1175
|
+
oFormatOptions = undefined;
|
|
1176
|
+
}
|
|
1177
|
+
if (!oLocale) {
|
|
1178
|
+
oLocale = new Locale(Formatting.getLanguageTag());
|
|
1179
|
+
}
|
|
1180
|
+
oFormat.oLocale = oLocale;
|
|
1181
|
+
oFormat.oLocaleData = LocaleData.getInstance(oLocale);
|
|
1182
|
+
oFormat.oOriginalFormatOptions = oFormatOptions;
|
|
1183
|
+
|
|
1184
|
+
// If a pattern is defined in the format option, parse it and add options
|
|
1185
|
+
if (oFormatOptions) {
|
|
1186
|
+
if (oFormatOptions.pattern) {
|
|
1187
|
+
oPatternOptions = this.parseNumberPattern(oFormatOptions.pattern);
|
|
1188
|
+
Object.keys(oPatternOptions).forEach(function (sName) {
|
|
1189
|
+
oFormatOptions[sName] = oPatternOptions[sName];
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
1192
|
+
if (oFormatOptions.emptyString !== undefined) {
|
|
1193
|
+
assert(oFormatOptions.emptyString === "" || oFormatOptions.emptyString === 0 || oFormatOptions.emptyString === null
|
|
1194
|
+
// eslint-disable-next-line no-self-compare -- check if it's NaN (only NaN doesn't equal to itself)
|
|
1195
|
+
|| oFormatOptions.emptyString !== oFormatOptions.emptyString, "The format option 'emptyString' must be either '', 0, null, or NaN");
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
return oFormat;
|
|
1199
|
+
};
|
|
1200
|
+
|
|
1201
|
+
/**
|
|
1202
|
+
* Returns a default unit format/parse pattern for the given unit short name.
|
|
1203
|
+
* The returned pattern can then be used for custom units, for example as a <code>unitPattern-count-other</code> pattern.
|
|
1204
|
+
* The <code>unitPattern-count-other</code> pattern is then used by NumberFormat instances as a fallback in case
|
|
1205
|
+
* no other patterns are defined, see the below example:
|
|
1206
|
+
*
|
|
1207
|
+
* <pre>
|
|
1208
|
+
* var oFormat = NumberFormat.getUnitInstance({
|
|
1209
|
+
* "customUnits": {
|
|
1210
|
+
* "myUnit": {
|
|
1211
|
+
* "unitPattern-count-other": NumberFormat.getDefaultUnitPattern("Bottles"); // returns "{0} Bottles"
|
|
1212
|
+
* }
|
|
1213
|
+
* }
|
|
1214
|
+
* });
|
|
1215
|
+
* oFormat.format(1234, "myUnit"); // returns "1.234,00 Bottles"
|
|
1216
|
+
* </pre>
|
|
1217
|
+
*
|
|
1218
|
+
* @param {string} sShortName the short name of the unit used in the created pattern
|
|
1219
|
+
* @returns {string} a pattern, which can be used for formatting and parsing a custom unit of measure
|
|
1220
|
+
* @private
|
|
1221
|
+
* @ui5-restricted sap.ui.model.odata.type
|
|
1222
|
+
*/
|
|
1223
|
+
NumberFormat.getDefaultUnitPattern = function (sShortName) {
|
|
1224
|
+
return "{0}\u00a0" + sShortName;
|
|
1225
|
+
};
|
|
1226
|
+
|
|
1227
|
+
/**
|
|
1228
|
+
* Gets the default locale-dependent format options for the given number format type.
|
|
1229
|
+
*
|
|
1230
|
+
* @param {"integer"|"float"|"currency"|"unit"|"percent"} sType
|
|
1231
|
+
* The number format type
|
|
1232
|
+
* @returns {object} The default locale-dependent format options for the given number format type
|
|
1233
|
+
*
|
|
1234
|
+
* @private
|
|
1235
|
+
*/
|
|
1236
|
+
NumberFormat.prototype.getLocaleFormatOptions = function (sType) {
|
|
1237
|
+
const oLocaleData = this.oLocaleData;
|
|
1238
|
+
let sNumberPattern;
|
|
1239
|
+
let sContext;
|
|
1240
|
+
switch (sType) {
|
|
1241
|
+
case mNumberType.PERCENT:
|
|
1242
|
+
sNumberPattern = oLocaleData.getPercentPattern();
|
|
1243
|
+
break;
|
|
1244
|
+
case mNumberType.CURRENCY:
|
|
1245
|
+
sContext = this.oOriginalFormatOptions?.currencyContext || NumberFormat.oDefaultCurrencyFormat.style;
|
|
1246
|
+
// prepend "sap-" to pattern params to load (context and short)
|
|
1247
|
+
if (this.showTrailingCurrencyCode()) {
|
|
1248
|
+
sContext = "sap-" + sContext;
|
|
1249
|
+
}
|
|
1250
|
+
sNumberPattern = oLocaleData.getCurrencyPattern(sContext);
|
|
1251
|
+
break;
|
|
1252
|
+
default:
|
|
1253
|
+
sNumberPattern = oLocaleData.getDecimalPattern();
|
|
1254
|
+
}
|
|
1255
|
+
const oLocaleFormatOptions = NumberFormat.parseNumberPattern(sNumberPattern);
|
|
1256
|
+
oLocaleFormatOptions.plusSign = oLocaleData.getNumberSymbol("plusSign");
|
|
1257
|
+
oLocaleFormatOptions.minusSign = oLocaleData.getNumberSymbol("minusSign");
|
|
1258
|
+
oLocaleFormatOptions.decimalSeparator = oLocaleData.getNumberSymbol("decimal");
|
|
1259
|
+
oLocaleFormatOptions.groupingSeparator = oLocaleData.getNumberSymbol("group");
|
|
1260
|
+
oLocaleFormatOptions.percentSign = oLocaleData.getNumberSymbol("percentSign");
|
|
1261
|
+
oLocaleFormatOptions.pattern = sNumberPattern;
|
|
1262
|
+
|
|
1263
|
+
// Some options need to be overridden to stay compatible with the formatting defaults
|
|
1264
|
+
// before pattern parsing was added to the NumberFormat
|
|
1265
|
+
switch (sType) {
|
|
1266
|
+
case mNumberType.INTEGER:
|
|
1267
|
+
// No fraction digits and no grouping for integer values
|
|
1268
|
+
oLocaleFormatOptions.minFractionDigits = 0;
|
|
1269
|
+
oLocaleFormatOptions.maxFractionDigits = 0;
|
|
1270
|
+
oLocaleFormatOptions.groupingEnabled = false;
|
|
1271
|
+
break;
|
|
1272
|
+
case mNumberType.CURRENCY:
|
|
1273
|
+
// reset the iMin/MaxFractionDigits because the extracted info from the pattern doesn't contain the currency specific info.
|
|
1274
|
+
oLocaleFormatOptions.minFractionDigits = undefined;
|
|
1275
|
+
oLocaleFormatOptions.maxFractionDigits = undefined;
|
|
1276
|
+
break;
|
|
1277
|
+
default:
|
|
1278
|
+
// cases: mNumberType.UNIT, mNumberType.FLOAT and mNumberType.PERCENT
|
|
1279
|
+
// Unlimited fraction digits
|
|
1280
|
+
oLocaleFormatOptions.minFractionDigits = 0;
|
|
1281
|
+
oLocaleFormatOptions.maxFractionDigits = 99;
|
|
1282
|
+
}
|
|
1283
|
+
return oLocaleFormatOptions;
|
|
1284
|
+
};
|
|
1285
|
+
|
|
1286
|
+
/**
|
|
1287
|
+
* Get digit information from number format.
|
|
1288
|
+
*
|
|
1289
|
+
* @static
|
|
1290
|
+
*/
|
|
1291
|
+
NumberFormat.parseNumberPattern = function (sFormatString) {
|
|
1292
|
+
var iMinIntegerDigits = 0,
|
|
1293
|
+
iMinFractionDigits = 0,
|
|
1294
|
+
iMaxFractionDigits = 0,
|
|
1295
|
+
bGroupingEnabled = false,
|
|
1296
|
+
iGroupSize = 0,
|
|
1297
|
+
iBaseGroupSize = 0,
|
|
1298
|
+
iSeparatorPos = sFormatString.indexOf(";"),
|
|
1299
|
+
mSection = {
|
|
1300
|
+
Integer: 0,
|
|
1301
|
+
Fraction: 1
|
|
1302
|
+
},
|
|
1303
|
+
iSection = mSection.Integer;
|
|
1304
|
+
|
|
1305
|
+
// The sFormatString can be ¤#,##0.00;(¤#,##0.00). If the whole string is parsed, the wrong
|
|
1306
|
+
// iMinFractionDigits and iMaxFractionDigits are wrong.
|
|
1307
|
+
// Only the sub string before ';' is taken into consideration.
|
|
1308
|
+
if (iSeparatorPos !== -1) {
|
|
1309
|
+
sFormatString = sFormatString.substring(0, iSeparatorPos);
|
|
1310
|
+
}
|
|
1311
|
+
for (var i = 0; i < sFormatString.length; i++) {
|
|
1312
|
+
var sCharacter = sFormatString[i];
|
|
1313
|
+
switch (sCharacter) {
|
|
1314
|
+
case ",":
|
|
1315
|
+
if (bGroupingEnabled) {
|
|
1316
|
+
iGroupSize = iBaseGroupSize;
|
|
1317
|
+
iBaseGroupSize = 0;
|
|
1318
|
+
}
|
|
1319
|
+
bGroupingEnabled = true;
|
|
1320
|
+
break;
|
|
1321
|
+
case ".":
|
|
1322
|
+
iSection = mSection.Fraction;
|
|
1323
|
+
break;
|
|
1324
|
+
case "0":
|
|
1325
|
+
if (iSection === mSection.Integer) {
|
|
1326
|
+
iMinIntegerDigits++;
|
|
1327
|
+
if (bGroupingEnabled) {
|
|
1328
|
+
iBaseGroupSize++;
|
|
1329
|
+
}
|
|
1330
|
+
} else {
|
|
1331
|
+
iMinFractionDigits++;
|
|
1332
|
+
iMaxFractionDigits++;
|
|
1333
|
+
}
|
|
1334
|
+
break;
|
|
1335
|
+
case "#":
|
|
1336
|
+
if (iSection === mSection.Integer) {
|
|
1337
|
+
if (bGroupingEnabled) {
|
|
1338
|
+
iBaseGroupSize++;
|
|
1339
|
+
}
|
|
1340
|
+
} else {
|
|
1341
|
+
iMaxFractionDigits++;
|
|
1342
|
+
}
|
|
1343
|
+
break;
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
if (!iGroupSize) {
|
|
1347
|
+
iGroupSize = iBaseGroupSize;
|
|
1348
|
+
iBaseGroupSize = 0;
|
|
1349
|
+
}
|
|
1350
|
+
return {
|
|
1351
|
+
minIntegerDigits: iMinIntegerDigits,
|
|
1352
|
+
minFractionDigits: iMinFractionDigits,
|
|
1353
|
+
maxFractionDigits: iMaxFractionDigits,
|
|
1354
|
+
groupingEnabled: bGroupingEnabled,
|
|
1355
|
+
groupingSize: iGroupSize,
|
|
1356
|
+
groupingBaseSize: iBaseGroupSize
|
|
1357
|
+
};
|
|
1358
|
+
};
|
|
1359
|
+
|
|
1360
|
+
/**
|
|
1361
|
+
* Compiles a map <code>this.mKnownCurrencySymbols</code>
|
|
1362
|
+
* of all custom currency symbols. Symbols are either defined in
|
|
1363
|
+
* the custom currency object itself, or are looked up on the
|
|
1364
|
+
* LocaleData in case an ISO Code is given.
|
|
1365
|
+
*
|
|
1366
|
+
* It also checks if there are duplicated symbols defined,
|
|
1367
|
+
* which lead to an ambiguous parse result.
|
|
1368
|
+
*
|
|
1369
|
+
* In case there are custom currencies defined on instance level,
|
|
1370
|
+
* it also compiles a map <code>this.mKnownCurrencyCodes</code>
|
|
1371
|
+
* of custom currency codes.
|
|
1372
|
+
*
|
|
1373
|
+
* The function is only used by the Currency formatting.
|
|
1374
|
+
* @private
|
|
1375
|
+
*/
|
|
1376
|
+
NumberFormat.prototype._defineCustomCurrencySymbols = function () {
|
|
1377
|
+
var oOptions = this.oFormatOptions;
|
|
1378
|
+
var mCurrencySymbols = this.oLocaleData.getCurrencySymbols();
|
|
1379
|
+
var fnFindDuplicates = function (mSymbols, mResult) {
|
|
1380
|
+
var aUniqueSymbols = [];
|
|
1381
|
+
var sSymbol;
|
|
1382
|
+
for (var sKey in mSymbols) {
|
|
1383
|
+
sSymbol = mSymbols[sKey];
|
|
1384
|
+
if (aUniqueSymbols.indexOf(sSymbol) === -1) {
|
|
1385
|
+
aUniqueSymbols.push(sSymbol);
|
|
1386
|
+
} else if (sSymbol !== undefined) {
|
|
1387
|
+
// Duplicated symbol found
|
|
1388
|
+
mResult[sSymbol] = true;
|
|
1389
|
+
Log.error("Symbol '" + sSymbol + "' is defined multiple times in custom currencies.", undefined, "NumberFormat");
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
};
|
|
1393
|
+
|
|
1394
|
+
// process custom currencies on instance-level
|
|
1395
|
+
if (oOptions.customCurrencies && typeof oOptions.customCurrencies === "object") {
|
|
1396
|
+
this.mKnownCurrencySymbols = {};
|
|
1397
|
+
this.mKnownCurrencyCodes = {};
|
|
1398
|
+
|
|
1399
|
+
// get all relevant symbols for custom currencies
|
|
1400
|
+
Object.keys(oOptions.customCurrencies).forEach(function (sKey) {
|
|
1401
|
+
if (oOptions.customCurrencies[sKey].symbol) {
|
|
1402
|
+
this.mKnownCurrencySymbols[sKey] = oOptions.customCurrencies[sKey].symbol;
|
|
1403
|
+
} else {
|
|
1404
|
+
// if no symbol is defined, we make a look up into the locale data with the given isoCode
|
|
1405
|
+
var sIsoCode = oOptions.customCurrencies[sKey].isoCode;
|
|
1406
|
+
if (sIsoCode) {
|
|
1407
|
+
this.mKnownCurrencySymbols[sKey] = mCurrencySymbols[sIsoCode];
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
// In case no symbol is found during parsing,
|
|
1412
|
+
// we take the custom currency key itself
|
|
1413
|
+
this.mKnownCurrencyCodes[sKey] = sKey;
|
|
1414
|
+
}.bind(this));
|
|
1415
|
+
} else {
|
|
1416
|
+
// find duplicated symbols in global config/CLDR
|
|
1417
|
+
// mCurrencySymbols
|
|
1418
|
+
this.mKnownCurrencySymbols = mCurrencySymbols;
|
|
1419
|
+
this.mKnownCurrencyCodes = this.oLocaleData.getCustomCurrencyCodes();
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
// Find duplicated symbols defined in custom currencies
|
|
1423
|
+
this.mDuplicatedSymbols = {};
|
|
1424
|
+
fnFindDuplicates(this.mKnownCurrencySymbols, this.mDuplicatedSymbols);
|
|
1425
|
+
};
|
|
1426
|
+
|
|
1427
|
+
/**
|
|
1428
|
+
* Removes trailing zero decimals
|
|
1429
|
+
* @param {string} sNumber the number, e.g. "1.23000"
|
|
1430
|
+
* @param {number} minDecimalsPreserved the minimum decimals preserved, e.g. 3
|
|
1431
|
+
* @returns {string} the number with stripped trailing zero decimals, e.g. "1.230"
|
|
1432
|
+
*/
|
|
1433
|
+
function stripTrailingZeroDecimals(sNumber, minDecimalsPreserved) {
|
|
1434
|
+
if (sNumber.indexOf(".") >= 0 && sNumber.endsWith("0")) {
|
|
1435
|
+
var iFractionDigitsLength = sNumber.length - sNumber.lastIndexOf(".") - 1;
|
|
1436
|
+
var iFractionsToRemove = iFractionDigitsLength - minDecimalsPreserved;
|
|
1437
|
+
if (iFractionsToRemove > 0) {
|
|
1438
|
+
while (sNumber.endsWith("0") && iFractionsToRemove-- > 0) {
|
|
1439
|
+
sNumber = sNumber.substring(0, sNumber.length - 1);
|
|
1440
|
+
}
|
|
1441
|
+
if (sNumber.endsWith(".")) {
|
|
1442
|
+
sNumber = sNumber.substring(0, sNumber.length - 1);
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
return sNumber;
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
/**
|
|
1450
|
+
* Applies the grouping to the given integer part and returns it.
|
|
1451
|
+
*
|
|
1452
|
+
* @param {string} sIntegerPart
|
|
1453
|
+
* A string with the integer value, e.g. "1234567"
|
|
1454
|
+
* @param {object} oOptions
|
|
1455
|
+
* The format options
|
|
1456
|
+
* @param {int} oOptions.groupingBaseSize
|
|
1457
|
+
* The grouping base size in digits if it is different from the grouping size (e.g. Indian grouping)
|
|
1458
|
+
* @param {string} oOptions.groupingSeparator
|
|
1459
|
+
* The character used as grouping separator
|
|
1460
|
+
* @param {int} oOptions.groupingSize
|
|
1461
|
+
* The grouping size in digits
|
|
1462
|
+
* @returns {string}
|
|
1463
|
+
* The integer part with grouping, e.g. "1.234.567" for locale de-DE
|
|
1464
|
+
* @private
|
|
1465
|
+
*/
|
|
1466
|
+
function applyGrouping(sIntegerPart, oOptions) {
|
|
1467
|
+
var iGroupSize = oOptions.groupingSize,
|
|
1468
|
+
iBaseGroupSize = oOptions.groupingBaseSize || iGroupSize,
|
|
1469
|
+
iLength = sIntegerPart.length,
|
|
1470
|
+
iPosition = Math.max(iLength - iBaseGroupSize, 0) % iGroupSize || iGroupSize,
|
|
1471
|
+
sGroupedIntegerPart = sIntegerPart.slice(0, iPosition);
|
|
1472
|
+
while (iLength - iPosition >= iBaseGroupSize) {
|
|
1473
|
+
sGroupedIntegerPart += oOptions.groupingSeparator;
|
|
1474
|
+
sGroupedIntegerPart += sIntegerPart.slice(iPosition, iPosition + iGroupSize);
|
|
1475
|
+
iPosition += iGroupSize;
|
|
1476
|
+
}
|
|
1477
|
+
sGroupedIntegerPart += sIntegerPart.slice(iPosition, iLength);
|
|
1478
|
+
return sGroupedIntegerPart;
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
/**
|
|
1482
|
+
* Format a number according to the given format options.
|
|
1483
|
+
*
|
|
1484
|
+
* @param {number|string|array} vValue
|
|
1485
|
+
* The number to format as a number or a string, such as <code>1234.45</code> or <code>"-1234.45"</code>, or an
|
|
1486
|
+
* array which contains both the number to format as a number or a string and the <code>sMeasure</code> parameter
|
|
1487
|
+
* @param {string} [sMeasure]
|
|
1488
|
+
* An optional unit which has an impact on formatting currencies and units
|
|
1489
|
+
* @returns {string}
|
|
1490
|
+
* The formatted value
|
|
1491
|
+
* @public
|
|
1492
|
+
*/
|
|
1493
|
+
NumberFormat.prototype.format = function (vValue, sMeasure) {
|
|
1494
|
+
if (Array.isArray(vValue)) {
|
|
1495
|
+
sMeasure = vValue[1];
|
|
1496
|
+
vValue = vValue[0];
|
|
1497
|
+
}
|
|
1498
|
+
var sIntegerPart = "",
|
|
1499
|
+
sFractionPart = "",
|
|
1500
|
+
sGroupedIntegerPart = "",
|
|
1501
|
+
sResult = "",
|
|
1502
|
+
sNumber = "",
|
|
1503
|
+
sPattern = "",
|
|
1504
|
+
bNegative = vValue < 0,
|
|
1505
|
+
iDotPos = -1,
|
|
1506
|
+
oOptions = Object.assign({}, this.oFormatOptions),
|
|
1507
|
+
oOrigOptions = this.oOriginalFormatOptions,
|
|
1508
|
+
bIndianCurrency = oOptions.type === mNumberType.CURRENCY && sMeasure === "INR" && this.oLocale.getLanguage() === "en" && this.oLocale.getRegion() === "IN",
|
|
1509
|
+
oShortFormat,
|
|
1510
|
+
nShortRefNumber,
|
|
1511
|
+
sPluralCategory,
|
|
1512
|
+
mUnitPatterns,
|
|
1513
|
+
sLookupMeasure,
|
|
1514
|
+
bValueIsNullOrUndefined = vValue === undefined || vValue === null;
|
|
1515
|
+
|
|
1516
|
+
// emptyString is only relevant for the number part (vValue)
|
|
1517
|
+
if (oOptions.showNumber && (vValue === oOptions.emptyString || isNaN(vValue) && isNaN(oOptions.emptyString))) {
|
|
1518
|
+
// if the value equals the 'emptyString' format option, return empty string.
|
|
1519
|
+
// the NaN case has to be checked by using isNaN because NaN !== NaN
|
|
1520
|
+
return "";
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
// sMeasure must be a string if defined
|
|
1524
|
+
if (sMeasure !== undefined && sMeasure !== null && typeof sMeasure !== "string" && !(sMeasure instanceof String)) {
|
|
1525
|
+
return "";
|
|
1526
|
+
}
|
|
1527
|
+
if (!oOptions.showNumber && !sMeasure) {
|
|
1528
|
+
return "";
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
// cannot create number from null or undefined
|
|
1532
|
+
if (bValueIsNullOrUndefined && (!sMeasure || !oOptions.showMeasure || oOptions.showNumber)) {
|
|
1533
|
+
return "";
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
// If custom currencies are defined, we exclusively accept the defined ones,
|
|
1537
|
+
// other currencies are ignored
|
|
1538
|
+
if (sMeasure && oOptions.customCurrencies && !oOptions.customCurrencies[sMeasure]) {
|
|
1539
|
+
Log.error("Currency '" + sMeasure + "' is unknown.");
|
|
1540
|
+
return "";
|
|
1541
|
+
}
|
|
1542
|
+
if (!oOptions.showNumber && !oOptions.showMeasure) {
|
|
1543
|
+
return "";
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
// Recognize the correct unit definition (either custom unit or CLDR unit)
|
|
1547
|
+
if (sMeasure && oOptions.type === mNumberType.UNIT) {
|
|
1548
|
+
if (oOptions.customUnits && typeof oOptions.customUnits === "object") {
|
|
1549
|
+
//custom units are exclusive (no fallback to LocaleData)
|
|
1550
|
+
mUnitPatterns = oOptions.customUnits[sMeasure];
|
|
1551
|
+
} else {
|
|
1552
|
+
//check if there is a unit mapping for the given unit
|
|
1553
|
+
sLookupMeasure = this.oLocaleData.getUnitFromMapping(sMeasure) || sMeasure;
|
|
1554
|
+
mUnitPatterns = this.oLocaleData.getUnitFormat(sLookupMeasure);
|
|
1555
|
+
}
|
|
1556
|
+
if (oOptions.showMeasure) {
|
|
1557
|
+
// a list of allowed unit types is given, so we check if the given measure is ok
|
|
1558
|
+
var bUnitTypeAllowed = !oOptions.allowedUnits || oOptions.allowedUnits.indexOf(sMeasure) >= 0;
|
|
1559
|
+
if (!bUnitTypeAllowed) {
|
|
1560
|
+
return "";
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
if (!mUnitPatterns && !oOptions.showNumber) {
|
|
1564
|
+
return this._addOriginInfo(sMeasure);
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
if (oOptions.type === mNumberType.UNIT) {
|
|
1568
|
+
// either take the decimals/precision on the custom units or fallback to the given format-options
|
|
1569
|
+
oOptions.decimals = mUnitPatterns && typeof mUnitPatterns.decimals === "number" && mUnitPatterns.decimals >= 0 ? mUnitPatterns.decimals : oOptions.decimals;
|
|
1570
|
+
oOptions.decimals = NumberFormat.getMaximumDecimals(oOptions);
|
|
1571
|
+
oOptions.precision = mUnitPatterns && typeof mUnitPatterns.precision === "number" && mUnitPatterns.precision >= 0 ? mUnitPatterns.precision : oOptions.precision;
|
|
1572
|
+
}
|
|
1573
|
+
let sCurrencySymbolOrCode;
|
|
1574
|
+
if (oOptions.type === mNumberType.CURRENCY) {
|
|
1575
|
+
sCurrencySymbolOrCode = this.getCurrencySymbolOrCode(sMeasure, oOptions.currencyCode);
|
|
1576
|
+
// Make sure the "trailingCurrencyCode" mode is only used on currency codes:
|
|
1577
|
+
// The "customCurrencies" format option takes precedence over CLDR and global configuration. If the given measure isn't found
|
|
1578
|
+
// there, we already return an empty string in the check above (look for error log 'Currency "xy" is unknown').
|
|
1579
|
+
// "mKnownCurrencyCodes" either contains the keys of the "customCurrencies" format option or the accumulated currency codes
|
|
1580
|
+
// from CLDR and global configuration. If the given measure isn't found there and does not have the three letter ISO code format,
|
|
1581
|
+
// it shouldn't be formatted with the "trailingCurrencyCode" pattern.
|
|
1582
|
+
if (sMeasure && oOptions.trailingCurrencyCode) {
|
|
1583
|
+
if (!this.mKnownCurrencyCodes[sMeasure] && !/(^[A-Z]{3}$)/.test(sMeasure)) {
|
|
1584
|
+
// Revert to non-"sap-" prefixed (trailing-currency-code) pattern. Also see code in getCurrencyInstance()
|
|
1585
|
+
oOptions.trailingCurrencyCode = false;
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
if (!oOptions.showNumber) {
|
|
1589
|
+
// if the number should not be shown, return the sMeasure part standalone, without anything number specific
|
|
1590
|
+
return sCurrencySymbolOrCode;
|
|
1591
|
+
}
|
|
1592
|
+
if (oOptions.style === "long" || oOptions.style === "short") {
|
|
1593
|
+
oOptions.maxFractionDigits ??= 0;
|
|
1594
|
+
} else {
|
|
1595
|
+
if (oOptions.customCurrencies?.[sMeasure]?.decimals !== undefined) {
|
|
1596
|
+
oOptions.decimals = oOptions.customCurrencies[sMeasure].decimals;
|
|
1597
|
+
} else {
|
|
1598
|
+
oOptions.decimals ??= this.oLocaleData.getCurrencyDigits(sMeasure);
|
|
1599
|
+
}
|
|
1600
|
+
oOptions.decimals = NumberFormat.getMaximumDecimals(oOptions);
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
// set fraction digits based on the given or derived decimals
|
|
1605
|
+
if (oOptions.decimals !== undefined) {
|
|
1606
|
+
oOptions.minFractionDigits = oOptions.decimals;
|
|
1607
|
+
oOptions.maxFractionDigits = oOptions.decimals;
|
|
1608
|
+
}
|
|
1609
|
+
if (oOptions.shortLimit === undefined || Math.abs(vValue) >= oOptions.shortLimit) {
|
|
1610
|
+
nShortRefNumber = oOptions.shortRefNumber === undefined ? vValue : oOptions.shortRefNumber;
|
|
1611
|
+
oShortFormat = this.getShortenedFormat(nShortRefNumber, oOptions, bIndianCurrency);
|
|
1612
|
+
if (oShortFormat && oShortFormat.formatString != "0") {
|
|
1613
|
+
vValue = vValue / oShortFormat.magnitude;
|
|
1614
|
+
// If shortDecimals is defined, override the fractionDigits
|
|
1615
|
+
if (oOptions.shortDecimals !== undefined) {
|
|
1616
|
+
oOptions.minFractionDigits = oOptions.shortDecimals;
|
|
1617
|
+
oOptions.maxFractionDigits = oOptions.shortDecimals;
|
|
1618
|
+
} else {
|
|
1619
|
+
if (oOrigOptions.minFractionDigits === undefined && oOrigOptions.maxFractionDigits === undefined && oOrigOptions.decimals === undefined && oOrigOptions.precision === undefined && oOrigOptions.pattern === undefined) {
|
|
1620
|
+
// if none of the options which can affect the decimal digits is set, the default precision is set to 2
|
|
1621
|
+
oOptions.precision = 2;
|
|
1622
|
+
// set the default min/maxFractionDigits after setting the default precision
|
|
1623
|
+
oOptions.minFractionDigits = 0;
|
|
1624
|
+
oOptions.maxFractionDigits = 99;
|
|
1625
|
+
}
|
|
1626
|
+
if (oOrigOptions.maxFractionDigits === undefined && oOrigOptions.decimals === undefined) {
|
|
1627
|
+
// overwrite the default setting of Integer instance because
|
|
1628
|
+
// Integer with short format could have fraction part
|
|
1629
|
+
oOptions.maxFractionDigits = 99;
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
// Always use HALF_AWAY_FROM_ZERO for short formats
|
|
1634
|
+
oOptions.roundingMode = NumberFormat.RoundingMode.HALF_AWAY_FROM_ZERO;
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
// Must be done after calculating the short value, as it depends on the value
|
|
1639
|
+
// If short format is enabled or the precision isn't ignored, take the precision
|
|
1640
|
+
// option into consideration
|
|
1641
|
+
if ((oShortFormat || !oOptions.ignorePrecision) && oOptions.precision !== undefined) {
|
|
1642
|
+
// the number of decimal digits is calculated using (precision - number of integer digits)
|
|
1643
|
+
// the maxFractionDigits is adapted if the calculated value is smaller than the maxFractionDigits
|
|
1644
|
+
oOptions.maxFractionDigits = Math.min(oOptions.maxFractionDigits, getDecimals(vValue, oOptions.precision));
|
|
1645
|
+
|
|
1646
|
+
// if the minFractionDigits is greater than the maxFractionDigits, adapt the minFractionDigits with
|
|
1647
|
+
// the same value of the maxFractionDigits
|
|
1648
|
+
oOptions.minFractionDigits = Math.min(oOptions.minFractionDigits, oOptions.maxFractionDigits);
|
|
1649
|
+
}
|
|
1650
|
+
if (oOptions.type == mNumberType.PERCENT) {
|
|
1651
|
+
vValue = NumberFormat._shiftDecimalPoint(vValue, 2);
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
// Rounding the value with oOptions.maxFractionDigits and oOptions.roundingMode.
|
|
1655
|
+
//
|
|
1656
|
+
// If the number of fraction digits are equal or less than oOptions.maxFractionDigits, the
|
|
1657
|
+
// number isn't changed. After this operation, the number of fraction digits is
|
|
1658
|
+
// equal or less than oOptions.maxFractionDigits.
|
|
1659
|
+
if ((typeof vValue === "number" || typeof vValue === "string" && typeof oOptions.roundingMode !== "function") && !oOptions.preserveDecimals) {
|
|
1660
|
+
vValue = rounding(vValue, oOptions.maxFractionDigits, oOptions.roundingMode);
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
// No sign on zero values
|
|
1664
|
+
if (vValue == 0) {
|
|
1665
|
+
bNegative = false;
|
|
1666
|
+
}
|
|
1667
|
+
if (!bValueIsNullOrUndefined) {
|
|
1668
|
+
sNumber = LocaleData.convertToDecimal(vValue);
|
|
1669
|
+
}
|
|
1670
|
+
if (sNumber === "NaN") {
|
|
1671
|
+
return sNumber;
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
// strip of trailing zeros in decimals
|
|
1675
|
+
// "1000.00" -> "1000" (maxFractionDigits: 0)
|
|
1676
|
+
// "1000.0" -> "1000.0" (maxFractionDigits: 1)
|
|
1677
|
+
// the intention behind preserveDecimals is to keep the precision in the number.
|
|
1678
|
+
// Trailing zero decimals are not required for the precision (e.g. 1,23000000 EUR).
|
|
1679
|
+
// These zeros are cut off until maxFractionDigits is reached to be backward compatible.
|
|
1680
|
+
// If more trailing decimal zeros are required the option maxFractionDigits can be increased.
|
|
1681
|
+
// Note: default maxFractionDigits for Unit and Float is 99.
|
|
1682
|
+
if (oOptions.preserveDecimals) {
|
|
1683
|
+
sNumber = stripTrailingZeroDecimals(sNumber, oOptions.maxFractionDigits);
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
// if number is negative remove minus
|
|
1687
|
+
if (bNegative) {
|
|
1688
|
+
sNumber = sNumber.substr(1);
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
// if number contains fraction, extract it
|
|
1692
|
+
iDotPos = sNumber.indexOf(".");
|
|
1693
|
+
if (iDotPos > -1) {
|
|
1694
|
+
sIntegerPart = sNumber.substr(0, iDotPos);
|
|
1695
|
+
sFractionPart = sNumber.substr(iDotPos + 1);
|
|
1696
|
+
} else {
|
|
1697
|
+
sIntegerPart = sNumber;
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
// integer part length
|
|
1701
|
+
if (sIntegerPart.length < oOptions.minIntegerDigits) {
|
|
1702
|
+
sIntegerPart = sIntegerPart.padStart(oOptions.minIntegerDigits, "0");
|
|
1703
|
+
} else if (sIntegerPart.length > oOptions.maxIntegerDigits) {
|
|
1704
|
+
sIntegerPart = "".padStart(oOptions.maxIntegerDigits, "?");
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
// fraction part length
|
|
1708
|
+
if (sFractionPart.length < oOptions.minFractionDigits) {
|
|
1709
|
+
sFractionPart = sFractionPart.padEnd(oOptions.minFractionDigits, "0");
|
|
1710
|
+
} else if (sFractionPart.length > oOptions.maxFractionDigits && !oOptions.preserveDecimals) {
|
|
1711
|
+
sFractionPart = sFractionPart.substr(0, oOptions.maxFractionDigits);
|
|
1712
|
+
}
|
|
1713
|
+
if (oOptions.type === mNumberType.UNIT && !oOptions.showNumber) {
|
|
1714
|
+
if (mUnitPatterns) {
|
|
1715
|
+
// the plural category of a unit pattern is determined for the complete number, maybe as compact
|
|
1716
|
+
// notation, e.g. "1.2M" must check "1.2c6"
|
|
1717
|
+
sPluralCategory = this._getPluralCategory(sIntegerPart, sFractionPart, oShortFormat);
|
|
1718
|
+
sPattern = mUnitPatterns["unitPattern-count-" + sPluralCategory];
|
|
1719
|
+
if (!sPattern) {
|
|
1720
|
+
sPattern = mUnitPatterns["unitPattern-count-other"];
|
|
1721
|
+
}
|
|
1722
|
+
if (!sPattern) {
|
|
1723
|
+
return this._addOriginInfo(sMeasure);
|
|
1724
|
+
}
|
|
1725
|
+
// fallback to "other" pattern if pattern does not include the number placeholder
|
|
1726
|
+
if (sPluralCategory !== "other" && sPattern.indexOf("{0}") === -1) {
|
|
1727
|
+
sPattern = mUnitPatterns["unitPattern-count-other"];
|
|
1728
|
+
if (!sPattern) {
|
|
1729
|
+
return this._addOriginInfo(sMeasure);
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
// with the current CLDR data this is not possible
|
|
1734
|
+
// but if there is the case when there is no number placeholder, the number cannot be separated from the unit
|
|
1735
|
+
// therefore it does not make sense to return a pattern which contains the number part in any other form as part of the pattern
|
|
1736
|
+
if (sPattern.indexOf("{0}") === -1) {
|
|
1737
|
+
Log.warning("Cannot separate the number from the unit because unitPattern-count-other '" + sPattern + "' does not include the number placeholder '{0}' for unit '" + sMeasure + "'");
|
|
1738
|
+
} else {
|
|
1739
|
+
return this._addOriginInfo(sPattern.replace("{0}", "").trim());
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
// grouping
|
|
1745
|
+
if (oOptions.groupingEnabled) {
|
|
1746
|
+
sGroupedIntegerPart = applyGrouping(sIntegerPart, oOptions);
|
|
1747
|
+
} else {
|
|
1748
|
+
sGroupedIntegerPart = sIntegerPart;
|
|
1749
|
+
}
|
|
1750
|
+
const iDecimalPadding = oOptions.decimalPadding || 0;
|
|
1751
|
+
if (iDecimalPadding) {
|
|
1752
|
+
oOptions.minusSign = oOptions.minusSign.replace(rLeftToRightMark, "");
|
|
1753
|
+
}
|
|
1754
|
+
// combine
|
|
1755
|
+
if (bNegative) {
|
|
1756
|
+
sResult = oOptions.minusSign;
|
|
1757
|
+
}
|
|
1758
|
+
sResult += sGroupedIntegerPart;
|
|
1759
|
+
if (sFractionPart) {
|
|
1760
|
+
sResult += oOptions.decimalSeparator + sFractionPart;
|
|
1761
|
+
}
|
|
1762
|
+
const bUseCompactPattern = oShortFormat && oShortFormat.formatString && oOptions.showScale;
|
|
1763
|
+
let sCompactPattern;
|
|
1764
|
+
if (bUseCompactPattern) {
|
|
1765
|
+
// Get correct format string based on actual decimal/fraction digits
|
|
1766
|
+
// the plural category of a compact number/currency is determined for the reduced short number without
|
|
1767
|
+
// compact notation, e.g. "1.2M" must check "1.2"
|
|
1768
|
+
// (see CLDR "decimalFormat-short" and "decimalFormat-long" or "currencyFormat-short")
|
|
1769
|
+
sPluralCategory = this._getPluralCategory(sIntegerPart, sFractionPart);
|
|
1770
|
+
sCompactPattern = this.getCompactPattern(oOptions.type, oOptions.style, oShortFormat.key, sPluralCategory, oOptions.trailingCurrencyCode, bIndianCurrency, sMeasure && oOptions.showMeasure, sCurrencySymbolOrCode, bNegative);
|
|
1771
|
+
if (oOptions.type !== mNumberType.CURRENCY) {
|
|
1772
|
+
// inject formatted shortValue in the formatString
|
|
1773
|
+
sResult = sCompactPattern.replace(oShortFormat.valueSubString, sResult);
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
if (oOptions.type === mNumberType.CURRENCY) {
|
|
1777
|
+
sPattern = bUseCompactPattern ? sCompactPattern : this.getCurrencyPattern(oOptions.currencyContext, oOptions.trailingCurrencyCode, sMeasure && oOptions.showMeasure, sCurrencySymbolOrCode, bNegative);
|
|
1778
|
+
sResult = NumberFormat._composeCurrencyResult(sPattern, sResult, sCurrencySymbolOrCode, oOptions.minusSign, bNegative);
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
// format percent values:
|
|
1782
|
+
if (oOptions.type === mNumberType.PERCENT) {
|
|
1783
|
+
sPattern = oOptions.pattern;
|
|
1784
|
+
sResult = sPattern.replace(rNumberPattern, sResult);
|
|
1785
|
+
sResult = sResult.replace(/%/, oOptions.percentSign);
|
|
1786
|
+
}
|
|
1787
|
+
if (oOptions.showMeasure && sMeasure && oOptions.type === mNumberType.UNIT) {
|
|
1788
|
+
// the plural category of a unit pattern is determined for the complete number, maybe as compact
|
|
1789
|
+
// notation, e.g. "1.2M" must check "1.2c6"
|
|
1790
|
+
sPluralCategory = this._getPluralCategory(sIntegerPart, sFractionPart, oShortFormat);
|
|
1791
|
+
if (mUnitPatterns) {
|
|
1792
|
+
sPattern = mUnitPatterns["unitPattern-count-" + sPluralCategory];
|
|
1793
|
+
// some units do not have a pattern for each plural and therefore "other" is used as fallback
|
|
1794
|
+
if (!sPattern) {
|
|
1795
|
+
sPattern = mUnitPatterns["unitPattern-count-other"];
|
|
1796
|
+
}
|
|
1797
|
+
if (!sPattern) {
|
|
1798
|
+
sPattern = NumberFormat.getDefaultUnitPattern(sMeasure);
|
|
1799
|
+
}
|
|
1800
|
+
} else {
|
|
1801
|
+
sPattern = NumberFormat.getDefaultUnitPattern(sMeasure);
|
|
1802
|
+
}
|
|
1803
|
+
sResult = sPattern.replace("{0}", sResult);
|
|
1804
|
+
}
|
|
1805
|
+
let iDecimalPaddingLength = iDecimalPadding - sFractionPart.length;
|
|
1806
|
+
if (iDecimalPaddingLength > 0) {
|
|
1807
|
+
const bNegativeAccounting = sResult[sResult.length - 1] === ")";
|
|
1808
|
+
const sCharPunctuationSpace = "\u2008";
|
|
1809
|
+
if (sFractionPart) {
|
|
1810
|
+
if (bNegativeAccounting) {
|
|
1811
|
+
// the ")" and the CHAR_PUNCTUATION_SPACE u2008 have a combined width close to the width of a
|
|
1812
|
+
// CHAR_FIGURE_SPACE u2007
|
|
1813
|
+
sResult += sCharPunctuationSpace;
|
|
1814
|
+
iDecimalPaddingLength = iDecimalPaddingLength - 1;
|
|
1815
|
+
}
|
|
1816
|
+
} else if (!bNegativeAccounting) {
|
|
1817
|
+
// only add CHAR_PUNCTUATION_SPACE u2008 if there is no ")" at the end
|
|
1818
|
+
sResult += sCharPunctuationSpace;
|
|
1819
|
+
}
|
|
1820
|
+
sResult += "\u2007".repeat(iDecimalPaddingLength); // CHAR_FIGURE_SPACE u2007
|
|
1821
|
+
}
|
|
1822
|
+
return this._addOriginInfo(sResult);
|
|
1823
|
+
};
|
|
1824
|
+
|
|
1825
|
+
/**
|
|
1826
|
+
* Gets the currency symbol or the currency code for the given currency code depending on the given format options.
|
|
1827
|
+
*
|
|
1828
|
+
* @param {string} sCurrencyCode
|
|
1829
|
+
* The currency code
|
|
1830
|
+
* @param {boolean} bCurrencyCode
|
|
1831
|
+
* Whether to show the currency code instead of the currency symbol, see {@link Numberformat.getCurrencyInstance}
|
|
1832
|
+
* @returns {string}
|
|
1833
|
+
* The currency symbol or the currency code
|
|
1834
|
+
*
|
|
1835
|
+
* @private
|
|
1836
|
+
*/
|
|
1837
|
+
NumberFormat.prototype.getCurrencySymbolOrCode = function (sCurrencyCode, bCurrencyCode) {
|
|
1838
|
+
if (bCurrencyCode) {
|
|
1839
|
+
return sCurrencyCode;
|
|
1840
|
+
}
|
|
1841
|
+
// the custom currency symbol map was preprocessed on instance creation
|
|
1842
|
+
return (typeof this.oFormatOptions.customCurrencies === "object" ? this.mKnownCurrencySymbols[sCurrencyCode] : this.oLocaleData.getCurrencySymbol(sCurrencyCode)) || sCurrencyCode;
|
|
1843
|
+
};
|
|
1844
|
+
|
|
1845
|
+
/**
|
|
1846
|
+
* Gets the plural category for the given number information. With a given <code>oShortFormat</code>
|
|
1847
|
+
* the category is determined based on the compact notation.
|
|
1848
|
+
*
|
|
1849
|
+
* @param {int} sIntegerPart
|
|
1850
|
+
* The integer part
|
|
1851
|
+
* @param {int} [sFractionPart]
|
|
1852
|
+
* The fraction part
|
|
1853
|
+
* @param {{magnitude: int}} [oShortFormat]
|
|
1854
|
+
* An object containing the <code>magnitude</code> information describing the factor of a compact number
|
|
1855
|
+
* @returns {string}
|
|
1856
|
+
* The plural category
|
|
1857
|
+
*
|
|
1858
|
+
* @private
|
|
1859
|
+
*/
|
|
1860
|
+
NumberFormat.prototype._getPluralCategory = function (sIntegerPart, sFractionPart, oShortFormat) {
|
|
1861
|
+
var sNumber = sIntegerPart;
|
|
1862
|
+
if (sFractionPart) {
|
|
1863
|
+
sNumber += "." + sFractionPart;
|
|
1864
|
+
}
|
|
1865
|
+
if (oShortFormat) {
|
|
1866
|
+
sNumber += "c" + oShortFormat.magnitude.toExponential().slice(2);
|
|
1867
|
+
}
|
|
1868
|
+
return this.oLocaleData.getPluralCategory(sNumber);
|
|
1869
|
+
};
|
|
1870
|
+
NumberFormat.prototype._addOriginInfo = function (sResult) {
|
|
1871
|
+
if (Supportability.collectOriginInfo()) {
|
|
1872
|
+
// String object is created on purpose and must not be a string literal
|
|
1873
|
+
// eslint-disable-next-line no-new-wrappers
|
|
1874
|
+
sResult = new String(sResult);
|
|
1875
|
+
sResult.originInfo = {
|
|
1876
|
+
source: "Common Locale Data Repository",
|
|
1877
|
+
locale: this.oLocale.toString()
|
|
1878
|
+
};
|
|
1879
|
+
}
|
|
1880
|
+
return sResult;
|
|
1881
|
+
};
|
|
1882
|
+
|
|
1883
|
+
/**
|
|
1884
|
+
* Replaces the amount, measure, and minus sign parts in the given pattern with the given values and returns the
|
|
1885
|
+
* result.
|
|
1886
|
+
*
|
|
1887
|
+
* @param {string} sPattern
|
|
1888
|
+
* The currency pattern, e.g. "¤#,##0.00;(¤#,##0.00)", "¤#,##0.00;¤-#,##0.00", "#,##0.00", or "¤ 000K"
|
|
1889
|
+
* @param {string} sAmount
|
|
1890
|
+
* The formatted amount, e.g. "1,234.56"
|
|
1891
|
+
* @param {string} sMeasure
|
|
1892
|
+
* The currency symbol or code
|
|
1893
|
+
* @param {string} sMinusSign
|
|
1894
|
+
* The locale specific minus sign
|
|
1895
|
+
* @param {boolean} bNegative
|
|
1896
|
+
* Whether the amount is negative
|
|
1897
|
+
* @returns {string}
|
|
1898
|
+
* The resulting string after replacing the amount, measure, and minus sign parts in the given pattern with the
|
|
1899
|
+
* given values
|
|
1900
|
+
*
|
|
1901
|
+
* @private
|
|
1902
|
+
*/
|
|
1903
|
+
NumberFormat._composeCurrencyResult = function (sPattern, sAmount, sMeasure, sMinusSign, bNegative) {
|
|
1904
|
+
const aPatternParts = sPattern.split(";");
|
|
1905
|
+
if (aPatternParts.length === 2) {
|
|
1906
|
+
sPattern = aPatternParts[bNegative ? 1 : 0];
|
|
1907
|
+
if (bNegative) {
|
|
1908
|
+
sAmount = sAmount.slice(sMinusSign.length);
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
let sResult = sPattern.replace("-", sMinusSign).replace(rNumberPattern, sAmount).replace("\u00a4", sMeasure);
|
|
1912
|
+
if (bNegative) {
|
|
1913
|
+
// when no space is inserted between measure and number
|
|
1914
|
+
// and when the number is negative and the measure is shown before the number
|
|
1915
|
+
// a zero-width non-breakable space ("\ufeff") is inserted before the minus sign
|
|
1916
|
+
// in order to prevent the formatted currency number from being wrapped after the
|
|
1917
|
+
// minus sign when the space isn't enough for displaying the currency number within
|
|
1918
|
+
// one line
|
|
1919
|
+
sResult = sResult.replace(sMeasure + sMinusSign, sMeasure + "\ufeff" + sMinusSign);
|
|
1920
|
+
}
|
|
1921
|
+
return sResult;
|
|
1922
|
+
};
|
|
1923
|
+
|
|
1924
|
+
/**
|
|
1925
|
+
* Parse a string which is formatted according to the given format options.
|
|
1926
|
+
*
|
|
1927
|
+
* @param {string} sValue the string containing a formatted numeric value
|
|
1928
|
+
* @return {number|array|string|null} the parsed value as:
|
|
1929
|
+
* <ul>
|
|
1930
|
+
* <li>number</li>
|
|
1931
|
+
* <li>array which contains the parsed value and the currency code (symbol) or unit for currency and unit instances</li>
|
|
1932
|
+
* <li>string when option "parseAsString" is <code>true</code></li>
|
|
1933
|
+
* <li><code>NaN</code> if value cannot be parsed</li>
|
|
1934
|
+
* <li><code>null</code> if value is invalid</li>
|
|
1935
|
+
* </ul>
|
|
1936
|
+
* @public
|
|
1937
|
+
*/
|
|
1938
|
+
NumberFormat.prototype.parse = function (sValue) {
|
|
1939
|
+
var oOptions = this.oFormatOptions,
|
|
1940
|
+
sPlusSigns = oOptions.plusSign + this.oLocaleData.getLenientNumberSymbols("plusSign"),
|
|
1941
|
+
sMinusSigns = oOptions.minusSign + this.oLocaleData.getLenientNumberSymbols("minusSign"),
|
|
1942
|
+
// Note: the minus sign ('-') needs to be quoted as well such that it is not confused with the range operator, e.g. in [A-Z]
|
|
1943
|
+
sPlusMinusSigns = quote(sPlusSigns + sMinusSigns),
|
|
1944
|
+
sGroupingSeparator = quote(oOptions.groupingSeparator),
|
|
1945
|
+
sDecimalSeparator = quote(oOptions.decimalSeparator),
|
|
1946
|
+
sRegExpFloat = "^\\s*([" + sPlusMinusSigns + "]?(?:[0-9" + sGroupingSeparator + "]+|[0-9" + sGroupingSeparator + "]*" + sDecimalSeparator + "[0-9]*)(?:[eE][+-]?[0-9]+)?)\\s*$",
|
|
1947
|
+
sRegExpInt = "^\\s*([" + sPlusMinusSigns + "]?[0-9" + sGroupingSeparator + "]+)\\s*$",
|
|
1948
|
+
oGroupingRegExp = new RegExp(sGroupingSeparator, "g"),
|
|
1949
|
+
oDecimalRegExp = new RegExp(sDecimalSeparator, "g"),
|
|
1950
|
+
sPercentSign = this.oLocaleData.getNumberSymbol("percentSign"),
|
|
1951
|
+
bIndianCurrency = oOptions.type === mNumberType.CURRENCY && this.oLocale.getLanguage() === "en" && this.oLocale.getRegion() === "IN",
|
|
1952
|
+
oRegExp,
|
|
1953
|
+
bPercent,
|
|
1954
|
+
sMeasure,
|
|
1955
|
+
sPercentPattern,
|
|
1956
|
+
vResult = 0,
|
|
1957
|
+
oShort,
|
|
1958
|
+
vEmptyParseValue;
|
|
1959
|
+
if (typeof sValue !== "string" && !(sValue instanceof String)) {
|
|
1960
|
+
return null;
|
|
1961
|
+
}
|
|
1962
|
+
sValue = FormatUtils.normalize(sValue).trim();
|
|
1963
|
+
if (sValue === "") {
|
|
1964
|
+
const bUnitOrCurrency = oOptions.type === mNumberType.CURRENCY || oOptions.type === mNumberType.UNIT;
|
|
1965
|
+
if (!oOptions.showNumber && !bUnitOrCurrency) {
|
|
1966
|
+
return null;
|
|
1967
|
+
}
|
|
1968
|
+
vEmptyParseValue = oOptions.emptyString;
|
|
1969
|
+
// If the 'emptyString' option is set to 0 or NaN and parseAsString is set to true, the return value should be converted to a string.
|
|
1970
|
+
// Because null is a valid value for string type, therefore null is not converted to a string.
|
|
1971
|
+
if (oOptions.parseAsString && (oOptions.emptyString === 0 || isNaN(oOptions.emptyString))) {
|
|
1972
|
+
vEmptyParseValue = oOptions.emptyString + "";
|
|
1973
|
+
}
|
|
1974
|
+
if (bUnitOrCurrency) {
|
|
1975
|
+
if (!oOptions.showNumber) {
|
|
1976
|
+
return [undefined, vEmptyParseValue];
|
|
1977
|
+
}
|
|
1978
|
+
return [vEmptyParseValue, undefined];
|
|
1979
|
+
} else {
|
|
1980
|
+
return vEmptyParseValue;
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
if (oOptions.groupingSeparator === oOptions.decimalSeparator) {
|
|
1984
|
+
Log.error("The grouping and decimal separator both have the same value '" + oOptions.groupingSeparator + "'. " + "They must be different from each other such that values can be parsed correctly.");
|
|
1985
|
+
}
|
|
1986
|
+
sPercentPattern = oOptions.type === mNumberType.PERCENT ? oOptions.pattern : this.oLocaleData.getPercentPattern();
|
|
1987
|
+
if (sPercentPattern.charAt(0) === "%") {
|
|
1988
|
+
sRegExpFloat = sRegExpFloat.slice(0, 1) + "%?" + sRegExpFloat.slice(1);
|
|
1989
|
+
} else if (sPercentPattern.charAt(sPercentPattern.length - 1) === "%") {
|
|
1990
|
+
sRegExpFloat = sRegExpFloat.slice(0, sRegExpFloat.length - 1) + "%?" + sRegExpFloat.slice(sRegExpFloat.length - 1);
|
|
1991
|
+
}
|
|
1992
|
+
var aUnitCode;
|
|
1993
|
+
if (oOptions.type === mNumberType.UNIT && oOptions.showMeasure) {
|
|
1994
|
+
var mUnitPatterns;
|
|
1995
|
+
if (oOptions.customUnits && typeof oOptions.customUnits === "object") {
|
|
1996
|
+
//custom units are exclusive (no fallback to LocaleData)
|
|
1997
|
+
mUnitPatterns = oOptions.customUnits;
|
|
1998
|
+
} else {
|
|
1999
|
+
mUnitPatterns = this.oLocaleData.getUnitFormats();
|
|
2000
|
+
}
|
|
2001
|
+
assert(mUnitPatterns, "Unit patterns cannot be loaded");
|
|
2002
|
+
|
|
2003
|
+
// filter using allowedUnits option
|
|
2004
|
+
if (oOptions.allowedUnits) {
|
|
2005
|
+
var mFilteredUnits = {};
|
|
2006
|
+
for (var i = 0; i < oOptions.allowedUnits.length; i++) {
|
|
2007
|
+
var sUnitType = oOptions.allowedUnits[i];
|
|
2008
|
+
mFilteredUnits[sUnitType] = mUnitPatterns[sUnitType];
|
|
2009
|
+
}
|
|
2010
|
+
mUnitPatterns = mFilteredUnits;
|
|
2011
|
+
}
|
|
2012
|
+
var oPatternAndResult = parseNumberAndUnit(mUnitPatterns, sValue, oOptions.showNumber, this.oLocaleData.sCLDRLocaleId);
|
|
2013
|
+
var bUnitIsAmbiguous = false;
|
|
2014
|
+
aUnitCode = oPatternAndResult.cldrCode;
|
|
2015
|
+
if (aUnitCode.length === 1) {
|
|
2016
|
+
sMeasure = aUnitCode[0];
|
|
2017
|
+
if (!oOptions.showNumber) {
|
|
2018
|
+
return [undefined, sMeasure];
|
|
2019
|
+
}
|
|
2020
|
+
} else if (aUnitCode.length === 0) {
|
|
2021
|
+
// in case showMeasure is set to false or unitOptional is set to true
|
|
2022
|
+
// we only try to parse the numberValue
|
|
2023
|
+
// the currency format behaves the same
|
|
2024
|
+
if (oOptions.unitOptional) {
|
|
2025
|
+
oPatternAndResult.numberValue = sValue;
|
|
2026
|
+
} else {
|
|
2027
|
+
//unit not found
|
|
2028
|
+
return null;
|
|
2029
|
+
}
|
|
2030
|
+
} else {
|
|
2031
|
+
//ambiguous unit
|
|
2032
|
+
assert(aUnitCode.length === 1, "Ambiguous unit [" + aUnitCode.join(", ") + "] for input: '" + sValue + "'");
|
|
2033
|
+
sMeasure = undefined;
|
|
2034
|
+
bUnitIsAmbiguous = true;
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
// TODO: better error handling in strict mode
|
|
2038
|
+
// Next steps will be to implement a more helpful error message for these cases.
|
|
2039
|
+
// Right now we simply return null. For now this will force the types to throw
|
|
2040
|
+
// a default ParseException with a non-descriptive error.
|
|
2041
|
+
if (oOptions.strictParsing) {
|
|
2042
|
+
// two cases:
|
|
2043
|
+
// 1. showMeasure is set to false, but still a unit was parsed
|
|
2044
|
+
// 2. no unit (either none could be found OR the unit is ambiguous, should be separate error logs later on)
|
|
2045
|
+
if (bUnitIsAmbiguous) {
|
|
2046
|
+
return null;
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
sValue = oPatternAndResult.numberValue || sValue;
|
|
2050
|
+
}
|
|
2051
|
+
var oResult;
|
|
2052
|
+
if (oOptions.type === mNumberType.CURRENCY && oOptions.showMeasure) {
|
|
2053
|
+
oResult = parseNumberAndCurrency({
|
|
2054
|
+
value: sValue,
|
|
2055
|
+
currencySymbols: this.mKnownCurrencySymbols,
|
|
2056
|
+
customCurrencyCodes: this.mKnownCurrencyCodes,
|
|
2057
|
+
duplicatedSymbols: this.mDuplicatedSymbols,
|
|
2058
|
+
customCurrenciesAvailable: !!oOptions.customCurrencies
|
|
2059
|
+
});
|
|
2060
|
+
if (!oResult) {
|
|
2061
|
+
return null;
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
// TODO: better error handling in strict mode
|
|
2065
|
+
// Next steps will be to implement a more helpful error message for these cases.
|
|
2066
|
+
// Right now we simply return null. For now this will force the types to throw
|
|
2067
|
+
// a default ParseException with a non-descriptive error.
|
|
2068
|
+
if (oOptions.strictParsing) {
|
|
2069
|
+
if (!oResult.currencyCode || oResult.duplicatedSymbolFound) {
|
|
2070
|
+
// here we need an error log for:
|
|
2071
|
+
// 1. missing currency code/symbol (CLDR & custom)
|
|
2072
|
+
// 2. duplicated symbol was found (only custom, CLDR has no duplicates)
|
|
2073
|
+
return null;
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
sValue = oResult.numberValue;
|
|
2077
|
+
sMeasure = oResult.currencyCode;
|
|
2078
|
+
if (oOptions.customCurrencies && sMeasure === null) {
|
|
2079
|
+
return null;
|
|
2080
|
+
}
|
|
2081
|
+
if (!oOptions.showNumber) {
|
|
2082
|
+
if (sValue) {
|
|
2083
|
+
return null;
|
|
2084
|
+
}
|
|
2085
|
+
return [undefined, sMeasure];
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
|
|
2089
|
+
// remove all white spaces because when grouping separator is a non-breaking space (russian and french for example)
|
|
2090
|
+
// user will not input it this way. Also white spaces or grouping separator can be ignored by determining the value
|
|
2091
|
+
sValue = sValue.replace(rAllWhiteSpaces, "");
|
|
2092
|
+
oShort = this.getNumberFromShortened(sValue, bIndianCurrency);
|
|
2093
|
+
if (oShort) {
|
|
2094
|
+
sValue = oShort.number;
|
|
2095
|
+
}
|
|
2096
|
+
var bScientificNotation = isScientificNotation(sValue);
|
|
2097
|
+
|
|
2098
|
+
// Check for valid syntax
|
|
2099
|
+
// integer might be expressed in scientific format, e.g. 1.23e+5
|
|
2100
|
+
// for this case it must be parsed as float
|
|
2101
|
+
if (oOptions.isInteger && !oShort && !bScientificNotation) {
|
|
2102
|
+
oRegExp = new RegExp(sRegExpInt);
|
|
2103
|
+
} else {
|
|
2104
|
+
oRegExp = new RegExp(sRegExpFloat);
|
|
2105
|
+
}
|
|
2106
|
+
if (!oRegExp.test(sValue)) {
|
|
2107
|
+
return oOptions.type === mNumberType.CURRENCY || oOptions.type === mNumberType.UNIT ? null : NaN;
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
// Replace "minus/plus" sign with a parsable symbol
|
|
2111
|
+
// e.g. "➖47" ("➖" or "\u2796" cannot be parsed using parseInt) --> "-47" (can be parsed using parseInt)
|
|
2112
|
+
var iValueLength = sValue.length;
|
|
2113
|
+
for (var iValuePos = 0; iValuePos < iValueLength; iValuePos++) {
|
|
2114
|
+
var sCurrentValueChar = sValue[iValuePos];
|
|
2115
|
+
|
|
2116
|
+
// it can either be a minus or a plus
|
|
2117
|
+
// if one was found break because there can only be one in a value
|
|
2118
|
+
if (sPlusSigns.includes(sCurrentValueChar)) {
|
|
2119
|
+
sValue = sValue.replace(sCurrentValueChar, "+");
|
|
2120
|
+
break;
|
|
2121
|
+
} else if (sMinusSigns.includes(sCurrentValueChar)) {
|
|
2122
|
+
sValue = sValue.replace(sCurrentValueChar, "-");
|
|
2123
|
+
break;
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
|
|
2127
|
+
// Remove the leading "+" sign because when "parseAsString" is set to true the "parseInt" or "parseFloat" isn't called and the leading "+" has to be moved manually
|
|
2128
|
+
sValue = sValue.replace(/^\+/, "");
|
|
2129
|
+
|
|
2130
|
+
// remove the percentage sign
|
|
2131
|
+
if (!oOptions.isInteger && sValue.indexOf(sPercentSign) !== -1) {
|
|
2132
|
+
bPercent = true;
|
|
2133
|
+
sValue = sValue.replace(sPercentSign, "");
|
|
2134
|
+
}
|
|
2135
|
+
var sValueWithGrouping = sValue;
|
|
2136
|
+
|
|
2137
|
+
// Remove grouping separator and replace locale dependant decimal separator,
|
|
2138
|
+
// before calling parseInt/parseFloat
|
|
2139
|
+
sValue = sValue.replace(oGroupingRegExp, "");
|
|
2140
|
+
|
|
2141
|
+
// Expanding short value before using parseInt/parseFloat
|
|
2142
|
+
if (oShort) {
|
|
2143
|
+
sValue = sValue.replace(oDecimalRegExp, ".");
|
|
2144
|
+
sValue = NumberFormat._shiftDecimalPoint(sValue, Math.round(Math.log(oShort.factor) / Math.LN10));
|
|
2145
|
+
}
|
|
2146
|
+
if (oOptions.isInteger) {
|
|
2147
|
+
var iInt;
|
|
2148
|
+
// check if it is a valid integer
|
|
2149
|
+
// 1.234567e+5 is 123456.7 is not an integer
|
|
2150
|
+
// 1.234567e+6 is 1234567 is an integer
|
|
2151
|
+
if (bScientificNotation) {
|
|
2152
|
+
sValue = sValue.replace(oDecimalRegExp, ".");
|
|
2153
|
+
iInt = getInteger(sValue);
|
|
2154
|
+
if (iInt === undefined) {
|
|
2155
|
+
return NaN;
|
|
2156
|
+
}
|
|
2157
|
+
} else {
|
|
2158
|
+
iInt = parseInt(sValue);
|
|
2159
|
+
}
|
|
2160
|
+
vResult = oOptions.parseAsString ? sValue : iInt;
|
|
2161
|
+
} else {
|
|
2162
|
+
sValue = sValue.replace(oDecimalRegExp, ".");
|
|
2163
|
+
vResult = oOptions.parseAsString ? sValue : parseFloat(sValue);
|
|
2164
|
+
if (bPercent) {
|
|
2165
|
+
vResult = NumberFormat._shiftDecimalPoint(vResult, -2);
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
// strict grouping validation
|
|
2170
|
+
var bIsGroupingValid = this._checkGrouping(sValueWithGrouping, oOptions, bScientificNotation);
|
|
2171
|
+
if (!bIsGroupingValid) {
|
|
2172
|
+
// treat invalid grouping the same way as if the value cannot be parsed
|
|
2173
|
+
return oOptions.type === mNumberType.CURRENCY || oOptions.type === mNumberType.UNIT ? null : NaN;
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
// Get rid of leading zeros (percent was already shifted)
|
|
2177
|
+
if (oOptions.parseAsString && !bPercent) {
|
|
2178
|
+
vResult = NumberFormat._shiftDecimalPoint(sValue, 0);
|
|
2179
|
+
}
|
|
2180
|
+
if (oOptions.type === mNumberType.CURRENCY || oOptions.type === mNumberType.UNIT) {
|
|
2181
|
+
return [vResult, sMeasure];
|
|
2182
|
+
}
|
|
2183
|
+
return vResult;
|
|
2184
|
+
};
|
|
2185
|
+
|
|
2186
|
+
/**
|
|
2187
|
+
* Returns the scaling factor which is calculated based on the format options and the current locale being used.
|
|
2188
|
+
*
|
|
2189
|
+
* This function only returns a meaningful scaling factor when the 'style' formatting option is set
|
|
2190
|
+
* to 'short' or 'long', and the 'shortRefNumber' option for calculating the scale factor is set.
|
|
2191
|
+
*
|
|
2192
|
+
* Consider using this function when the 'showScale' option is set to <code>false</code>, which
|
|
2193
|
+
* causes the scale factor not to appear in every formatted number but in a shared place.
|
|
2194
|
+
*
|
|
2195
|
+
* @example thousand (locale "en")
|
|
2196
|
+
*
|
|
2197
|
+
* NumberFormat.getFloatInstance({style: "long", shortRefNumber: 1000}).getScale();
|
|
2198
|
+
* // "thousand"
|
|
2199
|
+
*
|
|
2200
|
+
* @returns {string|undefined} The scale string if it exists based on the given 'shortRefNumber' option. Otherwise it returns <code>undefined</code>.
|
|
2201
|
+
* @since 1.100
|
|
2202
|
+
* @public
|
|
2203
|
+
*/
|
|
2204
|
+
NumberFormat.prototype.getScale = function () {
|
|
2205
|
+
if (this.oFormatOptions.style !== "short" && this.oFormatOptions.style !== "long" || this.oFormatOptions.shortRefNumber === undefined) {
|
|
2206
|
+
return;
|
|
2207
|
+
}
|
|
2208
|
+
var oShortFormat = this.getShortenedFormat(this.oFormatOptions.shortRefNumber, this.oFormatOptions),
|
|
2209
|
+
sScale;
|
|
2210
|
+
if (oShortFormat && oShortFormat.formatString) {
|
|
2211
|
+
// remove the placeholder of number
|
|
2212
|
+
// replace the "'.'" with "."
|
|
2213
|
+
// trim to remove the space and non-breakable space
|
|
2214
|
+
sScale = oShortFormat.formatString.replace(rNumPlaceHolder, "").replace(/'.'/g, ".").trim();
|
|
2215
|
+
if (sScale) {
|
|
2216
|
+
// sScale could be an empty string and undefined should be returned in this case
|
|
2217
|
+
return sScale;
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
};
|
|
2221
|
+
|
|
2222
|
+
/**
|
|
2223
|
+
* Moves the decimal seperator of the given number by the given steps to the right or left.
|
|
2224
|
+
*
|
|
2225
|
+
* @param {number|string} vValue
|
|
2226
|
+
* The number
|
|
2227
|
+
* @param {int} iStep
|
|
2228
|
+
* The number of decimal places to shift the "."; positive values shift to the right, negative values shift to the
|
|
2229
|
+
* left
|
|
2230
|
+
* @param {boolean} bNormalize
|
|
2231
|
+
* Whether the result is normalized if <code>vValue</code> is of type "string"; that means whether trailing zeros
|
|
2232
|
+
* are removed and whether scientific notation is resolved to a decimal string without exponent
|
|
2233
|
+
* @returns {number|string|null}
|
|
2234
|
+
* The number with shifted decimal point; or <code>null</code> if the given value is neither of type "number", nor
|
|
2235
|
+
* of type "string"
|
|
2236
|
+
*/
|
|
2237
|
+
NumberFormat._shiftDecimalPoint = function (vValue, iStep, bNormalize) {
|
|
2238
|
+
var sMinus = "";
|
|
2239
|
+
var aExpParts = vValue.toString().toLowerCase().split("e");
|
|
2240
|
+
if (typeof vValue === "number") {
|
|
2241
|
+
// Exponential operation is used instead of simply multiply the number by
|
|
2242
|
+
// Math.pow(10, maxFractionDigits) because Exponential operation returns exact float
|
|
2243
|
+
// result but multiply doesn't. For example 1.005*100 = 100.49999999999999.
|
|
2244
|
+
|
|
2245
|
+
iStep = aExpParts[1] ? +aExpParts[1] + iStep : iStep;
|
|
2246
|
+
return +(aExpParts[0] + "e" + iStep);
|
|
2247
|
+
} else if (typeof vValue === "string") {
|
|
2248
|
+
if (!bNormalize && parseFloat(vValue) === 0 && iStep >= 0) {
|
|
2249
|
+
// input "00000" should become "0"
|
|
2250
|
+
// input "000.000" should become "0.000" to keep precision of decimals
|
|
2251
|
+
// input "1e-1337" should remain "1e-1337" in order to keep the precision
|
|
2252
|
+
return vValue.replace(rLeadingZeros, "$1$2");
|
|
2253
|
+
}
|
|
2254
|
+
// In case of a negative value the leading minus needs to be cut off before shifting the decimal point.
|
|
2255
|
+
// Otherwise the minus will affect the positioning by index 1.
|
|
2256
|
+
// The minus sign will be added to the final result again.
|
|
2257
|
+
var sFirstChar = aExpParts[0].charAt(0);
|
|
2258
|
+
sMinus = sFirstChar === "-" ? sFirstChar : "";
|
|
2259
|
+
if (sMinus || sFirstChar === "+") {
|
|
2260
|
+
aExpParts[0] = aExpParts[0].slice(1);
|
|
2261
|
+
}
|
|
2262
|
+
vValue = aExpParts[0];
|
|
2263
|
+
var iDecimalPos = vValue.indexOf("."),
|
|
2264
|
+
// the expected position after move
|
|
2265
|
+
iAfterMovePos,
|
|
2266
|
+
// the integer part in the final result
|
|
2267
|
+
sInt,
|
|
2268
|
+
// the decimal part in the final result
|
|
2269
|
+
sDecimal;
|
|
2270
|
+
if (iDecimalPos === -1) {
|
|
2271
|
+
// when there's no decimal point, add one to the end
|
|
2272
|
+
vValue = vValue + ".";
|
|
2273
|
+
iDecimalPos = vValue.length - 1;
|
|
2274
|
+
}
|
|
2275
|
+
if (aExpParts[1]) {
|
|
2276
|
+
iDecimalPos += +aExpParts[1];
|
|
2277
|
+
}
|
|
2278
|
+
iAfterMovePos = iDecimalPos + iStep;
|
|
2279
|
+
if (iAfterMovePos <= 0) {
|
|
2280
|
+
// pad 0 to the left when decimal point should be shifted far left
|
|
2281
|
+
vValue = vValue.padStart(vValue.length - iAfterMovePos + 1, '0');
|
|
2282
|
+
iAfterMovePos = 1;
|
|
2283
|
+
} else if (iAfterMovePos >= vValue.length - 1) {
|
|
2284
|
+
// pad 0 to the right
|
|
2285
|
+
vValue = vValue.padEnd(iAfterMovePos + 1, '0');
|
|
2286
|
+
iAfterMovePos = vValue.length - 1;
|
|
2287
|
+
}
|
|
2288
|
+
vValue = vValue.replace(".", "");
|
|
2289
|
+
sInt = vValue.substring(0, iAfterMovePos);
|
|
2290
|
+
sDecimal = vValue.substring(iAfterMovePos);
|
|
2291
|
+
// remove unnecessary leading zeros
|
|
2292
|
+
sInt = sInt.replace(rLeadingZeros, "$1$2");
|
|
2293
|
+
if (bNormalize) {
|
|
2294
|
+
sDecimal = sDecimal.replace(rTrailingZeros, "");
|
|
2295
|
+
}
|
|
2296
|
+
return sMinus + sInt + (sDecimal ? "." + sDecimal : "");
|
|
2297
|
+
} else {
|
|
2298
|
+
// can't shift decimal point in this case
|
|
2299
|
+
return null;
|
|
2300
|
+
}
|
|
2301
|
+
};
|
|
2302
|
+
|
|
2303
|
+
/**
|
|
2304
|
+
* Checks whether there is a letter next to the number using the given currency pattern.
|
|
2305
|
+
*
|
|
2306
|
+
* @param {string} sPattern
|
|
2307
|
+
* The currency pattern, e.g. "¤#,##0.00;(¤#,##0.00)" or "¤ 000K"
|
|
2308
|
+
* @param {string} sCurrency
|
|
2309
|
+
* The currency code or the currency symbol to check, e.g. "USD" or "$"
|
|
2310
|
+
* @param {boolean} bNegative
|
|
2311
|
+
* Whether the value to be formatted is negative
|
|
2312
|
+
* @returns {boolean}
|
|
2313
|
+
* Whether there is a letter next to the number for the given pattern and currency
|
|
2314
|
+
*
|
|
2315
|
+
* @private
|
|
2316
|
+
*/
|
|
2317
|
+
NumberFormat.isAlphaNextToNumber = function (sPattern, sCurrency, bNegative) {
|
|
2318
|
+
if (!sPattern || !sCurrency) {
|
|
2319
|
+
return false;
|
|
2320
|
+
}
|
|
2321
|
+
const aPatterns = sPattern.split(";");
|
|
2322
|
+
sPattern = (aPatterns[bNegative ? 1 : 0] || aPatterns[0]).replace(rAllRTLCharacters, "");
|
|
2323
|
+
const aMatches = rSplitCurrencyPattern.exec(sPattern);
|
|
2324
|
+
|
|
2325
|
+
// number in front of the currency placeholder
|
|
2326
|
+
if (aMatches[1]) {
|
|
2327
|
+
return rStartsWithLetter.test(sCurrency) && !aMatches[2];
|
|
2328
|
+
}
|
|
2329
|
+
|
|
2330
|
+
// currency placeholder in front of the number
|
|
2331
|
+
return !aMatches[4] // no characters between the placeholder and the number
|
|
2332
|
+
&& rEndsWithLetter.test(sCurrency) // currency ends with a letter
|
|
2333
|
+
// if there is no separate negative pattern and the value is negative, there is a minus sign between
|
|
2334
|
+
// the currency and the number, so there is no need for the alphaNextToNumber pattern
|
|
2335
|
+
&& (!bNegative || aPatterns.length !== 1);
|
|
2336
|
+
};
|
|
2337
|
+
|
|
2338
|
+
/**
|
|
2339
|
+
* Gets the compact decimal or currency pattern for the given power of ten and plural category.
|
|
2340
|
+
*
|
|
2341
|
+
* @param {"integer"|"float"|"currency"|"unit"|"percent"} sType
|
|
2342
|
+
* The number format type
|
|
2343
|
+
* @param {"long"|"short"} sStyle
|
|
2344
|
+
* The style of the compact format
|
|
2345
|
+
* @param {string} sPowerOfTen
|
|
2346
|
+
* The power of ten
|
|
2347
|
+
* @param {"few"|"many"|"one"|"other"|"two"|"zero"} sPluralCategory
|
|
2348
|
+
* The plural category
|
|
2349
|
+
* @param {boolean} [bTrailingCurrencyCode]
|
|
2350
|
+
* Whether the currency code is formatted after the amount; only relevant if type "currency" is used
|
|
2351
|
+
* @param {boolean} [bIndianCurrency]
|
|
2352
|
+
* Whether to use the Indian currency format; only relevant if type "currency" is used
|
|
2353
|
+
* @param {boolean} [bShowMeasure]
|
|
2354
|
+
* Whether to show the measure
|
|
2355
|
+
* @param {string} [sCurrency]
|
|
2356
|
+
* The currency code or symbol
|
|
2357
|
+
* @param {boolean} [bNegative]
|
|
2358
|
+
* Whether the number is negative
|
|
2359
|
+
* @returns {string|undefined}
|
|
2360
|
+
* The compact decimal or currency pattern for the given power of ten and plural category; or
|
|
2361
|
+
* <code>undefined</code> if there is no pattern for the given parameters
|
|
2362
|
+
*
|
|
2363
|
+
* @private
|
|
2364
|
+
*/
|
|
2365
|
+
NumberFormat.prototype.getCompactPattern = function (sType, sStyle, sPowerOfTen, sPluralCategory, bTrailingCurrencyCode, bIndianCurrency, bShowMeasure, sCurrency, bNegative) {
|
|
2366
|
+
let sPattern;
|
|
2367
|
+
if (sType === mNumberType.CURRENCY) {
|
|
2368
|
+
if (bTrailingCurrencyCode) {
|
|
2369
|
+
sStyle = "sap-short";
|
|
2370
|
+
}
|
|
2371
|
+
if (bIndianCurrency) {
|
|
2372
|
+
sStyle += "-indian";
|
|
2373
|
+
}
|
|
2374
|
+
if (bShowMeasure) {
|
|
2375
|
+
// Use currency specific format because for some languages there is a difference between the
|
|
2376
|
+
// decimalFormat and the currencyFormat
|
|
2377
|
+
sPattern = this.oLocaleData.getCompactCurrencyPattern(sStyle, sPowerOfTen, sPluralCategory);
|
|
2378
|
+
if (NumberFormat.isAlphaNextToNumber(sPattern, sCurrency, bNegative)) {
|
|
2379
|
+
sPattern = this.oLocaleData.getCompactCurrencyPattern(sStyle, sPowerOfTen, sPluralCategory, "alphaNextToNumber") || sPattern;
|
|
2380
|
+
}
|
|
2381
|
+
} else {
|
|
2382
|
+
sPattern = this.oLocaleData.getCompactCurrencyPattern(sStyle, sPowerOfTen, sPluralCategory, "noCurrency");
|
|
2383
|
+
if (!sPattern) {
|
|
2384
|
+
if (sStyle.startsWith("sap-")) {
|
|
2385
|
+
sStyle = sStyle.slice(4);
|
|
2386
|
+
}
|
|
2387
|
+
sPattern = this.oLocaleData.getCompactDecimalPattern(sStyle, sPowerOfTen, sPluralCategory);
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2390
|
+
} else {
|
|
2391
|
+
sPattern = this.oLocaleData.getCompactDecimalPattern(sStyle, sPowerOfTen, sPluralCategory);
|
|
2392
|
+
}
|
|
2393
|
+
|
|
2394
|
+
// pattern may contain a single quoted dot ('.') to differentiate them from decimal separator; replace it
|
|
2395
|
+
// with an unquoted dot (.)
|
|
2396
|
+
sPattern = sPattern?.replace(/'.'/g, ".");
|
|
2397
|
+
return sPattern;
|
|
2398
|
+
};
|
|
2399
|
+
|
|
2400
|
+
/**
|
|
2401
|
+
* Gets the locale specific currency pattern for the given parameters.
|
|
2402
|
+
*
|
|
2403
|
+
* @param {"accounting"|"standard"} sContext The context of the currency pattern
|
|
2404
|
+
* @param {boolean} [bShowTrailingCurrencyCode] Whether the currency code shall be shown after the amount
|
|
2405
|
+
* @param {boolean} [bShowMeasure] Whether to include the measure (currency code or currency symbol) in the pattern
|
|
2406
|
+
* @param {string} [sCurrency] The currency code or symbol to use
|
|
2407
|
+
* @param {boolean} [bNegative] Whether the current value is negative
|
|
2408
|
+
* @returns {string} The currency pattern
|
|
2409
|
+
*
|
|
2410
|
+
* @private
|
|
2411
|
+
*/
|
|
2412
|
+
NumberFormat.prototype.getCurrencyPattern = function (sContext, bShowTrailingCurrencyCode, bShowMeasure, sCurrency, bNegative) {
|
|
2413
|
+
if (bShowTrailingCurrencyCode) {
|
|
2414
|
+
sContext = "sap-" + sContext;
|
|
2415
|
+
}
|
|
2416
|
+
let sPattern = this.oLocaleData.getCurrencyPattern(sContext, bShowMeasure ? undefined : "noCurrency");
|
|
2417
|
+
if (bShowMeasure && NumberFormat.isAlphaNextToNumber(sPattern, sCurrency, bNegative)) {
|
|
2418
|
+
sPattern = this.oLocaleData.getCurrencyPattern(sContext, "alphaNextToNumber") || sPattern;
|
|
2419
|
+
}
|
|
2420
|
+
return sPattern;
|
|
2421
|
+
};
|
|
2422
|
+
|
|
2423
|
+
/**
|
|
2424
|
+
* Gets the compact decimal or currency format for the given value and parameters.
|
|
2425
|
+
*
|
|
2426
|
+
* @param {number|string} vValue
|
|
2427
|
+
* The value for which the shortened format is determined
|
|
2428
|
+
* @param {object} oOptions
|
|
2429
|
+
* The options used for getting the compact pattern
|
|
2430
|
+
* @param {int} [oOptions.precision = 2]
|
|
2431
|
+
* The maximum number of digits in the formatted representation of the number
|
|
2432
|
+
* @param {"long"|"short"} oOptions.style
|
|
2433
|
+
* The style of the compact format
|
|
2434
|
+
* @param {boolean} [oOptions.trailingCurrencyCode]
|
|
2435
|
+
* Whether the currency code is formatted after the amount; only relevant if type "currency" is used
|
|
2436
|
+
* @param {"integer"|"float"|"currency"|"unit"|"percent"} oOptions.type
|
|
2437
|
+
* The number format type
|
|
2438
|
+
* @param {boolean} [bIndianCurrency]
|
|
2439
|
+
* Whether to use the Indian currency format; only relevant if type "currency" is used
|
|
2440
|
+
*
|
|
2441
|
+
* @returns {object|undefined}
|
|
2442
|
+
* The compact decimal or currency format for the given value; or <code>undefined</code> if neither the "short"
|
|
2443
|
+
* or the "long" style is used, or if there is no compact format for the given parameters; the returned object
|
|
2444
|
+
* contains the following properties:
|
|
2445
|
+
* <ul>
|
|
2446
|
+
* <li><code>decimals</code>: The number of decimals used in the compact format pattern</li>
|
|
2447
|
+
* <li><code>formatString</code>: The compact format pattern to use</li>
|
|
2448
|
+
* <li><code>key</code>: The power of ten matching the given value</li>
|
|
2449
|
+
* <li><code>magnitude</code>: The divisor to get the compact number to show from the given value</li>
|
|
2450
|
+
* <li><code>valueSubString</code>: The number part of the format pattern</li>
|
|
2451
|
+
* </ul>
|
|
2452
|
+
*
|
|
2453
|
+
* @private
|
|
2454
|
+
*/
|
|
2455
|
+
NumberFormat.prototype.getShortenedFormat = function (vValue, oOptions, bIndianCurrency) {
|
|
2456
|
+
var oShortFormat,
|
|
2457
|
+
iKey,
|
|
2458
|
+
sKey,
|
|
2459
|
+
sStyle = oOptions.style,
|
|
2460
|
+
iPrecision = oOptions.precision !== undefined ? oOptions.precision : 2;
|
|
2461
|
+
if (sStyle != "short" && sStyle != "long") {
|
|
2462
|
+
return undefined;
|
|
2463
|
+
}
|
|
2464
|
+
for (var i = 0; i < 15; i++) {
|
|
2465
|
+
iKey = Math.pow(10, i);
|
|
2466
|
+
if (rounding(Math.abs(vValue) / iKey, iPrecision - 1) < 10) {
|
|
2467
|
+
break;
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
sKey = iKey.toString();
|
|
2471
|
+
|
|
2472
|
+
// Use "other" format to find the right magnitude, the actual format will be retrieved later
|
|
2473
|
+
// after the value has been calculated
|
|
2474
|
+
const sCldrFormat = this.getCompactPattern(oOptions.type, oOptions.style, sKey, "other", oOptions.trailingCurrencyCode, bIndianCurrency, oOptions.showMeasure);
|
|
2475
|
+
if (!sCldrFormat || sCldrFormat == "0") {
|
|
2476
|
+
//no format or special "0" format => number doesn't need to be shortened
|
|
2477
|
+
return undefined;
|
|
2478
|
+
} else {
|
|
2479
|
+
oShortFormat = {};
|
|
2480
|
+
oShortFormat.key = sKey;
|
|
2481
|
+
oShortFormat.formatString = sCldrFormat;
|
|
2482
|
+
var match = sCldrFormat.match(rNumPlaceHolder);
|
|
2483
|
+
if (match) {
|
|
2484
|
+
//to get magnitude, we need to remove from key the number of zeros
|
|
2485
|
+
//contained in valueSubString before decimal separator minus 1
|
|
2486
|
+
// "0.0" => magnitude = key
|
|
2487
|
+
// "00" => magnitude = key / 10
|
|
2488
|
+
// "000" => magnitude = key / 100
|
|
2489
|
+
oShortFormat.valueSubString = match[0];
|
|
2490
|
+
var decimalSeparatorPosition = oShortFormat.valueSubString.indexOf(".");
|
|
2491
|
+
if (decimalSeparatorPosition == -1) {
|
|
2492
|
+
oShortFormat.decimals = 0;
|
|
2493
|
+
oShortFormat.magnitude = iKey * Math.pow(10, 1 - oShortFormat.valueSubString.length);
|
|
2494
|
+
} else {
|
|
2495
|
+
oShortFormat.decimals = oShortFormat.valueSubString.length - decimalSeparatorPosition - 1;
|
|
2496
|
+
oShortFormat.magnitude = iKey * Math.pow(10, 1 - decimalSeparatorPosition);
|
|
2497
|
+
}
|
|
2498
|
+
} else {
|
|
2499
|
+
//value pattern has not be recognized
|
|
2500
|
+
//we cannot shorten
|
|
2501
|
+
return undefined;
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
return oShortFormat;
|
|
2505
|
+
};
|
|
2506
|
+
|
|
2507
|
+
/**
|
|
2508
|
+
* Returns an object with two properties. The first is the number substring of the given <code>sValue</code>.
|
|
2509
|
+
* The second property is the factor with which the determined number must be multiplied to resolve
|
|
2510
|
+
* the short format.
|
|
2511
|
+
* If the number of the given <code>sValue</code> is larger than 100 000 000 000 000 no short format will be found
|
|
2512
|
+
* and <code>undefined</code> is returned.
|
|
2513
|
+
*
|
|
2514
|
+
* @example
|
|
2515
|
+
* Formatted value to be parsed: "123K"
|
|
2516
|
+
* Chosen short format: "100000-other": "000K",
|
|
2517
|
+
* Number Substring: "123"
|
|
2518
|
+
* Factor: 1000
|
|
2519
|
+
* Parsed number: 123 * 1000 = 123000
|
|
2520
|
+
*
|
|
2521
|
+
* @param {string} sValue The value for which the short format shall be determined
|
|
2522
|
+
* @param {boolean} bIndianCurrency Whether the the value has to be treated as Indian currency
|
|
2523
|
+
*
|
|
2524
|
+
* @returns {{number: string, factor: number}|undefined}
|
|
2525
|
+
* An object containing the number substring of the given <code>sValue</code>, e.g. <code>"123"</code> and
|
|
2526
|
+
* the factor with which the determined number must be multiplied to resolve the short format;
|
|
2527
|
+
* <code>undefined</code> if no short format is found for the given <code>sValue</code>
|
|
2528
|
+
*
|
|
2529
|
+
* @private
|
|
2530
|
+
*/
|
|
2531
|
+
NumberFormat.prototype.getNumberFromShortened = function (sValue, bIndianCurrency) {
|
|
2532
|
+
const aPluralCategories = this.oLocaleData.getPluralCategories();
|
|
2533
|
+
const oBestResult = {
|
|
2534
|
+
number: undefined,
|
|
2535
|
+
factor: 1
|
|
2536
|
+
};
|
|
2537
|
+
const oIndianBestResult = {
|
|
2538
|
+
number: undefined,
|
|
2539
|
+
factor: 1
|
|
2540
|
+
};
|
|
2541
|
+
for (const iPowerOfTen of aPowerOfTens) {
|
|
2542
|
+
for (const sPluralCategory of aPluralCategories) {
|
|
2543
|
+
if (bIndianCurrency) {
|
|
2544
|
+
this.updateBestResult(oIndianBestResult, sPluralCategory, iPowerOfTen, "short-indian", sValue);
|
|
2545
|
+
}
|
|
2546
|
+
this.updateBestResult(oBestResult, sPluralCategory, iPowerOfTen, "long", sValue);
|
|
2547
|
+
this.updateBestResult(oBestResult, sPluralCategory, iPowerOfTen, "short", sValue);
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
if (oIndianBestResult.number) {
|
|
2551
|
+
return oIndianBestResult;
|
|
2552
|
+
} else if (oBestResult.number) {
|
|
2553
|
+
return oBestResult;
|
|
2554
|
+
}
|
|
2555
|
+
return undefined;
|
|
2556
|
+
};
|
|
2557
|
+
|
|
2558
|
+
/**
|
|
2559
|
+
* Updates the number and factor in the given reference object based on the plural category,
|
|
2560
|
+
* power of ten, and style with the best match if one can be found for a given value.
|
|
2561
|
+
*
|
|
2562
|
+
* @param {{number: string, factor: number}} oBestResult
|
|
2563
|
+
* A reference in which the number and factor determined by this function will be stored
|
|
2564
|
+
* @param {"zero"|"one"|"two"|"few"|"many"} sPluralCategory
|
|
2565
|
+
* The plural category
|
|
2566
|
+
* @param {int} iPowerOfTen
|
|
2567
|
+
* The power of ten, max: 100 000 000 000 000
|
|
2568
|
+
* @param {"long"|"short"|"short-indian"} sStyle
|
|
2569
|
+
* The style
|
|
2570
|
+
* @param {string} sValue
|
|
2571
|
+
* The value for which the number and factor has to be determined
|
|
2572
|
+
*
|
|
2573
|
+
* @private
|
|
2574
|
+
*/
|
|
2575
|
+
NumberFormat.prototype.updateBestResult = function (oBestResult, sPluralCategory, iPowerOfTen, sStyle, sValue) {
|
|
2576
|
+
let sCldrFormat = this.oLocaleData.getCompactDecimalPattern(sStyle, iPowerOfTen.toString(), sPluralCategory);
|
|
2577
|
+
if (sCldrFormat) {
|
|
2578
|
+
// Note: CLDR uses a non-breaking space and right-to-left mark u+200f in the format string
|
|
2579
|
+
sCldrFormat = FormatUtils.normalize(sCldrFormat, true);
|
|
2580
|
+
//formatString may contain '.' (quoted to differentiate them from decimal separator)
|
|
2581
|
+
//which must be replaced with .
|
|
2582
|
+
sCldrFormat = sCldrFormat.replace(/'.'/g, ".");
|
|
2583
|
+
const aMatch = sCldrFormat.match(rNumPlaceHolder);
|
|
2584
|
+
if (aMatch) {
|
|
2585
|
+
// determine unit -> may be on the beginning e.g. for he
|
|
2586
|
+
const sValueSubString = aMatch[0];
|
|
2587
|
+
const sScalingFactor = sCldrFormat.replace(sValueSubString, "");
|
|
2588
|
+
if (sScalingFactor && sValue.includes(sScalingFactor)) {
|
|
2589
|
+
// parse the number part like every other number and then use the factor to get the real number
|
|
2590
|
+
const sNumber = sValue.replace(sScalingFactor, "");
|
|
2591
|
+
// spanish numbers e.g. for MRD in format for "one" is "00 MRD" therefore factor needs to be
|
|
2592
|
+
// adjusted
|
|
2593
|
+
// german numbers e.g. for Mrd. in format for "one" is "0 Mrd." therefore number does not need to
|
|
2594
|
+
// be adjusted
|
|
2595
|
+
// "0" => magnitude = key
|
|
2596
|
+
// "00" => magnitude = key / 10
|
|
2597
|
+
// "000" => magnitude = key / 100
|
|
2598
|
+
const iFactor = iPowerOfTen * Math.pow(10, 1 - sValueSubString.length);
|
|
2599
|
+
if (oBestResult.number === undefined || sNumber.length < oBestResult.number.length) {
|
|
2600
|
+
oBestResult.number = sNumber;
|
|
2601
|
+
oBestResult.factor = iFactor;
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
};
|
|
2607
|
+
|
|
2608
|
+
/**
|
|
2609
|
+
* Whether to show the currency code at the end based on the original format options and the global configuration.
|
|
2610
|
+
*
|
|
2611
|
+
* @returns {boolean} Whether to show trailing currency code
|
|
2612
|
+
*/
|
|
2613
|
+
NumberFormat.prototype.showTrailingCurrencyCode = function () {
|
|
2614
|
+
const oFormatOptions = this.oOriginalFormatOptions;
|
|
2615
|
+
// use default currency mode if custom pattern is given or currency code shall not be shown
|
|
2616
|
+
if (oFormatOptions?.pattern || oFormatOptions?.currencyCode === false) {
|
|
2617
|
+
return false;
|
|
2618
|
+
}
|
|
2619
|
+
return oFormatOptions?.trailingCurrencyCode !== undefined ? oFormatOptions.trailingCurrencyCode // overwritten by instance configuration
|
|
2620
|
+
: Formatting.getTrailingCurrencyCode();
|
|
2621
|
+
};
|
|
2622
|
+
|
|
2623
|
+
/**
|
|
2624
|
+
* Checks if grouping is performed correctly (decimal separator is not confused with grouping separator).
|
|
2625
|
+
* The examples use the German locale.
|
|
2626
|
+
*
|
|
2627
|
+
* Validity:
|
|
2628
|
+
* * The grouping is valid if there are at least 2 grouping separators present.
|
|
2629
|
+
* Because there can only be one decimal separator, and by writing 2 grouping separators there is no confusion.
|
|
2630
|
+
* E.g. 1.2.3
|
|
2631
|
+
* * The grouping is valid if there is a decimal separator and one grouping separator present.
|
|
2632
|
+
* Because the user wrote both, there cannot be a confusion.
|
|
2633
|
+
* (If it was confused, it has already been taken care by the syntax check.)
|
|
2634
|
+
* E.g. 1.2,3
|
|
2635
|
+
*
|
|
2636
|
+
* Invalidity:
|
|
2637
|
+
* * If there is exactly one grouping separator present, no decimal separator, and the grouping
|
|
2638
|
+
* separator at the most right grouping position is wrong.
|
|
2639
|
+
* E.g. 1.2
|
|
2640
|
+
* E.g. 1.234567
|
|
2641
|
+
*
|
|
2642
|
+
* The grouping is checked even if the groupingEnabled format is set to <code>false</code>, because the
|
|
2643
|
+
* input could be copied from external sources which might have wrong grouping separators.
|
|
2644
|
+
*
|
|
2645
|
+
* The empty grouping separator is ignored and <code>true</code> is returned, because it cannot be validated.
|
|
2646
|
+
*
|
|
2647
|
+
* An additional check is performed which invalidates a wrong number syntax
|
|
2648
|
+
* E.g. 0.123
|
|
2649
|
+
* E.g. -.123
|
|
2650
|
+
*
|
|
2651
|
+
* @param {string} sValueWithGrouping the normalized value which only contains the grouping (e.g. "1.000"),
|
|
2652
|
+
* i.e. the following modifications were already applied:
|
|
2653
|
+
* <ul>
|
|
2654
|
+
* <li>remove percent symbol</li>
|
|
2655
|
+
* <li>remove leading plus</li>
|
|
2656
|
+
* <li>remove whitespaces</li>
|
|
2657
|
+
* <li>remove RTL characters</li>
|
|
2658
|
+
* <li>remove short/long format (e.g. "Mio"/"Million")</li>
|
|
2659
|
+
* <li>resolve lenient symbols</li>
|
|
2660
|
+
* </ul>
|
|
2661
|
+
* This means grouping separators which are space characters or RTL characters are not validated.
|
|
2662
|
+
* @param {object} oOptions the format options, relevant are: groupingSeparator, groupingSize, groupingBaseSize and decimalSeparator
|
|
2663
|
+
* @param {boolean} bScientificNotation is scientific notation, e.g. "1.234e+1"
|
|
2664
|
+
* @returns {boolean} true if the grouping is done correctly, e.g. "1.23" is not grouped correctly for grouping separator "." and groupingSize 3
|
|
2665
|
+
* @private
|
|
2666
|
+
*/
|
|
2667
|
+
NumberFormat.prototype._checkGrouping = function (sValueWithGrouping, oOptions, bScientificNotation) {
|
|
2668
|
+
if (oOptions.groupingSeparator && sValueWithGrouping.includes(oOptions.groupingSeparator)) {
|
|
2669
|
+
// All following checks are only done, if the value contains at least one (non-falsy) grouping separator.
|
|
2670
|
+
// The examples below use the German locale:
|
|
2671
|
+
// groupingSeparator: '.'
|
|
2672
|
+
// decimalSeparator: ','
|
|
2673
|
+
// groupingSize: 3
|
|
2674
|
+
|
|
2675
|
+
// remove leading minus sign, it is irrelevant for grouping check
|
|
2676
|
+
// "-123.456" -> "123.456"
|
|
2677
|
+
sValueWithGrouping = sValueWithGrouping.replace(/^-/, "");
|
|
2678
|
+
|
|
2679
|
+
// remove leading zeros before non-zero digits
|
|
2680
|
+
// "001.234" -> "1.234"
|
|
2681
|
+
// "0.234" -> "0.234"
|
|
2682
|
+
sValueWithGrouping = sValueWithGrouping.replace(/^0+(\d)/, "$1");
|
|
2683
|
+
|
|
2684
|
+
// if value still starts with 0, or it starts with a grouping separator, it is invalid
|
|
2685
|
+
// e.g. "0.123", ".123" (invalid)
|
|
2686
|
+
if (sValueWithGrouping.startsWith("0") || sValueWithGrouping.startsWith(oOptions.groupingSeparator)) {
|
|
2687
|
+
return false;
|
|
2688
|
+
}
|
|
2689
|
+
|
|
2690
|
+
// remove scientific notation
|
|
2691
|
+
// "1.234e+1" -> "1.234"
|
|
2692
|
+
if (bScientificNotation) {
|
|
2693
|
+
sValueWithGrouping = sValueWithGrouping.replace(/[eE].*/, "");
|
|
2694
|
+
}
|
|
2695
|
+
var bHasDecimalSeparator = sValueWithGrouping.includes(oOptions.decimalSeparator);
|
|
2696
|
+
// Integer types often have identical decimal and grouping separators configured,
|
|
2697
|
+
// therefore we do not remove the decimals part and validate them as if they would not
|
|
2698
|
+
// have decimals
|
|
2699
|
+
if (oOptions.decimalSeparator === oOptions.groupingSeparator) {
|
|
2700
|
+
bHasDecimalSeparator = false;
|
|
2701
|
+
} else if (bHasDecimalSeparator) {
|
|
2702
|
+
// remove decimals part to be able to validate grouping
|
|
2703
|
+
sValueWithGrouping = sValueWithGrouping.split(oOptions.decimalSeparator)[0];
|
|
2704
|
+
}
|
|
2705
|
+
|
|
2706
|
+
// check if decimal and grouping separator were confused.
|
|
2707
|
+
// This check is performed in addition to stricter grouping validation (strictGroupingValidation)
|
|
2708
|
+
// to reduce the confusion between decimal and grouping separator.
|
|
2709
|
+
// e.g. for "de": 1.234567 (is invalid)
|
|
2710
|
+
// Pre-requisites (examples for "de")
|
|
2711
|
+
// * number has exactly one grouping separator, e.g. "1.23"
|
|
2712
|
+
// since there can be only one decimal separator, if there is exactly one grouping
|
|
2713
|
+
// separator they could have been confused
|
|
2714
|
+
// * number has no decimal separator, e.g. 1.23
|
|
2715
|
+
// if there is a decimal separator and a grouping separator present,
|
|
2716
|
+
// there cannot be a confusion
|
|
2717
|
+
var bHasExactlyOneGroupingSeparator = sValueWithGrouping.split(oOptions.groupingSeparator).length === 2;
|
|
2718
|
+
if (bHasExactlyOneGroupingSeparator && !bHasDecimalSeparator) {
|
|
2719
|
+
// find least-significant ("lowest") grouping separator
|
|
2720
|
+
var iLowestGroupingIndex = sValueWithGrouping.length - sValueWithGrouping.lastIndexOf(oOptions.groupingSeparator);
|
|
2721
|
+
var iBaseGroupSize = oOptions.groupingBaseSize || oOptions.groupingSize;
|
|
2722
|
+
// if least-significant grouping size doesn't match grouping base size, the value is invalid
|
|
2723
|
+
// e.g. 12.34 (invalid)
|
|
2724
|
+
if (iLowestGroupingIndex !== iBaseGroupSize + oOptions.groupingSeparator.length) {
|
|
2725
|
+
return false;
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
|
|
2729
|
+
/**
|
|
2730
|
+
* With strictGroupingValidation enabled the behaviour is closer to ABAP, the position
|
|
2731
|
+
* of the grouping separators are validated as well.
|
|
2732
|
+
* e.g. for "de" <code>1.2.3</code> becomes invalid
|
|
2733
|
+
*/
|
|
2734
|
+
if (oOptions.strictGroupingValidation) {
|
|
2735
|
+
if (!this._rGrouping) {
|
|
2736
|
+
this._rGrouping = getGroupingRegExp(oOptions.groupingSeparator, oOptions.groupingSize, oOptions.groupingBaseSize || oOptions.groupingSize);
|
|
2737
|
+
}
|
|
2738
|
+
|
|
2739
|
+
// e.g. for "de" with valid grouping separators at the correct position
|
|
2740
|
+
// rGrouping: /^\d+(?:\.?\d{3})*\.?\d{3}$/
|
|
2741
|
+
// sValueWithGrouping: 123 456.789
|
|
2742
|
+
// 123 456 789
|
|
2743
|
+
// 123.456.789
|
|
2744
|
+
// Note: spaces are just there for visual aid.
|
|
2745
|
+
if (!this._rGrouping.test(sValueWithGrouping)) {
|
|
2746
|
+
return false;
|
|
2747
|
+
}
|
|
2748
|
+
}
|
|
2749
|
+
}
|
|
2750
|
+
return true;
|
|
2751
|
+
};
|
|
2752
|
+
|
|
2753
|
+
/**
|
|
2754
|
+
* Checks whether grouping will be enabled for this instance.
|
|
2755
|
+
*
|
|
2756
|
+
* @private
|
|
2757
|
+
*/
|
|
2758
|
+
NumberFormat.prototype.checkGroupingFormatOptions = function () {
|
|
2759
|
+
if (this.oFormatOptions.groupingEnabled && this.oFormatOptions.groupingSize <= 0) {
|
|
2760
|
+
Log.warning("Grouping is disabled due to non-positive groupingSize set to '" + this.oFormatOptions.groupingSize + "'.");
|
|
2761
|
+
this.oFormatOptions.groupingEnabled = false;
|
|
2762
|
+
}
|
|
2763
|
+
};
|
|
2764
|
+
|
|
2765
|
+
/**
|
|
2766
|
+
* Whether or not the given value is in scientific notation
|
|
2767
|
+
*
|
|
2768
|
+
* @param {string} sValue string value, e.g. "9e+4"
|
|
2769
|
+
* @returns {boolean} <code>true</code> if it is in scientific notation
|
|
2770
|
+
*/
|
|
2771
|
+
function isScientificNotation(sValue) {
|
|
2772
|
+
return sValue.indexOf("e") > 0 || sValue.indexOf("E") > 0;
|
|
2773
|
+
}
|
|
2774
|
+
|
|
2775
|
+
/**
|
|
2776
|
+
* Evaluates if the given number is an integer and returns it.
|
|
2777
|
+
* Otherwise returns <code>undefined</code>
|
|
2778
|
+
*
|
|
2779
|
+
* @param {string} sValue string value, e.g. "9e+4" or "1.2345e+25"
|
|
2780
|
+
* @returns {int} if value can be parsed to integer e.g. 90000, <code>undefined</code> otherwise
|
|
2781
|
+
*/
|
|
2782
|
+
function getInteger(sValue) {
|
|
2783
|
+
// when resolving the e-notation check if there is still a dot character present and after the dot character there are no zeros
|
|
2784
|
+
var sResolvedENotation = NumberFormat._shiftDecimalPoint(sValue, 0);
|
|
2785
|
+
if (sResolvedENotation.indexOf(".") > 0 && !rOnlyZeros.test(sResolvedENotation.split(".")[1])) {
|
|
2786
|
+
return undefined;
|
|
2787
|
+
}
|
|
2788
|
+
var fFloat = parseFloat(sResolvedENotation);
|
|
2789
|
+
var sFloat = "" + fFloat;
|
|
2790
|
+
|
|
2791
|
+
// parseFloat() still produces the scientific notation output for bigger values such
|
|
2792
|
+
// as "1.2345e+25".
|
|
2793
|
+
// This conversion is required because parseInt() cannot handle scientific notation with
|
|
2794
|
+
// the mantissa being a floating point number, e.g. "1.2345e+25"
|
|
2795
|
+
if (isScientificNotation(sFloat)) {
|
|
2796
|
+
// retrieve the string value from the given float number
|
|
2797
|
+
// "1.2345e+25" becomes "12345000000000000000000000"
|
|
2798
|
+
sFloat = NumberFormat._shiftDecimalPoint(sFloat, 0);
|
|
2799
|
+
}
|
|
2800
|
+
var iInt = parseInt(sFloat);
|
|
2801
|
+
if (iInt !== fFloat) {
|
|
2802
|
+
return undefined;
|
|
2803
|
+
}
|
|
2804
|
+
return iInt;
|
|
2805
|
+
}
|
|
2806
|
+
|
|
2807
|
+
/**
|
|
2808
|
+
* Rounds the given value by the given number of fraction digits based on the given rounding mode.
|
|
2809
|
+
*
|
|
2810
|
+
* @param {number|string} vValue
|
|
2811
|
+
* The number to be rounded, may be a string or a number; has to be of type number if a custom rounding function
|
|
2812
|
+
* is used
|
|
2813
|
+
* @param {int|string} iMaxFractionDigits
|
|
2814
|
+
* The maximum number of fraction digits
|
|
2815
|
+
* @param {sap.ui.core.format.NumberFormat.RoundingMode|function(number,int):number} vRoundingMode
|
|
2816
|
+
* The rounding mode or a custom function for rounding which is called with the number and the number of decimal
|
|
2817
|
+
* digits that should be reserved; <b>using a function is deprecated since 1.121.0</b>; string based numbers are
|
|
2818
|
+
* not rounded via this custom function.
|
|
2819
|
+
* @returns {number|string}
|
|
2820
|
+
* The rounded value; the returned type is the same as the type of the given <code>vValue</code>
|
|
2821
|
+
*/
|
|
2822
|
+
function rounding(vValue, iMaxFractionDigits, vRoundingMode) {
|
|
2823
|
+
vRoundingMode = vRoundingMode || NumberFormat.RoundingMode.HALF_AWAY_FROM_ZERO;
|
|
2824
|
+
iMaxFractionDigits = parseInt(iMaxFractionDigits);
|
|
2825
|
+
|
|
2826
|
+
// only round if it is required (number of fraction digits is bigger than the maxFractionDigits option)
|
|
2827
|
+
var sValue = "" + vValue;
|
|
2828
|
+
if (!isScientificNotation(sValue)) {
|
|
2829
|
+
var iIndexOfPoint = sValue.indexOf(".");
|
|
2830
|
+
if (iIndexOfPoint < 0) {
|
|
2831
|
+
return vValue;
|
|
2832
|
+
}
|
|
2833
|
+
if (sValue.substring(iIndexOfPoint + 1).length <= iMaxFractionDigits) {
|
|
2834
|
+
if (typeof vValue === "string") {
|
|
2835
|
+
vValue = NumberFormat._shiftDecimalPoint(vValue, 0, true);
|
|
2836
|
+
}
|
|
2837
|
+
return vValue;
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
if (typeof vRoundingMode === "function") {
|
|
2841
|
+
// Support custom function for rounding the number
|
|
2842
|
+
vValue = vRoundingMode(vValue, iMaxFractionDigits);
|
|
2843
|
+
} else {
|
|
2844
|
+
// The NumberFormat.RoundingMode had all values in lower case before and later changed all values to upper case
|
|
2845
|
+
// to match the key according to the UI5 guideline for defining enum. Therefore it's needed to support both
|
|
2846
|
+
// lower and upper cases. Here checks whether the value has only lower case letters and converts it all to upper
|
|
2847
|
+
// case if so.
|
|
2848
|
+
if (vRoundingMode.match(/^[a-z_]+$/)) {
|
|
2849
|
+
vRoundingMode = vRoundingMode.toUpperCase();
|
|
2850
|
+
}
|
|
2851
|
+
|
|
2852
|
+
// 1. Move the decimal point to right by maxFactionDigits; e.g. 1.005 with maxFractionDigits 2 => 100.5
|
|
2853
|
+
vValue = NumberFormat._shiftDecimalPoint(vValue, iMaxFractionDigits, true);
|
|
2854
|
+
// 2. Use the rounding function to round the first digit after decimal point; e.g. ceil(100.5) => 101
|
|
2855
|
+
vValue = mRoundingFunction[vRoundingMode](vValue);
|
|
2856
|
+
// 3. Finally move the decimal point back to the original position; e.g. by 2 digits => 1.01
|
|
2857
|
+
vValue = NumberFormat._shiftDecimalPoint(vValue, -iMaxFractionDigits, true);
|
|
2858
|
+
if (typeof vValue === "string") {
|
|
2859
|
+
vValue = vValue.replace(rRemoveMinusFromZero, "$1");
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2862
|
+
return vValue;
|
|
2863
|
+
}
|
|
2864
|
+
function quote(sRegex) {
|
|
2865
|
+
return sRegex.replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
|
|
2866
|
+
}
|
|
2867
|
+
function getDecimals(fValue, iPrecision) {
|
|
2868
|
+
var iIntegerDigits = Math.floor(Math.log(Math.abs(fValue)) / Math.LN10);
|
|
2869
|
+
return Math.max(0, iPrecision - iIntegerDigits - 1);
|
|
2870
|
+
}
|
|
2871
|
+
|
|
2872
|
+
/**
|
|
2873
|
+
* Returns the CLDR code and the number value by checking each pattern and finding the best
|
|
2874
|
+
* match. The best match means most of the unit value matched and the number match is shorter.
|
|
2875
|
+
*
|
|
2876
|
+
* Example input: "12km" matches for the unit postfix "m" and the resulting number value is
|
|
2877
|
+
* "12k" while the unit postfix "km" results in "12". Since unit postfix "km" returns a shorter
|
|
2878
|
+
* result it is considered the best match.
|
|
2879
|
+
*
|
|
2880
|
+
* Note: the CLDR data is not distinct in its patterns.
|
|
2881
|
+
* For example "100 c" could be in "en_gb" either 100 units of "volume-cup" or
|
|
2882
|
+
* "duration-century" both having the same pattern "{0} c". Therefore best matches will be
|
|
2883
|
+
* returned in an array.
|
|
2884
|
+
*
|
|
2885
|
+
* @param {object} mUnitPatterns The unit patterns
|
|
2886
|
+
* @param {string} sValue The given value
|
|
2887
|
+
* @param {boolean} bShowNumber Whether the number is shown
|
|
2888
|
+
* @param {string} sLanguageTag The language tag of the locale for language dependent processing
|
|
2889
|
+
* @return {{cldrCode: string[], numberValue: (string|undefined)}}
|
|
2890
|
+
* An object containing the unit codes and the number value
|
|
2891
|
+
*/
|
|
2892
|
+
function parseNumberAndUnit(mUnitPatterns, sValue, bShowNumber, sLanguageTag) {
|
|
2893
|
+
var bContainsNumber,
|
|
2894
|
+
sKey,
|
|
2895
|
+
sNumber,
|
|
2896
|
+
iNumberPatternIndex,
|
|
2897
|
+
sPostfix,
|
|
2898
|
+
sPostfixLowerCase,
|
|
2899
|
+
sPrefix,
|
|
2900
|
+
sPrefixLowerCase,
|
|
2901
|
+
sUnitCode,
|
|
2902
|
+
sUnitPattern,
|
|
2903
|
+
sUnitPatternLowerCase,
|
|
2904
|
+
oBestMatch = {
|
|
2905
|
+
numberValue: undefined,
|
|
2906
|
+
cldrCode: []
|
|
2907
|
+
},
|
|
2908
|
+
aCaseInsensitiveMatches = [],
|
|
2909
|
+
bCaseSensitive = true,
|
|
2910
|
+
bPatternMatchWasCaseSensitive = true,
|
|
2911
|
+
iShortestNumberPartLength = Number.POSITIVE_INFINITY,
|
|
2912
|
+
bShortestNumberPartWasCaseSensitive = true,
|
|
2913
|
+
sValueLowerCase = sValue.toLocaleLowerCase(sLanguageTag);
|
|
2914
|
+
for (sUnitCode in mUnitPatterns) {
|
|
2915
|
+
for (sKey in mUnitPatterns[sUnitCode]) {
|
|
2916
|
+
//use only unit patterns
|
|
2917
|
+
if (!sKey.startsWith("unitPattern")) {
|
|
2918
|
+
continue;
|
|
2919
|
+
}
|
|
2920
|
+
sUnitPattern = FormatUtils.normalize(mUnitPatterns[sUnitCode][sKey]);
|
|
2921
|
+
|
|
2922
|
+
// IMPORTANT:
|
|
2923
|
+
// To increase performance we are using native string operations instead of regex,
|
|
2924
|
+
// to match the patterns against the input.
|
|
2925
|
+
//
|
|
2926
|
+
// sample input: e.g. "mi 12 tsd. ms²"
|
|
2927
|
+
// unit pattern: e.g. "mi {0} ms²"
|
|
2928
|
+
|
|
2929
|
+
// The smallest resulting number (String length) will be the best match
|
|
2930
|
+
iNumberPatternIndex = sUnitPattern.indexOf("{0}");
|
|
2931
|
+
bContainsNumber = iNumberPatternIndex > -1;
|
|
2932
|
+
if (bContainsNumber && !bShowNumber) {
|
|
2933
|
+
sUnitPattern = sUnitPattern.replace("{0}", "").trim();
|
|
2934
|
+
bContainsNumber = false;
|
|
2935
|
+
}
|
|
2936
|
+
sNumber = undefined;
|
|
2937
|
+
bCaseSensitive = true;
|
|
2938
|
+
if (bContainsNumber) {
|
|
2939
|
+
sPrefix = sUnitPattern.substring(0, iNumberPatternIndex);
|
|
2940
|
+
sPrefixLowerCase = sPrefix.toLocaleLowerCase(sLanguageTag);
|
|
2941
|
+
sPostfix = sUnitPattern.substring(iNumberPatternIndex + "{0}".length);
|
|
2942
|
+
sPostfixLowerCase = sPostfix.toLocaleLowerCase(sLanguageTag);
|
|
2943
|
+
if (sValue.startsWith(sPrefix) && sValue.endsWith(sPostfix)) {
|
|
2944
|
+
sNumber = sValue.substring(sPrefix.length, sValue.length - sPostfix.length);
|
|
2945
|
+
} else if (sValueLowerCase.startsWith(sPrefixLowerCase) && sValueLowerCase.endsWith(sPostfixLowerCase)) {
|
|
2946
|
+
bCaseSensitive = false;
|
|
2947
|
+
sNumber = sValue.substring(sPrefixLowerCase.length, sValueLowerCase.length - sPostfixLowerCase.length);
|
|
2948
|
+
}
|
|
2949
|
+
if (sNumber) {
|
|
2950
|
+
//get the match with the shortest result.
|
|
2951
|
+
// e.g. 1km -> (.+)m -> "1k" -> length 2
|
|
2952
|
+
// e.g. 1km -> (.+)km -> "1" -> length 1
|
|
2953
|
+
|
|
2954
|
+
if (sNumber.length < iShortestNumberPartLength) {
|
|
2955
|
+
iShortestNumberPartLength = sNumber.length;
|
|
2956
|
+
bShortestNumberPartWasCaseSensitive = bCaseSensitive;
|
|
2957
|
+
oBestMatch.numberValue = sNumber;
|
|
2958
|
+
oBestMatch.cldrCode = [sUnitCode];
|
|
2959
|
+
} else if (sNumber.length === iShortestNumberPartLength && oBestMatch.cldrCode.indexOf(sUnitCode) === -1) {
|
|
2960
|
+
if (bCaseSensitive && !bShortestNumberPartWasCaseSensitive) {
|
|
2961
|
+
oBestMatch.numberValue = sNumber;
|
|
2962
|
+
oBestMatch.cldrCode = [sUnitCode];
|
|
2963
|
+
bShortestNumberPartWasCaseSensitive = true;
|
|
2964
|
+
} else if (bCaseSensitive || !bShortestNumberPartWasCaseSensitive) {
|
|
2965
|
+
//ambiguous unit (en locale)
|
|
2966
|
+
// e.g. 100 c -> (.+) c -> duration-century
|
|
2967
|
+
// e.g. 100 c -> (.+) c -> volume-cup
|
|
2968
|
+
oBestMatch.cldrCode.push(sUnitCode);
|
|
2969
|
+
}
|
|
2970
|
+
}
|
|
2971
|
+
}
|
|
2972
|
+
} else {
|
|
2973
|
+
sUnitPatternLowerCase = sUnitPattern.toLocaleLowerCase(sLanguageTag);
|
|
2974
|
+
if (sUnitPattern === sValue || sUnitPatternLowerCase === sValueLowerCase) {
|
|
2975
|
+
if (bShowNumber) {
|
|
2976
|
+
//for units which do not have a number representation, get the number from the pattern
|
|
2977
|
+
if (sKey.endsWith("-zero")) {
|
|
2978
|
+
sNumber = "0";
|
|
2979
|
+
} else if (sKey.endsWith("-one")) {
|
|
2980
|
+
sNumber = "1";
|
|
2981
|
+
} else if (sKey.endsWith("-two")) {
|
|
2982
|
+
sNumber = "2";
|
|
2983
|
+
}
|
|
2984
|
+
if (sUnitPattern === sValue) {
|
|
2985
|
+
oBestMatch.numberValue = sNumber;
|
|
2986
|
+
oBestMatch.cldrCode = [sUnitCode];
|
|
2987
|
+
return oBestMatch;
|
|
2988
|
+
} else if (!oBestMatch.cldrCode.includes(sUnitCode)) {
|
|
2989
|
+
bPatternMatchWasCaseSensitive = false;
|
|
2990
|
+
oBestMatch.numberValue = sNumber;
|
|
2991
|
+
oBestMatch.cldrCode.push(sUnitCode);
|
|
2992
|
+
}
|
|
2993
|
+
} else if (oBestMatch.cldrCode.indexOf(sUnitCode) === -1) {
|
|
2994
|
+
if (sUnitPattern === sValue) {
|
|
2995
|
+
oBestMatch.cldrCode.push(sUnitCode);
|
|
2996
|
+
} else if (!aCaseInsensitiveMatches.includes(sUnitCode)) {
|
|
2997
|
+
aCaseInsensitiveMatches.push(sUnitCode);
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
}
|
|
3004
|
+
if ((!bShortestNumberPartWasCaseSensitive || !bPatternMatchWasCaseSensitive) && oBestMatch.cldrCode.length > 1) {
|
|
3005
|
+
oBestMatch.numberValue = undefined;
|
|
3006
|
+
}
|
|
3007
|
+
if (!bShowNumber && !oBestMatch.cldrCode.length) {
|
|
3008
|
+
oBestMatch.cldrCode = aCaseInsensitiveMatches;
|
|
3009
|
+
}
|
|
3010
|
+
return oBestMatch;
|
|
3011
|
+
}
|
|
3012
|
+
|
|
3013
|
+
/**
|
|
3014
|
+
* Identify the longest match between a sub string of <code>sValue</code>
|
|
3015
|
+
* and one of the values of the <code>mCollection</code> map.
|
|
3016
|
+
*
|
|
3017
|
+
* @param {string} sValue
|
|
3018
|
+
* The string value which is checked for all currency codes/symbols
|
|
3019
|
+
* @param {Object<string, string>} mCollection
|
|
3020
|
+
* An object mapping a currency code to a either a currency symbol or the currency code itself
|
|
3021
|
+
* @param {boolean} bCaseInsensitive Whether case insensitive matches are allowed
|
|
3022
|
+
* @return {{code: string, recognizedCurrency: string, symbol: string}}
|
|
3023
|
+
* An object with the code, the recognized currency and the symbol found in the given value;
|
|
3024
|
+
* an empty object in case of either conflicting case insensitive matches, or no match
|
|
3025
|
+
*/
|
|
3026
|
+
function findLongestMatch(sValue, mCollection, bCaseInsensitive) {
|
|
3027
|
+
var sCode,
|
|
3028
|
+
sCurCode,
|
|
3029
|
+
sCurSymbol,
|
|
3030
|
+
sCurSymbolToUpperCase,
|
|
3031
|
+
iIndex,
|
|
3032
|
+
sLanguageTag,
|
|
3033
|
+
sRecognizedCurrency,
|
|
3034
|
+
sValueSubStr,
|
|
3035
|
+
bDuplicate = false,
|
|
3036
|
+
bExactMatch = false,
|
|
3037
|
+
sSymbol = "";
|
|
3038
|
+
for (sCurCode in mCollection) {
|
|
3039
|
+
sCurSymbol = mCollection[sCurCode];
|
|
3040
|
+
if (!sCurSymbol) {
|
|
3041
|
+
continue;
|
|
3042
|
+
}
|
|
3043
|
+
sCurSymbol = FormatUtils.normalize(sCurSymbol);
|
|
3044
|
+
if (sValue.indexOf(sCurSymbol) >= 0 && sSymbol.length <= sCurSymbol.length) {
|
|
3045
|
+
sCode = sCurCode;
|
|
3046
|
+
bDuplicate = false;
|
|
3047
|
+
bExactMatch = true;
|
|
3048
|
+
sSymbol = sCurSymbol;
|
|
3049
|
+
sRecognizedCurrency = sCurSymbol;
|
|
3050
|
+
} else if (bCaseInsensitive) {
|
|
3051
|
+
sLanguageTag = Localization.getLanguageTag().toString();
|
|
3052
|
+
sCurSymbolToUpperCase = sCurSymbol.toLocaleUpperCase(sLanguageTag);
|
|
3053
|
+
iIndex = sValue.toLocaleUpperCase(sLanguageTag).indexOf(sCurSymbolToUpperCase);
|
|
3054
|
+
if (iIndex >= 0) {
|
|
3055
|
+
if (sSymbol.length === sCurSymbol.length && !bExactMatch) {
|
|
3056
|
+
bDuplicate = true;
|
|
3057
|
+
} else if (sSymbol.length < sCurSymbol.length) {
|
|
3058
|
+
sValueSubStr = sValue.substring(iIndex, iIndex + sCurSymbol.length);
|
|
3059
|
+
if (sValueSubStr.toLocaleUpperCase(sLanguageTag) === sCurSymbolToUpperCase) {
|
|
3060
|
+
sCode = sCurCode;
|
|
3061
|
+
bDuplicate = false;
|
|
3062
|
+
bExactMatch = false;
|
|
3063
|
+
sSymbol = sCurSymbol;
|
|
3064
|
+
sRecognizedCurrency = sValueSubStr;
|
|
3065
|
+
}
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
3070
|
+
if (bDuplicate || !sCode) {
|
|
3071
|
+
return {};
|
|
3072
|
+
}
|
|
3073
|
+
return {
|
|
3074
|
+
code: sCode,
|
|
3075
|
+
recognizedCurrency: sRecognizedCurrency,
|
|
3076
|
+
symbol: sSymbol
|
|
3077
|
+
};
|
|
3078
|
+
}
|
|
3079
|
+
|
|
3080
|
+
/**
|
|
3081
|
+
* Parses number and currency.
|
|
3082
|
+
*
|
|
3083
|
+
* Search for the currency symbol first, looking for the longest match. In case no currency
|
|
3084
|
+
* symbol is found, search for a three letter currency code.
|
|
3085
|
+
*
|
|
3086
|
+
* @param {object} oConfig
|
|
3087
|
+
* @param {string} oConfig.value the string value to be parse
|
|
3088
|
+
* @param {object} oConfig.currencySymbols the list of currency symbols to respect during parsing
|
|
3089
|
+
* @param {object} oConfig.customCurrencyCodes the list of currency codes used for parsing in case no symbol was found in the value string
|
|
3090
|
+
* @param {object} oConfig.duplicatedSymbols a list of all duplicated symbols;
|
|
3091
|
+
* In case oFormatOptions.currencyCode is set to false and the value string contains a duplicated symbol,
|
|
3092
|
+
* the value is not parsable. The result will be a parsed number and <code>undefined</code> for the currency.
|
|
3093
|
+
* @param {boolean} oConfig.customCurrenciesAvailable a flag to mark if custom currencies are available on the instance
|
|
3094
|
+
*
|
|
3095
|
+
* @private
|
|
3096
|
+
* @returns {object|undefined} returns object containing numberValue and currencyCode or undefined
|
|
3097
|
+
*/
|
|
3098
|
+
function parseNumberAndCurrency(oConfig) {
|
|
3099
|
+
var aIsoMatches,
|
|
3100
|
+
sValue = oConfig.value;
|
|
3101
|
+
|
|
3102
|
+
// Search for known symbols (longest match)
|
|
3103
|
+
// no distinction between default and custom currencies
|
|
3104
|
+
var oMatch = findLongestMatch(sValue, oConfig.currencySymbols);
|
|
3105
|
+
|
|
3106
|
+
// Search for currency code
|
|
3107
|
+
if (!oMatch.code) {
|
|
3108
|
+
// before falling back to the default regex for ISO codes we check the
|
|
3109
|
+
// codes for custom currencies (if defined)
|
|
3110
|
+
oMatch = findLongestMatch(sValue, oConfig.customCurrencyCodes, true);
|
|
3111
|
+
if (!oMatch.code && !oConfig.customCurrenciesAvailable) {
|
|
3112
|
+
// Match 3-letter iso code
|
|
3113
|
+
aIsoMatches = sValue.match(/(^[A-Z]{3}|[A-Z]{3}$)/i);
|
|
3114
|
+
oMatch.code = aIsoMatches && aIsoMatches[0].toLocaleUpperCase(Localization.getLanguageTag().toString());
|
|
3115
|
+
oMatch.recognizedCurrency = aIsoMatches && aIsoMatches[0];
|
|
3116
|
+
}
|
|
3117
|
+
}
|
|
3118
|
+
|
|
3119
|
+
// Remove symbol/code from value
|
|
3120
|
+
if (oMatch.code) {
|
|
3121
|
+
var iLastCodeIndex = oMatch.recognizedCurrency.length - 1;
|
|
3122
|
+
var sLastCodeChar = oMatch.recognizedCurrency.charAt(iLastCodeIndex);
|
|
3123
|
+
var iDelimiterPos;
|
|
3124
|
+
var rValidDelimiters = /[\-\s]+/;
|
|
3125
|
+
|
|
3126
|
+
// Check whether last character of matched code is a number
|
|
3127
|
+
if (/\d$/.test(sLastCodeChar)) {
|
|
3128
|
+
// Check whether parse string starts with the matched code
|
|
3129
|
+
if (sValue.startsWith(oMatch.recognizedCurrency)) {
|
|
3130
|
+
iDelimiterPos = iLastCodeIndex + 1;
|
|
3131
|
+
// \s matching any whitespace character including
|
|
3132
|
+
// non-breaking ws and invisible non-breaking ws
|
|
3133
|
+
if (!rValidDelimiters.test(sValue.charAt(iDelimiterPos))) {
|
|
3134
|
+
return undefined;
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
// Check whether first character of matched code is a number
|
|
3138
|
+
} else if (/^\d/.test(oMatch.recognizedCurrency)) {
|
|
3139
|
+
// Check whether parse string ends with the matched code
|
|
3140
|
+
if (sValue.endsWith(oMatch.recognizedCurrency)) {
|
|
3141
|
+
iDelimiterPos = sValue.indexOf(oMatch.recognizedCurrency) - 1;
|
|
3142
|
+
if (!rValidDelimiters.test(sValue.charAt(iDelimiterPos))) {
|
|
3143
|
+
return undefined;
|
|
3144
|
+
}
|
|
3145
|
+
}
|
|
3146
|
+
}
|
|
3147
|
+
sValue = sValue.replace(oMatch.recognizedCurrency, "");
|
|
3148
|
+
}
|
|
3149
|
+
|
|
3150
|
+
// Set currency code to undefined, as the defined custom currencies
|
|
3151
|
+
// contain multiple currencies having the same symbol.
|
|
3152
|
+
var bDuplicatedSymbolFound = false;
|
|
3153
|
+
if (oConfig.duplicatedSymbols && oConfig.duplicatedSymbols[oMatch.symbol]) {
|
|
3154
|
+
oMatch.code = undefined;
|
|
3155
|
+
bDuplicatedSymbolFound = true;
|
|
3156
|
+
Log.error("The parsed currency symbol '" + oMatch.symbol + "' is defined multiple " + "times in custom currencies.Therefore the result is not distinct.");
|
|
3157
|
+
}
|
|
3158
|
+
return {
|
|
3159
|
+
numberValue: sValue,
|
|
3160
|
+
currencyCode: oMatch.code || undefined,
|
|
3161
|
+
duplicatedSymbolFound: bDuplicatedSymbolFound
|
|
3162
|
+
};
|
|
3163
|
+
}
|
|
3164
|
+
export default NumberFormat;
|