--- /dev/null
+/*
+*******************************************************************************
+* Copyright (C) 2011, International Business Machines
+* Corporation and others. All Rights Reserved.
+*******************************************************************************
+* created on: 2011jul14
+* created by: Markus W. Scherer
+*/
+
+package com.ibm.icu.dev.demo.messagepattern;
+
+import com.ibm.icu.text.MessagePattern;
+import com.ibm.icu.text.MessagePatternUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Demo code for MessagePattern class.
+ * @author Markus Scherer
+ * @since 2011-jul-14
+ */
+public class MessagePatternUtilDemo {
+ private static final String manySpaces=" ";
+
+ private static final void printMessage(MessagePatternUtil.MessageNode msg, int depth) {
+ String indent = manySpaces.substring(0, depth * 2);
+ for (MessagePatternUtil.MessageContentsNode contents : msg.getContents()) {
+ switch (contents.getType()) {
+ case TEXT:
+ System.out.println(indent + "text: «" +
+ ((MessagePatternUtil.TextNode)contents).getText() + "»");
+ break;
+ case ARG:
+ printArg((MessagePatternUtil.ArgNode)contents, depth);
+ break;
+ case REPLACE_NUMBER:
+ System.out.println(indent + "replace: number");
+ break;
+ }
+ }
+ }
+
+ private static final void printArg(MessagePatternUtil.ArgNode arg, int depth) {
+ System.out.print(manySpaces.substring(0, depth * 2) + "arg: «" + arg.getName() + "»");
+ MessagePattern.ArgType argType = arg.getArgType();
+ if (argType == MessagePattern.ArgType.NONE) {
+ System.out.println(" (no type)");
+ } else {
+ System.out.print(" (" + arg.getTypeName() + ")");
+ if (argType == MessagePattern.ArgType.SIMPLE) {
+ String styleString = arg.getSimpleStyle();
+ if (styleString == null) {
+ System.out.println(" (no style)");
+ } else {
+ System.out.println(" style: «" + styleString + "»");
+ }
+ } else {
+ System.out.println();
+ printComplexArgStyle(arg.getComplexStyle(), depth + 1);
+ }
+ }
+ }
+
+ private static final void printComplexArgStyle(MessagePatternUtil.ComplexArgStyleNode style,
+ int depth) {
+ if (style.hasExplicitOffset()) {
+ System.out.println(manySpaces.substring(0, depth * 2) + "offset: " + style.getOffset());
+ }
+ String indent = manySpaces.substring(0, depth * 2);
+ MessagePattern.ArgType argType = style.getArgType();
+ for (MessagePatternUtil.VariantNode variant : style.getVariants()) {
+ double value;
+ switch (argType) {
+ case CHOICE:
+ System.out.println(indent + variant.getSelectorValue() + " " + variant.getSelector() + ":");
+ break;
+ case PLURAL:
+ value = variant.getSelectorValue();
+ if (value == MessagePattern.NO_NUMERIC_VALUE) {
+ System.out.println(indent + variant.getSelector() + ":");
+ } else {
+ System.out.println(indent + variant.getSelector() + " (" + value + "):");
+ }
+ break;
+ case SELECT:
+ System.out.println(indent + variant.getSelector() + ":");
+ break;
+ }
+ printMessage(variant.getMessage(), depth + 1);
+ }
+ }
+
+ /**
+ * This is a <em>prototype/demo/sample</em> for how we could use the MessagePatternUtil class
+ * for generating something like JavaScript code for evaluating some
+ * of the MessageFormat syntax.
+ *
+ * <p>This is not intended to be production code, nor to generate production code
+ * or even syntactically correct JavaScript.
+ * @param msg
+ */
+ private static final void genCode(MessagePatternUtil.MessageNode msg) {
+ List<String> args = new ArrayList<String>();
+ addArgs(msg, args);
+ System.out.print("def function(");
+ boolean firstArg = true;
+ for (String argName : args) {
+ if (firstArg) {
+ System.out.print(argName);
+ firstArg = false;
+ } else {
+ System.out.print(", " + argName);
+ }
+ }
+ System.out.println(") {");
+ genCode(msg, 1, true, "");
+ System.out.println(" return result");
+ System.out.println("}");
+ }
+
+ private static final void genCode(MessagePatternUtil.MessageNode msg,
+ int depth,
+ boolean firstResult,
+ String pluralNumber) {
+ String prefix = manySpaces.substring(0, depth * 2) + "result ";
+ for (MessagePatternUtil.MessageContentsNode contents : msg.getContents()) {
+ String operator = firstResult ? "=" : "+=";
+ switch (contents.getType()) {
+ case TEXT:
+ System.out.println(
+ prefix + operator + " \"" +
+ escapeString(((MessagePatternUtil.TextNode)contents).getText()) +
+ "\"");
+ break;
+ case ARG:
+ genCode((MessagePatternUtil.ArgNode)contents, depth, firstResult);
+ break;
+ case REPLACE_NUMBER:
+ System.out.println(prefix + operator + " formatNumber(" + pluralNumber + ")");
+ break;
+ }
+ firstResult = false;
+ }
+ }
+
+ private static final void genCode(MessagePatternUtil.ArgNode arg,
+ int depth,
+ boolean firstResult) {
+ String prefix = manySpaces.substring(0, depth * 2) + "result ";
+ String operator = firstResult ? "=" : "+=";
+ String argName = arg.getName();
+ if (arg.getNumber() >= 0) {
+ argName = "arg_" + argName; // Prefix for numbered argument.
+ }
+ switch (arg.getArgType()) {
+ case NONE:
+ System.out.println(prefix + operator + " " + argName);
+ break;
+ case SIMPLE:
+ case CHOICE:
+ System.out.println(prefix + operator + " \"(unsupported syntax)\"");
+ break;
+ case PLURAL:
+ genCodeForPlural(arg.getComplexStyle(), depth, firstResult, argName);
+ break;
+ case SELECT:
+ genCodeForSelect(arg.getComplexStyle(), depth, firstResult, argName);
+ break;
+ }
+ }
+
+ private static final void genCodeForPlural(MessagePatternUtil.ComplexArgStyleNode style,
+ int depth,
+ boolean firstResult,
+ String argName) {
+ List<MessagePatternUtil.VariantNode> numericVariants =
+ new ArrayList<MessagePatternUtil.VariantNode>();
+ List<MessagePatternUtil.VariantNode> keywordVariants =
+ new ArrayList<MessagePatternUtil.VariantNode>();
+ MessagePatternUtil.VariantNode otherVariant =
+ style.getVariantsByType(numericVariants, keywordVariants);
+ double offset = style.getOffset();
+ String pluralNumber = offset == 0. ? argName : argName + " - " + offset;
+ int origDepth = depth;
+ if (!numericVariants.isEmpty()) {
+ genCodeForNumericVariants(numericVariants, depth++, firstResult, argName, pluralNumber);
+ }
+ if (!keywordVariants.isEmpty()) {
+ System.out.println(manySpaces.substring(0, depth * 2) +
+ "_keyword = PluralRules.select(" + pluralNumber + ")");
+ genCodeForKeywordVariants(keywordVariants, depth++, firstResult, "_keyword", pluralNumber);
+ }
+ genCode(otherVariant.getMessage(), depth, firstResult, pluralNumber);
+ if (origDepth < depth) {
+ System.out.println(manySpaces.substring(0, --depth * 2) + "}");
+ if (origDepth < depth) {
+ System.out.println(manySpaces.substring(0, --depth * 2) + "}");
+ }
+ }
+ }
+
+ private static final void genCodeForSelect(MessagePatternUtil.ComplexArgStyleNode style,
+ int depth,
+ boolean firstResult,
+ String argName) {
+ List<MessagePatternUtil.VariantNode> keywordVariants =
+ new ArrayList<MessagePatternUtil.VariantNode>();
+ MessagePatternUtil.VariantNode otherVariant = style.getVariantsByType(null, keywordVariants);
+ if (keywordVariants.isEmpty()) {
+ genCode(otherVariant.getMessage(), depth, firstResult, "");
+ } else {
+ genCodeForKeywordVariants(keywordVariants, depth, firstResult, argName, "");
+ genCode(otherVariant.getMessage(), depth + 1, firstResult, "");
+ System.out.println(manySpaces.substring(0, depth * 2) + "}");
+ }
+ }
+
+ private static final void genCodeForNumericVariants(List<MessagePatternUtil.VariantNode> variants,
+ int depth,
+ boolean firstResult,
+ String varName,
+ String pluralNumber) {
+ String indent = manySpaces.substring(0, depth++ * 2);
+ boolean firstVariant = true;
+ for (MessagePatternUtil.VariantNode variant : variants) {
+ System.out.println(
+ indent +
+ (firstVariant ? "if (" : "} else if (") +
+ varName + " == " + variant.getSelectorValue() + ") {");
+ genCode(variant.getMessage(), depth, firstResult, pluralNumber);
+ firstVariant = false;
+ }
+ System.out.println(indent + "} else {");
+ }
+
+ private static final void genCodeForKeywordVariants(List<MessagePatternUtil.VariantNode> variants,
+ int depth,
+ boolean firstResult,
+ String varName,
+ String pluralNumber) {
+ String indent = manySpaces.substring(0, depth++ * 2);
+ boolean firstVariant = true;
+ for (MessagePatternUtil.VariantNode variant : variants) {
+ System.out.println(
+ indent +
+ (firstVariant ? "if (" : "} else if (") +
+ varName + " == \"" + variant.getSelector() + "\") {");
+ genCode(variant.getMessage(), depth, firstResult, pluralNumber);
+ firstVariant = false;
+ }
+ System.out.println(indent + "} else {");
+ }
+
+ /**
+ * Adds the message's argument names to the args list.
+ * Adds each argument only once, in the order of first appearance.
+ * Numbered arguments get an "arg_" prefix prepended.
+ * @param msg
+ * @param args
+ */
+ private static final void addArgs(MessagePatternUtil.MessageNode msg, List<String> args) {
+ for (MessagePatternUtil.MessageContentsNode contents : msg.getContents()) {
+ if (contents.getType() == MessagePatternUtil.MessageContentsNode.Type.ARG) {
+ MessagePatternUtil.ArgNode arg = (MessagePatternUtil.ArgNode)contents;
+ String argName;
+ if (arg.getNumber() >= 0) {
+ argName = "arg_" + arg.getNumber(); // Prefix for numbered argument.
+ } else {
+ argName = arg.getName();
+ }
+ if (!args.contains(argName)) {
+ args.add(argName);
+ }
+ MessagePatternUtil.ComplexArgStyleNode complexStyle = arg.getComplexStyle();
+ if (complexStyle != null) {
+ for (MessagePatternUtil.VariantNode variant : complexStyle.getVariants()) {
+ addArgs(variant.getMessage(), args);
+ }
+ }
+ }
+ }
+ }
+
+ private static final String escapeString(String s) {
+ if (s.indexOf('"') < 0) {
+ return s;
+ } else {
+ return s.replace("\"", "\\\"");
+ }
+ }
+
+ private static final MessagePatternUtil.MessageNode print(String s) {
+ System.out.println("message: «" + s + "»");
+ try {
+ MessagePatternUtil.MessageNode msg = MessagePatternUtil.buildMessageNode(s);
+ printMessage(msg, 1);
+ genCode(msg);
+ return msg;
+ } catch(Exception e) {
+ System.out.println("Exception: "+e.getMessage());
+ return null;
+ }
+ }
+
+ public static void main(String[] argv) {
+ print("Hello!");
+ print("Hel'lo!");
+ print("Hel'{o");
+ print("Hel'{'o");
+ // double apostrophe inside quoted literal text still encodes a single apostrophe
+ print("a'{bc''de'f");
+ print("a'{bc''de'f{0,number,g'hi''jk'l#}");
+ print("abc{0}def");
+ print("abc{ arg }def");
+ print("abc{1}def{arg}ghi");
+ print("abc{2, number}ghi{3, select, xx {xxx} other {ooo}} xyz");
+ print("abc{gender,select,"+
+ "other{His name is {tc,XMB,<ph name=\"PERSON\">{$PERSON}</ph>}.}}xyz");
+ print("abc{num_people, plural, offset:17 few{fff} other {oooo}}xyz");
+ print("abc{ num , plural , offset: 2 =1 {1} =-1 {-1} =3.14 {3.14} other {oo} }xyz");
+ print("I don't {a,plural,other{w'{'on't #'#'}} and "+
+ "{b,select,other{shan't'}'}} '{'''know'''}' and "+
+ "{c,choice,0#can't'|'}"+
+ "{z,number,#'#'###.00'}'}.");
+ print("a_{0,choice,-∞ #-inf| 5≤ five | 99 # ninety'|'nine }_z");
+ print("a_{0,plural,other{num=#'#'=#'#'={1,number,##}!}}_z");
+ print("}}}{0}}"); // yes, unmatched '}' are ok in ICU MessageFormat
+ print("Hello {0}!");
+ String msg="++{0, select, female{{1} calls you her friend}"+
+ "other{{1} calls you '{their}' friend}"+
+ "male{{1} calls you his friend}}--";
+ print(msg);
+ msg="_'__{gender, select, female{Her n'ame is {person_name}.}"+
+ "other{His n'ame is {person_name}.}}__'_";
+ print(msg);
+ print("{num,plural,offset:1 =0{no one} =1{one, that is one and # others} " +
+ "one{one and # (probably 1) others} few{one and # others} " +
+ "other{lots & lots}}");
+ print(
+ "{p1_gender,select," +
+ "female{" +
+ "{p2_gender,select," +
+ "female{" +
+ "{num_people,plural,offset:1 "+
+ "=0{she alone}" +
+ "=1{she and her girlfriend {p2}}" +
+ "=2{she and her girlfriend {p2} and another}" +
+ "other{she, her girlfriend {p2} and # others}}}" +
+ "male{" +
+ "{num_people,plural,offset:1 "+
+ "=0{she alone}" +
+ "=1{she and her boyfriend {p2}}" +
+ "=2{she and her boyfriend {p2} and another}" +
+ "other{she, her boyfriend {p2} and # others}}}" +
+ "other{" +
+ "{num_people,plural,offset:1 "+
+ "=0{she alone}" +
+ "=1{she and her friend {p2}}" +
+ "=2{she and her friend {p2} and another}" +
+ "other{she, her friend {p2} and # others}}}}}" +
+ "male{" +
+ "{p2_gender,select," +
+ "female{" +
+ "{num_people,plural,offset:1 "+
+ "=0{he alone}" +
+ "=1{he and his girlfriend {p2}}" +
+ "=2{he and his girlfriend {p2} and another}" +
+ "other{he, his girlfriend {p2} and # others}}}" +
+ "male{" +
+ "{num_people,plural,offset:1 "+
+ "=0{he alone}" +
+ "=1{he and his boyfriend {p2}}" +
+ "=2{he and his boyfriend {p2} and another}" +
+ "other{he, his boyfriend {p2} and # others}}}" +
+ "other{" +
+ "{num_people,plural,offset:1 "+
+ "=0{she alone}" +
+ "=1{she and his friend {p2}}" +
+ "=2{she and his friend {p2} and another}" +
+ "other{she, his friend {p2} and # others}}}}}" +
+ "other{" +
+ "{p2_gender,select," +
+ "female{" +
+ "{num_people,plural,offset:1 "+
+ "=0{they alone}" +
+ "=1{they and their girlfriend {p2}}" +
+ "=2{they and their girlfriend {p2} and another}" +
+ "other{they, their girlfriend {p2} and # others}}}" +
+ "male{" +
+ "{num_people,plural,offset:1 "+
+ "=0{they alone}" +
+ "=1{they and their boyfriend {p2}}" +
+ "=2{they and their boyfriend {p2} and another}" +
+ "other{they, their boyfriend {p2} and # others}}}" +
+ "other{" +
+ "{num_people,plural,offset:1 "+
+ "=0{they alone}" +
+ "=1{they and their friend {p2}}" +
+ "=2{they and their friend {p2} and another}" +
+ "other{they, their friend {p2} and # others}}}}}}");
+ }
+}
--- /dev/null
+/*
+*******************************************************************************
+* Copyright (C) 2011, International Business Machines
+* Corporation and others. All Rights Reserved.
+*******************************************************************************
+* created on: 2011jul14
+* created by: Markus W. Scherer
+*/
+
+package com.ibm.icu.text;
+
+import com.ibm.icu.text.MessagePattern;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Utilities for working with a MessagePattern.
+ * Intended for use in tools when convenience is more important than
+ * minimizing runtime and object creations.
+ *
+ * <p>This class and its nested classes are not intended for public subclassing.
+ * @draft ICU 49
+ * @provisional This API might change or be removed in a future release.
+ * @author Markus Scherer
+ */
+public final class MessagePatternUtil {
+ /**
+ * Factory method, builds and returns a MessageNode from a MessageFormat pattern string.
+ * @param patternString a MessageFormat pattern string
+ * @return a MessageNode or a ComplexArgStyleNode
+ * @throws IllegalArgumentException if the MessagePattern is empty
+ * or does not represent a MessageFormat pattern
+ * @draft ICU 49
+ * @provisional This API might change or be removed in a future release.
+ */
+ public static MessageNode buildMessageNode(String patternString) {
+ return buildMessageNode(new MessagePattern(patternString));
+ }
+
+ /**
+ * Factory method, builds and returns a MessageNode from a MessagePattern.
+ * @param pattern a parsed MessageFormat pattern string
+ * @return a MessageNode or a ComplexArgStyleNode
+ * @throws IllegalArgumentException if the MessagePattern is empty
+ * or does not represent a MessageFormat pattern
+ * @draft ICU 49
+ * @provisional This API might change or be removed in a future release.
+ */
+ public static MessageNode buildMessageNode(MessagePattern pattern) {
+ int limit = pattern.countParts() - 1;
+ if (limit < 0) {
+ throw new IllegalArgumentException("The MessagePattern is empty");
+ } else if (pattern.getPartType(0) != MessagePattern.Part.Type.MSG_START) {
+ throw new IllegalArgumentException(
+ "The MessagePattern does not represent a MessageFormat pattern");
+ }
+ return buildMessageNode(pattern, 0, limit);
+ }
+
+ /**
+ * Common base class for all elements in a tree of nodes
+ * returned by {@link MessagePatternUtil#buildMessageNode(MessagePattern)}.
+ * @draft ICU 49
+ * @provisional This API might change or be removed in a future release.
+ */
+ public static class Node {
+ private Node() {}
+ }
+
+ /**
+ * A Node representing a parsed MessageFormat pattern string.
+ * @draft ICU 49
+ * @provisional This API might change or be removed in a future release.
+ */
+ public static class MessageNode extends Node {
+ /**
+ * @return the list of MessageContentsNode nodes that this message contains
+ * @draft ICU 49
+ * @provisional This API might change or be removed in a future release.
+ */
+ public List<MessageContentsNode> getContents() {
+ return list;
+ }
+ /**
+ * {@inheritDoc}
+ * @draft ICU 49
+ * @provisional This API might change or be removed in a future release.
+ */
+ @Override
+ public String toString() {
+ return list.toString();
+ }
+
+ private MessageNode() {
+ super();
+ }
+ private void addContentsNode(MessageContentsNode node) {
+ if (node instanceof TextNode && !list.isEmpty()) {
+ // Coalesce adjacent text nodes.
+ MessageContentsNode lastNode = list.get(list.size() - 1);
+ if (lastNode instanceof TextNode) {
+ TextNode textNode = (TextNode)lastNode;
+ textNode.text = textNode.text + ((TextNode)node).text;
+ return;
+ }
+ }
+ list.add(node);
+ }
+ private MessageNode freeze() {
+ list = Collections.unmodifiableList(list);
+ return this;
+ }
+
+ private List<MessageContentsNode> list = new ArrayList<MessageContentsNode>();
+ }
+
+ /**
+ * A piece of MessageNode contents.
+ * Use getType() to determine the type and the actual Node subclass.
+ * @draft ICU 49
+ * @provisional This API might change or be removed in a future release.
+ */
+ public static class MessageContentsNode extends Node {
+ /**
+ * The type of a piece of MessageNode contents.
+ * @draft ICU 49
+ * @provisional This API might change or be removed in a future release.
+ */
+ public enum Type {
+ /**
+ * This is a TextNode containing literal text (downcast and call getText()).
+ * @draft ICU 49
+ * @provisional This API might change or be removed in a future release.
+ */
+ TEXT,
+ /**
+ * This is an ArgNode representing a message argument
+ * (downcast and use specific methods).
+ * @draft ICU 49
+ * @provisional This API might change or be removed in a future release.
+ */
+ ARG,
+ /**
+ * This Node represents a place in a plural argument's variant where
+ * the formatted (plural-offset) value is to be put.
+ * @draft ICU 49
+ * @provisional This API might change or be removed in a future release.
+ */
+ REPLACE_NUMBER
+ }
+ /**
+ * Returns the type of this piece of MessageNode contents.
+ * @draft ICU 49
+ * @provisional This API might change or be removed in a future release.
+ */
+ public Type getType() {
+ return type;
+ }
+ /**
+ * {@inheritDoc}
+ * @draft ICU 49
+ * @provisional This API might change or be removed in a future release.
+ */
+ @Override
+ public String toString() {
+ return "{REPLACE_NUMBER}";
+ }
+
+ private MessageContentsNode(Type type) {
+ super();
+ this.type = type;
+ }
+ private static MessageContentsNode createReplaceNumberNode() {
+ return new MessageContentsNode(Type.REPLACE_NUMBER);
+ }
+
+ private Type type;
+ }
+
+ /**
+ * Literal text, a piece of MessageNode contents.
+ * @draft ICU 49
+ * @provisional This API might change or be removed in a future release.
+ */
+ public static class TextNode extends MessageContentsNode {
+ /**
+ * @return the literal text at this point in the message
+ * @draft ICU 49
+ * @provisional This API might change or be removed in a future release.
+ */
+ public String getText() {
+ return text;
+ }
+ /**
+ * {@inheritDoc}
+ * @draft ICU 49
+ * @provisional This API might change or be removed in a future release.
+ */
+ @Override
+ public String toString() {
+ return "«" + text + "»";
+ }
+
+ private TextNode(String text) {
+ super(Type.TEXT);
+ this.text = text;
+ }
+
+ private String text;
+ }
+
+ /**
+ * A piece of MessageNode contents representing a message argument and its details.
+ * @draft ICU 49
+ * @provisional This API might change or be removed in a future release.
+ */
+ public static class ArgNode extends MessageContentsNode {
+ /**
+ * @return the argument type
+ * @draft ICU 49
+ * @provisional This API might change or be removed in a future release.
+ */
+ public MessagePattern.ArgType getArgType() {
+ return argType;
+ }
+ /**
+ * @return the argument name string (the decimal-digit string if the argument has a number)
+ * @draft ICU 49
+ * @provisional This API might change or be removed in a future release.
+ */
+ public String getName() {
+ return name;
+ }
+ /**
+ * @return the argument number, or -1 if none (for a named argument)
+ * @draft ICU 49
+ * @provisional This API might change or be removed in a future release.
+ */
+ public int getNumber() {
+ return number;
+ }
+ /**
+ * @return the argument type string, or null if none was specified
+ * @draft ICU 49
+ * @provisional This API might change or be removed in a future release.
+ */
+ public String getTypeName() {
+ return typeName;
+ }
+ /**
+ * @return the simple-argument style string,
+ * or null if no style is specified and for other argument types
+ * @draft ICU 49
+ * @provisional This API might change or be removed in a future release.
+ */
+ public String getSimpleStyle() {
+ return style;
+ }
+ /**
+ * @return the complex-argument-style object,
+ * or null if the argument type is NONE_ARG or SIMPLE_ARG
+ * @draft ICU 49
+ * @provisional This API might change or be removed in a future release.
+ */
+ public ComplexArgStyleNode getComplexStyle() {
+ return complexStyle;
+ }
+ /**
+ * {@inheritDoc}
+ * @draft ICU 49
+ * @provisional This API might change or be removed in a future release.
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append('{').append(name);
+ if (argType != MessagePattern.ArgType.NONE) {
+ sb.append(',').append(typeName);
+ if (argType == MessagePattern.ArgType.SIMPLE) {
+ if (style != null) {
+ sb.append(',').append(style);
+ }
+ } else {
+ sb.append(',').append(complexStyle.toString());
+ }
+ }
+ return sb.append('}').toString();
+ }
+
+ private ArgNode() {
+ super(Type.ARG);
+ }
+ private static ArgNode createArgNode() {
+ return new ArgNode();
+ }
+
+ private MessagePattern.ArgType argType;
+ private String name;
+ private int number = -1;
+ private String typeName;
+ private String style;
+ private ComplexArgStyleNode complexStyle;
+ }
+
+ /**
+ * A Node representing details of the argument style of a complex argument.
+ * (Which is a choice/plural/select argument which selects among nested messages.)
+ * @draft ICU 49
+ * @provisional This API might change or be removed in a future release.
+ */
+ public static class ComplexArgStyleNode extends Node {
+ /**
+ * @return the argument type (same as getArgType() on the parent ArgNode)
+ * @draft ICU 49
+ * @provisional This API might change or be removed in a future release.
+ */
+ public MessagePattern.ArgType getArgType() {
+ return argType;
+ }
+ /**
+ * @return true if this is a plural style with an explicit offset
+ * @draft ICU 49
+ * @provisional This API might change or be removed in a future release.
+ */
+ public boolean hasExplicitOffset() {
+ return explicitOffset;
+ }
+ /**
+ * @return the plural offset, or 0 if this is not a plural style or
+ * the offset is explicitly or implicitly 0
+ * @draft ICU 49
+ * @provisional This API might change or be removed in a future release.
+ */
+ public double getOffset() {
+ return offset;
+ }
+ /**
+ * @return the list of variants: the nested messages with their selection criteria
+ * @draft ICU 49
+ * @provisional This API might change or be removed in a future release.
+ */
+ public List<VariantNode> getVariants() {
+ return list;
+ }
+ /**
+ * Separates the variants by type.
+ * Intended for use with plural and select argument styles,
+ * not useful for choice argument styles.
+ *
+ * <p>Both parameters are used only for output, and are first cleared.
+ * @param numericVariants Variants with numeric-value selectors (if any) are added here.
+ * Can be null for a select argument style.
+ * @param keywordVariants Variants with keyword selectors, except "other", are added here.
+ * For a plural argument, if this list is empty after the call, then
+ * all variants except "other" have explicit values
+ * and PluralRules need not be called.
+ * @return the "other" variant (the first one if there are several),
+ * null if none (choice style)
+ * @draft ICU 49
+ * @provisional This API might change or be removed in a future release.
+ */
+ public VariantNode getVariantsByType(List<VariantNode> numericVariants,
+ List<VariantNode> keywordVariants) {
+ if (numericVariants != null) {
+ numericVariants.clear();
+ }
+ keywordVariants.clear();
+ VariantNode other = null;
+ for (VariantNode variant : list) {
+ if (variant.isSelectorNumeric()) {
+ numericVariants.add(variant);
+ } else if ("other".equals(variant.getSelector())) {
+ if (other == null) {
+ // Return the first "other" variant. (MessagePattern allows duplicates.)
+ other = variant;
+ }
+ } else {
+ keywordVariants.add(variant);
+ }
+ }
+ return other;
+ }
+ /**
+ * {@inheritDoc}
+ * @draft ICU 49
+ * @provisional This API might change or be removed in a future release.
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append('(').append(argType.toString()).append(" style) ");
+ if (hasExplicitOffset()) {
+ sb.append("offset:").append(offset).append(' ');
+ }
+ return sb.append(list.toString()).toString();
+ }
+
+ private ComplexArgStyleNode(MessagePattern.ArgType argType) {
+ super();
+ this.argType = argType;
+ }
+ private void addVariant(VariantNode variant) {
+ list.add(variant);
+ }
+ private ComplexArgStyleNode freeze() {
+ list = Collections.unmodifiableList(list);
+ return this;
+ }
+
+ private MessagePattern.ArgType argType;
+ private double offset;
+ private boolean explicitOffset;
+ private List<VariantNode> list = new ArrayList<VariantNode>();
+ }
+
+ /**
+ * A Node representing a nested message (nested inside an argument)
+ * with its selection criterium.
+ * @draft ICU 49
+ * @provisional This API might change or be removed in a future release.
+ */
+ public static class VariantNode extends Node {
+ /**
+ * Returns the selector string.
+ * For example: A plural/select keyword ("few"), a plural explicit value ("=1"),
+ * a choice comparison operator ("#").
+ * @return the selector string
+ * @draft ICU 49
+ * @provisional This API might change or be removed in a future release.
+ */
+ public String getSelector() {
+ return selector;
+ }
+ /**
+ * @return true for choice variants and for plural explicit values
+ * @draft ICU 49
+ * @provisional This API might change or be removed in a future release.
+ */
+ public boolean isSelectorNumeric() {
+ return numericValue != MessagePattern.NO_NUMERIC_VALUE;
+ }
+ /**
+ * @return the selector's numeric value, or NO_NUMERIC_VALUE if !isSelectorNumeric()
+ * @draft ICU 49
+ * @provisional This API might change or be removed in a future release.
+ */
+ public double getSelectorValue() {
+ return numericValue;
+ }
+ /**
+ * @return the nested message
+ * @draft ICU 49
+ * @provisional This API might change or be removed in a future release.
+ */
+ public MessageNode getMessage() {
+ return msgNode;
+ }
+ /**
+ * {@inheritDoc}
+ * @draft ICU 49
+ * @provisional This API might change or be removed in a future release.
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ if (isSelectorNumeric()) {
+ sb.append(numericValue).append(" (").append(selector).append(") {");
+ } else {
+ sb.append(selector).append(" {");
+ }
+ return sb.append(msgNode.toString()).append('}').toString();
+ }
+
+ private VariantNode() {
+ super();
+ }
+
+ private String selector;
+ private double numericValue = MessagePattern.NO_NUMERIC_VALUE;
+ private MessageNode msgNode;
+ }
+
+ private static MessageNode buildMessageNode(MessagePattern pattern, int start, int limit) {
+ int prevPatternIndex = pattern.getPart(start).getLimit();
+ MessageNode node = new MessageNode();
+ for (int i = start + 1;; ++i) {
+ MessagePattern.Part part = pattern.getPart(i);
+ int patternIndex = part.getIndex();
+ if (prevPatternIndex < patternIndex) {
+ node.addContentsNode(
+ new TextNode(pattern.getPatternString().substring(prevPatternIndex,
+ patternIndex)));
+ }
+ if (i == limit) {
+ break;
+ }
+ MessagePattern.Part.Type partType = part.getType();
+ if (partType == MessagePattern.Part.Type.ARG_START) {
+ int argLimit = pattern.getLimitPartIndex(i);
+ node.addContentsNode(buildArgNode(pattern, i, argLimit));
+ i = argLimit;
+ part = pattern.getPart(i);
+ } else if (partType == MessagePattern.Part.Type.REPLACE_NUMBER) {
+ node.addContentsNode(MessageContentsNode.createReplaceNumberNode());
+ // else: ignore SKIP_SYNTAX and INSERT_CHAR parts.
+ }
+ prevPatternIndex = part.getLimit();
+ }
+ return node.freeze();
+ }
+
+ private static ArgNode buildArgNode(MessagePattern pattern, int start, int limit) {
+ ArgNode node = ArgNode.createArgNode();
+ MessagePattern.Part part = pattern.getPart(start);
+ MessagePattern.ArgType argType = node.argType = part.getArgType();
+ part = pattern.getPart(++start); // ARG_NAME or ARG_NUMBER
+ node.name = pattern.getSubstring(part);
+ if (part.getType() == MessagePattern.Part.Type.ARG_NUMBER) {
+ node.number = part.getValue();
+ }
+ ++start;
+ switch(argType) {
+ case SIMPLE:
+ // ARG_TYPE
+ node.typeName = pattern.getSubstring(pattern.getPart(start++));
+ if (start < limit) {
+ // ARG_STYLE
+ node.style = pattern.getSubstring(pattern.getPart(start));
+ }
+ break;
+ case CHOICE:
+ node.typeName = "choice";
+ node.complexStyle = buildChoiceStyleNode(pattern, start, limit);
+ break;
+ case PLURAL:
+ node.typeName = "plural";
+ node.complexStyle = buildPluralStyleNode(pattern, start, limit);
+ break;
+ case SELECT:
+ node.typeName = "select";
+ node.complexStyle = buildSelectStyleNode(pattern, start, limit);
+ break;
+ default:
+ // NONE type, nothing else to do
+ break;
+ }
+ return node;
+ }
+
+ private static ComplexArgStyleNode buildChoiceStyleNode(MessagePattern pattern,
+ int start, int limit) {
+ ComplexArgStyleNode node = new ComplexArgStyleNode(MessagePattern.ArgType.CHOICE);
+ while (start < limit) {
+ int valueIndex = start;
+ MessagePattern.Part part = pattern.getPart(start);
+ double value = pattern.getNumericValue(part);
+ start += 2;
+ int msgLimit = pattern.getLimitPartIndex(start);
+ VariantNode variant = new VariantNode();
+ variant.selector = pattern.getSubstring(pattern.getPart(valueIndex + 1));
+ variant.numericValue = value;
+ variant.msgNode = buildMessageNode(pattern, start, msgLimit);
+ node.addVariant(variant);
+ start = msgLimit + 1;
+ }
+ return node.freeze();
+ }
+
+ private static ComplexArgStyleNode buildPluralStyleNode(MessagePattern pattern,
+ int start, int limit) {
+ ComplexArgStyleNode node = new ComplexArgStyleNode(MessagePattern.ArgType.PLURAL);
+ MessagePattern.Part offset = pattern.getPart(start);
+ if (offset.getType().hasNumericValue()) {
+ node.explicitOffset = true;
+ node.offset = pattern.getNumericValue(offset);
+ ++start;
+ }
+ while (start < limit) {
+ MessagePattern.Part selector = pattern.getPart(start++);
+ double value = MessagePattern.NO_NUMERIC_VALUE;
+ MessagePattern.Part part = pattern.getPart(start);
+ if (part.getType().hasNumericValue()) {
+ value = pattern.getNumericValue(part);
+ ++start;
+ }
+ int msgLimit = pattern.getLimitPartIndex(start);
+ VariantNode variant = new VariantNode();
+ variant.selector = pattern.getSubstring(selector);
+ variant.numericValue = value;
+ variant.msgNode = buildMessageNode(pattern, start, msgLimit);
+ node.addVariant(variant);
+ start = msgLimit + 1;
+ }
+ return node.freeze();
+ }
+
+ private static ComplexArgStyleNode buildSelectStyleNode(MessagePattern pattern,
+ int start, int limit) {
+ ComplexArgStyleNode node = new ComplexArgStyleNode(MessagePattern.ArgType.SELECT);
+ while (start < limit) {
+ MessagePattern.Part selector = pattern.getPart(start++);
+ int msgLimit = pattern.getLimitPartIndex(start);
+ VariantNode variant = new VariantNode();
+ variant.selector = pattern.getSubstring(selector);
+ variant.msgNode = buildMessageNode(pattern, start, msgLimit);
+ node.addVariant(variant);
+ start = msgLimit + 1;
+ }
+ return node.freeze();
+ }
+}
--- /dev/null
+/*
+*******************************************************************************
+* Copyright (C) 2011, International Business Machines
+* Corporation and others. All Rights Reserved.
+*******************************************************************************
+* created on: 2011aug12
+* created by: Markus W. Scherer
+*/
+
+package com.ibm.icu.dev.test.format;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import com.ibm.icu.text.MessagePattern;
+import com.ibm.icu.text.MessagePatternUtil;
+import com.ibm.icu.text.MessagePatternUtil.MessageNode;
+import com.ibm.icu.text.MessagePatternUtil.MessageContentsNode;
+import com.ibm.icu.text.MessagePatternUtil.TextNode;
+import com.ibm.icu.text.MessagePatternUtil.ArgNode;
+import com.ibm.icu.text.MessagePatternUtil.ComplexArgStyleNode;
+import com.ibm.icu.text.MessagePatternUtil.VariantNode;
+
+/**
+ * Test MessagePatternUtil (MessagePattern-as-tree-of-nodes API)
+ * by building parallel trees of nodes and verifying that they match.
+ */
+public final class MessagePatternUtilTest extends com.ibm.icu.dev.test.TestFmwk {
+ public static void main(String[] args) throws Exception {
+ new MessagePatternUtilTest().run(args);
+ }
+
+ // The following nested "Expect..." classes are used to build
+ // a tree structure parallel to what the MessagePatternUtil class builds.
+ // These nested test classes are not static so that they have access to TestFmwk methods.
+
+ private class ExpectMessageNode {
+ private ExpectMessageNode expectTextThatContains(String s) {
+ contents.add(new ExpectTextNode(s));
+ return this;
+ }
+ private ExpectMessageNode expectReplaceNumber() {
+ contents.add(new ExpectMessageContentsNode());
+ return this;
+ }
+ private ExpectMessageNode expectNoneArg(Object name) {
+ contents.add(new ExpectArgNode(name));
+ return this;
+ }
+ private ExpectMessageNode expectSimpleArg(Object name, String type) {
+ contents.add(new ExpectArgNode(name, type));
+ return this;
+ }
+ private ExpectMessageNode expectSimpleArg(Object name, String type, String style) {
+ contents.add(new ExpectArgNode(name, type, style));
+ return this;
+ }
+ private ExpectComplexArgNode expectChoiceArg(Object name) {
+ return expectComplexArg(name, MessagePattern.ArgType.CHOICE);
+ }
+ private ExpectComplexArgNode expectPluralArg(Object name) {
+ return expectComplexArg(name, MessagePattern.ArgType.PLURAL);
+ }
+ private ExpectComplexArgNode expectSelectArg(Object name) {
+ return expectComplexArg(name, MessagePattern.ArgType.SELECT);
+ }
+ private ExpectComplexArgNode expectComplexArg(Object name, MessagePattern.ArgType argType) {
+ ExpectComplexArgNode complexArg = new ExpectComplexArgNode(this, name, argType);
+ contents.add(complexArg);
+ return complexArg;
+ }
+ private ExpectComplexArgNode finishVariant() {
+ return parent;
+ }
+ private void checkMatches(MessageNode msg) {
+ // matches() prints all errors.
+ matches(msg);
+ }
+ private boolean matches(MessageNode msg) {
+ List<MessageContentsNode> msgContents = msg.getContents();
+ boolean ok = assertEquals("different numbers of MessageContentsNode",
+ contents.size(), msgContents.size());
+ if (ok) {
+ Iterator<MessageContentsNode> msgIter = msgContents.iterator();
+ for (ExpectMessageContentsNode ec : contents) {
+ ok &= ec.matches(msgIter.next());
+ }
+ }
+ if (!ok) {
+ errln("error in message: " + msg.toString());
+ }
+ return ok;
+ }
+ private ExpectComplexArgNode parent; // for finishVariant()
+ private List<ExpectMessageContentsNode> contents =
+ new ArrayList<ExpectMessageContentsNode>();
+ }
+
+ /**
+ * Base class for message contents nodes.
+ * Used directly for REPLACE_NUMBER nodes, subclassed for others.
+ */
+ private class ExpectMessageContentsNode {
+ protected boolean matches(MessageContentsNode c) {
+ return assertEquals("not a REPLACE_NUMBER node",
+ MessageContentsNode.Type.REPLACE_NUMBER, c.getType());
+ }
+ }
+
+ private class ExpectTextNode extends ExpectMessageContentsNode {
+ private ExpectTextNode(String subString) {
+ this.subString = subString;
+ }
+ @Override
+ protected boolean matches(MessageContentsNode c) {
+ return
+ assertEquals("not a TextNode",
+ MessageContentsNode.Type.TEXT, c.getType()) &&
+ assertTrue("TextNode does not contain \"" + subString + "\"",
+ ((TextNode)c).getText().contains(subString));
+ }
+ private String subString;
+ }
+
+ private class ExpectArgNode extends ExpectMessageContentsNode {
+ private ExpectArgNode(Object name) {
+ this(name, null, null);
+ }
+ private ExpectArgNode(Object name, String type) {
+ this(name, type, null);
+ }
+ private ExpectArgNode(Object name, String type, String style) {
+ if (name instanceof String) {
+ this.name = (String)name;
+ this.number = -1;
+ } else {
+ this.number = (Integer)name;
+ this.name = Integer.toString(this.number);
+ }
+ if (type == null) {
+ argType = MessagePattern.ArgType.NONE;
+ } else {
+ argType = MessagePattern.ArgType.SIMPLE;
+ }
+ this.type = type;
+ this.style = style;
+ }
+ @Override
+ protected boolean matches(MessageContentsNode c) {
+ boolean ok =
+ assertEquals("not an ArgNode",
+ MessageContentsNode.Type.ARG, c.getType());
+ if (!ok) {
+ return ok;
+ }
+ ArgNode arg = (ArgNode)c;
+ ok &= assertEquals("unexpected ArgNode argType",
+ argType, arg.getArgType());
+ ok &= assertEquals("unexpected ArgNode arg name",
+ name, arg.getName());
+ ok &= assertEquals("unexpected ArgNode arg number",
+ number, arg.getNumber());
+ ok &= assertEquals("unexpected ArgNode arg type name",
+ type, arg.getTypeName());
+ ok &= assertEquals("unexpected ArgNode arg style",
+ style, arg.getSimpleStyle());
+ if (argType == MessagePattern.ArgType.NONE || argType == MessagePattern.ArgType.SIMPLE) {
+ ok &= assertNull("unexpected non-null complex style", arg.getComplexStyle());
+ }
+ return ok;
+ }
+ private String name;
+ private int number;
+ protected MessagePattern.ArgType argType;
+ private String type;
+ private String style;
+ }
+
+ private class ExpectComplexArgNode extends ExpectArgNode {
+ private ExpectComplexArgNode(ExpectMessageNode parent,
+ Object name, MessagePattern.ArgType argType) {
+ super(name,
+ argType == MessagePattern.ArgType.CHOICE ? "choice" :
+ argType == MessagePattern.ArgType.PLURAL ? "plural" : "select");
+ this.argType = argType;
+ this.parent = parent;
+ }
+ private ExpectComplexArgNode expectOffset(double offset) {
+ this.offset = offset;
+ explicitOffset = true;
+ return this;
+ }
+ private ExpectMessageNode expectVariant(String selector) {
+ ExpectVariantNode variant = new ExpectVariantNode(this, selector);
+ variants.add(variant);
+ return variant.msg;
+ }
+ private ExpectMessageNode expectVariant(String selector, double value) {
+ ExpectVariantNode variant = new ExpectVariantNode(this, selector, value);
+ variants.add(variant);
+ return variant.msg;
+ }
+ private ExpectMessageNode finishComplexArg() {
+ return parent;
+ }
+ @Override
+ protected boolean matches(MessageContentsNode c) {
+ boolean ok = super.matches(c);
+ if (!ok) {
+ return ok;
+ }
+ ArgNode arg = (ArgNode)c;
+ ComplexArgStyleNode complexStyle = arg.getComplexStyle();
+ ok &= assertNotNull("unexpected null complex style", complexStyle);
+ if (!ok) {
+ return ok;
+ }
+ ok &= assertEquals("unexpected complex-style argType",
+ argType, complexStyle.getArgType());
+ ok &= assertEquals("unexpected complex-style hasExplicitOffset()",
+ explicitOffset, complexStyle.hasExplicitOffset());
+ ok &= assertEquals("unexpected complex-style offset",
+ offset, complexStyle.getOffset());
+ List<VariantNode> complexVariants = complexStyle.getVariants();
+ ok &= assertEquals("different number of variants",
+ variants.size(), complexVariants.size());
+ if (!ok) {
+ return ok;
+ }
+ Iterator<VariantNode> complexIter = complexVariants.iterator();
+ for (ExpectVariantNode variant : variants) {
+ ok &= variant.matches(complexIter.next());
+ }
+ return ok;
+ }
+ private ExpectMessageNode parent; // for finishComplexArg()
+ private boolean explicitOffset;
+ private double offset;
+ private List<ExpectVariantNode> variants = new ArrayList<ExpectVariantNode>();
+ }
+
+ private class ExpectVariantNode {
+ private ExpectVariantNode(ExpectComplexArgNode parent, String selector) {
+ this(parent, selector, MessagePattern.NO_NUMERIC_VALUE);
+ }
+ private ExpectVariantNode(ExpectComplexArgNode parent, String selector, double value) {
+ this.selector = selector;
+ numericValue = value;
+ msg = new ExpectMessageNode();
+ msg.parent = parent;
+ }
+ private boolean matches(VariantNode v) {
+ boolean ok = assertEquals("different selector strings",
+ selector, v.getSelector());
+ ok &= assertEquals("different selector strings",
+ isSelectorNumeric(), v.isSelectorNumeric());
+ ok &= assertEquals("different selector strings",
+ numericValue, v.getSelectorValue());
+ return ok & msg.matches(v.getMessage());
+ }
+ private boolean isSelectorNumeric() {
+ return numericValue != MessagePattern.NO_NUMERIC_VALUE;
+ }
+ private String selector;
+ private double numericValue;
+ private ExpectMessageNode msg;
+ }
+
+ // The actual tests start here. ---------------------------------------- ***
+ // Sample message strings are mostly from the MessagePatternUtilDemo.
+
+ public void TestHello() {
+ // No syntax.
+ MessageNode msg = MessagePatternUtil.buildMessageNode("Hello!");
+ ExpectMessageNode expect = new ExpectMessageNode().expectTextThatContains("Hello");
+ expect.checkMatches(msg);
+ }
+
+ public void TestHelloWithApos() {
+ // Literal ASCII apostrophe.
+ MessageNode msg = MessagePatternUtil.buildMessageNode("Hel'lo!");
+ ExpectMessageNode expect = new ExpectMessageNode().expectTextThatContains("Hel'lo");
+ expect.checkMatches(msg);
+ }
+
+ public void TestHelloWithQuote() {
+ // Apostrophe starts quoted literal text.
+ MessageNode msg = MessagePatternUtil.buildMessageNode("Hel'{o!");
+ ExpectMessageNode expect = new ExpectMessageNode().expectTextThatContains("Hel{o");
+ expect.checkMatches(msg);
+ // Terminating the quote should yield the same result.
+ msg = MessagePatternUtil.buildMessageNode("Hel'{'o!");
+ expect.checkMatches(msg);
+ // Double apostrophe inside quoted literal text still encodes a single apostrophe.
+ msg = MessagePatternUtil.buildMessageNode("a'{bc''de'f");
+ expect = new ExpectMessageNode().expectTextThatContains("a{bc'def");
+ expect.checkMatches(msg);
+ }
+
+ public void TestNoneArg() {
+ // Numbered argument.
+ MessageNode msg = MessagePatternUtil.buildMessageNode("abc{0}def");
+ ExpectMessageNode expect = new ExpectMessageNode().
+ expectTextThatContains("abc").expectNoneArg(0).expectTextThatContains("def");
+ expect.checkMatches(msg);
+ // Named argument.
+ msg = MessagePatternUtil.buildMessageNode("abc{ arg }def");
+ expect = new ExpectMessageNode().
+ expectTextThatContains("abc").expectNoneArg("arg").expectTextThatContains("def");
+ expect.checkMatches(msg);
+ // Numbered and named arguments.
+ msg = MessagePatternUtil.buildMessageNode("abc{1}def{arg}ghi");
+ expect = new ExpectMessageNode().
+ expectTextThatContains("abc").expectNoneArg(1).expectTextThatContains("def").
+ expectNoneArg("arg").expectTextThatContains("ghi");
+ expect.checkMatches(msg);
+ }
+
+ public void TestSimpleArg() {
+ MessageNode msg = MessagePatternUtil.buildMessageNode("a'{bc''de'f{0,number,g'hi''jk'l#}");
+ ExpectMessageNode expect = new ExpectMessageNode().
+ expectTextThatContains("a{bc'def").expectSimpleArg(0, "number", "g'hi''jk'l#");
+ expect.checkMatches(msg);
+ }
+
+ public void TestSelectArg() {
+ MessageNode msg = MessagePatternUtil.buildMessageNode(
+ "abc{2, number}ghi{3, select, xx {xxx} other {ooo}} xyz");
+ ExpectMessageNode expect = new ExpectMessageNode().
+ expectTextThatContains("abc").expectSimpleArg(2, "number").
+ expectTextThatContains("ghi").
+ expectSelectArg(3).
+ expectVariant("xx").expectTextThatContains("xxx").finishVariant().
+ expectVariant("other").expectTextThatContains("ooo").finishVariant().
+ finishComplexArg().
+ expectTextThatContains(" xyz");
+ expect.checkMatches(msg);
+ }
+
+ public void TestPluralArg() {
+ // Plural with only keywords.
+ MessageNode msg = MessagePatternUtil.buildMessageNode(
+ "abc{num_people, plural, offset:17 few{fff} other {oooo}}xyz");
+ ExpectMessageNode expect = new ExpectMessageNode().
+ expectTextThatContains("abc").
+ expectPluralArg("num_people").
+ expectOffset(17).
+ expectVariant("few").expectTextThatContains("fff").finishVariant().
+ expectVariant("other").expectTextThatContains("oooo").finishVariant().
+ finishComplexArg().
+ expectTextThatContains("xyz");
+ expect.checkMatches(msg);
+ // Plural with explicit-value selectors.
+ msg = MessagePatternUtil.buildMessageNode(
+ "abc{ num , plural , offset: 2 =1 {1} =-1 {-1} =3.14 {3.14} other {oo} }xyz");
+ expect = new ExpectMessageNode().
+ expectTextThatContains("abc").
+ expectPluralArg("num").
+ expectOffset(2).
+ expectVariant("=1", 1).expectTextThatContains("1").finishVariant().
+ expectVariant("=-1", -1).expectTextThatContains("-1").finishVariant().
+ expectVariant("=3.14", 3.14).expectTextThatContains("3.14").finishVariant().
+ expectVariant("other").expectTextThatContains("oo").finishVariant().
+ finishComplexArg().
+ expectTextThatContains("xyz");
+ expect.checkMatches(msg);
+ // Plural with number replacement.
+ msg = MessagePatternUtil.buildMessageNode(
+ "a_{0,plural,other{num=#'#'=#'#'={1,number,##}!}}_z");
+ expect = new ExpectMessageNode().
+ expectTextThatContains("a_").
+ expectPluralArg(0).
+ expectVariant("other").
+ expectTextThatContains("num=").expectReplaceNumber().
+ expectTextThatContains("#=").expectReplaceNumber().
+ expectTextThatContains("#=").expectSimpleArg(1, "number", "##").
+ expectTextThatContains("!").finishVariant().
+ finishComplexArg().
+ expectTextThatContains("_z");
+ expect.checkMatches(msg);
+ }
+
+ public void TestChoiceArg() {
+ MessageNode msg = MessagePatternUtil.buildMessageNode(
+ "a_{0,choice,-∞ #-inf| 5≤ five | 99 # ninety'|'nine }_z");
+ ExpectMessageNode expect = new ExpectMessageNode().
+ expectTextThatContains("a_").
+ expectChoiceArg(0).
+ expectVariant("#", Double.NEGATIVE_INFINITY).
+ expectTextThatContains("-inf").finishVariant().
+ expectVariant("≤", 5).expectTextThatContains(" five ").finishVariant().
+ expectVariant("#", 99).expectTextThatContains(" ninety|nine ").finishVariant().
+ finishComplexArg().
+ expectTextThatContains("_z");
+ expect.checkMatches(msg);
+ }
+
+ public void TestComplexArgs() {
+ MessageNode msg = MessagePatternUtil.buildMessageNode(
+ "I don't {a,plural,other{w'{'on't #'#'}} and "+
+ "{b,select,other{shan't'}'}} '{'''know'''}' and "+
+ "{c,choice,0#can't'|'}"+
+ "{z,number,#'#'###.00'}'}.");
+ ExpectMessageNode expect = new ExpectMessageNode().
+ expectTextThatContains("I don't ").
+ expectPluralArg("a").
+ expectVariant("other").
+ expectTextThatContains("w{on't ").expectReplaceNumber().
+ expectTextThatContains("#").finishVariant().
+ finishComplexArg().
+ expectTextThatContains(" and ").
+ expectSelectArg("b").
+ expectVariant("other").expectTextThatContains("shan't}").finishVariant().
+ finishComplexArg().
+ expectTextThatContains(" {'know'} and ").
+ expectChoiceArg("c").
+ expectVariant("#", 0).expectTextThatContains("can't|").finishVariant().
+ finishComplexArg().
+ expectSimpleArg("z", "number", "#'#'###.00'}'").
+ expectTextThatContains(".");
+ expect.checkMatches(msg);
+ }
+
+ /**
+ * @return the text string of the VariantNode's message;
+ * assumes that its message consists of only text
+ */
+ private String variantText(VariantNode v) {
+ return ((TextNode)v.getMessage().getContents().get(0)).getText();
+ }
+
+ public void TestPluralVariantsByType() {
+ MessageNode msg = MessagePatternUtil.buildMessageNode(
+ "{p,plural,a{A}other{O}=4{iv}b{B}other{U}=2{ii}}");
+ ExpectMessageNode expect = new ExpectMessageNode().
+ expectPluralArg("p").
+ expectVariant("a").expectTextThatContains("A").finishVariant().
+ expectVariant("other").expectTextThatContains("O").finishVariant().
+ expectVariant("=4", 4).expectTextThatContains("iv").finishVariant().
+ expectVariant("b").expectTextThatContains("B").finishVariant().
+ expectVariant("other").expectTextThatContains("U").finishVariant().
+ expectVariant("=2", 2).expectTextThatContains("ii").finishVariant().
+ finishComplexArg();
+ if (!expect.matches(msg)) {
+ return;
+ }
+ List<VariantNode> numericVariants = new ArrayList<VariantNode>();
+ List<VariantNode> keywordVariants = new ArrayList<VariantNode>();
+ VariantNode other =
+ ((ArgNode)msg.getContents().get(0)).getComplexStyle().
+ getVariantsByType(numericVariants, keywordVariants);
+ assertEquals("'other' selector", "other", other.getSelector());
+ assertEquals("message string of first 'other'", "O", variantText(other));
+
+ assertEquals("numericVariants.size()", 2, numericVariants.size());
+ VariantNode v = numericVariants.get(0);
+ assertEquals("numericVariants[0] selector", "=4", v.getSelector());
+ assertEquals("numericVariants[0] selector value", 4., v.getSelectorValue());
+ assertEquals("numericVariants[0] text", "iv", variantText(v));
+ v = numericVariants.get(1);
+ assertEquals("numericVariants[1] selector", "=2", v.getSelector());
+ assertEquals("numericVariants[1] selector value", 2., v.getSelectorValue());
+ assertEquals("numericVariants[1] text", "ii", variantText(v));
+
+ assertEquals("keywordVariants.size()", 2, keywordVariants.size());
+ v = keywordVariants.get(0);
+ assertEquals("keywordVariants[0] selector", "a", v.getSelector());
+ assertFalse("keywordVariants[0].isSelectorNumeric()", v.isSelectorNumeric());
+ assertEquals("keywordVariants[0] text", "A", variantText(v));
+ v = keywordVariants.get(1);
+ assertEquals("keywordVariants[1] selector", "b", v.getSelector());
+ assertFalse("keywordVariants[1].isSelectorNumeric()", v.isSelectorNumeric());
+ assertEquals("keywordVariants[1] text", "B", variantText(v));
+ }
+
+ public void TestSelectVariantsByType() {
+ MessageNode msg = MessagePatternUtil.buildMessageNode(
+ "{s,select,a{A}other{O}b{B}other{U}}");
+ ExpectMessageNode expect = new ExpectMessageNode().
+ expectSelectArg("s").
+ expectVariant("a").expectTextThatContains("A").finishVariant().
+ expectVariant("other").expectTextThatContains("O").finishVariant().
+ expectVariant("b").expectTextThatContains("B").finishVariant().
+ expectVariant("other").expectTextThatContains("U").finishVariant().
+ finishComplexArg();
+ if (!expect.matches(msg)) {
+ return;
+ }
+ // Check that we can use numericVariants = null.
+ List<VariantNode> keywordVariants = new ArrayList<VariantNode>();
+ VariantNode other =
+ ((ArgNode)msg.getContents().get(0)).getComplexStyle().
+ getVariantsByType(null, keywordVariants);
+ assertEquals("'other' selector", "other", other.getSelector());
+ assertEquals("message string of first 'other'", "O", variantText(other));
+
+ assertEquals("keywordVariants.size()", 2, keywordVariants.size());
+ VariantNode v = keywordVariants.get(0);
+ assertEquals("keywordVariants[0] selector", "a", v.getSelector());
+ assertFalse("keywordVariants[0].isSelectorNumeric()", v.isSelectorNumeric());
+ assertEquals("keywordVariants[0] text", "A", variantText(v));
+ v = keywordVariants.get(1);
+ assertEquals("keywordVariants[1] selector", "b", v.getSelector());
+ assertFalse("keywordVariants[1].isSelectorNumeric()", v.isSelectorNumeric());
+ assertEquals("keywordVariants[1] text", "B", variantText(v));
+ }
+}
/*
*******************************************************************************
- * Copyright (c) 2004-2010, International Business Machines
+ * Copyright (c) 2004-2011, International Business Machines
* Corporation and others. All Rights Reserved.
* Copyright (C) 2010 , Yahoo! Inc.
*******************************************************************************
super(new String[] {
"TestMessageFormat",
"MessageRegression",
+ "MessagePatternUtilTest",
});
}
}