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