blob: dbecf776bfeaba9c9f3c2c622b812e398156a9c6 [file] [log] [blame]
/*
*******************************************************************************
* Copyright (C) 1997-2010, International Business Machines Corporation
* and others. All Rights Reserved.
*******************************************************************************
*/
#include "unicode/utypeinfo.h" // for 'typeid' to work
#include "unicode/rbnf.h"
#if U_HAVE_RBNF
#include "unicode/normlzr.h"
#include "unicode/tblcoll.h"
#include "unicode/uchar.h"
#include "unicode/ucol.h"
#include "unicode/uloc.h"
#include "unicode/unum.h"
#include "unicode/ures.h"
#include "unicode/ustring.h"
#include "unicode/utf16.h"
#include "unicode/udata.h"
#include "nfrs.h"
#include "cmemory.h"
#include "cstring.h"
#include "util.h"
#include "uresimp.h"
// debugging
// #define DEBUG
#ifdef DEBUG
#include "stdio.h"
#endif
#define U_ICUDATA_RBNF U_ICUDATA_NAME U_TREE_SEPARATOR_STRING "rbnf"
static const UChar gPercentPercent[] =
{
0x25, 0x25, 0
}; /* "%%" */
// All urbnf objects are created through openRules, so we init all of the
// Unicode string constants required by rbnf, nfrs, or nfr here.
static const UChar gLenientParse[] =
{
0x25, 0x25, 0x6C, 0x65, 0x6E, 0x69, 0x65, 0x6E, 0x74, 0x2D, 0x70, 0x61, 0x72, 0x73, 0x65, 0x3A, 0
}; /* "%%lenient-parse:" */
static const UChar gSemiColon = 0x003B;
static const UChar gSemiPercent[] =
{
0x3B, 0x25, 0
}; /* ";%" */
#define kSomeNumberOfBitsDiv2 22
#define kHalfMaxDouble (double)(1 << kSomeNumberOfBitsDiv2)
#define kMaxDouble (kHalfMaxDouble * kHalfMaxDouble)
// Temporary workaround - when noParse is true, do noting in parse.
// TODO: We need a real fix - see #6895/#6896
static const char *NO_SPELLOUT_PARSE_LANGUAGES[] = { "ga", NULL };
U_NAMESPACE_BEGIN
UOBJECT_DEFINE_RTTI_IMPLEMENTATION(RuleBasedNumberFormat)
/*
This is a utility class. It does not use ICU's RTTI.
If ICU's RTTI is needed again, you can uncomment the RTTI code and derive from UObject.
Please make sure that intltest passes on Windows in Release mode,
since the string pooling per compilation unit will mess up how RTTI works.
The RTTI code was also removed due to lack of code coverage.
*/
class LocalizationInfo : public UMemory {
protected:
virtual ~LocalizationInfo() {};
uint32_t refcount;
public:
LocalizationInfo() : refcount(0) {}
LocalizationInfo* ref(void) {
++refcount;
return this;
}
LocalizationInfo* unref(void) {
if (refcount && --refcount == 0) {
delete this;
}
return NULL;
}
virtual UBool operator==(const LocalizationInfo* rhs) const;
inline UBool operator!=(const LocalizationInfo* rhs) const { return !operator==(rhs); }
virtual int32_t getNumberOfRuleSets(void) const = 0;
virtual const UChar* getRuleSetName(int32_t index) const = 0;
virtual int32_t getNumberOfDisplayLocales(void) const = 0;
virtual const UChar* getLocaleName(int32_t index) const = 0;
virtual const UChar* getDisplayName(int32_t localeIndex, int32_t ruleIndex) const = 0;
virtual int32_t indexForLocale(const UChar* locale) const;
virtual int32_t indexForRuleSet(const UChar* ruleset) const;
// virtual UClassID getDynamicClassID() const = 0;
// static UClassID getStaticClassID(void);
};
//UOBJECT_DEFINE_ABSTRACT_RTTI_IMPLEMENTATION(LocalizationInfo)
// if both strings are NULL, this returns TRUE
static UBool
streq(const UChar* lhs, const UChar* rhs) {
if (rhs == lhs) {
return TRUE;
}
if (lhs && rhs) {
return u_strcmp(lhs, rhs) == 0;
}
return FALSE;
}
UBool
LocalizationInfo::operator==(const LocalizationInfo* rhs) const {
if (rhs) {
if (this == rhs) {
return TRUE;
}
int32_t rsc = getNumberOfRuleSets();
if (rsc == rhs->getNumberOfRuleSets()) {
for (int i = 0; i < rsc; ++i) {
if (!streq(getRuleSetName(i), rhs->getRuleSetName(i))) {
return FALSE;
}
}
int32_t dlc = getNumberOfDisplayLocales();
if (dlc == rhs->getNumberOfDisplayLocales()) {
for (int i = 0; i < dlc; ++i) {
const UChar* locale = getLocaleName(i);
int32_t ix = rhs->indexForLocale(locale);
// if no locale, ix is -1, getLocaleName returns null, so streq returns false
if (!streq(locale, rhs->getLocaleName(ix))) {
return FALSE;
}
for (int j = 0; j < rsc; ++j) {
if (!streq(getDisplayName(i, j), rhs->getDisplayName(ix, j))) {
return FALSE;
}
}
}
return TRUE;
}
}
}
return FALSE;
}
int32_t
LocalizationInfo::indexForLocale(const UChar* locale) const {
for (int i = 0; i < getNumberOfDisplayLocales(); ++i) {
if (streq(locale, getLocaleName(i))) {
return i;
}
}
return -1;
}
int32_t
LocalizationInfo::indexForRuleSet(const UChar* ruleset) const {
if (ruleset) {
for (int i = 0; i < getNumberOfRuleSets(); ++i) {
if (streq(ruleset, getRuleSetName(i))) {
return i;
}
}
}
return -1;
}
typedef void (*Fn_Deleter)(void*);
class VArray {
void** buf;
int32_t cap;
int32_t size;
Fn_Deleter deleter;
public:
VArray() : buf(NULL), cap(0), size(0), deleter(NULL) {}
VArray(Fn_Deleter del) : buf(NULL), cap(0), size(0), deleter(del) {}
~VArray() {
if (deleter) {
for (int i = 0; i < size; ++i) {
(*deleter)(buf[i]);
}
}
uprv_free(buf);
}
int32_t length() {
return size;
}
void add(void* elem, UErrorCode& status) {
if (U_SUCCESS(status)) {
if (size == cap) {
if (cap == 0) {
cap = 1;
} else if (cap < 256) {
cap *= 2;
} else {
cap += 256;
}
if (buf == NULL) {
buf = (void**)uprv_malloc(cap * sizeof(void*));
} else {
buf = (void**)uprv_realloc(buf, cap * sizeof(void*));
}
if (buf == NULL) {
// if we couldn't realloc, we leak the memory we've already allocated, but we're in deep trouble anyway
status = U_MEMORY_ALLOCATION_ERROR;
return;
}
void* start = &buf[size];
size_t count = (cap - size) * sizeof(void*);
uprv_memset(start, 0, count); // fill with nulls, just because
}
buf[size++] = elem;
}
}
void** release(void) {
void** result = buf;
buf = NULL;
cap = 0;
size = 0;
return result;
}
};
class LocDataParser;
class StringLocalizationInfo : public LocalizationInfo {
UChar* info;
UChar*** data;
int32_t numRuleSets;
int32_t numLocales;
friend class LocDataParser;
StringLocalizationInfo(UChar* i, UChar*** d, int32_t numRS, int32_t numLocs)
: info(i), data(d), numRuleSets(numRS), numLocales(numLocs)
{
}
public:
static StringLocalizationInfo* create(const UnicodeString& info, UParseError& perror, UErrorCode& status);
virtual ~StringLocalizationInfo();
virtual int32_t getNumberOfRuleSets(void) const { return numRuleSets; }
virtual const UChar* getRuleSetName(int32_t index) const;
virtual int32_t getNumberOfDisplayLocales(void) const { return numLocales; }
virtual const UChar* getLocaleName(int32_t index) const;
virtual const UChar* getDisplayName(int32_t localeIndex, int32_t ruleIndex) const;
// virtual UClassID getDynamicClassID() const;
// static UClassID getStaticClassID(void);
private:
void init(UErrorCode& status) const;
};
enum {
OPEN_ANGLE = 0x003c, /* '<' */
CLOSE_ANGLE = 0x003e, /* '>' */
COMMA = 0x002c,
TICK = 0x0027,
QUOTE = 0x0022,
SPACE = 0x0020
};
/**
* Utility for parsing a localization string and returning a StringLocalizationInfo*.
*/
class LocDataParser {
UChar* data;
const UChar* e;
UChar* p;
UChar ch;
UParseError& pe;
UErrorCode& ec;
public:
LocDataParser(UParseError& parseError, UErrorCode& status)
: data(NULL), e(NULL), p(NULL), ch(0xffff), pe(parseError), ec(status) {}
~LocDataParser() {}
/*
* On a successful parse, return a StringLocalizationInfo*, otherwise delete locData, set perror and status,
* and return NULL. The StringLocalizationInfo will adopt locData if it is created.
*/
StringLocalizationInfo* parse(UChar* data, int32_t len);
private:
void inc(void) { ++p; ch = 0xffff; }
UBool checkInc(UChar c) { if (p < e && (ch == c || *p == c)) { inc(); return TRUE; } return FALSE; }
UBool check(UChar c) { return p < e && (ch == c || *p == c); }
void skipWhitespace(void) { while (p < e && uprv_isRuleWhiteSpace(ch != 0xffff ? ch : *p)) inc();}
UBool inList(UChar c, const UChar* list) const {
if (*list == SPACE && uprv_isRuleWhiteSpace(c)) return TRUE;
while (*list && *list != c) ++list; return *list == c;
}
void parseError(const char* msg);
StringLocalizationInfo* doParse(void);
UChar** nextArray(int32_t& requiredLength);
UChar* nextString(void);
};
#ifdef DEBUG
#define ERROR(msg) parseError(msg); return NULL;
#else
#define ERROR(msg) parseError(NULL); return NULL;
#endif
static const UChar DQUOTE_STOPLIST[] = {
QUOTE, 0
};
static const UChar SQUOTE_STOPLIST[] = {
TICK, 0
};
static const UChar NOQUOTE_STOPLIST[] = {
SPACE, COMMA, CLOSE_ANGLE, OPEN_ANGLE, TICK, QUOTE, 0
};
static void
DeleteFn(void* p) {
uprv_free(p);
}
StringLocalizationInfo*
LocDataParser::parse(UChar* _data, int32_t len) {
if (U_FAILURE(ec)) {
if (_data) uprv_free(_data);
return NULL;
}
pe.line = 0;
pe.offset = -1;
pe.postContext[0] = 0;
pe.preContext[0] = 0;
if (_data == NULL) {
ec = U_ILLEGAL_ARGUMENT_ERROR;
return NULL;
}
if (len <= 0) {
ec = U_ILLEGAL_ARGUMENT_ERROR;
uprv_free(_data);
return NULL;
}
data = _data;
e = data + len;
p = _data;
ch = 0xffff;
return doParse();
}
StringLocalizationInfo*
LocDataParser::doParse(void) {
skipWhitespace();
if (!checkInc(OPEN_ANGLE)) {
ERROR("Missing open angle");
} else {
VArray array(DeleteFn);
UBool mightHaveNext = TRUE;
int32_t requiredLength = -1;
while (mightHaveNext) {
mightHaveNext = FALSE;
UChar** elem = nextArray(requiredLength);
skipWhitespace();
UBool haveComma = check(COMMA);
if (elem) {
array.add(elem, ec);
if (haveComma) {
inc();
mightHaveNext = TRUE;
}
} else if (haveComma) {
ERROR("Unexpected character");
}
}
skipWhitespace();
if (!checkInc(CLOSE_ANGLE)) {
if (check(OPEN_ANGLE)) {
ERROR("Missing comma in outer array");
} else {
ERROR("Missing close angle bracket in outer array");
}
}
skipWhitespace();
if (p != e) {
ERROR("Extra text after close of localization data");
}
array.add(NULL, ec);
if (U_SUCCESS(ec)) {
int32_t numLocs = array.length() - 2; // subtract first, NULL
UChar*** result = (UChar***)array.release();
return new StringLocalizationInfo(data, result, requiredLength-2, numLocs); // subtract first, NULL
}
}
ERROR("Unknown error");
}
UChar**
LocDataParser::nextArray(int32_t& requiredLength) {
if (U_FAILURE(ec)) {
return NULL;
}
skipWhitespace();
if (!checkInc(OPEN_ANGLE)) {
ERROR("Missing open angle");
}
VArray array;
UBool mightHaveNext = TRUE;
while (mightHaveNext) {
mightHaveNext = FALSE;
UChar* elem = nextString();
skipWhitespace();
UBool haveComma = check(COMMA);
if (elem) {
array.add(elem, ec);
if (haveComma) {
inc();
mightHaveNext = TRUE;
}
} else if (haveComma) {
ERROR("Unexpected comma");
}
}
skipWhitespace();
if (!checkInc(CLOSE_ANGLE)) {
if (check(OPEN_ANGLE)) {
ERROR("Missing close angle bracket in inner array");
} else {
ERROR("Missing comma in inner array");
}
}
array.add(NULL, ec);
if (U_SUCCESS(ec)) {
if (requiredLength == -1) {
requiredLength = array.length() + 1;
} else if (array.length() != requiredLength) {
ec = U_ILLEGAL_ARGUMENT_ERROR;
ERROR("Array not of required length");
}
return (UChar**)array.release();
}
ERROR("Unknown Error");
}
UChar*
LocDataParser::nextString() {
UChar* result = NULL;
skipWhitespace();
if (p < e) {
const UChar* terminators;
UChar c = *p;
UBool haveQuote = c == QUOTE || c == TICK;
if (haveQuote) {
inc();
terminators = c == QUOTE ? DQUOTE_STOPLIST : SQUOTE_STOPLIST;
} else {
terminators = NOQUOTE_STOPLIST;
}
UChar* start = p;
while (p < e && !inList(*p, terminators)) ++p;
if (p == e) {
ERROR("Unexpected end of data");
}
UChar x = *p;
if (p > start) {
ch = x;
*p = 0x0; // terminate by writing to data
result = start; // just point into data
}
if (haveQuote) {
if (x != c) {
ERROR("Missing matching quote");
} else if (p == start) {
ERROR("Empty string");
}
inc();
} else if (x == OPEN_ANGLE || x == TICK || x == QUOTE) {
ERROR("Unexpected character in string");
}
}
// ok for there to be no next string
return result;
}
void
LocDataParser::parseError(const char* /*str*/) {
if (!data) {
return;
}
const UChar* start = p - U_PARSE_CONTEXT_LEN - 1;
if (start < data) {
start = data;
}
for (UChar* x = p; --x >= start;) {
if (!*x) {
start = x+1;
break;
}
}
const UChar* limit = p + U_PARSE_CONTEXT_LEN - 1;
if (limit > e) {
limit = e;
}
u_strncpy(pe.preContext, start, (int32_t)(p-start));
pe.preContext[p-start] = 0;
u_strncpy(pe.postContext, p, (int32_t)(limit-p));
pe.postContext[limit-p] = 0;
pe.offset = (int32_t)(p - data);
#ifdef DEBUG
fprintf(stderr, "%s at or near character %d: ", str, p-data);
UnicodeString msg;
msg.append(start, p - start);
msg.append((UChar)0x002f); /* SOLIDUS/SLASH */
msg.append(p, limit-p);
msg.append("'");
char buf[128];
int32_t len = msg.extract(0, msg.length(), buf, 128);
if (len >= 128) {
buf[127] = 0;
} else {
buf[len] = 0;
}
fprintf(stderr, "%s\n", buf);
fflush(stderr);
#endif
uprv_free(data);
data = NULL;
p = NULL;
e = NULL;
if (U_SUCCESS(ec)) {
ec = U_PARSE_ERROR;
}
}
//UOBJECT_DEFINE_RTTI_IMPLEMENTATION(StringLocalizationInfo)
StringLocalizationInfo*
StringLocalizationInfo::create(const UnicodeString& info, UParseError& perror, UErrorCode& status) {
if (U_FAILURE(status)) {
return NULL;
}
int32_t len = info.length();
if (len == 0) {
return NULL; // no error;
}
UChar* p = (UChar*)uprv_malloc(len * sizeof(UChar));
if (!p) {
status = U_MEMORY_ALLOCATION_ERROR;
return NULL;
}
info.extract(p, len, status);
if (!U_FAILURE(status)) {
status = U_ZERO_ERROR; // clear warning about non-termination
}
LocDataParser parser(perror, status);
return parser.parse(p, len);
}
StringLocalizationInfo::~StringLocalizationInfo() {
for (UChar*** p = (UChar***)data; *p; ++p) {
// remaining data is simply pointer into our unicode string data.
if (*p) uprv_free(*p);
}
if (data) uprv_free(data);
if (info) uprv_free(info);
}
const UChar*
StringLocalizationInfo::getRuleSetName(int32_t index) const {
if (index >= 0 && index < getNumberOfRuleSets()) {
return data[0][index];
}
return NULL;
}
const UChar*
StringLocalizationInfo::getLocaleName(int32_t index) const {
if (index >= 0 && index < getNumberOfDisplayLocales()) {
return data[index+1][0];
}
return NULL;
}
const UChar*
StringLocalizationInfo::getDisplayName(int32_t localeIndex, int32_t ruleIndex) const {
if (localeIndex >= 0 && localeIndex < getNumberOfDisplayLocales() &&
ruleIndex >= 0 && ruleIndex < getNumberOfRuleSets()) {
return data[localeIndex+1][ruleIndex+1];
}
return NULL;
}
// ----------
RuleBasedNumberFormat::RuleBasedNumberFormat(const UnicodeString& description,
const UnicodeString& locs,
const Locale& alocale, UParseError& perror, UErrorCode& status)
: ruleSets(NULL)
, defaultRuleSet(NULL)
, locale(alocale)
, collator(NULL)
, decimalFormatSymbols(NULL)
, lenient(FALSE)
, lenientParseRules(NULL)
, localizations(NULL)
, noParse(FALSE) //TODO: to be removed after #6895
{
LocalizationInfo* locinfo = StringLocalizationInfo::create(locs, perror, status);
init(description, locinfo, perror, status);
}
RuleBasedNumberFormat::RuleBasedNumberFormat(const UnicodeString& description,
const UnicodeString& locs,
UParseError& perror, UErrorCode& status)
: ruleSets(NULL)
, defaultRuleSet(NULL)
, locale(Locale::getDefault())
, collator(NULL)
, decimalFormatSymbols(NULL)
, lenient(FALSE)
, lenientParseRules(NULL)
, localizations(NULL)
, noParse(FALSE) //TODO: to be removed after #6895
{
LocalizationInfo* locinfo = StringLocalizationInfo::create(locs, perror, status);
init(description, locinfo, perror, status);
}
RuleBasedNumberFormat::RuleBasedNumberFormat(const UnicodeString& description,
LocalizationInfo* info,
const Locale& alocale, UParseError& perror, UErrorCode& status)
: ruleSets(NULL)
, defaultRuleSet(NULL)
, locale(alocale)
, collator(NULL)
, decimalFormatSymbols(NULL)
, lenient(FALSE)
, lenientParseRules(NULL)
, localizations(NULL)
, noParse(FALSE) //TODO: to be removed after #6895
{
init(description, info, perror, status);
}
RuleBasedNumberFormat::RuleBasedNumberFormat(const UnicodeString& description,
UParseError& perror,
UErrorCode& status)
: ruleSets(NULL)
, defaultRuleSet(NULL)
, locale(Locale::getDefault())
, collator(NULL)
, decimalFormatSymbols(NULL)
, lenient(FALSE)
, lenientParseRules(NULL)
, localizations(NULL)
, noParse(FALSE) //TODO: to be removed after #6895
{
init(description, NULL, perror, status);
}
RuleBasedNumberFormat::RuleBasedNumberFormat(const UnicodeString& description,
const Locale& aLocale,
UParseError& perror,
UErrorCode& status)
: ruleSets(NULL)
, defaultRuleSet(NULL)
, locale(aLocale)
, collator(NULL)
, decimalFormatSymbols(NULL)
, lenient(FALSE)
, lenientParseRules(NULL)
, localizations(NULL)
, noParse(FALSE) //TODO: to be removed after #6895
{
init(description, NULL, perror, status);
}
RuleBasedNumberFormat::RuleBasedNumberFormat(URBNFRuleSetTag tag, const Locale& alocale, UErrorCode& status)
: ruleSets(NULL)
, defaultRuleSet(NULL)
, locale(alocale)
, collator(NULL)
, decimalFormatSymbols(NULL)
, lenient(FALSE)
, lenientParseRules(NULL)
, localizations(NULL)
{
if (U_FAILURE(status)) {
return;
}
const char* rules_tag = "RBNFRules";
const char* fmt_tag = "";
switch (tag) {
case URBNF_SPELLOUT: fmt_tag = "SpelloutRules"; break;
case URBNF_ORDINAL: fmt_tag = "OrdinalRules"; break;
case URBNF_DURATION: fmt_tag = "DurationRules"; break;
case URBNF_NUMBERING_SYSTEM: fmt_tag = "NumberingSystemRules"; break;
default: status = U_ILLEGAL_ARGUMENT_ERROR; return;
}
// TODO: read localization info from resource
LocalizationInfo* locinfo = NULL;
int32_t len = 0;
UResourceBundle* nfrb = ures_open(U_ICUDATA_RBNF, locale.getName(), &status);
if (U_SUCCESS(status)) {
setLocaleIDs(ures_getLocaleByType(nfrb, ULOC_VALID_LOCALE, &status),
ures_getLocaleByType(nfrb, ULOC_ACTUAL_LOCALE, &status));
UResourceBundle* rbnfRules = ures_getByKeyWithFallback(nfrb, rules_tag, NULL, &status);
if (U_FAILURE(status)) {
ures_close(nfrb);
}
UResourceBundle* ruleSets = ures_getByKeyWithFallback(rbnfRules, fmt_tag, NULL, &status);
if (U_FAILURE(status)) {
ures_close(rbnfRules);
ures_close(nfrb);
return;
}
UnicodeString desc;
while (ures_hasNext(ruleSets)) {
const UChar* currentString = ures_getNextString(ruleSets,&len,NULL,&status);
desc.append(currentString);
}
UParseError perror;
init (desc, locinfo, perror, status);
//TODO: we need a real fix - see #6895 / #6896
noParse = FALSE;
if (tag == URBNF_SPELLOUT) {
const char *lang = alocale.getLanguage();
for (int32_t i = 0; NO_SPELLOUT_PARSE_LANGUAGES[i] != NULL; i++) {
if (uprv_strcmp(lang, NO_SPELLOUT_PARSE_LANGUAGES[i]) == 0) {
noParse = TRUE;
break;
}
}
}
//TODO: end
ures_close(ruleSets);
ures_close(rbnfRules);
}
ures_close(nfrb);
}
RuleBasedNumberFormat::RuleBasedNumberFormat(const RuleBasedNumberFormat& rhs)
: NumberFormat(rhs)
, ruleSets(NULL)
, defaultRuleSet(NULL)
, locale(rhs.locale)
, collator(NULL)
, decimalFormatSymbols(NULL)
, lenient(FALSE)
, lenientParseRules(NULL)
, localizations(NULL)
{
this->operator=(rhs);
}
// --------
RuleBasedNumberFormat&
RuleBasedNumberFormat::operator=(const RuleBasedNumberFormat& rhs)
{
UErrorCode status = U_ZERO_ERROR;
dispose();
locale = rhs.locale;
lenient = rhs.lenient;
UnicodeString rules = rhs.getRules();
UParseError perror;
init(rules, rhs.localizations ? rhs.localizations->ref() : NULL, perror, status);
//TODO: remove below when we fix the parse bug - See #6895 / #6896
noParse = rhs.noParse;
return *this;
}
RuleBasedNumberFormat::~RuleBasedNumberFormat()
{
dispose();
}
Format*
RuleBasedNumberFormat::clone(void) const
{
RuleBasedNumberFormat * result = NULL;
UnicodeString rules = getRules();
UErrorCode status = U_ZERO_ERROR;
UParseError perror;
result = new RuleBasedNumberFormat(rules, localizations, locale, perror, status);
/* test for NULL */
if (result == 0) {
status = U_MEMORY_ALLOCATION_ERROR;
return 0;
}
if (U_FAILURE(status)) {
delete result;
result = 0;
} else {
result->lenient = lenient;
//TODO: remove below when we fix the parse bug - See #6895 / #6896
result->noParse = noParse;
}
return result;
}
UBool
RuleBasedNumberFormat::operator==(const Format& other) const
{
if (this == &other) {
return TRUE;
}
if (typeid(*this) == typeid(other)) {
const RuleBasedNumberFormat& rhs = (const RuleBasedNumberFormat&)other;
if (locale == rhs.locale &&
lenient == rhs.lenient &&
(localizations == NULL
? rhs.localizations == NULL
: (rhs.localizations == NULL
? FALSE
: *localizations == rhs.localizations))) {
NFRuleSet** p = ruleSets;
NFRuleSet** q = rhs.ruleSets;
if (p == NULL) {
return q == NULL;
} else if (q == NULL) {
return FALSE;
}
while (*p && *q && (**p == **q)) {
++p;
++q;
}
return *q == NULL && *p == NULL;
}
}
return FALSE;
}
UnicodeString
RuleBasedNumberFormat::getRules() const
{
UnicodeString result;
if (ruleSets != NULL) {
for (NFRuleSet** p = ruleSets; *p; ++p) {
(*p)->appendRules(result);
}
}
return result;
}
UnicodeString
RuleBasedNumberFormat::getRuleSetName(int32_t index) const
{
if (localizations) {
UnicodeString string(TRUE, localizations->getRuleSetName(index), (int32_t)-1);
return string;
} else if (ruleSets) {
UnicodeString result;
for (NFRuleSet** p = ruleSets; *p; ++p) {
NFRuleSet* rs = *p;
if (rs->isPublic()) {
if (--index == -1) {
rs->getName(result);
return result;
}
}
}
}
UnicodeString empty;
return empty;
}
int32_t
RuleBasedNumberFormat::getNumberOfRuleSetNames() const
{
int32_t result = 0;
if (localizations) {
result = localizations->getNumberOfRuleSets();
} else if (ruleSets) {
for (NFRuleSet** p = ruleSets; *p; ++p) {
if ((**p).isPublic()) {
++result;
}
}
}
return result;
}
int32_t
RuleBasedNumberFormat::getNumberOfRuleSetDisplayNameLocales(void) const {
if (localizations) {
return localizations->getNumberOfDisplayLocales();
}
return 0;
}
Locale
RuleBasedNumberFormat::getRuleSetDisplayNameLocale(int32_t index, UErrorCode& status) const {
if (U_FAILURE(status)) {
return Locale("");
}
if (localizations && index >= 0 && index < localizations->getNumberOfDisplayLocales()) {
UnicodeString name(TRUE, localizations->getLocaleName(index), -1);
char buffer[64];
int32_t cap = name.length() + 1;
char* bp = buffer;
if (cap > 64) {
bp = (char *)uprv_malloc(cap);
if (bp == NULL) {
status = U_MEMORY_ALLOCATION_ERROR;
return Locale("");
}
}
name.extract(0, name.length(), bp, cap, UnicodeString::kInvariant);
Locale retLocale(bp);
if (bp != buffer) {
uprv_free(bp);
}
return retLocale;
}
status = U_ILLEGAL_ARGUMENT_ERROR;
Locale retLocale;
return retLocale;
}
UnicodeString
RuleBasedNumberFormat::getRuleSetDisplayName(int32_t index, const Locale& localeParam) {
if (localizations && index >= 0 && index < localizations->getNumberOfRuleSets()) {
UnicodeString localeName(localeParam.getBaseName(), -1, UnicodeString::kInvariant);
int32_t len = localeName.length();
UChar* localeStr = localeName.getBuffer(len + 1);
while (len >= 0) {
localeStr[len] = 0;
int32_t ix = localizations->indexForLocale(localeStr);
if (ix >= 0) {
UnicodeString name(TRUE, localizations->getDisplayName(ix, index), -1);
return name;
}
// trim trailing portion, skipping over ommitted sections
do { --len;} while (len > 0 && localeStr[len] != 0x005f); // underscore
while (len > 0 && localeStr[len-1] == 0x005F) --len;
}
UnicodeString name(TRUE, localizations->getRuleSetName(index), -1);
return name;
}
UnicodeString bogus;
bogus.setToBogus();
return bogus;
}
UnicodeString
RuleBasedNumberFormat::getRuleSetDisplayName(const UnicodeString& ruleSetName, const Locale& localeParam) {
if (localizations) {
UnicodeString rsn(ruleSetName);
int32_t ix = localizations->indexForRuleSet(rsn.getTerminatedBuffer());
return getRuleSetDisplayName(ix, localeParam);
}
UnicodeString bogus;
bogus.setToBogus();
return bogus;
}
NFRuleSet*
RuleBasedNumberFormat::findRuleSet(const UnicodeString& name, UErrorCode& status) const
{
if (U_SUCCESS(status) && ruleSets) {
for (NFRuleSet** p = ruleSets; *p; ++p) {
NFRuleSet* rs = *p;
if (rs->isNamed(name)) {
return rs;
}
}
status = U_ILLEGAL_ARGUMENT_ERROR;
}
return NULL;
}
UnicodeString&
RuleBasedNumberFormat::format(int32_t number,
UnicodeString& toAppendTo,
FieldPosition& /* pos */) const
{
if (defaultRuleSet) defaultRuleSet->format((int64_t)number, toAppendTo, toAppendTo.length());
return toAppendTo;
}
UnicodeString&
RuleBasedNumberFormat::format(int64_t number,
UnicodeString& toAppendTo,
FieldPosition& /* pos */) const
{
if (defaultRuleSet) defaultRuleSet->format(number, toAppendTo, toAppendTo.length());
return toAppendTo;
}
UnicodeString&
RuleBasedNumberFormat::format(double number,
UnicodeString& toAppendTo,
FieldPosition& /* pos */) const
{
if (defaultRuleSet) defaultRuleSet->format(number, toAppendTo, toAppendTo.length());
return toAppendTo;
}
UnicodeString&
RuleBasedNumberFormat::format(int32_t number,
const UnicodeString& ruleSetName,
UnicodeString& toAppendTo,
FieldPosition& /* pos */,
UErrorCode& status) const
{
// return format((int64_t)number, ruleSetName, toAppendTo, pos, status);
if (U_SUCCESS(status)) {
if (ruleSetName.indexOf(gPercentPercent) == 0) {
// throw new IllegalArgumentException("Can't use internal rule set");
status = U_ILLEGAL_ARGUMENT_ERROR;
} else {
NFRuleSet *rs = findRuleSet(ruleSetName, status);
if (rs) {
rs->format((int64_t)number, toAppendTo, toAppendTo.length());
}
}
}
return toAppendTo;
}
UnicodeString&
RuleBasedNumberFormat::format(int64_t number,
const UnicodeString& ruleSetName,
UnicodeString& toAppendTo,
FieldPosition& /* pos */,
UErrorCode& status) const
{
if (U_SUCCESS(status)) {
if (ruleSetName.indexOf(gPercentPercent) == 0) {
// throw new IllegalArgumentException("Can't use internal rule set");
status = U_ILLEGAL_ARGUMENT_ERROR;
} else {
NFRuleSet *rs = findRuleSet(ruleSetName, status);
if (rs) {
rs->format(number, toAppendTo, toAppendTo.length());
}
}
}
return toAppendTo;
}
// make linker happy
UnicodeString&
RuleBasedNumberFormat::format(const Formattable& obj,
UnicodeString& toAppendTo,
FieldPosition& pos,
UErrorCode& status) const
{
return NumberFormat::format(obj, toAppendTo, pos, status);
}
UnicodeString&
RuleBasedNumberFormat::format(double number,
const UnicodeString& ruleSetName,
UnicodeString& toAppendTo,
FieldPosition& /* pos */,
UErrorCode& status) const
{
if (U_SUCCESS(status)) {
if (ruleSetName.indexOf(gPercentPercent) == 0) {
// throw new IllegalArgumentException("Can't use internal rule set");
status = U_ILLEGAL_ARGUMENT_ERROR;
} else {
NFRuleSet *rs = findRuleSet(ruleSetName, status);
if (rs) {
rs->format(number, toAppendTo, toAppendTo.length());
}
}
}
return toAppendTo;
}
void
RuleBasedNumberFormat::parse(const UnicodeString& text,
Formattable& result,
ParsePosition& parsePosition) const
{
//TODO: We need a real fix. See #6895 / #6896
if (noParse) {
// skip parsing
parsePosition.setErrorIndex(0);
return;
}
if (!ruleSets) {
parsePosition.setErrorIndex(0);
return;
}
UnicodeString workingText(text, parsePosition.getIndex());
ParsePosition workingPos(0);
ParsePosition high_pp(0);
Formattable high_result;
for (NFRuleSet** p = ruleSets; *p; ++p) {
NFRuleSet *rp = *p;
if (rp->isPublic() && rp->isParseable()) {
ParsePosition working_pp(0);
Formattable working_result;
rp->parse(workingText, working_pp, kMaxDouble, working_result);
if (working_pp.getIndex() > high_pp.getIndex()) {
high_pp = working_pp;
high_result = working_result;
if (high_pp.getIndex() == workingText.length()) {
break;
}
}
}
}
int32_t startIndex = parsePosition.getIndex();
parsePosition.setIndex(startIndex + high_pp.getIndex());
if (high_pp.getIndex() > 0) {
parsePosition.setErrorIndex(-1);
} else {
int32_t errorIndex = (high_pp.getErrorIndex()>0)? high_pp.getErrorIndex(): 0;
parsePosition.setErrorIndex(startIndex + errorIndex);
}
result = high_result;
if (result.getType() == Formattable::kDouble) {
int32_t r = (int32_t)result.getDouble();
if ((double)r == result.getDouble()) {
result.setLong(r);
}
}
}
#if !UCONFIG_NO_COLLATION
void
RuleBasedNumberFormat::setLenient(UBool enabled)
{
lenient = enabled;
if (!enabled && collator) {
delete collator;
collator = NULL;
}
}
#endif
void
RuleBasedNumberFormat::setDefaultRuleSet(const UnicodeString& ruleSetName, UErrorCode& status) {
if (U_SUCCESS(status)) {
if (ruleSetName.isEmpty()) {
if (localizations) {
UnicodeString name(TRUE, localizations->getRuleSetName(0), -1);
defaultRuleSet = findRuleSet(name, status);
} else {
initDefaultRuleSet();
}
} else if (ruleSetName.startsWith(UNICODE_STRING_SIMPLE("%%"))) {
status = U_ILLEGAL_ARGUMENT_ERROR;
} else {
NFRuleSet* result = findRuleSet(ruleSetName, status);
if (result != NULL) {
defaultRuleSet = result;
}
}
}
}
UnicodeString
RuleBasedNumberFormat::getDefaultRuleSetName() const {
UnicodeString result;
if (defaultRuleSet && defaultRuleSet->isPublic()) {
defaultRuleSet->getName(result);
} else {
result.setToBogus();
}
return result;
}
void
RuleBasedNumberFormat::initDefaultRuleSet()
{
defaultRuleSet = NULL;
if (!ruleSets) {
return;
}
const UnicodeString spellout = UNICODE_STRING_SIMPLE("%spellout-numbering");
const UnicodeString ordinal = UNICODE_STRING_SIMPLE("%digits-ordinal");
const UnicodeString duration = UNICODE_STRING_SIMPLE("%duration");
NFRuleSet**p = &ruleSets[0];
while (*p) {
if ((*p)->isNamed(spellout) || (*p)->isNamed(ordinal) || (*p)->isNamed(duration)) {
defaultRuleSet = *p;
return;
} else {
++p;
}
}
defaultRuleSet = *--p;
if (!defaultRuleSet->isPublic()) {
while (p != ruleSets) {
if ((*--p)->isPublic()) {
defaultRuleSet = *p;
break;
}
}
}
}
void
RuleBasedNumberFormat::init(const UnicodeString& rules, LocalizationInfo* localizationInfos,
UParseError& pErr, UErrorCode& status)
{
// TODO: implement UParseError
uprv_memset(&pErr, 0, sizeof(UParseError));
// Note: this can leave ruleSets == NULL, so remaining code should check
if (U_FAILURE(status)) {
return;
}
this->localizations = localizationInfos == NULL ? NULL : localizationInfos->ref();
UnicodeString description(rules);
if (!description.length()) {
status = U_MEMORY_ALLOCATION_ERROR;
return;
}
// start by stripping the trailing whitespace from all the rules
// (this is all the whitespace follwing each semicolon in the
// description). This allows us to look for rule-set boundaries
// by searching for ";%" without having to worry about whitespace
// between the ; and the %
stripWhitespace(description);
// check to see if there's a set of lenient-parse rules. If there
// is, pull them out into our temporary holding place for them,
// and delete them from the description before the real desciption-
// parsing code sees them
int32_t lp = description.indexOf(gLenientParse);
if (lp != -1) {
// we've got to make sure we're not in the middle of a rule
// (where "%%lenient-parse" would actually get treated as
// rule text)
if (lp == 0 || description.charAt(lp - 1) == gSemiColon) {
// locate the beginning and end of the actual collation
// rules (there may be whitespace between the name and
// the first token in the description)
int lpEnd = description.indexOf(gSemiPercent, lp);
if (lpEnd == -1) {
lpEnd = description.length() - 1;
}
int lpStart = lp + u_strlen(gLenientParse);
while (uprv_isRuleWhiteSpace(description.charAt(lpStart))) {
++lpStart;
}
// copy out the lenient-parse rules and delete them
// from the description
lenientParseRules = new UnicodeString();
/* test for NULL */
if (lenientParseRules == 0) {
status = U_MEMORY_ALLOCATION_ERROR;
return;
}
lenientParseRules->setTo(description, lpStart, lpEnd - lpStart);
description.remove(lp, lpEnd + 1 - lp);
}
}
// pre-flight parsing the description and count the number of
// rule sets (";%" marks the end of one rule set and the beginning
// of the next)
int numRuleSets = 0;
for (int32_t p = description.indexOf(gSemiPercent); p != -1; p = description.indexOf(gSemiPercent, p)) {
++numRuleSets;
++p;
}
++numRuleSets;
// our rule list is an array of the appropriate size
ruleSets = (NFRuleSet **)uprv_malloc((numRuleSets + 1) * sizeof(NFRuleSet *));
/* test for NULL */
if (ruleSets == 0) {
status = U_MEMORY_ALLOCATION_ERROR;
return;
}
for (int i = 0; i <= numRuleSets; ++i) {
ruleSets[i] = NULL;
}
// divide up the descriptions into individual rule-set descriptions
// and store them in a temporary array. At each step, we also
// new up a rule set, but all this does is initialize its name
// and remove it from its description. We can't actually parse
// the rest of the descriptions and finish initializing everything
// because we have to know the names and locations of all the rule
// sets before we can actually set everything up
if(!numRuleSets) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return;
}
UnicodeString* ruleSetDescriptions = new UnicodeString[numRuleSets];
if (ruleSetDescriptions == 0) {
status = U_MEMORY_ALLOCATION_ERROR;
return;
}
{
int curRuleSet = 0;
int32_t start = 0;
for (int32_t p = description.indexOf(gSemiPercent); p != -1; p = description.indexOf(gSemiPercent, start)) {
ruleSetDescriptions[curRuleSet].setTo(description, start, p + 1 - start);
ruleSets[curRuleSet] = new NFRuleSet(ruleSetDescriptions, curRuleSet, status);
if (ruleSets[curRuleSet] == 0) {
status = U_MEMORY_ALLOCATION_ERROR;
goto cleanup;
}
++curRuleSet;
start = p + 1;
}
ruleSetDescriptions[curRuleSet].setTo(description, start, description.length() - start);
ruleSets[curRuleSet] = new NFRuleSet(ruleSetDescriptions, curRuleSet, status);
if (ruleSets[curRuleSet] == 0) {
status = U_MEMORY_ALLOCATION_ERROR;
goto cleanup;
}
}
// now we can take note of the formatter's default rule set, which
// is the last public rule set in the description (it's the last
// rather than the first so that a user can create a new formatter
// from an existing formatter and change its default behavior just
// by appending more rule sets to the end)
// {dlf} Initialization of a fraction rule set requires the default rule
// set to be known. For purposes of initialization, this is always the
// last public rule set, no matter what the localization data says.
initDefaultRuleSet();
// finally, we can go back through the temporary descriptions
// list and finish seting up the substructure (and we throw
// away the temporary descriptions as we go)
{
for (int i = 0; i < numRuleSets; i++) {
ruleSets[i]->parseRules(ruleSetDescriptions[i], this, status);
}
}
// Now that the rules are initialized, the 'real' default rule
// set can be adjusted by the localization data.
// The C code keeps the localization array as is, rather than building
// a separate array of the public rule set names, so we have less work
// to do here-- but we still need to check the names.
if (localizationInfos) {
// confirm the names, if any aren't in the rules, that's an error
// it is ok if the rules contain public rule sets that are not in this list
for (int32_t i = 0; i < localizationInfos->getNumberOfRuleSets(); ++i) {
UnicodeString name(TRUE, localizationInfos->getRuleSetName(i), -1);
NFRuleSet* rs = findRuleSet(name, status);
if (rs == NULL) {
break; // error
}
if (i == 0) {
defaultRuleSet = rs;
}
}
} else {
defaultRuleSet = getDefaultRuleSet();
}
cleanup:
delete[] ruleSetDescriptions;
}
void
RuleBasedNumberFormat::stripWhitespace(UnicodeString& description)
{
// iterate through the characters...
UnicodeString result;
int start = 0;
while (start != -1 && start < description.length()) {
// seek to the first non-whitespace character...
while (start < description.length()
&& uprv_isRuleWhiteSpace(description.charAt(start))) {
++start;
}
// locate the next semicolon in the text and copy the text from
// our current position up to that semicolon into the result
int32_t p = description.indexOf(gSemiColon, start);
if (p == -1) {
// or if we don't find a semicolon, just copy the rest of
// the string into the result
result.append(description, start, description.length() - start);
start = -1;
}
else if (p < description.length()) {
result.append(description, start, p + 1 - start);
start = p + 1;
}
// when we get here, we've seeked off the end of the sring, and
// we terminate the loop (we continue until *start* is -1 rather
// than until *p* is -1, because otherwise we'd miss the last
// rule in the description)
else {
start = -1;
}
}
description.setTo(result);
}
void
RuleBasedNumberFormat::dispose()
{
if (ruleSets) {
for (NFRuleSet** p = ruleSets; *p; ++p) {
delete *p;
}
uprv_free(ruleSets);
ruleSets = NULL;
}
#if !UCONFIG_NO_COLLATION
delete collator;
#endif
collator = NULL;
delete decimalFormatSymbols;
decimalFormatSymbols = NULL;
delete lenientParseRules;
lenientParseRules = NULL;
if (localizations) localizations = localizations->unref();
}
//-----------------------------------------------------------------------
// package-internal API
//-----------------------------------------------------------------------
/**
* Returns the collator to use for lenient parsing. The collator is lazily created:
* this function creates it the first time it's called.
* @return The collator to use for lenient parsing, or null if lenient parsing
* is turned off.
*/
Collator*
RuleBasedNumberFormat::getCollator() const
{
#if !UCONFIG_NO_COLLATION
if (!ruleSets) {
return NULL;
}
// lazy-evaulate the collator
if (collator == NULL && lenient) {
// create a default collator based on the formatter's locale,
// then pull out that collator's rules, append any additional
// rules specified in the description, and create a _new_
// collator based on the combinaiton of those rules
UErrorCode status = U_ZERO_ERROR;
Collator* temp = Collator::createInstance(locale, status);
RuleBasedCollator* newCollator;
if (U_SUCCESS(status) && (newCollator = dynamic_cast<RuleBasedCollator*>(temp)) != NULL) {
if (lenientParseRules) {
UnicodeString rules(newCollator->getRules());
rules.append(*lenientParseRules);
newCollator = new RuleBasedCollator(rules, status);
// Exit if newCollator could not be created.
if (newCollator == NULL) {
return NULL;
}
} else {
temp = NULL;
}
if (U_SUCCESS(status)) {
newCollator->setAttribute(UCOL_DECOMPOSITION_MODE, UCOL_ON, status);
// cast away const
((RuleBasedNumberFormat*)this)->collator = newCollator;
} else {
delete newCollator;
}
}
delete temp;
}
#endif
// if lenient-parse mode is off, this will be null
// (see setLenientParseMode())
return collator;
}
/**
* Returns the DecimalFormatSymbols object that should be used by all DecimalFormat
* instances owned by this formatter. This object is lazily created: this function
* creates it the first time it's called.
* @return The DecimalFormatSymbols object that should be used by all DecimalFormat
* instances owned by this formatter.
*/
DecimalFormatSymbols*
RuleBasedNumberFormat::getDecimalFormatSymbols() const
{
// lazy-evaluate the DecimalFormatSymbols object. This object
// is shared by all DecimalFormat instances belonging to this
// formatter
if (decimalFormatSymbols == NULL) {
UErrorCode status = U_ZERO_ERROR;
DecimalFormatSymbols* temp = new DecimalFormatSymbols(locale, status);
if (U_SUCCESS(status)) {
((RuleBasedNumberFormat*)this)->decimalFormatSymbols = temp;
} else {
delete temp;
}
}
return decimalFormatSymbols;
}
U_NAMESPACE_END
/* U_HAVE_RBNF */
#endif