1 package org.unicode.cldr.util; 2 3 import java.util.Collections; 4 import java.util.HashMap; 5 import java.util.HashSet; 6 import java.util.Iterator; 7 import java.util.Map; 8 import java.util.Set; 9 import java.util.regex.Pattern; 10 11 import org.unicode.cldr.util.XPathParts.Comments; 12 13 import com.ibm.icu.impl.Relation; 14 import com.ibm.icu.text.Normalizer2; 15 import com.ibm.icu.text.UnicodeSet; 16 import com.ibm.icu.util.VersionInfo; 17 18 public class SimpleXMLSource extends XMLSource { 19 private Map<String, String> xpath_value = CldrUtility.newConcurrentHashMap(); 20 private Map<String, String> xpath_fullXPath = CldrUtility.newConcurrentHashMap(); 21 private Comments xpath_comments = new Comments(); // map from paths to comments. 22 private Relation<String, String> VALUE_TO_PATH = null; 23 private Object VALUE_TO_PATH_MUTEX = new Object(); 24 private VersionInfo dtdVersionInfo; 25 SimpleXMLSource(String localeID)26 public SimpleXMLSource(String localeID) { 27 this.setLocaleID(localeID); 28 } 29 30 /** 31 * Create a shallow, locked copy of another XMLSource. 32 * 33 * @param copyAsLockedFrom 34 */ SimpleXMLSource(SimpleXMLSource copyAsLockedFrom)35 protected SimpleXMLSource(SimpleXMLSource copyAsLockedFrom) { 36 this.xpath_value = copyAsLockedFrom.xpath_value; 37 this.xpath_fullXPath = copyAsLockedFrom.xpath_fullXPath; 38 this.xpath_comments = copyAsLockedFrom.xpath_comments; 39 this.setLocaleID(copyAsLockedFrom.getLocaleID()); 40 locked = true; 41 } 42 getValueAtDPath(String xpath)43 public String getValueAtDPath(String xpath) { 44 return (String) xpath_value.get(xpath); 45 } 46 getFullPathAtDPath(String xpath)47 public String getFullPathAtDPath(String xpath) { 48 String result = (String) xpath_fullXPath.get(xpath); 49 if (result != null) return result; 50 if (xpath_value.get(xpath) != null) return xpath; // we don't store duplicates 51 // System.err.println("WARNING: "+getLocaleID()+": path not present in data: " + xpath); 52 // return xpath; 53 return null; // throw new IllegalArgumentException("Path not present in data: " + xpath); 54 } 55 getXpathComments()56 public Comments getXpathComments() { 57 return xpath_comments; 58 } 59 setXpathComments(Comments xpath_comments)60 public void setXpathComments(Comments xpath_comments) { 61 this.xpath_comments = xpath_comments; 62 } 63 64 // public void putPathValue(String xpath, String value) { 65 // if (locked) throw new UnsupportedOperationException("Attempt to modify locked object"); 66 // String distinguishingXPath = CLDRFile.getDistinguishingXPath(xpath, fixedPath); 67 // xpath_value.put(distinguishingXPath, value); 68 // if (!fixedPath[0].equals(distinguishingXPath)) { 69 // xpath_fullXPath.put(distinguishingXPath, fixedPath[0]); 70 // } 71 // } removeValueAtDPath(String distinguishingXPath)72 public void removeValueAtDPath(String distinguishingXPath) { 73 String oldValue = xpath_value.get(distinguishingXPath); 74 xpath_value.remove(distinguishingXPath); 75 xpath_fullXPath.remove(distinguishingXPath); 76 updateValuePathMapping(distinguishingXPath, oldValue, null); 77 } 78 iterator()79 public Iterator<String> iterator() { // must be unmodifiable or locked 80 return Collections.unmodifiableSet(xpath_value.keySet()).iterator(); 81 } 82 freeze()83 public XMLSource freeze() { 84 locked = true; 85 return this; 86 } 87 cloneAsThawed()88 public XMLSource cloneAsThawed() { 89 SimpleXMLSource result = (SimpleXMLSource) super.cloneAsThawed(); 90 result.xpath_comments = (Comments) result.xpath_comments.clone(); 91 result.xpath_fullXPath = CldrUtility.newConcurrentHashMap(result.xpath_fullXPath); 92 result.xpath_value = CldrUtility.newConcurrentHashMap(result.xpath_value); 93 return result; 94 } 95 putFullPathAtDPath(String distinguishingXPath, String fullxpath)96 public void putFullPathAtDPath(String distinguishingXPath, String fullxpath) { 97 xpath_fullXPath.put(distinguishingXPath, fullxpath); 98 } 99 putValueAtDPath(String distinguishingXPath, String value)100 public void putValueAtDPath(String distinguishingXPath, String value) { 101 String oldValue = xpath_value.get(distinguishingXPath); 102 xpath_value.put(distinguishingXPath, value); 103 updateValuePathMapping(distinguishingXPath, oldValue, value); 104 } 105 updateValuePathMapping(String distinguishingXPath, String oldValue, String newValue)106 private void updateValuePathMapping(String distinguishingXPath, String oldValue, String newValue) { 107 synchronized (VALUE_TO_PATH_MUTEX) { 108 if (VALUE_TO_PATH != null) { 109 if (oldValue != null) { 110 VALUE_TO_PATH.remove(normalize(oldValue), distinguishingXPath); 111 } 112 if (newValue != null) { 113 VALUE_TO_PATH.put(normalize(newValue), distinguishingXPath); 114 } 115 } 116 } 117 } 118 119 @Override getPathsWithValue(String valueToMatch, String pathPrefix, Set<String> result)120 public void getPathsWithValue(String valueToMatch, String pathPrefix, Set<String> result) { 121 // build a Relation mapping value to paths, if needed 122 synchronized (VALUE_TO_PATH_MUTEX) { 123 if (VALUE_TO_PATH == null) { 124 VALUE_TO_PATH = Relation.of(new HashMap<String, Set<String>>(), HashSet.class); 125 for (Iterator<String> it = iterator(); it.hasNext();) { 126 String path = it.next(); 127 String value = normalize(getValueAtDPath(path)); 128 VALUE_TO_PATH.put(value, path); 129 } 130 } 131 Set<String> paths = VALUE_TO_PATH.getAll(normalize(valueToMatch)); 132 if (paths == null) { 133 return; 134 } 135 if (pathPrefix == null || pathPrefix.length() == 0) { 136 result.addAll(paths); 137 return; 138 } 139 for (String path : paths) { 140 if (path.startsWith(pathPrefix)) { 141 // if (altPath.originalPath.startsWith(altPrefix.originalPath)) { 142 result.add(path); 143 } 144 } 145 } 146 } 147 148 static final Normalizer2 NFKCCF = Normalizer2.getNFKCCasefoldInstance(); 149 static final Normalizer2 NFKC = Normalizer2.getNFKCInstance(); 150 151 // The following includes letters, marks, numbers, currencies, and *selected* symbols/punctuation 152 static final UnicodeSet NON_ALPHANUM = new UnicodeSet("[^[:L:][:M:][:N:][:Sc:]/+\\-°′″%‰٪؉−⍰()]").freeze(); 153 normalize(String valueToMatch)154 public static String normalize(String valueToMatch) { 155 return replace(NON_ALPHANUM, NFKCCF.normalize(valueToMatch), ""); 156 } 157 normalizeCaseSensitive(String valueToMatch)158 public static String normalizeCaseSensitive(String valueToMatch) { 159 return replace(NON_ALPHANUM, NFKC.normalize(valueToMatch), ""); 160 } 161 replace(UnicodeSet unicodeSet, String valueToMatch, String substitute)162 public static String replace(UnicodeSet unicodeSet, String valueToMatch, String substitute) { 163 // handle patterns 164 if (valueToMatch.contains("{")) { 165 valueToMatch = PLACEHOLDER.matcher(valueToMatch).replaceAll("⍰").trim(); 166 } 167 StringBuilder b = null; // delay creating until needed 168 for (int i = 0; i < valueToMatch.length(); ++i) { 169 int cp = valueToMatch.codePointAt(i); 170 if (unicodeSet.contains(cp)) { 171 if (b == null) { 172 b = new StringBuilder(); 173 b.append(valueToMatch.substring(0, i)); // copy the start 174 } 175 if (substitute.length() != 0) { 176 b.append(substitute); 177 } 178 } else if (b != null) { 179 b.appendCodePoint(cp); 180 } 181 if (cp > 0xFFFF) { // skip end of supplemental character 182 ++i; 183 } 184 } 185 if (b != null) { 186 valueToMatch = b.toString(); 187 } 188 return valueToMatch; 189 } 190 191 static final Pattern PLACEHOLDER = PatternCache.get("\\{\\d\\}"); 192 setDtdVersionInfo(VersionInfo dtdVersionInfo)193 public void setDtdVersionInfo(VersionInfo dtdVersionInfo) { 194 this.dtdVersionInfo = dtdVersionInfo; 195 } 196 getDtdVersionInfo()197 public VersionInfo getDtdVersionInfo() { 198 return dtdVersionInfo; 199 } 200 }