/** * Parses a string using the rule set or DecimalFormat belonging * to this substitution. If there's a match, a mathematical * operation (the inverse of the one used in formatting) is * performed on the result of the parse and the value passed in * and returned as the result. The parse position is updated to * point to the first unmatched character in the string. * @param text The string to parse * @param parsePosition On entry, ignored, but assumed to be 0. * On exit, this is updated to point to the first unmatched * character (or 0 if the substitution didn't match) * @param baseValue A partial parse result that should be * combined with the result of this parse * @param upperBound When searching the rule set for a rule * matching the string passed in, only rules with base values * lower than this are considered * @param lenientParse If true and matching against rules fails, * the substitution will also try matching the text against * numerals using a default-costructed NumberFormat. If false, * no extra work is done. (This value is false whenever the * formatter isn't in lenient-parse mode, but is also false * under some conditions even when the formatter _is_ in * lenient-parse mode.) * @return If there's a match, this is the result of composing * baseValue with whatever was returned from matching the * characters. This will be either a Long or a Double. If there's * no match this is new Long(0) (not null), and parsePosition * is left unchanged. */ UBool NFSubstitution::doParse(const UnicodeString& text, ParsePosition& parsePosition, double baseValue, double upperBound, UBool lenientParse, Formattable& result) const { #ifdef RBNF_DEBUG fprintf(stderr, "<nfsubs> %x bv: %g ub: %g\n", this, baseValue, upperBound); #endif // figure out the highest base value a rule can have and match // the text being parsed (this varies according to the type of // substitutions: multiplier, modulus, and numerator substitutions // restrict the search to rules with base values lower than their // own; same-value substitutions leave the upper bound wherever // it was, and the others allow any rule to match upperBound = calcUpperBound(upperBound); // use our rule set to parse the text. If that fails and // lenient parsing is enabled (this is always false if the // formatter's lenient-parsing mode is off, but it may also // be false even when the formatter's lenient-parse mode is // on), then also try parsing the text using a default- // constructed NumberFormat if (ruleSet != NULL) { ruleSet->parse(text, parsePosition, upperBound, result); if (lenientParse && !ruleSet->isFractionRuleSet() && parsePosition.getIndex() == 0) { UErrorCode status = U_ZERO_ERROR; NumberFormat* fmt = NumberFormat::createInstance(status); if (U_SUCCESS(status)) { fmt->parse(text, result, parsePosition); } delete fmt; } // ...or use our DecimalFormat to parse the text } else { numberFormat->parse(text, result, parsePosition); } // if the parse was successful, we've already advanced the caller's // parse position (this is the one function that doesn't have one // of its own). Derive a parse result and return it as a Long, // if possible, or a Double if (parsePosition.getIndex() != 0) { UErrorCode status = U_ZERO_ERROR; double tempResult = result.getDouble(status); // composeRuleValue() produces a full parse result from // the partial parse result passed to this function from // the caller (this is either the owning rule's base value // or the partial result obtained from composing the // owning rule's base value with its other substitution's // parse result) and the partial parse result obtained by // matching the substitution (which will be the same value // the caller would get by parsing just this part of the // text with RuleBasedNumberFormat.parse() ). How the two // values are used to derive the full parse result depends // on the types of substitutions: For a regular rule, the // ultimate result is its multiplier substitution's result // times the rule's divisor (or the rule's base value) plus // the modulus substitution's result (which will actually // supersede part of the rule's base value). For a negative- // number rule, the result is the negative of its substitution's // result. For a fraction rule, it's the sum of its two // substitution results. For a rule in a fraction rule set, // it's the numerator substitution's result divided by // the rule's base value. Results from same-value substitutions // propagate back upard, and null substitutions don't affect // the result. tempResult = composeRuleValue(tempResult, baseValue); result.setDouble(tempResult); return TRUE; // if the parse was UNsuccessful, return 0 } else { result.setLong(0); return FALSE; } }
/* @bug 4031438 * More robust message formats. */ void MessageFormatRegressionTest::Test4031438() { UErrorCode status = U_ZERO_ERROR; UnicodeString pattern1("Impossible {1} has occurred -- status code is {0} and message is {2}."); UnicodeString pattern2("Double '' Quotes {0} test and quoted '{1}' test plus 'other {2} stuff'."); MessageFormat *messageFormatter = new MessageFormat("", status); failure(status, "new MessageFormat"); const UBool possibleDataError = TRUE; //try { logln("Apply with pattern : " + pattern1); messageFormatter->applyPattern(pattern1, status); failure(status, "messageFormat->applyPattern"); //Object[] params = {new Integer(7)}; Formattable params []= { Formattable((int32_t)7) }; UnicodeString tempBuffer; FieldPosition pos(FieldPosition::DONT_CARE); tempBuffer = messageFormatter->format(params, 1, tempBuffer, pos, status); if(tempBuffer != "Impossible {1} has occurred -- status code is 7 and message is {2}." || failure(status, "MessageFormat::format")) dataerrln("Tests arguments < substitution failed"); logln("Formatted with 7 : " + tempBuffer); ParsePosition pp(0); int32_t count = 0; Formattable *objs = messageFormatter->parse(tempBuffer, pp, count); //if(objs[7/*params.length*/] != NULL) // errln("Parse failed with more than expected arguments"); NumberFormat *fmt = 0; UnicodeString temp, temp1; for (int i = 0; i < count; i++) { // convert to string if not already Formattable obj = objs[i]; temp.remove(); if(obj.getType() == Formattable::kString) temp = obj.getString(temp); else { fmt = NumberFormat::createInstance(status); switch (obj.getType()) { case Formattable::kLong: fmt->format(obj.getLong(), temp); break; case Formattable::kInt64: fmt->format(obj.getInt64(), temp); break; case Formattable::kDouble: fmt->format(obj.getDouble(), temp); break; default: break; } } // convert to string if not already Formattable obj1 = params[i]; temp1.remove(); if(obj1.getType() == Formattable::kString) temp1 = obj1.getString(temp1); else { fmt = NumberFormat::createInstance(status); switch (obj1.getType()) { case Formattable::kLong: fmt->format(obj1.getLong(), temp1); break; case Formattable::kInt64: fmt->format(obj1.getInt64(), temp1); break; case Formattable::kDouble: fmt->format(obj1.getDouble(), temp1); break; default: break; } } //if (objs[i] != NULL && objs[i].getString(temp1) != params[i].getString(temp2)) { if (temp != temp1) { errln("Parse failed on object " + objs[i].getString(temp1) + " at index : " + i); } } delete fmt; delete [] objs; // {sfb} does this apply? no way to really pass a null Formattable, // only a null array /*tempBuffer = messageFormatter->format(null, tempBuffer, FieldPosition(FieldPosition::DONT_CARE), status); if (tempBuffer != "Impossible {1} has occurred -- status code is {0} and message is {2}." || failure(status, "messageFormat->format")) errln("Tests with no arguments failed"); logln("Formatted with null : " + tempBuffer);*/ logln("Apply with pattern : " + pattern2); messageFormatter->applyPattern(pattern2, status); failure(status, "messageFormatter->applyPattern", possibleDataError); tempBuffer.remove(); tempBuffer = messageFormatter->format(params, 1, tempBuffer, pos, status); if (tempBuffer != "Double ' Quotes 7 test and quoted {1} test plus 'other {2} stuff'.") dataerrln("quote format test (w/ params) failed. - %s", u_errorName(status)); logln("Formatted with params : " + tempBuffer); /*tempBuffer = messageFormatter->format(null); if (!tempBuffer.equals("Double ' Quotes {0} test and quoted {1} test plus other {2} stuff.")) errln("quote format test (w/ null) failed."); logln("Formatted with null : " + tempBuffer); logln("toPattern : " + messageFormatter.toPattern());*/ /*} catch (Exception foo) { errln("Exception when formatting in bug 4031438. "+foo.getMessage()); }*/ delete messageFormatter; }
void NumberFormatRoundTripTest::test(NumberFormat *fmt, const Formattable& value) { fmt->setMaximumFractionDigits(999); if(fmt->getDynamicClassID() == DecimalFormat::getStaticClassID()) { ((DecimalFormat *)fmt)->setRoundingIncrement(0.0); } UErrorCode status = U_ZERO_ERROR; UnicodeString s, s2, temp; if(isDouble(value)) s = fmt->format(value.getDouble(), s); else s = fmt->format(value.getLong(), s); Formattable n; UBool show = verbose; if(DEBUG) logln(/*value.getString(temp) +*/ " F> " + escape(s)); fmt->parse(s, n, status); failure(status, "fmt->parse"); if(DEBUG) logln(escape(s) + " P> " /*+ n.getString(temp)*/); if(isDouble(n)) s2 = fmt->format(n.getDouble(), s2); else s2 = fmt->format(n.getLong(), s2); if(DEBUG) logln(/*n.getString(temp) +*/ " F> " + escape(s2)); if(STRING_COMPARE) { if (s != s2) { errln("*** STRING ERROR \"" + escape(s) + "\" != \"" + escape(s2) + "\""); show = TRUE; } } if(EXACT_NUMERIC_COMPARE) { if(value != n) { errln("*** NUMERIC ERROR"); show = TRUE; } } else { // Compute proportional error double error = proportionalError(value, n); if(error > MAX_ERROR) { errln(UnicodeString("*** NUMERIC ERROR ") + error); show = TRUE; } if (error > max_numeric_error) max_numeric_error = error; if (error < min_numeric_error) min_numeric_error = error; } if (show) { errln(/*value.getString(temp) +*/ typeOf(value, temp) + " F> " + escape(s) + " P> " + /*n.getString(temp) +*/ typeOf(n, temp) + " F> " + escape(s2)); } }
std::string GlobalizationNDK::stringToNumber(const std::string& args) { if (args.empty()) { slog2f(0, ID_G11N, SLOG2_ERROR, "GlobalizationNDK::stringToNumber: no arguments provided!"); return errorInJson(PARSING_ERROR, "No arguments provided!"); } Json::Reader reader; Json::Value root; bool parse = reader.parse(args, root); if (!parse) { slog2f(0, ID_G11N, SLOG2_ERROR, "GlobalizationNDK::stringToNumber: invalid json data: %s", args.c_str()); return errorInJson(PARSING_ERROR, "Invalid json data!"); } Json::Value sv = root["numberString"]; if (sv.isNull()) { slog2f(0, ID_G11N, SLOG2_ERROR, "GlobalizationNDK::stringToNumber: no numberString provided!"); return errorInJson(FORMATTING_ERROR, "No numberString provided!"); } if (!sv.isString()) { slog2f(0, ID_G11N, SLOG2_ERROR, "GlobalizationNDK::stringToNumber: invalid numberString type: %d!", sv.type()); return errorInJson(FORMATTING_ERROR, "Invalid numberString type!"); } std::string str = sv.asString(); if (str.empty()) { slog2f(0, ID_G11N, SLOG2_ERROR, "GlobalizationNDK::stringToNumber: empty numberString!"); return errorInJson(FORMATTING_ERROR, "Empty numberString!"); } // This is the default value when no options provided. ENumberType type = kNumberDecimal; Json::Value options = root["options"]; std::string error; if (!handleNumberOptions(options, type, error)) return errorInJson(PARSING_ERROR, error); UErrorCode status = U_ZERO_ERROR; NumberFormat* nf; switch (type) { case kNumberDecimal: default: nf = NumberFormat::createInstance(status); break; case kNumberCurrency: nf = NumberFormat::createCurrencyInstance(status); break; case kNumberPercent: nf = NumberFormat::createPercentInstance(status); break; } if (!nf) { slog2f(0, ID_G11N, SLOG2_ERROR, "GlobalizationNDK::stringToNumber: failed to create NumberFormat instance for type %d: %d", status, type); return errorInJson(UNKNOWN_ERROR, "Failed to create NumberFormat instance!"); } std::auto_ptr<NumberFormat> deleter(nf); UnicodeString uStr = UnicodeString::fromUTF8(str); Formattable value; if (type == kNumberCurrency) { ParsePosition pos; CurrencyAmount* ca = nf->parseCurrency(uStr, pos); if (ca) value = ca->getNumber(); else nf->parse(uStr, value, status); } else nf->parse(uStr, value, status); if (status != U_ZERO_ERROR && status != U_ERROR_WARNING_START) { slog2f(0, ID_G11N, SLOG2_ERROR, "GlobalizationNDK::stringToNumber: failed (%d) to parse string: %s", status, str.c_str()); return errorInJson(PARSING_ERROR, "Failed to parse string!"); } if (!value.isNumeric()) { slog2f(0, ID_G11N, SLOG2_ERROR, "GlobalizationNDK::stringToNumber: string is not numeric: %s", str.c_str()); return errorInJson(FORMATTING_ERROR, "String is not numeric!"); } return resultInJson(value.getDouble()); }