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
14namespace BPrivate {
15namespace Libroot {
16
17
18ICUMonetaryData::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
34void
35ICUMonetaryData::Initialize(LocaleMonetaryDataBridge* dataBridge)
36{
37	fPosixLocaleConv = dataBridge->posixLocaleConv;
38}
39
40
41status_t
42ICUMonetaryData::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
184status_t
185ICUMonetaryData::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
217const char*
218ICUMonetaryData::GetLanginfo(int index)
219{
220	switch(index) {
221		case CRNCYSTR:
222			return fCurrencySymbol;
223		default:
224			return "";
225	}
226}
227
228
229int32
230ICUMonetaryData::_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 */
293int32
294ICUMonetaryData::_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