// ICU4N specific - de-nested Node class // ICU4N specific - de-nested MessageNode class // ICU4N specific - de-nested MessageContentsNode class // ICU4N specific - de-nested TextNode class // ICU4N specific - de-nested ArgNode class // ICU4N specific - de-nested ComplexArgStyleNode class // ICU4N specific - de-nested VariantNode class private static MessageNode BuildMessageNode(MessagePattern pattern, int start, int limit) { int prevPatternIndex = pattern.GetPart(start).Limit; MessageNode node = new MessageNode(); for (int i = start + 1; ; ++i) { MessagePatternPart part = pattern.GetPart(i); int patternIndex = part.Index; if (prevPatternIndex < patternIndex) { node.AddContentsNode( new TextNode(pattern.PatternString.Substring(prevPatternIndex, patternIndex - prevPatternIndex))); // ICU4N: Corrected 2nd arg } if (i == limit) { break; } MessagePatternPartType partType = part.Type; if (partType == MessagePatternPartType.ArgStart) { int argLimit = pattern.GetLimitPartIndex(i); node.AddContentsNode(BuildArgNode(pattern, i, argLimit)); i = argLimit; part = pattern.GetPart(i); } else if (partType == MessagePatternPartType.ReplaceNumber) { node.AddContentsNode(MessageContentsNode.CreateReplaceNumberNode()); // else: ignore SKIP_SYNTAX and INSERT_CHAR parts. } prevPatternIndex = part.Limit; } return(node.Freeze()); }
/// <summary> /// Finds the <see cref="SelectFormat"/> sub-message for the given <paramref name="keyword"/>, or the "other" sub-message. /// </summary> /// <param name="pattern">A <see cref="MessagePattern"/>.</param> /// <param name="partIndex">The index of the first <see cref="SelectFormat"/> argument style part.</param> /// <param name="keyword">A keyword to be matched to one of the <see cref="SelectFormat"/> argument's keywords.</param> /// <returns>The sub-message start part index.</returns> internal static int FindSubMessage(MessagePattern pattern, int partIndex, string keyword) { int count = pattern.CountParts(); int msgStart = 0; // Iterate over (ARG_SELECTOR, message) pairs until ARG_LIMIT or end of select-only pattern. do { MessagePatternPart part = pattern.GetPart(partIndex++); MessagePatternPartType type = part.Type; if (type == MessagePatternPartType.ArgLimit) { break; } Debug.Assert(type == MessagePatternPartType.ArgSelector); // part is an ARG_SELECTOR followed by a message if (pattern.PartSubstringMatches(part, keyword)) { // keyword matches return(partIndex); } else if (msgStart == 0 && pattern.PartSubstringMatches(part, "other")) { msgStart = partIndex; } partIndex = pattern.GetLimitPartIndex(partIndex); } while (++partIndex < count); return(msgStart); }
/// <summary> /// Selects the phrase for the given <paramref name="keyword"/>. /// </summary> /// <param name="keyword">A phrase selection keyword.</param> /// <returns>The string containing the formatted select message.</returns> /// <exception cref="ArgumentException">When the given keyword is not a "pattern identifier".</exception> /// <stable>ICU 4.4</stable> public string Format(string keyword) { //Check for the validity of the keyword if (!PatternProps.IsIdentifier(keyword)) { throw new ArgumentException("Invalid formatting argument."); } // If no pattern was applied, throw an exception if (msgPattern == null || msgPattern.CountParts() == 0) { throw new InvalidOperationException("Invalid format error."); } // Get the appropriate sub-message. int msgStart = FindSubMessage(msgPattern, 0, keyword); if (!msgPattern.JdkAposMode) { int msgLimit = msgPattern.GetLimitPartIndex(msgStart); return(msgPattern.PatternString.Substring(msgPattern.GetPart(msgStart).Limit, msgPattern.GetPatternIndex(msgLimit))); } // JDK compatibility mode: Remove SKIP_SYNTAX. StringBuilder result = null; int prevIndex = msgPattern.GetPart(msgStart).Limit; for (int i = msgStart; ;) { MessagePatternPart part = msgPattern.GetPart(++i); MessagePatternPartType type = part.Type; int index = part.Index; if (type == MessagePatternPartType.MsgLimit) { if (result == null) { return(pattern.Substring(prevIndex, index - prevIndex)); // ICU4N: Corrected 2nd arg } else { return(result.Append(pattern, prevIndex, index).ToString()); } } else if (type == MessagePatternPartType.SkipSyntax) { if (result == null) { result = new StringBuilder(); } result.Append(pattern, prevIndex, index); prevIndex = part.Limit; } else if (type == MessagePatternPartType.ArgStart) { if (result == null) { result = new StringBuilder(); } result.Append(pattern, prevIndex, index); prevIndex = index; i = msgPattern.GetLimitPartIndex(i); index = msgPattern.GetPart(i).Limit; MessagePattern.AppendReducedApostrophes(pattern, prevIndex, index, result); prevIndex = index; } } }
private string Format(/*Number*/ object numberObject, double number) { // If no pattern was applied, return the formatted number. if (msgPattern == null || msgPattern.CountParts() == 0) { return(numberFormat.Format(numberObject)); } // Get the appropriate sub-message. // Select it based on the formatted number-offset. double numberMinusOffset = number - offset; string numberString; if (offset == 0) { numberString = numberFormat.Format(numberObject); // could be BigDecimal etc. } else { numberString = numberFormat.Format(numberMinusOffset); } #pragma warning disable 612, 618 IFixedDecimal dec; // ICU4N TODO: //if (numberFormat is DecimalFormat) //{ // dec = ((DecimalFormat)numberFormat).GetFixedDecimal(numberMinusOffset); //} //else //{ dec = new FixedDecimal(numberMinusOffset); #pragma warning restore 612, 618 //} int partIndex = FindSubMessage(msgPattern, 0, pluralRulesWrapper, dec, number); // Replace syntactic # signs in the top level of this sub-message // (not in nested arguments) with the formatted number-offset. StringBuilder result = null; int prevIndex = msgPattern.GetPart(partIndex).Limit; for (; ;) { MessagePatternPart part = msgPattern.GetPart(++partIndex); MessagePatternPartType type = part.Type; int index = part.Index; if (type == MessagePatternPartType.MsgLimit) { if (result == null) { return(pattern.Substring(prevIndex, index - prevIndex)); // ICU4N: Corrected 2nd arg } else { return(result.Append(pattern, prevIndex, index).ToString()); } } else if (type == MessagePatternPartType.ReplaceNumber || // JDK compatibility mode: Remove SKIP_SYNTAX. (type == MessagePatternPartType.SkipSyntax && msgPattern.JdkAposMode)) { if (result == null) { result = new StringBuilder(); } result.Append(pattern, prevIndex, index); if (type == MessagePatternPartType.ReplaceNumber) { result.Append(numberString); } prevIndex = part.Limit; } else if (type == MessagePatternPartType.ArgStart) { if (result == null) { result = new StringBuilder(); } result.Append(pattern, prevIndex, index); prevIndex = index; partIndex = msgPattern.GetLimitPartIndex(partIndex); index = msgPattern.GetPart(partIndex).Limit; MessagePattern.AppendReducedApostrophes(pattern, prevIndex, index, result); prevIndex = index; } } }
/// <summary> /// Finds the <see cref="PluralFormat"/> sub-message for the given number, or the "other" sub-message. /// </summary> /// <param name="pattern">A <see cref="MessagePattern"/>.</param> /// <param name="partIndex">the index of the first <see cref="PluralFormat"/> argument style part.</param> /// <param name="selector">the <see cref="IPluralSelector"/> for mapping the number (minus offset) to a keyword.</param> /// <param name="context">worker object for the selector.</param> /// <param name="number">a number to be matched to one of the <see cref="PluralFormat"/> argument's explicit values, /// or mapped via the <see cref="IPluralSelector"/>.</param> /// <returns>the sub-message start part index.</returns> internal static int FindSubMessage( MessagePattern pattern, int partIndex, IPluralSelector selector, object context, double number) { int count = pattern.CountParts(); double offset; MessagePatternPart part = pattern.GetPart(partIndex); if (part.Type.HasNumericValue()) { offset = pattern.GetNumericValue(part); ++partIndex; } else { offset = 0; } // The keyword is null until we need to match against a non-explicit, not-"other" value. // Then we get the keyword from the selector. // (In other words, we never call the selector if we match against an explicit value, // or if the only non-explicit keyword is "other".) string keyword = null; // When we find a match, we set msgStart>0 and also set this boolean to true // to avoid matching the keyword again (duplicates are allowed) // while we continue to look for an explicit-value match. bool haveKeywordMatch = false; // msgStart is 0 until we find any appropriate sub-message. // We remember the first "other" sub-message if we have not seen any // appropriate sub-message before. // We remember the first matching-keyword sub-message if we have not seen // one of those before. // (The parser allows [does not check for] duplicate keywords. // We just have to make sure to take the first one.) // We avoid matching the keyword twice by also setting haveKeywordMatch=true // at the first keyword match. // We keep going until we find an explicit-value match or reach the end of the plural style. int msgStart = 0; // Iterate over (ARG_SELECTOR [ARG_INT|ARG_DOUBLE] message) tuples // until ARG_LIMIT or end of plural-only pattern. do { part = pattern.GetPart(partIndex++); MessagePatternPartType type = part.Type; if (type == MessagePatternPartType.ArgLimit) { break; } Debug.Assert(type == MessagePatternPartType.ArgSelector); // part is an ARG_SELECTOR followed by an optional explicit value, and then a message if (pattern.GetPartType(partIndex).HasNumericValue()) { // explicit value like "=2" part = pattern.GetPart(partIndex++); if (number == pattern.GetNumericValue(part)) { // matches explicit value return(partIndex); } } else if (!haveKeywordMatch) { // plural keyword like "few" or "other" // Compare "other" first and call the selector if this is not "other". if (pattern.PartSubstringMatches(part, "other")) { if (msgStart == 0) { msgStart = partIndex; if (keyword != null && keyword.Equals("other")) { // This is the first "other" sub-message, // and the selected keyword is also "other". // Do not match "other" again. haveKeywordMatch = true; } } } else { if (keyword == null) { keyword = selector.Select(context, number - offset); if (msgStart != 0 && keyword.Equals("other")) { // We have already seen an "other" sub-message. // Do not match "other" again. haveKeywordMatch = true; // Skip keyword matching but do getLimitPartIndex(). } } if (!haveKeywordMatch && pattern.PartSubstringMatches(part, keyword)) { // keyword matches msgStart = partIndex; // Do not match this keyword again. haveKeywordMatch = true; } } } partIndex = pattern.GetLimitPartIndex(partIndex); } while (++partIndex < count); return(msgStart); }