• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.unicode.cldr.tool;
2 
3 import java.io.IOException;
4 import java.util.Arrays;
5 import java.util.Collection;
6 import java.util.HashMap;
7 import java.util.LinkedHashMap;
8 import java.util.List;
9 import java.util.Map;
10 import java.util.Map.Entry;
11 import java.util.Set;
12 import java.util.TreeMap;
13 import java.util.TreeSet;
14 import java.util.regex.Matcher;
15 import java.util.stream.Collectors;
16 
17 import org.unicode.cldr.draft.FileUtilities;
18 import org.unicode.cldr.tool.FormattedFileWriter.Anchors;
19 import org.unicode.cldr.util.CLDRConfig;
20 import org.unicode.cldr.util.CLDRFile;
21 import org.unicode.cldr.util.CLDRLocale;
22 import org.unicode.cldr.util.CLDRPaths;
23 import org.unicode.cldr.util.CldrUtility;
24 import org.unicode.cldr.util.Factory;
25 import org.unicode.cldr.util.FileCopier;
26 import org.unicode.cldr.util.GrammarInfo;
27 import org.unicode.cldr.util.GrammarInfo.GrammaticalFeature;
28 import org.unicode.cldr.util.GrammarInfo.GrammaticalScope;
29 import org.unicode.cldr.util.GrammarInfo.GrammaticalTarget;
30 import org.unicode.cldr.util.ICUServiceBuilder;
31 import org.unicode.cldr.util.LanguageTagParser;
32 import org.unicode.cldr.util.MapComparator;
33 import org.unicode.cldr.util.Pair;
34 import org.unicode.cldr.util.Rational;
35 import org.unicode.cldr.util.Rational.FormatStyle;
36 import org.unicode.cldr.util.StandardCodes.LstrType;
37 import org.unicode.cldr.util.SupplementalDataInfo;
38 import org.unicode.cldr.util.SupplementalDataInfo.PluralInfo;
39 import org.unicode.cldr.util.SupplementalDataInfo.PluralInfo.Count;
40 import org.unicode.cldr.util.SupplementalDataInfo.PluralType;
41 import org.unicode.cldr.util.UnitConverter;
42 import org.unicode.cldr.util.UnitConverter.ConversionInfo;
43 import org.unicode.cldr.util.UnitConverter.PlaceholderLocation;
44 import org.unicode.cldr.util.UnitConverter.UnitId;
45 import org.unicode.cldr.util.UnitPathType;
46 import org.unicode.cldr.util.Validity;
47 
48 import com.google.common.collect.ComparisonChain;
49 import com.google.common.collect.ImmutableSet;
50 import com.google.common.collect.Iterables;
51 import com.google.common.collect.Multimap;
52 import com.google.common.collect.TreeMultimap;
53 import com.ibm.icu.impl.locale.XCldrStub.ImmutableMap;
54 import com.ibm.icu.text.DecimalFormat;
55 import com.ibm.icu.text.MessageFormat;
56 import com.ibm.icu.text.PluralRules;
57 import com.ibm.icu.text.PluralRules.SampleType;
58 import com.ibm.icu.text.RuleBasedCollator;
59 import com.ibm.icu.util.Output;
60 import com.ibm.icu.util.ULocale;
61 
62 public class ChartGrammaticalForms extends Chart {
63 
64     private static final String MAIN_HEADER = "<h2>Grammatical Forms</h2>";
65     private static final boolean DEBUG = false;
66     private static final String DIR = CLDRPaths.CHART_DIRECTORY + "grammar/";
67     public static final PluralRules ENGLISH_PLURAL_RULES = SDI.getPlurals("en").getPluralRules();
68 
main(String[] args)69     public static void main(String[] args) {
70         new ChartGrammaticalForms().writeChart(null);
71     }
72 
73     @Override
getDirectory()74     public String getDirectory() {
75         return DIR;
76     }
77 
78     @Override
getTitle()79     public String getTitle() {
80         return "Grammatical Forms Charts";
81     }
82 
83     @Override
getFileName()84     public String getFileName() {
85         return "index";
86     }
87 
88     @Override
getExplanation()89     public String getExplanation() {
90         return MAIN_HEADER + "<p>In this version a preliminary set of languages have additional grammatical information, as listed below.<p>";
91     }
92 
93     @Override
writeContents(FormattedFileWriter pw)94     public void writeContents(FormattedFileWriter pw) throws IOException {
95         FileCopier.ensureDirectoryExists(DIR);
96         FileCopier.copy(Chart.class, "index.css", DIR);
97         FormattedFileWriter.copyIncludeHtmls(DIR);
98 
99         FormattedFileWriter.Anchors anchors = new FormattedFileWriter.Anchors();
100         writeSubcharts(anchors);
101         pw.setIndex("Main Chart Index", "../index.html");
102         pw.write(anchors.toString());
103     }
104 
105     static final UnitConverter uc = SDI.getUnitConverter();
106     static final Map<String, Map<Rational, String>> BASE_TO_FACTOR_TO_UNIT;
107     static {
108         Map<String, Map<Rational, String>> _BASE_TO_BEST = new TreeMap<>();
109         ImmutableSet<String> skip = ImmutableSet.of("mile-scandinavian", "100-kilometer", "dunam");
110         Output<String> baseOut = new Output<>();
111         for (String longUnit : Validity.getInstance().getStatusToCodes(LstrType.unit).get(Validity.Status.regular)) {
112             String shortUnit = uc.getShortId(longUnit);
113             System.out.println(shortUnit);
114             if (skip.contains(shortUnit)) {
115                 continue;
116             }
117             if ("mile-per-gallon".equals(shortUnit)) {
118                 int debug = 0;
119             }
120             //Set<String> systems = uc.getSystems(unit);
121             ConversionInfo info = uc.parseUnitId(shortUnit, baseOut, false);
122             if (info == null) {
123                 continue;
124             }
125             Map<Rational, String> factorToUnit = _BASE_TO_BEST.get(baseOut.value);
126             if (factorToUnit == null) {
_BASE_TO_BEST.put(baseOut.value, factorToUnit = new TreeMap<>())127                 _BASE_TO_BEST.put(baseOut.value, factorToUnit = new TreeMap<>());
factorToUnit.put(Rational.ONE, baseOut.value)128                 factorToUnit.put(Rational.ONE, baseOut.value);
129             }
130 
131             if (!info.factor.isPowerOfTen()) {
132                 continue;
133             }
134 
135             String old = factorToUnit.get(info.factor);
136             if (old == null || old.length() > shortUnit.length()) {
factorToUnit.put(info.factor, shortUnit)137                 factorToUnit.put(info.factor, shortUnit);
138             }
139         }
140         BASE_TO_FACTOR_TO_UNIT = CldrUtility.protectCollection(_BASE_TO_BEST);
141         for (Entry<String, Map<Rational, String>> entry : BASE_TO_FACTOR_TO_UNIT.entrySet()) {
142             System.out.println(entry);
143         }
144     }
145 
146     class BestUnitForGender implements Comparable<BestUnitForGender> {
147         final boolean durationOrLength; // true is better
148         final boolean metric; // true is better
149         final double distanceFromOne; // zero is better
150         final String quantity;
151         final String shortUnit;
BestUnitForGender(String shortUnit, String quantity, Collection<String> systems, double baseSize)152         public BestUnitForGender(String shortUnit, String quantity, Collection<String> systems, double baseSize) {
153             super();
154             this.shortUnit = shortUnit;
155             this.quantity = quantity;
156             this.durationOrLength = quantity.equals("duration") || quantity.equals("length");
157             this.metric = systems.contains("metric");
158             this.distanceFromOne = Math.abs(Math.log(baseSize));
159         }
160         @Override
compareTo(BestUnitForGender o)161         public int compareTo(BestUnitForGender o) {
162             // negation, because we want the best one first
163             return ComparisonChain.start()
164                 .compare(o.durationOrLength, durationOrLength)
165                 .compare(o.metric, metric)
166                 .compare(quantity, o.quantity)
167                 .compare(distanceFromOne, o.distanceFromOne)
168                 .compare(shortUnit, o.shortUnit)
169                 .result();
170         }
171         @Override
hashCode()172         public int hashCode() {
173             return shortUnit.hashCode();
174         }
175         @Override
equals(Object obj)176         public boolean equals(Object obj) {
177             return compareTo((BestUnitForGender)obj) == 0;
178         }
179         @Override
toString()180         public String toString() {
181             return shortUnit + "(" + (durationOrLength ? "D" : "") + (metric ? "M" : "") + ":" + quantity + ":" + Math.round(distanceFromOne*10) + ")";
182         }
183     }
184 
185     public class TablePrinterWithHeader {
186         final String header;
187         final TablePrinter tablePrinter;
TablePrinterWithHeader(String header, TablePrinter tablePrinter)188         public TablePrinterWithHeader(String header, TablePrinter tablePrinter) {
189             this.header = header;
190             this.tablePrinter = tablePrinter;
191         }
192     }
193 
writeSubcharts(Anchors anchors)194     public void writeSubcharts(Anchors anchors) throws IOException {
195         Set<String> locales = GrammarInfo.SEED_LOCALES;
196 
197         LanguageTagParser ltp = new LanguageTagParser();
198         //ImmutableSet<String> casesNominativeOnly = ImmutableSet.of(GrammaticalFeature.grammaticalCase.getDefault(null));
199         Factory factory = CLDRConfig.getInstance().getCldrFactory();
200 
201         MapComparator<String> caseOrder = new MapComparator<>(new String[] {
202             "nominative", "vocative", "accusative", "oblique",
203             "genitive", "dative", "locative", "instrumental"});
204         Set<String> sortedCases = new TreeSet<>(caseOrder);
205 
206         MapComparator<String> genderOrder = new MapComparator<>(new String[] {
207             "masculine", "inanimate", "animate", "common", "feminine", "neuter"});
208         Set<String> sortedGenders = new TreeSet<>(genderOrder);
209 
210         Output<Double> sizeInBaseUnits = new Output<>();
211 
212         // collect the "best unit ordering"
213         Map<String, BestUnitForGender> unitToBestUnit = new TreeMap<>();
214         for (String longUnit : GrammarInfo.SPECIAL_TRANSLATION_UNITS) {
215             final String shortUnit = uc.getShortId(longUnit);
216             if (shortUnit.equals("generic")) {
217                 continue;
218             }
219             String unitCell = getBestBaseUnit(uc, shortUnit, sizeInBaseUnits);
220             String quantity = shortUnit.contentEquals("generic") ? "temperature" : uc.getQuantityFromUnit(shortUnit, false);
221 
222             Set<String> systems = uc.getSystems(shortUnit);
223             unitToBestUnit.put(shortUnit, new BestUnitForGender(shortUnit, quantity, systems, sizeInBaseUnits.value));
224         }
225         unitToBestUnit = ImmutableMap.copyOf(unitToBestUnit);
226         // quick check
227         final BestUnitForGender u1 = unitToBestUnit.get("meter");
228         final BestUnitForGender u2 = unitToBestUnit.get("square-centimeter");
229         int comp = u1.compareTo(u2); // should be less
230 
231         Set<BestUnitForGender> sorted2 = new TreeSet<>(unitToBestUnit.values());
232         System.out.println(sorted2);
233 
234         PlaceholderLocation placeholderPosition = PlaceholderLocation.missing;
235         Matcher placeholderMatcher = UnitConverter.PLACEHOLDER.matcher("");
236         Output<String> unitPatternOut = new Output<>();
237 
238         for (String locale : locales) {
239             if (locale.equals("root")) {
240                 continue;
241             }
242             ltp.set(locale);
243             String region = ltp.getRegion();
244             if (!region.isEmpty()) {
245                 continue;
246             }
247             GrammarInfo grammarInfo = SupplementalDataInfo.getInstance().getGrammarInfo(locale, true);
248             if (grammarInfo == null || !grammarInfo.hasInfo(GrammaticalTarget.nominal)) {
249                 continue;
250             }
251             CLDRFile cldrFile = factory.make(locale, true);
252 
253             {
254                 Collection<String> genders = grammarInfo.get(GrammaticalTarget.nominal, GrammaticalFeature.grammaticalGender, GrammaticalScope.units);
255                 sortedGenders.clear();
256                 sortedGenders.addAll(genders);
257             }
258             {
259                 Collection<String> rawCases = grammarInfo.get(GrammaticalTarget.nominal, GrammaticalFeature.grammaticalCase, GrammaticalScope.units);
260                 if (rawCases.isEmpty()) {
261                     rawCases = ImmutableSet.of(GrammaticalFeature.grammaticalCase.getDefault(null));
262                 }
263                 sortedCases.clear();
264                 sortedCases.addAll(rawCases);
265             }
266 
267             //Collection<String> nomCases = rawCases.isEmpty() ? casesNominativeOnly : rawCases;
268 
269             PluralInfo plurals = SupplementalDataInfo.getInstance().getPlurals(PluralType.cardinal, locale);
270             if (plurals == null) {
271                 System.err.println("No " + PluralType.cardinal + "  plurals for " + locale);
272             }
273             Collection<Count> adjustedPlurals = plurals.getCounts();
274             ICUServiceBuilder isb = ICUServiceBuilder.forLocale(CLDRLocale.getInstance(locale));
275             DecimalFormat decFormat = isb.getNumberFormat(1);
276 
277             Map<String, TablePrinterWithHeader> info = new LinkedHashMap<>();
278 
279 
280             if (sortedCases.size() > 1) {
281                 // set up the table and add the headers
282                 TablePrinter caseTablePrinter = new TablePrinter()
283                     .addColumn("Unit", "class='source' width='1%'", CldrUtility.getDoubleLinkMsg(), "class='source'", true)
284                     .setSortPriority(2)
285                     .setRepeatHeader(true)
286                     .addColumn("Quantity", "class='source' width='1%'", null, "class='source'", true)
287                     .setSortPriority(0)
288                     .addColumn("Size", "class='source' width='1%'", null, "class='source'", true)
289                     .setSortPriority(1)
290                     .setHidden(true)
291                     .addColumn("Gender", "class='source' width='1%'", null, "class='source'", true)
292                     .addColumn("Case", "class='source' width='1%'", null, "class='source'", true)
293                     ;
294                 double width = ((int) ((99.0 / (adjustedPlurals.size()*2 + 1)) * 1000)) / 1000.0;
295                 String widthStringTarget = "class='target' width='" + width + "%'";
296                 final PluralRules pluralRules = plurals.getPluralRules();
297 
298                 addTwoColumns(caseTablePrinter, widthStringTarget, adjustedPlurals, pluralRules, true);
299 
300                 // now get the items
301                 // also gather info on the "best power units"
302 
303                 for (String longUnit : GrammarInfo.SPECIAL_TRANSLATION_UNITS) {
304                     final String shortUnit = uc.getShortId(longUnit);
305                     String unitCell = getBestBaseUnit(uc, shortUnit, sizeInBaseUnits);
306                     String quantity = shortUnit.contentEquals("generic") ? "temperature" : uc.getQuantityFromUnit(shortUnit, false);
307 
308                     String gender = UnitPathType.gender.getTrans(cldrFile, "long", shortUnit, null, null, null, null);
309                     Set<String> systems = uc.getSystems(shortUnit);
310 
311                     for (String case1 : sortedCases) { //
312                         // start a row, then add the cells in the row.
313                         caseTablePrinter
314                         .addRow()
315                         .addCell(unitCell)
316                         .addCell(quantity)
317                         .addCell(sizeInBaseUnits.value)
318                         .addCell(gender)
319                         .addCell(case1);
320 
321                         for (Count plural : adjustedPlurals) {
322                             Double sample = getBestSample(pluralRules, plural);
323 
324                             // <caseMinimalPairs case="nominative">{0} kostet €3,50.</caseMinimalPairs>
325 
326                             String unitPattern = cldrFile.getStringValueWithBailey("//ldml/units/unitLength[@type=\"long\"]/unit[@type=\"" + longUnit + "\"]/unitPattern"
327                                 + GrammarInfo.getGrammaticalInfoAttributes(grammarInfo, UnitPathType.unit, plural.toString(), null, case1));
328 
329                             caseTablePrinter.addCell(unitPattern);
330 
331                             String numberPlusUnit = MessageFormat.format(unitPattern, decFormat.format(sample));
332 
333                             String caseMinimalPair = cldrFile.getStringValue("//ldml/numbers/minimalPairs/caseMinimalPairs[@case=\"" + case1 + "\"]");
334                             String withContext = caseMinimalPair == null ? numberPlusUnit : MessageFormat.format(caseMinimalPair, numberPlusUnit);
335 
336                             caseTablePrinter.addCell(withContext);
337                         }
338                         // finish the row
339                         caseTablePrinter.finishRow();
340                     }
341                 }
342                 info.put("Unit Case Info", new TablePrinterWithHeader(
343                     "<p>This table has rows contains unit forms appropriate for different grammatical cases and plural forms. "
344                         + "Each plural form has a sample value such as <i>(1.2)</i> or <i>2</i>. "
345                         + "That value is used with the localized unit pattern to form a formatted measure, such as “2,0 Stunden”. "
346                         + "That formatted measure is in turn substituted into a "
347                         + "<b><a target='doc-minimal-pairs' href='http://cldr.unicode.org/translation/grammatical-inflection#TOC-Miscellaneous-Minimal-Pairs'>case minimal pair pattern</a></b>. "
348                         + "The <b>Gender</b> column is informative; it just supplies the supplied gender for the unit.</p>\n"
349                         + "<ul><li>For clarity, conversion values are supplied for non-metric units. "
350                         + "For more information, see <a target='unit_conversions' href='../supplemental/unit_conversions.html'>Unit Conversions</a>.</li>"
351                         + "</ul>\n"
352                         , caseTablePrinter));
353             }
354 
355             if (sortedCases.size() > 1 || sortedGenders.size() > 1) {
356 
357                 // get best units for gender.
358                 Multimap<String, BestUnitForGender> bestUnitForGender = TreeMultimap.create();
359 
360                 for (String longUnit : GrammarInfo.SPECIAL_TRANSLATION_UNITS) {
361                     final String shortUnit = uc.getShortId(longUnit);
362                     String gender = UnitPathType.gender.getTrans(cldrFile, "long", shortUnit, null, null, null, null);
363                     final BestUnitForGender bestUnit = unitToBestUnit.get(shortUnit);
364                     if (bestUnit != null) {
365                         bestUnitForGender.put(gender, bestUnit);
366                     }
367                 }
368 
369                 for (Entry<String, Collection<BestUnitForGender>> entry : bestUnitForGender.asMap().entrySet()) {
370                     List<String> items = entry.getValue()
371                         .stream()
372                         .map(x -> x.shortUnit)
373                         .collect(Collectors.toList());
374                     System.out.println(locale + "\t" + entry.getKey() + "\t" + items);
375                 }
376 
377 
378                 TablePrinter caseTablePrinter = new TablePrinter()
379                     .addColumn("Unit", "class='source' width='1%'", CldrUtility.getDoubleLinkMsg(), "class='source'", true)
380                     .setSortPriority(2)
381                     .setRepeatHeader(true)
382                     .addColumn("Case", "class='source' width='1%'", null, "class='source'", true)
383                     .addColumn("Gender", "class='source' width='1%'", null, "class='source'", true)
384                     ;
385                 double width = ((int) ((99.0 / (adjustedPlurals.size()*2 + 1)) * 1000)) / 1000.0;
386                 String widthStringTarget = "class='target' width='" + width + "%'";
387                 final PluralRules pluralRules = plurals.getPluralRules();
388 
389                 addTwoColumns(caseTablePrinter, widthStringTarget, adjustedPlurals, pluralRules, false);
390 
391                 // now get the items
392                 for (String power : Arrays.asList("power2", "power3")) {
393                     String unitCell = power;
394 
395                     for (String gender : sortedGenders) {
396                         Collection<BestUnitForGender> bestUnits = bestUnitForGender.get(gender);
397                         String bestUnit = null;
398                         if (!bestUnits.isEmpty()) {
399                             bestUnit = bestUnits.iterator().next().shortUnit;
400                         }
401 
402                         for (String case1 : sortedCases) { //
403                             // start a row, then add the cells in the row.
404                             caseTablePrinter
405                             .addRow()
406                             .addCell(unitCell)
407                             .addCell(case1)
408                             .addCell(gender + (bestUnit == null ? "" : "\n(" + bestUnit + ")"))
409                             ;
410 
411                             for (Count plural : adjustedPlurals) {
412                                 String localizedPowerPattern = UnitPathType.power.getTrans(cldrFile, "long", power, plural.toString(), case1, gender, null);
413                                 caseTablePrinter.addCell(localizedPowerPattern);
414 
415                                 if (bestUnit == null) {
416                                     caseTablePrinter.addCell("n/a");
417                                 } else {
418                                     Double samplePlural = getBestSample(pluralRules, plural);
419                                     String localizedUnitPattern = UnitPathType.unit.getTrans(cldrFile, "long", bestUnit, plural.toString(), case1, gender, null);
420                                     placeholderPosition = UnitConverter.extractUnit(placeholderMatcher, localizedUnitPattern, unitPatternOut);
421                                     if (placeholderPosition != PlaceholderLocation.middle) {
422                                         localizedUnitPattern = unitPatternOut.value;
423                                         String placeholderPattern = placeholderMatcher.group();
424 
425                                         String combined;
426                                         try {
427                                             combined = UnitConverter.combineLowercasing(new ULocale(locale), "long", localizedPowerPattern, localizedUnitPattern);
428                                         } catch (Exception e) {
429                                            throw new IllegalArgumentException(locale + ") Can't combine "
430                                                + "localizedPowerPattern=«" + localizedPowerPattern
431                                                + "» with localizedUnitPattern=«"+ localizedUnitPattern + "»"
432                                                );
433                                         }
434                                         String combinedWithPlaceholder = UnitConverter.addPlaceholder(combined, placeholderPattern, placeholderPosition);
435 
436                                         String sample = MessageFormat.format(combinedWithPlaceholder, decFormat.format(samplePlural));
437 
438                                         String caseMinimalPair = cldrFile.getStringValue("//ldml/numbers/minimalPairs/caseMinimalPairs[@case=\"" + case1 + "\"]");
439                                         String withContext = caseMinimalPair == null ? sample : MessageFormat.format(caseMinimalPair, sample);
440 
441                                         caseTablePrinter.addCell(withContext);
442                                     }
443                                 }
444                             }
445                             // finish the row
446                             caseTablePrinter.finishRow();
447                         }
448                     }
449                 }
450                 info.put("Unit Power Components", new TablePrinterWithHeader(
451                     "<p>This table shows the square (power2) and cubic (power3) patterns, which may vary by case, gender, and plural forms. "
452                         + "Each gender is illustrated with a unit where possible, such as <i>(second)</i> or <i>(meter)</i>. "
453                         + "Each plural category is illustrated with a unit where possible, such as <i>(1)</i> or <i>(1.2)</i>. "
454                         + "The patterns are first supplied, and then combined with the samples and "
455                         + "<b><a target='doc-minimal-pairs' href='http://cldr.unicode.org/translation/grammatical-inflection#TOC-Miscellaneous-Minimal-Pairs'>case minimal pair patterns</a></b> "
456                         + "in the next <b>Formatted Sample</b> column."
457                         + "</p>", caseTablePrinter));
458             }
459 
460 
461             if (!info.isEmpty()) {
462                 String name = ENGLISH.getName(locale);
463                 new Subchart(name + ": Unit Grammar Info", locale, info).writeChart(anchors);
464             }
465         }
466     }
467 
addTwoColumns(TablePrinter caseTablePrinter, String widthStringTarget, Collection<Count> adjustedPlurals, final PluralRules pluralRules, boolean spanRows)468     public void addTwoColumns(TablePrinter caseTablePrinter, String widthStringTarget, Collection<Count> adjustedPlurals, final PluralRules pluralRules, boolean spanRows) {
469         for (Count plural : adjustedPlurals) {
470             Double sample = getBestSample(pluralRules, plural);
471             final String pluralHeader = plural.toString() + " (" + sample + ")";
472             caseTablePrinter.addColumn("Form for: " + pluralHeader, widthStringTarget, null, "class='target'", true)
473             .setSpanRows(spanRows);
474             caseTablePrinter.addColumn("Formatted Sample: " + pluralHeader, widthStringTarget, null, "class='target'", true);
475         }
476     }
477 
478     static final Map<String, Pair<String, Double>> BEST_UNIT_CACHE = new HashMap<>();
479 
getBestBaseUnit(UnitConverter uc, final String shortUnit, Output<Double> sizeInBaseUnits)480     public static String getBestBaseUnit(UnitConverter uc, final String shortUnit, Output<Double> sizeInBaseUnits) {
481         Pair<String, Double> cached = BEST_UNIT_CACHE.get(shortUnit);
482         if (cached != null) {
483             sizeInBaseUnits.value = cached.getSecond();
484             return cached.getFirst();
485         }
486         if (shortUnit.equals("square-mile")) {
487             int debug = 0;
488         }
489         String unitCell = ENGLISH.getStringValue("//ldml/units/unitLength[@type=\"long\"]/unit[@type=\"" + uc.getLongId(shortUnit)
490         + "\"]/displayName");
491         Output<String> baseUnit = new Output<>();
492         ConversionInfo info = uc.parseUnitId(shortUnit, baseUnit, false);
493 
494         if (info != null) {
495             sizeInBaseUnits.value = info.factor.doubleValue();
496             Map<Rational, String> factorToUnit = BASE_TO_FACTOR_TO_UNIT.get(baseUnit.value);
497             if (factorToUnit == null) {
498                 int debug = 0;
499             }
500             String bestUnit = null;
501             Rational bestFactor = null;
502             Rational inputBoundary = Rational.of(2).multiply(info.factor);
503             for (Entry<Rational, String> entry : factorToUnit.entrySet()) {
504                 final Rational currentFactor = entry.getKey();
505                 if (bestUnit != null && currentFactor.compareTo(inputBoundary) >= 0) {
506                     break;
507                 }
508                 bestFactor = currentFactor;
509                 bestUnit = entry.getValue();
510             }
511             bestFactor = info.factor.divide(bestFactor); // scale for bestUnit
512             if (!bestFactor.equals(Rational.ONE) || !shortUnit.equals(bestUnit)) {
513                 final String string = bestFactor.toString(FormatStyle.repeating);
514                 final double bestDoubleFactor = bestFactor.doubleValue();
515                 String pluralCategory = ENGLISH_PLURAL_RULES.select(bestDoubleFactor);
516                 final String unitPath = "//ldml/units/unitLength[@type=\"long\"]/unit[@type=\"" + uc.getLongId(bestUnit)
517                 + "\"]/unitPattern[@count=\"" + pluralCategory
518                 + "\"]";
519                 String unitPattern = ENGLISH.getStringValue(unitPath);
520                 if (unitPattern == null) {
521                     final UnitId unitId = uc.createUnitId(bestUnit);
522                     unitPattern = unitId.toString(ENGLISH, "long", pluralCategory, null, null, false);
523                     if (unitPattern == null) {
524                         int debug = 0;
525                     }
526                 }
527                 String unitMeasure = MessageFormat.format(unitPattern, string.contains("/") ? "~" + bestDoubleFactor : string);
528                 unitCell = shortUnit + "\n( = " + unitMeasure + ")";
529             }
530         } else {
531             sizeInBaseUnits.value = Double.valueOf(-1);
532         }
533         BEST_UNIT_CACHE.put(shortUnit, Pair.of(unitCell, sizeInBaseUnits.value));
534         return unitCell;
535     }
536 
getBestSample(PluralRules pluralRules, Count plural)537     private Double getBestSample(PluralRules pluralRules, Count plural) {
538         Collection<Double> samples = pluralRules.getSamples(plural.toString());
539         if (samples.isEmpty()) {
540             samples = pluralRules.getSamples(plural.toString(), SampleType.DECIMAL);
541         }
542         int size = samples.size();
543         switch (size) {
544         case 0:
545             throw new IllegalArgumentException("shouldn't happen");
546         case 1:
547             return samples.iterator().next();
548         }
549         return Iterables.skip(samples, 1).iterator().next();
550     }
551 
552     private class Subchart extends Chart {
553         String title;
554         String file;
555         private Map<String,TablePrinterWithHeader> tablePrinter;
556 
557         @Override
getShowDate()558         public boolean getShowDate() {
559             return false;
560         }
561 
Subchart(String title, String file, Map<String, TablePrinterWithHeader> info)562         public Subchart(String title, String file, Map<String, TablePrinterWithHeader> info) {
563             super();
564             this.title = title;
565             this.file = file;
566             this.tablePrinter = info;
567         }
568 
569         @Override
getDirectory()570         public String getDirectory() {
571             return DIR;
572         }
573 
574         @Override
getTitle()575         public String getTitle() {
576             return title;
577         }
578 
579         @Override
getFileName()580         public String getFileName() {
581             return file;
582         }
583 
584         @Override
getExplanation()585         public String getExplanation() {
586             return MAIN_HEADER
587                 + "<p><i>Unit Inflections, Phase 1:</i> The end goal is to add full case and gender support for formatted units. "
588                 + "During Phase 1, a limited number of locales and units of measurement are being handled in CLDR v38, "
589                 + "so that we can work kinks out of the process before expanding to all units for all locales.</p>\n"
590                 + "<p>This chart shows grammatical information available for certain unit and/or power patterns. These patterns are also illustrated with <b>Formatted Samples</b> that combine the patterns with sample numbers and "
591                 + "<b><a target='doc-minimal-pairs' href='http://cldr.unicode.org/translation/grammatical-inflection#TOC-Miscellaneous-Minimal-Pairs'>case minimal pair patterns</a></b>. "
592                 + "For example, “… für {0} …” is a <i>case minimal pair pattern</i> that requires the placeholder {0} to be in the accusative case in German. By inserting into a minimal pair pattern, "
593                 + "it is easier to ensure that the original unit and/or power patterns are correctly inflected. </p>\n"
594                 + "<p><b>Notes</b>"
595                 + "<ul><li>We don't have the cross-product of minimal pairs for both case and plural forms, "
596                 + "so the <i>case minimal pair pattern</i> might not be correct for the row’s plural category, especially in the nominative.</li>"
597                 + "<li>Translators often have difficulties with the the minimal pair patterns, "
598                 + "since they are <i>transcreations</i> not translations. The Hindi minimal pair patterns for case and gender have been discarded because they were incorrectly translated.</li>"
599                 + "<li>We don't expect translators to supply minimal pair patterns that are natural for any kind of placeholder: "
600                 + "for example, it is probably not typical to use the vocative with 3.2 meters! So look at the <b>Formatted Samples</b> as an aid for helping to see the context for grammatical inflections, but one that has limitations.</li></ul>"
601                 ;
602         }
603 
604         @Override
writeContents(FormattedFileWriter pw)605         public void writeContents(FormattedFileWriter pw) throws IOException {
606             if (tablePrinter.size() > 1) {
607                 pw.write("<h2>Table of Contents</h2>\n");
608                 pw.append("<ol>\n");
609                 for (String header : tablePrinter.keySet()) {
610                     pw.write("<li><b>"
611                         + "<a href='#" + FileUtilities.anchorize(header)+ "'>" + header + "</a>"
612                         + "</b></li>\n");
613                 }
614                 pw.append("</ol>\n");
615             }
616             for (Entry<String, TablePrinterWithHeader> entry : tablePrinter.entrySet()) {
617                 final String header = entry.getKey();
618                 pw.write("<h2><a name='" + FileUtilities.anchorize(header)+ "'>" + header + "</a></h2>\n");
619                 final TablePrinterWithHeader explanation = entry.getValue();
620                 pw.write(explanation.header);
621                 pw.write(explanation.tablePrinter.toTable());
622             }
623         }
624     }
625 
626     public static RuleBasedCollator RBC;
627     static {
628         Factory cldrFactory = Factory.make(CLDRPaths.COMMON_DIRECTORY + "collation/", ".*");
629         CLDRFile root = cldrFactory.make("root", false);
630         String rules = root.getStringValue("//ldml/collations/collation[@type=\"emoji\"][@visibility=\"external\"]/cr");
631 
632 //        if (!rules.contains("'#⃣'")) {
633 //            rules = rules.replace("#⃣", "'#⃣'").replace("*⃣", "'*⃣'"); //hack for 8288
634 //        }
635 
636         try {
637             RBC = new RuleBasedCollator(rules);
638         } catch (Exception e) {
639             throw new IllegalArgumentException("Failure in rules for " + CLDRPaths.COMMON_DIRECTORY + "collation/" + "root", e);
640         }
641     }
642 }
643