• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
3 /*
4 *******************************************************************************
5 *   Copyright (C) 2011, International Business Machines
6 *   Corporation and others.  All Rights Reserved.
7 *******************************************************************************
8 *   created on: 2011jul14
9 *   created by: Markus W. Scherer
10 */
11 
12 package com.ibm.icu.samples.text.messagepattern;
13 
14 import java.util.ArrayList;
15 import java.util.List;
16 
17 import com.ibm.icu.text.MessagePattern;
18 import com.ibm.icu.text.MessagePatternUtil;
19 import com.ibm.icu.text.MessagePatternUtil.VariantNode;
20 
21 /**
22  * Demo code for MessagePattern class.
23  * @author Markus Scherer
24  * @since 2011-jul-14
25  */
26 public class MessagePatternUtilDemo {
27     private static final String manySpaces="                    ";
28 
printMessage(MessagePatternUtil.MessageNode msg, int depth)29     private static final void printMessage(MessagePatternUtil.MessageNode msg, int depth) {
30         String indent = manySpaces.substring(0, depth * 2);
31         for (MessagePatternUtil.MessageContentsNode contents : msg.getContents()) {
32             switch (contents.getType()) {
33             case TEXT:
34                 System.out.println(indent + "text: «" +
35                                    ((MessagePatternUtil.TextNode)contents).getText() + "»");
36                 break;
37             case ARG:
38                 printArg((MessagePatternUtil.ArgNode)contents, depth);
39                 break;
40             case REPLACE_NUMBER:
41                 System.out.println(indent + "replace: number");
42                 break;
43             }
44         }
45     }
46 
printArg(MessagePatternUtil.ArgNode arg, int depth)47     private static final void printArg(MessagePatternUtil.ArgNode arg, int depth) {
48         System.out.print(manySpaces.substring(0, depth * 2) + "arg: «" + arg.getName() + "»");
49         MessagePattern.ArgType argType = arg.getArgType();
50         if (argType == MessagePattern.ArgType.NONE) {
51             System.out.println(" (no type)");
52         } else {
53             System.out.print(" (" + arg.getTypeName() + ")");
54             if (argType == MessagePattern.ArgType.SIMPLE) {
55                 String styleString = arg.getSimpleStyle();
56                 if (styleString == null) {
57                     System.out.println(" (no style)");
58                 } else {
59                     System.out.println(" style: «" + styleString + "»");
60                 }
61             } else {
62                 System.out.println();
63                 printComplexArgStyle(arg.getComplexStyle(), depth + 1);
64             }
65         }
66     }
67 
printComplexArgStyle(MessagePatternUtil.ComplexArgStyleNode style, int depth)68     private static final void printComplexArgStyle(MessagePatternUtil.ComplexArgStyleNode style,
69                                                    int depth) {
70         if (style.hasExplicitOffset()) {
71             System.out.println(manySpaces.substring(0, depth * 2) + "offset: " + style.getOffset());
72         }
73         String indent = manySpaces.substring(0, depth * 2);
74         MessagePattern.ArgType argType = style.getArgType();
75         for (MessagePatternUtil.VariantNode variant : style.getVariants()) {
76             double value;
77             switch (argType) {
78             case CHOICE:
79                 System.out.println(indent + variant.getSelectorValue() + " " +
80                                    variant.getSelector() + ":");
81                 break;
82             case PLURAL:
83                 value = variant.getSelectorValue();
84                 if (value == MessagePattern.NO_NUMERIC_VALUE) {
85                     System.out.println(indent + variant.getSelector() + ":");
86                 } else {
87                     System.out.println(indent + variant.getSelector() + " (" + value + "):");
88                 }
89                 break;
90             case SELECT:
91                 System.out.println(indent + variant.getSelector() + ":");
92                 break;
93             }
94             printMessage(variant.getMessage(), depth + 1);
95         }
96     }
97 
98     /**
99      * This is a <em>prototype/demo/sample</em> for how we could use the MessagePatternUtil class
100      * for generating something like JavaScript code for evaluating some
101      * of the MessageFormat syntax.
102      *
103      * <p>This is not intended to be production code, nor to generate production code
104      * or even syntactically correct JavaScript.
105      * @param msg
106      */
genCode(MessagePatternUtil.MessageNode msg)107     private static final void genCode(MessagePatternUtil.MessageNode msg) {
108         List<String> args = new ArrayList<String>();
109         addArgs(msg, args);
110         System.out.print("def function(");
111         boolean firstArg = true;
112         for (String argName : args) {
113             if (firstArg) {
114                 System.out.print(argName);
115                 firstArg = false;
116             } else {
117                 System.out.print(", " + argName);
118             }
119         }
120         System.out.println(") {");
121         genCode(msg, 1, true, "");
122         System.out.println("  return result");
123         System.out.println("}");
124     }
125 
genCode(MessagePatternUtil.MessageNode msg, int depth, boolean firstResult, String pluralNumber)126     private static final void genCode(MessagePatternUtil.MessageNode msg,
127                                       int depth,
128                                       boolean firstResult,
129                                       String pluralNumber) {
130         String prefix = manySpaces.substring(0, depth * 2) + "result ";
131         for (MessagePatternUtil.MessageContentsNode contents : msg.getContents()) {
132             String operator = firstResult ? "=" : "+=";
133             switch (contents.getType()) {
134             case TEXT:
135                 System.out.println(
136                         prefix + operator + " \"" +
137                         escapeString(((MessagePatternUtil.TextNode)contents).getText()) +
138                 "\"");
139                 break;
140             case ARG:
141                 genCode((MessagePatternUtil.ArgNode)contents, depth, firstResult);
142                 break;
143             case REPLACE_NUMBER:
144                 System.out.println(prefix + operator + " formatNumber(" + pluralNumber + ")");
145                 break;
146             }
147             firstResult = false;
148         }
149     }
150 
genCode(MessagePatternUtil.ArgNode arg, int depth, boolean firstResult)151     private static final void genCode(MessagePatternUtil.ArgNode arg,
152                                       int depth,
153                                       boolean firstResult) {
154         String prefix = manySpaces.substring(0, depth * 2) + "result ";
155         String operator = firstResult ? "=" : "+=";
156         String argName = arg.getName();
157         if (arg.getNumber() >= 0) {
158             argName = "arg_" + argName;  // Prefix for numbered argument.
159         }
160         switch (arg.getArgType()) {
161         case NONE:
162             System.out.println(prefix + operator + " " + argName);
163             break;
164         case SIMPLE:
165         case CHOICE:
166             System.out.println(prefix + operator + " \"(unsupported syntax)\"");
167             break;
168         case PLURAL:
169             genCodeForPlural(arg.getComplexStyle(), depth, firstResult, argName);
170             break;
171         case SELECT:
172             genCodeForSelect(arg.getComplexStyle(), depth, firstResult, argName);
173             break;
174         }
175     }
176 
genCodeForPlural(MessagePatternUtil.ComplexArgStyleNode style, int depth, boolean firstResult, String argName)177     private static final void genCodeForPlural(MessagePatternUtil.ComplexArgStyleNode style,
178                                                int depth,
179                                                boolean firstResult,
180                                                String argName) {
181         List<MessagePatternUtil.VariantNode> numericVariants =
182             new ArrayList<MessagePatternUtil.VariantNode>();
183         List<MessagePatternUtil.VariantNode> keywordVariants =
184             new ArrayList<MessagePatternUtil.VariantNode>();
185         MessagePatternUtil.VariantNode otherVariant =
186             style.getVariantsByType(numericVariants, keywordVariants);
187         double offset = style.getOffset();
188         String pluralNumber = offset == 0. ? argName : argName + " - " + offset;
189         int origDepth = depth;
190         if (!numericVariants.isEmpty()) {
191             genCodeForNumericVariants(numericVariants, depth++, firstResult, argName, pluralNumber);
192         }
193         if (!keywordVariants.isEmpty()) {
194             System.out.println(manySpaces.substring(0, depth * 2) +
195                                "_keyword = PluralRules.select(" + pluralNumber + ")");
196             genCodeForKeywordVariants(keywordVariants, depth++, firstResult,
197                                       "_keyword", pluralNumber);
198         }
199         genCode(otherVariant.getMessage(), depth, firstResult, pluralNumber);
200         if (origDepth < depth) {
201             System.out.println(manySpaces.substring(0, --depth * 2) + "}");
202             if (origDepth < depth) {
203                 System.out.println(manySpaces.substring(0, --depth * 2) + "}");
204             }
205         }
206     }
207 
genCodeForSelect(MessagePatternUtil.ComplexArgStyleNode style, int depth, boolean firstResult, String argName)208     private static final void genCodeForSelect(MessagePatternUtil.ComplexArgStyleNode style,
209                                                int depth,
210                                                boolean firstResult,
211                                                String argName) {
212         List<MessagePatternUtil.VariantNode> keywordVariants =
213             new ArrayList<MessagePatternUtil.VariantNode>();
214         MessagePatternUtil.VariantNode otherVariant = style.getVariantsByType(null, keywordVariants);
215         if (keywordVariants.isEmpty()) {
216             genCode(otherVariant.getMessage(), depth, firstResult, "");
217         } else {
218             genCodeForKeywordVariants(keywordVariants, depth, firstResult, argName, "");
219             genCode(otherVariant.getMessage(), depth + 1, firstResult, "");
220             System.out.println(manySpaces.substring(0, depth * 2) + "}");
221         }
222     }
223 
genCodeForNumericVariants(List<VariantNode> variants, int depth, boolean firstResult, String varName, String pluralNumber)224     private static final void genCodeForNumericVariants(List<VariantNode> variants,
225                                                         int depth,
226                                                         boolean firstResult,
227                                                         String varName,
228                                                         String pluralNumber) {
229         String indent = manySpaces.substring(0, depth++ * 2);
230         boolean firstVariant = true;
231         for (MessagePatternUtil.VariantNode variant : variants) {
232             System.out.println(
233                     indent +
234                     (firstVariant ? "if (" : "} else if (") +
235                     varName + " == " + variant.getSelectorValue() + ") {");
236             genCode(variant.getMessage(), depth, firstResult, pluralNumber);
237             firstVariant = false;
238         }
239         System.out.println(indent + "} else {");
240     }
241 
genCodeForKeywordVariants(List<VariantNode> variants, int depth, boolean firstResult, String varName, String pluralNumber)242     private static final void genCodeForKeywordVariants(List<VariantNode> variants,
243                                                         int depth,
244                                                         boolean firstResult,
245                                                         String varName,
246                                                         String pluralNumber) {
247         String indent = manySpaces.substring(0, depth++ * 2);
248         boolean firstVariant = true;
249         for (MessagePatternUtil.VariantNode variant : variants) {
250             System.out.println(
251                     indent +
252                     (firstVariant ? "if (" : "} else if (") +
253                     varName + " == \"" + variant.getSelector() + "\") {");
254             genCode(variant.getMessage(), depth, firstResult, pluralNumber);
255             firstVariant = false;
256         }
257         System.out.println(indent + "} else {");
258     }
259 
260     /**
261      * Adds the message's argument names to the args list.
262      * Adds each argument only once, in the order of first appearance.
263      * Numbered arguments get an "arg_" prefix prepended.
264      * @param msg
265      * @param args
266      */
addArgs(MessagePatternUtil.MessageNode msg, List<String> args)267     private static final void addArgs(MessagePatternUtil.MessageNode msg, List<String> args) {
268         for (MessagePatternUtil.MessageContentsNode contents : msg.getContents()) {
269             if (contents.getType() == MessagePatternUtil.MessageContentsNode.Type.ARG) {
270                 MessagePatternUtil.ArgNode arg = (MessagePatternUtil.ArgNode)contents;
271                 String argName;
272                 if (arg.getNumber() >= 0) {
273                     argName = "arg_" + arg.getNumber();  // Prefix for numbered argument.
274                 } else {
275                     argName = arg.getName();
276                 }
277                 if (!args.contains(argName)) {
278                     args.add(argName);
279                 }
280                 MessagePatternUtil.ComplexArgStyleNode complexStyle = arg.getComplexStyle();
281                 if (complexStyle != null) {
282                     for (MessagePatternUtil.VariantNode variant : complexStyle.getVariants()) {
283                         addArgs(variant.getMessage(), args);
284                     }
285                 }
286             }
287         }
288     }
289 
escapeString(String s)290     private static final String escapeString(String s) {
291         if (s.indexOf('"') < 0) {
292             return s;
293         } else {
294             return s.replace("\"", "\\\"");
295         }
296     }
297 
print(String s)298     private static final MessagePatternUtil.MessageNode print(String s) {
299         System.out.println("message:  «" + s + "»");
300         try {
301             MessagePatternUtil.MessageNode msg = MessagePatternUtil.buildMessageNode(s);
302             printMessage(msg, 1);
303             genCode(msg);
304             return msg;
305         } catch(Exception e) {
306             System.out.println("Exception: "+e.getMessage());
307             return null;
308         }
309     }
310 
main(String[] argv)311     public static void main(String[] argv) {
312         print("Hello!");
313         print("Hel'lo!");
314         print("Hel'{o");
315         print("Hel'{'o");
316         // double apostrophe inside quoted literal text still encodes a single apostrophe
317         print("a'{bc''de'f");
318         print("a'{bc''de'f{0,number,g'hi''jk'l#}");
319         print("abc{0}def");
320         print("abc{ arg }def");
321         print("abc{1}def{arg}ghi");
322         print("abc{2, number}ghi{3, select, xx {xxx} other {ooo}} xyz");
323         print("abc{gender,select,"+
324                   "other{His name is {tc,XMB,<ph name=\"PERSON\">{$PERSON}</ph>}.}}xyz");
325         print("abc{num_people, plural, offset:17 few{fff} other {oooo}}xyz");
326         print("abc{ num , plural , offset: 2 =1 {1} =-1 {-1} =3.14 {3.14} other {oo} }xyz");
327         print("I don't {a,plural,other{w'{'on't #'#'}} and "+
328               "{b,select,other{shan't'}'}} '{'''know'''}' and "+
329               "{c,choice,0#can't'|'}"+
330               "{z,number,#'#'###.00'}'}.");
331         print("a_{0,choice,-∞ #-inf|  5≤ five | 99 # ninety'|'nine  }_z");
332         print("a_{0,plural,other{num=#'#'=#'#'={1,number,##}!}}_z");
333         print("}}}{0}}");  // yes, unmatched '}' are ok in ICU MessageFormat
334         print("Hello {0}!");
335         String msg="++{0, select, female{{1} calls you her friend}"+
336                                  "other{{1} calls you '{their}' friend}"+
337                                  "male{{1} calls you his friend}}--";
338         print(msg);
339         msg="_'__{gender, select, female{Her n'ame is {person_name}.}"+
340                                  "other{His n'ame is {person_name}.}}__'_";
341         print(msg);
342         print("{num,plural,offset:1 " +
343                 "=0{no one} =1{one, that is one and # others} " +
344                 "one{one and # (probably 1) others} few{one and # others} " +
345                 "other{lots & lots}}");
346         print(
347             "{p1_gender,select," +
348               "female{" +
349                 "{p2_gender,select," +
350                   "female{" +
351                     "{num_people,plural,offset:1 "+
352                       "=0{she alone}" +
353                       "=1{she and her girlfriend {p2}}" +
354                       "=2{she and her girlfriend {p2} and another}" +
355                       "other{she, her girlfriend {p2} and # others}}}" +
356                   "male{" +
357                     "{num_people,plural,offset:1 "+
358                       "=0{she alone}" +
359                       "=1{she and her boyfriend {p2}}" +
360                       "=2{she and her boyfriend {p2} and another}" +
361                       "other{she, her boyfriend {p2} and # others}}}" +
362                   "other{" +
363                     "{num_people,plural,offset:1 "+
364                       "=0{she alone}" +
365                       "=1{she and her friend {p2}}" +
366                       "=2{she and her friend {p2} and another}" +
367                       "other{she, her friend {p2} and # others}}}}}" +
368               "male{" +
369                 "{p2_gender,select," +
370                   "female{" +
371                     "{num_people,plural,offset:1 "+
372                       "=0{he alone}" +
373                       "=1{he and his girlfriend {p2}}" +
374                       "=2{he and his girlfriend {p2} and another}" +
375                       "other{he, his girlfriend {p2} and # others}}}" +
376                     "male{" +
377                       "{num_people,plural,offset:1 "+
378                         "=0{he alone}" +
379                         "=1{he and his boyfriend {p2}}" +
380                         "=2{he and his boyfriend {p2} and another}" +
381                         "other{he, his boyfriend {p2} and # others}}}" +
382                     "other{" +
383                       "{num_people,plural,offset:1 "+
384                         "=0{she alone}" +
385                         "=1{she and his friend {p2}}" +
386                         "=2{she and his friend {p2} and another}" +
387                         "other{she, his friend {p2} and # others}}}}}" +
388               "other{" +
389                 "{p2_gender,select," +
390                   "female{" +
391                     "{num_people,plural,offset:1 "+
392                       "=0{they alone}" +
393                       "=1{they and their girlfriend {p2}}" +
394                       "=2{they and their girlfriend {p2} and another}" +
395                       "other{they, their girlfriend {p2} and # others}}}" +
396                   "male{" +
397                     "{num_people,plural,offset:1 "+
398                       "=0{they alone}" +
399                       "=1{they and their boyfriend {p2}}" +
400                       "=2{they and their boyfriend {p2} and another}" +
401                       "other{they, their boyfriend {p2} and # others}}}" +
402                   "other{" +
403                     "{num_people,plural,offset:1 "+
404                       "=0{they alone}" +
405                       "=1{they and their friend {p2}}" +
406                       "=2{they and their friend {p2} and another}" +
407                       "other{they, their friend {p2} and # others}}}}}}");
408     }
409 }
410