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