1 // 2016 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html#License 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 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 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 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 */ 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 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 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 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 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 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 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 */ 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 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 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 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