ICU-8745 merge MessagePatternUtil into trunk, from merge --reintegrate branches/markus/msgnodes from r30506
X-SVN-Rev: 30510
This commit is contained in:
parent
3b11db76a4
commit
69c0605df2
@ -0,0 +1,403 @@
|
||||
/*
|
||||
*******************************************************************************
|
||||
* 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}}}}}}");
|
||||
}
|
||||
}
|
@ -0,0 +1,613 @@
|
||||
/*
|
||||
*******************************************************************************
|
||||
* 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();
|
||||
}
|
||||
}
|
@ -0,0 +1,508 @@
|
||||
/*
|
||||
*******************************************************************************
|
||||
* 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));
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
*******************************************************************************
|
||||
* 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.
|
||||
*******************************************************************************
|
||||
@ -122,6 +122,7 @@ public class TestAll extends TestGroup {
|
||||
super(new String[] {
|
||||
"TestMessageFormat",
|
||||
"MessageRegression",
|
||||
"MessagePatternUtilTest",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user