1 package org.unicode.cldr.tool; 2 3 import com.google.common.base.Joiner; 4 import com.ibm.icu.impl.Utility; 5 import com.ibm.icu.impl.number.DecimalQuantity; 6 import com.ibm.icu.text.CompactDecimalFormat; 7 import com.ibm.icu.text.CompactDecimalFormat.CompactStyle; 8 import com.ibm.icu.text.NumberFormat; 9 import com.ibm.icu.text.PluralRules; 10 import com.ibm.icu.text.PluralRules.DecimalQuantitySamples; 11 import com.ibm.icu.text.PluralRules.Operand; 12 import com.ibm.icu.util.ICUUncheckedIOException; 13 import com.ibm.icu.util.ULocale; 14 import java.io.IOException; 15 import java.io.PrintWriter; 16 import java.io.StringWriter; 17 import java.io.UncheckedIOException; 18 import java.util.LinkedHashSet; 19 import java.util.List; 20 import java.util.Set; 21 import org.unicode.cldr.test.BuildIcuCompactDecimalFormat; 22 import org.unicode.cldr.test.BuildIcuCompactDecimalFormat.CurrencyStyle; 23 import org.unicode.cldr.tool.GeneratePluralRanges.RangeSample; 24 import org.unicode.cldr.util.CLDRConfig; 25 import org.unicode.cldr.util.CLDRFile; 26 import org.unicode.cldr.util.CLDRURLS; 27 import org.unicode.cldr.util.CldrUtility; 28 import org.unicode.cldr.util.Factory; 29 import org.unicode.cldr.util.ICUServiceBuilder; 30 import org.unicode.cldr.util.LanguageTagCanonicalizer; 31 import org.unicode.cldr.util.PluralSnapshot; 32 import org.unicode.cldr.util.SupplementalDataInfo; 33 import org.unicode.cldr.util.SupplementalDataInfo.PluralInfo; 34 import org.unicode.cldr.util.SupplementalDataInfo.PluralInfo.Count; 35 import org.unicode.cldr.util.SupplementalDataInfo.PluralType; 36 37 public class ShowPlurals { 38 39 private static final String NO_PLURAL_DIFFERENCES = "<i>no plural differences</i>"; 40 private static final String NOT_AVAILABLE = 41 "<i>Not available.<br>Please <a target='_blank' href='" 42 + CLDRURLS.CLDR_NEWTICKET_URL 43 + "'>file a ticket</a> to supply.</i>"; 44 final SupplementalDataInfo supplementalDataInfo; 45 main(String[] args)46 public static void main(String[] args) { 47 Factory cldrFactory = 48 CLDRConfig.getInstance().getCldrFactory(); // .make(CLDRPaths.MAIN_DIRECTORY, ".*"); 49 CLDRFile english = CLDRConfig.getInstance().getEnglish(); 50 StringWriter sw = new StringWriter(); 51 PrintWriter pw = new PrintWriter(sw); 52 53 try { 54 new ShowPlurals().printPlurals(english, null, pw, cldrFactory); 55 } catch (IOException e) { 56 throw new UncheckedIOException(e); 57 } 58 } 59 ShowPlurals()60 public ShowPlurals() { 61 supplementalDataInfo = CLDRConfig.getInstance().getSupplementalDataInfo(); 62 } 63 ShowPlurals(SupplementalDataInfo supplementalDataInfo)64 public ShowPlurals(SupplementalDataInfo supplementalDataInfo) { 65 this.supplementalDataInfo = supplementalDataInfo; 66 } 67 printPlurals( CLDRFile english, String localeFilter, PrintWriter index, Factory factory)68 public void printPlurals( 69 CLDRFile english, String localeFilter, PrintWriter index, Factory factory) 70 throws IOException { 71 String section1 = "Rules"; 72 String section2 = "Comparison"; 73 74 final String title = "Language Plural Rules"; 75 final PrintWriter pw = 76 new PrintWriter( 77 new FormattedFileWriter( 78 null, title, null, ShowLanguages.SUPPLEMENTAL_INDEX_ANCHORS)); 79 80 pw.append("<div style='margin-right:2em; margin-left:2em'>\n"); 81 ShowLanguages.showContents(pw, "rules", "Rules", "comparison", "Comparison"); 82 83 pw.append( 84 "<h2>" 85 + CldrUtility.getDoubleLinkedText("rules", "1. " + section1) 86 + "</h2>" 87 + System.lineSeparator()); 88 pw.append("<div style='margin-right:2em; margin-left:2em'>\n"); 89 printPluralTable(english, localeFilter, pw, factory); 90 pw.append("</div>\n"); 91 92 pw.append( 93 "<h2>" 94 + CldrUtility.getDoubleLinkedText("comparison", "2. " + section2) 95 + "</h2>" 96 + System.lineSeparator()); 97 pw.append( 98 "<p style='text-align:left'>The plural forms are abbreviated by first letter, with 'x' for 'other'. " 99 + "If values are made redundant by explicit 0 and 1, they are underlined. " 100 + "The fractional and integral results are separated for clarity.</p>" 101 + System.lineSeparator()); 102 pw.append("<div style='margin-right:2em; margin-left:2em'>\n"); 103 PluralSnapshot.writeTables(english, pw); 104 pw.append("</div>\n"); 105 pw.append("</div>\n"); 106 appendBlanksForScrolling(pw); 107 pw.close(); 108 } 109 appendBlanksForScrolling(final Appendable pw)110 public static void appendBlanksForScrolling(final Appendable pw) { 111 try { 112 pw.append(Utility.repeat("<br>", 100)).append(System.lineSeparator()); 113 } catch (IOException e) { 114 throw new ICUUncheckedIOException(e); 115 } 116 } 117 printPluralTable( CLDRFile english, String localeFilter, Appendable appendable, Factory factory)118 public void printPluralTable( 119 CLDRFile english, String localeFilter, Appendable appendable, Factory factory) 120 throws IOException { 121 122 final TablePrinter tablePrinter = 123 new TablePrinter() 124 .setTableAttributes("class='dtf-table'") 125 .addColumn("Name", "class='source'", null, "class='source'", true) 126 .setSortPriority(0) 127 .setHeaderAttributes("class='dtf-th'") 128 .setCellAttributes("class='dtf-s'") 129 .setBreakSpans(true) 130 .setRepeatHeader(true) 131 .addColumn( 132 "Code", 133 "class='source'", 134 CldrUtility.getDoubleLinkMsg(), 135 "class='source'", 136 true) 137 .setHeaderAttributes("class='dtf-th'") 138 .setCellAttributes("class='dtf-s'") 139 .addColumn("Type", "class='source'", null, "class='source'", true) 140 .setHeaderAttributes("class='dtf-th'") 141 .setCellAttributes("class='dtf-s'") 142 .setBreakSpans(true) 143 .addColumn("Category", "class='target'", null, "class='target'", true) 144 .setHeaderAttributes("class='dtf-th'") 145 .setCellAttributes("class='dtf-s'") 146 .setSpanRows(false) 147 .addColumn("Examples", "class='target'", null, "class='target'", true) 148 .setHeaderAttributes("class='dtf-th'") 149 .setCellAttributes("class='dtf-s'") 150 .addColumn("Minimal Pairs", "class='target'", null, "class='target'", true) 151 .setHeaderAttributes("class='dtf-th'") 152 .setCellAttributes("class='dtf-s'") 153 .addColumn("Rules", "class='target'", null, "class='target' nowrap", true) 154 .setHeaderAttributes("class='dtf-th'") 155 .setCellAttributes("class='dtf-s'") 156 .setSpanRows(false); 157 PluralRulesFactory prf = PluralRulesFactory.getInstance(supplementalDataInfo); 158 // Map<ULocale, PluralRulesFactory.SamplePatterns> samples = 159 // PluralRulesFactory.getLocaleToSamplePatterns(); 160 Set<String> cardinalLocales = supplementalDataInfo.getPluralLocales(PluralType.cardinal); 161 Set<String> ordinalLocales = supplementalDataInfo.getPluralLocales(PluralType.ordinal); 162 Set<String> all = new LinkedHashSet<>(cardinalLocales); 163 all.addAll(ordinalLocales); 164 165 LanguageTagCanonicalizer canonicalizer = new LanguageTagCanonicalizer(); 166 SampleMaker sampleMaker = new SampleMaker(); 167 168 for (String locale : supplementalDataInfo.getPluralLocales()) { 169 if (localeFilter != null && !localeFilter.equals(locale) || locale.equals("root")) { 170 continue; 171 } 172 final String name = english.getName(locale); 173 String canonicalLocale = canonicalizer.transform(locale); 174 if (!locale.equals(canonicalLocale)) { 175 String redirect = 176 "<i>=<a href='#" + canonicalLocale + "'>" + canonicalLocale + "</a></i>"; 177 tablePrinter 178 .addRow() 179 .addCell(name) 180 .addCell(locale) 181 .addCell(redirect) 182 .addCell(redirect) 183 .addCell(redirect) 184 .addCell(redirect) 185 .addCell(redirect) 186 .finishRow(); 187 continue; 188 } 189 190 CLDRFile cldrFile2; 191 try { 192 cldrFile2 = factory.make(locale, true); 193 } catch (Exception e1) { 194 continue; 195 } 196 sampleMaker.setCldrFile(cldrFile2); 197 198 for (PluralType pluralType : PluralType.values()) { 199 if (pluralType == PluralType.ordinal && !ordinalLocales.contains(locale) 200 || pluralType == PluralType.cardinal && !cardinalLocales.contains(locale)) { 201 continue; 202 } 203 final PluralInfo plurals = supplementalDataInfo.getPlurals(pluralType, locale); 204 ULocale locale2 = new ULocale(locale); 205 final PluralMinimalPairs samplePatterns = 206 PluralMinimalPairs.getInstance(locale2.toString()); 207 // pluralType == PluralType.ordinal ? null 208 // : CldrUtility.get(samples, locale2); 209 210 String rules = plurals.getRules(); 211 rules += 212 rules.length() == 0 213 ? "other:<i>everything</i>" 214 : ";other:<i>everything else</i>"; 215 rules = rules.replace(":", " → ").replace(";", ";<br>"); 216 PluralRules pluralRules = plurals.getPluralRules(); 217 // final Map<PluralInfo.Count, String> typeToExamples = 218 // plurals.getCountToStringExamplesMap(); 219 // final String examples = typeToExamples.get(type).toString().replace(";", 220 // ";<br>"); 221 Set<Count> counts = plurals.getCounts(); 222 for (PluralInfo.Count count : counts) { 223 String keyword = count.toString(); 224 DecimalQuantitySamples exampleList = 225 pluralRules.getDecimalSamples( 226 keyword, 227 PluralRules.SampleType 228 .INTEGER); // plurals.getSamples9999(count); 229 DecimalQuantitySamples exampleList2 = 230 pluralRules.getDecimalSamples(keyword, PluralRules.SampleType.DECIMAL); 231 if (exampleList == null) { 232 exampleList = exampleList2; 233 exampleList2 = null; 234 } 235 String examples = getExamples(exampleList); 236 if (exampleList2 != null) { 237 examples += "<br>" + getExamples(exampleList2); 238 } 239 String rule = pluralRules.getRules(keyword); 240 rule = 241 rule != null 242 ? rule.replace(":", " → ") 243 .replace(" and ", " and<br> ") 244 .replace(" or ", " or<br>") 245 : counts.size() == 1 246 ? "<i>everything</i>" 247 : "<i>everything else</i>"; 248 249 String sample = counts.size() == 1 ? NO_PLURAL_DIFFERENCES : NOT_AVAILABLE; 250 if (samplePatterns != null) { 251 String samplePattern = 252 samplePatterns.get( 253 pluralType.standardType, 254 Count.valueOf( 255 keyword)); // CldrUtility.get(samplePatterns.keywordToPattern, Count.valueOf(keyword)); 256 if (samplePattern != null) { 257 DecimalQuantity sampleDecimal = 258 PluralInfo.getNonZeroSampleIfPossible(exampleList); 259 sample = sampleMaker.getSample(sampleDecimal, samplePattern); 260 if (exampleList2 != null) { 261 sampleDecimal = PluralInfo.getNonZeroSampleIfPossible(exampleList2); 262 sample += 263 "<br>" 264 + sampleMaker.getSample( 265 sampleDecimal, samplePattern); 266 } 267 } 268 } 269 tablePrinter 270 .addRow() 271 .addCell(name) 272 .addCell(locale) 273 .addCell(pluralType.toString()) 274 .addCell(count.toString()) 275 .addCell(examples.toString()) 276 .addCell(sample) 277 .addCell(rule) 278 .finishRow(); 279 } 280 } 281 List<RangeSample> rangeInfoList = null; 282 try { 283 rangeInfoList = 284 new GeneratePluralRanges(supplementalDataInfo).getRangeInfo(cldrFile2); 285 } catch (Exception e) { 286 } 287 if (rangeInfoList != null) { 288 for (RangeSample item : rangeInfoList) { 289 tablePrinter 290 .addRow() 291 .addCell(name) 292 .addCell(locale) 293 .addCell("range") 294 .addCell(item.start + "+" + item.end) 295 .addCell(item.min + "–" + item.max) 296 .addCell(item.resultExample.replace(". ", ".<br>")) 297 .addCell(item.start + " + " + item.end + " → " + item.result) 298 .finishRow(); 299 } 300 } else { 301 String message = 302 supplementalDataInfo 303 .getPlurals(PluralType.cardinal, locale) 304 .getCounts() 305 .size() 306 == 1 307 ? NO_PLURAL_DIFFERENCES 308 : NOT_AVAILABLE; 309 tablePrinter 310 .addRow() 311 .addCell(name) 312 .addCell(locale) 313 .addCell("range") 314 .addCell("<i>n/a</i>") 315 .addCell("<i>n/a</i>") 316 .addCell(message) 317 .addCell("<i>n/a</i>") 318 .finishRow(); 319 } 320 } 321 appendable.append(tablePrinter.toTable()).append(System.lineSeparator()); 322 } 323 getExamples(DecimalQuantitySamples exampleList)324 private String getExamples(DecimalQuantitySamples exampleList) { 325 return Joiner.on(", ").join(exampleList.getSamples()) + (exampleList.bounded ? "" : ", …"); 326 } 327 328 static final class SampleMaker { 329 ICUServiceBuilder icusb = new ICUServiceBuilder(); 330 CLDRFile cldrFile; 331 setCldrFile(CLDRFile cldrFile)332 void setCldrFile(CLDRFile cldrFile) { 333 this.cldrFile = cldrFile; 334 icusb.setCldrFile(cldrFile); 335 } 336 getSample(DecimalQuantity numb, String samplePattern)337 private String getSample(DecimalQuantity numb, String samplePattern) { 338 String sample; 339 String value; 340 if (numb.getExponent() > 0) { 341 Set<String> debugCreationErrors = new LinkedHashSet<>(); 342 String[] debugOriginals = null; 343 CompactDecimalFormat cdfCurr = 344 BuildIcuCompactDecimalFormat.build( 345 cldrFile, 346 debugCreationErrors, 347 debugOriginals, 348 CompactStyle.SHORT, 349 ULocale.forLanguageTag(cldrFile.getLocaleID()), 350 CurrencyStyle.PLAIN, 351 null); 352 value = cdfCurr.format(numb.toDouble()); 353 } else { 354 NumberFormat nf = icusb.getGenericNumberFormat("latn"); 355 nf.setMaximumFractionDigits((int) numb.getPluralOperand(Operand.v)); 356 nf.setMinimumFractionDigits((int) numb.getPluralOperand(Operand.v)); 357 value = nf.format(numb.toDouble()); 358 } 359 sample = 360 samplePattern 361 .replace('\u00A0', '\u0020') 362 .replace("{0}", value) 363 .replace(". ", ".<br>"); 364 return sample; 365 } 366 } 367 } 368