1 package org.unicode.cldr.test; 2 3 import java.util.HashMap; 4 import java.util.Iterator; 5 import java.util.Locale; 6 import java.util.Map; 7 import java.util.Set; 8 import java.util.regex.Pattern; 9 10 import org.unicode.cldr.util.CLDRFile; 11 import org.unicode.cldr.util.ICUServiceBuilder; 12 import org.unicode.cldr.util.PatternCache; 13 import org.unicode.cldr.util.SupplementalDataInfo; 14 import org.unicode.cldr.util.XPathParts; 15 16 import com.ibm.icu.impl.number.DecimalFormatProperties; 17 import com.ibm.icu.text.CompactDecimalFormat; 18 import com.ibm.icu.text.CompactDecimalFormat.CompactStyle; 19 import com.ibm.icu.text.DecimalFormat.PropertySetter; 20 import com.ibm.icu.util.Currency; 21 import com.ibm.icu.util.ULocale; 22 23 @SuppressWarnings("deprecation") 24 public class BuildIcuCompactDecimalFormat { 25 private static boolean DEBUG = false; 26 static SupplementalDataInfo sdi = SupplementalDataInfo.getInstance(); 27 static final int MINIMUM_ARRAY_LENGTH = 15; 28 static final Pattern PATTERN = PatternCache.get("([^0,]*)([0]+)([.]0+)?([^0]*)"); 29 static final Pattern TYPE = PatternCache.get("1([0]*)"); 30 31 public enum CurrencyStyle { 32 PLAIN, CURRENCY, LONG_CURRENCY, ISO_CURRENCY, UNIT 33 } 34 35 /** 36 * JUST FOR DEVELOPMENT 37 * 38 * @param currencyStyle 39 * @param currencyCode 40 */ build(CLDRFile resolvedCldrFile, Set<String> debugCreationErrors, String[] debugOriginals, CompactStyle style, ULocale locale, CurrencyStyle currencyStyle, String currencyCodeOrUnit)41 public static final CompactDecimalFormat build(CLDRFile resolvedCldrFile, 42 Set<String> debugCreationErrors, String[] debugOriginals, 43 CompactStyle style, ULocale locale, CurrencyStyle currencyStyle, String currencyCodeOrUnit) { 44 45 final Map<String, Map<String, String>> customData = new HashMap<String, Map<String, String>>(); 46 // Map<String,String> inner = new HashMap<String,String>(); 47 // inner.put("one", "0 qwerty"); 48 // inner.put("other", "0 dvorak"); 49 // customData.put("1000", inner); 50 51 // Map<String, String[][]> affixes = new HashMap<String, String[][]>(); 52 // Map<String, String[]> unitPrefixes = new HashMap<String, String[]>(); 53 // 54 // // String[] prefix = new String[CompactDecimalFormat.MINIMUM_ARRAY_LENGTH]; 55 // // String[] suffix = new String[CompactDecimalFormat.MINIMUM_ARRAY_LENGTH]; 56 // long[] divisor = new long[MINIMUM_ARRAY_LENGTH]; 57 // // get the pattern details from the locale 58 // PluralInfo pluralInfo = sdi.getPlurals(PluralType.cardinal, locale.toString()); 59 // 60 // // fix low numbers 61 // Set<String> canonicalKeywords = pluralInfo.getCanonicalKeywords(); 62 // for (String key : canonicalKeywords) { 63 // String[][] affix = new String[MINIMUM_ARRAY_LENGTH][]; 64 // for (int i = 0; i < 3; ++i) { 65 // affix[i] = new String[] { "", "" }; 66 // divisor[i] = 1; 67 // } 68 // affixes.put(key, affix); 69 // } 70 // Matcher patternMatcher = PATTERN.matcher(""); 71 // Matcher typeMatcher = TYPE.matcher(""); 72 // XPathParts parts = new XPathParts(); 73 // for (String path : 74 // With.in(resolvedCldrFile.iterator("//ldml/numbers/decimalFormats/decimalFormatLength[@type=\"short\"]/decimalFormat[@type=\"standard\"]/"))) 75 // { 76 String prefix = currencyStyle == CurrencyStyle.PLAIN ? "//ldml/numbers/decimalFormats[@numberSystem=\"latn\"]/decimalFormatLength" 77 : "//ldml/numbers/currencyFormats[@numberSystem=\"latn\"]/currencyFormatLength"; 78 79 Iterator<String> it = resolvedCldrFile.iterator(prefix); 80 81 String styleString = style.toString().toLowerCase(Locale.ENGLISH); 82 while (it.hasNext()) { 83 String path = it.next(); 84 if (path.endsWith("/alias")) { 85 continue; 86 } 87 // String sourceLocale = resolvedCldrFile.getSourceLocaleID(path, null); 88 // if ("root".equals(sourceLocale)) { 89 // continue; 90 // } 91 // if (!path.contains("decimalFormatLength")) { 92 // continue; 93 // } 94 XPathParts parts = XPathParts.getFrozenInstance(path); 95 String stype = parts.getAttributeValue(3, "type"); 96 if (!styleString.equals(stype)) { 97 continue; 98 } 99 String type = parts.getAttributeValue(-1, "type"); 100 String key = parts.getAttributeValue(-1, "count"); 101 String pattern = resolvedCldrFile.getStringValue(path); 102 103 /* 104 <pattern type="1000" count="one">0K</pattern> 105 */ 106 107 add(customData, type, key, pattern); 108 109 // if (DEBUG && path.contains("miliony")) { 110 // System.out.println(key + ", " + path); 111 // } 112 // String[][] affix = affixes.get(key); 113 // 114 // if (!typeMatcher.reset(type).matches()) { 115 // debugCreationErrors.add("type (" + type + 116 // ") doesn't match expected " + TYPE.pattern()); 117 // continue; 118 // } 119 // 120 // String pattern = resolvedCldrFile.getStringValue(path); 121 // 122 // int typeZeroCount = typeMatcher.end(1) - typeMatcher.start(1); 123 // // for debugging 124 // if (debugOriginals != null) { 125 // debugOriginals[typeZeroCount] = pattern; 126 // } 127 // 128 // // special pattern for unused 129 // if (pattern.equals("0")) { 130 // affix[typeZeroCount] = new String[] { "", "" }; 131 // divisor[typeZeroCount] = 1; 132 // continue; 133 // } 134 // 135 // if (!patternMatcher.reset(pattern).matches()) { 136 // debugCreationErrors.add("pattern (" + pattern + 137 // ") doesn't match expected " + PATTERN.pattern()); 138 // continue; 139 // } 140 // 141 // // HACK '.' just in case. 142 // affix[typeZeroCount] = new String[] { 143 // escape(patternMatcher.group(1).replace("'.'", ".")), 144 // escape(patternMatcher.group(4).replace("'.'", ".")) 145 // 146 //// patternMatcher.group(1).replace("'.'", "."), 147 //// patternMatcher.group(4).replace("'.'", ".") 148 // }; 149 // if (DEBUG && key.equals("one")) { 150 // System.out.println(key + ", " + typeZeroCount + ", " + Arrays.asList(affix[typeZeroCount])); 151 // } 152 // int zeroCount = patternMatcher.end(2) - patternMatcher.start(2) - 1; 153 // divisor[typeZeroCount] = (long) Math.pow(10.0, typeZeroCount - zeroCount); 154 } 155 156 // DecimalFormat format = (DecimalFormat) (currencyStyle == CurrencyStyle.PLAIN 157 // ? NumberFormat.getInstance(new ULocale(resolvedCldrFile.getLocaleID())) 158 // : NumberFormat.getCurrencyInstance(new ULocale(resolvedCldrFile.getLocaleID()))); 159 160 // ICUServiceBuilder builder = new ICUServiceBuilder().setCldrFile(resolvedCldrFile); 161 //// final DecimalFormat format = builder.getNumberFormat(1); 162 // switch (currencyStyle) { 163 // case PLAIN: 164 // default: 165 // break; 166 // case LONG_CURRENCY: 167 // // if the long form, modify the patterns 168 // CurrencyInfo names = new CurrencyInfo(resolvedCldrFile, canonicalKeywords, 169 // currencyCodeOrUnit, parts); 170 // if (!names.isEmpty()) { 171 // for (String count : canonicalKeywords) { 172 // String unitPattern = names.getUnitPattern(count); 173 // int pos = unitPattern.indexOf("{0}"); 174 // String prefixUnit = unitPattern.substring(0, pos); 175 // String suffixUnit = unitPattern.substring(pos + 3); 176 // String currencyName = names.getCurrencyName(count); 177 // addPrefixSuffixInfo(unitPrefixes, count, 178 // MessageFormat.format(prefixUnit, null, currencyName), 179 // MessageFormat.format(suffixUnit, null, currencyName)); 180 // } 181 // break; 182 // } 183 // // otherwise fallthru 184 // case CURRENCY: 185 // DecimalFormat format2 = builder.getCurrencyFormat(currencyCodeOrUnit); 186 // String prefix1 = format2.getPositivePrefix(); 187 // String suffix2 = format2.getPositiveSuffix(); 188 // for (String count : canonicalKeywords) { 189 // addPrefixSuffixInfo(unitPrefixes, count, prefix1, suffix2); 190 // } 191 // break; 192 // case ISO_CURRENCY: 193 // throw new IllegalArgumentException(); 194 // case UNIT: 195 // String unit = currencyCodeOrUnit == null ? "mass-kilogram" : currencyCodeOrUnit; 196 // String otherValue = getUnitString(resolvedCldrFile, unit, "other"); 197 // for (String count : canonicalKeywords) { 198 // String value = getUnitString(resolvedCldrFile, unit, count); 199 // if (value == null) { 200 // value = otherValue; 201 // } 202 // int pos = value.indexOf("{0}"); 203 // addPrefixSuffixInfo(unitPrefixes, count, value.substring(0, pos), value.substring(pos + 3)); 204 // } 205 // break; 206 // } 207 208 // DecimalFormat currencyFormat = new 209 // ICUServiceBuilder().setCldrFile(resolvedCldrFile).getCurrencyFormat("USD"); 210 // for (String s : With.in(resolvedCldrFile.iterator("//ldml/numbers/currencyFormats/"))) { 211 // System.out.println(s + "\t" + resolvedCldrFile.getStringValue(s)); 212 // } 213 // DecimalFormat currencyFormat = new DecimalFormat(pattern); 214 // do this by hand, because the DecimalFormat pattern parser does too much. 215 // String pattern = 216 // resolvedCldrFile.getWinningValue("//ldml/numbers/currencyFormats/currencyFormatLength/currencyFormat[@type=\"standard\"]/pattern[@type=\"" 217 // + 218 // "standard" + 219 // "\"]"); 220 // String[] currencyAffixes = new String[CompactDecimalFormat.AFFIX_SIZE]; 221 // String[] patterns = pattern.split(";"); 222 // final Matcher matcher = CURRENCY_PATTERN.matcher(patterns[0]); 223 // if (!matcher.matches()) { 224 // throw new IllegalArgumentException("Can't match currency pattern"); 225 // } 226 // currencyAffixes[CompactDecimalFormat.POSITIVE_PREFIX] = matcher.group(1); 227 // currencyAffixes[CompactDecimalFormat.POSITIVE_SUFFIX] = matcher.group(2); 228 // 229 // if (DEBUG) { 230 // for (Entry<String, String[][]> keyList : affixes.entrySet()) { 231 // System.out.println("*\t" + keyList.getKey() + "\t" + CldrUtility.toString(keyList)); 232 // } 233 // } 234 235 // TODO fix to get right symbol for the count 236 // String pattern = format.toPattern(); 237 238 // if (style == Style.LONG) { 239 // pattern = pattern.replace("¤", "¤¤¤"); 240 // } 241 242 // JCE 2017-03-28 - This constructor was removed in ICU 59. Shane is working on a 243 // workaround, but until one is done, we can't use it in its current state. 244 // TODO: Put it back once the fix is in place, See Ticket #10166 245 // try { 246 // return new CompactDecimalFormat( 247 // pattern, format.getDecimalFormatSymbols(), 248 // style, pluralInfo.getPluralRules(), 249 // divisor, affixes, unitPrefixes, 250 // debugCreationErrors); 251 // } catch (Exception e) { 252 // debugCreationErrors.add(e.getMessage()); 253 // return null; 254 255 CompactDecimalFormat cdf = CompactDecimalFormat.getInstance(locale, style); 256 ICUServiceBuilder builder = new ICUServiceBuilder().setCldrFile(resolvedCldrFile); 257 258 cdf.setDecimalFormatSymbols(builder.getDecimalFormatSymbols("latn")); 259 cdf.setProperties(new PropertySetter() { 260 @Override 261 public void set(DecimalFormatProperties props) { 262 props.setCompactCustomData(customData); 263 } 264 }); 265 return cdf; 266 267 // debugCreationErrors.add("Can't create due to lack of 'from scratch' constructor for CompactDecimalFormat"); 268 // return null; 269 // } 270 /* 271 * divisor, prefixes, suffixes, 272 unitPrefixes, unitSuffixes, 273 currencyAffixes, new CompactDecimalFormatTest.MyCurrencySymbolDisplay(resolvedCldrFile), 274 debugCreationErrors 275 276 */ 277 // CompactDecimalFormat cdf = new CompactDecimalFormat( 278 // "#,###.00", 279 // DecimalFormatSymbols.getInstance(new ULocale("fr")), 280 // CompactStyle.SHORT, PluralRules.createRules("one: j is 1 or f is 1"), 281 // divisors, affixes, null, 282 // debugCreationErrors 283 // ); 284 285 } 286 add(Map<A, Map<B, C>> customData, A a, B b, C c)287 private static <A, B, C> void add(Map<A, Map<B, C>> customData, A a, B b, C c) { 288 Map<B, C> inner = customData.get(a); 289 if (inner == null) { 290 customData.put(a, inner = new HashMap<>()); 291 } 292 inner.put(b, c); 293 } 294 addPrefixSuffixInfo(Map<String, String[]> unitPrefixes, String count, final String prefix, final String suffix)295 private static String[] addPrefixSuffixInfo(Map<String, String[]> unitPrefixes, String count, 296 final String prefix, final String suffix) { 297 return unitPrefixes.put(count, new String[] { escape(prefix), escape(suffix) }); 298 } 299 escape(String prefix)300 private static String escape(String prefix) { 301 return prefix.isEmpty() ? prefix : "'" + prefix.replace("'", "''") + "'"; 302 } 303 getUnitString(CLDRFile resolvedCldrFile, String unit, String count)304 private static String getUnitString(CLDRFile resolvedCldrFile, String unit, String count) { 305 return resolvedCldrFile.getStringValue("//ldml/units/unitLength[@type=\"short\"]/unit[@type=\"" 306 + unit + "\"]/unitPattern[@count=\"" + 307 count + 308 "\"]"); 309 } 310 311 private static class CurrencyInfo { 312 private HashMap<String, String> currencyNames = new HashMap<String, String>(); 313 private HashMap<String, String> unitPatterns = new HashMap<String, String>(); 314 CurrencyInfo(CLDRFile resolvedCldrFile, Set<String> canonicalKeywords, String currencyCode, XPathParts parts)315 CurrencyInfo(CLDRFile resolvedCldrFile, Set<String> canonicalKeywords, String currencyCode, XPathParts parts) { 316 Iterator<String> it; 317 it = resolvedCldrFile.iterator( 318 "//ldml/numbers/currencies/currency[@type=\"" + 319 currencyCode + 320 "\"]/displayName"); 321 // //ldml/numbers/currencies/currency[@type="SRD"]/symbol 322 while (it.hasNext()) { 323 String path = it.next(); 324 parts.set(path); 325 String key = parts.getAttributeValue(-1, "count"); 326 if (key == null) { 327 key = "default"; 328 } 329 currencyNames.put(key, resolvedCldrFile.getStringValue(path)); 330 } 331 332 it = resolvedCldrFile.iterator( 333 "//ldml/numbers/currencyFormats/unitPattern"); 334 while (it.hasNext()) { 335 String path = it.next(); 336 parts.set(path); 337 String key = parts.getAttributeValue(-1, "count"); 338 unitPatterns.put(key, resolvedCldrFile.getStringValue(path)); 339 } 340 // <displayName count="one" draft="contributed">evro</displayName> 341 // flesh out missing 342 } 343 isEmpty()344 public boolean isEmpty() { 345 return currencyNames.isEmpty(); 346 } 347 getUnitPattern(String count)348 String getUnitPattern(String count) { 349 String result = unitPatterns.get(count); 350 if (result == null) { 351 result = unitPatterns.get("other"); 352 } 353 return result; 354 } 355 getCurrencyName(String count)356 String getCurrencyName(String count) { 357 String result = currencyNames.get(count); 358 if (result != null) { 359 return result; 360 } 361 if (!count.equals("other")) { 362 result = currencyNames.get("other"); 363 if (result != null) { 364 return result; 365 } 366 } 367 result = currencyNames.get("default"); 368 return result; 369 } 370 } 371 372 // <currencyFormats numberSystem="latn"> 373 // <currencyFormatLength> 374 // <currencyFormat> 375 // <pattern>¤#,##0.00;(¤#,##0.00)</pattern> 376 // </currencyFormat> 377 // </currencyFormatLength> 378 // <unitPattern count="one">{0} {1}</unitPattern> 379 // <unitPattern count="other">{0} {1}</unitPattern> 380 // </currencyFormats> 381 382 static class MyCurrencySymbolDisplay { 383 CLDRFile cldrFile; 384 MyCurrencySymbolDisplay(CLDRFile cldrFile)385 public MyCurrencySymbolDisplay(CLDRFile cldrFile) { 386 this.cldrFile = cldrFile; 387 } 388 getName(Currency currency, int count)389 public String getName(Currency currency, int count) { 390 final String currencyCode = currency.getCurrencyCode(); 391 if (count > 1) { 392 return currencyCode; 393 } 394 String prefix = "//ldml/numbers/currencies/currency[@type=\"" + currencyCode + "\"]/"; 395 String currencySymbol = cldrFile.getWinningValue(prefix + "symbol"); 396 return currencySymbol != null ? currencySymbol : currencyCode; 397 } 398 }; 399 400 } 401