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
12package com.ibm.icu.samples.text.messagepattern;
13
14import java.util.ArrayList;
15import java.util.List;
16
17import com.ibm.icu.text.MessagePattern;
18import com.ibm.icu.text.MessagePatternUtil;
19import com.ibm.icu.text.MessagePatternUtil.VariantNode;
20
21/**
22 * Demo code for MessagePattern class.
23 * @author Markus Scherer
24 * @since 2011-jul-14
25 */
26public 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