1 // © 2020 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html 3 package org.unicode.icu.tool.cldrtoicu; 4 5 import static com.google.common.truth.Truth.assertThat; 6 7 import java.util.Arrays; 8 import java.util.LinkedHashMap; 9 import java.util.Map; 10 import java.util.Objects; 11 12 import org.junit.Test; 13 import org.junit.runner.RunWith; 14 import org.junit.runners.JUnit4; 15 import org.unicode.cldr.api.AttributeKey; 16 import org.unicode.cldr.api.CldrData; 17 import org.unicode.cldr.api.CldrDataSupplier; 18 import org.unicode.cldr.api.CldrValue; 19 20 import com.google.common.collect.ImmutableMap; 21 22 @RunWith(JUnit4.class) 23 public class CldrDataProcessorTest { 24 25 private static final AttributeKey TERRITORY_TYPE = AttributeKey.keyOf("territory", "type"); 26 private static final AttributeKey CURRENCY_TYPE = AttributeKey.keyOf("currency", "type"); 27 28 // An overly simplistic value type for currency for testing purposes. In real code you would 29 // probably want an immutable type and a separate builder, or a mutable type just to collect 30 // values that doesn't need equals/hashcode (this class serves 2 purposes in the test). 31 private static final class CurrencyData { 32 final String key; 33 String name = ""; 34 String symbol = ""; 35 CurrencyData(String key)36 CurrencyData(String key) { 37 this.key = key; 38 } 39 CurrencyData(String key, String name, String symbol)40 CurrencyData(String key, String name, String symbol) { 41 this.key = key; 42 this.name = name; 43 this.symbol = symbol; 44 } 45 equals(Object o)46 @Override public boolean equals(Object o) { 47 if (o instanceof CurrencyData) { 48 CurrencyData that = (CurrencyData) o; 49 return key.equals(that.key) && name.equals(that.name) && symbol.equals(that.symbol); 50 } 51 return false; 52 } 53 hashCode()54 @Override public int hashCode() { 55 return Objects.hash(key, name, symbol); 56 } 57 toString()58 @Override public String toString() { 59 return String.format("CurrencyData{name=%s, symbol='%s'}", name, symbol); 60 } 61 } 62 63 // For collecting processed values. 64 private static final class State { 65 ImmutableMap<String, String> names = ImmutableMap.of(); 66 ImmutableMap<String, CurrencyData> currencies = ImmutableMap.of(); 67 setNames(Map<String, String> map)68 void setNames(Map<String, String> map) { 69 names = ImmutableMap.copyOf(map); 70 } 71 setCurrencies(Map<String, CurrencyData> map)72 void setCurrencies(Map<String, CurrencyData> map) { 73 currencies = ImmutableMap.copyOf(map); 74 } 75 } 76 77 private static final CldrDataProcessor<State> VISITOR = createTestVisitor(); 78 createTestVisitor()79 private static CldrDataProcessor<State> createTestVisitor() { 80 // Note that this is deliberately doing things the "messy" way by creating and then copying 81 // a map. This is to show an extra level of processing in tests. You could just have a 82 // value action which adds the territory to a map in the State object. 83 CldrDataProcessor.Builder<State> builder = CldrDataProcessor.builder(); 84 builder 85 .addAction( 86 "//ldml/localeDisplayNames/territories", 87 () -> new LinkedHashMap<String, String>(), 88 State::setNames) 89 .addValueAction( 90 "territory[@type=*]", 91 (map, value) -> map.put(value.getPath().get(TERRITORY_TYPE), value.getValue())); 92 93 // Another convoluted example for testing. This has the same additional level for a map 94 // just so we can show a 3-level processor. In real code this wouldn't look so messy. 95 CldrDataProcessor.SubProcessor<CurrencyData> currencyProcessor = builder 96 .addAction( 97 "//ldml/numbers/currencies", 98 () -> new LinkedHashMap<String, CurrencyData>(), 99 State::setCurrencies) 100 .addAction( 101 "currency[@type=*]", 102 (map, path) -> new CurrencyData(path.get(CURRENCY_TYPE)), 103 (map, data) -> map.put(data.key, data)); 104 currencyProcessor.addValueAction( 105 "displayName", 106 (data, value) -> data.name = value.getValue()); 107 currencyProcessor.addValueAction( 108 "symbol", 109 (data, value) -> data.symbol = value.getValue()); 110 111 return builder.build(); 112 } 113 114 @Test testTwoLevelProcessing()115 public void testTwoLevelProcessing() { 116 CldrData data = CldrDataSupplier.forValues(Arrays.asList( 117 ldml("localeDisplayNames/territories/territory[@type=\"BE\"]", "Belgium"), 118 ldml("localeDisplayNames/territories/territory[@type=\"CH\"]", "Switzerland"), 119 ldml("localeDisplayNames/territories/territory[@type=\"IN\"]", "India"))); 120 121 State state = VISITOR.process(data, new State(), CldrData.PathOrder.DTD); 122 123 assertThat(state.names) 124 .containsExactly( 125 "BE", "Belgium", 126 "CH", "Switzerland", 127 "IN", "India") 128 .inOrder(); 129 } 130 131 @Test testThreeLevelProcessing()132 public void testThreeLevelProcessing() { 133 CldrData data = CldrDataSupplier.forValues(Arrays.asList( 134 ldml("numbers/currencies/currency[@type=\"EUR\"]/displayName", "euro"), 135 ldml("numbers/currencies/currency[@type=\"EUR\"]/symbol", "€"), 136 ldml("numbers/currencies/currency[@type=\"CHF\"]/displayName", "Swiss franc"), 137 ldml("numbers/currencies/currency[@type=\"CHF\"]/symbol", "Fr."), 138 ldml("numbers/currencies/currency[@type=\"INR\"]/displayName", "Indian rupee"), 139 ldml("numbers/currencies/currency[@type=\"INR\"]/symbol", "₹"))); 140 141 State state = VISITOR.process(data, new State(), CldrData.PathOrder.DTD); 142 143 assertThat(state.currencies) 144 .containsExactly( 145 "CHF", new CurrencyData("CHF", "Swiss franc", "Fr."), 146 "EUR", new CurrencyData("EUR", "euro", "€"), 147 "INR", new CurrencyData("INR", "Indian rupee", "₹")) 148 .inOrder(); 149 } 150 ldml(String path, String value)151 private static CldrValue ldml(String path, String value) { 152 return CldrValue.parseValue("//ldml/" + path, value); 153 } 154 } 155