xref: /haiku/src/system/libroot/add-ons/icu/ICUMonetaryData.cpp (revision a906d0a031e721e2f2ec9d95274103e74a3a774f)
1 /*
2  * Copyright 2010, Oliver Tappe, zooey@hirschkaefer.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include "ICUMonetaryData.h"
8 
9 #include <langinfo.h>
10 #include <limits.h>
11 #include <strings.h>
12 
13 
14 namespace BPrivate {
15 namespace Libroot {
16 
17 
18 ICUMonetaryData::ICUMonetaryData(struct lconv& localeConv)
19 	:
20 	fLocaleConv(localeConv),
21 	fPosixLocaleConv(NULL)
22 {
23 	fLocaleConv.int_curr_symbol = fIntCurrSymbol;
24 	fLocaleConv.currency_symbol = fCurrencySymbol;
25 	fLocaleConv.mon_decimal_point = fDecimalPoint;
26 	fLocaleConv.mon_thousands_sep = fThousandsSep;
27 	fLocaleConv.mon_grouping = fGrouping;
28 	fLocaleConv.positive_sign = fPositiveSign;
29 	fLocaleConv.negative_sign = fNegativeSign;
30 }
31 
32 
33 void
34 ICUMonetaryData::Initialize(LocaleMonetaryDataBridge* dataBridge)
35 {
36 	fPosixLocaleConv = dataBridge->posixLocaleConv;
37 }
38 
39 
40 status_t
41 ICUMonetaryData::SetTo(const Locale& locale, const char* posixLocaleName)
42 {
43 	status_t result = inherited::SetTo(locale, posixLocaleName);
44 
45 	if (result == B_OK) {
46 		UErrorCode icuStatus = U_ZERO_ERROR;
47 		UChar intlCurrencySeparatorChar = CHAR_MAX;
48 		DecimalFormat* currencyFormat = dynamic_cast<DecimalFormat*>(
49 			NumberFormat::createCurrencyInstance(locale, icuStatus));
50 		if (!U_SUCCESS(icuStatus))
51 			return B_UNSUPPORTED;
52 		if (!currencyFormat)
53 			return B_BAD_TYPE;
54 
55 		const DecimalFormatSymbols* formatSymbols
56 			= currencyFormat->getDecimalFormatSymbols();
57 		if (!formatSymbols)
58 			result = B_BAD_DATA;
59 
60 		if (result == B_OK) {
61 			result = _SetLocaleconvEntry(formatSymbols, fDecimalPoint,
62 				DecimalFormatSymbols::kMonetarySeparatorSymbol);
63 		}
64 		if (result == B_OK) {
65 			result = _SetLocaleconvEntry(formatSymbols, fThousandsSep,
66 				DecimalFormatSymbols::kMonetaryGroupingSeparatorSymbol);
67 		}
68 		if (result == B_OK) {
69 			int32 groupingSize = currencyFormat->getGroupingSize();
70 			if (groupingSize < 1)
71 				fGrouping[0] = '\0';
72 			else {
73 				fGrouping[0] = groupingSize;
74 				int32 secondaryGroupingSize
75 					= currencyFormat->getSecondaryGroupingSize();
76 				if (secondaryGroupingSize < 1)
77 					fGrouping[1] = '\0';
78 				else {
79 					fGrouping[1] = secondaryGroupingSize;
80 					fGrouping[2] = '\0';
81 				}
82 			}
83 		}
84 		if (result == B_OK) {
85 			fLocaleConv.int_frac_digits
86 				= currencyFormat->getMinimumFractionDigits();
87 			fLocaleConv.frac_digits
88 				= currencyFormat->getMinimumFractionDigits();
89 		}
90 		if (result == B_OK) {
91 			UnicodeString positivePrefix, positiveSuffix, negativePrefix,
92 				negativeSuffix;
93 			currencyFormat->getPositivePrefix(positivePrefix);
94 			currencyFormat->getPositiveSuffix(positiveSuffix);
95 			currencyFormat->getNegativePrefix(negativePrefix);
96 			currencyFormat->getNegativeSuffix(negativeSuffix);
97 			UnicodeString currencySymbol = formatSymbols->getSymbol(
98 				DecimalFormatSymbols::kCurrencySymbol);
99 			UnicodeString plusSymbol = formatSymbols->getSymbol(
100 				DecimalFormatSymbols::kPlusSignSymbol);
101 			UnicodeString minusSymbol = formatSymbols->getSymbol(
102 				DecimalFormatSymbols::kMinusSignSymbol);
103 
104 			// fill national values
105 			int32 positiveCurrencyFlags = _DetermineCurrencyPosAndSeparator(
106 				positivePrefix, positiveSuffix, plusSymbol, currencySymbol,
107 				intlCurrencySeparatorChar);
108 			fLocaleConv.p_cs_precedes
109 				= (positiveCurrencyFlags & kCsPrecedesFlag) ? 1 : 0;
110 			fLocaleConv.p_sep_by_space
111 				= (positiveCurrencyFlags & kSepBySpaceFlag) ? 1 : 0;
112 
113 			int32 negativeCurrencyFlags = _DetermineCurrencyPosAndSeparator(
114 				negativePrefix, negativeSuffix, minusSymbol, currencySymbol,
115 				intlCurrencySeparatorChar);
116 			fLocaleConv.n_cs_precedes
117 				= (negativeCurrencyFlags & kCsPrecedesFlag) ? 1 : 0;
118 			fLocaleConv.n_sep_by_space
119 				= (negativeCurrencyFlags & kSepBySpaceFlag) ? 1 : 0;
120 
121 			fLocaleConv.p_sign_posn = _DetermineSignPos(positivePrefix,
122 				positiveSuffix, plusSymbol, currencySymbol);
123 			fLocaleConv.n_sign_posn = _DetermineSignPos(negativePrefix,
124 				negativeSuffix, minusSymbol, currencySymbol);
125 			if (fLocaleConv.p_sign_posn == CHAR_MAX) {
126 				// usually there is no positive sign indicator, so we
127 				// adopt the sign pos of the negative sign symbol
128 				fLocaleConv.p_sign_posn = fLocaleConv.n_sign_posn;
129 			}
130 
131 			// copy national to international values, as ICU does not seem
132 			// to have separate info for those
133 			fLocaleConv.int_p_cs_precedes = fLocaleConv.p_cs_precedes;
134 			fLocaleConv.int_p_sep_by_space = fLocaleConv.p_sep_by_space;
135 			fLocaleConv.int_n_cs_precedes = fLocaleConv.n_cs_precedes;
136 			fLocaleConv.int_n_sep_by_space = fLocaleConv.n_sep_by_space;
137 			fLocaleConv.int_p_sign_posn = fLocaleConv.p_sign_posn;
138 			fLocaleConv.int_n_sign_posn = fLocaleConv.n_sign_posn;
139 
140 			// only set sign symbols if they are actually used in any pattern
141 			if (positivePrefix.indexOf(plusSymbol) > -1
142 				|| positiveSuffix.indexOf(plusSymbol) > -1) {
143 				result = _SetLocaleconvEntry(formatSymbols, fPositiveSign,
144 					DecimalFormatSymbols::kPlusSignSymbol);
145 			} else
146 				fPositiveSign[0] = '\0';
147 			if (negativePrefix.indexOf(minusSymbol) > -1
148 				|| negativeSuffix.indexOf(minusSymbol) > -1) {
149 				result = _SetLocaleconvEntry(formatSymbols, fNegativeSign,
150 					DecimalFormatSymbols::kMinusSignSymbol);
151 			} else
152 				fNegativeSign[0] = '\0';
153 		}
154 		if (result == B_OK) {
155 			UnicodeString intlCurrencySymbol = formatSymbols->getSymbol(
156 				DecimalFormatSymbols::kIntlCurrencySymbol);
157 			if (intlCurrencySeparatorChar != CHAR_MAX)
158 				intlCurrencySymbol += intlCurrencySeparatorChar;
159 			else
160 				intlCurrencySymbol += ' ';
161 			result = _ConvertUnicodeStringToLocaleconvEntry(intlCurrencySymbol,
162 				fIntCurrSymbol, skLCBufSize);
163 		}
164 		if (result == B_OK) {
165 			result = _SetLocaleconvEntry(formatSymbols, fCurrencySymbol,
166 				DecimalFormatSymbols::kCurrencySymbol);
167 			if (fCurrencySymbol[0] == '\0') {
168 				// fall back to the international currency symbol
169 				result = _SetLocaleconvEntry(formatSymbols, fCurrencySymbol,
170 					DecimalFormatSymbols::kIntlCurrencySymbol);
171 				fCurrencySymbol[3] = '\0';
172 					// drop separator char that's contained in int-curr-symbol
173 			}
174 		}
175 
176 		delete currencyFormat;
177 	}
178 
179 	return result;
180 }
181 
182 
183 status_t
184 ICUMonetaryData::SetToPosix()
185 {
186 	status_t result = inherited::SetToPosix();
187 
188 	if (result == B_OK) {
189 		strcpy(fDecimalPoint, fPosixLocaleConv->mon_decimal_point);
190 		strcpy(fThousandsSep, fPosixLocaleConv->mon_thousands_sep);
191 		strcpy(fGrouping, fPosixLocaleConv->mon_grouping);
192 		strcpy(fCurrencySymbol, fPosixLocaleConv->currency_symbol);
193 		strcpy(fIntCurrSymbol, fPosixLocaleConv->int_curr_symbol);
194 		strcpy(fPositiveSign, fPosixLocaleConv->positive_sign);
195 		strcpy(fNegativeSign, fPosixLocaleConv->negative_sign);
196 		fLocaleConv.int_frac_digits = fPosixLocaleConv->int_frac_digits;
197 		fLocaleConv.frac_digits = fPosixLocaleConv->frac_digits;
198 		fLocaleConv.p_cs_precedes = fPosixLocaleConv->p_cs_precedes;
199 		fLocaleConv.p_sep_by_space = fPosixLocaleConv->p_sep_by_space;
200 		fLocaleConv.n_cs_precedes = fPosixLocaleConv->n_cs_precedes;
201 		fLocaleConv.n_sep_by_space = fPosixLocaleConv->n_sep_by_space;
202 		fLocaleConv.p_sign_posn = fPosixLocaleConv->p_sign_posn;
203 		fLocaleConv.n_sign_posn = fPosixLocaleConv->n_sign_posn;
204 		fLocaleConv.int_p_cs_precedes = fPosixLocaleConv->int_p_cs_precedes;
205 		fLocaleConv.int_p_sep_by_space = fPosixLocaleConv->int_p_sep_by_space;
206 		fLocaleConv.int_n_cs_precedes = fPosixLocaleConv->int_n_cs_precedes;
207 		fLocaleConv.int_n_sep_by_space = fPosixLocaleConv->int_n_sep_by_space;
208 		fLocaleConv.int_p_sign_posn = fPosixLocaleConv->int_p_sign_posn;
209 		fLocaleConv.int_n_sign_posn = fPosixLocaleConv->int_n_sign_posn;
210 	}
211 
212 	return result;
213 }
214 
215 
216 const char*
217 ICUMonetaryData::GetLanginfo(int index)
218 {
219 	switch(index) {
220 		case CRNCYSTR:
221 			return fCurrencySymbol;
222 		default:
223 			return "";
224 	}
225 }
226 
227 
228 int32
229 ICUMonetaryData::_DetermineCurrencyPosAndSeparator(const UnicodeString& prefix,
230 	const UnicodeString& suffix, const UnicodeString& signSymbol,
231 	const UnicodeString& currencySymbol, UChar& currencySeparatorChar)
232 {
233 	int32 result = 0;
234 
235 	int32 currencySymbolPos = prefix.indexOf(currencySymbol);
236 	if (currencySymbolPos > -1) {
237 		result |= kCsPrecedesFlag;
238 		int32 signSymbolPos = prefix.indexOf(signSymbol);
239 		// if a char is following the currency symbol, we assume it's
240 		// the separator (usually space), but we need to take care to
241 		// skip over the sign symbol, if found
242 		int32 potentialSeparatorPos
243 			= currencySymbolPos + currencySymbol.length();
244 		if (potentialSeparatorPos == signSymbolPos)
245 			potentialSeparatorPos++;
246 		if (prefix.charAt(potentialSeparatorPos) != 0xFFFF) {
247 			// We can't use the actual separator char since this is usually
248 			// 'c2a0' (non-breakable space), which is not available in the
249 			// ASCII charset used/assumed by POSIX lconv. So we use space
250 			// instead.
251 			currencySeparatorChar = ' ';
252 			result |= kSepBySpaceFlag;
253 		}
254 	} else {
255 		currencySymbolPos = suffix.indexOf(currencySymbol);
256 		if (currencySymbolPos > -1) {
257 			int32 signSymbolPos = suffix.indexOf(signSymbol);
258 			// if a char is preceding the currency symbol, we assume
259 			// it's the separator (usually space), but we need to take
260 			// care to skip the sign symbol, if found
261 			int32 potentialSeparatorPos = currencySymbolPos - 1;
262 			if (potentialSeparatorPos == signSymbolPos)
263 				potentialSeparatorPos--;
264 			if (suffix.charAt(potentialSeparatorPos) != 0xFFFF) {
265 				// We can't use the actual separator char since this is usually
266 				// 'c2a0' (non-breakable space), which is not available in the
267 				// ASCII charset used/assumed by POSIX lconv. So we use space
268 				// instead.
269 				currencySeparatorChar = ' ';
270 				result |= kSepBySpaceFlag;
271 			}
272 		}
273 	}
274 
275 	return result;
276 }
277 
278 
279 /*
280  * This method determines the positive/negative sign position value according to
281  * the following map (where '$' indicated the currency symbol, '#' the number
282  * value, and '-' or parantheses the sign symbol):
283  *		($#)	-> 	0
284  *		(#$) 	->	0
285  *		-$# 	->	1
286  *		-#$		->	1
287  *		$-#		->	4
288  *		$#-		->	2
289  *		#$-		->	2
290  *		#-$		->	3
291  */
292 int32
293 ICUMonetaryData::_DetermineSignPos(const UnicodeString& prefix,
294 	const UnicodeString& suffix, const UnicodeString& signSymbol,
295 	const UnicodeString& currencySymbol)
296 {
297 	if (prefix.indexOf(UnicodeString("(", "")) >= 0
298 		&& suffix.indexOf(UnicodeString(")", "")) >= 0)
299 		return kParenthesesAroundCurrencyAndValue;
300 
301 	UnicodeString value("#", "");
302 	UnicodeString prefixNumberSuffixString = prefix + value + suffix;
303 	int32 signSymbolPos = prefixNumberSuffixString.indexOf(signSymbol);
304 	if (signSymbolPos >= 0) {
305 		int32 valuePos = prefixNumberSuffixString.indexOf(value);
306 		int32 currencySymbolPos
307 			= prefixNumberSuffixString.indexOf(currencySymbol);
308 
309 		if (signSymbolPos < valuePos && signSymbolPos < currencySymbolPos)
310 			return kSignPrecedesCurrencyAndValue;
311 		if (signSymbolPos > valuePos && signSymbolPos > currencySymbolPos)
312 			return kSignSucceedsCurrencyAndValue;
313 		if (signSymbolPos == currencySymbolPos - 1)
314 			return kSignImmediatelyPrecedesCurrency;
315 		if (signSymbolPos == currencySymbolPos + currencySymbol.length())
316 			return kSignImmediatelySucceedsCurrency;
317 	}
318 
319 	return CHAR_MAX;
320 }
321 
322 
323 }	// namespace Libroot
324 }	// namespace BPrivate
325