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