| package org.unicode.cldr.test; |
| |
| import java.io.PrintWriter; |
| import java.io.StringWriter; |
| import java.text.ChoiceFormat; |
| import java.util.ArrayList; |
| import java.util.BitSet; |
| import java.util.Collection; |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Set; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import org.unicode.cldr.tool.CLDRFileTransformer; |
| import org.unicode.cldr.tool.CLDRFileTransformer.LocaleTransform; |
| import org.unicode.cldr.tool.LikelySubtags; |
| import org.unicode.cldr.util.AnnotationUtil; |
| import org.unicode.cldr.util.CLDRConfig; |
| import org.unicode.cldr.util.CLDRFile; |
| import org.unicode.cldr.util.CLDRLocale; |
| import org.unicode.cldr.util.CLDRPaths; |
| import org.unicode.cldr.util.CldrUtility; |
| import org.unicode.cldr.util.DayPeriodInfo; |
| import org.unicode.cldr.util.DayPeriodInfo.DayPeriod; |
| import org.unicode.cldr.util.EmojiConstants; |
| import org.unicode.cldr.util.Factory; |
| import org.unicode.cldr.util.GrammarInfo; |
| import org.unicode.cldr.util.GrammarInfo.GrammaticalFeature; |
| import org.unicode.cldr.util.GrammarInfo.GrammaticalScope; |
| import org.unicode.cldr.util.GrammarInfo.GrammaticalTarget; |
| import org.unicode.cldr.util.ICUServiceBuilder; |
| import org.unicode.cldr.util.LanguageTagParser; |
| import org.unicode.cldr.util.Level; |
| import org.unicode.cldr.util.PathDescription; |
| import org.unicode.cldr.util.PatternCache; |
| import org.unicode.cldr.util.PluralSamples; |
| import org.unicode.cldr.util.StandardCodes.LstrType; |
| import org.unicode.cldr.util.SupplementalDataInfo; |
| import org.unicode.cldr.util.SupplementalDataInfo.PluralInfo; |
| import org.unicode.cldr.util.SupplementalDataInfo.PluralInfo.Count; |
| import org.unicode.cldr.util.SupplementalDataInfo.PluralType; |
| import org.unicode.cldr.util.TransliteratorUtilities; |
| import org.unicode.cldr.util.UnitConverter; |
| import org.unicode.cldr.util.Units; |
| import org.unicode.cldr.util.Validity; |
| import org.unicode.cldr.util.Validity.Status; |
| import org.unicode.cldr.util.XListFormatter.ListTypeLength; |
| import org.unicode.cldr.util.XPathParts; |
| import org.unicode.cldr.util.personname.PersonNameFormatter; |
| import org.unicode.cldr.util.personname.PersonNameFormatter.FallbackFormatter; |
| import org.unicode.cldr.util.personname.PersonNameFormatter.FormatParameters; |
| import org.unicode.cldr.util.personname.PersonNameFormatter.NameObject; |
| import org.unicode.cldr.util.personname.PersonNameFormatter.NamePattern; |
| import org.unicode.cldr.util.personname.SimpleNameObject; |
| |
| import com.google.common.base.Joiner; |
| import com.google.common.collect.ImmutableList; |
| import com.ibm.icu.impl.Row.R3; |
| import com.ibm.icu.impl.Utility; |
| import com.ibm.icu.impl.number.DecimalQuantity; |
| import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD; |
| import com.ibm.icu.lang.UCharacter; |
| import com.ibm.icu.text.BreakIterator; |
| import com.ibm.icu.text.DateFormat; |
| import com.ibm.icu.text.DateFormatSymbols; |
| import com.ibm.icu.text.DateTimePatternGenerator; |
| import com.ibm.icu.text.DecimalFormat; |
| import com.ibm.icu.text.DecimalFormatSymbols; |
| import com.ibm.icu.text.ListFormatter; |
| import com.ibm.icu.text.MessageFormat; |
| import com.ibm.icu.text.NumberFormat; |
| import com.ibm.icu.text.PluralRules; |
| import com.ibm.icu.text.PluralRules.DecimalQuantitySamples; |
| import com.ibm.icu.text.PluralRules.DecimalQuantitySamplesRange; |
| import com.ibm.icu.text.PluralRules.Operand; |
| import com.ibm.icu.text.PluralRules.SampleType; |
| import com.ibm.icu.text.SimpleDateFormat; |
| import com.ibm.icu.text.SimpleFormatter; |
| import com.ibm.icu.text.Transliterator; |
| import com.ibm.icu.text.UnicodeSet; |
| import com.ibm.icu.text.UTF16; |
| import com.ibm.icu.util.Calendar; |
| import com.ibm.icu.util.Output; |
| import com.ibm.icu.util.TimeZone; |
| import com.ibm.icu.util.ULocale; |
| |
| /** |
| * Class to generate examples and help messages for the Survey tool (or console version). |
| * |
| * @author markdavis |
| */ |
| public class ExampleGenerator { |
| private static final String EXAMPLE_OF_INCORRECT = "❌ "; |
| private static final String EXAMPLE_OF_CAUTION = "⚠️ "; |
| |
| private static final boolean DEBUG_EXAMPLE_GENERATOR = false; |
| |
| final static boolean DEBUG_SHOW_HELP = false; |
| |
| private static final CLDRConfig CONFIG = CLDRConfig.getInstance(); |
| |
| private static final String ALT_STAND_ALONE = "[@alt=\"stand-alone\"]"; |
| |
| private static final String EXEMPLAR_CITY_LOS_ANGELES = "//ldml/dates/timeZoneNames/zone[@type=\"America/Los_Angeles\"]/exemplarCity"; |
| |
| private static final Pattern URL_PATTERN = Pattern |
| .compile("http://[\\-a-zA-Z0-9]+(\\.[\\-a-zA-Z0-9]+)*([/#][\\-a-zA-Z0-9]+)*"); |
| |
| private static final SupplementalDataInfo supplementalDataInfo = SupplementalDataInfo.getInstance(); |
| static final UnitConverter UNIT_CONVERTER = supplementalDataInfo.getUnitConverter(); |
| static final Set<String> UNITS = Validity.getInstance().getStatusToCodes(LstrType.unit).get(Status.regular); |
| |
| public final static double NUMBER_SAMPLE = 123456.789; |
| public final static double NUMBER_SAMPLE_WHOLE = 2345; |
| |
| public final static TimeZone ZONE_SAMPLE = TimeZone.getTimeZone("America/Indianapolis"); |
| public final static TimeZone GMT_ZONE_SAMPLE = TimeZone.getTimeZone("Etc/GMT"); |
| |
| private static final String exampleStart = "<div class='cldr_example'>"; |
| private static final String exampleStartAuto = "<div class='cldr_example_auto' dir='auto'>"; |
| private static final String exampleStartRTL = "<div class='cldr_example_rtl' dir='rtl'>"; |
| private static final String exampleStartHeader = "<div class='cldr_example_rtl'>"; |
| private static final String exampleEnd = "</div>"; |
| private static final String startItalic = "<i>"; |
| private static final String endItalic = "</i>"; |
| private static final String startSup = "<sup>"; |
| private static final String endSup = "</sup>"; |
| private static final String backgroundAutoStart = "<span class='cldr_background_auto'>"; |
| private static final String backgroundAutoEnd = "</span>"; |
| private String backgroundStart = "<span class='cldr_substituted'>"; // overrideable |
| private String backgroundEnd = "</span>"; // overrideable |
| |
| public static final String backgroundStartSymbol = "\uE234"; |
| public static final String backgroundEndSymbol = "\uE235"; |
| private static final String backgroundTempSymbol = "\uE236"; |
| private static final String exampleSeparatorSymbol = "\uE237"; |
| private static final String startItalicSymbol = "\uE238"; |
| private static final String endItalicSymbol = "\uE239"; |
| private static final String startSupSymbol = "\uE23A"; |
| private static final String endSupSymbol = "\uE23B"; |
| private static final String backgroundAutoStartSymbol = "\uE23C"; |
| private static final String backgroundAutoEndSymbol = "\uE23D"; |
| private static final String exampleStartAutoSymbol = "\uE23E"; |
| private static final String exampleStartRTLSymbol = "\uE23F"; |
| private static final String exampleStartHeaderSymbol = "\uE240"; |
| private static final String exampleEndSymbol = "\uE241"; |
| |
| private static final String contextheader = "Key: " + backgroundAutoStartSymbol + "neutral" + backgroundAutoEndSymbol + ", RTL"; |
| |
| public static final char TEXT_VARIANT = '\uFE0E'; |
| |
| private static final UnicodeSet BIDI_MARKS = new UnicodeSet("[:Bidi_Control:]").freeze(); |
| |
| public final static Date DATE_SAMPLE; |
| |
| private final static Date DATE_SAMPLE2; |
| private final static Date DATE_SAMPLE3; |
| private final static Date DATE_SAMPLE4; |
| |
| static { |
| Calendar calendar = Calendar.getInstance(ZONE_SAMPLE, ULocale.ENGLISH); |
| calendar.set(1999, 8, 5, 13, 25, 59); // 1999-08-05 13:25:59 |
| DATE_SAMPLE = calendar.getTime(); |
| calendar.set(1999, 9, 27, 13, 25, 59); // 1999-09-27 13:25:59 |
| DATE_SAMPLE2 = calendar.getTime(); |
| |
| calendar.set(1999, 8, 5, 7, 0, 0); // 1999-08-5 07:00:00 |
| DATE_SAMPLE3 = calendar.getTime(); |
| calendar.set(1999, 8, 5, 23, 0, 0); // 1999-08-5 23:00:00 |
| DATE_SAMPLE4 = calendar.getTime(); |
| } |
| |
| @SuppressWarnings("deprecation") |
| static final List<DecimalQuantity> CURRENCY_SAMPLES = ImmutableList.of( |
| DecimalQuantity_DualStorageBCD.fromExponentString("1.23"), |
| DecimalQuantity_DualStorageBCD.fromExponentString("0"), |
| DecimalQuantity_DualStorageBCD.fromExponentString("2.34"), |
| DecimalQuantity_DualStorageBCD.fromExponentString("3.45"), |
| DecimalQuantity_DualStorageBCD.fromExponentString("5.67"), |
| DecimalQuantity_DualStorageBCD.fromExponentString("1")); |
| |
| public static final Pattern PARAMETER = PatternCache.get("(\\{(?:0|[1-9][0-9]*)\\})"); |
| public static final Pattern PARAMETER_SKIP0 = PatternCache.get("(\\{[1-9][0-9]*\\})"); |
| public static final Pattern ALL_DIGITS = PatternCache.get("(\\p{Nd}+(.\\p{Nd}+)?)"); |
| |
| private static Calendar generatingCalendar = Calendar.getInstance(ULocale.US); |
| |
| private static Date getDate(int year, int month, int date, int hour, int minute, int second) { |
| synchronized (generatingCalendar) { |
| generatingCalendar.setTimeZone(GMT_ZONE_SAMPLE); |
| generatingCalendar.set(year, month, date, hour, minute, second); |
| return generatingCalendar.getTime(); |
| } |
| } |
| |
| private static Date FIRST_INTERVAL = getDate(2008, 1, 13, 5, 7, 9); |
| private static Map<String, Date> SECOND_INTERVAL = CldrUtility.asMap(new Object[][] { |
| { "G", getDate(1009, 2, 14, 17, 8, 10) }, // "G" mostly useful for calendars that have short eras, like Japanese |
| { "y", getDate(2009, 2, 14, 17, 8, 10) }, |
| { "M", getDate(2008, 2, 14, 17, 8, 10) }, |
| { "d", getDate(2008, 1, 14, 17, 8, 10) }, |
| { "a", getDate(2008, 1, 13, 17, 8, 10) }, |
| { "h", getDate(2008, 1, 13, 6, 8, 10) }, |
| { "m", getDate(2008, 1, 13, 5, 8, 10) } |
| }); |
| |
| public void setCachingEnabled(boolean enabled) { |
| exCache.setCachingEnabled(enabled); |
| } |
| |
| public void setCacheOnly(boolean cacheOnly) { |
| exCache.setCacheOnly(cacheOnly); |
| } |
| |
| /** |
| * verboseErrors affects not only the verboseness of error reporting, but also, for |
| * example, whether some unit tests pass or fail. The function setVerboseErrors |
| * can be used to modify it. It must be initialized here to false, otherwise |
| * cldr-unittest TestAll.java fails. Reference: https://unicode.org/cldr/trac/ticket/12025 |
| */ |
| private boolean verboseErrors = false; |
| |
| private Calendar calendar = Calendar.getInstance(ZONE_SAMPLE, ULocale.ENGLISH); |
| |
| private CLDRFile cldrFile; |
| |
| private CLDRFile englishFile; |
| private BestMinimalPairSamples bestMinimalPairSamples = null; |
| |
| private ExampleCache exCache = new ExampleCache(); |
| |
| private ICUServiceBuilder icuServiceBuilder = new ICUServiceBuilder(); |
| |
| private PluralInfo pluralInfo; |
| |
| private GrammarInfo grammarInfo; |
| |
| private PluralSamples patternExamples; |
| |
| private Map<String, String> subdivisionIdToName; |
| |
| private String creationTime = null; // only used if DEBUG_EXAMPLE_GENERATOR |
| |
| private IntervalFormat intervalFormat = new IntervalFormat(); |
| |
| private PathDescription pathDescription; |
| |
| /** |
| * True if this ExampleGenerator is especially for generating "English" examples, |
| * false if it is for generating "native" examples. |
| */ |
| private boolean typeIsEnglish; |
| |
| /** |
| * True if this ExampleGenerator is for RTL locale. |
| */ |
| private boolean isRTL; |
| |
| HelpMessages helpMessages; |
| |
| public CLDRFile getCldrFile() { |
| return cldrFile; |
| } |
| |
| /** |
| * For this (locale-specific) ExampleGenerator, clear the cached examples for |
| * any paths whose examples might depend on the winning value of the given path, |
| * since the winning value of the given path has changed. |
| * |
| * @param xpath the path whose winning value has changed |
| * |
| * Called by TestCache.updateExampleGeneratorCache |
| */ |
| public void updateCache(String xpath) { |
| exCache.update(xpath); |
| } |
| |
| /** |
| * For getting the end of the "background" style. Default is "</span>". It is |
| * used in composing patterns, so it can show the part that corresponds to the |
| * value. |
| * |
| * @return |
| */ |
| public String getBackgroundEnd() { |
| return backgroundEnd; |
| } |
| |
| /** |
| * For setting the end of the "background" style. Default is "</span>". It is |
| * used in composing patterns, so it can show the part that corresponds to the |
| * value. |
| * |
| * @return |
| */ |
| public void setBackgroundEnd(String backgroundEnd) { |
| this.backgroundEnd = backgroundEnd; |
| } |
| |
| /** |
| * For getting the "background" style. Default is "<span |
| * style='background-color: gray'>". It is used in composing patterns, so it |
| * can show the part that corresponds to the value. |
| * |
| * @return |
| */ |
| public String getBackgroundStart() { |
| return backgroundStart; |
| } |
| |
| /** |
| * For setting the "background" style. Default is "<span |
| * style='background-color: gray'>". It is used in composing patterns, so it |
| * can show the part that corresponds to the value. |
| * |
| * @return |
| */ |
| public void setBackgroundStart(String backgroundStart) { |
| this.backgroundStart = backgroundStart; |
| } |
| |
| /** |
| * Set the verbosity level of internal errors. |
| * For example, setVerboseErrors(true) will cause |
| * full stack traces to be shown in some cases. |
| */ |
| public void setVerboseErrors(boolean verbosity) { |
| this.verboseErrors = verbosity; |
| } |
| |
| /** |
| * Create an Example Generator. If this is shared across threads, it must be synchronized. |
| * |
| * @param resolvedCldrFile |
| * @param englishFile |
| * @param supplementalDataDirectory |
| */ |
| public ExampleGenerator(CLDRFile resolvedCldrFile, CLDRFile englishFile, String supplementalDataDirectory) { |
| if (!resolvedCldrFile.isResolved()) { |
| throw new IllegalArgumentException("CLDRFile must be resolved"); |
| } |
| if (!englishFile.isResolved()) { |
| throw new IllegalArgumentException("English CLDRFile must be resolved"); |
| } |
| cldrFile = resolvedCldrFile; |
| final String localeId = cldrFile.getLocaleID(); |
| subdivisionIdToName = EmojiSubdivisionNames.getSubdivisionIdToName(localeId); |
| pluralInfo = supplementalDataInfo.getPlurals(PluralType.cardinal, localeId); |
| grammarInfo = supplementalDataInfo.getGrammarInfo(localeId); // getGrammarInfo can return null |
| this.englishFile = englishFile; |
| this.typeIsEnglish = (resolvedCldrFile == englishFile); |
| icuServiceBuilder.setCldrFile(cldrFile); |
| |
| bestMinimalPairSamples = new BestMinimalPairSamples(cldrFile, icuServiceBuilder, false); |
| |
| String characterOrder = cldrFile.getStringValue("//ldml/layout/orientation/characterOrder"); |
| this.isRTL = (characterOrder != null && characterOrder.equals("right-to-left")); |
| |
| if (DEBUG_EXAMPLE_GENERATOR) { |
| creationTime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(Calendar.getInstance().getTime()); |
| System.out.println("🧞 Created new ExampleGenerator for loc " + localeId + " at " + creationTime); |
| } |
| } |
| |
| /** |
| * Get an example string, in html, if there is one for this path, |
| * otherwise null. For use in the survey tool, an example might be returned |
| * *even* if there is no value in the locale. For example, the locale might |
| * have a path that English doesn't, but you want to return the best English |
| * example. <br> |
| * The result is valid HTML. |
| * |
| * If generating examples for an inheritance marker, use the "real" inherited value |
| * to generate from. Do this BEFORE accessing the cache, which doesn't use INHERITANCE_MARKER. |
| * |
| * @param xpath the path; e.g., "//ldml/dates/timeZoneNames/fallbackFormat" |
| * @param value the value; e.g., "{1} [{0}]"; not necessarily the winning value |
| * @return the example HTML, or null |
| */ |
| public String getExampleHtml(String xpath, String value) { |
| if (value == null || xpath == null || xpath.endsWith("/alias")) { |
| return null; |
| } |
| String result = null; |
| try { |
| if (CldrUtility.INHERITANCE_MARKER.equals(value)) { |
| value = cldrFile.getBaileyValue(xpath, null, null); |
| if (value == null) { |
| /* |
| * This can happen for some paths, such as |
| * //ldml/dates/timeZoneNames/metazone[@type="Mawson"]/short/daylight |
| */ |
| return null; |
| } |
| } |
| ExampleCache.ExampleCacheItem cacheItem = exCache.new ExampleCacheItem(xpath, value); |
| result = cacheItem.getExample(); |
| if (result != null) { |
| return result; |
| } |
| result = constructExampleHtml(xpath, value); |
| cacheItem.putExample(result); |
| } catch (RuntimeException e) { |
| e.printStackTrace(); |
| String unchained = verboseErrors ? ("<br>" + finalizeBackground(unchainException(e))) : ""; |
| result = "<i>Parsing error. " + finalizeBackground(e.getMessage()) + "</i>" + unchained; |
| } |
| return result; |
| } |
| |
| /** |
| * Do the main work of getExampleHtml given that the result was not |
| * found in the cache. |
| * |
| * @param xpath the path; e.g., "//ldml/dates/timeZoneNames/fallbackFormat" |
| * @param value the value; e.g., "{1} [{0}]"; not necessarily the winning value |
| * @return the example HTML, or null |
| */ |
| private String constructExampleHtml(String xpath, String value) { |
| String result = null; |
| boolean showContexts = isRTL || BIDI_MARKS.containsSome(value); // only used for certain example types |
| /* |
| * Need getInstance, not getFrozenInstance here: some functions such as handleNumberSymbol |
| * expect to call functions like parts.addRelative which throw exceptions if parts is frozen. |
| */ |
| XPathParts parts = XPathParts.getFrozenInstance(xpath).cloneAsThawed(); |
| if (parts.contains("dateRangePattern")) { // {0} - {1} |
| result = handleDateRangePattern(value); |
| } else if (parts.contains("timeZoneNames")) { |
| result = handleTimeZoneName(parts, value); |
| } else if (parts.contains("localeDisplayNames")) { |
| result = handleDisplayNames(xpath, parts, value); |
| } else if (parts.contains("currency")) { |
| result = handleCurrency(xpath, parts, value); |
| } else if (parts.contains("dayPeriods")) { |
| result = handleDayPeriod(parts, value); |
| } else if (parts.contains("pattern") || parts.contains("dateFormatItem")) { |
| if (parts.contains("calendar")) { |
| result = handleDateFormatItem(xpath, value, showContexts); |
| } else if (parts.contains("miscPatterns")) { |
| result = handleMiscPatterns(parts, value); |
| } else if (parts.contains("numbers")) { |
| if (parts.contains("currencyFormat")) { |
| result = handleCurrencyFormat(parts, value, showContexts); |
| } else { |
| result = handleDecimalFormat(parts, value, showContexts); |
| } |
| } |
| } else if (parts.getElement(2).contains("symbols")) { |
| result = handleNumberSymbol(parts, value); |
| } else if (parts.contains("defaultNumberingSystem") || parts.contains("otherNumberingSystems")) { |
| result = handleNumberingSystem(value); |
| } else if (parts.contains("currencyFormats") && parts.contains("unitPattern")) { |
| result = formatCountValue(xpath, parts, value); |
| } else if (parts.getElement(-1).equals("compoundUnitPattern")) { |
| result = handleCompoundUnit(parts); |
| } else if (parts.getElement(-1).equals("compoundUnitPattern1") |
| || parts.getElement(-1).equals("unitPrefixPattern")) { |
| result = handleCompoundUnit1(parts, value); |
| } else if (parts.getElement(-1).equals("unitPattern")) { |
| result = handleFormatUnit(parts, value); |
| } else if (parts.getElement(-1).equals("perUnitPattern")) { |
| result = handleFormatPerUnit(parts, value); |
| } else if (parts.getElement(-2).equals("minimalPairs")) { |
| result = handleMinimalPairs(parts, value); |
| } else if (parts.getElement(-1).equals("durationUnitPattern")) { |
| result = handleDurationUnit(value); |
| } else if (parts.contains("intervalFormats")) { |
| result = handleIntervalFormats(parts, value); |
| } else if (parts.getElement(1).equals("delimiters")) { |
| result = handleDelimiters(parts, xpath, value); |
| } else if (parts.getElement(1).equals("listPatterns")) { |
| result = handleListPatterns(parts, value); |
| } else if (parts.getElement(2).equals("ellipsis")) { |
| result = handleEllipsis(parts.getAttributeValue(-1, "type"), value); |
| } else if (parts.getElement(-1).equals("monthPattern")) { |
| result = handleMonthPatterns(parts, value); |
| } else if (parts.getElement(-1).equals("appendItem")) { |
| result = handleAppendItems(parts, value); |
| } else if (parts.getElement(-1).equals("annotation")) { |
| result = handleAnnotationName(parts, value); |
| } else if (parts.getElement(-1).equals("characterLabel")) { |
| result = handleLabel(parts, value); |
| } else if (parts.getElement(-1).equals("characterLabelPattern")) { |
| result = handleLabelPattern(parts, value); |
| } else if (parts.getElement(1).equals("personNames")) { |
| result = handlePersonName(parts, value); |
| } |
| if (result != null) { |
| // if (!typeIsEnglish) { |
| // result = addTransliteration(result, value); |
| // } |
| result = finalizeBackground(result); |
| } |
| return result; |
| } |
| |
| /** |
| * Holds a map and an object that are relatively expensive to build, |
| * so we don't want to do that on each call. |
| * TODO clean up the synchronization model. |
| */ |
| private class PersonNamesCache implements ExampleCache.ClearableCache { |
| Map<PersonNameFormatter.SampleType, SimpleNameObject> sampleNames = null; |
| PersonNameFormatter personNameFormatter = null; |
| @Override |
| public void clear() { |
| sampleNames = null; |
| personNameFormatter = null; |
| } |
| Map<PersonNameFormatter.SampleType, SimpleNameObject> getSampleNames(CLDRFile cldrFile) { |
| synchronized (this) { |
| if (sampleNames == null) { |
| sampleNames = PersonNameFormatter.loadSampleNames(cldrFile); |
| } |
| return sampleNames; |
| } |
| } |
| PersonNameFormatter getPersonNameFormatter(CLDRFile cldrFile) { |
| synchronized (this) { |
| if (personNameFormatter == null) { |
| personNameFormatter = new PersonNameFormatter(cldrFile); |
| } |
| return personNameFormatter; |
| } |
| } |
| @Override |
| public String toString() { |
| return "[" + (sampleNames == null ? "" : Joiner.on('\n').join(sampleNames.entrySet())) |
| + ", " + (personNameFormatter == null ? "" : personNameFormatter.toString()) |
| + "]"; |
| } |
| } |
| |
| /** |
| * Register the cache, so that it gets cleared when any of the paths change |
| */ |
| PersonNamesCache personNamesCache = exCache.registerCache(new PersonNamesCache(), |
| "//ldml/personNames/sampleName[@item=\"*\"]/nameField[@type=\"*\"]", |
| "//ldml/personNames/initialPattern[@type=\"*\"]"); |
| |
| private String handlePersonName(XPathParts parts, String value) { |
| //ldml/personNames/personName[@order="givenFirst"][@length="long"][@usage="addressing"][@style="formal"]/namePattern => {prefix} {surname} |
| String debugState = "start"; |
| try { |
| FormatParameters formatParameters = new FormatParameters( |
| PersonNameFormatter.Order.from(parts.getAttributeValue(2, "order")), |
| PersonNameFormatter.Length.from(parts.getAttributeValue(2, "length")), |
| PersonNameFormatter.Usage.from(parts.getAttributeValue(2, "usage")), |
| PersonNameFormatter.Formality.from(parts.getAttributeValue(2, "formality"))); |
| |
| List<String> examples = null; |
| final CLDRFile cldrFile2 = getCldrFile(); |
| switch(parts.getElement(2)) { |
| case "nameOrderLocales": |
| examples = new ArrayList<>(); |
| for (String localeId : PersonNameFormatter.SPLIT_SPACE.split(value)) { |
| final String name = localeId.equals("und") ? "«any other»" : cldrFile2.getName(localeId); |
| examples.add(localeId + " = " + name); |
| } |
| break; |
| case "initialPattern": |
| return null; |
| case "sampleName": |
| return null; |
| case "personName": |
| examples = new ArrayList<>(); |
| Map<PersonNameFormatter.SampleType, SimpleNameObject> sampleNames = personNamesCache.getSampleNames(cldrFile2); |
| PersonNameFormatter personNameFormatter = personNamesCache.getPersonNameFormatter(cldrFile2); |
| |
| // We might need the alt, however: String alt = parts.getAttributeValue(-1, "alt"); |
| |
| for (NameObject sampleNameObject1 : sampleNames.values()) { |
| NamePattern namePattern = NamePattern.from(0, value); |
| debugState = "<NamePattern.from: " + namePattern; |
| final FallbackFormatter fallbackInfo = personNameFormatter.getFallbackInfo(); |
| debugState = "<getFallbackInfo: " + fallbackInfo; |
| String result = namePattern.format(sampleNameObject1, formatParameters, fallbackInfo); |
| debugState = "<namePattern.format: " + result; |
| examples.add(result); |
| } |
| break; |
| } |
| return formatExampleList(examples); |
| } catch (Exception e) { |
| StringBuffer stackTrace = null; |
| try (StringWriter sw = new StringWriter(); |
| PrintWriter p = new PrintWriter(sw)) { |
| e.printStackTrace(p); |
| stackTrace = sw.getBuffer(); |
| } catch (Exception e2) { |
| stackTrace = new StringBuffer("internal error"); |
| } |
| return "Internal error: " + e.getMessage() + "\n" + debugState + "\n" + stackTrace; |
| } |
| } |
| |
| private String handleLabelPattern(XPathParts parts, String value) { |
| switch (parts.getAttributeValue(-1, "type")) { |
| case "category-list": |
| List<String> examples = new ArrayList<>(); |
| CLDRFile cfile = getCldrFile(); |
| SimpleFormatter initialPattern = SimpleFormatter.compile(setBackground(value)); |
| String path = CLDRFile.getKey(CLDRFile.TERRITORY_NAME, "FR"); |
| String regionName = cfile.getStringValue(path); |
| String flagName = cfile.getStringValue("//ldml/characterLabels/characterLabel[@type=\"flag\"]"); |
| examples.add(invertBackground(EmojiConstants.getEmojiFromRegionCodes("FR") |
| + " ⇒ " + initialPattern.format(flagName, regionName))); |
| return formatExampleList(examples); |
| default: |
| return null; |
| } |
| } |
| |
| private String handleLabel(XPathParts parts, String value) { |
| // "//ldml/characterLabels/characterLabel[@type=\"" + typeAttributeValue + "\"]" |
| switch (parts.getAttributeValue(-1, "type")) { |
| case "flag": { |
| String value2 = backgroundStartSymbol + value + backgroundEndSymbol; |
| CLDRFile cfile = getCldrFile(); |
| List<String> examples = new ArrayList<>(); |
| SimpleFormatter initialPattern = SimpleFormatter.compile(cfile.getStringValue("//ldml/characterLabels/characterLabelPattern[@type=\"category-list\"]")); |
| addFlag(value2, "FR", cfile, initialPattern, examples); |
| addFlag(value2, "CN", cfile, initialPattern, examples); |
| addSubdivisionFlag(value2, "gbeng", initialPattern, examples); |
| addSubdivisionFlag(value2, "gbsct", initialPattern, examples); |
| addSubdivisionFlag(value2, "gbwls", initialPattern, examples); |
| return formatExampleList(examples); |
| } |
| case "keycap": { |
| String value2 = backgroundStartSymbol + value + backgroundEndSymbol; |
| List<String> examples = new ArrayList<>(); |
| CLDRFile cfile = getCldrFile(); |
| SimpleFormatter initialPattern = SimpleFormatter.compile(cfile.getStringValue("//ldml/characterLabels/characterLabelPattern[@type=\"category-list\"]")); |
| examples.add(invertBackground(initialPattern.format(value2, "1"))); |
| examples.add(invertBackground(initialPattern.format(value2, "10"))); |
| examples.add(invertBackground(initialPattern.format(value2, "#"))); |
| return formatExampleList(examples); |
| } |
| default: |
| return null; |
| } |
| } |
| |
| private void addFlag(String value2, String isoRegionCode, CLDRFile cfile, SimpleFormatter initialPattern, List<String> examples) { |
| String path = CLDRFile.getKey(CLDRFile.TERRITORY_NAME, isoRegionCode); |
| String regionName = cfile.getStringValue(path); |
| examples.add(invertBackground(EmojiConstants.getEmojiFromRegionCodes(isoRegionCode) |
| + " ⇒ " + initialPattern.format(value2, regionName))); |
| } |
| |
| private void addSubdivisionFlag(String value2, String isoSubdivisionCode, SimpleFormatter initialPattern, List<String> examples) { |
| String subdivisionName = subdivisionIdToName.get(isoSubdivisionCode); |
| if (subdivisionName == null) { |
| subdivisionName = isoSubdivisionCode; |
| } |
| examples.add(invertBackground(EmojiConstants.getEmojiFromSubdivisionCodes(isoSubdivisionCode) |
| + " ⇒ " + initialPattern.format(value2, subdivisionName))); |
| } |
| |
| private String handleAnnotationName(XPathParts parts, String value) { |
| //ldml/annotations/annotation[@cp="🦰"][@type="tts"] |
| // skip anything but the name |
| if (!"tts".equals(parts.getAttributeValue(-1, "type"))) { |
| return null; |
| } |
| String cp = parts.getAttributeValue(-1, "cp"); |
| if (cp == null || cp.isEmpty()) { |
| return null; |
| } |
| Set<String> examples = new LinkedHashSet<>(); |
| int first = cp.codePointAt(0); |
| switch(first) { |
| case 0x1F46A: // 👪 U+1F46A FAMILY |
| examples.add(formatGroup(value, "👨👩👧👦", "👨", "👩", "👧", "👦")); |
| examples.add(formatGroup(value, "👩👩👦", "👩", "👩", "👦")); |
| break; |
| case 0x1F48F: // 💏 U+1F48F KISS 👩👨 |
| examples.add(formatGroup(value, "👩❤️💋👨", "👩", "👨")); |
| examples.add(formatGroup(value, "👩❤️💋👩", "👩", "👩")); |
| break; |
| case 0x1F491: // 💑 U+1F491 COUPLE WITH HEART |
| examples.add(formatGroup(value, "👩❤️👨", "👩", "👨")); |
| examples.add(formatGroup(value, "👩❤️👩", "👩", "👩")); |
| break; |
| default: |
| boolean isSkin = EmojiConstants.MODIFIERS.contains(first); |
| if (isSkin || EmojiConstants.HAIR.contains(first)) { |
| String value2 = backgroundStartSymbol + value + backgroundEndSymbol; |
| CLDRFile cfile = getCldrFile(); |
| String skin = "🏽"; |
| String hair = "🦰"; |
| String skinName = getEmojiName(cfile, skin); |
| String hairName = getEmojiName(cfile, hair); |
| if (hairName == null) { |
| hair = "[missing]"; |
| } |
| SimpleFormatter initialPattern = SimpleFormatter.compile(cfile.getStringValue("//ldml/characterLabels/characterLabelPattern[@type=\"category-list\"]")); |
| SimpleFormatter listPattern = SimpleFormatter.compile(cfile.getStringValue("//ldml/listPatterns/listPattern[@type=\"unit-short\"]/listPatternPart[@type=\"2\"]")); |
| |
| hair = EmojiConstants.JOINER_STRING + hair; |
| formatPeople(cfile, first, isSkin, value2, "👩", skin, skinName, hair, hairName, initialPattern, listPattern, examples); |
| formatPeople(cfile, first, isSkin, value2, "👨", skin, skinName, hair, hairName, initialPattern, listPattern, examples); |
| } |
| break; |
| } |
| return formatExampleList(examples); |
| } |
| |
| private String getEmojiName(CLDRFile cfile, String skin) { |
| return cfile.getStringValue("//ldml/annotations/annotation[@cp=\"" + skin + "\"][@type=\"tts\"]"); |
| } |
| |
| //ldml/listPatterns/listPattern[@type="standard-short"]/listPatternPart[@type="2"] |
| private String formatGroup(String value, String sourceEmoji, String... components) { |
| CLDRFile cfile = getCldrFile(); |
| SimpleFormatter initialPattern = SimpleFormatter.compile(cfile.getStringValue("//ldml/characterLabels/characterLabelPattern[@type=\"category-list\"]")); |
| String value2 = backgroundEndSymbol + value + backgroundStartSymbol; |
| String[] names = new String[components.length]; |
| int i = 0; |
| for (String component : components) { |
| names[i++] = getEmojiName(cfile, component); |
| } |
| return backgroundStartSymbol + sourceEmoji + " ⇒ " + initialPattern.format(value2, |
| longListPatternExample(EmojiConstants.COMPOSED_NAME_LIST.getPath(), "n/a", "n/a2", names)); |
| } |
| |
| private void formatPeople(CLDRFile cfile, int first, boolean isSkin, String value2, String person, String skin, String skinName, |
| String hair, String hairName, SimpleFormatter initialPattern, SimpleFormatter listPattern, Collection<String> examples) { |
| String cp; |
| String personName = getEmojiName(cfile, person); |
| StringBuilder emoji = new StringBuilder(person).appendCodePoint(first); |
| cp = UTF16.valueOf(first); |
| cp = isSkin ? cp : EmojiConstants.JOINER_STRING + cp; |
| examples.add(person + cp + " ⇒ " + invertBackground(initialPattern.format(personName,value2))); |
| emoji.setLength(0); |
| emoji.append(personName); |
| if (isSkin) { |
| skinName = value2; |
| skin = cp; |
| } else { |
| hairName = value2; |
| hair = cp; |
| } |
| examples.add(person + skin + hair + " ⇒ " + invertBackground(listPattern.format(initialPattern.format(personName, skinName), hairName))); |
| } |
| |
| private String handleDayPeriod(XPathParts parts, String value) { |
| //ldml/dates/calendars/calendar[@type="gregorian"]/dayPeriods/dayPeriodContext[@type="format"]/dayPeriodWidth[@type="wide"]/dayPeriod[@type="morning1"] |
| //ldml/dates/calendars/calendar[@type="gregorian"]/dayPeriods/dayPeriodContext[@type="stand-alone"]/dayPeriodWidth[@type="wide"]/dayPeriod[@type="morning1"] |
| List<String> examples = new ArrayList<>(); |
| final String dayPeriodType = parts.getAttributeValue(5, "type"); |
| if (dayPeriodType == null) { |
| return null; // formerly happened for some "/alias" paths |
| } |
| org.unicode.cldr.util.DayPeriodInfo.Type aType = dayPeriodType.equals("format") ? DayPeriodInfo.Type.format : DayPeriodInfo.Type.selection; |
| DayPeriodInfo dayPeriodInfo = supplementalDataInfo.getDayPeriods(aType, cldrFile.getLocaleID()); |
| String periodString = parts.getAttributeValue(-1, "type"); |
| if (periodString == null) { |
| return null; // formerly happened for some "/alias" paths |
| } |
| DayPeriod dayPeriod = DayPeriod.valueOf(periodString); |
| String periods = dayPeriodInfo.toString(dayPeriod); |
| examples.add(periods); |
| if ("format".equals(dayPeriodType)) { |
| if (value == null) { |
| value = "�"; |
| } |
| R3<Integer, Integer, Boolean> info = dayPeriodInfo.getFirstDayPeriodInfo(dayPeriod); |
| if (info != null) { |
| int time = (((info.get0() + info.get1()) % DayPeriodInfo.DAY_LIMIT) / 2); |
| String timeFormatString = icuServiceBuilder.formatDayPeriod(time, backgroundStartSymbol + value + backgroundEndSymbol); |
| examples.add(invertBackground(timeFormatString)); |
| } |
| } |
| return formatExampleList(examples.toArray(new String[examples.size()])); |
| } |
| |
| private String handleMinimalPairs(XPathParts parts, String minimalPattern) { |
| List<String> examples = new ArrayList<>(); |
| |
| Output<String> output = new Output<>(); |
| String count = null; |
| String otherCount = null; |
| String sample = null; |
| String sampleBad = null; |
| String locale = getCldrFile().getLocaleID(); |
| |
| switch(parts.getElement(-1)) { |
| |
| case "ordinalMinimalPairs": //ldml/numbers/minimalPairs/ordinalMinimalPairs[@count="one"] |
| count = parts.getAttributeValue(-1, "ordinal"); |
| sample = bestMinimalPairSamples.getPluralOrOrdinalSample(PluralType.ordinal, count); // Pick a unit that exhibits the most variation |
| otherCount = getOtherCount(locale, PluralType.ordinal, count); |
| sampleBad = bestMinimalPairSamples.getPluralOrOrdinalSample(PluralType.ordinal, otherCount); // Pick a unit that exhibits the most variation |
| break; |
| |
| case "pluralMinimalPairs": //ldml/numbers/minimalPairs/pluralMinimalPairs[@count="one"] |
| count = parts.getAttributeValue(-1, "count"); |
| sample = bestMinimalPairSamples.getPluralOrOrdinalSample(PluralType.cardinal, count); // Pick a unit that exhibits the most variation |
| otherCount = getOtherCount(locale, PluralType.cardinal, count); |
| sampleBad = bestMinimalPairSamples.getPluralOrOrdinalSample(PluralType.cardinal, otherCount); // Pick a unit that exhibits the most variation |
| break; |
| |
| case "caseMinimalPairs": //ldml/numbers/minimalPairs/caseMinimalPairs[@case="accusative"] |
| String gCase = parts.getAttributeValue(-1, "case"); |
| sample = bestMinimalPairSamples.getBestUnitWithCase(gCase, output); // Pick a unit that exhibits the most variation |
| sampleBad = getOtherCase(bestMinimalPairSamples, locale, gCase, sample); |
| break; |
| |
| case "genderMinimalPairs": //ldml/numbers/minimalPairs/genderMinimalPairs[@gender="feminine"] |
| String gender = parts.getAttributeValue(-1, "gender"); |
| sample = bestMinimalPairSamples.getBestUnitWithGender(gender, output); |
| String otherGender = getOtherGender(locale, gender); |
| sampleBad = bestMinimalPairSamples.getBestUnitWithGender(otherGender, output); |
| break; |
| default: |
| return null; |
| } |
| String formattedUnit = format(minimalPattern, backgroundStartSymbol + sample + backgroundEndSymbol); |
| examples.add(formattedUnit); |
| if (sampleBad == null) { |
| sampleBad = "n/a"; |
| } |
| formattedUnit = format(minimalPattern, backgroundStartSymbol + sampleBad + backgroundEndSymbol); |
| examples.add(EXAMPLE_OF_INCORRECT + formattedUnit); |
| return formatExampleList(examples); |
| } |
| |
| private String getOtherGender(String locale, String gender) { |
| Collection<String> unitGenders = grammarInfo.get(GrammaticalTarget.nominal, GrammaticalFeature.grammaticalGender, GrammaticalScope.units); |
| for (String otherGender : unitGenders) { |
| if (!gender.equals(otherGender)) { |
| return otherGender; |
| } |
| } |
| return null; |
| } |
| |
| private String getOtherCase(BestMinimalPairSamples bestMinimalPairSamples2, String locale, String gCase, String sample) { |
| Collection<String> unitCases = grammarInfo.get(GrammaticalTarget.nominal, GrammaticalFeature.grammaticalCase, GrammaticalScope.units); |
| Output<String> output = new Output<>(); |
| for (String otherCase : unitCases) { |
| String sampleBad = bestMinimalPairSamples.getBestUnitWithCase(otherCase, output); // Pick a unit that exhibits the most variation |
| if (!sampleBad.equals(sample)) { |
| return sampleBad; |
| } |
| } |
| return null; |
| } |
| |
| private static String getOtherCount(String locale, PluralType ordinal, String count) { |
| String otherCount = null; |
| if (!Objects.equals(count, "other")) { |
| otherCount = "other"; |
| } else { |
| PluralInfo rules = SupplementalDataInfo.getInstance().getPlurals(ordinal, locale); |
| Set<String> counts = rules.getAdjustedCountStrings(); |
| for (String tryCount : counts) { |
| if (!tryCount.equals("other")) { |
| otherCount = tryCount; |
| break; |
| } |
| } |
| } |
| return otherCount; |
| } |
| |
| private UnitLength getUnitLength(XPathParts parts) { |
| return UnitLength.valueOf(parts.getAttributeValue(-3, "type").toUpperCase(Locale.ENGLISH)); |
| } |
| |
| private String handleFormatUnit(XPathParts parts, String unitPattern) { |
| // Sample: //ldml/units/unitLength[@type="long"]/unit[@type="duration-day"]/unitPattern[@count="one"][@case="accusative"] |
| |
| String count = parts.getAttributeValue(-1, "count"); |
| List<String> examples = new ArrayList<>(); |
| /* |
| * PluralRules.FixedDecimal is deprecated, but deprecated in ICU is |
| * also used to mark internal methods (which are OK for us to use in CLDR). |
| */ |
| @SuppressWarnings("deprecation") |
| DecimalQuantity amount = getBest(Count.valueOf(count)); |
| if (amount == null) { |
| return null; |
| } |
| DecimalFormat numberFormat = null; |
| String formattedAmount = null; |
| numberFormat = icuServiceBuilder.getNumberFormat(1); |
| formattedAmount = numberFormat.format(amount.toBigDecimal()); |
| examples.add(format(unitPattern, backgroundStartSymbol + formattedAmount + backgroundEndSymbol)); |
| |
| if (parts.getElement(-2).equals("unit")) { |
| String longUnitId = parts.getAttributeValue(-2, "type"); |
| final String shortUnitId = UNIT_CONVERTER.getShortId(longUnitId); |
| if (UnitConverter.HACK_SKIP_UNIT_NAMES.contains(shortUnitId)) { |
| return null; |
| } |
| if (unitPattern != null) { |
| String gCase = parts.getAttributeValue(-1, "case"); |
| if (gCase == null) { |
| gCase = GrammaticalFeature.grammaticalCase.getDefault(null); |
| } |
| Collection<String> unitCaseInfo = null; |
| if (grammarInfo != null) { |
| unitCaseInfo = grammarInfo.get(GrammaticalTarget.nominal, GrammaticalFeature.grammaticalCase, GrammaticalScope.units); |
| } |
| String minimalPattern = cldrFile.getStringValue("//ldml/numbers/minimalPairs/caseMinimalPairs[@case=\"" + gCase + "\"]"); |
| if (minimalPattern != null && numberFormat != null) { |
| String composed = format(unitPattern, backgroundStartSymbol + formattedAmount + backgroundEndSymbol); |
| examples.add(backgroundStartSymbol + format(minimalPattern, backgroundEndSymbol + composed + backgroundStartSymbol) + backgroundEndSymbol); |
| // get contrasting case |
| if (unitCaseInfo != null && !unitCaseInfo.isEmpty()) { |
| String constrastingCase = getConstrastingCase(unitPattern, gCase, unitCaseInfo, parts); |
| if (constrastingCase != null) { |
| minimalPattern = cldrFile.getStringValue("//ldml/numbers/minimalPairs/caseMinimalPairs[@case=\"" + constrastingCase + "\"]"); |
| composed = format(unitPattern, backgroundStartSymbol + formattedAmount + backgroundEndSymbol); |
| examples.add(EXAMPLE_OF_INCORRECT + backgroundStartSymbol + format(minimalPattern, backgroundEndSymbol + composed + backgroundStartSymbol) + backgroundEndSymbol); |
| } |
| } else { |
| examples.add(EXAMPLE_OF_CAUTION + "️No Case Minimal Pair available yet️"); |
| } |
| } |
| } |
| } |
| return formatExampleList(examples); |
| } |
| |
| private String getConstrastingCase(String unitPattern, String gCase, Collection<String> unitCaseInfo, XPathParts parts) { |
| for (String otherCase : unitCaseInfo) { |
| if (otherCase.equals(gCase)) { |
| continue; |
| } |
| parts.putAttributeValue(-1, "case", "nominative".equals(otherCase) ? null : otherCase); |
| String otherValue = cldrFile.getStringValue(parts.toString()); |
| if (otherValue != null && !otherValue.equals(unitPattern)) { |
| return otherCase; |
| } |
| } |
| return null; |
| } |
| |
| private String handleFormatPerUnit(XPathParts parts, String value) { |
| DecimalFormat numberFormat = icuServiceBuilder.getNumberFormat(1); |
| return format(value, backgroundStartSymbol + numberFormat.format(1) + backgroundEndSymbol); |
| } |
| |
| public String handleCompoundUnit(XPathParts parts) { |
| UnitLength unitLength = getUnitLength(parts); |
| String compoundType = parts.getAttributeValue(-2, "type"); |
| Count count = Count.valueOf(CldrUtility.ifNull(parts.getAttributeValue(-1, "count"), "other")); |
| return handleCompoundUnit(unitLength, compoundType, count); |
| } |
| |
| @SuppressWarnings("deprecation") |
| public String handleCompoundUnit(UnitLength unitLength, String compoundType, Count count) { |
| /** |
| * <units> |
| <unitLength type="long"> |
| <alias source="locale" path="../unitLength[@type='short']"/> |
| </unitLength> |
| <unitLength type="short"> |
| <compoundUnit type="per"> |
| <unitPattern count="other">{0}/{1}</unitPattern> |
| </compoundUnit> |
| |
| * <compoundUnit type="per"> |
| <unitPattern count="one">{0}/{1}</unitPattern> |
| <unitPattern count="other">{0}/{1}</unitPattern> |
| </compoundUnit> |
| <unit type="length-m"> |
| <unitPattern count="one">{0} meter</unitPattern> |
| <unitPattern count="other">{0} meters</unitPattern> |
| </unit> |
| |
| */ |
| |
| // we want to get a number that works for the count passed in. |
| DecimalQuantity amount = getBest(count); |
| if (amount == null) { |
| return "n/a"; |
| } |
| DecimalQuantity oneValue = DecimalQuantity_DualStorageBCD.fromExponentString("1"); |
| |
| String unit1mid; |
| String unit2mid; |
| switch (compoundType) { |
| default: |
| return "n/a"; |
| case "per": |
| unit1mid = getFormattedUnit("length-meter", unitLength, amount); |
| unit2mid = getFormattedUnit("duration-second", unitLength, oneValue, ""); |
| break; |
| case "times": |
| unit1mid = getFormattedUnit("force-newton", unitLength, oneValue, icuServiceBuilder.getNumberFormat(1).format(amount.toBigDecimal())); |
| unit2mid = getFormattedUnit("length-meter", unitLength, amount, ""); |
| break; |
| } |
| String unit1 = backgroundStartSymbol + unit1mid.trim() + backgroundEndSymbol; |
| String unit2 = backgroundStartSymbol + unit2mid.trim() + backgroundEndSymbol; |
| |
| String form = this.pluralInfo.getPluralRules().select(amount); |
| // we rebuild a path, because we may have changed it. |
| String perPath = makeCompoundUnitPath(unitLength, compoundType, "compoundUnitPattern"); |
| return format(getValueFromFormat(perPath, form), unit1, unit2); |
| } |
| |
| public String handleCompoundUnit1(XPathParts parts, String compoundPattern) { |
| UnitLength unitLength = getUnitLength(parts); |
| String pathCount = parts.getAttributeValue(-1, "count"); |
| if (pathCount == null) { |
| return handleCompoundUnit1Name(unitLength, compoundPattern); |
| } else { |
| return handleCompoundUnit1(unitLength, Count.valueOf(pathCount), compoundPattern); |
| } |
| } |
| |
| private String handleCompoundUnit1Name(UnitLength unitLength, String compoundPattern) { |
| String pathFormat = "//ldml/units/unitLength" + unitLength.typeString + "/unit[@type=\"{0}\"]/displayName"; |
| |
| String meterFormat = getValueFromFormat(pathFormat, "length-meter"); |
| |
| String modFormat = combinePrefix(meterFormat, compoundPattern, unitLength == UnitLength.LONG); |
| |
| return removeEmptyRuns(modFormat); |
| } |
| |
| public String handleCompoundUnit1(UnitLength unitLength, Count count, String compoundPattern) { |
| |
| // we want to get a number that works for the count passed in. |
| @SuppressWarnings("deprecation") |
| DecimalQuantity amount = getBest(count); |
| if (amount == null) { |
| return "n/a"; |
| } |
| DecimalFormat numberFormat = icuServiceBuilder.getNumberFormat(1); |
| |
| @SuppressWarnings("deprecation") |
| String form1 = this.pluralInfo.getPluralRules().select(amount); |
| |
| String pathFormat = "//ldml/units/unitLength" + unitLength.typeString |
| + "/unit[@type=\"{0}\"]/unitPattern[@count=\"{1}\"]"; |
| |
| // now pick up the meter pattern |
| |
| String meterFormat = getValueFromFormat(pathFormat, "length-meter", form1); |
| |
| // now combine them |
| |
| String modFormat = combinePrefix(meterFormat, compoundPattern, unitLength == UnitLength.LONG); |
| |
| return removeEmptyRuns(format(modFormat, numberFormat.format(amount.toBigDecimal()))); |
| } |
| |
| // TODO, pass in unitLength instead of last parameter, and do work in Units.combinePattern. |
| |
| public String combinePrefix(String unitFormat, String inCompoundPattern, boolean lowercaseUnitIfNoSpaceInCompound) { |
| // mark the part except for the {0} as foreground |
| String compoundPattern = backgroundEndSymbol |
| + inCompoundPattern.replace("{0}", backgroundStartSymbol + "{0}" + backgroundEndSymbol) |
| + backgroundStartSymbol; |
| |
| String modFormat = Units.combinePattern(unitFormat, compoundPattern, lowercaseUnitIfNoSpaceInCompound); |
| |
| return backgroundStartSymbol + modFormat + backgroundEndSymbol; |
| } |
| |
| //ldml/units/unitLength[@type="long"]/compoundUnit[@type="per"]/compoundUnitPattern |
| public String makeCompoundUnitPath(UnitLength unitLength, String compoundType, String patternType) { |
| return "//ldml/units/unitLength" + unitLength.typeString |
| + "/compoundUnit[@type=\"" + compoundType + "\"]" |
| + "/" + patternType; |
| } |
| |
| @SuppressWarnings("deprecation") |
| private DecimalQuantity getBest(Count count) { |
| DecimalQuantitySamples samples = pluralInfo.getPluralRules().getDecimalSamples(count.name(), SampleType.DECIMAL); |
| if (samples == null) { |
| samples = pluralInfo.getPluralRules().getDecimalSamples(count.name(), SampleType.INTEGER); |
| } |
| if (samples == null) { |
| return null; |
| } |
| Set<DecimalQuantitySamplesRange> samples2 = samples.getSamples(); |
| DecimalQuantitySamplesRange range = samples2.iterator().next(); |
| return range.end; |
| } |
| |
| private String handleMiscPatterns(XPathParts parts, String value) { |
| DecimalFormat numberFormat = icuServiceBuilder.getNumberFormat(0); |
| String start = backgroundStartSymbol + numberFormat.format(99) + backgroundEndSymbol; |
| if ("range".equals(parts.getAttributeValue(-1, "type"))) { |
| String end = backgroundStartSymbol + numberFormat.format(144) + backgroundEndSymbol; |
| return format(value, start, end); |
| } else { |
| return format(value, start); |
| } |
| } |
| |
| private String handleIntervalFormats(XPathParts parts, String value) { |
| if (!parts.getAttributeValue(3, "type").equals("gregorian")) { |
| return null; |
| } |
| if (parts.getElement(6).equals("intervalFormatFallback")) { |
| SimpleDateFormat dateFormat = new SimpleDateFormat(); |
| String fallbackFormat = invertBackground(setBackground(value)); |
| return format(fallbackFormat, dateFormat.format(FIRST_INTERVAL), |
| dateFormat.format(SECOND_INTERVAL.get("y"))); |
| } |
| String greatestDifference = parts.getAttributeValue(-1, "id"); |
| /* |
| * Choose an example interval suitable for the symbol. If testing years, use an interval |
| * of more than one year, and so forth. For the purpose of choosing the interval, |
| * "H" is equivalent to "h", and so forth; map to a symbol that occurs in SECOND_INTERVAL. |
| */ |
| if (greatestDifference.equals("H")) { // Hour [0-23] |
| greatestDifference = "h"; // Hour [1-12] |
| } else if (greatestDifference.equals("B") // flexible day periods |
| || greatestDifference.equals("b")) { // am, pm, noon, midnight |
| greatestDifference = "a"; // AM, PM |
| } |
| // intervalFormatFallback |
| // //ldml/dates/calendars/calendar[@type="gregorian"]/dateTimeFormats/intervalFormats/intervalFormatItem[@id="yMd"]/greatestDifference[@id="y"] |
| // find where to split the value |
| intervalFormat.setPattern(parts, value); |
| Date later = SECOND_INTERVAL.get(greatestDifference); |
| if (later == null) { |
| /* |
| * This may still happen for some less-frequently used symbols such as "Q" (Quarter), |
| * if they ever occur in the data. |
| * Reference: https://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table |
| * For now, such paths do not get examples. |
| */ |
| return null; |
| } |
| return intervalFormat.format(FIRST_INTERVAL, later); |
| } |
| |
| private String handleDelimiters(XPathParts parts, String xpath, String value) { |
| String lastElement = parts.getElement(-1); |
| final String[] elements = { |
| "quotationStart", "alternateQuotationStart", |
| "alternateQuotationEnd", "quotationEnd" }; |
| String[] quotes = new String[4]; |
| String baseXpath = xpath.substring(0, xpath.lastIndexOf('/')); |
| for (int i = 0; i < quotes.length; i++) { |
| String currElement = elements[i]; |
| if (lastElement.equals(currElement)) { |
| quotes[i] = backgroundStartSymbol + value + backgroundEndSymbol; |
| } else { |
| quotes[i] = cldrFile.getWinningValue(baseXpath + '/' + currElement); |
| } |
| } |
| String example = cldrFile |
| .getStringValue("//ldml/localeDisplayNames/types/type[@key=\"calendar\"][@type=\"gregorian\"]"); |
| // NOTE: the example provided here is partially in English because we don't |
| // have a translated conversational example in CLDR. |
| return invertBackground(format("{0}They said {1}" + example + "{2}.{3}", (Object[]) quotes)); |
| } |
| |
| private String handleListPatterns(XPathParts parts, String value) { |
| // listPatternType is either "duration" or null/other list |
| String listPatternType = parts.getAttributeValue(-2, "type"); |
| if (listPatternType == null || !listPatternType.contains("unit")) { |
| return handleRegularListPatterns(parts, value, ListTypeLength.from(listPatternType)); |
| } else { |
| return handleDurationListPatterns(parts, value, UnitLength.from(listPatternType)); |
| } |
| } |
| |
| private String handleRegularListPatterns(XPathParts parts, String value, ListTypeLength listTypeLength) { |
| String patternType = parts.getAttributeValue(-1, "type"); |
| if (patternType == null) { |
| return null; // formerly happened for some "/alias" paths |
| } |
| String pathFormat = "//ldml/localeDisplayNames/territories/territory[@type=\"{0}\"]"; |
| String territory1 = getValueFromFormat(pathFormat, "CH"); |
| String territory2 = getValueFromFormat(pathFormat, "JP"); |
| if (patternType.equals("2")) { |
| return invertBackground(format(setBackground(value), territory1, territory2)); |
| } |
| String territory3 = getValueFromFormat(pathFormat, "EG"); |
| String territory4 = getValueFromFormat(pathFormat, "CA"); |
| return longListPatternExample( |
| listTypeLength.getPath(), patternType, value, territory1, territory2, territory3, territory4); |
| } |
| |
| private String handleDurationListPatterns(XPathParts parts, String value, UnitLength unitWidth) { |
| String patternType = parts.getAttributeValue(-1, "type"); |
| if (patternType == null) { |
| return null; // formerly happened for some "/alias" paths |
| } |
| String duration1 = getFormattedUnit("duration-day", unitWidth, 4); |
| String duration2 = getFormattedUnit("duration-hour", unitWidth, 2); |
| if (patternType.equals("2")) { |
| return invertBackground(format(setBackground(value), duration1, duration2)); |
| } |
| String duration3 = getFormattedUnit("duration-minute", unitWidth, 37); |
| String duration4 = getFormattedUnit("duration-second", unitWidth, 23); |
| return longListPatternExample( |
| unitWidth.listTypeLength.getPath(), patternType, value, duration1, duration2, duration3, duration4); |
| } |
| |
| public enum UnitLength { |
| LONG(ListTypeLength.UNIT_WIDE), SHORT(ListTypeLength.UNIT_SHORT), NARROW(ListTypeLength.UNIT_NARROW); |
| final String typeString; |
| final ListTypeLength listTypeLength; |
| |
| UnitLength(ListTypeLength listTypeLength) { |
| typeString = "[@type=\"" + name().toLowerCase(Locale.ENGLISH) + "\"]"; |
| this.listTypeLength = listTypeLength; |
| } |
| |
| public static UnitLength from(String listPatternType) { |
| if (listPatternType.equals("unit")) { |
| return UnitLength.LONG; |
| } else if (listPatternType.equals("unit-narrow")) { |
| return UnitLength.NARROW; |
| } else if (listPatternType.equals("unit-short")) { |
| return UnitLength.SHORT; |
| } else { |
| throw new IllegalArgumentException(); |
| } |
| } |
| } |
| |
| @SuppressWarnings("deprecation") |
| private String getFormattedUnit(String unitType, UnitLength unitWidth, DecimalQuantity unitAmount) { |
| DecimalFormat numberFormat = icuServiceBuilder.getNumberFormat(1); |
| return getFormattedUnit(unitType, unitWidth, unitAmount, numberFormat.format(unitAmount.toBigDecimal())); |
| } |
| |
| @SuppressWarnings("deprecation") |
| private String getFormattedUnit(String unitType, UnitLength unitWidth, double unitAmount) { |
| return getFormattedUnit(unitType, unitWidth, new DecimalQuantity_DualStorageBCD(unitAmount)); |
| } |
| |
| @SuppressWarnings("deprecation") |
| private String getFormattedUnit(String unitType, UnitLength unitWidth, DecimalQuantity unitAmount, String formattedUnitAmount) { |
| String form = this.pluralInfo.getPluralRules().select(unitAmount); |
| String pathFormat = "//ldml/units/unitLength" + unitWidth.typeString |
| + "/unit[@type=\"{0}\"]/unitPattern[@count=\"{1}\"]"; |
| return format(getValueFromFormat(pathFormat, unitType, form), formattedUnitAmount); |
| } |
| |
| //ldml/listPatterns/listPattern/listPatternPart[@type="2"] — And |
| //ldml/listPatterns/listPattern[@type="standard-short"]/listPatternPart[@type="2"] Short And |
| //ldml/listPatterns/listPattern[@type="or"]/listPatternPart[@type="2"] or list |
| //ldml/listPatterns/listPattern[@type="unit"]/listPatternPart[@type="2"] |
| //ldml/listPatterns/listPattern[@type="unit-short"]/listPatternPart[@type="2"] |
| //ldml/listPatterns/listPattern[@type="unit-narrow"]/listPatternPart[@type="2"] |
| |
| private String longListPatternExample(String listPathFormat, String patternType, String value, String... items) { |
| String doublePattern = getPattern(listPathFormat, "2", patternType, value); |
| String startPattern = getPattern(listPathFormat, "start", patternType, value); |
| String middlePattern = getPattern(listPathFormat, "middle", patternType, value); |
| String endPattern = getPattern(listPathFormat, "end", patternType, value); |
| /* |
| * DateTimePatternGenerator.FormatParser is deprecated, but deprecated in ICU is |
| * also used to mark internal methods (which are OK for us to use in CLDR). |
| */ |
| @SuppressWarnings("deprecation") |
| ListFormatter listFormatter = new ListFormatter(doublePattern, startPattern, middlePattern, endPattern); |
| String example = listFormatter.format((Object[]) items); |
| return invertBackground(example); |
| } |
| |
| /** |
| * Helper method for handleListPatterns. Returns the pattern to be used for |
| * a specified pattern type. |
| * |
| * @param pathFormat |
| * @param pathPatternType |
| * @param valuePatternType |
| * @param value |
| * @return |
| */ |
| private String getPattern(String pathFormat, String pathPatternType, String valuePatternType, String value) { |
| return valuePatternType.equals(pathPatternType) ? setBackground(value) : getValueFromFormat(pathFormat, pathPatternType); |
| } |
| |
| private String getValueFromFormat(String format, Object... arguments) { |
| return cldrFile.getWinningValue(format(format, arguments)); |
| } |
| |
| public String handleEllipsis(String type, String value) { |
| String pathFormat = "//ldml/localeDisplayNames/territories/territory[@type=\"{0}\"]"; |
| // <ellipsis type="word-final">{0} …</ellipsis> |
| // <ellipsis type="word-initial">… {0}</ellipsis> |
| // <ellipsis type="word-medial">{0} … {1}</ellipsis> |
| String territory1 = getValueFromFormat(pathFormat, "CH"); |
| String territory2 = getValueFromFormat(pathFormat, "JP"); |
| // if it isn't a word, break in the middle |
| if (!type.contains("word")) { |
| territory1 = clip(territory1, 0, 1); |
| territory2 = clip(territory2, 1, 0); |
| } |
| if (type.contains("initial")) { |
| territory1 = territory2; |
| } |
| return invertBackground(format(setBackground(value), territory1, territory2)); |
| } |
| |
| public static String clip(String text, int clipStart, int clipEnd) { |
| BreakIterator bi = BreakIterator.getCharacterInstance(); |
| bi.setText(text); |
| for (int i = 0; i < clipStart; ++i) { |
| bi.next(); |
| } |
| int start = bi.current(); |
| bi.last(); |
| for (int i = 0; i < clipEnd; ++i) { |
| bi.previous(); |
| } |
| int end = bi.current(); |
| return start >= end ? text : text.substring(start, end); |
| } |
| |
| /** |
| * Handle miscellaneous calendar patterns. |
| * |
| * @param parts |
| * @param value |
| * @return |
| */ |
| private String handleMonthPatterns(XPathParts parts, String value) { |
| String calendar = parts.getAttributeValue(3, "type"); |
| String context = parts.getAttributeValue(5, "type"); |
| String month = "8"; |
| if (!context.equals("numeric")) { |
| String width = parts.getAttributeValue(6, "type"); |
| String xpath = "//ldml/dates/calendars/calendar[@type=\"{0}\"]/months/monthContext[@type=\"{1}\"]/monthWidth[@type=\"{2}\"]/month[@type=\"8\"]"; |
| month = getValueFromFormat(xpath, calendar, context, width); |
| } |
| return invertBackground(format(setBackground(value), month)); |
| } |
| |
| private String handleAppendItems(XPathParts parts, String value) { |
| String request = parts.getAttributeValue(-1, "request"); |
| if (!"Timezone".equals(request)) { |
| return null; |
| } |
| String calendar = parts.getAttributeValue(3, "type"); |
| |
| SimpleDateFormat sdf = icuServiceBuilder.getDateFormat(calendar, 0, DateFormat.MEDIUM, null); |
| String zone = cldrFile.getStringValue("//ldml/dates/timeZoneNames/gmtZeroFormat"); |
| String result = format(value, setBackground(sdf.format(DATE_SAMPLE)), setBackground(zone)); |
| return result; |
| } |
| |
| private class IntervalFormat { |
| @SuppressWarnings("deprecation") |
| DateTimePatternGenerator.FormatParser formatParser = new DateTimePatternGenerator.FormatParser(); |
| SimpleDateFormat firstFormat = new SimpleDateFormat(); |
| SimpleDateFormat secondFormat = new SimpleDateFormat(); |
| StringBuilder first = new StringBuilder(); |
| StringBuilder second = new StringBuilder(); |
| BitSet letters = new BitSet(); |
| |
| public String format(Date earlier, Date later) { |
| if (earlier == null || later == null) { |
| return null; |
| } |
| if (later.compareTo(earlier) < 0) { |
| /* |
| * Swap so earlier is earlier than later. |
| * This is necessary for "G" (Era) given the current FIRST_INTERVAL, SECOND_INTERVAL |
| */ |
| Date tmp = earlier; |
| earlier = later; |
| later = tmp; |
| } |
| return firstFormat.format(earlier) + secondFormat.format(later); |
| } |
| |
| @SuppressWarnings("deprecation") |
| public IntervalFormat setPattern(XPathParts parts, String pattern) { |
| if (formatParser == null || pattern == null) { |
| return this; |
| } |
| try { |
| formatParser.set(pattern); |
| } catch (NullPointerException e) { |
| /* |
| * This has been observed to occur, within ICU, for unknown reasons. |
| */ |
| System.err.println("Caught NullPointerException in IntervalFormat.setPattern, pattern = " + pattern); |
| e.printStackTrace(); |
| return null; |
| } |
| first.setLength(0); |
| second.setLength(0); |
| boolean doFirst = true; |
| letters.clear(); |
| |
| for (Object item : formatParser.getItems()) { |
| if (item instanceof DateTimePatternGenerator.VariableField) { |
| char c = item.toString().charAt(0); |
| if (letters.get(c)) { |
| doFirst = false; |
| } else { |
| letters.set(c); |
| } |
| if (doFirst) { |
| first.append(item); |
| } else { |
| second.append(item); |
| } |
| } else { |
| if (doFirst) { |
| first.append(formatParser.quoteLiteral((String) item)); |
| } else { |
| second.append(formatParser.quoteLiteral((String) item)); |
| } |
| } |
| } |
| String calendar = parts.findAttributeValue("calendar", "type"); |
| firstFormat = icuServiceBuilder.getDateFormat(calendar, first.toString()); |
| firstFormat.setTimeZone(GMT_ZONE_SAMPLE); |
| |
| secondFormat = icuServiceBuilder.getDateFormat(calendar, second.toString()); |
| secondFormat.setTimeZone(GMT_ZONE_SAMPLE); |
| return this; |
| } |
| } |
| |
| private String handleDurationUnit(String value) { |
| DateFormat df = this.icuServiceBuilder.getDateFormat("gregorian", value.replace('h', 'H')); |
| df.setTimeZone(TimeZone.GMT_ZONE); |
| long time = ((5 * 60 + 37) * 60 + 23) * 1000; |
| try { |
| return df.format(new Date(time)); |
| } catch (IllegalArgumentException e) { |
| // e.g., Illegal pattern character 'o' in "aɖabaƒoƒo m:ss" |
| return null; |
| } |
| } |
| |
| @SuppressWarnings("deprecation") |
| private String formatCountValue(String xpath, XPathParts parts, String value) { |
| if (!parts.containsAttribute("count")) { // no examples for items that don't format |
| return null; |
| } |
| final PluralInfo plurals = supplementalDataInfo.getPlurals(PluralType.cardinal, cldrFile.getLocaleID()); |
| PluralRules pluralRules = plurals.getPluralRules(); |
| |
| String unitType = parts.getAttributeValue(-2, "type"); |
| if (unitType == null) { |
| unitType = "USD"; // sample for currency pattern |
| } |
| final boolean isPattern = parts.contains("unitPattern"); |
| final boolean isCurrency = !parts.contains("units"); |
| |
| Count count = null; |
| final LinkedHashSet<DecimalQuantity> exampleCount = new LinkedHashSet<>(); |
| exampleCount.addAll(CURRENCY_SAMPLES); |
| String countString = parts.getAttributeValue(-1, "count"); |
| if (countString == null) { |
| // count = Count.one; |
| return null; |
| } else { |
| try { |
| count = Count.valueOf(countString); |
| } catch (Exception e) { |
| return null; // counts like 0 |
| } |
| } |
| |
| // we used to just get the samples for the given keyword, but that doesn't work well any more. |
| getStartEndSamples(pluralRules.getDecimalSamples(countString, SampleType.INTEGER), exampleCount); |
| getStartEndSamples(pluralRules.getDecimalSamples(countString, SampleType.DECIMAL), exampleCount); |
| |
| String result = ""; |
| DecimalFormat currencyFormat = icuServiceBuilder.getCurrencyFormat(unitType); |
| int decimalCount = currencyFormat.getMinimumFractionDigits(); |
| |
| // Unless/until DecimalQuantity overrides hashCode() or implements Comparable, we |
| // should use a concrete collection type for examplesSeen for which .contains() only |
| // relies on DecimalQuantity.equals() . The reason is that the default hashCode() |
| // implementation for DecimalQuantity may return false when .equals() returns true. |
| Collection<DecimalQuantity> examplesSeen = new ArrayList<>(); |
| |
| // we will cycle until we have (at most) two examples. |
| int maxCount = 2; |
| main: |
| // If we are a currency, we will try to see if we can set the decimals to match. |
| // but if nothing works, we will just use a plain sample. |
| for (int phase = 0; phase < 2; ++phase) { |
| for (DecimalQuantity example : exampleCount) { |
| // we have to first see whether we have a currency. If so, we have to see if the count works. |
| |
| if (isCurrency && phase == 0) { |
| DecimalQuantity_DualStorageBCD newExample = new DecimalQuantity_DualStorageBCD(); |
| newExample.copyFrom(example); |
| newExample.setMinFraction(decimalCount); |
| example = newExample; |
| } |
| // skip if we've done before (can happen because of the currency reset) |
| if (examplesSeen.contains(example)) { |
| continue; |
| } |
| examplesSeen.add(example); |
| // skip if the count isn't appropriate |
| if (!pluralRules.select(example).equals(count.toString())) { |
| continue; |
| } |
| |
| if (value == null) { |
| String fallbackPath = cldrFile.getCountPathWithFallback(xpath, count, true); |
| value = cldrFile.getStringValue(fallbackPath); |
| } |
| String resultItem; |
| |
| resultItem = formatCurrency(value, unitType, isPattern, isCurrency, count, example); |
| // now add to list |
| result = addExampleResult(resultItem, result); |
| if (isPattern) { |
| String territory = getDefaultTerritory(); |
| String currency = supplementalDataInfo.getDefaultCurrency(territory); |
| if (currency.equals(unitType)) { |
| currency = "EUR"; |
| if (currency.equals(unitType)) { |
| currency = "JAY"; |
| } |
| } |
| resultItem = formatCurrency(value, currency, isPattern, isCurrency, count, example); |
| // now add to list |
| result = addExampleResult(resultItem, result); |
| |
| } |
| if (--maxCount < 1) { |
| break main; |
| } |
| } |
| } |
| return result.isEmpty() ? null : result; |
| } |
| |
| @SuppressWarnings("deprecation") |
| static public void getStartEndSamples(DecimalQuantitySamples samples, Set<DecimalQuantity> target) { |
| if (samples != null) { |
| for (DecimalQuantitySamplesRange item : samples.getSamples()) { |
| target.add(item.start); |
| target.add(item.end); |
| } |
| } |
| } |
| |
| @SuppressWarnings("deprecation") |
| private String formatCurrency(String value, String unitType, final boolean isPattern, final boolean isCurrency, Count count, |
| DecimalQuantity example) { |
| String resultItem; |
| { |
| // If we have a pattern, get the unit from the count |
| // If we have a unit, get the pattern from the count |
| // English is special; both values are retrieved based on the count. |
| String unitPattern; |
| String unitName; |
| if (isPattern) { |
| // //ldml/numbers/currencies/currency[@type="USD"]/displayName |
| unitName = getUnitName(unitType, isCurrency, count); |
| unitPattern = typeIsEnglish ? getUnitPattern(unitType, isCurrency, count) : value; |
| } else { |
| unitPattern = getUnitPattern(unitType, isCurrency, count); |
| unitName = typeIsEnglish ? getUnitName(unitType, isCurrency, count) : value; |
| } |
| |
| if (isPattern) { |
| unitPattern = setBackground(unitPattern); |
| } else { |
| unitPattern = setBackgroundExceptMatch(unitPattern, PARAMETER_SKIP0); |
| } |
| |
| MessageFormat unitPatternFormat = new MessageFormat(unitPattern); |
| |
| // get the format for the currency |
| // TODO fix this for special currency overrides |
| |
| DecimalFormat unitDecimalFormat = icuServiceBuilder.getNumberFormat(1); // decimal |
| unitDecimalFormat.setMaximumFractionDigits((int) example.getPluralOperand(Operand.v)); |
| unitDecimalFormat.setMinimumFractionDigits((int) example.getPluralOperand(Operand.v)); |
| |
| String formattedNumber = unitDecimalFormat.format(example.toDouble()); |
| unitPatternFormat.setFormatByArgumentIndex(0, unitDecimalFormat); |
| resultItem = unitPattern.replace("{0}", formattedNumber).replace("{1}", unitName); |
| |
| if (isPattern) { |
| resultItem = invertBackground(resultItem); |
| } |
| } |
| return resultItem; |
| } |
| |
| private String addExampleResult(String resultItem, String resultToAddTo) { |
| return addExampleResult(resultItem, resultToAddTo, false); |
| } |
| |
| private String addExampleResult(String resultItem, String resultToAddTo, boolean showContexts) { |
| if (!showContexts) { |
| if (resultToAddTo.length() != 0) { |
| resultToAddTo += exampleSeparatorSymbol; |
| } |
| resultToAddTo += resultItem; |
| } else { |
| resultToAddTo += exampleStartAutoSymbol + resultItem + exampleEndSymbol; // example in neutral context |
| resultToAddTo += exampleStartRTLSymbol + resultItem + exampleEndSymbol; // example in RTL context |
| } |
| return resultToAddTo; |
| } |
| |
| private String getUnitPattern(String unitType, final boolean isCurrency, Count count) { |
| return cldrFile.getStringValue(isCurrency |
| ? "//ldml/numbers/currencyFormats/unitPattern" + countAttribute(count) |
| : "//ldml/units/unit[@type=\"" + unitType + "\"]/unitPattern" + countAttribute(count)); |
| } |
| |
| private String getUnitName(String unitType, final boolean isCurrency, Count count) { |
| return cldrFile.getStringValue(isCurrency |
| ? "//ldml/numbers/currencies/currency[@type=\"" + unitType + "\"]/displayName" + countAttribute(count) |
| : "//ldml/units/unit[@type=\"" + unitType + "\"]/unitPattern" + countAttribute(count)); |
| } |
| |
| public String countAttribute(Count count) { |
| return "[@count=\"" + count + "\"]"; |
| } |
| |
| private String handleNumberSymbol(XPathParts parts, String value) { |
| String symbolType = parts.getElement(-1); |
| String numberSystem = parts.getAttributeValue(2, "numberSystem"); // null if not present |
| int index = 1;// dec/percent/sci |
| double numberSample = NUMBER_SAMPLE; |
| String originalValue = cldrFile.getWinningValue(parts.toString()); |
| boolean isSuperscripting = false; |
| if (symbolType.equals("decimal") || symbolType.equals("group")) { |
| index = 1; |
| } else if (symbolType.equals("minusSign")) { |
| index = 1; |
| numberSample = -numberSample; |
| } else if (symbolType.equals("percentSign")) { |
| // For the perMille symbol, we reuse the percent example. |
| index = 2; |
| numberSample = 0.23; |
| } else if (symbolType.equals("perMille")) { |
| // For the perMille symbol, we reuse the percent example. |
| index = 2; |
| numberSample = 0.023; |
| originalValue = cldrFile.getWinningValue(parts.addRelative("../percentSign").toString()); |
| } else if (symbolType.equals("approximatelySign")) { |
| // Substitute the approximately symbol in for the minus sign. |
| index = 1; |
| numberSample = -numberSample; |
| originalValue = cldrFile.getWinningValue(parts.addRelative("../minusSign").toString()); |
| } else if (symbolType.equals("exponential") || symbolType.equals("plusSign")) { |
| index = 3; |
| } else if (symbolType.equals("superscriptingExponent")) { |
| index = 3; |
| isSuperscripting = true; |
| } else { |
| // We don't need examples for standalone symbols, i.e. infinity and nan. |
| // We don't have an example for the list symbol either. |
| return null; |
| } |
| DecimalFormat x = icuServiceBuilder.getNumberFormat(index, numberSystem); |
| String example; |
| String formattedValue; |
| if (isSuperscripting) { |
| DecimalFormatSymbols symbols = x.getDecimalFormatSymbols(); |
| char[] digits = symbols.getDigits(); |
| x.setDecimalFormatSymbols(symbols); |
| x.setNegativeSuffix(endSupSymbol + x.getNegativeSuffix()); |
| x.setPositiveSuffix(endSupSymbol + x.getPositiveSuffix()); |
| x.setExponentSignAlwaysShown(false); |
| |
| // Don't set the exponent directly because future examples for items |
| // will be affected as well. |
| originalValue = symbols.getExponentSeparator(); |
| formattedValue = backgroundEndSymbol + value + digits[1] + digits[0] + backgroundStartSymbol + startSupSymbol; |
| example = x.format(numberSample); |
| } else { |
| x.setExponentSignAlwaysShown(true); |
| formattedValue = backgroundEndSymbol + value + backgroundStartSymbol; |
| } |
| example = x.format(numberSample); |
| example = example.replace(originalValue, formattedValue); |
| return backgroundStartSymbol + example + backgroundEndSymbol; |
| } |
| |
| private String handleNumberingSystem(String value) { |
| NumberFormat x = icuServiceBuilder.getGenericNumberFormat(value); |
| x.setGroupingUsed(false); |
| return x.format(NUMBER_SAMPLE_WHOLE); |
| } |
| |
| private String handleTimeZoneName(XPathParts parts, String value) { |
| String result = null; |
| if (parts.contains("exemplarCity")) { |
| // ldml/dates/timeZoneNames/zone[@type="America/Los_Angeles"]/exemplarCity |
| String timezone = parts.getAttributeValue(3, "type"); |
| String countryCode = supplementalDataInfo.getZone_territory(timezone); |
| if (countryCode == null) { |
| if (value == null) { |
| result = timezone.substring(timezone.lastIndexOf('/') + 1).replace('_', ' '); |
| } else { |
| result = value; |
| } |
| return result; |
| } |
| if (countryCode.equals("001")) { |
| // GMT code, so format. |
| try { |
| String hourOffset = timezone.substring(timezone.contains("+") ? 8 : 7); |
| int hours = Integer.parseInt(hourOffset); |
| result = getGMTFormat(null, null, hours); |
| } catch (RuntimeException e) { |
| return result; // fail, skip |
| } |
| } else { |
| result = setBackground(cldrFile.getName(CLDRFile.TERRITORY_NAME, countryCode)); |
| } |
| } else if (parts.contains("zone")) { // {0} Time |
| result = value; |
| } else if (parts.contains("regionFormat")) { // {0} Time |
| result = format(value, setBackground(cldrFile.getName(CLDRFile.TERRITORY_NAME, "JP"))); |
| result = addExampleResult( |
| format(value, setBackground(cldrFile.getWinningValue(EXEMPLAR_CITY_LOS_ANGELES))), result); |
| } else if (parts.contains("fallbackFormat")) { // {1} ({0}) |
| String central = setBackground(cldrFile.getWinningValue("//ldml/dates/timeZoneNames/metazone[@type=\"America_Central\"]/long/generic")); |
| String cancun = setBackground(cldrFile.getWinningValue("//ldml/dates/timeZoneNames/zone[@type=\"America/Cancun\"]/exemplarCity")); |
| result = format(value, cancun, central); |
| } else if (parts.contains("gmtFormat")) { // GMT{0} |
| result = getGMTFormat(null, value, -8); |
| } else if (parts.contains("hourFormat")) { // +HH:mm;-HH:mm |
| result = getGMTFormat(value, null, -8); |
| } else if (parts.contains("metazone") && !parts.contains("commonlyUsed")) { // Metazone string |
| if (value != null && value.length() > 0) { |
| result = getMZTimeFormat() + " " + value; |
| } else { |
| // TODO check for value |
| if (parts.contains("generic")) { |
| String metazone_name = parts.getAttributeValue(3, "type"); |
| String timezone = supplementalDataInfo.getZoneForMetazoneByRegion(metazone_name, "001"); |
| String countryCode = supplementalDataInfo.getZone_territory(timezone); |
| String regionFormat = cldrFile.getWinningValue("//ldml/dates/timeZoneNames/regionFormat"); |
| String exemplarCity = cldrFile.getWinningValue("//ldml/dates/timeZoneNames/zone[@type=\"" |
| + timezone + "\"]/exemplarCity"); |
| if (exemplarCity == null) { |
| exemplarCity = timezone.substring(timezone.lastIndexOf('/') + 1).replace('_', ' '); |
| } |
| String countryName = cldrFile |
| .getWinningValue("//ldml/localeDisplayNames/territories/territory[@type=\"" + countryCode |
| + "\"]"); |
| result = setBackground(getMZTimeFormat() + " " + |
| format(regionFormat, countryName)); |
| } else { |
| String gmtFormat = cldrFile.getWinningValue("//ldml/dates/timeZoneNames/gmtFormat"); |
| String hourFormat = cldrFile.getWinningValue("//ldml/dates/timeZoneNames/hourFormat"); |
| String metazone_name = parts.getAttributeValue(3, "type"); |
| String tz_string = supplementalDataInfo.getZoneForMetazoneByRegion(metazone_name, "001"); |
| TimeZone currentZone = TimeZone.getTimeZone(tz_string); |
| int tzOffset = currentZone.getRawOffset(); |
| if (parts.contains("daylight")) { |
| tzOffset += currentZone.getDSTSavings(); |
| } |
| int MILLIS_PER_MINUTE = 1000 * 60; |
| int MILLIS_PER_HOUR = MILLIS_PER_MINUTE * 60; |
| int tm_hrs = tzOffset / MILLIS_PER_HOUR; |
| int tm_mins = (tzOffset % MILLIS_PER_HOUR) / 60000; // millis per minute |
| result = setBackground(getMZTimeFormat() + " " |
| + getGMTFormat(hourFormat, gmtFormat, tm_hrs, tm_mins)); |
| } |
| } |
| } |
| return result; |
| } |
| |
| @SuppressWarnings("deprecation") |
| private String handleDateFormatItem(String xpath, String value, boolean showContexts) { |
| // Get here if parts contains "calendar" and either of "pattern", "dateFormatItem" |
| |
| String fullpath = cldrFile.getFullXPath(xpath); |
| XPathParts parts = XPathParts.getFrozenInstance(fullpath); |
| String calendar = parts.findAttributeValue("calendar", "type"); |
| |
| if (parts.contains("dateTimeFormat")) { // date-time combining patterns |
| String dateFormatXPath = cldrFile.getWinningPath(xpath.replaceAll("dateTimeFormat", "dateFormat").replaceAll("atTime", "standard")); |
| String timeFormatXPath = cldrFile.getWinningPath(xpath.replaceAll("dateTimeFormat", "timeFormat").replaceAll("atTime", "standard")); |
| String dateFormatValue = cldrFile.getWinningValue(dateFormatXPath); |
| String timeFormatValue = cldrFile.getWinningValue(timeFormatXPath); |
| parts = XPathParts.getFrozenInstance(cldrFile.getFullXPath(dateFormatXPath)); |
| String dateNumbersOverride = parts.findAttributeValue("pattern", "numbers"); |
| parts = XPathParts.getFrozenInstance(cldrFile.getFullXPath(timeFormatXPath)); |
| String timeNumbersOverride = parts.findAttributeValue("pattern", "numbers"); |
| SimpleDateFormat df = icuServiceBuilder.getDateFormat(calendar, dateFormatValue, dateNumbersOverride); |
| SimpleDateFormat tf = icuServiceBuilder.getDateFormat(calendar, timeFormatValue, timeNumbersOverride); |
| df.setTimeZone(ZONE_SAMPLE); |
| tf.setTimeZone(ZONE_SAMPLE); |
| String dfResult = "'" + df.format(DATE_SAMPLE) + "'"; |
| String tfResult = "'" + tf.format(DATE_SAMPLE) + "'"; |
| SimpleDateFormat dtf = icuServiceBuilder.getDateFormat(calendar, |
| MessageFormat.format(value, (Object[]) new String[] { setBackground(tfResult), setBackground(dfResult) })); |
| return dtf.format(DATE_SAMPLE); |
| } else { |
| String id = parts.findAttributeValue("dateFormatItem", "id"); |
| if ("NEW".equals(id) || value == null) { |
| return startItalicSymbol + "n/a" + endItalicSymbol; |
| } else { |
| String numbersOverride = parts.findAttributeValue("pattern", "numbers"); |
| SimpleDateFormat sdf = icuServiceBuilder.getDateFormat(calendar, value, numbersOverride); |
| sdf.setTimeZone(ZONE_SAMPLE); |
| String defaultNumberingSystem = cldrFile.getWinningValue("//ldml/numbers/defaultNumberingSystem"); |
| String timeSeparator = cldrFile.getWinningValue("//ldml/numbers/symbols[@numberSystem='" + defaultNumberingSystem + "']/timeSeparator"); |
| DateFormatSymbols dfs = sdf.getDateFormatSymbols(); |
| dfs.setTimeSeparatorString(timeSeparator); |
| sdf.setDateFormatSymbols(dfs); |
| if (id == null || id.indexOf('B') < 0) { |
| // Standard date/time format, or availableFormat without dayPeriod |
| if (value.indexOf("MMM") >= 0 || value.indexOf("LLL") >= 0) { |
| // alpha month, do not need context examples |
| return sdf.format(DATE_SAMPLE); |
| } else { |
| // Use contextExamples if showContexts T |
| String example = showContexts? exampleStartHeaderSymbol + contextheader + exampleEndSymbol : ""; |
| example = addExampleResult(sdf.format(DATE_SAMPLE), example, showContexts); |
| return example; |
| } |
| } else { |
| List<String> examples = new ArrayList<>(); |
| examples.add(sdf.format(DATE_SAMPLE3)); |
| examples.add(sdf.format(DATE_SAMPLE)); |
| examples.add(sdf.format(DATE_SAMPLE4)); |
| return formatExampleList(examples.toArray(new String[examples.size()])); |
| } |
| } |
| } |
| } |
| |
| // Simple check whether the currency symbol has letters on one or both sides |
| private boolean symbolIsLetters(String currencySymbol, boolean onBothSides) { |
| int len = currencySymbol.length(); |
| if (len == 0) { |
| return false; |
| } |
| int limitChar = currencySymbol.codePointAt(0); |
| if (UCharacter.isLetter(limitChar)) { |
| if (!onBothSides) { |
| return true; |
| } |
| } else if (onBothSides) { |
| return false; |
| } |
| if (len > 1) { |
| limitChar = currencySymbol.codePointAt(len - 1); |
| if (UCharacter.isLetter(limitChar)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Creates examples for currency formats. |
| * |
| * @param value |
| * @return |
| */ |
| private String handleCurrencyFormat(XPathParts parts, String value, boolean showContexts) { |
| |
| String example = showContexts? exampleStartHeaderSymbol + contextheader + exampleEndSymbol : ""; |
| String territory = getDefaultTerritory(); |
| |
| String currency = supplementalDataInfo.getDefaultCurrency(territory); |
| String checkPath = "//ldml/numbers/currencies/currency[@type=\"" + currency + "\"]/symbol"; |
| String currencySymbol = cldrFile.getWinningValue(checkPath); |
| String altValue = parts.getAttributeValue(-1, "alt"); |
| boolean altAlpha = (altValue != null && altValue.equals("alphaNextToNumber")); |
| if (altAlpha && !symbolIsLetters(currencySymbol, true)) { |
| // If this example is for alt="alphaNextToNumber" and the default currency symbol |
| // does not have letters on both sides, need to use a fully alphabetic one. |
| currencySymbol = currency; |
| } |
| |
| String numberSystem = parts.getAttributeValue(2, "numberSystem"); // null if not present |
| |
| DecimalFormat df = icuServiceBuilder.getCurrencyFormat(currency, currencySymbol, numberSystem); |
| df.applyPattern(value); |
| |
| String countValue = parts.getAttributeValue(-1, "count"); |
| if (countValue != null) { |
| return formatCountDecimal(df, countValue); |
| } |
| |
| double sampleAmount = 1295.00; |
| example = addExampleResult(formatNumber(df, sampleAmount), example, showContexts); |
| example = addExampleResult(formatNumber(df, -sampleAmount), example, showContexts); |
| |
| if (showContexts && !altAlpha && countValue == null) { |
| // If this example is not for alt="alphaNextToNumber", then if the currency symbol |
| // above has letters (strong dir) add another example with non-letter symbol |
| // (weak or neutral), or vice versa |
| if (symbolIsLetters(currencySymbol, false)) { |
| currency = "EUR"; |
| checkPath = "//ldml/numbers/currencies/currency[@type=\"" + currency + "\"]/symbol"; |
| currencySymbol = cldrFile.getWinningValue(checkPath); |
| } else { |
| currencySymbol = currency; |
| } |
| df = icuServiceBuilder.getCurrencyFormat(currency, currencySymbol, numberSystem); |
| df.applyPattern(value); |
| example = addExampleResult(formatNumber(df, sampleAmount), example, showContexts); |
| example = addExampleResult(formatNumber(df, -sampleAmount), example, showContexts); |
| } |
| |
| return example; |
| } |
| |
| private String getDefaultTerritory() { |
| CLDRLocale loc; |
| String territory = "US"; |
| if (!typeIsEnglish) { |
| loc = CLDRLocale.getInstance(cldrFile.getLocaleID()); |
| territory = loc.getCountry(); |
| if (territory == null || territory.length() == 0) { |
| loc = supplementalDataInfo.getDefaultContentFromBase(loc); |
| if (loc != null) { |
| territory = loc.getCountry(); |
| if (territory.equals("001") && loc.getLanguage().equals("ar")) { |
| territory = "EG"; // Use Egypt as territory for examples in ar locale, since its default content is ar_001. |
| } |
| } |
| } |
| if (territory == null || territory.length() == 0) { |
| territory = "US"; |
| } |
| } |
| return territory; |
| } |
| |
| /** |
| * Creates examples for decimal formats. |
| * |
| * @param value |
| * @return |
| */ |
| private String handleDecimalFormat(XPathParts parts, String value, boolean showContexts) { |
| String example = showContexts? exampleStartHeaderSymbol + contextheader + exampleEndSymbol : ""; |
| String numberSystem = parts.getAttributeValue(2, "numberSystem"); // null if not present |
| DecimalFormat numberFormat = icuServiceBuilder.getNumberFormat(value, numberSystem); |
| String countValue = parts.getAttributeValue(-1, "count"); |
| if (countValue != null) { |
| return formatCountDecimal(numberFormat, countValue); |
| } |
| |
| double sampleNum1 = 5.43; |
| double sampleNum2 = NUMBER_SAMPLE; |
| if (parts.getElement(4).equals("percentFormat")) { |
| sampleNum1 = 0.0543; |
| } |
| example = addExampleResult(formatNumber(numberFormat, sampleNum1), example, showContexts); |
| example = addExampleResult(formatNumber(numberFormat, sampleNum2), example, showContexts); |
| // have positive and negative |
| example = addExampleResult(formatNumber(numberFormat, -sampleNum2), example, showContexts); |
| return example; |
| } |
| |
| private String formatCountDecimal(DecimalFormat numberFormat, String countValue) { |
| Count count; |
| try { |
| count = Count.valueOf(countValue); |
| } catch (Exception e) { |
| String locale = getCldrFile().getLocaleID(); |
| PluralInfo pluralInfo = supplementalDataInfo.getPlurals(locale); |
| count = pluralInfo.getCount(DecimalQuantity_DualStorageBCD.fromExponentString(countValue)); |
| } |
| Double numberSample = getExampleForPattern(numberFormat, count); |
| if (numberSample == null) { |
| // Ideally, we would suppress the value in the survey tool. |
| // However, until we switch over to the ICU samples, we are not guaranteed |
| // that "no samples" means "can't occur". So we manufacture something. |
| int digits = numberFormat.getMinimumIntegerDigits(); |
| numberSample = (double) Math.round(1.2345678901234 * Math.pow(10, digits - 1)); |
| } |
| String temp = String.valueOf(numberSample); |
| int fractionLength = temp.endsWith(".0") ? 0 : temp.length() - temp.indexOf('.') - 1; |
| if (fractionLength != numberFormat.getMaximumFractionDigits()) { |
| numberFormat = (DecimalFormat) numberFormat.clone(); // for safety |
| numberFormat.setMinimumFractionDigits(fractionLength); |
| numberFormat.setMaximumFractionDigits(fractionLength); |
| } |
| return formatNumber(numberFormat, numberSample); |
| } |
| |
| private String formatNumber(DecimalFormat format, double value) { |
| String example = format.format(value); |
| return setBackgroundOnMatch(example, ALL_DIGITS); |
| } |
| |
| /** |
| * Calculates a numerical example to use for the specified pattern using |
| * brute force (there should be a more elegant way to do this). |
| * |
| * @param format |
| * @param count |
| * @return |
| */ |
| private Double getExampleForPattern(DecimalFormat format, Count count) { |
| if (patternExamples == null) { |
| patternExamples = PluralSamples.getInstance(cldrFile.getLocaleID()); |
| } |
| int numDigits = format.getMinimumIntegerDigits(); |
| Map<Count, Double> samples = patternExamples.getSamples(numDigits); |
| if (samples == null) { |
| return null; |
| } |
| return samples.get(count); |
| } |
| |
| private String handleCurrency(String xpath, XPathParts parts, String value) { |
| String currency = parts.getAttributeValue(-2, "type"); |
| String fullPath = cldrFile.getFullXPath(xpath, false); |
| if (parts.contains("symbol")) { |
| if (fullPath != null && fullPath.contains("[@choice=\"true\"]")) { |
| ChoiceFormat cf = new ChoiceFormat(value); |
| value = cf.format(NUMBER_SAMPLE); |
| } |
| String result; |
| if (value == null) { |
| throw new NullPointerException( |
| cldrFile.getSourceLocation(fullPath) + |
| ": " + cldrFile.getLocaleID()+ ": " + |
| ": Error: no currency symbol for " + currency); |
| } |
| DecimalFormat x = icuServiceBuilder.getCurrencyFormat(currency, value); |
| result = x.format(NUMBER_SAMPLE); |
| result = setBackground(result).replace(value, backgroundEndSymbol + value + backgroundStartSymbol); |
| return result; |
| } else if (parts.contains("displayName")) { |
| return formatCountValue(xpath, parts, value); |
| } |
| return null; |
| } |
| |
| private String handleDateRangePattern(String value) { |
| String result; |
| SimpleDateFormat dateFormat = icuServiceBuilder.getDateFormat("gregorian", 2, 0); |
| result = format(value, setBackground(dateFormat.format(DATE_SAMPLE)), |
| setBackground(dateFormat.format(DATE_SAMPLE2))); |
| return result; |
| } |
| |
| /** |
| * @param elementToOverride the element that is to be overridden |
| * @param element the overriding element |
| * @param value the value to override element with |
| * @return |
| */ |
| private String getLocaleDisplayPattern(String elementToOverride, String element, String value) { |
| final String localeDisplayPatternPath = "//ldml/localeDisplayNames/localeDisplayPattern/"; |
| if (elementToOverride.equals(element)) { |
| return value; |
| } else { |
| return cldrFile.getWinningValue(localeDisplayPatternPath + elementToOverride); |
| } |
| } |
| |
| private String handleDisplayNames(String xpath, XPathParts parts, String value) { |
| String result = null; |
| if (parts.contains("codePatterns")) { |
| //ldml/localeDisplayNames/codePatterns/codePattern[@type="language"] |
| //ldml/localeDisplayNames/codePatterns/codePattern[@type="script"] |
| //ldml/localeDisplayNames/codePatterns/codePattern[@type="territory"] |
| String type = parts.getAttributeValue(-1, "type"); |
| result = format(value, setBackground( |
| type.equals("language") ? "ace" |
| : type.equals("script") ? "Avst" |
| : type.equals("territory") ? "057" : "CODE")); |
| } else if (parts.contains("localeDisplayPattern")) { |
| //ldml/localeDisplayNames/localeDisplayPattern/localePattern |
| //ldml/localeDisplayNames/localeDisplayPattern/localeSeparator |
| //ldml/localeDisplayNames/localeDisplayPattern/localeKeyTypePattern |
| String element = parts.getElement(-1); |
| value = setBackground(value); |
| String localeKeyTypePattern = getLocaleDisplayPattern("localeKeyTypePattern", element, value); |
| String localePattern = getLocaleDisplayPattern("localePattern", element, value); |
| String localeSeparator = getLocaleDisplayPattern("localeSeparator", element, value); |
| |
| List<String> locales = new ArrayList<>(); |
| if (element.equals("localePattern")) { |
| locales.add("uz-AF"); |
| } |
| locales.add(element.equals("localeKeyTypePattern") ? "uz-Arab-u-tz-etadd" : "uz-Arab-AF"); |
| locales.add("uz-Arab-AF-u-tz-etadd-nu-arab"); |
| String[] examples = new String[locales.size()]; |
| for (int i = 0; i < locales.size(); i++) { |
| examples[i] = invertBackground(cldrFile.getName(locales.get(i), false, |
| localeKeyTypePattern, localePattern, localeSeparator)); |
| } |
| result = formatExampleList(examples); |
| } else if (parts.contains("languages") || parts.contains("scripts") || parts.contains("territories")) { |
| //ldml/localeDisplayNames/languages/language[@type="ar"] |
| //ldml/localeDisplayNames/scripts/script[@type="Arab"] |
| //ldml/localeDisplayNames/territories/territory[@type="CA"] |
| String type = parts.getAttributeValue(-1, "type"); |
| if (type.contains("_")) { |
| if (value != null && !value.equals(type)) { |
| result = value; |
| } else { |
| result = cldrFile.getBaileyValue(xpath, null, null); |
| } |
| } else { |
| value = setBackground(value); |
| List<String> examples = new ArrayList<>(); |
| String nameType = parts.getElement(3); |
| |
| Map<String, String> likely = supplementalDataInfo.getLikelySubtags(); |
| String alt = parts.getAttributeValue(-1, "alt"); |
| boolean isStandAloneValue = "stand-alone".equals(alt); |
| if (!isStandAloneValue) { |
| // only do this if the value is not a stand-alone form |
| String tag = "language".equals(nameType) ? type : "und_" + type; |
| String max = LikelySubtags.maximize(tag, likely); |
| if (max == null) { |
| return null; |
| } |
| LanguageTagParser ltp = new LanguageTagParser().set(max); |
| String languageName = null; |
| String scriptName = null; |
| String territoryName = null; |
| if (nameType.equals("language")) { |
| languageName = value; |
| } else if (nameType.equals("script")) { |
| scriptName = value; |
| } else { |
| territoryName = value; |
| } |
| if (languageName == null) { |
| languageName = cldrFile.getStringValueWithBailey(CLDRFile.getKey(CLDRFile.LANGUAGE_NAME, ltp.getLanguage())); |
| if (languageName == null) { |
| languageName = cldrFile.getStringValueWithBailey(CLDRFile.getKey(CLDRFile.LANGUAGE_NAME, "en")); |
| } |
| if (languageName == null) { |
| languageName = ltp.getLanguage(); |
| } |
| } |
| if (scriptName == null) { |
| scriptName = cldrFile.getStringValueWithBailey(CLDRFile.getKey(CLDRFile.SCRIPT_NAME, ltp.getScript())); |
| if (scriptName == null) { |
| scriptName = cldrFile.getStringValueWithBailey(CLDRFile.getKey(CLDRFile.SCRIPT_NAME, "Latn")); |
| } |
| if (scriptName == null) { |
| scriptName = ltp.getScript(); |
| } |
| } |
| if (territoryName == null) { |
| territoryName = cldrFile.getStringValueWithBailey(CLDRFile.getKey(CLDRFile.TERRITORY_NAME, ltp.getRegion())); |
| if (territoryName == null) { |
| territoryName = cldrFile.getStringValueWithBailey(CLDRFile.getKey(CLDRFile.TERRITORY_NAME, "US")); |
| } |
| if (territoryName == null) { |
| territoryName = ltp.getRegion(); |
| } |
| } |
| languageName = languageName.replace('(', '[').replace(')', ']').replace('(', '[').replace(')', ']'); |
| scriptName = scriptName.replace('(', '[').replace(')', ']').replace('(', '[').replace(')', ']'); |
| territoryName = territoryName.replace('(', '[').replace(')', ']').replace('(', '[').replace(')', ']'); |
| |
| String localePattern = cldrFile.getStringValueWithBailey("//ldml/localeDisplayNames/localeDisplayPattern/localePattern"); |
| String localeSeparator = cldrFile.getStringValueWithBailey("//ldml/localeDisplayNames/localeDisplayPattern/localeSeparator"); |
| String scriptTerritory = format(localeSeparator, scriptName, territoryName); |
| if (!nameType.equals("script")) { |
| examples.add(invertBackground(format(localePattern, languageName, territoryName))); |
| } |
| if (!nameType.equals("territory")) { |
| examples.add(invertBackground(format(localePattern, languageName, scriptName))); |
| } |
| examples.add(invertBackground(format(localePattern, languageName, scriptTerritory))); |
| } |
| Output<String> pathWhereFound = null; |
| if (isStandAloneValue |
| || cldrFile.getStringValueWithBailey(xpath + ALT_STAND_ALONE, pathWhereFound = new Output<>(), null) == null |
| || !pathWhereFound.value.contains(ALT_STAND_ALONE)) { |
| // only do this if either it is a stand-alone form, |
| // or it isn't and there is no separate stand-alone form |
| // the extra check after the == null is to make sure that we don't have sideways inheritance |
| String codePattern = cldrFile.getStringValueWithBailey("//ldml/localeDisplayNames/codePatterns/codePattern[@type=\"" + nameType + "\"]"); |
| examples.add(invertBackground(format(codePattern, value))); |
| } |
| result = formatExampleList(examples.toArray(new String[examples.size()])); |
| } |
| } |
| return result; |
| } |
| |
| private String formatExampleList(String[] examples) { |
| String result = examples[0]; |
| for (int i = 1, len = examples.length; i < len; i++) { |
| result = addExampleResult(examples[i], result); |
| } |
| return result; |
| } |
| |
| /** |
| * Return examples formatted as string, with null returned for null or empty examples. |
| * @param examples |
| * @return |
| */ |
| private String formatExampleList(Collection<String> examples) { |
| if (examples == null || examples.isEmpty()) { |
| return null; |
| } |
| String result = ""; |
| boolean first = true; |
| for (String example : examples) { |
| if (first) { |
| result = example; |
| first = false; |
| } else { |
| result = addExampleResult(example, result); |
| } |
| } |
| return result; |
| } |
| |
| public static String format(String format, Object... objects) { |
| if (format == null) return null; |
| return MessageFormat.format(format, objects); |
| } |
| |
| public static final String unchainException(Exception e) { |
| String stackStr = "[unknown stack]<br>"; |
| try { |
| StringWriter asString = new StringWriter(); |
| e.printStackTrace(new PrintWriter(asString)); |
| stackStr = "<pre>" + asString.toString() + "</pre>"; |
| } catch (Throwable tt) { |
| // ... |
| } |
| return stackStr; |
| } |
| |
| /** |
| * Put a background on an item, skipping enclosed patterns. |
| * @param sampleTerritory |
| * @return |
| */ |
| private String setBackground(String inputPattern) { |
| if (inputPattern == null) { |
| return "?"; |
| } |
| Matcher m = PARAMETER.matcher(inputPattern); |
| return backgroundStartSymbol + m.replaceAll(backgroundEndSymbol + "$1" + backgroundStartSymbol) |
| + backgroundEndSymbol; |
| } |
| |
| /** |
| * Put a background on an item, skipping enclosed patterns, except for {0} |
| * @param patternToEmbed |
| * @param sampleTerritory |
| * @return |
| */ |
| private String setBackgroundExceptMatch(String input, Pattern patternToEmbed) { |
| Matcher m = patternToEmbed.matcher(input); |
| return backgroundStartSymbol + m.replaceAll(backgroundEndSymbol + "$1" + backgroundStartSymbol) |
| + backgroundEndSymbol; |
| } |
| |
| /** |
| * Put a background on an item, skipping enclosed patterns, except for {0} |
| * |
| * @param patternToEmbed |
| * TODO |
| * @param sampleTerritory |
| * |
| * @return |
| */ |
| private String setBackgroundOnMatch(String inputPattern, Pattern patternToEmbed) { |
| Matcher m = patternToEmbed.matcher(inputPattern); |
| return m.replaceAll(backgroundStartSymbol + "$1" + backgroundEndSymbol); |
| } |
| |
| /** |
| * This adds the transliteration of a result in case it has one (i.e. sr_Cyrl -> sr_Latn). |
| * |
| * @param input |
| * string with special characters from setBackground. |
| * @param value |
| * value to be transliterated |
| * @return string with attached transliteration if there is one. |
| */ |
| private String addTransliteration(String input, String value) { |
| if (value == null) { |
| return input; |
| } |
| for (LocaleTransform localeTransform : LocaleTransform.values()) { |
| |
| String locale = cldrFile.getLocaleID(); |
| |
| if (!(localeTransform.getInputLocale().equals(locale))) { |
| continue; |
| } |
| |
| Factory factory = CONFIG.getCldrFactory(); |
| CLDRFileTransformer transformer = new CLDRFileTransformer(factory, CLDRPaths.COMMON_DIRECTORY + "transforms/"); |
| Transliterator transliterator = transformer.loadTransliterator(localeTransform); |
| final String transliterated = transliterator.transliterate(value); |
| if (!transliterated.equals(value)) { |
| return backgroundStartSymbol + "[ " + transliterated + " ]" + backgroundEndSymbol + exampleSeparatorSymbol + input; |
| } |
| } |
| return input; |
| } |
| |
| /** |
| * This is called just before we return a result. It fixes the special characters that were added by setBackground. |
| * |
| * @param input string with special characters from setBackground. |
| * @param invert |
| * @return string with HTML for the background. |
| */ |
| private String finalizeBackground(String input) { |
| if (input == null) { |
| return input; |
| } |
| String coreString = |
| TransliteratorUtilities.toHTML.transliterate(input) |
| .replace(backgroundStartSymbol + backgroundEndSymbol, "") |
| // remove null runs |
| .replace(backgroundEndSymbol + backgroundStartSymbol, "") |
| // remove null runs |
| .replace(backgroundStartSymbol, backgroundStart) |
| .replace(backgroundEndSymbol, backgroundEnd) |
| .replace(backgroundAutoStartSymbol, backgroundAutoStart) |
| .replace(backgroundAutoEndSymbol, backgroundAutoEnd) |
| .replace(exampleSeparatorSymbol, exampleEnd + exampleStart) |
| .replace(exampleStartAutoSymbol, exampleStartAuto) |
| .replace(exampleStartRTLSymbol, exampleStartRTL) |
| .replace(exampleStartHeaderSymbol, exampleStartHeader) |
| .replace(exampleEndSymbol, exampleEnd) |
| .replace(startItalicSymbol, startItalic) |
| .replace(endItalicSymbol, endItalic) |
| .replace(startSupSymbol, startSup) |
| .replace(endSupSymbol, endSup) |
| ; |
| // If we are not showing context, we use exampleSeparatorSymbol between examples, |
| // and then need to add the initial exampleStart and final exampleEnd. |
| return (input.indexOf(exampleStartAutoSymbol) >= 0)? coreString: |
| exampleStart + coreString + exampleEnd; |
| } |
| |
| private String invertBackground(String input) { |
| return input == null ? null |
| : backgroundStartSymbol |
| + input.replace(backgroundStartSymbol, backgroundTempSymbol) |
| .replace(backgroundEndSymbol, backgroundStartSymbol) |
| .replace(backgroundTempSymbol, backgroundEndSymbol) |
| + backgroundEndSymbol; |
| } |
| |
| private String removeEmptyRuns(String input) { |
| return input.replace(backgroundStartSymbol + backgroundEndSymbol, "") |
| .replace(backgroundEndSymbol + backgroundStartSymbol, ""); |
| } |
| |
| /** |
| * Utility to format using a gmtHourString, gmtFormat, and an integer hours. We only need the hours because that's |
| * all |
| * the TZDB IDs need. Should merge this eventually into TimeZoneFormatter and call there. |
| * |
| * @param gmtHourString |
| * @param gmtFormat |
| * @param hours |
| * @return |
| */ |
| private String getGMTFormat(String gmtHourString, String gmtFormat, int hours) { |
| return getGMTFormat(gmtHourString, gmtFormat, hours, 0); |
| } |
| |
| private String getGMTFormat(String gmtHourString, String gmtFormat, int hours, int minutes) { |
| boolean hoursBackground = false; |
| if (gmtHourString == null) { |
| hoursBackground = true; |
| gmtHourString = cldrFile.getWinningValue("//ldml/dates/timeZoneNames/hourFormat"); |
| } |
| if (gmtFormat == null) { |
| hoursBackground = false; // for the hours case |
| gmtFormat = setBackground(cldrFile.getWinningValue("//ldml/dates/timeZoneNames/gmtFormat")); |
| } |
| String[] plusMinus = gmtHourString.split(";"); |
| |
| SimpleDateFormat dateFormat = icuServiceBuilder.getDateFormat("gregorian", plusMinus[hours >= 0 ? 0 : 1]); |
| dateFormat.setTimeZone(ZONE_SAMPLE); |
| calendar.set(1999, 9, 27, Math.abs(hours), minutes, 0); // 1999-09-13 13:25:59 |
| Date sample = calendar.getTime(); |
| String hourString = dateFormat.format(sample); |
| if (hoursBackground) { |
| hourString = setBackground(hourString); |
| } |
| String result = format(gmtFormat, hourString); |
| return result; |
| } |
| |
| private String getMZTimeFormat() { |
| String timeFormat = cldrFile |
| .getWinningValue( |
| "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/timeFormats/timeFormatLength[@type=\"short\"]/timeFormat[@type=\"standard\"]/pattern[@type=\"standard\"]"); |
| if (timeFormat == null) { |
| timeFormat = "HH:mm"; |
| } |
| // the following is <= because the TZDB inverts the hours |
| SimpleDateFormat dateFormat = icuServiceBuilder.getDateFormat("gregorian", timeFormat); |
| dateFormat.setTimeZone(ZONE_SAMPLE); |
| calendar.set(1999, 9, 13, 13, 25, 59); // 1999-09-13 13:25:59 |
| Date sample = calendar.getTime(); |
| String result = dateFormat.format(sample); |
| return result; |
| } |
| |
| /** |
| * Return a help string, in html, that should be shown in the Zoomed view. |
| * Presumably at the end of each help section is something like: <br> |
| * <br>For more information, see <a |
| * href='http://unicode.org/cldr/wiki?SurveyToolHelp/characters'>help</a>. <br> |
| * The result is valid HTML. Set listPlaceholders to true to include a |
| * HTML-formatted table of all placeholders required in the value.<br> |
| * TODO: add more help, and modify to get from property or xml file for easy |
| * modification. |
| * |
| * @return null if none available. |
| */ |
| public synchronized String getHelpHtml(String xpath, String value, boolean listPlaceholders) { |
| |
| // lazy initialization |
| |
| if (pathDescription == null) { |
| Map<String, List<Set<String>>> starredPaths = new HashMap<>(); |
| Map<String, String> extras = new HashMap<>(); |
| |
| this.pathDescription = new PathDescription(supplementalDataInfo, englishFile, extras, starredPaths, |
| PathDescription.ErrorHandling.CONTINUE); |
| |
| if (helpMessages == null) { |
| helpMessages = new HelpMessages("test_help_messages.html"); |
| } |
| } |
| |
| // now get the description |
| |
| Level level = CONFIG.getCoverageInfo().getCoverageLevel(xpath, cldrFile.getLocaleID()); |
| String description = pathDescription.getDescription(xpath, value, level, null); |
| if (description == null || description.equals("SKIP")) { |
| return null; |
| } |
| int start = 0; |
| StringBuilder buffer = new StringBuilder(); |
| |
| Matcher URLMatcher = URL_PATTERN.matcher(""); |
| while (URLMatcher.reset(description).find(start)) { |
| final String url = URLMatcher.group(); |
| buffer |
| .append(TransliteratorUtilities.toHTML.transliterate(description.substring(start, URLMatcher.start()))) |
| .append("<a target='CLDR-ST-DOCS' href='") |
| .append(url) |
| .append("'>") |
| .append(url) |
| .append("</a>"); |
| start = URLMatcher.end(); |
| } |
| buffer.append(TransliteratorUtilities.toHTML.transliterate(description.substring(start))); |
| |
| if (listPlaceholders) { |
| buffer.append(pathDescription.getPlaceholderDescription(xpath)); |
| } |
| if (AnnotationUtil.pathIsAnnotation(xpath)) { |
| XPathParts emoji = XPathParts.getFrozenInstance(xpath); |
| String cp = emoji.getAttributeValue(-1, "cp"); |
| String minimal = Utility.hex(cp.replace("", "")).replace(',', '_').toLowerCase(Locale.ROOT); |
| buffer.append("<br><img height='64px' width='auto' src='images/emoji/emoji_" + minimal + ".png'>"); |
| } |
| |
| return buffer.toString(); |
| } |
| |
| public synchronized String getHelpHtml(String xpath, String value) { |
| return getHelpHtml(xpath, value, false); |
| } |
| |
| public static String simplify(String exampleHtml) { |
| return simplify(exampleHtml, false); |
| } |
| |
| public static String simplify(String exampleHtml, boolean internal) { |
| if (exampleHtml == null) { |
| return null; |
| } |
| if (internal) { |
| return "〖" |
| + exampleHtml |
| .replace(backgroundStartSymbol, "❬") |
| .replace(backgroundEndSymbol, "❭") |
| + "〗"; |
| } |
| int startIndex = exampleHtml.indexOf(exampleStartHeader); |
| if (startIndex >= 0) { |
| int endIndex = exampleHtml.indexOf(exampleEnd, startIndex); |
| if (endIndex > startIndex) { |
| // remove header for context examples |
| endIndex += exampleEnd.length(); |
| String head = exampleHtml.substring(0,startIndex); |
| String tail = exampleHtml.substring(endIndex); |
| exampleHtml = head + tail; |
| } |
| } |
| return exampleHtml |
| .replace("<div class='cldr_example'>", "〖") |
| .replace("<div class='cldr_example_auto' dir='auto'>", "【") |
| .replace("<div class='cldr_example_rtl' dir='rtl'>", "【⃪") |
| .replace("</div>", "〗") |
| .replace("<span class='cldr_substituted'>", "❬") |
| .replace("</span>", "❭"); |
| } |
| } |