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