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
15U_NAMESPACE_USE
16
17
18namespace BPrivate {
19namespace Libroot {
20
21
22ICUMonetaryData::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
38void
39ICUMonetaryData::Initialize(LocaleMonetaryDataBridge* dataBridge)
40{
41	fPosixLocaleConv = dataBridge->posixLocaleConv;
42}
43
44
45status_t
46ICUMonetaryData::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
188status_t
189ICUMonetaryData::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
221const char*
222ICUMonetaryData::GetLanginfo(int index)
223{
224	switch(index) {
225		case CRNCYSTR:
226			return fCurrencySymbol;
227		default:
228			return "";
229	}
230}
231
232
233int32
234ICUMonetaryData::_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 */
297int32
298ICUMonetaryData::_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