/* ******************************************************************************* * Copyright (C) 2009, International Business Machines Corporation and * others. All Rights Reserved. ******************************************************************************* * * File PLURFMT.CPP * * Modification History: * * Date Name Description ******************************************************************************* */ #include "unicode/utypes.h" #include "unicode/plurfmt.h" #include "unicode/plurrule.h" #include "plurrule_impl.h" #if !UCONFIG_NO_FORMATTING U_NAMESPACE_BEGIN U_CDECL_BEGIN static void U_CALLCONV deleteHashStrings(void *obj) { delete (UnicodeString *)obj; } U_CDECL_END UOBJECT_DEFINE_RTTI_IMPLEMENTATION(PluralFormat) #define MAX_KEYWORD_SIZE 30 PluralFormat::PluralFormat(UErrorCode& status) { init(NULL, Locale::getDefault(), status); } PluralFormat::PluralFormat(const Locale& loc, UErrorCode& status) { init(NULL, loc, status); } PluralFormat::PluralFormat(const PluralRules& rules, UErrorCode& status) { init(&rules, Locale::getDefault(), status); } PluralFormat::PluralFormat(const Locale& loc, const PluralRules& rules, UErrorCode& status) { init(&rules, loc, status); } PluralFormat::PluralFormat(const UnicodeString& pat, UErrorCode& status) { init(NULL, Locale::getDefault(), status); applyPattern(pat, status); } PluralFormat::PluralFormat(const Locale& loc, const UnicodeString& pat, UErrorCode& status) { init(NULL, loc, status); applyPattern(pat, status); } PluralFormat::PluralFormat(const PluralRules& rules, const UnicodeString& pat, UErrorCode& status) { init(&rules, Locale::getDefault(), status); applyPattern(pat, status); } PluralFormat::PluralFormat(const Locale& loc, const PluralRules& rules, const UnicodeString& pat, UErrorCode& status) { init(&rules, loc, status); applyPattern(pat, status); } PluralFormat::PluralFormat(const PluralFormat& other) : Format(other) { UErrorCode status = U_ZERO_ERROR; locale = other.locale; pluralRules = other.pluralRules->clone(); pattern = other.pattern; copyHashtable(other.fParsedValuesHash, status); if (U_FAILURE(status)) { delete pluralRules; pluralRules = NULL; return; } numberFormat=NumberFormat::createInstance(locale, status); if (U_FAILURE(status)) { delete pluralRules; pluralRules = NULL; delete fParsedValuesHash; fParsedValuesHash = NULL; return; } replacedNumberFormat=other.replacedNumberFormat; } PluralFormat::~PluralFormat() { delete pluralRules; delete fParsedValuesHash; delete numberFormat; } void PluralFormat::init(const PluralRules* rules, const Locale& curLocale, UErrorCode& status) { if (U_FAILURE(status)) { return; } locale = curLocale; if ( rules==NULL) { pluralRules = PluralRules::forLocale(locale, status); if (U_FAILURE(status)) { return; } } else { pluralRules = rules->clone(); } fParsedValuesHash=NULL; pattern.remove(); numberFormat= NumberFormat::createInstance(curLocale, status); if (U_FAILURE(status)) { delete pluralRules; pluralRules = NULL; return; } replacedNumberFormat=NULL; } void PluralFormat::applyPattern(const UnicodeString& newPattern, UErrorCode& status) { if (U_FAILURE(status)) { return; } this->pattern = newPattern; UnicodeString token; int32_t braceCount=0; fmtToken type; UBool spaceIncluded=FALSE; if (fParsedValuesHash==NULL) { fParsedValuesHash = new Hashtable(TRUE, status); if (U_FAILURE(status)) { return; } fParsedValuesHash->setValueDeleter(deleteHashStrings); } UBool getKeyword=TRUE; UnicodeString hashKeyword; UnicodeString *hashPattern; for (int32_t i=0; i<pattern.length(); ++i) { UChar ch=pattern.charAt(i); if ( !inRange(ch, type) ) { if (getKeyword) { status = U_ILLEGAL_CHARACTER; return; } else { token += ch; continue; } } switch (type) { case tSpace: if (token.length()==0) { continue; } if (getKeyword) { // space after keyword spaceIncluded = TRUE; } else { token += ch; } break; case tLeftBrace: if ( getKeyword ) { if (fParsedValuesHash->get(token)!= NULL) { status = U_DUPLICATE_KEYWORD; return; } if (token.length()==0) { status = U_PATTERN_SYNTAX_ERROR; return; } if (!pluralRules->isKeyword(token)) { status = U_UNDEFINED_KEYWORD; return; } hashKeyword = token; getKeyword = FALSE; token.remove(); } else { if (braceCount==0) { status = U_UNEXPECTED_TOKEN; return; } else { token += ch; } } braceCount++; spaceIncluded = FALSE; break; case tRightBrace: if ( getKeyword ) { status = U_UNEXPECTED_TOKEN; return; } else { hashPattern = new UnicodeString(token); fParsedValuesHash->put(hashKeyword, hashPattern, status); if (U_FAILURE(status)) { return; } braceCount--; if ( braceCount==0 ) { getKeyword=TRUE; hashKeyword.remove(); hashPattern=NULL; token.remove(); } else { token += ch; } } spaceIncluded = FALSE; break; case tLetter: case tNumberSign: if (spaceIncluded) { status = U_PATTERN_SYNTAX_ERROR; return; } default: token+=ch; break; } } if ( checkSufficientDefinition() ) { return; } else { status = U_DEFAULT_KEYWORD_MISSING; return; } } UnicodeString& PluralFormat::format(const Formattable& obj, UnicodeString& appendTo, FieldPosition& pos, UErrorCode& status) const { if (U_FAILURE(status)) return appendTo; int32_t number; switch (obj.getType()) { case Formattable::kDouble: return format((int32_t)obj.getDouble(), appendTo, pos, status); break; case Formattable::kLong: number = (int32_t)obj.getLong(); return format(number, appendTo, pos, status); break; case Formattable::kInt64: return format((int32_t)obj.getInt64(), appendTo, pos, status); default: status = U_ILLEGAL_ARGUMENT_ERROR; return appendTo; } } UnicodeString PluralFormat::format(int32_t number, UErrorCode& status) const { if (U_FAILURE(status)) { return UnicodeString(); } FieldPosition fpos(0); UnicodeString result; return format(number, result, fpos, status); } UnicodeString PluralFormat::format(double number, UErrorCode& status) const { if (U_FAILURE(status)) { return UnicodeString(); } FieldPosition fpos(0); UnicodeString result; return format(number, result, fpos, status); } UnicodeString& PluralFormat::format(int32_t number, UnicodeString& appendTo, FieldPosition& pos, UErrorCode& status) const { return format((double)number, appendTo, pos, status); } UnicodeString& PluralFormat::format(double number, UnicodeString& appendTo, FieldPosition& pos, UErrorCode& /*status*/) const { if (fParsedValuesHash==NULL) { if ( replacedNumberFormat== NULL ) { return numberFormat->format(number, appendTo, pos); } else { replacedNumberFormat->format(number, appendTo, pos); } } UnicodeString selectedRule = pluralRules->select(number); UnicodeString *selectedPattern = (UnicodeString *)fParsedValuesHash->get(selectedRule); if (selectedPattern==NULL) { selectedPattern = (UnicodeString *)fParsedValuesHash->get(pluralRules->getKeywordOther()); } appendTo = insertFormattedNumber(number, *selectedPattern, appendTo, pos); return appendTo; } UnicodeString& PluralFormat::toPattern(UnicodeString& appendTo) { appendTo+= pattern; return appendTo; } UBool PluralFormat::inRange(UChar ch, fmtToken& type) { if ((ch>=CAP_A) && (ch<=CAP_Z)) { // we assume all characters are in lower case already. return FALSE; } if ((ch>=LOW_A) && (ch<=LOW_Z)) { type = tLetter; return TRUE; } switch (ch) { case LEFTBRACE: type = tLeftBrace; return TRUE; case SPACE: type = tSpace; return TRUE; case RIGHTBRACE: type = tRightBrace; return TRUE; case NUMBER_SIGN: type = tNumberSign; return TRUE; default : type = none; return FALSE; } } UBool PluralFormat::checkSufficientDefinition() { // Check that at least the default rule is defined. if (fParsedValuesHash==NULL) return FALSE; if (fParsedValuesHash->get(pluralRules->getKeywordOther()) == NULL) { return FALSE; } else { return TRUE; } } void PluralFormat::setLocale(const Locale& loc, UErrorCode& status) { if (U_FAILURE(status)) { return; } if (pluralRules!=NULL) { delete pluralRules; pluralRules=NULL; } if (fParsedValuesHash!= NULL) { delete fParsedValuesHash; fParsedValuesHash = NULL; } if (numberFormat!=NULL) { delete numberFormat; numberFormat = NULL; replacedNumberFormat=NULL; } init(NULL, loc, status); } void PluralFormat::setNumberFormat(const NumberFormat* format, UErrorCode& /*status*/) { // TODO: The copy constructor and assignment op of NumberFormat class are protected. // create a pointer as the workaround. replacedNumberFormat = (NumberFormat *)format; } Format* PluralFormat::clone() const { return new PluralFormat(*this); } PluralFormat& PluralFormat::operator=(const PluralFormat& other) { if (this != &other) { UErrorCode status = U_ZERO_ERROR; delete pluralRules; delete fParsedValuesHash; delete numberFormat; locale = other.locale; pluralRules = other.pluralRules->clone(); pattern = other.pattern; copyHashtable(other.fParsedValuesHash, status); if (U_FAILURE(status)) { delete pluralRules; pluralRules = NULL; fParsedValuesHash = NULL; numberFormat = NULL; return *this; } numberFormat=NumberFormat::createInstance(locale, status); if (U_FAILURE(status)) { delete pluralRules; delete fParsedValuesHash; pluralRules = NULL; fParsedValuesHash = NULL; numberFormat = NULL; return *this; } replacedNumberFormat=other.replacedNumberFormat; } return *this; } UBool PluralFormat::operator==(const Format& other) const { // This protected comparison operator should only be called by subclasses // which have confirmed that the other object being compared against is // an instance of a sublcass of PluralFormat. THIS IS IMPORTANT. // Format::operator== guarantees that this cast is safe PluralFormat* fmt = (PluralFormat*)&other; return ((*pluralRules == *(fmt->pluralRules)) && (*numberFormat == *(fmt->numberFormat))); } UBool PluralFormat::operator!=(const Format& other) const { return !operator==(other); } void PluralFormat::parseObject(const UnicodeString& /*source*/, Formattable& /*result*/, ParsePosition& /*pos*/) const { // TODO: not yet supported in icu4j and icu4c } UnicodeString PluralFormat::insertFormattedNumber(double number, UnicodeString& message, UnicodeString& appendTo, FieldPosition& pos) const { UnicodeString result; int32_t braceStack=0; int32_t startIndex=0; if (message.length()==0) { return result; } appendTo = numberFormat->format(number, appendTo, pos); for(int32_t i=0; i<message.length(); ++i) { switch(message.charAt(i)) { case LEFTBRACE: ++braceStack; break; case RIGHTBRACE: --braceStack; break; case NUMBER_SIGN: if (braceStack==0) { result += UnicodeString(message, startIndex, i); result += appendTo; startIndex = i + 1; } break; } } if ( startIndex < message.length() ) { result += UnicodeString(message, startIndex, message.length()-startIndex); } appendTo = result; return result; } void PluralFormat::copyHashtable(Hashtable *other, UErrorCode& status) { if (other == NULL || U_FAILURE(status)) { fParsedValuesHash = NULL; return; } fParsedValuesHash = new Hashtable(TRUE, status); if(U_FAILURE(status)){ return; } fParsedValuesHash->setValueDeleter(deleteHashStrings); int32_t pos = -1; const UHashElement* elem = NULL; // walk through the hash table and create a deep clone while((elem = other->nextElement(pos))!= NULL){ const UHashTok otherKeyTok = elem->key; UnicodeString* otherKey = (UnicodeString*)otherKeyTok.pointer; const UHashTok otherKeyToVal = elem->value; UnicodeString* otherValue = (UnicodeString*)otherKeyToVal.pointer; fParsedValuesHash->put(*otherKey, new UnicodeString(*otherValue), status); if(U_FAILURE(status)){ return; } } } U_NAMESPACE_END #endif /* #if !UCONFIG_NO_FORMATTING */ //eof