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