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 43 @Override getValueAtDPath(String xpath)44 public String getValueAtDPath(String xpath) { 45 return xpath_value.get(xpath); 46 } 47 getValueAtDPathSkippingInheritanceMarker(String xpath)48 public String getValueAtDPathSkippingInheritanceMarker(String xpath) { 49 String result = xpath_value.get(xpath); 50 return CldrUtility.INHERITANCE_MARKER.equals(result) ? null : result; 51 } 52 53 @Override getFullPathAtDPath(String xpath)54 public String getFullPathAtDPath(String xpath) { 55 String result = xpath_fullXPath.get(xpath); 56 if (result != null) return result; 57 if (xpath_value.get(xpath) != null) return xpath; // we don't store duplicates 58 // System.err.println("WARNING: "+getLocaleID()+": path not present in data: " + xpath); 59 // return xpath; 60 return null; // throw new IllegalArgumentException("Path not present in data: " + xpath); 61 } 62 63 @Override getXpathComments()64 public Comments getXpathComments() { 65 return xpath_comments; 66 } 67 68 @Override setXpathComments(Comments xpath_comments)69 public void setXpathComments(Comments xpath_comments) { 70 this.xpath_comments = xpath_comments; 71 } 72 73 // public void putPathValue(String xpath, String value) { 74 // if (locked) throw new UnsupportedOperationException("Attempt to modify locked object"); 75 // String distinguishingXPath = CLDRFile.getDistinguishingXPath(xpath, fixedPath); 76 // xpath_value.put(distinguishingXPath, value); 77 // if (!fixedPath[0].equals(distinguishingXPath)) { 78 // xpath_fullXPath.put(distinguishingXPath, fixedPath[0]); 79 // } 80 // } 81 @Override removeValueAtDPath(String distinguishingXPath)82 public void removeValueAtDPath(String distinguishingXPath) { 83 String oldValue = xpath_value.get(distinguishingXPath); 84 xpath_value.remove(distinguishingXPath); 85 xpath_fullXPath.remove(distinguishingXPath); 86 updateValuePathMapping(distinguishingXPath, oldValue, null); 87 } 88 89 @Override iterator()90 public Iterator<String> iterator() { // must be unmodifiable or locked 91 return Collections.unmodifiableSet(xpath_value.keySet()).iterator(); 92 } 93 94 @Override freeze()95 public XMLSource freeze() { 96 locked = true; 97 return this; 98 } 99 100 @Override cloneAsThawed()101 public XMLSource cloneAsThawed() { 102 SimpleXMLSource result = (SimpleXMLSource) super.cloneAsThawed(); 103 result.xpath_comments = (Comments) result.xpath_comments.clone(); 104 result.xpath_fullXPath = CldrUtility.newConcurrentHashMap(result.xpath_fullXPath); 105 result.xpath_value = CldrUtility.newConcurrentHashMap(result.xpath_value); 106 return result; 107 } 108 109 @Override putFullPathAtDPath(String distinguishingXPath, String fullxpath)110 public void putFullPathAtDPath(String distinguishingXPath, String fullxpath) { 111 xpath_fullXPath.put(distinguishingXPath, fullxpath); 112 } 113 114 @Override putValueAtDPath(String distinguishingXPath, String value)115 public void putValueAtDPath(String distinguishingXPath, String value) { 116 String oldValue = xpath_value.get(distinguishingXPath); 117 xpath_value.put(distinguishingXPath, value); 118 updateValuePathMapping(distinguishingXPath, oldValue, value); 119 } 120 updateValuePathMapping(String distinguishingXPath, String oldValue, String newValue)121 private void updateValuePathMapping(String distinguishingXPath, String oldValue, String newValue) { 122 synchronized (VALUE_TO_PATH_MUTEX) { 123 if (VALUE_TO_PATH != null) { 124 if (oldValue != null) { 125 VALUE_TO_PATH.remove(normalize(oldValue), distinguishingXPath); 126 } 127 if (newValue != null) { 128 VALUE_TO_PATH.put(normalize(newValue), distinguishingXPath); 129 } 130 } 131 } 132 } 133 134 @Override getPathsWithValue(String valueToMatch, String pathPrefix, Set<String> result)135 public void getPathsWithValue(String valueToMatch, String pathPrefix, Set<String> result) { 136 // build a Relation mapping value to paths, if needed 137 synchronized (VALUE_TO_PATH_MUTEX) { 138 if (VALUE_TO_PATH == null) { 139 VALUE_TO_PATH = Relation.of(new HashMap<String, Set<String>>(), HashSet.class); 140 for (Iterator<String> it = iterator(); it.hasNext();) { 141 String path = it.next(); 142 String value1 = getValueAtDPathSkippingInheritanceMarker(path); 143 if (value1 == null) { 144 continue; 145 } 146 String value = normalize(value1); 147 VALUE_TO_PATH.put(value, path); 148 } 149 } 150 Set<String> paths = VALUE_TO_PATH.getAll(normalize(valueToMatch)); 151 if (paths == null) { 152 return; 153 } 154 if (pathPrefix == null || pathPrefix.length() == 0) { 155 result.addAll(paths); 156 return; 157 } 158 for (String path : paths) { 159 if (path.startsWith(pathPrefix)) { 160 // if (altPath.originalPath.startsWith(altPrefix.originalPath)) { 161 result.add(path); 162 } 163 } 164 } 165 } 166 167 static final Normalizer2 NFKCCF = Normalizer2.getNFKCCasefoldInstance(); 168 static final Normalizer2 NFKC = Normalizer2.getNFKCInstance(); 169 170 // The following includes letters, marks, numbers, currencies, and *selected* symbols/punctuation 171 static final UnicodeSet NON_ALPHANUM = new UnicodeSet("[^[:L:][:M:][:N:][:Sc:]/+\\-°′″%‰‱٪؉−⍰()⊕☉]").freeze(); 172 normalize(String valueToMatch)173 public static String normalize(String valueToMatch) { 174 return replace(NON_ALPHANUM, NFKCCF.normalize(valueToMatch), ""); 175 } 176 normalizeCaseSensitive(String valueToMatch)177 public static String normalizeCaseSensitive(String valueToMatch) { 178 return replace(NON_ALPHANUM, NFKC.normalize(valueToMatch), ""); 179 } 180 replace(UnicodeSet unicodeSet, String valueToMatch, String substitute)181 public static String replace(UnicodeSet unicodeSet, String valueToMatch, String substitute) { 182 // handle patterns 183 if (valueToMatch.contains("{")) { 184 valueToMatch = PLACEHOLDER.matcher(valueToMatch).replaceAll("⍰").trim(); 185 } 186 StringBuilder b = null; // delay creating until needed 187 for (int i = 0; i < valueToMatch.length(); ++i) { 188 int cp = valueToMatch.codePointAt(i); 189 if (unicodeSet.contains(cp)) { 190 if (b == null) { 191 b = new StringBuilder(); 192 b.append(valueToMatch.substring(0, i)); // copy the start 193 } 194 if (substitute.length() != 0) { 195 b.append(substitute); 196 } 197 } else if (b != null) { 198 b.appendCodePoint(cp); 199 } 200 if (cp > 0xFFFF) { // skip end of supplemental character 201 ++i; 202 } 203 } 204 if (b != null) { 205 valueToMatch = b.toString(); 206 } 207 return valueToMatch; 208 } 209 210 static final Pattern PLACEHOLDER = PatternCache.get("\\{\\d\\}"); 211 setDtdVersionInfo(VersionInfo dtdVersionInfo)212 public void setDtdVersionInfo(VersionInfo dtdVersionInfo) { 213 this.dtdVersionInfo = dtdVersionInfo; 214 } 215 216 @Override getDtdVersionInfo()217 public VersionInfo getDtdVersionInfo() { 218 return dtdVersionInfo; 219 } 220 }