michael@0: /* michael@0: ******************************************************************************* michael@0: * Copyright (C) 2009-2013, International Business Machines Corporation and michael@0: * others. All Rights Reserved. michael@0: ******************************************************************************* michael@0: * michael@0: * File PLURFMT.CPP michael@0: ******************************************************************************* michael@0: */ michael@0: michael@0: #include "unicode/decimfmt.h" michael@0: #include "unicode/messagepattern.h" michael@0: #include "unicode/plurfmt.h" michael@0: #include "unicode/plurrule.h" michael@0: #include "unicode/utypes.h" michael@0: #include "cmemory.h" michael@0: #include "messageimpl.h" michael@0: #include "plurrule_impl.h" michael@0: #include "uassert.h" michael@0: #include "uhash.h" michael@0: michael@0: #if !UCONFIG_NO_FORMATTING michael@0: michael@0: U_NAMESPACE_BEGIN michael@0: michael@0: static const UChar OTHER_STRING[] = { michael@0: 0x6F, 0x74, 0x68, 0x65, 0x72, 0 // "other" michael@0: }; michael@0: michael@0: UOBJECT_DEFINE_RTTI_IMPLEMENTATION(PluralFormat) michael@0: michael@0: PluralFormat::PluralFormat(UErrorCode& status) michael@0: : locale(Locale::getDefault()), michael@0: msgPattern(status), michael@0: numberFormat(NULL), michael@0: offset(0) { michael@0: init(NULL, UPLURAL_TYPE_CARDINAL, status); michael@0: } michael@0: michael@0: PluralFormat::PluralFormat(const Locale& loc, UErrorCode& status) michael@0: : locale(loc), michael@0: msgPattern(status), michael@0: numberFormat(NULL), michael@0: offset(0) { michael@0: init(NULL, UPLURAL_TYPE_CARDINAL, status); michael@0: } michael@0: michael@0: PluralFormat::PluralFormat(const PluralRules& rules, UErrorCode& status) michael@0: : locale(Locale::getDefault()), michael@0: msgPattern(status), michael@0: numberFormat(NULL), michael@0: offset(0) { michael@0: init(&rules, UPLURAL_TYPE_COUNT, status); michael@0: } michael@0: michael@0: PluralFormat::PluralFormat(const Locale& loc, michael@0: const PluralRules& rules, michael@0: UErrorCode& status) michael@0: : locale(loc), michael@0: msgPattern(status), michael@0: numberFormat(NULL), michael@0: offset(0) { michael@0: init(&rules, UPLURAL_TYPE_COUNT, status); michael@0: } michael@0: michael@0: PluralFormat::PluralFormat(const Locale& loc, michael@0: UPluralType type, michael@0: UErrorCode& status) michael@0: : locale(loc), michael@0: msgPattern(status), michael@0: numberFormat(NULL), michael@0: offset(0) { michael@0: init(NULL, type, status); michael@0: } michael@0: michael@0: PluralFormat::PluralFormat(const UnicodeString& pat, michael@0: UErrorCode& status) michael@0: : locale(Locale::getDefault()), michael@0: msgPattern(status), michael@0: numberFormat(NULL), michael@0: offset(0) { michael@0: init(NULL, UPLURAL_TYPE_CARDINAL, status); michael@0: applyPattern(pat, status); michael@0: } michael@0: michael@0: PluralFormat::PluralFormat(const Locale& loc, michael@0: const UnicodeString& pat, michael@0: UErrorCode& status) michael@0: : locale(loc), michael@0: msgPattern(status), michael@0: numberFormat(NULL), michael@0: offset(0) { michael@0: init(NULL, UPLURAL_TYPE_CARDINAL, status); michael@0: applyPattern(pat, status); michael@0: } michael@0: michael@0: PluralFormat::PluralFormat(const PluralRules& rules, michael@0: const UnicodeString& pat, michael@0: UErrorCode& status) michael@0: : locale(Locale::getDefault()), michael@0: msgPattern(status), michael@0: numberFormat(NULL), michael@0: offset(0) { michael@0: init(&rules, UPLURAL_TYPE_COUNT, status); michael@0: applyPattern(pat, status); michael@0: } michael@0: michael@0: PluralFormat::PluralFormat(const Locale& loc, michael@0: const PluralRules& rules, michael@0: const UnicodeString& pat, michael@0: UErrorCode& status) michael@0: : locale(loc), michael@0: msgPattern(status), michael@0: numberFormat(NULL), michael@0: offset(0) { michael@0: init(&rules, UPLURAL_TYPE_COUNT, status); michael@0: applyPattern(pat, status); michael@0: } michael@0: michael@0: PluralFormat::PluralFormat(const Locale& loc, michael@0: UPluralType type, michael@0: const UnicodeString& pat, michael@0: UErrorCode& status) michael@0: : locale(loc), michael@0: msgPattern(status), michael@0: numberFormat(NULL), michael@0: offset(0) { michael@0: init(NULL, type, status); michael@0: applyPattern(pat, status); michael@0: } michael@0: michael@0: PluralFormat::PluralFormat(const PluralFormat& other) michael@0: : Format(other), michael@0: locale(other.locale), michael@0: msgPattern(other.msgPattern), michael@0: numberFormat(NULL), michael@0: offset(other.offset) { michael@0: copyObjects(other); michael@0: } michael@0: michael@0: void michael@0: PluralFormat::copyObjects(const PluralFormat& other) { michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: if (numberFormat != NULL) { michael@0: delete numberFormat; michael@0: } michael@0: if (pluralRulesWrapper.pluralRules != NULL) { michael@0: delete pluralRulesWrapper.pluralRules; michael@0: } michael@0: michael@0: if (other.numberFormat == NULL) { michael@0: numberFormat = NumberFormat::createInstance(locale, status); michael@0: } else { michael@0: numberFormat = (NumberFormat*)other.numberFormat->clone(); michael@0: } michael@0: if (other.pluralRulesWrapper.pluralRules == NULL) { michael@0: pluralRulesWrapper.pluralRules = PluralRules::forLocale(locale, status); michael@0: } else { michael@0: pluralRulesWrapper.pluralRules = other.pluralRulesWrapper.pluralRules->clone(); michael@0: } michael@0: } michael@0: michael@0: michael@0: PluralFormat::~PluralFormat() { michael@0: delete numberFormat; michael@0: } michael@0: michael@0: void michael@0: PluralFormat::init(const PluralRules* rules, UPluralType type, UErrorCode& status) { michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: michael@0: if (rules==NULL) { michael@0: pluralRulesWrapper.pluralRules = PluralRules::forLocale(locale, type, status); michael@0: } else { michael@0: pluralRulesWrapper.pluralRules = rules->clone(); michael@0: if (pluralRulesWrapper.pluralRules == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: return; michael@0: } michael@0: } michael@0: michael@0: numberFormat= NumberFormat::createInstance(locale, status); michael@0: } michael@0: michael@0: void michael@0: PluralFormat::applyPattern(const UnicodeString& newPattern, UErrorCode& status) { michael@0: msgPattern.parsePluralStyle(newPattern, NULL, status); michael@0: if (U_FAILURE(status)) { michael@0: msgPattern.clear(); michael@0: offset = 0; michael@0: return; michael@0: } michael@0: offset = msgPattern.getPluralOffset(0); michael@0: } michael@0: michael@0: UnicodeString& michael@0: PluralFormat::format(const Formattable& obj, michael@0: UnicodeString& appendTo, michael@0: FieldPosition& pos, michael@0: UErrorCode& status) const michael@0: { michael@0: if (U_FAILURE(status)) return appendTo; michael@0: michael@0: if (obj.isNumeric()) { michael@0: return format(obj, obj.getDouble(), appendTo, pos, status); michael@0: } else { michael@0: status = U_ILLEGAL_ARGUMENT_ERROR; michael@0: return appendTo; michael@0: } michael@0: } michael@0: michael@0: UnicodeString michael@0: PluralFormat::format(int32_t number, UErrorCode& status) const { michael@0: FieldPosition fpos(0); michael@0: UnicodeString result; michael@0: return format(Formattable(number), number, result, fpos, status); michael@0: } michael@0: michael@0: UnicodeString michael@0: PluralFormat::format(double number, UErrorCode& status) const { michael@0: FieldPosition fpos(0); michael@0: UnicodeString result; michael@0: return format(Formattable(number), number, result, fpos, status); michael@0: } michael@0: michael@0: michael@0: UnicodeString& michael@0: PluralFormat::format(int32_t number, michael@0: UnicodeString& appendTo, michael@0: FieldPosition& pos, michael@0: UErrorCode& status) const { michael@0: return format(Formattable(number), (double)number, appendTo, pos, status); michael@0: } michael@0: michael@0: UnicodeString& michael@0: PluralFormat::format(double number, michael@0: UnicodeString& appendTo, michael@0: FieldPosition& pos, michael@0: UErrorCode& status) const { michael@0: return format(Formattable(number), (double)number, appendTo, pos, status); michael@0: } michael@0: michael@0: UnicodeString& michael@0: PluralFormat::format(const Formattable& numberObject, double number, michael@0: UnicodeString& appendTo, michael@0: FieldPosition& pos, michael@0: UErrorCode& status) const { michael@0: if (U_FAILURE(status)) { michael@0: return appendTo; michael@0: } michael@0: if (msgPattern.countParts() == 0) { michael@0: return numberFormat->format(numberObject, appendTo, pos, status); michael@0: } michael@0: // Get the appropriate sub-message. michael@0: // Select it based on the formatted number-offset. michael@0: double numberMinusOffset = number - offset; michael@0: UnicodeString numberString; michael@0: FieldPosition ignorePos; michael@0: FixedDecimal dec(numberMinusOffset); michael@0: if (offset == 0) { michael@0: numberFormat->format(numberObject, numberString, ignorePos, status); // could be BigDecimal etc. michael@0: DecimalFormat *decFmt = dynamic_cast(numberFormat); michael@0: if(decFmt != NULL) { michael@0: dec = decFmt->getFixedDecimal(numberObject, status); michael@0: } michael@0: } else { michael@0: numberFormat->format(numberMinusOffset, numberString, ignorePos, status); michael@0: DecimalFormat *decFmt = dynamic_cast(numberFormat); michael@0: if(decFmt != NULL) { michael@0: dec = decFmt->getFixedDecimal(numberMinusOffset, status); michael@0: } michael@0: } michael@0: int32_t partIndex = findSubMessage(msgPattern, 0, pluralRulesWrapper, &dec, number, status); michael@0: if (U_FAILURE(status)) { return appendTo; } michael@0: // Replace syntactic # signs in the top level of this sub-message michael@0: // (not in nested arguments) with the formatted number-offset. michael@0: const UnicodeString& pattern = msgPattern.getPatternString(); michael@0: int32_t prevIndex = msgPattern.getPart(partIndex).getLimit(); michael@0: for (;;) { michael@0: const MessagePattern::Part& part = msgPattern.getPart(++partIndex); michael@0: const UMessagePatternPartType type = part.getType(); michael@0: int32_t index = part.getIndex(); michael@0: if (type == UMSGPAT_PART_TYPE_MSG_LIMIT) { michael@0: return appendTo.append(pattern, prevIndex, index - prevIndex); michael@0: } else if ((type == UMSGPAT_PART_TYPE_REPLACE_NUMBER) || michael@0: (type == UMSGPAT_PART_TYPE_SKIP_SYNTAX && MessageImpl::jdkAposMode(msgPattern))) { michael@0: appendTo.append(pattern, prevIndex, index - prevIndex); michael@0: if (type == UMSGPAT_PART_TYPE_REPLACE_NUMBER) { michael@0: appendTo.append(numberString); michael@0: } michael@0: prevIndex = part.getLimit(); michael@0: } else if (type == UMSGPAT_PART_TYPE_ARG_START) { michael@0: appendTo.append(pattern, prevIndex, index - prevIndex); michael@0: prevIndex = index; michael@0: partIndex = msgPattern.getLimitPartIndex(partIndex); michael@0: index = msgPattern.getPart(partIndex).getLimit(); michael@0: MessageImpl::appendReducedApostrophes(pattern, prevIndex, index, appendTo); michael@0: prevIndex = index; michael@0: } michael@0: } michael@0: } michael@0: michael@0: UnicodeString& michael@0: PluralFormat::toPattern(UnicodeString& appendTo) { michael@0: if (0 == msgPattern.countParts()) { michael@0: appendTo.setToBogus(); michael@0: } else { michael@0: appendTo.append(msgPattern.getPatternString()); michael@0: } michael@0: return appendTo; michael@0: } michael@0: michael@0: void michael@0: PluralFormat::setLocale(const Locale& loc, UErrorCode& status) { michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: locale = loc; michael@0: msgPattern.clear(); michael@0: delete numberFormat; michael@0: offset = 0; michael@0: numberFormat = NULL; michael@0: pluralRulesWrapper.reset(); michael@0: init(NULL, UPLURAL_TYPE_CARDINAL, status); michael@0: } michael@0: michael@0: void michael@0: PluralFormat::setNumberFormat(const NumberFormat* format, UErrorCode& status) { michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: NumberFormat* nf = (NumberFormat*)format->clone(); michael@0: if (nf != NULL) { michael@0: delete numberFormat; michael@0: numberFormat = nf; michael@0: } else { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: } michael@0: } michael@0: michael@0: Format* michael@0: PluralFormat::clone() const michael@0: { michael@0: return new PluralFormat(*this); michael@0: } michael@0: michael@0: michael@0: PluralFormat& michael@0: PluralFormat::operator=(const PluralFormat& other) { michael@0: if (this != &other) { michael@0: locale = other.locale; michael@0: msgPattern = other.msgPattern; michael@0: offset = other.offset; michael@0: copyObjects(other); michael@0: } michael@0: michael@0: return *this; michael@0: } michael@0: michael@0: UBool michael@0: PluralFormat::operator==(const Format& other) const { michael@0: if (this == &other) { michael@0: return TRUE; michael@0: } michael@0: if (!Format::operator==(other)) { michael@0: return FALSE; michael@0: } michael@0: const PluralFormat& o = (const PluralFormat&)other; michael@0: return michael@0: locale == o.locale && michael@0: msgPattern == o.msgPattern && // implies same offset michael@0: (numberFormat == NULL) == (o.numberFormat == NULL) && michael@0: (numberFormat == NULL || *numberFormat == *o.numberFormat) && michael@0: (pluralRulesWrapper.pluralRules == NULL) == (o.pluralRulesWrapper.pluralRules == NULL) && michael@0: (pluralRulesWrapper.pluralRules == NULL || michael@0: *pluralRulesWrapper.pluralRules == *o.pluralRulesWrapper.pluralRules); michael@0: } michael@0: michael@0: UBool michael@0: PluralFormat::operator!=(const Format& other) const { michael@0: return !operator==(other); michael@0: } michael@0: michael@0: void michael@0: PluralFormat::parseObject(const UnicodeString& /*source*/, michael@0: Formattable& /*result*/, michael@0: ParsePosition& pos) const michael@0: { michael@0: // Parsing not supported. michael@0: pos.setErrorIndex(pos.getIndex()); michael@0: } michael@0: michael@0: int32_t PluralFormat::findSubMessage(const MessagePattern& pattern, int32_t partIndex, michael@0: const PluralSelector& selector, void *context, michael@0: double number, UErrorCode& ec) { michael@0: if (U_FAILURE(ec)) { michael@0: return 0; michael@0: } michael@0: int32_t count=pattern.countParts(); michael@0: double offset; michael@0: const MessagePattern::Part* part=&pattern.getPart(partIndex); michael@0: if (MessagePattern::Part::hasNumericValue(part->getType())) { michael@0: offset=pattern.getNumericValue(*part); michael@0: ++partIndex; michael@0: } else { michael@0: offset=0; michael@0: } michael@0: // The keyword is empty until we need to match against a non-explicit, not-"other" value. michael@0: // Then we get the keyword from the selector. michael@0: // (In other words, we never call the selector if we match against an explicit value, michael@0: // or if the only non-explicit keyword is "other".) michael@0: UnicodeString keyword; michael@0: UnicodeString other(FALSE, OTHER_STRING, 5); michael@0: // When we find a match, we set msgStart>0 and also set this boolean to true michael@0: // to avoid matching the keyword again (duplicates are allowed) michael@0: // while we continue to look for an explicit-value match. michael@0: UBool haveKeywordMatch=FALSE; michael@0: // msgStart is 0 until we find any appropriate sub-message. michael@0: // We remember the first "other" sub-message if we have not seen any michael@0: // appropriate sub-message before. michael@0: // We remember the first matching-keyword sub-message if we have not seen michael@0: // one of those before. michael@0: // (The parser allows [does not check for] duplicate keywords. michael@0: // We just have to make sure to take the first one.) michael@0: // We avoid matching the keyword twice by also setting haveKeywordMatch=true michael@0: // at the first keyword match. michael@0: // We keep going until we find an explicit-value match or reach the end of the plural style. michael@0: int32_t msgStart=0; michael@0: // Iterate over (ARG_SELECTOR [ARG_INT|ARG_DOUBLE] message) tuples michael@0: // until ARG_LIMIT or end of plural-only pattern. michael@0: do { michael@0: part=&pattern.getPart(partIndex++); michael@0: const UMessagePatternPartType type = part->getType(); michael@0: if(type==UMSGPAT_PART_TYPE_ARG_LIMIT) { michael@0: break; michael@0: } michael@0: U_ASSERT (type==UMSGPAT_PART_TYPE_ARG_SELECTOR); michael@0: // part is an ARG_SELECTOR followed by an optional explicit value, and then a message michael@0: if(MessagePattern::Part::hasNumericValue(pattern.getPartType(partIndex))) { michael@0: // explicit value like "=2" michael@0: part=&pattern.getPart(partIndex++); michael@0: if(number==pattern.getNumericValue(*part)) { michael@0: // matches explicit value michael@0: return partIndex; michael@0: } michael@0: } else if(!haveKeywordMatch) { michael@0: // plural keyword like "few" or "other" michael@0: // Compare "other" first and call the selector if this is not "other". michael@0: if(pattern.partSubstringMatches(*part, other)) { michael@0: if(msgStart==0) { michael@0: msgStart=partIndex; michael@0: if(0 == keyword.compare(other)) { michael@0: // This is the first "other" sub-message, michael@0: // and the selected keyword is also "other". michael@0: // Do not match "other" again. michael@0: haveKeywordMatch=TRUE; michael@0: } michael@0: } michael@0: } else { michael@0: if(keyword.isEmpty()) { michael@0: keyword=selector.select(context, number-offset, ec); michael@0: if(msgStart!=0 && (0 == keyword.compare(other))) { michael@0: // We have already seen an "other" sub-message. michael@0: // Do not match "other" again. michael@0: haveKeywordMatch=TRUE; michael@0: // Skip keyword matching but do getLimitPartIndex(). michael@0: } michael@0: } michael@0: if(!haveKeywordMatch && pattern.partSubstringMatches(*part, keyword)) { michael@0: // keyword matches michael@0: msgStart=partIndex; michael@0: // Do not match this keyword again. michael@0: haveKeywordMatch=TRUE; michael@0: } michael@0: } michael@0: } michael@0: partIndex=pattern.getLimitPartIndex(partIndex); michael@0: } while(++partIndex(context); michael@0: U_ASSERT(dec->source==number); michael@0: return pluralRules->select(*dec); michael@0: } michael@0: michael@0: void PluralFormat::PluralSelectorAdapter::reset() { michael@0: delete pluralRules; michael@0: pluralRules = NULL; michael@0: } michael@0: michael@0: michael@0: U_NAMESPACE_END michael@0: michael@0: michael@0: #endif /* #if !UCONFIG_NO_FORMATTING */ michael@0: michael@0: //eof