Home | History | Annotate | Download | only in messagepattern
      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