Skip to content

Commit dc01a8f

Browse files
committed
List formatting with grammatical inflection on each list item #3
1 parent 6b17832 commit dc01a8f

File tree

1 file changed

+344
-0
lines changed
  • experiments/data_model/java_mihai/src/test/java/com/mihnita/mf2/messageformat

1 file changed

+344
-0
lines changed
Lines changed: 344 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,344 @@
1+
package com.mihnita.mf2.messageformat;
2+
3+
import static com.mihnita.mf2.messageformat.helpers.ConstSelectors.CASE_EXACTLY_ONE;
4+
import static com.mihnita.mf2.messageformat.helpers.ConstSelectors.CASE_EXACTLY_ZERO;
5+
import static com.mihnita.mf2.messageformat.helpers.ConstSelectors.CASE_OTHER;
6+
import static org.junit.Assert.assertEquals;
7+
8+
import java.util.ArrayList;
9+
import java.util.Collection;
10+
import java.util.HashMap;
11+
import java.util.List;
12+
import java.util.Locale;
13+
import java.util.Map;
14+
15+
import org.junit.Test;
16+
import org.junit.runner.RunWith;
17+
import org.junit.runners.JUnit4;
18+
19+
import com.ibm.icu.text.ListFormatter;
20+
import com.ibm.icu.text.ListFormatter.Type;
21+
import com.ibm.icu.text.ListFormatter.Width;
22+
import com.ibm.icu.util.ULocale;
23+
import com.mihnita.mf2.messageformat.datamodel.IPart;
24+
import com.mihnita.mf2.messageformat.datamodel.ISelectorMessage.ISelectorVal;
25+
import com.mihnita.mf2.messageformat.datamodel.functions.IPlaceholderFormatter;
26+
import com.mihnita.mf2.messageformat.impl.Placeholder;
27+
import com.mihnita.mf2.messageformat.impl.PlainText;
28+
import com.mihnita.mf2.messageformat.impl.SelectorMessage;
29+
import com.mihnita.mf2.messageformat.impl.SelectorMessage.SelectorArg;
30+
import com.mihnita.mf2.messageformat.impl.SimpleMessage;
31+
32+
@RunWith(JUnit4.class)
33+
@SuppressWarnings("static-method")
34+
public class FancyListTest {
35+
36+
// List formatting with grammatical inflection on each list item #3
37+
// https://github.com/unicode-org/message-format-wg/issues/3
38+
39+
@Test
40+
public void testGrammarCaseBlackBox() {
41+
final String locale = "ro";
42+
Placeholder.CUSTOM_FORMATTER_FUNC.put("grammarBB", new GrammarFormatter(locale));
43+
44+
IPart[] parts = {
45+
new PlainText("Cartea "),
46+
new Placeholder("owner", "grammarBB", Parameters.ph().put("case", "genitive").build())
47+
};
48+
49+
SimpleMessage mf = new SimpleMessage("", locale, parts);
50+
51+
Map<String, Object> params = new HashMap<>();
52+
params.put("owner", "Maria");
53+
assertEquals("Cartea Mariei", mf.format(params));
54+
params.put("owner", "Rodica");
55+
assertEquals("Cartea Rodicăi", mf.format(params));
56+
params.put("owner", "Ileana");
57+
assertEquals("Cartea Ilenei", mf.format(params));
58+
params.put("owner", "Petre");
59+
assertEquals("Cartea lui Petre", mf.format(params));
60+
}
61+
62+
@Test
63+
public void testSimpleList() {
64+
final String locale = "ro";
65+
66+
String [] peopleNamesEmpty = {};
67+
String [] peopleNames1Female = { "Maria" };
68+
String [] peopleNames1Male = { "Petre" };
69+
String [] peopleNames = { "Maria", "Ileana", "Petre" };
70+
71+
IPart[] partsExactly0 = {
72+
new PlainText("Nu am dat cadou nimanui"),
73+
};
74+
IPart[] partsExactly1 = {
75+
new PlainText("I-am dat cadou "),
76+
new Placeholder("people", "listFormat")
77+
};
78+
IPart[] partsOther = {
79+
new PlainText("Le-am dat cadou "),
80+
new Placeholder("people", "listFormat")
81+
};
82+
83+
final SelectorArg[] selectorArgs = { new SelectorArg("listLen", "plural") };
84+
MsgMap messages = MsgMap.sel()
85+
.put(new ISelectorVal[]{CASE_EXACTLY_ZERO}, new SimpleMessage("", locale, partsExactly0))
86+
.put(new ISelectorVal[]{CASE_EXACTLY_ONE}, new SimpleMessage("", locale, partsExactly1))
87+
.put(new ISelectorVal[]{CASE_OTHER}, new SimpleMessage("", locale, partsOther));
88+
89+
Placeholder.CUSTOM_FORMATTER_FUNC.put("listFormat", new FormatList());
90+
final SelectorMessage mf = new SelectorMessage("id", locale, selectorArgs, messages.map());
91+
92+
Map<String, Object> params = new HashMap<>();
93+
94+
params.put("listLen", peopleNamesEmpty.length);
95+
params.put("people", peopleNamesEmpty);
96+
assertEquals("Nu am dat cadou nimanui", mf.format(params));
97+
98+
params.put("listLen", peopleNames1Female.length);
99+
params.put("people", peopleNames1Female);
100+
assertEquals("I-am dat cadou Maria", mf.format(params));
101+
102+
params.put("listLen", peopleNames1Male.length);
103+
params.put("people", peopleNames1Male);
104+
assertEquals("I-am dat cadou Petre", mf.format(params));
105+
106+
params.put("listLen", peopleNames.length);
107+
params.put("people", peopleNames);
108+
assertEquals("Le-am dat cadou Maria, Ileana și Petre", mf.format(params));
109+
110+
partsOther[1] = new Placeholder("people", "listFormat", Parameters.ph().put("listType", "or").build());
111+
assertEquals("Le-am dat cadou Maria, Ileana sau Petre", mf.format(params));
112+
113+
partsOther[1] = new Placeholder("people", "listFormat", Parameters.ph().put("listWidth", "narrow").build());
114+
assertEquals("Le-am dat cadou Maria, Ileana, Petre", mf.format(params));
115+
}
116+
117+
@Test
118+
public void testListWithItemProcess() {
119+
final String locale = "ro";
120+
121+
String [] peopleNames = { "Maria", "Ileana", "Petre" };
122+
IPart[] partsOther = {
123+
new PlainText("Le-am dat cadou "),
124+
new Placeholder("people", "listFormat", Parameters.ph()
125+
.put("listForEach", "grammarBB")
126+
.put("case", "genitive")
127+
.build())
128+
};
129+
130+
SimpleMessage mf = new SimpleMessage("", locale, partsOther);
131+
132+
Placeholder.CUSTOM_FORMATTER_FUNC.put("listFormat", new FormatList());
133+
Placeholder.CUSTOM_FORMATTER_FUNC.put("grammarBB", new GrammarFormatter(locale));
134+
135+
Map<String, Object> params = new HashMap<>();
136+
params.put("listLen", peopleNames.length);
137+
params.put("people", peopleNames);
138+
assertEquals("Le-am dat cadou Mariei, Ilenei și lui Petre", mf.format(params));
139+
}
140+
141+
@Test
142+
public void testListWithItemMultiProcess() {
143+
final String locale = "ro";
144+
145+
Person [] peopleNames = {
146+
new Person("Maria", "Stanescu"),
147+
new Person("Ileana", "Zamfir"),
148+
new Person("Petre", "Belu"),
149+
};
150+
IPart[] partsOther = {
151+
new PlainText("Le-am dat cadou "),
152+
new Placeholder("people", "listFormat", Parameters.ph()
153+
.put("listForEach", "personName|grammarBB")
154+
.put("case", "genitive")
155+
.put("personName", "first")
156+
.build())
157+
};
158+
159+
SimpleMessage mf = new SimpleMessage("", locale, partsOther);
160+
161+
Placeholder.CUSTOM_FORMATTER_FUNC.put("listFormat", new FormatList());
162+
Placeholder.CUSTOM_FORMATTER_FUNC.put("grammarBB", new GrammarFormatter(locale));
163+
Placeholder.CUSTOM_FORMATTER_FUNC.put("personName", new GetPersonName());
164+
165+
Map<String, Object> params = new HashMap<>();
166+
params.put("listLen", peopleNames.length);
167+
params.put("people", peopleNames);
168+
assertEquals("Le-am dat cadou Mariei, Ilenei și lui Petre", mf.format(params));
169+
170+
IPart[] partsOtherLastName = {
171+
new PlainText("Le-am dat cadou "),
172+
new Placeholder("people", "listFormat", Parameters.ph()
173+
.put("listForEach", "personName|grammarBB")
174+
.put("case", "genitive")
175+
.put("personName", "last") // LAST instead of FIRST
176+
.put("listType", "or") // OR instead of AND
177+
.build())
178+
};
179+
mf = new SimpleMessage("", locale, partsOtherLastName);
180+
assertEquals("Le-am dat cadou lui Stanescu, lui Zamfir sau lui Belu", mf.format(params));
181+
}
182+
}
183+
184+
class Person {
185+
final String firstName;
186+
final String lastName;
187+
public Person(String firstName, String lastName) {
188+
this.firstName = firstName;
189+
this.lastName = lastName;
190+
}
191+
}
192+
193+
class GetPersonName implements IPlaceholderFormatter {
194+
@Override
195+
public String format(Object value, String locale, Map<String, String> options) {
196+
if (value instanceof Person) {
197+
Person person = (Person) value;
198+
String field = (options == null) ? "first" : options.get("personName");
199+
if ("first".equals(field)) {
200+
return person.firstName;
201+
}
202+
if ("last".equals(field)) {
203+
return person.lastName;
204+
}
205+
return person.toString();
206+
}
207+
return "{" + value + "}";
208+
}
209+
}
210+
211+
class Utils {
212+
static IPlaceholderFormatter[] getFunctions(String functionNames) {
213+
String[] arrFunctionNames = functionNames.split("\\|");
214+
IPlaceholderFormatter[] result = new IPlaceholderFormatter[arrFunctionNames.length];
215+
for (int i = 0; i < arrFunctionNames.length; i++) {
216+
result[i] = Placeholder.CUSTOM_FORMATTER_FUNC.get(arrFunctionNames[i]);
217+
}
218+
return result;
219+
}
220+
221+
static String applyFunctions(Object value, String locale, Map<String, String> options, IPlaceholderFormatter ... functions) {
222+
Object r = value;
223+
for (IPlaceholderFormatter function : functions) {
224+
r = function.format(r, locale, options);
225+
}
226+
return r.toString();
227+
}
228+
}
229+
230+
class FormatList implements IPlaceholderFormatter {
231+
232+
Width stringToWidth(String val, Width fallback) {
233+
try {
234+
return Width.valueOf(val.toUpperCase(Locale.US));
235+
} catch (IllegalArgumentException | NullPointerException expected) {
236+
return fallback;
237+
}
238+
}
239+
240+
Type stringToType(String val, Type fallback) {
241+
try {
242+
return Type.valueOf(val.toUpperCase(Locale.US));
243+
} catch (IllegalArgumentException | NullPointerException expected) {
244+
return fallback;
245+
}
246+
}
247+
248+
static String safeMapGet(Map<String, String> options, String key) {
249+
return safeMapGet(options, key, "");
250+
}
251+
252+
static String safeMapGet(Map<String, String> options, String key, String def) {
253+
if (options == null)
254+
return def;
255+
String result = options.get(key);
256+
return result != null ? result : def;
257+
}
258+
259+
@Override
260+
public String format(Object value, String locale, Map<String, String> options) {
261+
Width width = stringToWidth(safeMapGet(options, "listWidth"), Width.WIDE);
262+
Type type = stringToType(safeMapGet(options, "listType"), Type.AND);
263+
String listForEach = safeMapGet(options, "listForEach");
264+
265+
// IPlaceholderFormatter itemFunction = null;
266+
IPlaceholderFormatter[] itemFunctions = null;
267+
if (!listForEach.isEmpty()) {
268+
itemFunctions = Utils.getFunctions(listForEach);
269+
// itemFunction = Placeholder.CUSTOM_FORMATTER_FUNC.get(listForEach);
270+
}
271+
272+
List<Object> toFormat = new ArrayList<>();
273+
ListFormatter formatter = ListFormatter.getInstance(ULocale.forLanguageTag(locale), type, width);
274+
if (value instanceof Object[]) {
275+
for (Object v : (Object[]) value) {
276+
if (itemFunctions != null) {
277+
// toFormat.add(itemFunction.format(v, locale, options));
278+
toFormat.add(Utils.applyFunctions(v, locale, options, itemFunctions));
279+
} else {
280+
toFormat.add(v);
281+
}
282+
}
283+
}
284+
if (value instanceof Collection<?>) {
285+
for (Object v : (Collection<?>) value) {
286+
if (itemFunctions != null) {
287+
// toFormat.add(itemFunction.format(v, locale, options));
288+
toFormat.add(Utils.applyFunctions(v, locale, options, itemFunctions));
289+
} else {
290+
toFormat.add(v);
291+
}
292+
}
293+
}
294+
return formatter.format(toFormat);
295+
}
296+
}
297+
298+
class GrammarCasesBlackBox {
299+
public GrammarCasesBlackBox(String localeId) {
300+
}
301+
302+
String getCase(String value, String grammarCase) {
303+
switch (grammarCase) {
304+
case "dative": // intentional fallback
305+
case "genitive":
306+
return getDativeAndGenitive(value);
307+
// and so on, missing for now
308+
default:
309+
return value;
310+
}
311+
}
312+
313+
private String getDativeAndGenitive(String value) {
314+
if (value.endsWith("ana"))
315+
return value.substring(0, value.length() - 3) + "nei";
316+
if (value.endsWith("ca"))
317+
return value.substring(0, value.length() - 2) + "căi";
318+
if (value.endsWith("ga"))
319+
return value.substring(0, value.length() - 2) + "găi";
320+
if (value.endsWith("a"))
321+
return value.substring(0, value.length() - 1) + "ei";
322+
return "lui " + value;
323+
}
324+
}
325+
326+
class GrammarFormatter implements IPlaceholderFormatter {
327+
private final GrammarCasesBlackBox grammarMagic;
328+
329+
public GrammarFormatter(String locale) {
330+
grammarMagic = new GrammarCasesBlackBox(locale);
331+
}
332+
333+
@Override
334+
public String format(Object value, String locale, Map<String, String> options) {
335+
String result = null;
336+
if (value instanceof CharSequence) {
337+
String gcase = options.get("case");
338+
if (gcase == null)
339+
gcase = "other";
340+
result = grammarMagic.getCase(value.toString(), gcase);
341+
}
342+
return result == null ? "{" + value + "}" : result;
343+
}
344+
}

0 commit comments

Comments
 (0)