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