michael@0: /* michael@0: ****************************************************************************** michael@0: * Copyright (C) 1997-2011, International Business Machines michael@0: * Corporation and others. All Rights Reserved. michael@0: ****************************************************************************** michael@0: * file name: nfrule.cpp michael@0: * encoding: US-ASCII michael@0: * tab size: 8 (not used) michael@0: * indentation:4 michael@0: * michael@0: * Modification history michael@0: * Date Name Comments michael@0: * 10/11/2001 Doug Ported from ICU4J michael@0: */ michael@0: michael@0: #include "nfrule.h" michael@0: michael@0: #if U_HAVE_RBNF michael@0: michael@0: #include "unicode/rbnf.h" michael@0: #include "unicode/tblcoll.h" michael@0: #include "unicode/coleitr.h" michael@0: #include "unicode/uchar.h" michael@0: #include "nfrs.h" michael@0: #include "nfrlist.h" michael@0: #include "nfsubs.h" michael@0: #include "patternprops.h" michael@0: michael@0: U_NAMESPACE_BEGIN michael@0: michael@0: NFRule::NFRule(const RuleBasedNumberFormat* _rbnf) michael@0: : baseValue((int32_t)0) michael@0: , radix(0) michael@0: , exponent(0) michael@0: , ruleText() michael@0: , sub1(NULL) michael@0: , sub2(NULL) michael@0: , formatter(_rbnf) michael@0: { michael@0: } michael@0: michael@0: NFRule::~NFRule() michael@0: { michael@0: delete sub1; michael@0: delete sub2; michael@0: } michael@0: michael@0: static const UChar gLeftBracket = 0x005b; michael@0: static const UChar gRightBracket = 0x005d; michael@0: static const UChar gColon = 0x003a; michael@0: static const UChar gZero = 0x0030; michael@0: static const UChar gNine = 0x0039; michael@0: static const UChar gSpace = 0x0020; michael@0: static const UChar gSlash = 0x002f; michael@0: static const UChar gGreaterThan = 0x003e; michael@0: static const UChar gLessThan = 0x003c; michael@0: static const UChar gComma = 0x002c; michael@0: static const UChar gDot = 0x002e; michael@0: static const UChar gTick = 0x0027; michael@0: //static const UChar gMinus = 0x002d; michael@0: static const UChar gSemicolon = 0x003b; michael@0: michael@0: static const UChar gMinusX[] = {0x2D, 0x78, 0}; /* "-x" */ michael@0: static const UChar gXDotX[] = {0x78, 0x2E, 0x78, 0}; /* "x.x" */ michael@0: static const UChar gXDotZero[] = {0x78, 0x2E, 0x30, 0}; /* "x.0" */ michael@0: static const UChar gZeroDotX[] = {0x30, 0x2E, 0x78, 0}; /* "0.x" */ michael@0: michael@0: static const UChar gLessLess[] = {0x3C, 0x3C, 0}; /* "<<" */ michael@0: static const UChar gLessPercent[] = {0x3C, 0x25, 0}; /* "<%" */ michael@0: static const UChar gLessHash[] = {0x3C, 0x23, 0}; /* "<#" */ michael@0: static const UChar gLessZero[] = {0x3C, 0x30, 0}; /* "<0" */ michael@0: static const UChar gGreaterGreater[] = {0x3E, 0x3E, 0}; /* ">>" */ michael@0: static const UChar gGreaterPercent[] = {0x3E, 0x25, 0}; /* ">%" */ michael@0: static const UChar gGreaterHash[] = {0x3E, 0x23, 0}; /* ">#" */ michael@0: static const UChar gGreaterZero[] = {0x3E, 0x30, 0}; /* ">0" */ michael@0: static const UChar gEqualPercent[] = {0x3D, 0x25, 0}; /* "=%" */ michael@0: static const UChar gEqualHash[] = {0x3D, 0x23, 0}; /* "=#" */ michael@0: static const UChar gEqualZero[] = {0x3D, 0x30, 0}; /* "=0" */ michael@0: static const UChar gGreaterGreaterGreater[] = {0x3E, 0x3E, 0x3E, 0}; /* ">>>" */ michael@0: michael@0: static const UChar * const tokenStrings[] = { michael@0: gLessLess, gLessPercent, gLessHash, gLessZero, michael@0: gGreaterGreater, gGreaterPercent,gGreaterHash, gGreaterZero, michael@0: gEqualPercent, gEqualHash, gEqualZero, NULL michael@0: }; michael@0: michael@0: void michael@0: NFRule::makeRules(UnicodeString& description, michael@0: const NFRuleSet *ruleSet, michael@0: const NFRule *predecessor, michael@0: const RuleBasedNumberFormat *rbnf, michael@0: NFRuleList& rules, michael@0: UErrorCode& status) michael@0: { michael@0: // we know we're making at least one rule, so go ahead and michael@0: // new it up and initialize its basevalue and divisor michael@0: // (this also strips the rule descriptor, if any, off the michael@0: // descripton string) michael@0: NFRule* rule1 = new NFRule(rbnf); michael@0: /* test for NULL */ michael@0: if (rule1 == 0) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: return; michael@0: } michael@0: rule1->parseRuleDescriptor(description, status); michael@0: michael@0: // check the description to see whether there's text enclosed michael@0: // in brackets michael@0: int32_t brack1 = description.indexOf(gLeftBracket); michael@0: int32_t brack2 = description.indexOf(gRightBracket); michael@0: michael@0: // if the description doesn't contain a matched pair of brackets, michael@0: // or if it's of a type that doesn't recognize bracketed text, michael@0: // then leave the description alone, initialize the rule's michael@0: // rule text and substitutions, and return that rule michael@0: if (brack1 == -1 || brack2 == -1 || brack1 > brack2 michael@0: || rule1->getType() == kProperFractionRule michael@0: || rule1->getType() == kNegativeNumberRule) { michael@0: rule1->ruleText = description; michael@0: rule1->extractSubstitutions(ruleSet, predecessor, rbnf, status); michael@0: rules.add(rule1); michael@0: } else { michael@0: // if the description does contain a matched pair of brackets, michael@0: // then it's really shorthand for two rules (with one exception) michael@0: NFRule* rule2 = NULL; michael@0: UnicodeString sbuf; michael@0: michael@0: // we'll actually only split the rule into two rules if its michael@0: // base value is an even multiple of its divisor (or it's one michael@0: // of the special rules) michael@0: if ((rule1->baseValue > 0 michael@0: && (rule1->baseValue % util64_pow(rule1->radix, rule1->exponent)) == 0) michael@0: || rule1->getType() == kImproperFractionRule michael@0: || rule1->getType() == kMasterRule) { michael@0: michael@0: // if it passes that test, new up the second rule. If the michael@0: // rule set both rules will belong to is a fraction rule michael@0: // set, they both have the same base value; otherwise, michael@0: // increment the original rule's base value ("rule1" actually michael@0: // goes SECOND in the rule set's rule list) michael@0: rule2 = new NFRule(rbnf); michael@0: /* test for NULL */ michael@0: if (rule2 == 0) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: return; michael@0: } michael@0: if (rule1->baseValue >= 0) { michael@0: rule2->baseValue = rule1->baseValue; michael@0: if (!ruleSet->isFractionRuleSet()) { michael@0: ++rule1->baseValue; michael@0: } michael@0: } michael@0: michael@0: // if the description began with "x.x" and contains bracketed michael@0: // text, it describes both the improper fraction rule and michael@0: // the proper fraction rule michael@0: else if (rule1->getType() == kImproperFractionRule) { michael@0: rule2->setType(kProperFractionRule); michael@0: } michael@0: michael@0: // if the description began with "x.0" and contains bracketed michael@0: // text, it describes both the master rule and the michael@0: // improper fraction rule michael@0: else if (rule1->getType() == kMasterRule) { michael@0: rule2->baseValue = rule1->baseValue; michael@0: rule1->setType(kImproperFractionRule); michael@0: } michael@0: michael@0: // both rules have the same radix and exponent (i.e., the michael@0: // same divisor) michael@0: rule2->radix = rule1->radix; michael@0: rule2->exponent = rule1->exponent; michael@0: michael@0: // rule2's rule text omits the stuff in brackets: initalize michael@0: // its rule text and substitutions accordingly michael@0: sbuf.append(description, 0, brack1); michael@0: if (brack2 + 1 < description.length()) { michael@0: sbuf.append(description, brack2 + 1, description.length() - brack2 - 1); michael@0: } michael@0: rule2->ruleText.setTo(sbuf); michael@0: rule2->extractSubstitutions(ruleSet, predecessor, rbnf, status); michael@0: } michael@0: michael@0: // rule1's text includes the text in the brackets but omits michael@0: // the brackets themselves: initialize _its_ rule text and michael@0: // substitutions accordingly michael@0: sbuf.setTo(description, 0, brack1); michael@0: sbuf.append(description, brack1 + 1, brack2 - brack1 - 1); michael@0: if (brack2 + 1 < description.length()) { michael@0: sbuf.append(description, brack2 + 1, description.length() - brack2 - 1); michael@0: } michael@0: rule1->ruleText.setTo(sbuf); michael@0: rule1->extractSubstitutions(ruleSet, predecessor, rbnf, status); michael@0: michael@0: // if we only have one rule, return it; if we have two, return michael@0: // a two-element array containing them (notice that rule2 goes michael@0: // BEFORE rule1 in the list: in all cases, rule2 OMITS the michael@0: // material in the brackets and rule1 INCLUDES the material michael@0: // in the brackets) michael@0: if (rule2 != NULL) { michael@0: rules.add(rule2); michael@0: } michael@0: rules.add(rule1); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * This function parses the rule's rule descriptor (i.e., the base michael@0: * value and/or other tokens that precede the rule's rule text michael@0: * in the description) and sets the rule's base value, radix, and michael@0: * exponent according to the descriptor. (If the description doesn't michael@0: * include a rule descriptor, then this function sets everything to michael@0: * default values and the rule set sets the rule's real base value). michael@0: * @param description The rule's description michael@0: * @return If "description" included a rule descriptor, this is michael@0: * "description" with the descriptor and any trailing whitespace michael@0: * stripped off. Otherwise; it's "descriptor" unchangd. michael@0: */ michael@0: void michael@0: NFRule::parseRuleDescriptor(UnicodeString& description, UErrorCode& status) michael@0: { michael@0: // the description consists of a rule descriptor and a rule body, michael@0: // separated by a colon. The rule descriptor is optional. If michael@0: // it's omitted, just set the base value to 0. michael@0: int32_t p = description.indexOf(gColon); michael@0: if (p == -1) { michael@0: setBaseValue((int32_t)0, status); michael@0: } else { michael@0: // copy the descriptor out into its own string and strip it, michael@0: // along with any trailing whitespace, out of the original michael@0: // description michael@0: UnicodeString descriptor; michael@0: descriptor.setTo(description, 0, p); michael@0: michael@0: ++p; michael@0: while (p < description.length() && PatternProps::isWhiteSpace(description.charAt(p))) { michael@0: ++p; michael@0: } michael@0: description.removeBetween(0, p); michael@0: michael@0: // check first to see if the rule descriptor matches the token michael@0: // for one of the special rules. If it does, set the base michael@0: // value to the correct identfier value michael@0: if (0 == descriptor.compare(gMinusX, 2)) { michael@0: setType(kNegativeNumberRule); michael@0: } michael@0: else if (0 == descriptor.compare(gXDotX, 3)) { michael@0: setType(kImproperFractionRule); michael@0: } michael@0: else if (0 == descriptor.compare(gZeroDotX, 3)) { michael@0: setType(kProperFractionRule); michael@0: } michael@0: else if (0 == descriptor.compare(gXDotZero, 3)) { michael@0: setType(kMasterRule); michael@0: } michael@0: michael@0: // if the rule descriptor begins with a digit, it's a descriptor michael@0: // for a normal rule michael@0: // since we don't have Long.parseLong, and this isn't much work anyway, michael@0: // just build up the value as we encounter the digits. michael@0: else if (descriptor.charAt(0) >= gZero && descriptor.charAt(0) <= gNine) { michael@0: int64_t val = 0; michael@0: p = 0; michael@0: UChar c = gSpace; michael@0: michael@0: // begin parsing the descriptor: copy digits michael@0: // into "tempValue", skip periods, commas, and spaces, michael@0: // stop on a slash or > sign (or at the end of the string), michael@0: // and throw an exception on any other character michael@0: int64_t ll_10 = 10; michael@0: while (p < descriptor.length()) { michael@0: c = descriptor.charAt(p); michael@0: if (c >= gZero && c <= gNine) { michael@0: val = val * ll_10 + (int32_t)(c - gZero); michael@0: } michael@0: else if (c == gSlash || c == gGreaterThan) { michael@0: break; michael@0: } michael@0: else if (PatternProps::isWhiteSpace(c) || c == gComma || c == gDot) { michael@0: } michael@0: else { michael@0: // throw new IllegalArgumentException("Illegal character in rule descriptor"); michael@0: status = U_PARSE_ERROR; michael@0: return; michael@0: } michael@0: ++p; michael@0: } michael@0: michael@0: // we have the base value, so set it michael@0: setBaseValue(val, status); michael@0: michael@0: // if we stopped the previous loop on a slash, we're michael@0: // now parsing the rule's radix. Again, accumulate digits michael@0: // in tempValue, skip punctuation, stop on a > mark, and michael@0: // throw an exception on anything else michael@0: if (c == gSlash) { michael@0: val = 0; michael@0: ++p; michael@0: int64_t ll_10 = 10; michael@0: while (p < descriptor.length()) { michael@0: c = descriptor.charAt(p); michael@0: if (c >= gZero && c <= gNine) { michael@0: val = val * ll_10 + (int32_t)(c - gZero); michael@0: } michael@0: else if (c == gGreaterThan) { michael@0: break; michael@0: } michael@0: else if (PatternProps::isWhiteSpace(c) || c == gComma || c == gDot) { michael@0: } michael@0: else { michael@0: // throw new IllegalArgumentException("Illegal character is rule descriptor"); michael@0: status = U_PARSE_ERROR; michael@0: return; michael@0: } michael@0: ++p; michael@0: } michael@0: michael@0: // tempValue now contain's the rule's radix. Set it michael@0: // accordingly, and recalculate the rule's exponent michael@0: radix = (int32_t)val; michael@0: if (radix == 0) { michael@0: // throw new IllegalArgumentException("Rule can't have radix of 0"); michael@0: status = U_PARSE_ERROR; michael@0: } michael@0: michael@0: exponent = expectedExponent(); michael@0: } michael@0: michael@0: // if we stopped the previous loop on a > sign, then continue michael@0: // for as long as we still see > signs. For each one, michael@0: // decrement the exponent (unless the exponent is already 0). michael@0: // If we see another character before reaching the end of michael@0: // the descriptor, that's also a syntax error. michael@0: if (c == gGreaterThan) { michael@0: while (p < descriptor.length()) { michael@0: c = descriptor.charAt(p); michael@0: if (c == gGreaterThan && exponent > 0) { michael@0: --exponent; michael@0: } else { michael@0: // throw new IllegalArgumentException("Illegal character in rule descriptor"); michael@0: status = U_PARSE_ERROR; michael@0: return; michael@0: } michael@0: ++p; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // finally, if the rule body begins with an apostrophe, strip it off michael@0: // (this is generally used to put whitespace at the beginning of michael@0: // a rule's rule text) michael@0: if (description.length() > 0 && description.charAt(0) == gTick) { michael@0: description.removeBetween(0, 1); michael@0: } michael@0: michael@0: // return the description with all the stuff we've just waded through michael@0: // stripped off the front. It now contains just the rule body. michael@0: // return description; michael@0: } michael@0: michael@0: /** michael@0: * Searches the rule's rule text for the substitution tokens, michael@0: * creates the substitutions, and removes the substitution tokens michael@0: * from the rule's rule text. michael@0: * @param owner The rule set containing this rule michael@0: * @param predecessor The rule preseding this one in "owners" rule list michael@0: * @param ownersOwner The RuleBasedFormat that owns this rule michael@0: */ michael@0: void michael@0: NFRule::extractSubstitutions(const NFRuleSet* ruleSet, michael@0: const NFRule* predecessor, michael@0: const RuleBasedNumberFormat* rbnf, michael@0: UErrorCode& status) michael@0: { michael@0: if (U_SUCCESS(status)) { michael@0: sub1 = extractSubstitution(ruleSet, predecessor, rbnf, status); michael@0: sub2 = extractSubstitution(ruleSet, predecessor, rbnf, status); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Searches the rule's rule text for the first substitution token, michael@0: * creates a substitution based on it, and removes the token from michael@0: * the rule's rule text. michael@0: * @param owner The rule set containing this rule michael@0: * @param predecessor The rule preceding this one in the rule set's michael@0: * rule list michael@0: * @param ownersOwner The RuleBasedNumberFormat that owns this rule michael@0: * @return The newly-created substitution. This is never null; if michael@0: * the rule text doesn't contain any substitution tokens, this will michael@0: * be a NullSubstitution. michael@0: */ michael@0: NFSubstitution * michael@0: NFRule::extractSubstitution(const NFRuleSet* ruleSet, michael@0: const NFRule* predecessor, michael@0: const RuleBasedNumberFormat* rbnf, michael@0: UErrorCode& status) michael@0: { michael@0: NFSubstitution* result = NULL; michael@0: michael@0: // search the rule's rule text for the first two characters of michael@0: // a substitution token michael@0: int32_t subStart = indexOfAny(tokenStrings); michael@0: int32_t subEnd = subStart; michael@0: michael@0: // if we didn't find one, create a null substitution positioned michael@0: // at the end of the rule text michael@0: if (subStart == -1) { michael@0: return NFSubstitution::makeSubstitution(ruleText.length(), this, predecessor, michael@0: ruleSet, rbnf, UnicodeString(), status); michael@0: } michael@0: michael@0: // special-case the ">>>" token, since searching for the > at the michael@0: // end will actually find the > in the middle michael@0: if (ruleText.indexOf(gGreaterGreaterGreater, 3, 0) == subStart) { michael@0: subEnd = subStart + 2; michael@0: michael@0: // otherwise the substitution token ends with the same character michael@0: // it began with michael@0: } else { michael@0: UChar c = ruleText.charAt(subStart); michael@0: subEnd = ruleText.indexOf(c, subStart + 1); michael@0: // special case for '<%foo<<' michael@0: if (c == gLessThan && subEnd != -1 && subEnd < ruleText.length() - 1 && ruleText.charAt(subEnd+1) == c) { michael@0: // ordinals use "=#,##0==%abbrev=" as their rule. Notice that the '==' in the middle michael@0: // occurs because of the juxtaposition of two different rules. The check for '<' is a hack michael@0: // to get around this. Having the duplicate at the front would cause problems with michael@0: // rules like "<<%" to format, say, percents... michael@0: ++subEnd; michael@0: } michael@0: } michael@0: michael@0: // if we don't find the end of the token (i.e., if we're on a single, michael@0: // unmatched token character), create a null substitution positioned michael@0: // at the end of the rule michael@0: if (subEnd == -1) { michael@0: return NFSubstitution::makeSubstitution(ruleText.length(), this, predecessor, michael@0: ruleSet, rbnf, UnicodeString(), status); michael@0: } michael@0: michael@0: // if we get here, we have a real substitution token (or at least michael@0: // some text bounded by substitution token characters). Use michael@0: // makeSubstitution() to create the right kind of substitution michael@0: UnicodeString subToken; michael@0: subToken.setTo(ruleText, subStart, subEnd + 1 - subStart); michael@0: result = NFSubstitution::makeSubstitution(subStart, this, predecessor, ruleSet, michael@0: rbnf, subToken, status); michael@0: michael@0: // remove the substitution from the rule text michael@0: ruleText.removeBetween(subStart, subEnd+1); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: /** michael@0: * Sets the rule's base value, and causes the radix and exponent michael@0: * to be recalculated. This is used during construction when we michael@0: * don't know the rule's base value until after it's been michael@0: * constructed. It should be used at any other time. michael@0: * @param The new base value for the rule. michael@0: */ michael@0: void michael@0: NFRule::setBaseValue(int64_t newBaseValue, UErrorCode& status) michael@0: { michael@0: // set the base value michael@0: baseValue = newBaseValue; michael@0: michael@0: // if this isn't a special rule, recalculate the radix and exponent michael@0: // (the radix always defaults to 10; if it's supposed to be something michael@0: // else, it's cleaned up by the caller and the exponent is michael@0: // recalculated again-- the only function that does this is michael@0: // NFRule.parseRuleDescriptor() ) michael@0: if (baseValue >= 1) { michael@0: radix = 10; michael@0: exponent = expectedExponent(); michael@0: michael@0: // this function gets called on a fully-constructed rule whose michael@0: // description didn't specify a base value. This means it michael@0: // has substitutions, and some substitutions hold on to copies michael@0: // of the rule's divisor. Fix their copies of the divisor. michael@0: if (sub1 != NULL) { michael@0: sub1->setDivisor(radix, exponent, status); michael@0: } michael@0: if (sub2 != NULL) { michael@0: sub2->setDivisor(radix, exponent, status); michael@0: } michael@0: michael@0: // if this is a special rule, its radix and exponent are basically michael@0: // ignored. Set them to "safe" default values michael@0: } else { michael@0: radix = 10; michael@0: exponent = 0; michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * This calculates the rule's exponent based on its radix and base michael@0: * value. This will be the highest power the radix can be raised to michael@0: * and still produce a result less than or equal to the base value. michael@0: */ michael@0: int16_t michael@0: NFRule::expectedExponent() const michael@0: { michael@0: // since the log of 0, or the log base 0 of something, causes an michael@0: // error, declare the exponent in these cases to be 0 (we also michael@0: // deal with the special-rule identifiers here) michael@0: if (radix == 0 || baseValue < 1) { michael@0: return 0; michael@0: } michael@0: michael@0: // we get rounding error in some cases-- for example, log 1000 / log 10 michael@0: // gives us 1.9999999996 instead of 2. The extra logic here is to take michael@0: // that into account michael@0: int16_t tempResult = (int16_t)(uprv_log((double)baseValue) / uprv_log((double)radix)); michael@0: int64_t temp = util64_pow(radix, tempResult + 1); michael@0: if (temp <= baseValue) { michael@0: tempResult += 1; michael@0: } michael@0: return tempResult; michael@0: } michael@0: michael@0: /** michael@0: * Searches the rule's rule text for any of the specified strings. michael@0: * @param strings An array of strings to search the rule's rule michael@0: * text for michael@0: * @return The index of the first match in the rule's rule text michael@0: * (i.e., the first substring in the rule's rule text that matches michael@0: * _any_ of the strings in "strings"). If none of the strings in michael@0: * "strings" is found in the rule's rule text, returns -1. michael@0: */ michael@0: int32_t michael@0: NFRule::indexOfAny(const UChar* const strings[]) const michael@0: { michael@0: int result = -1; michael@0: for (int i = 0; strings[i]; i++) { michael@0: int32_t pos = ruleText.indexOf(*strings[i]); michael@0: if (pos != -1 && (result == -1 || pos < result)) { michael@0: result = pos; michael@0: } michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------- michael@0: // boilerplate michael@0: //----------------------------------------------------------------------- michael@0: michael@0: /** michael@0: * Tests two rules for equality. michael@0: * @param that The rule to compare this one against michael@0: * @return True is the two rules are functionally equivalent michael@0: */ michael@0: UBool michael@0: NFRule::operator==(const NFRule& rhs) const michael@0: { michael@0: return baseValue == rhs.baseValue michael@0: && radix == rhs.radix michael@0: && exponent == rhs.exponent michael@0: && ruleText == rhs.ruleText michael@0: && *sub1 == *rhs.sub1 michael@0: && *sub2 == *rhs.sub2; michael@0: } michael@0: michael@0: /** michael@0: * Returns a textual representation of the rule. This won't michael@0: * necessarily be the same as the description that this rule michael@0: * was created with, but it will produce the same result. michael@0: * @return A textual description of the rule michael@0: */ michael@0: static void util_append64(UnicodeString& result, int64_t n) michael@0: { michael@0: UChar buffer[256]; michael@0: int32_t len = util64_tou(n, buffer, sizeof(buffer)); michael@0: UnicodeString temp(buffer, len); michael@0: result.append(temp); michael@0: } michael@0: michael@0: void michael@0: NFRule::_appendRuleText(UnicodeString& result) const michael@0: { michael@0: switch (getType()) { michael@0: case kNegativeNumberRule: result.append(gMinusX, 2); break; michael@0: case kImproperFractionRule: result.append(gXDotX, 3); break; michael@0: case kProperFractionRule: result.append(gZeroDotX, 3); break; michael@0: case kMasterRule: result.append(gXDotZero, 3); break; michael@0: default: michael@0: // for a normal rule, write out its base value, and if the radix is michael@0: // something other than 10, write out the radix (with the preceding michael@0: // slash, of course). Then calculate the expected exponent and if michael@0: // if isn't the same as the actual exponent, write an appropriate michael@0: // number of > signs. Finally, terminate the whole thing with michael@0: // a colon. michael@0: util_append64(result, baseValue); michael@0: if (radix != 10) { michael@0: result.append(gSlash); michael@0: util_append64(result, radix); michael@0: } michael@0: int numCarets = expectedExponent() - exponent; michael@0: for (int i = 0; i < numCarets; i++) { michael@0: result.append(gGreaterThan); michael@0: } michael@0: break; michael@0: } michael@0: result.append(gColon); michael@0: result.append(gSpace); michael@0: michael@0: // if the rule text begins with a space, write an apostrophe michael@0: // (whitespace after the rule descriptor is ignored; the michael@0: // apostrophe is used to make the whitespace significant) michael@0: if (ruleText.charAt(0) == gSpace && sub1->getPos() != 0) { michael@0: result.append(gTick); michael@0: } michael@0: michael@0: // now, write the rule's rule text, inserting appropriate michael@0: // substitution tokens in the appropriate places michael@0: UnicodeString ruleTextCopy; michael@0: ruleTextCopy.setTo(ruleText); michael@0: michael@0: UnicodeString temp; michael@0: sub2->toString(temp); michael@0: ruleTextCopy.insert(sub2->getPos(), temp); michael@0: sub1->toString(temp); michael@0: ruleTextCopy.insert(sub1->getPos(), temp); michael@0: michael@0: result.append(ruleTextCopy); michael@0: michael@0: // and finally, top the whole thing off with a semicolon and michael@0: // return the result michael@0: result.append(gSemicolon); michael@0: } michael@0: michael@0: //----------------------------------------------------------------------- michael@0: // formatting michael@0: //----------------------------------------------------------------------- michael@0: michael@0: /** michael@0: * Formats the number, and inserts the resulting text into michael@0: * toInsertInto. michael@0: * @param number The number being formatted michael@0: * @param toInsertInto The string where the resultant text should michael@0: * be inserted michael@0: * @param pos The position in toInsertInto where the resultant text michael@0: * should be inserted michael@0: */ michael@0: void michael@0: NFRule::doFormat(int64_t number, UnicodeString& toInsertInto, int32_t pos) const michael@0: { michael@0: // first, insert the rule's rule text into toInsertInto at the michael@0: // specified position, then insert the results of the substitutions michael@0: // into the right places in toInsertInto (notice we do the michael@0: // substitutions in reverse order so that the offsets don't get michael@0: // messed up) michael@0: toInsertInto.insert(pos, ruleText); michael@0: sub2->doSubstitution(number, toInsertInto, pos); michael@0: sub1->doSubstitution(number, toInsertInto, pos); michael@0: } michael@0: michael@0: /** michael@0: * Formats the number, and inserts the resulting text into michael@0: * toInsertInto. michael@0: * @param number The number being formatted michael@0: * @param toInsertInto The string where the resultant text should michael@0: * be inserted michael@0: * @param pos The position in toInsertInto where the resultant text michael@0: * should be inserted michael@0: */ michael@0: void michael@0: NFRule::doFormat(double number, UnicodeString& toInsertInto, int32_t pos) const michael@0: { michael@0: // first, insert the rule's rule text into toInsertInto at the michael@0: // specified position, then insert the results of the substitutions michael@0: // into the right places in toInsertInto michael@0: // [again, we have two copies of this routine that do the same thing michael@0: // so that we don't sacrifice precision in a long by casting it michael@0: // to a double] michael@0: toInsertInto.insert(pos, ruleText); michael@0: sub2->doSubstitution(number, toInsertInto, pos); michael@0: sub1->doSubstitution(number, toInsertInto, pos); michael@0: } michael@0: michael@0: /** michael@0: * Used by the owning rule set to determine whether to invoke the michael@0: * rollback rule (i.e., whether this rule or the one that precedes michael@0: * it in the rule set's list should be used to format the number) michael@0: * @param The number being formatted michael@0: * @return True if the rule set should use the rule that precedes michael@0: * this one in its list; false if it should use this rule michael@0: */ michael@0: UBool michael@0: NFRule::shouldRollBack(double number) const michael@0: { michael@0: // we roll back if the rule contains a modulus substitution, michael@0: // the number being formatted is an even multiple of the rule's michael@0: // divisor, and the rule's base value is NOT an even multiple michael@0: // of its divisor michael@0: // In other words, if the original description had michael@0: // 100: << hundred[ >>]; michael@0: // that expands into michael@0: // 100: << hundred; michael@0: // 101: << hundred >>; michael@0: // internally. But when we're formatting 200, if we use the rule michael@0: // at 101, which would normally apply, we get "two hundred zero". michael@0: // To prevent this, we roll back and use the rule at 100 instead. michael@0: // This is the logic that makes this happen: the rule at 101 has michael@0: // a modulus substitution, its base value isn't an even multiple michael@0: // of 100, and the value we're trying to format _is_ an even michael@0: // multiple of 100. This is called the "rollback rule." michael@0: if ((sub1->isModulusSubstitution()) || (sub2->isModulusSubstitution())) { michael@0: int64_t re = util64_pow(radix, exponent); michael@0: return uprv_fmod(number, (double)re) == 0 && (baseValue % re) != 0; michael@0: } michael@0: return FALSE; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------- michael@0: // parsing michael@0: //----------------------------------------------------------------------- michael@0: michael@0: /** michael@0: * Attempts to parse the string with this rule. michael@0: * @param text The string being parsed michael@0: * @param parsePosition On entry, the value is ignored and assumed to michael@0: * be 0. On exit, this has been updated with the position of the first michael@0: * character not consumed by matching the text against this rule michael@0: * (if this rule doesn't match the text at all, the parse position michael@0: * if left unchanged (presumably at 0) and the function returns michael@0: * new Long(0)). michael@0: * @param isFractionRule True if this rule is contained within a michael@0: * fraction rule set. This is only used if the rule has no michael@0: * substitutions. michael@0: * @return If this rule matched the text, this is the rule's base value michael@0: * combined appropriately with the results of parsing the substitutions. michael@0: * If nothing matched, this is new Long(0) and the parse position is michael@0: * left unchanged. The result will be an instance of Long if the michael@0: * result is an integer and Double otherwise. The result is never null. michael@0: */ michael@0: #ifdef RBNF_DEBUG michael@0: #include michael@0: michael@0: static void dumpUS(FILE* f, const UnicodeString& us) { michael@0: int len = us.length(); michael@0: char* buf = (char *)uprv_malloc((len+1)*sizeof(char)); //new char[len+1]; michael@0: if (buf != NULL) { michael@0: us.extract(0, len, buf); michael@0: buf[len] = 0; michael@0: fprintf(f, "%s", buf); michael@0: uprv_free(buf); //delete[] buf; michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: UBool michael@0: NFRule::doParse(const UnicodeString& text, michael@0: ParsePosition& parsePosition, michael@0: UBool isFractionRule, michael@0: double upperBound, michael@0: Formattable& resVal) const michael@0: { michael@0: // internally we operate on a copy of the string being parsed michael@0: // (because we're going to change it) and use our own ParsePosition michael@0: ParsePosition pp; michael@0: UnicodeString workText(text); michael@0: michael@0: // check to see whether the text before the first substitution michael@0: // matches the text at the beginning of the string being michael@0: // parsed. If it does, strip that off the front of workText; michael@0: // otherwise, dump out with a mismatch michael@0: UnicodeString prefix; michael@0: prefix.setTo(ruleText, 0, sub1->getPos()); michael@0: michael@0: #ifdef RBNF_DEBUG michael@0: fprintf(stderr, "doParse %x ", this); michael@0: { michael@0: UnicodeString rt; michael@0: _appendRuleText(rt); michael@0: dumpUS(stderr, rt); michael@0: } michael@0: michael@0: fprintf(stderr, " text: '", this); michael@0: dumpUS(stderr, text); michael@0: fprintf(stderr, "' prefix: '"); michael@0: dumpUS(stderr, prefix); michael@0: #endif michael@0: stripPrefix(workText, prefix, pp); michael@0: int32_t prefixLength = text.length() - workText.length(); michael@0: michael@0: #ifdef RBNF_DEBUG michael@0: fprintf(stderr, "' pl: %d ppi: %d s1p: %d\n", prefixLength, pp.getIndex(), sub1->getPos()); michael@0: #endif michael@0: michael@0: if (pp.getIndex() == 0 && sub1->getPos() != 0) { michael@0: // commented out because ParsePosition doesn't have error index in 1.1.x michael@0: // restored for ICU4C port michael@0: parsePosition.setErrorIndex(pp.getErrorIndex()); michael@0: resVal.setLong(0); michael@0: return TRUE; michael@0: } michael@0: michael@0: // this is the fun part. The basic guts of the rule-matching michael@0: // logic is matchToDelimiter(), which is called twice. The first michael@0: // time it searches the input string for the rule text BETWEEN michael@0: // the substitutions and tries to match the intervening text michael@0: // in the input string with the first substitution. If that michael@0: // succeeds, it then calls it again, this time to look for the michael@0: // rule text after the second substitution and to match the michael@0: // intervening input text against the second substitution. michael@0: // michael@0: // For example, say we have a rule that looks like this: michael@0: // first << middle >> last; michael@0: // and input text that looks like this: michael@0: // first one middle two last michael@0: // First we use stripPrefix() to match "first " in both places and michael@0: // strip it off the front, leaving michael@0: // one middle two last michael@0: // Then we use matchToDelimiter() to match " middle " and try to michael@0: // match "one" against a substitution. If it's successful, we now michael@0: // have michael@0: // two last michael@0: // We use matchToDelimiter() a second time to match " last" and michael@0: // try to match "two" against a substitution. If "two" matches michael@0: // the substitution, we have a successful parse. michael@0: // michael@0: // Since it's possible in many cases to find multiple instances michael@0: // of each of these pieces of rule text in the input string, michael@0: // we need to try all the possible combinations of these michael@0: // locations. This prevents us from prematurely declaring a mismatch, michael@0: // and makes sure we match as much input text as we can. michael@0: int highWaterMark = 0; michael@0: double result = 0; michael@0: int start = 0; michael@0: double tempBaseValue = (double)(baseValue <= 0 ? 0 : baseValue); michael@0: michael@0: UnicodeString temp; michael@0: do { michael@0: // our partial parse result starts out as this rule's base michael@0: // value. If it finds a successful match, matchToDelimiter() michael@0: // will compose this in some way with what it gets back from michael@0: // the substitution, giving us a new partial parse result michael@0: pp.setIndex(0); michael@0: michael@0: temp.setTo(ruleText, sub1->getPos(), sub2->getPos() - sub1->getPos()); michael@0: double partialResult = matchToDelimiter(workText, start, tempBaseValue, michael@0: temp, pp, sub1, michael@0: upperBound); michael@0: michael@0: // if we got a successful match (or were trying to match a michael@0: // null substitution), pp is now pointing at the first unmatched michael@0: // character. Take note of that, and try matchToDelimiter() michael@0: // on the input text again michael@0: if (pp.getIndex() != 0 || sub1->isNullSubstitution()) { michael@0: start = pp.getIndex(); michael@0: michael@0: UnicodeString workText2; michael@0: workText2.setTo(workText, pp.getIndex(), workText.length() - pp.getIndex()); michael@0: ParsePosition pp2; michael@0: michael@0: // the second matchToDelimiter() will compose our previous michael@0: // partial result with whatever it gets back from its michael@0: // substitution if there's a successful match, giving us michael@0: // a real result michael@0: temp.setTo(ruleText, sub2->getPos(), ruleText.length() - sub2->getPos()); michael@0: partialResult = matchToDelimiter(workText2, 0, partialResult, michael@0: temp, pp2, sub2, michael@0: upperBound); michael@0: michael@0: // if we got a successful match on this second michael@0: // matchToDelimiter() call, update the high-water mark michael@0: // and result (if necessary) michael@0: if (pp2.getIndex() != 0 || sub2->isNullSubstitution()) { michael@0: if (prefixLength + pp.getIndex() + pp2.getIndex() > highWaterMark) { michael@0: highWaterMark = prefixLength + pp.getIndex() + pp2.getIndex(); michael@0: result = partialResult; michael@0: } michael@0: } michael@0: // commented out because ParsePosition doesn't have error index in 1.1.x michael@0: // restored for ICU4C port michael@0: else { michael@0: int32_t temp = pp2.getErrorIndex() + sub1->getPos() + pp.getIndex(); michael@0: if (temp> parsePosition.getErrorIndex()) { michael@0: parsePosition.setErrorIndex(temp); michael@0: } michael@0: } michael@0: } michael@0: // commented out because ParsePosition doesn't have error index in 1.1.x michael@0: // restored for ICU4C port michael@0: else { michael@0: int32_t temp = sub1->getPos() + pp.getErrorIndex(); michael@0: if (temp > parsePosition.getErrorIndex()) { michael@0: parsePosition.setErrorIndex(temp); michael@0: } michael@0: } michael@0: // keep trying to match things until the outer matchToDelimiter() michael@0: // call fails to make a match (each time, it picks up where it michael@0: // left off the previous time) michael@0: } while (sub1->getPos() != sub2->getPos() michael@0: && pp.getIndex() > 0 michael@0: && pp.getIndex() < workText.length() michael@0: && pp.getIndex() != start); michael@0: michael@0: // update the caller's ParsePosition with our high-water mark michael@0: // (i.e., it now points at the first character this function michael@0: // didn't match-- the ParsePosition is therefore unchanged if michael@0: // we didn't match anything) michael@0: parsePosition.setIndex(highWaterMark); michael@0: // commented out because ParsePosition doesn't have error index in 1.1.x michael@0: // restored for ICU4C port michael@0: if (highWaterMark > 0) { michael@0: parsePosition.setErrorIndex(0); michael@0: } michael@0: michael@0: // this is a hack for one unusual condition: Normally, whether this michael@0: // rule belong to a fraction rule set or not is handled by its michael@0: // substitutions. But if that rule HAS NO substitutions, then michael@0: // we have to account for it here. By definition, if the matching michael@0: // rule in a fraction rule set has no substitutions, its numerator michael@0: // is 1, and so the result is the reciprocal of its base value. michael@0: if (isFractionRule && michael@0: highWaterMark > 0 && michael@0: sub1->isNullSubstitution()) { michael@0: result = 1 / result; michael@0: } michael@0: michael@0: resVal.setDouble(result); michael@0: return TRUE; // ??? do we need to worry if it is a long or a double? michael@0: } michael@0: michael@0: /** michael@0: * This function is used by parse() to match the text being parsed michael@0: * against a possible prefix string. This function michael@0: * matches characters from the beginning of the string being parsed michael@0: * to characters from the prospective prefix. If they match, pp is michael@0: * updated to the first character not matched, and the result is michael@0: * the unparsed part of the string. If they don't match, the whole michael@0: * string is returned, and pp is left unchanged. michael@0: * @param text The string being parsed michael@0: * @param prefix The text to match against michael@0: * @param pp On entry, ignored and assumed to be 0. On exit, points michael@0: * to the first unmatched character (assuming the whole prefix matched), michael@0: * or is unchanged (if the whole prefix didn't match). michael@0: * @return If things match, this is the unparsed part of "text"; michael@0: * if they didn't match, this is "text". michael@0: */ michael@0: void michael@0: NFRule::stripPrefix(UnicodeString& text, const UnicodeString& prefix, ParsePosition& pp) const michael@0: { michael@0: // if the prefix text is empty, dump out without doing anything michael@0: if (prefix.length() != 0) { michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: // use prefixLength() to match the beginning of michael@0: // "text" against "prefix". This function returns the michael@0: // number of characters from "text" that matched (or 0 if michael@0: // we didn't match the whole prefix) michael@0: int32_t pfl = prefixLength(text, prefix, status); michael@0: if (U_FAILURE(status)) { // Memory allocation error. michael@0: return; michael@0: } michael@0: if (pfl != 0) { michael@0: // if we got a successful match, update the parse position michael@0: // and strip the prefix off of "text" michael@0: pp.setIndex(pp.getIndex() + pfl); michael@0: text.remove(0, pfl); michael@0: } michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Used by parse() to match a substitution and any following text. michael@0: * "text" is searched for instances of "delimiter". For each instance michael@0: * of delimiter, the intervening text is tested to see whether it michael@0: * matches the substitution. The longest match wins. michael@0: * @param text The string being parsed michael@0: * @param startPos The position in "text" where we should start looking michael@0: * for "delimiter". michael@0: * @param baseValue A partial parse result (often the rule's base value), michael@0: * which is combined with the result from matching the substitution michael@0: * @param delimiter The string to search "text" for. michael@0: * @param pp Ignored and presumed to be 0 on entry. If there's a match, michael@0: * on exit this will point to the first unmatched character. michael@0: * @param sub If we find "delimiter" in "text", this substitution is used michael@0: * to match the text between the beginning of the string and the michael@0: * position of "delimiter." (If "delimiter" is the empty string, then michael@0: * this function just matches against this substitution and updates michael@0: * everything accordingly.) michael@0: * @param upperBound When matching the substitution, it will only michael@0: * consider rules with base values lower than this value. michael@0: * @return If there's a match, this is the result of composing michael@0: * baseValue with the result of matching the substitution. Otherwise, michael@0: * this is new Long(0). It's never null. If the result is an integer, michael@0: * this will be an instance of Long; otherwise, it's an instance of michael@0: * Double. michael@0: * michael@0: * !!! note {dlf} in point of fact, in the java code the caller always converts michael@0: * the result to a double, so we might as well return one. michael@0: */ michael@0: double michael@0: NFRule::matchToDelimiter(const UnicodeString& text, michael@0: int32_t startPos, michael@0: double _baseValue, michael@0: const UnicodeString& delimiter, michael@0: ParsePosition& pp, michael@0: const NFSubstitution* sub, michael@0: double upperBound) const michael@0: { michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: // if "delimiter" contains real (i.e., non-ignorable) text, search michael@0: // it for "delimiter" beginning at "start". If that succeeds, then michael@0: // use "sub"'s doParse() method to match the text before the michael@0: // instance of "delimiter" we just found. michael@0: if (!allIgnorable(delimiter, status)) { michael@0: if (U_FAILURE(status)) { //Memory allocation error. michael@0: return 0; michael@0: } michael@0: ParsePosition tempPP; michael@0: Formattable result; michael@0: michael@0: // use findText() to search for "delimiter". It returns a two- michael@0: // element array: element 0 is the position of the match, and michael@0: // element 1 is the number of characters that matched michael@0: // "delimiter". michael@0: int32_t dLen; michael@0: int32_t dPos = findText(text, delimiter, startPos, &dLen); michael@0: michael@0: // if findText() succeeded, isolate the text preceding the michael@0: // match, and use "sub" to match that text michael@0: while (dPos >= 0) { michael@0: UnicodeString subText; michael@0: subText.setTo(text, 0, dPos); michael@0: if (subText.length() > 0) { michael@0: UBool success = sub->doParse(subText, tempPP, _baseValue, upperBound, michael@0: #if UCONFIG_NO_COLLATION michael@0: FALSE, michael@0: #else michael@0: formatter->isLenient(), michael@0: #endif michael@0: result); michael@0: michael@0: // if the substitution could match all the text up to michael@0: // where we found "delimiter", then this function has michael@0: // a successful match. Bump the caller's parse position michael@0: // to point to the first character after the text michael@0: // that matches "delimiter", and return the result michael@0: // we got from parsing the substitution. michael@0: if (success && tempPP.getIndex() == dPos) { michael@0: pp.setIndex(dPos + dLen); michael@0: return result.getDouble(); michael@0: } michael@0: // commented out because ParsePosition doesn't have error index in 1.1.x michael@0: // restored for ICU4C port michael@0: else { michael@0: if (tempPP.getErrorIndex() > 0) { michael@0: pp.setErrorIndex(tempPP.getErrorIndex()); michael@0: } else { michael@0: pp.setErrorIndex(tempPP.getIndex()); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // if we didn't match the substitution, search for another michael@0: // copy of "delimiter" in "text" and repeat the loop if michael@0: // we find it michael@0: tempPP.setIndex(0); michael@0: dPos = findText(text, delimiter, dPos + dLen, &dLen); michael@0: } michael@0: // if we make it here, this was an unsuccessful match, and we michael@0: // leave pp unchanged and return 0 michael@0: pp.setIndex(0); michael@0: return 0; michael@0: michael@0: // if "delimiter" is empty, or consists only of ignorable characters michael@0: // (i.e., is semantically empty), thwe we obviously can't search michael@0: // for "delimiter". Instead, just use "sub" to parse as much of michael@0: // "text" as possible. michael@0: } else { michael@0: ParsePosition tempPP; michael@0: Formattable result; michael@0: michael@0: // try to match the whole string against the substitution michael@0: UBool success = sub->doParse(text, tempPP, _baseValue, upperBound, michael@0: #if UCONFIG_NO_COLLATION michael@0: FALSE, michael@0: #else michael@0: formatter->isLenient(), michael@0: #endif michael@0: result); michael@0: if (success && (tempPP.getIndex() != 0 || sub->isNullSubstitution())) { michael@0: // if there's a successful match (or it's a null michael@0: // substitution), update pp to point to the first michael@0: // character we didn't match, and pass the result from michael@0: // sub.doParse() on through to the caller michael@0: pp.setIndex(tempPP.getIndex()); michael@0: return result.getDouble(); michael@0: } michael@0: // commented out because ParsePosition doesn't have error index in 1.1.x michael@0: // restored for ICU4C port michael@0: else { michael@0: pp.setErrorIndex(tempPP.getErrorIndex()); michael@0: } michael@0: michael@0: // and if we get to here, then nothing matched, so we return michael@0: // 0 and leave pp alone michael@0: return 0; michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Used by stripPrefix() to match characters. If lenient parse mode michael@0: * is off, this just calls startsWith(). If lenient parse mode is on, michael@0: * this function uses CollationElementIterators to match characters in michael@0: * the strings (only primary-order differences are significant in michael@0: * determining whether there's a match). michael@0: * @param str The string being tested michael@0: * @param prefix The text we're hoping to see at the beginning michael@0: * of "str" michael@0: * @return If "prefix" is found at the beginning of "str", this michael@0: * is the number of characters in "str" that were matched (this michael@0: * isn't necessarily the same as the length of "prefix" when matching michael@0: * text with a collator). If there's no match, this is 0. michael@0: */ michael@0: int32_t michael@0: NFRule::prefixLength(const UnicodeString& str, const UnicodeString& prefix, UErrorCode& status) const michael@0: { michael@0: // if we're looking for an empty prefix, it obviously matches michael@0: // zero characters. Just go ahead and return 0. michael@0: if (prefix.length() == 0) { michael@0: return 0; michael@0: } michael@0: michael@0: #if !UCONFIG_NO_COLLATION michael@0: // go through all this grief if we're in lenient-parse mode michael@0: if (formatter->isLenient()) { michael@0: // get the formatter's collator and use it to create two michael@0: // collation element iterators, one over the target string michael@0: // and another over the prefix (right now, we'll throw an michael@0: // exception if the collator we get back from the formatter michael@0: // isn't a RuleBasedCollator, because RuleBasedCollator defines michael@0: // the CollationElementIterator protocol. Hopefully, this michael@0: // will change someday.) michael@0: RuleBasedCollator* collator = (RuleBasedCollator*)formatter->getCollator(); michael@0: CollationElementIterator* strIter = collator->createCollationElementIterator(str); michael@0: CollationElementIterator* prefixIter = collator->createCollationElementIterator(prefix); michael@0: // Check for memory allocation error. michael@0: if (collator == NULL || strIter == NULL || prefixIter == NULL) { michael@0: delete collator; michael@0: delete strIter; michael@0: delete prefixIter; michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: return 0; michael@0: } michael@0: michael@0: UErrorCode err = U_ZERO_ERROR; michael@0: michael@0: // The original code was problematic. Consider this match: michael@0: // prefix = "fifty-" michael@0: // string = " fifty-7" michael@0: // The intent is to match string up to the '7', by matching 'fifty-' at position 1 michael@0: // in the string. Unfortunately, we were getting a match, and then computing where michael@0: // the match terminated by rematching the string. The rematch code was using as an michael@0: // initial guess the substring of string between 0 and prefix.length. Because of michael@0: // the leading space and trailing hyphen (both ignorable) this was succeeding, leaving michael@0: // the position before the hyphen in the string. Recursing down, we then parsed the michael@0: // remaining string '-7' as numeric. The resulting number turned out as 43 (50 - 7). michael@0: // This was not pretty, especially since the string "fifty-7" parsed just fine. michael@0: // michael@0: // We have newer APIs now, so we can use calls on the iterator to determine what we michael@0: // matched up to. If we terminate because we hit the last element in the string, michael@0: // our match terminates at this length. If we terminate because we hit the last element michael@0: // in the target, our match terminates at one before the element iterator position. michael@0: michael@0: // match collation elements between the strings michael@0: int32_t oStr = strIter->next(err); michael@0: int32_t oPrefix = prefixIter->next(err); michael@0: michael@0: while (oPrefix != CollationElementIterator::NULLORDER) { michael@0: // skip over ignorable characters in the target string michael@0: while (CollationElementIterator::primaryOrder(oStr) == 0 michael@0: && oStr != CollationElementIterator::NULLORDER) { michael@0: oStr = strIter->next(err); michael@0: } michael@0: michael@0: // skip over ignorable characters in the prefix michael@0: while (CollationElementIterator::primaryOrder(oPrefix) == 0 michael@0: && oPrefix != CollationElementIterator::NULLORDER) { michael@0: oPrefix = prefixIter->next(err); michael@0: } michael@0: michael@0: // dlf: move this above following test, if we consume the michael@0: // entire target, aren't we ok even if the source was also michael@0: // entirely consumed? michael@0: michael@0: // if skipping over ignorables brought to the end of michael@0: // the prefix, we DID match: drop out of the loop michael@0: if (oPrefix == CollationElementIterator::NULLORDER) { michael@0: break; michael@0: } michael@0: michael@0: // if skipping over ignorables brought us to the end michael@0: // of the target string, we didn't match and return 0 michael@0: if (oStr == CollationElementIterator::NULLORDER) { michael@0: delete prefixIter; michael@0: delete strIter; michael@0: return 0; michael@0: } michael@0: michael@0: // match collation elements from the two strings michael@0: // (considering only primary differences). If we michael@0: // get a mismatch, dump out and return 0 michael@0: if (CollationElementIterator::primaryOrder(oStr) michael@0: != CollationElementIterator::primaryOrder(oPrefix)) { michael@0: delete prefixIter; michael@0: delete strIter; michael@0: return 0; michael@0: michael@0: // otherwise, advance to the next character in each string michael@0: // and loop (we drop out of the loop when we exhaust michael@0: // collation elements in the prefix) michael@0: } else { michael@0: oStr = strIter->next(err); michael@0: oPrefix = prefixIter->next(err); michael@0: } michael@0: } michael@0: michael@0: int32_t result = strIter->getOffset(); michael@0: if (oStr != CollationElementIterator::NULLORDER) { michael@0: --result; // back over character that we don't want to consume; michael@0: } michael@0: michael@0: #ifdef RBNF_DEBUG michael@0: fprintf(stderr, "prefix length: %d\n", result); michael@0: #endif michael@0: delete prefixIter; michael@0: delete strIter; michael@0: michael@0: return result; michael@0: #if 0 michael@0: //---------------------------------------------------------------- michael@0: // JDK 1.2-specific API call michael@0: // return strIter.getOffset(); michael@0: //---------------------------------------------------------------- michael@0: // JDK 1.1 HACK (take out for 1.2-specific code) michael@0: michael@0: // if we make it to here, we have a successful match. Now we michael@0: // have to find out HOW MANY characters from the target string michael@0: // matched the prefix (there isn't necessarily a one-to-one michael@0: // mapping between collation elements and characters). michael@0: // In JDK 1.2, there's a simple getOffset() call we can use. michael@0: // In JDK 1.1, on the other hand, we have to go through some michael@0: // ugly contortions. First, use the collator to compare the michael@0: // same number of characters from the prefix and target string. michael@0: // If they're equal, we're done. michael@0: collator->setStrength(Collator::PRIMARY); michael@0: if (str.length() >= prefix.length()) { michael@0: UnicodeString temp; michael@0: temp.setTo(str, 0, prefix.length()); michael@0: if (collator->equals(temp, prefix)) { michael@0: #ifdef RBNF_DEBUG michael@0: fprintf(stderr, "returning: %d\n", prefix.length()); michael@0: #endif michael@0: return prefix.length(); michael@0: } michael@0: } michael@0: michael@0: // if they're not equal, then we have to compare successively michael@0: // larger and larger substrings of the target string until we michael@0: // get to one that matches the prefix. At that point, we know michael@0: // how many characters matched the prefix, and we can return. michael@0: int32_t p = 1; michael@0: while (p <= str.length()) { michael@0: UnicodeString temp; michael@0: temp.setTo(str, 0, p); michael@0: if (collator->equals(temp, prefix)) { michael@0: return p; michael@0: } else { michael@0: ++p; michael@0: } michael@0: } michael@0: michael@0: // SHOULD NEVER GET HERE!!! michael@0: return 0; michael@0: //---------------------------------------------------------------- michael@0: #endif michael@0: michael@0: // If lenient parsing is turned off, forget all that crap above. michael@0: // Just use String.startsWith() and be done with it. michael@0: } else michael@0: #endif michael@0: { michael@0: if (str.startsWith(prefix)) { michael@0: return prefix.length(); michael@0: } else { michael@0: return 0; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Searches a string for another string. If lenient parsing is off, michael@0: * this just calls indexOf(). If lenient parsing is on, this function michael@0: * uses CollationElementIterator to match characters, and only michael@0: * primary-order differences are significant in determining whether michael@0: * there's a match. michael@0: * @param str The string to search michael@0: * @param key The string to search "str" for michael@0: * @param startingAt The index into "str" where the search is to michael@0: * begin michael@0: * @return A two-element array of ints. Element 0 is the position michael@0: * of the match, or -1 if there was no match. Element 1 is the michael@0: * number of characters in "str" that matched (which isn't necessarily michael@0: * the same as the length of "key") michael@0: */ michael@0: int32_t michael@0: NFRule::findText(const UnicodeString& str, michael@0: const UnicodeString& key, michael@0: int32_t startingAt, michael@0: int32_t* length) const michael@0: { michael@0: #if !UCONFIG_NO_COLLATION michael@0: // if lenient parsing is turned off, this is easy: just call michael@0: // String.indexOf() and we're done michael@0: if (!formatter->isLenient()) { michael@0: *length = key.length(); michael@0: return str.indexOf(key, startingAt); michael@0: michael@0: // but if lenient parsing is turned ON, we've got some work michael@0: // ahead of us michael@0: } else michael@0: #endif michael@0: { michael@0: //---------------------------------------------------------------- michael@0: // JDK 1.1 HACK (take out of 1.2-specific code) michael@0: michael@0: // in JDK 1.2, CollationElementIterator provides us with an michael@0: // API to map between character offsets and collation elements michael@0: // and we can do this by marching through the string comparing michael@0: // collation elements. We can't do that in JDK 1.1. Insted, michael@0: // we have to go through this horrible slow mess: michael@0: int32_t p = startingAt; michael@0: int32_t keyLen = 0; michael@0: michael@0: // basically just isolate smaller and smaller substrings of michael@0: // the target string (each running to the end of the string, michael@0: // and with the first one running from startingAt to the end) michael@0: // and then use prefixLength() to see if the search key is at michael@0: // the beginning of each substring. This is excruciatingly michael@0: // slow, but it will locate the key and tell use how long the michael@0: // matching text was. michael@0: UnicodeString temp; michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: while (p < str.length() && keyLen == 0) { michael@0: temp.setTo(str, p, str.length() - p); michael@0: keyLen = prefixLength(temp, key, status); michael@0: if (U_FAILURE(status)) { michael@0: break; michael@0: } michael@0: if (keyLen != 0) { michael@0: *length = keyLen; michael@0: return p; michael@0: } michael@0: ++p; michael@0: } michael@0: // if we make it to here, we didn't find it. Return -1 for the michael@0: // location. The length should be ignored, but set it to 0, michael@0: // which should be "safe" michael@0: *length = 0; michael@0: return -1; michael@0: michael@0: //---------------------------------------------------------------- michael@0: // JDK 1.2 version of this routine michael@0: //RuleBasedCollator collator = (RuleBasedCollator)formatter.getCollator(); michael@0: // michael@0: //CollationElementIterator strIter = collator.getCollationElementIterator(str); michael@0: //CollationElementIterator keyIter = collator.getCollationElementIterator(key); michael@0: // michael@0: //int keyStart = -1; michael@0: // michael@0: //str.setOffset(startingAt); michael@0: // michael@0: //int oStr = strIter.next(); michael@0: //int oKey = keyIter.next(); michael@0: //while (oKey != CollationElementIterator.NULLORDER) { michael@0: // while (oStr != CollationElementIterator.NULLORDER && michael@0: // CollationElementIterator.primaryOrder(oStr) == 0) michael@0: // oStr = strIter.next(); michael@0: // michael@0: // while (oKey != CollationElementIterator.NULLORDER && michael@0: // CollationElementIterator.primaryOrder(oKey) == 0) michael@0: // oKey = keyIter.next(); michael@0: // michael@0: // if (oStr == CollationElementIterator.NULLORDER) { michael@0: // return new int[] { -1, 0 }; michael@0: // } michael@0: // michael@0: // if (oKey == CollationElementIterator.NULLORDER) { michael@0: // break; michael@0: // } michael@0: // michael@0: // if (CollationElementIterator.primaryOrder(oStr) == michael@0: // CollationElementIterator.primaryOrder(oKey)) { michael@0: // keyStart = strIter.getOffset(); michael@0: // oStr = strIter.next(); michael@0: // oKey = keyIter.next(); michael@0: // } else { michael@0: // if (keyStart != -1) { michael@0: // keyStart = -1; michael@0: // keyIter.reset(); michael@0: // } else { michael@0: // oStr = strIter.next(); michael@0: // } michael@0: // } michael@0: //} michael@0: // michael@0: //if (oKey == CollationElementIterator.NULLORDER) { michael@0: // return new int[] { keyStart, strIter.getOffset() - keyStart }; michael@0: //} else { michael@0: // return new int[] { -1, 0 }; michael@0: //} michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Checks to see whether a string consists entirely of ignorable michael@0: * characters. michael@0: * @param str The string to test. michael@0: * @return true if the string is empty of consists entirely of michael@0: * characters that the number formatter's collator says are michael@0: * ignorable at the primary-order level. false otherwise. michael@0: */ michael@0: UBool michael@0: NFRule::allIgnorable(const UnicodeString& str, UErrorCode& status) const michael@0: { michael@0: // if the string is empty, we can just return true michael@0: if (str.length() == 0) { michael@0: return TRUE; michael@0: } michael@0: michael@0: #if !UCONFIG_NO_COLLATION michael@0: // if lenient parsing is turned on, walk through the string with michael@0: // a collation element iterator and make sure each collation michael@0: // element is 0 (ignorable) at the primary level michael@0: if (formatter->isLenient()) { michael@0: RuleBasedCollator* collator = (RuleBasedCollator*)(formatter->getCollator()); michael@0: CollationElementIterator* iter = collator->createCollationElementIterator(str); michael@0: michael@0: // Memory allocation error check. michael@0: if (collator == NULL || iter == NULL) { michael@0: delete collator; michael@0: delete iter; michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: return FALSE; michael@0: } michael@0: michael@0: UErrorCode err = U_ZERO_ERROR; michael@0: int32_t o = iter->next(err); michael@0: while (o != CollationElementIterator::NULLORDER michael@0: && CollationElementIterator::primaryOrder(o) == 0) { michael@0: o = iter->next(err); michael@0: } michael@0: michael@0: delete iter; michael@0: return o == CollationElementIterator::NULLORDER; michael@0: } michael@0: #endif michael@0: michael@0: // if lenient parsing is turned off, there is no such thing as michael@0: // an ignorable character: return true only if the string is empty michael@0: return FALSE; michael@0: } michael@0: michael@0: U_NAMESPACE_END michael@0: michael@0: /* U_HAVE_RBNF */ michael@0: #endif michael@0: michael@0: