/*
*******************************************************************************
* 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