1 package org.unicode.cldr.json; 2 3 import com.google.common.collect.ImmutableSet; 4 import java.util.ArrayList; 5 import java.util.HashSet; 6 import java.util.Iterator; 7 import java.util.List; 8 import java.util.Set; 9 import java.util.concurrent.atomic.AtomicInteger; 10 import java.util.regex.Matcher; 11 import java.util.regex.Pattern; 12 import org.unicode.cldr.util.Builder; 13 import org.unicode.cldr.util.CLDRFile; 14 import org.unicode.cldr.util.FileProcessor; 15 import org.unicode.cldr.util.PatternCache; 16 17 class LdmlConvertRules { 18 19 /** File sets that will not be processed in JSON transformation. */ 20 public static final ImmutableSet<String> IGNORE_FILE_SET = 21 ImmutableSet.of( 22 "attributeValueValidity", "coverageLevels", "postalCodeData", "subdivisions"); 23 24 /** 25 * The attribute list that should become part of the name in form of name-(attribute)-(value). 26 * [parent_element]:[element]:[attribute] 27 */ 28 // common/main 29 static final ImmutableSet<String> NAME_PART_DISTINGUISHING_ATTR_SET = 30 ImmutableSet.of( 31 "monthWidth:month:yeartype", 32 "characters:parseLenients:scope", 33 "dateFormat:pattern:numbers", 34 "characterLabelPatterns:characterLabelPattern:count", // originally under 35 // characterLabels 36 "currencyFormats:unitPattern:count", 37 "currency:displayName:count", 38 "numbers:symbols:numberSystem", 39 "numbers:decimalFormats:numberSystem", 40 "numbers:currencyFormats:numberSystem", 41 "numbers:percentFormats:numberSystem", 42 "numbers:scientificFormats:numberSystem", 43 "numbers:miscPatterns:numberSystem", 44 "minimalPairs:pluralMinimalPairs:count", 45 "territoryContainment:group:status", 46 "decimalFormat:pattern:count", 47 "currencyFormat:pattern:count", 48 "unit:unitPattern:count", 49 // compound units 50 "compoundUnit:compoundUnitPattern1:count", 51 "compoundUnit:compoundUnitPattern1:gender", 52 "compoundUnit:compoundUnitPattern1:case", 53 "field:relative:type", 54 "field:relativeTime:type", 55 "relativeTime:relativeTimePattern:count", 56 "availableFormats:dateFormatItem:count", 57 "listPatterns:listPattern:type", 58 "timeZoneNames:regionFormat:type", 59 "units:durationUnit:type", 60 "weekData:minDays:territories", 61 "weekData:firstDay:territories", 62 "weekData:weekendStart:territories", 63 "weekData:weekendEnd:territories", 64 "supplemental:dayPeriodRuleSet:type", 65 // units 66 "unitPreferenceDataData:unitPreferences:category", 67 // grammatical features 68 // in common/supplemental/grammaticalFeatures.xml 69 "grammaticalData:grammaticalFeatures:targets", 70 "grammaticalGenderData:grammaticalFeatures:targets", 71 "grammaticalFeatures:grammaticalCase:scope", 72 "grammaticalFeatures:grammaticalGender:scope", 73 "grammaticalDerivations:deriveCompound:structure", 74 "grammaticalDerivations:deriveCompound:feature", 75 "grammaticalDerivations:deriveComponent:feature", 76 "grammaticalDerivations:deriveComponent:structure", 77 // measurement 78 "measurementData:measurementSystem:category", 79 "supplemental:plurals:type", 80 "pluralRanges:pluralRange:start", 81 "pluralRanges:pluralRange:end", 82 "pluralRules:pluralRule:count", 83 "languageMatches:languageMatch:desired", 84 "styleNames:styleName:subtype", 85 "styleNames:styleName:alt"); 86 87 /** 88 * The set of attributes that should become part of the name in form of 89 * name-(attribute)-(value). 90 */ 91 92 /** 93 * Following is a list of element:attribute pair. These attributes should be treated as values. 94 * For example, <type type="arab" key="numbers">Arabic-Indic Digits</type> should be really 95 * converted as, "arab": { "_value": "Arabic-Indic Digits", "_key": "numbers" } 96 */ 97 static final ImmutableSet<String> ATTR_AS_VALUE_SET = 98 ImmutableSet.of( 99 100 // in common/supplemental/dayPeriods.xml 101 "dayPeriodRules:dayPeriodRule:from", 102 103 // in common/supplemental/likelySubtags.xml 104 "likelySubtags:likelySubtag:to", 105 106 // in common/supplemental/metaZones.xml 107 "timezone:usesMetazone:mzone", 108 // Only the current usesMetazone will be kept, it is not necessary to keep 109 // "to" and "from" attributes to make key unique. This is needed as their 110 // value is not good if used as key. 111 "timezone:usesMetazone:to", 112 "timezone:usesMetazone:from", 113 "mapTimezones:mapZone:other", 114 "mapTimezones:mapZone:type", 115 "mapTimezones:mapZone:territory", 116 117 // in common/supplemental/numberingSystems.xml 118 "numberingSystems:numberingSystem:type", 119 120 // in common/supplemental/supplementalData.xml 121 "region:currency:from", 122 "region:currency:to", 123 "region:currency:tender", 124 "calendar:calendarSystem:type", 125 "codeMappings:territoryCodes:numeric", 126 "codeMappings:territoryCodes:alpha3", 127 "codeMappings:currencyCodes:numeric", 128 "timeData:hours:allowed", 129 "timeData:hours:preferred", 130 // common/supplemental/supplementalMetaData.xml 131 "validity:variable:type", 132 "deprecated:deprecatedItems:elements", 133 "deprecated:deprecatedItems:attributes", 134 "deprecated:deprecatedItems:type", 135 136 // in common/supplemental/telephoneCodeData.xml 137 "codesByTerritory:telephoneCountryCode:code", 138 139 // in common/supplemental/windowsZones.xml 140 "mapTimezones:mapZone:other", 141 142 // in common/supplemental/units.xml 143 "*:unitPreference:geq", 144 "*:unitPreference:skeleton", 145 146 // in common/supplemental/grammaticalFeatures.xml 147 "grammaticalDerivations:deriveComponent:value0", 148 "grammaticalDerivations:deriveComponent:value1", 149 150 // identity elements 151 "identity:language:type", 152 "identity:script:type", 153 "identity:territory:type", 154 "identity:variant:type", 155 156 // in common/bcp47/*.xml 157 "keyword:key:name", 158 159 // transforms 160 161 // transforms 162 "transforms:transform:source", 163 "transforms:transform:target", 164 "transforms:transform:direction", 165 "transforms:transform:variant"); 166 167 /** 168 * The set of element:attribute pair in which the attribute should be treated as value. All the 169 * attribute here are non-distinguishing attributes. 170 */ 171 172 /** 173 * For those attributes that are treated as values, they taken the form of element_name: { ..., 174 * attribute: value, ...} This is desirable as an element may have several attributes that are 175 * treated as values. But in some cases, there is one such attribute only, and it is more 176 * desirable to convert element_name: { attribute: value} to element_name: value With a solid 177 * example, (likelySubtags:likelySubtag:to) <likelySubtag from="zh" to="zh_Hans_CN" /> 178 * distinguishing attr "from" will become the key, its better to omit "to" and have this simple 179 * mapping: "zh" : "zh_Hans_CN", 180 */ 181 static final ImmutableSet<String> COMPACTABLE_ATTR_AS_VALUE_SET = 182 ImmutableSet.of( 183 // parent:element:attribute 184 // common/main 185 "calendars:default:choice", 186 "dateFormats:default:choice", 187 "months:default:choice", 188 "monthContext:default:choice", 189 "days:default:choice", 190 "dayContext:default:choice", 191 "timeFormats:default:choice", 192 "dateTimeFormats:default:choice", 193 "timeZoneNames:singleCountries:list", 194 195 // rbnf 196 "ruleset:rbnfrule:value", 197 // common/supplemental 198 "likelySubtags:likelySubtag:to", 199 // "territoryContainment:group:type", 200 "calendar:calendarSystem:type", 201 "calendarPreferenceData:calendarPreference:ordering", 202 "codesByTerritory:telephoneCountryCode:code", 203 204 // common/collation 205 "collations:default:choice", 206 207 // common/supplemental/pluralRanges.xml 208 "pluralRanges:pluralRange:result", 209 210 // identity elements 211 "identity:language:type", 212 "identity:script:type", 213 "identity:territory:type", 214 "identity:variant:type", 215 "grammaticalFeatures:grammaticalGender:values", 216 "grammaticalFeatures:grammaticalDefiniteness:values", 217 "grammaticalFeatures:grammaticalCase:values", 218 "grammaticalDerivations:deriveCompound:value"); 219 220 /** 221 * The set of attributes that should be treated as value, and reduce to simple value only form. 222 */ 223 224 /** Anonymous key name. */ 225 public static final String ANONYMOUS_KEY = "_"; 226 227 /** 228 * Check if the attribute should be suppressed. 229 * 230 * <p>Right now only "_q" is suppressed. In most cases array is used and there is no need for 231 * this information. In other cases, order is irrelevant. 232 * 233 * @return True if the attribute should be suppressed. 234 */ IsSuppresedAttr(String attr)235 public static boolean IsSuppresedAttr(String attr) { 236 return attr.endsWith("_q") || attr.endsWith("-q"); 237 } 238 239 /** The set of attributes that should be ignored in the conversion process. */ 240 public static final ImmutableSet<String> IGNORABLE_NONDISTINGUISHING_ATTR_SET = 241 ImmutableSet.of("draft", "references", "origin"); 242 243 /** 244 * List of attributes that should be suppressed. This list comes from 245 * cldr/common/supplemental/supplementalMetadata. Each three of them is a group, they are for 246 * element, value and attribute. If the specified attribute appears in specified element with 247 * specified = value, it should be suppressed. 248 */ 249 public static final String[] ATTR_SUPPRESS_LIST = { 250 // common/main 251 "dateFormat", "standard", "type", 252 "dateTimeFormat", "standard", "type", 253 "timeFormat", "standard", "type", 254 "decimalFormat", "standard", "type", 255 "percentFormat", "standard", "type", 256 "scientificFormat", "standard", "type", 257 "pattern", "standard", "type" 258 }; 259 260 /** This is a simple class to hold the splittable attribute specification. */ 261 public static class SplittableAttributeSpec { 262 public String element; 263 public String attribute; 264 public String attrAsValueAfterSplit; 265 SplittableAttributeSpec(String el, String attr, String av)266 SplittableAttributeSpec(String el, String attr, String av) { 267 element = el; 268 attribute = attr; 269 attrAsValueAfterSplit = av; 270 } 271 } 272 273 /** 274 * List of attributes that has value that can be split. Each two of them is a group, and 275 * represent element and value. Occurrences of such match should lead to creation of multiple 276 * node. Example: <weekendStart day="thu" territories="DZ KW OM SA SD YE AF IR"/> should be 277 * treated as if following node is encountered. <weekendStart day="thu" territories="DZ"/> 278 * <weekendStart day="thu" territories="KW"/> <weekendStart day="thu" territories="OM"/> 279 * <weekendStart day="thu" territories="SA"/> <weekendStart day="thu" territories="SD"/> 280 * <weekendStart day="thu" territories="YE"/> <weekendStart day="thu" territories="AF"/> 281 * <weekendStart day="thu" territories="IR"/> 282 */ 283 private static final SplittableAttributeSpec[] SPLITTABLE_ATTRS = { 284 new SplittableAttributeSpec("calendarPreference", "territories", null), 285 new SplittableAttributeSpec("pluralRanges", "locales", null), 286 new SplittableAttributeSpec("pluralRules", "locales", null), 287 new SplittableAttributeSpec("minDays", "territories", "count"), 288 new SplittableAttributeSpec("firstDay", "territories", "day"), 289 new SplittableAttributeSpec("weekendStart", "territories", "day"), 290 new SplittableAttributeSpec("weekendEnd", "territories", "day"), 291 new SplittableAttributeSpec("weekOfPreference", "locales", "ordering"), 292 new SplittableAttributeSpec("measurementSystem", "territories", "type"), 293 // this is deprecated, so no need to generalize this exception. 294 new SplittableAttributeSpec( 295 "measurementSystem-category-temperature", "territories", "type"), 296 new SplittableAttributeSpec("paperSize", "territories", "type"), 297 new SplittableAttributeSpec("parentLocale", "locales", "parent"), 298 new SplittableAttributeSpec( 299 "collations", "locales", "parent"), // parentLocale component=collations 300 new SplittableAttributeSpec( 301 "plurals", "locales", "parent"), // parentLocale component=plurals 302 new SplittableAttributeSpec( 303 "segmentations", "locales", "parent"), // parentLocale component=segmentations 304 new SplittableAttributeSpec("hours", "regions", null), 305 new SplittableAttributeSpec("dayPeriodRules", "locales", null), 306 // new SplittableAttributeSpec("group", "contains", "group"), 307 new SplittableAttributeSpec("personList", "locales", "type"), 308 new SplittableAttributeSpec("unitPreference", "regions", null), 309 new SplittableAttributeSpec("grammaticalFeatures", "locales", null), 310 new SplittableAttributeSpec("grammaticalDerivations", "locales", null), 311 // this will cause EMPTY parentLocales elements to work properly 312 new SplittableAttributeSpec("parentLocales", "component", "" /* Not null */), 313 }; 314 315 /** The set that contains all timezone type of elements. */ 316 public static final Set<String> TIMEZONE_ELEMENT_NAME_SET = 317 Builder.with(new HashSet<String>()) 318 .add("zone") 319 .add("timezone") 320 .add("zoneItem") 321 .add("typeMap") 322 .freeze(); 323 324 /** 325 * There are a handful of attribute values that are more properly represented as an array of 326 * strings rather than as a single string. These are not locked to a specific element, may need 327 * to change the matching algorithm if there are conflicts. 328 */ 329 public static final Set<String> ATTRVALUE_AS_ARRAY_SET = 330 Builder.with(new HashSet<String>()) 331 .add("territories") 332 .add("scripts") 333 .add("contains") 334 .add("systems") 335 .add("origin") 336 .add("component") // for parentLocales - may need to be more specific here 337 .add("localeRules") // for parentLocales 338 .add("values") // for unitIdComponents - may need to be more specific here 339 .freeze(); 340 341 /** 342 * Following is the list of elements that need to be sorted before output. 343 * 344 * <p>Time zone item is split to multiple level, and each level should be grouped together. The 345 * locale list in "dayPeriodRule" could be split to multiple items, and items for each locale 346 * should be grouped together. 347 */ 348 public static final String[] ELEMENT_NEED_SORT = { 349 "zone", 350 "timezone", 351 "zoneItem", 352 "typeMap", 353 "dayPeriodRule", 354 "pluralRanges", 355 "pluralRules", 356 "personList", 357 "calendarPreferenceData", 358 "character-fallback", 359 "types", 360 "timeData", 361 "minDays", 362 "firstDay", 363 "weekendStart", 364 "weekendEnd", 365 "measurementData", 366 "measurementSystem" 367 }; 368 369 /** 370 * Some elements in CLDR has multiple children of the same type of element. We would like to 371 * treat them as array. 372 */ 373 public static final Pattern ARRAY_ITEM_PATTERN = 374 PatternCache.get( 375 "(.*/collation[^/]*/rules[^/]*/" 376 + "|.*/character-fallback[^/]*/character[^/]*/" 377 + "|.*/rbnfrule[^/]*/" 378 + "|.*/ruleset[^/]*/" 379 + "|.*/languageMatching[^/]*/languageMatches[^/]*/" 380 + "|.*/unitPreferences/[^/]*/[^/]*/" 381 + "|.*/windowsZones[^/]*/mapTimezones[^/]*/" 382 + "|.*/metaZones[^/]*/mapTimezones[^/]*/" 383 + "|.*/segmentation[^/]*/variables[^/]*/" 384 + "|.*/segmentation[^/]*/suppressions[^/]*/" 385 + "|.*/transform[^/]*/tRules[^/]*/" 386 + "|.*/region/region[^/]*/" 387 + "|.*/keyword[^/]*/key[^/]*/" 388 + "|.*/telephoneCodeData[^/]*/codesByTerritory[^/]*/" 389 + "|.*/metazoneInfo[^/]*/timezone\\[[^\\]]*\\]/" 390 + "|.*/metadata[^/]*/validity[^/]*/" 391 + "|.*/metadata[^/]*/suppress[^/]*/" 392 + "|.*/metadata[^/]*/deprecated[^/]*/" 393 + ")(.*)"); 394 395 /** These objects values should be output as arrays. */ 396 public static final Pattern VALUE_IS_SPACESEP_ARRAY = 397 PatternCache.get( 398 "(grammaticalCase|grammaticalGender|grammaticalDefiniteness|nameOrderLocales|component)"); 399 400 /** 401 * Indicates that the child value of this element needs to be separated into array items. For 402 * example: {@code <weekOfPreference ordering="weekOfDate weekOfMonth" locales="en bn ja ka"/>} 403 * becomes {@code {"en":["weekOfDate","weekOfMonth"],"bn":["weekOfDate","weekOfMonth"]} } 404 */ 405 public static final Set<String> CHILD_VALUE_IS_SPACESEP_ARRAY = 406 ImmutableSet.of("weekOfPreference", "calendarPreferenceData"); 407 408 /** 409 * Number elements without a numbering system are there only for compatibility purposes. We 410 * automatically suppress generation of JSON objects for them. 411 */ 412 public static final Pattern NO_NUMBERING_SYSTEM_PATTERN = 413 Pattern.compile( 414 "//ldml/numbers/(symbols|(decimal|percent|scientific|currency)Formats)/.*"); 415 416 public static final Pattern NUMBERING_SYSTEM_PATTERN = 417 Pattern.compile( 418 "//ldml/numbers/(symbols|miscPatterns|(decimal|percent|scientific|currency)Formats)\\[@numberSystem=\"([^\"]++)\"\\]/.*"); 419 public static final String[] ACTIVE_NUMBERING_SYSTEM_XPATHS = { 420 "//ldml/numbers/defaultNumberingSystem", 421 "//ldml/numbers/otherNumberingSystems/native", 422 "//ldml/numbers/otherNumberingSystems/traditional", 423 "//ldml/numbers/otherNumberingSystems/finance" 424 }; 425 426 /** 427 * Root language id pattern should be discarded in all locales except root, even though the path 428 * will exist in a resolved CLDRFile. 429 */ 430 public static final Pattern ROOT_IDENTITY_PATTERN = 431 Pattern.compile("//ldml/identity/language\\[@type=\"root\"\\]"); 432 433 /** 434 * Version (coming from DTD) should be discarded everywhere. This information is now in 435 * package.json. 436 */ 437 public static final Pattern VERSION_PATTERN = Pattern.compile("//ldml/identity/version.*"); 438 439 /** A simple class to hold the specification of a path transformation. */ 440 public static class PathTransformSpec { 441 442 private final boolean DEBUG_TRANSFORMS = false; 443 public Pattern pattern; 444 public String replacement; 445 public String patternStr; 446 public String comment = ""; 447 private AtomicInteger use = new AtomicInteger(); 448 PathTransformSpec(String patternStr, String replacement, String comment)449 PathTransformSpec(String patternStr, String replacement, String comment) { 450 this.patternStr = patternStr; 451 pattern = PatternCache.get(patternStr); 452 this.replacement = replacement; 453 this.comment = comment; 454 if (this.comment == null) this.comment = ""; 455 } 456 457 @Override toString()458 public String toString() { 459 StringBuilder sb = new StringBuilder(); 460 sb.append('\n') 461 .append("# ") 462 .append(comment.replace('\n', ' ')) 463 .append('\n') 464 .append("< ") 465 .append(patternStr) 466 .append('\n') 467 .append("> ") 468 .append(replacement) 469 .append('\n'); 470 return sb.toString(); 471 } 472 473 /** 474 * Apply this rule to a string 475 * 476 * @param result input string 477 * @return result, or null if unchanged 478 */ apply(String result)479 public String apply(String result) { 480 Matcher m = pattern.matcher(result); 481 if (m.matches()) { 482 final String newResult = m.replaceFirst(replacement); 483 final int count = this.use.incrementAndGet(); 484 if (DEBUG_TRANSFORMS) { 485 System.err.println( 486 result 487 + " => " 488 + newResult 489 + " count " 490 + count 491 + " << " 492 + this.toString()); 493 } 494 return newResult; 495 } 496 return null; 497 } 498 dumpAll()499 public static void dumpAll() { 500 System.out.println("# Path Transformations"); 501 for (final PathTransformSpec ts : getPathTransformations()) { 502 System.out.append(ts.toString()); 503 } 504 System.out.println(); 505 } 506 applyAll(String result)507 public static final String applyAll(String result) { 508 for (final PathTransformSpec ts : getPathTransformations()) { 509 final String changed = ts.apply(result); 510 if (changed != null) { 511 result = changed; 512 break; 513 } 514 } 515 return result; 516 } 517 } 518 getPathTransformations()519 public static final Iterable<PathTransformSpec> getPathTransformations() { 520 return PathTransformSpecHelper.INSTANCE; 521 } 522 523 /** 524 * Add a path transform for the //ldml/identity/version element to the specific number 525 * 526 * @param version 527 */ addVersionHandler(String version)528 public static final void addVersionHandler(String version) { 529 if (!CLDRFile.GEN_VERSION.equals(version)) { 530 PathTransformSpecHelper.INSTANCE.prependVersionTransforms(version); 531 } 532 } 533 534 public static final class PathTransformSpecHelper extends FileProcessor 535 implements Iterable<PathTransformSpec> { 536 static final PathTransformSpecHelper INSTANCE = make(); 537 make()538 static final PathTransformSpecHelper make() { 539 final PathTransformSpecHelper helper = new PathTransformSpecHelper(); 540 helper.process(PathTransformSpecHelper.class, "pathTransforms.txt"); 541 return helper; 542 } 543 PathTransformSpecHelper()544 private PathTransformSpecHelper() {} 545 546 private List<PathTransformSpec> data = new ArrayList<>(); 547 private String lastComment = ""; 548 private String lastPattern = null; 549 private String lastReplacement = null; 550 551 @Override handleStart()552 protected void handleStart() { 553 // Add these to the beginning because of the dynamic version 554 String version = CLDRFile.GEN_VERSION; 555 prependVersionTransforms(version); 556 } 557 558 /** 559 * Prepend version transform. If called twice, the LAST caller will be used. 560 * 561 * @param version 562 */ prependVersionTransforms(String version)563 public void prependVersionTransforms(String version) { 564 data.add( 565 0, 566 new PathTransformSpec( 567 "(.+)/identity/version\\[@number=\"([^\"]*)\"\\]", 568 "$1" + "/identity/version\\[@cldrVersion=\"" + version + "\"\\]", 569 "added by code")); 570 // Add cldrVersion attribute to supplemental data 571 data.add( 572 0, 573 new PathTransformSpec( 574 "(.+)/version\\[@number=\"([^\"]*)\"\\]\\[@unicodeVersion=\"([^\"]*\")(\\])", 575 "$1" 576 + "/version\\[@cldrVersion=\"" 577 + version 578 + "\"\\]" 579 + "\\[@unicodeVersion=\"" 580 + "$3" 581 + "\\]", 582 "added by code")); 583 } 584 585 @Override handleLine(int lineCount, String line)586 protected boolean handleLine(int lineCount, String line) { 587 if (line.isEmpty()) return true; 588 if (line.startsWith("<")) { 589 lastReplacement = null; 590 if (lastPattern != null) { 591 throw new IllegalArgumentException("line " + lineCount + ": two <'s in a row"); 592 } 593 lastPattern = line.substring(1).trim(); 594 if (lastPattern.isEmpty()) { 595 throw new IllegalArgumentException("line " + lineCount + ": empty < pattern"); 596 } 597 } else if (line.startsWith(">")) { 598 if (lastPattern == null) { 599 throw new IllegalArgumentException( 600 "line " + lineCount + ": need < line before > line"); 601 } 602 lastReplacement = line.substring(1).trim(); 603 data.add(new PathTransformSpec(lastPattern, lastReplacement, lastComment)); 604 reset(); 605 } 606 return true; 607 } 608 609 @Override handleEnd()610 protected void handleEnd() { 611 if (lastPattern != null) { 612 throw new IllegalArgumentException("ended with a < but no >"); 613 } 614 } 615 reset()616 private void reset() { 617 this.lastComment = ""; 618 this.lastPattern = null; 619 this.lastReplacement = null; 620 } 621 622 @Override handleComment(String line, int commentCharPosition)623 public void handleComment(String line, int commentCharPosition) { 624 lastComment = line.substring(commentCharPosition + 1).trim(); 625 } 626 627 @Override iterator()628 public Iterator<PathTransformSpec> iterator() { 629 return data.iterator(); 630 } 631 } 632 main(String args[])633 public static void main(String args[]) { 634 // for debugging / verification 635 PathTransformSpec.dumpAll(); 636 } 637 getKeyStr(String name, String key)638 public static final String getKeyStr(String name, String key) { 639 String keyStr2 = "*:" + name + ":" + key; 640 return keyStr2; 641 } 642 getKeyStr(String parent, String name, String key)643 public static final String getKeyStr(String parent, String name, String key) { 644 String keyStr = parent + ":" + name + ":" + key; 645 return keyStr; 646 } 647 getSplittableAttrs()648 public static SplittableAttributeSpec[] getSplittableAttrs() { 649 return SPLITTABLE_ATTRS; 650 } 651 valueIsSpacesepArray(final String nodeName, String parent)652 public static final boolean valueIsSpacesepArray(final String nodeName, String parent) { 653 return VALUE_IS_SPACESEP_ARRAY.matcher(nodeName).matches() 654 || (parent != null && CHILD_VALUE_IS_SPACESEP_ARRAY.contains(parent)); 655 } 656 657 static final Set<String> BOOLEAN_OMIT_FALSE = 658 ImmutableSet.of( 659 // attribute names within bcp47 that are booleans, but omitted if false. 660 "deprecated"); 661 662 // These attributes are booleans, and should be omitted if false attrIsBooleanOmitFalse( final String fullPath, final String nodeName, final String parent, final String key)663 public static final boolean attrIsBooleanOmitFalse( 664 final String fullPath, final String nodeName, final String parent, final String key) { 665 return (fullPath != null 666 && (fullPath.startsWith("//supplementalData/metaZones/metazoneIds") 667 || fullPath.startsWith("//ldmlBCP47/keyword/key")) 668 && BOOLEAN_OMIT_FALSE.contains(key)); 669 } 670 } 671