1 package org.unicode.cldr.icu; 2 3 import java.io.BufferedReader; 4 import java.util.ArrayList; 5 import java.util.Arrays; 6 import java.util.Collections; 7 import java.util.Comparator; 8 import java.util.HashMap; 9 import java.util.HashSet; 10 import java.util.Iterator; 11 import java.util.List; 12 import java.util.Map; 13 import java.util.Map.Entry; 14 import java.util.Set; 15 import java.util.regex.Matcher; 16 import java.util.regex.Pattern; 17 18 import org.unicode.cldr.util.CLDRFile; 19 import org.unicode.cldr.util.CldrUtility.VariableReplacer; 20 import org.unicode.cldr.util.FileReaders; 21 import org.unicode.cldr.util.PatternCache; 22 import org.unicode.cldr.util.RegexLookup; 23 import org.unicode.cldr.util.RegexLookup.Finder; 24 import org.unicode.cldr.util.RegexLookup.Merger; 25 import org.unicode.cldr.util.RegexLookup.RegexFinder; 26 27 import com.ibm.icu.impl.Row.R2; 28 import com.ibm.icu.impl.Row.R3; 29 import com.ibm.icu.text.Transform; 30 import com.ibm.icu.text.UnicodeSet; 31 import com.ibm.icu.util.Output; 32 33 /** 34 * A class for loading and managing the xpath converters used to map CLDR data 35 * to ICU. 36 * @author jchye 37 */ 38 class RegexManager { 39 private static final Pattern SEMI = PatternCache.get("\\s*+;\\s*+"); 40 private static final Pattern FUNCTION_PATTERN = PatternCache.get("\\&(\\w++)\\(([^\\)]++)\\)"); 41 private static final Pattern QUOTES = PatternCache.get("\"([^\"]++)\""); 42 private static final UnicodeSet SPACE_CHARACTERS = new UnicodeSet( 43 "[\\u0000\\uFEFF[:pattern_whitespace:]]"); 44 // Matches arguments with or without enclosing quotes. 45 private static final Pattern ARGUMENT = PatternCache.get("[<\"]?\\$(\\d)[\">]?"); 46 47 private static Map<String, Function> functionMap = new HashMap<String, Function>(); 48 49 private String converterFile; 50 private Map<String, RegexResult> unprocessedMatchers; 51 private Map<String, String> xpathVariables; 52 private VariableReplacer cldrVariables; 53 54 /** 55 * Wrapper class for functions that need to be performed on CLDR values as 56 * part of the ICU conversion, 57 */ 58 static abstract class Function { 59 int maxArgs; 60 61 /** 62 * @param maxArgs 63 * the maximum number of args that can be passed to this function, 64 * or a negative number if there is no limit. 65 */ Function(int maxArgs)66 Function(int maxArgs) { 67 this.maxArgs = maxArgs; 68 } 69 70 /** 71 * Processes a comma-delimited list of arguments. 72 * 73 * @param arg 74 * @return 75 */ process(String arg)76 public String process(String arg) { 77 String[] args = arg.split(","); 78 if (maxArgs > -1 && args.length > maxArgs) { 79 throw new IllegalArgumentException("Function has too many args: expected " 80 + maxArgs + " but got (" + arg + ")"); 81 } 82 return run(args); 83 } 84 85 /** 86 * Runs the function on a list of arguments. 87 * 88 * @return the resultant string 89 */ run(String... args)90 abstract String run(String... args); 91 } 92 93 /** 94 * A wrapper class for storing and working with the unprocessed path-value pairs of a RegexResult. 95 */ 96 static class PathValueInfo { 97 private String rbPath; 98 private String valueArg; 99 private String groupKey; 100 private String baseXPath; 101 private int splitRbPathArg; 102 103 /** 104 * @param rbPath 105 * the rbPath expression to be used for this set of path-value pairs. 106 * @param instructions 107 * any special instructions to be carried out on this set of path-value pairs 108 * @param splitRbPathArg 109 * if applicable, the number of the argument in the rbPath 110 * expression that needs to be split to create multiple rbPaths, each with the same value 111 */ PathValueInfo(String rbPath, Map<String, String> instructions, int splitRbPathArg)112 public PathValueInfo(String rbPath, Map<String, String> instructions, int splitRbPathArg) { 113 this.rbPath = rbPath; 114 this.valueArg = instructions.get("values"); 115 this.groupKey = instructions.get("group"); 116 this.baseXPath = instructions.get("base_xpath"); 117 this.splitRbPathArg = splitRbPathArg; 118 } 119 120 /** 121 * @param arguments 122 * the arguments retrieved from the regex corresponding to this PathValueInfo 123 * @return the processed rb path 124 */ processRbPath(String[] arguments)125 public String processRbPath(String[] arguments) { 126 String path = processString(rbPath, arguments); 127 // Replace slashes in metazone names, 128 // e.g. "America/Argentina/La_Rioja" 129 Matcher matcher = QUOTES.matcher(path); 130 if (matcher.find()) { 131 path = path.substring(0, matcher.start(1)) 132 + matcher.group(1).replace('/', ':') 133 + path.substring(matcher.end(1)); 134 } 135 return path; 136 } 137 processGroupKey(String[] arguments)138 public String processGroupKey(String[] arguments) { 139 return processString(groupKey, arguments); 140 } 141 processValues(String[] arguments, String cldrValue)142 public List<String> processValues(String[] arguments, String cldrValue) { 143 if (valueArg == null) { 144 List<String> values = new ArrayList<String>(); 145 values.add(cldrValue); 146 return values; 147 } 148 // Split single args using spaces. 149 String processedValue = processValue(valueArg, cldrValue, arguments); 150 return splitValues(processedValue); 151 } 152 processXPath(String[] arguments, String xpath)153 public String processXPath(String[] arguments, String xpath) { 154 if (baseXPath != null) { 155 return processString(baseXPath, arguments); 156 } else { 157 return xpath; 158 } 159 } 160 splitValues(String unsplitValues)161 private List<String> splitValues(String unsplitValues) { 162 // Split up values using spaces unless enclosed by non-escaped quotes, 163 // e.g. a "b \" c" would become {"a", "b \" c"} 164 List<String> args = new ArrayList<String>(); 165 StringBuffer valueBuffer = new StringBuffer(); 166 boolean shouldSplit = true; 167 char lastChar = ' '; 168 for (char c : unsplitValues.toCharArray()) { 169 // Normalize whitespace input. 170 if (SPACE_CHARACTERS.contains(c)) { 171 c = ' '; 172 } 173 if (c == '"' && lastChar != '\\') { 174 shouldSplit = !shouldSplit; 175 } else if (c == ' ') { 176 if (lastChar == ' ') { 177 // Do nothing. 178 } else if (shouldSplit) { 179 args.add(valueBuffer.toString()); 180 valueBuffer.setLength(0); 181 } else { 182 valueBuffer.append(c); 183 } 184 } else { 185 valueBuffer.append(c); 186 } 187 lastChar = c; 188 } 189 if (valueBuffer.length() > 0) { 190 args.add(valueBuffer.toString()); 191 } 192 return args; 193 } 194 processValue(String value, String cldrValue, String[] arguments)195 private String processValue(String value, String cldrValue, String[] arguments) { 196 value = processString(value, arguments); 197 Matcher matcher = FUNCTION_PATTERN.matcher(value); 198 if (matcher.find()) { 199 int index = 0; 200 StringBuffer buffer = new StringBuffer(); 201 do { 202 buffer.append(value.substring(index, matcher.start())); 203 String fnName = matcher.group(1); 204 String fnArgs = matcher.group(2); 205 String result = functionMap.get(fnName).process(fnArgs); 206 buffer.append(result); 207 index = matcher.end(); 208 } while (matcher.find()); 209 buffer.append(value.substring(index)); 210 // System.out.println(value + " to " + buffer.toString()); 211 value = buffer.toString(); 212 } 213 if (value.contains("{value}")) { 214 value = value.replace("{value}", cldrValue); 215 } 216 return value; 217 } 218 219 @Override toString()220 public String toString() { 221 return rbPath + "=" + valueArg; 222 } 223 getSplitRbPathArg()224 public int getSplitRbPathArg() { 225 return splitRbPathArg; 226 } 227 228 @Override equals(Object o)229 public boolean equals(Object o) { 230 if (o instanceof PathValueInfo) { 231 PathValueInfo otherInfo = (PathValueInfo) o; 232 return rbPath.equals(otherInfo.rbPath) 233 && valueArg.equals(otherInfo.valueArg); 234 } else { 235 return false; 236 } 237 } 238 } 239 processString(String value, String[] arguments)240 static String processString(String value, String[] arguments) { 241 if (value == null) { 242 return null; 243 } 244 try { 245 return RegexLookup.replace(value, arguments); 246 } catch (ArrayIndexOutOfBoundsException e) { 247 throw new RuntimeException("Error while filling out arguments in " + value + " with " 248 + Arrays.asList(arguments), e); 249 } 250 } 251 252 static class RegexResult implements Iterable<PathValueInfo> { 253 254 private Set<PathValueInfo> unprocessed; 255 RegexResult()256 public RegexResult() { 257 unprocessed = new HashSet<PathValueInfo>(); 258 } 259 add(String rbPath, Map<String, String> instructions, int splitRbPathArg)260 public void add(String rbPath, Map<String, String> instructions, 261 int splitRbPathArg) { 262 unprocessed.add(new PathValueInfo(rbPath, instructions, splitRbPathArg)); 263 } 264 265 @Override iterator()266 public Iterator<PathValueInfo> iterator() { 267 return unprocessed.iterator(); 268 } 269 } 270 271 static class CldrArray { 272 // Map of xpaths to values and group key. 273 private Map<String, R2<List<String>, String>> map; 274 CldrArray()275 public CldrArray() { 276 map = new HashMap<String, R2<List<String>, String>>(); 277 } 278 put(String key, List<String> values, String groupKey)279 public void put(String key, List<String> values, String groupKey) { 280 map.put(key, new R2<List<String>, String>(values, groupKey)); 281 } 282 add(String key, String[] values, String groupKey)283 public void add(String key, String[] values, String groupKey) { 284 List<String> list = new ArrayList<String>(); 285 for (String value : values) { 286 list.add(value); 287 } 288 put(key, list, groupKey); 289 } 290 add(String key, String value, String groupKey)291 public void add(String key, String value, String groupKey) { 292 List<String> list = new ArrayList<String>(); 293 list.add(value); 294 put(key, list, groupKey); 295 } 296 addAll(CldrArray otherArray)297 public void addAll(CldrArray otherArray) { 298 // HACK: narrow alias to abbreviated. Remove after CLDR data fixed. 299 for (String otherKey : otherArray.map.keySet()) { 300 String narrowPath = otherKey.replace("eraAbbr", "eraNarrow"); 301 if (!map.containsKey(narrowPath)) { 302 map.put(narrowPath, otherArray.map.get(otherKey)); 303 } 304 305 } 306 } 307 findKey(Finder finder)308 public boolean findKey(Finder finder) { 309 for (String key : map.keySet()) { 310 if (finder.find(key, null, null)) { 311 return true; 312 } 313 } 314 return false; 315 } 316 sortValues(Comparator<String> comparator)317 public List<String[]> sortValues(Comparator<String> comparator) { 318 List<String> sortedKeys = new ArrayList<String>(map.keySet()); 319 Collections.sort(sortedKeys, comparator); 320 List<String[]> sortedValues = new ArrayList<String[]>(); 321 // Group isArray for the same xpath together. 322 List<String> arrayValues = new ArrayList<String>(); 323 for (int i = 0, len = sortedKeys.size(); i < len; i++) { 324 String key = sortedKeys.get(i); 325 R2<List<String>, String> currentEntry = map.get(key); 326 List<String> values = currentEntry.get0(); 327 String groupKey = currentEntry.get1(); 328 if (groupKey == null) { 329 for (String value : values) { 330 sortedValues.add(new String[] { value }); 331 } 332 } else { 333 arrayValues.addAll(values); 334 String nextKey = null; 335 if (i < len - 1) { 336 nextKey = map.get(sortedKeys.get(i + 1)).get1(); 337 } 338 if (!groupKey.equals(nextKey)) { 339 sortedValues.add(toArray(arrayValues)); 340 arrayValues.clear(); 341 } 342 } 343 } 344 return sortedValues; 345 } 346 } 347 348 /** 349 * Converts a list into an Array. 350 */ toArray(List<String> list)351 static String[] toArray(List<String> list) { 352 String[] array = new String[list.size()]; 353 list.toArray(array); 354 return array; 355 } 356 357 private static Transform<String, Finder> regexTransform = new Transform<String, Finder>() { 358 @Override 359 public Finder transform(String source) { 360 return new RegexFinder(source); 361 } 362 }; 363 addFunction(String fnName, Function function)364 Map<String, Function> addFunction(String fnName, Function function) { 365 functionMap.put(fnName, function); 366 return functionMap; 367 } 368 369 /** 370 * Checks if two strings match a specified pattern. 371 * 372 * @param pattern 373 * the pattern to be matched against 374 * @param arg0 375 * @param arg1 376 * @param matchers 377 * a 2-element array to contain the two matchers. The 378 * array is not guaranteed to be filled if matches() returns false 379 * @return true if both strings successfully matched the pattern 380 */ matches(Pattern pattern, String arg0, String arg1, Matcher[] matchers)381 static boolean matches(Pattern pattern, String arg0, String arg1, Matcher[] matchers) { 382 return (matchers[0] = pattern.matcher(arg0)).matches() 383 && (matchers[1] = pattern.matcher(arg1)).matches(); 384 } 385 386 /** 387 * Outputs the list of debugging results returned from a RegexLookup. 388 * Only the matches most similar to the specified xpath will be returned. 389 * @param xpath 390 * @param results 391 */ printLookupResults(String xpath, List<String> results)392 static void printLookupResults(String xpath, List<String> results) { 393 if (results == null) return; 394 System.out.println("Debugging " + xpath + ":"); 395 int[] haltPos = new int[results.size()]; 396 int furthestPos = 0; 397 for (int i = 0; i < haltPos.length; i++) { 398 int pos = results.get(i).indexOf('\u2639'); // ☹ 399 haltPos[i] = pos; 400 if (pos > furthestPos) furthestPos = pos; 401 } 402 for (int i = 0; i < haltPos.length; i++) { 403 if (haltPos[i] < furthestPos) continue; 404 System.out.println(results.get(i)); 405 } 406 } 407 408 RegexLookup<FallbackInfo> fallbackConverter; 409 RegexLookup<RegexResult> xpathConverter; 410 411 // One FallbackInfo object for every type of rbPath. 412 class FallbackInfo implements Iterable<R3<Finder, String, List<String>>> { 413 // Fallback info in the order: xpath matcher, fallback xpath, fallback values. 414 private List<R3<Finder, String, List<String>>> fallbackItems; 415 private List<Integer> argsUsed; // list of args used by the rb pattern 416 private int numXpathArgs; // Number of args in the xpath 417 FallbackInfo(List<Integer> argsUsed, int numXpathArgs)418 public FallbackInfo(List<Integer> argsUsed, int numXpathArgs) { 419 fallbackItems = new ArrayList<R3<Finder, String, List<String>>>(); 420 this.argsUsed = argsUsed; 421 this.numXpathArgs = numXpathArgs; 422 } 423 addItem(Finder xpathMatcher, String fallbackXpath, String[] fallbackValues)424 public void addItem(Finder xpathMatcher, String fallbackXpath, String[] fallbackValues) { 425 List<String> values = new ArrayList<String>(); 426 for (String fallbackValue : fallbackValues) { 427 values.add(fallbackValue); 428 } 429 fallbackItems.add(new R3<Finder, String, List<String>>(xpathMatcher, fallbackXpath, values)); 430 } 431 432 /** 433 * Takes in arguments obtained from a RegexLookup on a RB path and fleshes 434 * it out for the corresponding xpath. 435 * 436 * @param arguments 437 * @return 438 */ getArgumentsForXpath(String[] arguments)439 public String[] getArgumentsForXpath(String[] arguments) { 440 String[] output = new String[numXpathArgs + 1]; 441 output[0] = arguments[0]; 442 for (int i = 0; i < argsUsed.size(); i++) { 443 output[argsUsed.get(i)] = arguments[i + 1]; 444 } 445 for (int i = 0; i < output.length; i++) { 446 if (output[i] == null) output[i] = "x"; // dummy value 447 } 448 return output; 449 } 450 getItem(Finder finder)451 private R3<Finder, String, List<String>> getItem(Finder finder) { 452 for (R3<Finder, String, List<String>> item : fallbackItems) { 453 if (item.get0().equals(finder)) return item; 454 } 455 return null; 456 } 457 merge(FallbackInfo newInfo)458 public FallbackInfo merge(FallbackInfo newInfo) { 459 for (R3<Finder, String, List<String>> newItem : newInfo.fallbackItems) { 460 R3<Finder, String, List<String>> item = getItem(newItem.get0()); 461 if (item == null) { 462 fallbackItems.add(newItem); 463 } else { 464 item.get2().addAll(newItem.get2()); 465 } 466 } 467 return this; 468 } 469 470 @Override iterator()471 public Iterator<R3<Finder, String, List<String>>> iterator() { 472 return fallbackItems.iterator(); 473 } 474 } 475 476 /** 477 * All child classes should call this constructor. 478 * 479 * @param converterFile 480 */ RegexManager(String converterFile)481 RegexManager(String converterFile) { 482 this.converterFile = converterFile; 483 unprocessedMatchers = new HashMap<String, RegexResult>(); 484 } 485 486 /** 487 * @return a RegexLookup for matching rb paths that may require fallback 488 * values. 489 */ getFallbackConverter()490 RegexLookup<FallbackInfo> getFallbackConverter() { 491 if (fallbackConverter == null) { 492 loadConverters(); 493 } 494 return fallbackConverter; 495 } 496 497 /** 498 * @return a RegexLookup for matching xpaths 499 */ getPathConverter()500 RegexLookup<RegexResult> getPathConverter() { 501 if (xpathConverter == null) { 502 loadConverters(); 503 } 504 return xpathConverter; 505 } 506 getPathConverter(CLDRFile cldrFile)507 RegexLookup<RegexResult> getPathConverter(CLDRFile cldrFile) { 508 RegexLookup<RegexResult> basePathConverter = getPathConverter(); 509 510 RegexLookup<RegexResult> processedPathConverter = new RegexLookup<RegexResult>() 511 .setPatternTransform(regexTransform); 512 cldrVariables = new VariableReplacer(); 513 for (Entry<String, String> entry : xpathVariables.entrySet()) { 514 cldrVariables.add(entry.getKey(), cldrFile.getStringValue(entry.getValue())); 515 } 516 for (Map.Entry<Finder, RegexResult> entry : basePathConverter) { 517 processedPathConverter.add(entry.getKey(), entry.getValue()); 518 } 519 for (Entry<String, RegexResult> entry : unprocessedMatchers.entrySet()) { 520 processedPathConverter.add(cldrVariables.replace(entry.getKey()), 521 entry.getValue()); 522 } 523 return processedPathConverter; 524 } 525 loadConverters()526 private void loadConverters() { 527 xpathConverter = new RegexLookup<RegexResult>() 528 .setPatternTransform(regexTransform); 529 fallbackConverter = new RegexLookup<FallbackInfo>() 530 .setValueMerger(new Merger<FallbackInfo>() { 531 @Override 532 public FallbackInfo merge(FallbackInfo a, FallbackInfo into) { 533 return into.merge(a); 534 } 535 }); 536 xpathVariables = new HashMap<String, String>(); 537 BufferedReader reader = FileReaders.openFile(NewLdml2IcuConverter.class, converterFile); 538 VariableReplacer variables = new VariableReplacer(); 539 Finder xpathMatcher = null; 540 RegexResult regexResult = null; 541 String line = null; 542 int lineNum = 0; 543 try { 544 while ((line = reader.readLine()) != null) { 545 lineNum++; 546 line = line.trim(); 547 // Skip comments. 548 if (line.length() == 0 || line.startsWith("#")) continue; 549 // Read variables. 550 if (line.charAt(0) == '%') { 551 int pos = line.indexOf("="); 552 if (pos < 0) { 553 throw new IllegalArgumentException(); 554 } 555 String varName = line.substring(0, pos).trim(); 556 String varValue = line.substring(pos + 1).trim(); 557 // Variables representing xpaths should be replaced later on. 558 if (varValue.startsWith("//")) { 559 xpathVariables.put(varName, varValue); 560 } else { 561 variables.add(varName, varValue); 562 } 563 continue; 564 } 565 if (line.contains("%")) { 566 line = variables.replace(line); 567 } 568 // if line still contains "%", still unprocessed 569 // Process a line in the input file for xpath conversion. 570 String[] content = line.split(SEMI.toString()); 571 // xpath ; rbPath ; value 572 // Create a reverse lookup for rbPaths to xpaths. 573 if (!line.startsWith(";")) { 574 if (regexResult != null) { 575 if (xpathMatcher.toString().contains("%")) { 576 unprocessedMatchers.put(xpathMatcher.toString(), regexResult); 577 } else { 578 xpathConverter.add(xpathMatcher, regexResult); 579 } 580 } 581 xpathMatcher = new RegexFinder(content[0].replace("[@", "\\[@")); 582 regexResult = new RegexResult(); 583 } 584 if (content.length > 1) { 585 addConverterEntry(xpathMatcher, content, regexResult); 586 } 587 } 588 xpathConverter.add(xpathMatcher, regexResult); 589 } catch (Exception e) { 590 System.err.println("Error reading " + converterFile + " at line " + lineNum + ": " + line); 591 e.printStackTrace(); 592 } 593 } 594 addConverterEntry(Finder xpathMatcher, String[] content, RegexResult regexResult)595 private void addConverterEntry(Finder xpathMatcher, String[] content, 596 RegexResult regexResult) { 597 String rbPath = content[1]; 598 // Find arguments in rbPath. 599 Matcher matcher = ARGUMENT.matcher(rbPath); 600 int splitRbPathArg = -1; 601 while (matcher.find()) { 602 char startChar = rbPath.charAt(matcher.start()); 603 char endChar = rbPath.charAt(matcher.end() - 1); 604 boolean shouldSplit = !(startChar == '"' && endChar == '"' || 605 startChar == '<' && endChar == '>'); 606 if (shouldSplit) { 607 splitRbPathArg = Integer.parseInt(matcher.group(1)); 608 break; 609 } 610 } 611 612 // Parse remaining special instructions. 613 Map<String, String> instructions = new HashMap<String, String>(); 614 for (int i = 2; i < content.length; i++) { 615 String[] instruction = content[i].split("=", 2); 616 if (instruction[0].equals("fallback")) { 617 // WARNING: fallback might backfire if more than one type of xpath for the same rbpath 618 addFallback(xpathMatcher, rbPath, instruction[1]); 619 } else { 620 instructions.put(instruction[0], instruction[1]); 621 } 622 } 623 regexResult.add(rbPath, instructions, splitRbPathArg); 624 } 625 626 /** 627 * Adds an entry to the fallback converter. 628 * 629 * @param xpathMatcher 630 * the xpath matcher that determines if a fallback value 631 * is necessary 632 * @param rbPath 633 * the rb path that the fallback value is for 634 * @param fallbackValue 635 * the fallback value 636 */ addFallback(Finder xpathMatcher, String rbPath, String fallbackValue)637 private void addFallback(Finder xpathMatcher, String rbPath, String fallbackValue) { 638 ArrayList<StringBuffer> args = new ArrayList<StringBuffer>(); 639 int numBraces = 0; 640 int argNum = 0; 641 // Create RB path matcher and xpath replacement. 642 // WARNING: doesn't currently take lookaround groups into account. 643 StringBuffer xpathReplacement = new StringBuffer(); 644 for (char c : xpathMatcher.toString().toCharArray()) { 645 boolean isBrace = false; 646 if (c == '(') { 647 numBraces++; 648 argNum++; 649 args.add(new StringBuffer()); 650 isBrace = true; 651 if (numBraces == 1) { 652 xpathReplacement.append('$').append(argNum); 653 } 654 } 655 if (numBraces > 0) { 656 for (int i = args.size() - numBraces; i < args.size(); i++) { 657 args.get(i).append(c); 658 } 659 } 660 if (c == ')') { 661 numBraces--; 662 isBrace = true; 663 } 664 if (!isBrace && numBraces == 0) { 665 xpathReplacement.append(c); 666 } 667 } 668 String fallbackXpath = xpathReplacement.toString().replaceAll("\\\\\\[", "["); 669 if (fallbackXpath.contains("(")) { 670 System.err.println("Warning: malformed xpath " + fallbackXpath); 671 } 672 673 // Create rb matcher. 674 Matcher matcher = ARGUMENT.matcher(rbPath); 675 StringBuffer rbPattern = new StringBuffer(); 676 int lastIndex = 0; 677 List<Integer> argsUsed = new ArrayList<Integer>(); 678 while (matcher.find()) { 679 rbPattern.append(rbPath.substring(lastIndex, matcher.start())); 680 argNum = Integer.parseInt(matcher.group(1)); 681 rbPattern.append(args.get(argNum - 1)); 682 argsUsed.add(argNum); 683 lastIndex = matcher.end(); 684 } 685 rbPattern.append(rbPath.substring(lastIndex)); 686 FallbackInfo info = new FallbackInfo(argsUsed, args.size()); 687 info.addItem(xpathMatcher, fallbackXpath, fallbackValue.split("\\s")); 688 fallbackConverter.add(new RegexFinder(rbPattern.toString()), info); 689 } 690 addFallbackValues(Map<String, CldrArray> pathValueMap)691 void addFallbackValues(Map<String, CldrArray> pathValueMap) { 692 addFallbackValues(null, pathValueMap); 693 } 694 695 /** 696 * Checks if any fallback values are required and adds them to the specified 697 * map. 698 * 699 * @param pathValueMap 700 */ addFallbackValues(CLDRFile cldrFile, Map<String, CldrArray> pathValueMap)701 void addFallbackValues(CLDRFile cldrFile, Map<String, CldrArray> pathValueMap) { 702 RegexLookup<FallbackInfo> fallbackConverter = getFallbackConverter(); 703 for (String rbPath : pathValueMap.keySet()) { 704 Output<String[]> arguments = new Output<String[]>(); 705 FallbackInfo fallbackInfo = fallbackConverter.get(rbPath, null, arguments); 706 if (fallbackInfo == null) continue; 707 CldrArray values = pathValueMap.get(rbPath); 708 for (R3<Finder, String, List<String>> info : fallbackInfo) { 709 if (!values.findKey(info.get0())) { 710 // The fallback xpath is just for sorting purposes. 711 String fallbackXpath = processString(info.get1(), 712 fallbackInfo.getArgumentsForXpath(arguments.value)); 713 // Sanity check. 714 if (fallbackXpath.contains("$")) { 715 System.err.println("Warning: " + fallbackXpath + " for " + rbPath + 716 " still contains unreplaced arguments."); 717 } 718 List<String> fallbackValues = info.get2(); 719 List<String> valueList = new ArrayList<String>(); 720 for (String value : fallbackValues) { 721 value = processString(value, arguments.value); 722 // Value is an xpath, so get real value from CLDRFile. 723 if (value.startsWith("//") && cldrFile != null) { 724 value = cldrFile.getStringValue(cldrVariables.replace(value)); 725 } 726 valueList.add(value); 727 } 728 values.put(fallbackXpath, valueList, null); 729 } 730 } 731 } 732 } 733 getCldrArray(String key, Map<String, CldrArray> pathValueMap)734 static CldrArray getCldrArray(String key, Map<String, CldrArray> pathValueMap) { 735 CldrArray cldrArray = pathValueMap.get(key); 736 if (cldrArray == null) { 737 cldrArray = new CldrArray(); 738 pathValueMap.put(key, cldrArray); 739 } 740 return cldrArray; 741 } 742 } 743