xref: /aosp_15_r20/external/cldr/tools/cldr-code/src/main/java/org/unicode/cldr/util/LDMLUtilities.java (revision 912701f9769bb47905792267661f0baf2b85bed5)
1 /*
2  *******************************************************************************
3  * Copyright (C) 2004-2012, International Business Machines Corporation and    *
4  * others. All Rights Reserved.                                                *
5  *******************************************************************************
6  *
7  * Created on Jul 28, 2004
8  *
9  */
10 package org.unicode.cldr.util;
11 
12 import java.io.File;
13 import java.io.IOException;
14 import java.io.PrintWriter;
15 import java.util.ArrayList;
16 import java.util.HashMap;
17 import java.util.Iterator;
18 import java.util.List;
19 import java.util.Map;
20 import javax.xml.parsers.DocumentBuilder;
21 import javax.xml.parsers.DocumentBuilderFactory;
22 import javax.xml.transform.OutputKeys;
23 import javax.xml.transform.Transformer;
24 import javax.xml.transform.TransformerException;
25 import javax.xml.transform.TransformerFactory;
26 import javax.xml.transform.dom.DOMSource;
27 import javax.xml.transform.stream.StreamResult;
28 import javax.xml.xpath.XPath;
29 import javax.xml.xpath.XPathConstants;
30 import javax.xml.xpath.XPathExpression;
31 import javax.xml.xpath.XPathExpressionException;
32 import javax.xml.xpath.XPathFactory;
33 import org.unicode.cldr.icu.LDMLConstants;
34 import org.w3c.dom.Document;
35 import org.w3c.dom.NamedNodeMap;
36 import org.w3c.dom.Node;
37 import org.w3c.dom.NodeList;
38 import org.xml.sax.ErrorHandler;
39 import org.xml.sax.InputSource;
40 import org.xml.sax.SAXException;
41 import org.xml.sax.SAXParseException;
42 
43 /**
44  * @author ram
45  *     <p>TODO To change the template for this generated type comment go to Window - Preferences -
46  *     Java - Code Generation - Code and Comments
47  */
48 public class LDMLUtilities {
49 
50     public static final int XML = 0, TXT = 1;
51     private static final boolean DEBUG = false;
52 
53     /**
54      * Creates a fully resolved locale starting with root and
55      *
56      * @param sourceDir
57      * @param locale
58      * @return
59      */
getFullyResolvedLDML( String sourceDir, String locale, boolean ignoreRoot, boolean ignoreUnavailable, boolean ignoreIfNoneAvailable, boolean ignoreDraft)60     public static Document getFullyResolvedLDML(
61             String sourceDir,
62             String locale,
63             boolean ignoreRoot,
64             boolean ignoreUnavailable,
65             boolean ignoreIfNoneAvailable,
66             boolean ignoreDraft) {
67         return getFullyResolvedLDML(
68                 sourceDir,
69                 locale,
70                 ignoreRoot,
71                 ignoreUnavailable,
72                 ignoreIfNoneAvailable,
73                 ignoreDraft,
74                 null);
75     }
76 
getFullyResolvedLDML( String sourceDir, String locale, boolean ignoreRoot, boolean ignoreUnavailable, boolean ignoreIfNoneAvailable, boolean ignoreDraft, Map<String, String> stack)77     private static Document getFullyResolvedLDML(
78             String sourceDir,
79             String locale,
80             boolean ignoreRoot,
81             boolean ignoreUnavailable,
82             boolean ignoreIfNoneAvailable,
83             boolean ignoreDraft,
84             Map<String, String> stack) {
85         Document full = null;
86         if (stack != null) {
87             // For guarding against cicular references
88             String key = "SRC:" + sourceDir + File.separator + locale + ".xml";
89             if (stack.get(key) != null) {
90                 System.err.println("Found circular aliases! " + key);
91                 System.exit(-1);
92             }
93             stack.put(key, "");
94         }
95         // System.err.println("In getFullyResolvedLDML "+sourceDir + " " + locale);
96         try {
97             full = parse(sourceDir + File.separator + "root.xml", ignoreRoot);
98             /*
99              * Debugging
100              *
101              * Node[] list = getNodeArray(full, LDMLConstants.ALIAS);
102              * if(list.length>0){
103              * System.err.println("Aliases not resolved!. list.getLength() returned "+ list.length);
104              * }
105              */
106 
107             if (DEBUG) {
108                 try {
109                     java.io.OutputStreamWriter writer =
110                             new java.io.OutputStreamWriter(
111                                     new java.io.FileOutputStream(
112                                             "./" + File.separator + "root_debug.xml"),
113                                     "UTF-8");
114                     LDMLUtilities.printDOMTree(
115                             full,
116                             new PrintWriter(writer),
117                             "http://www.unicode.org/cldr/dtd/1.3/ldml.dtd",
118                             null);
119                     writer.flush();
120                 } catch (IOException e) {
121                     // throw the exceptionaway .. this is for debugging
122                 }
123             }
124         } catch (RuntimeException ex) {
125             if (!ignoreRoot) {
126                 throw ex;
127             }
128         }
129         int index = locale.indexOf(".xml");
130         if (index > -1) {
131             locale = locale.substring(0, index);
132         }
133         if (locale.equals("root")) {
134             full = resolveAliases(full, sourceDir, locale, ignoreDraft, stack);
135             return full;
136         }
137         String[] constituents = locale.split("_");
138         String loc = null;
139         boolean isAvailable = false;
140         // String lastLoc = "root";
141         for (int i = 0; i < constituents.length; i++) {
142             if (loc == null) {
143                 loc = constituents[i];
144             } else {
145                 loc = loc + "_" + constituents[i];
146             }
147             Document doc = null;
148 
149             // Try cache
150             // doc = readMergeCache(sourceDir, lastLoc, loc);
151             // if(doc == null) { ..
152             String fileName = sourceDir + File.separator + loc + ".xml";
153             File file = new File(fileName);
154             if (file.exists()) {
155                 isAvailable = true;
156                 doc = parseAndResolveAlias(fileName, loc, ignoreUnavailable);
157 
158                 /*
159                  * Debugging
160                  *
161                  * Node[] list = getNodeArray(doc, LDMLConstants.ALIAS);
162                  * if(list.length>0){
163                  * System.err.println("Aliases not resolved!. list.getLength() returned "+ list.length);
164                  * }
165                  */
166                 if (full == null) {
167                     full = doc;
168                 } else {
169                     StringBuffer xpath = new StringBuffer();
170                     mergeLDMLDocuments(full, doc, xpath, loc, sourceDir, ignoreDraft, false);
171                     if (DEBUG) {
172                         try {
173                             java.io.OutputStreamWriter writer =
174                                     new java.io.OutputStreamWriter(
175                                             new java.io.FileOutputStream(
176                                                     "./" + File.separator + loc + "_debug.xml"),
177                                             "UTF-8");
178                             LDMLUtilities.printDOMTree(
179                                     full,
180                                     new PrintWriter(writer),
181                                     "http://www.unicode.org/cldr/dtd/1.3/ldml.dtd",
182                                     null);
183                             writer.flush();
184                         } catch (IOException e) {
185                             // throw the exceptionaway .. this is for debugging
186                         }
187                     }
188                 }
189                 /*
190                  * debugging
191                  *
192                  * Node ec = getNode(full, "//ldml/characters/exemplarCharacters");
193                  * if(ec==null){
194                  * System.err.println("Could not find exemplarCharacters");
195                  * }else{
196                  * System.out.println("The chars are: "+ getNodeValue(ec));
197                  * }
198                  */
199 
200                 // writeMergeCache(sourceDir, lastLoc, loc, full);
201                 // lastLoc = loc;
202             } else {
203                 if (!ignoreUnavailable) {
204                     throw new RuntimeException("Could not find: " + fileName);
205                 }
206             }
207             // TODO: investigate if we really need to revalidate the DOM tree!
208             // full = revalidate(full, locale);
209         }
210 
211         if (ignoreIfNoneAvailable == true && isAvailable == false) {
212             return null;
213         }
214 
215         if (DEBUG) {
216             try {
217                 java.io.OutputStreamWriter writer =
218                         new java.io.OutputStreamWriter(
219                                 new java.io.FileOutputStream(
220                                         "./" + File.separator + locale + "_ba_debug.xml"),
221                                 "UTF-8");
222                 LDMLUtilities.printDOMTree(
223                         full,
224                         new PrintWriter(writer),
225                         "http://www.unicode.org/cldr/dtd/1.3/ldml.dtd",
226                         null);
227                 writer.flush();
228             } catch (IOException e) {
229                 // throw the exceptionaway .. this is for debugging
230             }
231         }
232         // get the real locale name
233         locale = getLocaleName(full);
234         // Resolve the aliases once the data is built
235         full = resolveAliases(full, sourceDir, locale, ignoreDraft, stack);
236 
237         if (DEBUG) {
238             try {
239                 java.io.OutputStreamWriter writer =
240                         new java.io.OutputStreamWriter(
241                                 new java.io.FileOutputStream(
242                                         "./" + File.separator + locale + "_aa_debug.xml"),
243                                 "UTF-8");
244                 LDMLUtilities.printDOMTree(
245                         full,
246                         new PrintWriter(writer),
247                         "http://www.unicode.org/cldr/dtd/1.3/ldml.dtd",
248                         null);
249                 writer.flush();
250             } catch (IOException e) {
251                 // throw the exceptionaway .. this is for debugging
252             }
253         }
254         return full;
255     }
256 
getLocaleName(Document doc)257     public static String getLocaleName(Document doc) {
258 
259         Node ln = LDMLUtilities.getNode(doc, "//ldml/identity/language");
260         Node tn = LDMLUtilities.getNode(doc, "//ldml/identity/territory");
261         Node sn = LDMLUtilities.getNode(doc, "//ldml/identity/script");
262         Node vn = LDMLUtilities.getNode(doc, "//ldml/identity/variant");
263 
264         StringBuffer locName = new StringBuffer();
265         String lang = LDMLUtilities.getAttributeValue(ln, LDMLConstants.TYPE);
266         if (lang != null) {
267             locName.append(lang);
268         } else {
269             throw new IllegalArgumentException(
270                     "Did not get any value for language node from identity.");
271         }
272         if (sn != null) {
273             String script = LDMLUtilities.getAttributeValue(sn, LDMLConstants.TYPE);
274             if (script != null) {
275                 locName.append("_");
276                 locName.append(script);
277             }
278         }
279         if (tn != null) {
280             String terr = LDMLUtilities.getAttributeValue(tn, LDMLConstants.TYPE);
281             if (terr != null) {
282                 locName.append("_");
283                 locName.append(terr);
284             }
285         }
286         if (vn != null) {
287             String variant = LDMLUtilities.getAttributeValue(vn, LDMLConstants.TYPE);
288             if (variant != null && tn != null) {
289                 locName.append("_");
290                 locName.append(variant);
291             }
292         }
293         return locName.toString();
294     }
295 
296     // revalidate wasn't called anywhere.
297     // TODO: if needed, reimplement using DOM level 3
298     /*
299      * public static Document revalidate(Document doc, String fileName){
300      * // what a waste!!
301      * // to revalidate an in-memory DOM tree we need to first
302      * // serialize it to byte array and read it back again.
303      * // in DOM level 3 implementation there is API to validate
304      * // in-memory DOM trees but the latest implementation of Xerces
305      * // can only validate against schemas not DTDs!!!
306      * try{
307      * // revalidate the document
308      * Serializer serializer = SerializerFactory.getSerializer(OutputProperties.getDefaultMethodProperties("xml"));
309      * ByteArrayOutputStream os = new ByteArrayOutputStream();
310      * serializer.setOutputStream(os);
311      * DOMSerializer ds = serializer.asDOMSerializer();
312      * //ds.serialize(doc);
313      * os.flush();
314      * ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
315      * doc = parse(new InputSource(is),"Fully resolved: "+fileName, false);
316      * return doc;
317      * }catch(IOException ex){
318      * throw new RuntimeException(ex);
319      * }
320      * }
321      */
322     @Deprecated
convertXPath2ICU(Node alias, Node namespaceNode, StringBuffer fullPath)323     public static String convertXPath2ICU(Node alias, Node namespaceNode, StringBuffer fullPath)
324             throws TransformerException {
325         StringBuilder sb = new StringBuilder(fullPath.toString());
326         return convertXPath2ICU(alias, namespaceNode, sb);
327     }
328 
convertXPath2ICU(Node alias, Node namespaceNode, StringBuilder fullPath)329     public static String convertXPath2ICU(Node alias, Node namespaceNode, StringBuilder fullPath)
330             throws TransformerException {
331         Node context = alias.getParentNode();
332         StringBuffer icu = new StringBuffer();
333         String source = getAttributeValue(alias, LDMLConstants.SOURCE);
334         String xpath = getAttributeValue(alias, LDMLConstants.PATH);
335 
336         // make sure that the xpaths are valid
337         if (namespaceNode == null) {
338             XPathAPI_eval(context, fullPath.toString());
339             if (xpath != null) {
340                 XPathAPI_eval(context, xpath);
341             }
342         } else {
343             XPathAPI_eval(context, fullPath.toString(), namespaceNode);
344             if (xpath != null) {
345                 XPathAPI_eval(context, xpath, namespaceNode);
346             }
347         }
348         if (source.equals(LDMLConstants.LOCALE)) {
349             icu.append("/");
350             icu.append(source.toUpperCase());
351         } else {
352             icu.append(source);
353         }
354         if (xpath != null) {
355             StringBuilder resolved = XPathTokenizer.relativeToAbsolute(xpath, fullPath);
356             // make sure that fullPath is not corrupted!
357             XPathAPI_eval(context, fullPath.toString());
358 
359             // TODO .. do the conversion
360             XPathTokenizer tokenizer = new XPathTokenizer(resolved.toString());
361 
362             String token = tokenizer.nextToken();
363             while (token != null) {
364                 if (!token.equals("ldml")) {
365                     String equiv = getICUEquivalent(token);
366                     if (equiv == null) {
367                         throw new IllegalArgumentException(
368                                 "Could not find ICU equivalent for token: " + token);
369                     }
370                     if (equiv.length() > 0) {
371                         icu.append("/");
372                         icu.append(equiv);
373                     }
374                 }
375                 token = tokenizer.nextToken();
376             }
377         }
378         return icu.toString();
379     }
380 
convertXPath2ICU( String source, String xpath, String basePath, String fullPath)381     public static String convertXPath2ICU(
382             String source, String xpath, String basePath, String fullPath)
383             throws TransformerException {
384         // Node context = alias.getParentNode();
385         StringBuffer icu = new StringBuffer();
386 
387         // TODO: make sure that the xpaths are valid. How?
388 
389         if (source.equals(LDMLConstants.LOCALE)) {
390             icu.append("/");
391             icu.append(source.toUpperCase());
392         } else {
393             icu.append(source);
394         }
395 
396         if (xpath != null) {
397             StringBuilder fullPathBuffer = new StringBuilder(fullPath);
398             StringBuilder resolved = XPathTokenizer.relativeToAbsolute(xpath, fullPathBuffer);
399             // TODO: make sure that fullPath is not corrupted! How?
400             // XPathAPI.eval(context, fullPath.toString());
401 
402             // TODO .. do the conversion
403             XPathTokenizer tokenizer = new XPathTokenizer(resolved);
404 
405             String token = tokenizer.nextToken();
406             while (token != null) {
407                 if (!token.equals("ldml")) {
408                     String equiv = getICUEquivalent(token);
409                     if (equiv == null) {
410                         throw new IllegalArgumentException(
411                                 "Could not find ICU equivalent for token: " + token);
412                     }
413                     if (equiv.length() > 0) {
414                         icu.append("/");
415                         icu.append(equiv);
416                     }
417                 }
418                 token = tokenizer.nextToken();
419             }
420         }
421         return icu.toString();
422     }
423 
getDayIndexAsString(String type)424     public static String getDayIndexAsString(String type) {
425         if (type.equals("sun")) {
426             return "0";
427         } else if (type.equals("mon")) {
428             return "1";
429         } else if (type.equals("tue")) {
430             return "2";
431         } else if (type.equals("wed")) {
432             return "3";
433         } else if (type.equals("thu")) {
434             return "4";
435         } else if (type.equals("fri")) {
436             return "5";
437         } else if (type.equals("sat")) {
438             return "6";
439         } else {
440             throw new IllegalArgumentException("Unknown type: " + type);
441         }
442     }
443 
getMonthIndexAsString(String type)444     public static String getMonthIndexAsString(String type) {
445         return Integer.toString(Integer.parseInt(type) - 1);
446     }
447 
getICUEquivalent(String token)448     private static String getICUEquivalent(String token) {
449         int index = 0;
450         if (token.indexOf(LDMLConstants.LDN) > -1) {
451             return "";
452         } else if (token.indexOf(LDMLConstants.LANGUAGES) > -1) {
453             return "Languages";
454         } else if (token.indexOf(LDMLConstants.LANGUAGE) > -1) {
455             return getAttributeValue(token, LDMLConstants.TYPE);
456         } else if (token.indexOf(LDMLConstants.TERRITORIES) > -1) {
457             return "Countries";
458         } else if (token.indexOf(LDMLConstants.TERRITORY) > -1) {
459             return getAttributeValue(token, LDMLConstants.TYPE);
460         } else if (token.indexOf(LDMLConstants.SCRIPTS) > -1) {
461             return "Scripts";
462         } else if (token.indexOf(LDMLConstants.SCRIPT) > -1) {
463             return getAttributeValue(token, LDMLConstants.TYPE);
464         } else if (token.indexOf(LDMLConstants.VARIANTS) > -1) {
465             return "Variants";
466         } else if (token.indexOf(LDMLConstants.VARIANT) > -1) {
467             return getAttributeValue(token, LDMLConstants.TYPE);
468         } else if (token.indexOf(LDMLConstants.KEYS) > -1) {
469             return "Keys";
470         } else if (token.indexOf(LDMLConstants.KEY) > -1) {
471             return getAttributeValue(token, LDMLConstants.TYPE);
472         } else if (token.indexOf(LDMLConstants.TYPES) > -1) {
473             return "Types";
474         } else if ((index = token.indexOf(LDMLConstants.TYPE)) > -1
475                 && token.charAt(index - 1) != '@') {
476             String type = getAttributeValue(token, LDMLConstants.TYPE);
477             String key = getAttributeValue(token, LDMLConstants.KEY);
478             return type + "/" + key;
479         } else if (token.indexOf(LDMLConstants.LAYOUT) > -1) {
480             return "Layout";
481         } else if (token.indexOf(LDMLConstants.ORIENTATION) > -1) {
482             // TODO fix this
483         } else if (token.indexOf(LDMLConstants.CONTEXT_TRANSFORMS) > -1) {
484             return "contextTransforms";
485         } else if (token.indexOf(LDMLConstants.CONTEXT_TRANSFORM_USAGE) > -1) {
486             return getAttributeValue(token, LDMLConstants.TYPE);
487         } else if (token.indexOf(LDMLConstants.CHARACTERS) > -1) {
488             return "";
489         } else if (token.indexOf(LDMLConstants.EXEMPLAR_CHARACTERS) > -1) {
490             return "ExemplarCharacters";
491         } else if (token.indexOf(LDMLConstants.MEASUREMENT) > -1) {
492             return "";
493         } else if (token.indexOf(LDMLConstants.MS) > -1) {
494             return "MeasurementSystem";
495         } else if (token.indexOf(LDMLConstants.PAPER_SIZE) > -1) {
496             return "PaperSize";
497         } else if (token.indexOf(LDMLConstants.HEIGHT) > -1) {
498             return "0";
499         } else if (token.indexOf(LDMLConstants.WIDTH) > -1) {
500             return "1";
501         } else if (token.indexOf(LDMLConstants.DATES) > -1) {
502             return "";
503         } else if (token.indexOf(LDMLConstants.LPC) > -1) {
504             return "localPatternCharacters";
505         } else if (token.indexOf(LDMLConstants.CALENDARS) > -1) {
506             return "calendar";
507         } else if (token.indexOf(LDMLConstants.DEFAULT) > -1) {
508             return "default";
509         } else if (token.indexOf(LDMLConstants.CALENDAR) > -1) {
510             return getAttributeValue(token, LDMLConstants.TYPE);
511         } else if (token.indexOf(LDMLConstants.ERAS) > -1) {
512             return "eras";
513         } else if (token.indexOf(LDMLConstants.ERAABBR) > -1) {
514             return "abbreviated";
515         } else if (token.indexOf(LDMLConstants.ERA) > -1) {
516             return getAttributeValue(token, LDMLConstants.TYPE);
517         } else if (token.indexOf(LDMLConstants.NUMBERS) > -1) {
518             // TODO fix this
519         } else if (token.indexOf(LDMLConstants.SYMBOLS) > -1) {
520             return "NumberElements";
521         } else if (token.indexOf(LDMLConstants.DATE_FORMATS) > -1) {
522             // TODO fix this
523         } else if (token.indexOf(LDMLConstants.DFL) > -1) {
524             // TODO fix this
525         } else if (token.indexOf(LDMLConstants.DATE_FORMAT) > -1) {
526             // TODO fix this
527         } else if (token.indexOf(LDMLConstants.TIME_FORMATS) > -1) {
528             // TODO fix this
529         } else if (token.indexOf(LDMLConstants.TFL) > -1) {
530             // TODO fix this
531         } else if (token.indexOf(LDMLConstants.TIME_FORMAT) > -1) {
532             // TODO fix this
533         } else if (token.indexOf(LDMLConstants.DATE_TIME_FORMATS) > -1) {
534             // TODO fix this
535             return "DateTimePatterns";
536         } else if (token.indexOf(LDMLConstants.INTVL_FMTS) > -1) {
537             return "intervalFormats";
538             // TODO fix this
539         } else if (token.indexOf(LDMLConstants.DTFL) > -1) {
540             // TODO fix this
541         } else if (token.indexOf(LDMLConstants.DATE_TIME_FORMAT) > -1) {
542             // TODO fix this
543         } else if (token.indexOf(LDMLConstants.INTVL_FMTS) > -1) {
544             // TODO fix this
545         } else if (token.indexOf(LDMLConstants.CYCLIC_NAME_SETS) > -1) {
546             return "cyclicNameSets";
547         } else if (token.indexOf(LDMLConstants.CYCLIC_NAME_SET) > -1) {
548             return getAttributeValue(token, LDMLConstants.TYPE);
549         } else if (token.indexOf(LDMLConstants.CYCLIC_NAME_CONTEXT) > -1) {
550             return getAttributeValue(token, LDMLConstants.TYPE);
551         } else if (token.indexOf(LDMLConstants.CYCLIC_NAME_WIDTH) > -1) {
552             return getAttributeValue(token, LDMLConstants.TYPE);
553         } else if (token.indexOf(LDMLConstants.CYCLIC_NAME) > -1) {
554             String valStr = getAttributeValue(token, LDMLConstants.TYPE);
555             return getMonthIndexAsString(valStr);
556         } else if (token.indexOf(LDMLConstants.MONTHS) > -1) {
557             return "monthNames";
558         } else if (token.indexOf(LDMLConstants.MONTH_PATTERNS) > -1) {
559             return "monthPatterns";
560         } else if (token.indexOf(LDMLConstants.MONTH_PATTERN_CONTEXT) > -1) {
561             return getAttributeValue(token, LDMLConstants.TYPE);
562         } else if (token.indexOf(LDMLConstants.MONTH_PATTERN_WIDTH) > -1) {
563             return getAttributeValue(token, LDMLConstants.TYPE);
564         } else if (token.indexOf(LDMLConstants.MONTH_PATTERN) > -1) {
565             return getAttributeValue(token, LDMLConstants.TYPE);
566         } else if (token.indexOf(LDMLConstants.MONTH_CONTEXT) > -1) {
567             return getAttributeValue(token, LDMLConstants.TYPE);
568         } else if (token.indexOf(LDMLConstants.MONTH_WIDTH) > -1) {
569             return getAttributeValue(token, LDMLConstants.TYPE);
570         } else if (token.indexOf(LDMLConstants.MONTH) > -1) {
571             String valStr = getAttributeValue(token, LDMLConstants.TYPE);
572             return getMonthIndexAsString(valStr);
573         } else if (token.indexOf(LDMLConstants.DAYPERIODS) > -1) {
574             return "dayPeriods";
575         } else if (token.indexOf(LDMLConstants.DAYS) > -1) {
576             return "dayNames";
577         } else if (token.indexOf(LDMLConstants.DAY_CONTEXT) > -1) {
578             return getAttributeValue(token, LDMLConstants.TYPE);
579         } else if (token.indexOf(LDMLConstants.DAY_WIDTH) > -1) {
580             return getAttributeValue(token, LDMLConstants.TYPE);
581         } else if (token.indexOf(LDMLConstants.DAY) > -1) {
582             String dayName = getAttributeValue(token, LDMLConstants.TYPE);
583             return getDayIndexAsString(dayName);
584         } else if (token.indexOf(LDMLConstants.QUARTER_WIDTH) > -1) {
585             return getAttributeValue(token, LDMLConstants.TYPE);
586         } else if (token.indexOf(LDMLConstants.QUARTER_CONTEXT) > -1) {
587             return getAttributeValue(token, LDMLConstants.TYPE);
588         } else if (token.indexOf(LDMLConstants.QUARTERS) > -1) {
589             return "quarters";
590         } else if (token.indexOf(LDMLConstants.QUARTER) > -1) {
591             String valStr = getAttributeValue(token, LDMLConstants.TYPE);
592             return getMonthIndexAsString(valStr);
593         } else if (token.indexOf(LDMLConstants.COLLATIONS) > -1) {
594             return "collations";
595         } else if (token.indexOf(LDMLConstants.COLLATION) > -1) {
596             return getAttributeValue(token, LDMLConstants.TYPE);
597         }
598 
599         // TODO: this method is not finished yet
600         // the conversion of Xpath to ICU alias path
601         // is not as straight forward as I thought
602         // need to cater to idiosynchracies of each
603         // element node :(
604         throw new IllegalArgumentException("Unknown Xpath fragment: " + token);
605     }
606 
607     /**
608      * @param token XPath token fragment
609      * @param attrib attribute whose value must be fetched
610      * @return
611      */
getAttributeValue(String token, String attrib)612     private static String getAttributeValue(String token, String attrib) {
613         int attribStart = token.indexOf(attrib);
614         int valStart = token.indexOf('=', attribStart) + 1 /* skip past the separtor */;
615         int valEnd = token.indexOf('@', valStart);
616         if (valEnd < 0) {
617             valEnd = valStart + (token.length() - valStart - 1);
618         } else {
619             valEnd = token.length() - 1 /* valEnd should be index */;
620         }
621         String value = token.substring(valStart, valEnd);
622         int s = value.indexOf('\'');
623         if (s > -1) {
624             s++;
625             int e = value.lastIndexOf('\'');
626             return value.substring(s, e);
627         } else {
628             // also handle ""
629             s = value.indexOf('"');
630             if (s > -1) {
631                 s++;
632                 int e = value.lastIndexOf('"');
633                 return value.substring(s, e);
634             }
635         }
636         return value;
637     }
638 
639     @Deprecated
mergeLDMLDocuments( Document source, Node override, StringBuffer xpath, String thisName, String sourceDir, boolean ignoreDraft, boolean ignoreVersion)640     public static Node mergeLDMLDocuments(
641             Document source,
642             Node override,
643             StringBuffer xpath,
644             String thisName,
645             String sourceDir,
646             boolean ignoreDraft,
647             boolean ignoreVersion) {
648         StringBuilder sb = new StringBuilder(xpath.toString());
649         return mergeLDMLDocuments(
650                 source, override, sb, thisName, sourceDir, ignoreDraft, ignoreVersion);
651     }
652 
653     /**
654      * Resolved Data File
655      *
656      * <p>To produce fully resolved locale data file from CLDR for a locale ID L, you start with
657      * root, and replace/add items from the child locales until you get down to L. More formally,
658      * this can be expressed as the following procedure.
659      *
660      * <ol>
661      *   <li>Let Result be an empty LDML file.
662      *   <li>For each Li in the locale chain for L
663      *       <ol>
664      *         <li>For each element pair P in the LDML file for Li:
665      *             <ol>
666      *               <li>If Result has an element pair Q with an equivalent element chain, remove Q.
667      *               <li>Add P to Result.
668      *             </ol>
669      *       </ol>
670      * </ol>
671      *
672      * <p>Note: when adding an element pair to a result, it has to go in the right order for it to
673      * be valid according to the DTD.
674      *
675      * @param source
676      * @param override
677      * @return the merged document
678      */
mergeLDMLDocuments( Document source, Node override, StringBuilder xpath, String thisName, String sourceDir, boolean ignoreDraft, boolean ignoreVersion)679     public static Node mergeLDMLDocuments(
680             Document source,
681             Node override,
682             StringBuilder xpath,
683             String thisName,
684             String sourceDir,
685             boolean ignoreDraft,
686             boolean ignoreVersion) {
687         if (source == null) {
688             return override;
689         }
690         if (xpath.length() == 0) {
691             xpath.append("/");
692         }
693 
694         // boolean gotcha = false;
695         // String oldx = new String(xpath);
696         // if(override.getNodeName().equals("week")) {
697         // gotcha = true;
698         // System.out.println("SRC: " + getNode(source, xpath.toString()).toString());
699         // System.out.println("OVR: " + override.toString());
700         // }
701 
702         // we know that every child xml file either adds or
703         // overrides the elements in parent
704         // so we traverse the child, at every node check if
705         // if the node is present in the source,
706         // if (present)
707         // recurse to replace any nodes that need to be overridded
708         // else
709         // import the node into source
710         Node child = override.getFirstChild();
711         while (child != null) {
712             // we are only concerned with element nodes
713             if (child.getNodeType() != Node.ELEMENT_NODE) {
714                 child = child.getNextSibling();
715                 continue;
716             }
717             String childName = child.getNodeName();
718 
719             int savedLength = xpath.length();
720             xpath.append("/");
721             xpath.append(childName);
722             appendXPathAttribute(child, xpath, false, false);
723             Node nodeInSource = null;
724 
725             if (childName.indexOf(":") > -1) {
726                 nodeInSource = getNode(source, xpath.toString(), child);
727             } else {
728                 nodeInSource = getNode(source, xpath.toString());
729             }
730 
731             Node parentNodeInSource = null;
732             if (nodeInSource == null) {
733                 // the child xml has a new node
734                 // that should be added to parent
735                 String parentXpath = xpath.substring(0, savedLength);
736 
737                 if (childName.indexOf(":") > -1) {
738                     parentNodeInSource = getNode(source, parentXpath, child);
739                 } else {
740                     parentNodeInSource = getNode(source, parentXpath);
741                 }
742                 if (parentNodeInSource == null) {
743                     throw new RuntimeException("Internal Error");
744                 }
745 
746                 Node childToImport = source.importNode(child, true);
747                 parentNodeInSource.appendChild(childToImport);
748             } else if (childName.equals(LDMLConstants.IDENTITY)) {
749                 if (!ignoreVersion) {
750                     // replace the source doc
751                     // none of the elements under collations are inherited
752                     // only the node as a whole!!
753                     parentNodeInSource = nodeInSource.getParentNode();
754                     Node childToImport = source.importNode(child, true);
755                     parentNodeInSource.replaceChild(childToImport, nodeInSource);
756                 }
757             } else if (childName.equals(LDMLConstants.COLLATION)) {
758                 // replace the source doc
759                 // none of the elements under collations are inherited
760                 // only the node as a whole!!
761                 parentNodeInSource = nodeInSource.getParentNode();
762                 Node childToImport = source.importNode(child, true);
763                 parentNodeInSource.replaceChild(childToImport, nodeInSource);
764                 // override the validSubLocales attribute
765                 String val =
766                         LDMLUtilities.getAttributeValue(
767                                 child.getParentNode(), LDMLConstants.VALID_SUBLOCALE);
768                 NamedNodeMap map = parentNodeInSource.getAttributes();
769                 Node vs = map.getNamedItem(LDMLConstants.VALID_SUBLOCALE);
770                 vs.setNodeValue(val);
771             } else {
772                 boolean childElementNodes = areChildrenElementNodes(child);
773                 boolean sourceElementNodes = areChildrenElementNodes(nodeInSource);
774                 // System.out.println(childName + ":" + childElementNodes + "/" +
775                 // sourceElementNodes);
776                 if (childElementNodes && sourceElementNodes) {
777                     // recurse to pickup any children!
778                     mergeLDMLDocuments(
779                             source, child, xpath, thisName, sourceDir, ignoreDraft, ignoreVersion);
780                 } else {
781                     // we have reached a leaf node now get the
782                     // replace to the source doc
783                     parentNodeInSource = nodeInSource.getParentNode();
784                     Node childToImport = source.importNode(child, true);
785                     parentNodeInSource.replaceChild(childToImport, nodeInSource);
786                 }
787             }
788             xpath.delete(savedLength, xpath.length());
789             child = child.getNextSibling();
790         }
791         // if(gotcha==true) {
792         // System.out.println("Final: " + getNode(source, oldx).toString());
793         // }
794         return source;
795     }
796 
getNodeArray(Document doc, String tagName)797     private static Node[] getNodeArray(Document doc, String tagName) {
798         NodeList list = doc.getElementsByTagName(tagName);
799         // node list is dynamic .. if a node is deleted, then
800         // list is immidiately updated.
801         // so first cache the nodes returned and do stuff
802         Node[] array = new Node[list.getLength()];
803         for (int i = 0; i < list.getLength(); i++) {
804             array[i] = list.item(i);
805         }
806         return array;
807     }
808 
809     /**
810      * Utility to create abosolute Xpath from 1.1 style alias element
811      *
812      * @param node
813      * @param type
814      * @return
815      */
getAbsoluteXPath(Node node, String type)816     public static String getAbsoluteXPath(Node node, String type) {
817         StringBuffer xpath = new StringBuffer();
818         StringBuffer xpathFragment = new StringBuffer();
819         node = node.getParentNode(); // the node is alias node .. get its parent
820         if (node == null) {
821             throw new IllegalArgumentException("Alias node's parent is null!");
822         }
823         xpath.append(node.getNodeName());
824         if (type != null) {
825             xpath.append("[@type='" + type + "']"); // TODO: double quotes?
826         }
827         Node parent = node;
828         while ((parent = parent.getParentNode()) != null) {
829             xpathFragment.setLength(0);
830             xpathFragment.append(parent.getNodeName());
831             if (parent.getNodeType() != Node.DOCUMENT_NODE) {
832                 appendXPathAttribute(parent, xpathFragment);
833                 xpath.insert(0, "/");
834                 xpath.insert(0, xpathFragment);
835             }
836         }
837         xpath.insert(0, "//");
838         return xpath.toString();
839     }
840 
841     /**
842      * @param n1
843      * @param n2 preferred list
844      * @param xpath
845      * @return
846      */
mergeNodeLists(Object[] n1, Object[] n2)847     private static Node[] mergeNodeLists(Object[] n1, Object[] n2) {
848         StringBuffer xp1 = new StringBuffer();
849         StringBuffer xp2 = new StringBuffer();
850         int l1 = xp1.length(), l2 = xp2.length();
851         Map<String, Object> map = new HashMap<>();
852         if (n2 == null || n2.length == 0) {
853             Node[] na = new Node[n1.length];
854             for (int i = 0; i < n1.length; i++) {
855                 na[i] = (Node) n1[i];
856             }
857             return na;
858         }
859         for (int i = 0; i < n1.length; i++) {
860             xp1.append(((Node) n1[i]).getNodeName());
861             appendXPathAttribute((Node) n1[i], xp1);
862             map.put(xp1.toString(), n1[i]);
863             xp1.setLength(l1);
864         }
865         for (int i = 0; i < n2.length; i++) {
866             xp2.append(((Node) n2[i]).getNodeName());
867             appendXPathAttribute((Node) n2[i], xp2);
868             map.put(xp2.toString(), n2[i]);
869             xp2.setLength(l2);
870         }
871         Object[] arr = map.values().toArray();
872         Node[] na = new Node[arr.length];
873         for (int i = 0; i < arr.length; i++) {
874             na[i] = (Node) arr[i];
875         }
876         return na;
877     }
878 
879     /**
880      * @param fullyResolvedDoc
881      * @param sourceDir
882      * @param thisLocale
883      */
884     // TODO guard against circular aliases
resolveAliases( Document fullyResolvedDoc, String sourceDir, String thisLocale, boolean ignoreDraft, Map<String, String> stack)885     public static Document resolveAliases(
886             Document fullyResolvedDoc,
887             String sourceDir,
888             String thisLocale,
889             boolean ignoreDraft,
890             Map<String, String> stack) {
891         Node[] array = getNodeArray(fullyResolvedDoc, LDMLConstants.ALIAS);
892 
893         // resolve all the aliases by iterating over
894         // the list of nodes
895         Node[] replacementList = null;
896         Node parent = null;
897         String source = null;
898         String path = null;
899         String type = null;
900         for (int i = 0; i < array.length; i++) {
901             Node node = array[i];
902             /*
903              * //stop inherited aliases from overwriting valid locale data
904              * //ldml.dtd does not allow alias to have any sibling elements
905              * boolean bFoundSibling = false;
906              * Node n = node.getNextSibling();
907              * while (n != null)
908              * {
909              * if (n.getNodeType() == Node.ELEMENT_NODE)
910              * {
911              * // System.err.println ("it's an element node " + n.getNodeName () + "  " + n.getNodeValue());
912              * bFoundSibling = true;
913              * break;
914              * }
915              * n = n.getNextSibling();
916              * }
917              * if (bFoundSibling == true)
918              * continue;
919              */
920             // initialize the stack for every alias!
921             stack = new HashMap<>();
922             if (node == null) {
923                 System.err.println(
924                         "list.item("
925                                 + i
926                                 + ") returned null!. The list reports it's length as: "
927                                 + array.length);
928                 continue;
929             }
930             parent = node.getParentNode();
931             // boolean isDraft = isNodeDraft(node);
932             source = getAttributeValue(node, LDMLConstants.SOURCE);
933             path = getAttributeValue(node, LDMLConstants.PATH);
934             type = getAttributeValue(parent, LDMLConstants.TYPE);
935             if (parent.getParentNode() == null) {
936                 // some of the nodes were orphaned by the previous alias resolution .. just continue
937                 continue;
938             }
939             if (source != null && path == null) {
940                 // this LDML 1.1 style alias parse it
941                 path = getAbsoluteXPath(node, type);
942             }
943             String key = "SRC:" + thisLocale + ";XPATH:" + getAbsoluteXPath(node, type);
944             if (stack.get(key) != null) {
945                 throw new IllegalStateException("Found circular aliases! " + key);
946             }
947             stack.put(key, "");
948             if (source.equals(LDMLConstants.LOCALE)) {
949 
950                 Object[] aliasList = getChildNodeListAsArray(getNode(parent, path), false);
951                 Object[] childList = getChildNodeListAsArray(parent, true);
952                 replacementList = mergeNodeLists(aliasList, childList);
953             } else if (source != null && !source.equals(thisLocale)) {
954                 // if source is defined then path should not be
955                 // relative
956                 if (path.indexOf("..") > 0) {
957                     throw new IllegalArgumentException(
958                             "Cannot parse relative xpath: "
959                                     + path
960                                     + " in locale: "
961                                     + source
962                                     + " from source locale: "
963                                     + thisLocale);
964                 }
965                 // this is a is an absolute XPath
966                 Document newDoc =
967                         getFullyResolvedLDML(
968                                 sourceDir, source, false, true, false, ignoreDraft, stack);
969                 replacementList = getNodeListAsArray(newDoc, path);
970             } else {
971                 // path attribute is referencing another node in this DOM tree
972                 replacementList = getNodeListAsArray(parent, path);
973             }
974             if (replacementList != null) {
975                 parent.removeChild(node);
976                 int listLen = replacementList.length;
977                 if (listLen > 1) {
978                     // check if the whole locale is aliased
979                     // if yes then remove the identity from
980                     // the current document!
981                     if (path != null && path.equals("//ldml/*")) {
982                         Node[] identity = getNodeArray(fullyResolvedDoc, LDMLConstants.IDENTICAL);
983                         for (int j = 0; j < identity.length; j++) {
984                             parent.removeChild(node);
985                         }
986                     } else {
987                         // remove all the children of the parent node
988                         removeChildNodes(parent);
989                     }
990                     for (int j = 0; j < listLen; j++) {
991                         // found an element node in the aliased resource
992                         // add to the source
993                         Node child = replacementList[j];
994                         Node childToImport = fullyResolvedDoc.importNode(child, true);
995                         // if(isDraft==true && childToImport.getNodeType() == Node.ELEMENT_NODE){
996                         // ((Element)childToImport).setAttribute("draft", "true");
997                         // }
998                         parent.appendChild(childToImport);
999                     }
1000                 } else {
1001                     Node replacement = replacementList[0];
1002                     // remove all the children of the parent node
1003                     removeChildNodes(parent);
1004                     for (Node child = replacement.getFirstChild();
1005                             child != null;
1006                             child = child.getNextSibling()) {
1007                         // found an element node in the aliased resource
1008                         // add to the source
1009                         Node childToImport = fullyResolvedDoc.importNode(child, true);
1010                         // if(isDraft==true && childToImport.getNodeType() == Node.ELEMENT_NODE){
1011                         // ((Element)childToImport).setAttribute("draft", "true");
1012                         // }
1013                         parent.appendChild(childToImport);
1014                     }
1015                 }
1016 
1017             } else {
1018                 throw new IllegalArgumentException(
1019                         "Could not find node for xpath: "
1020                                 + path
1021                                 + " in locale: "
1022                                 + source
1023                                 + " from source locale: "
1024                                 + thisLocale);
1025             }
1026         }
1027         return fullyResolvedDoc;
1028     }
1029 
removeChildNodes(Node parent)1030     private static void removeChildNodes(Node parent) {
1031         Node[] children = toNodeArray(parent.getChildNodes());
1032         for (int j = 0; j < children.length; j++) {
1033             parent.removeChild(children[j]);
1034         }
1035     }
1036 
1037     // TODO add funtions for fetching legitimate children
1038     // for ICU
isParentDraft(Document fullyResolved, String xpath)1039     public boolean isParentDraft(Document fullyResolved, String xpath) {
1040         Node node = getNode(fullyResolved, xpath);
1041         Node parentNode;
1042         while ((parentNode = node.getParentNode()) != null) {
1043             String draft = getAttributeValue(parentNode, LDMLConstants.DRAFT);
1044             if (draft != null) {
1045                 if (draft.equals("true")
1046                         || draft.equals("provisional")
1047                         || draft.equals("unconfirmed")) {
1048                     return true;
1049                 } else {
1050                     return false;
1051                 }
1052             }
1053         }
1054         // the default value is false if none specified
1055         return false;
1056     }
1057 
isNodeDraft(Node node)1058     public static boolean isNodeDraft(Node node) {
1059         String draft = getAttributeValue(node, LDMLConstants.DRAFT);
1060         if (draft != null) {
1061             if (draft.equals("true")
1062                     || draft.equals("provisional")
1063                     || draft.equals("unconfirmed")) {
1064                 return true;
1065             } else {
1066                 return false;
1067             }
1068         }
1069         return false;
1070     }
1071 
isDraft(Node fullyResolved, StringBuffer xpath)1072     public static boolean isDraft(Node fullyResolved, StringBuffer xpath) {
1073         Node current = getNode(fullyResolved, xpath.toString());
1074         String draft = null;
1075         while (current != null && current.getNodeType() == Node.ELEMENT_NODE) {
1076             draft = getAttributeValue(current, LDMLConstants.DRAFT);
1077             if (draft != null) {
1078                 if (draft.equals("true")
1079                         || draft.equals("provisional")
1080                         || draft.equals("unconfirmed")) {
1081                     return true;
1082                 } else {
1083                     return false;
1084                 }
1085             }
1086             current = current.getParentNode();
1087         }
1088         return false;
1089     }
1090 
isSiblingDraft(Node root)1091     public static boolean isSiblingDraft(Node root) {
1092         Node current = root;
1093         String draft = null;
1094         while (current != null && current.getNodeType() == Node.ELEMENT_NODE) {
1095             draft = getAttributeValue(current, LDMLConstants.DRAFT);
1096             if (draft != null) {
1097                 if (draft.equals("true")
1098                         || draft.equals("provisional")
1099                         || draft.equals("unconfirmed")) {
1100                     return true;
1101                 } else {
1102                     return false;
1103                 }
1104             }
1105             current = current.getNextSibling();
1106         }
1107         return false;
1108     }
1109 
appendAllAttributes(Node node, StringBuffer xpath)1110     public static void appendAllAttributes(Node node, StringBuffer xpath) {
1111         NamedNodeMap attr = node.getAttributes();
1112         int len = attr.getLength();
1113         if (len > 0) {
1114             for (int i = 0; i < len; i++) {
1115                 Node item = attr.item(i);
1116                 xpath.append("[@");
1117                 xpath.append(item.getNodeName());
1118                 xpath.append("='");
1119                 xpath.append(item.getNodeValue());
1120                 xpath.append("']");
1121             }
1122         }
1123     }
1124 
areChildNodesElements(Node node)1125     private static boolean areChildNodesElements(Node node) {
1126         NodeList list = node.getChildNodes();
1127         for (int i = 0; i < list.getLength(); i++) {
1128             if (list.item(i).getNodeType() == Node.ELEMENT_NODE) {
1129                 return true;
1130             }
1131         }
1132         return false;
1133     }
1134 
areSiblingsOfNodeElements(Node node)1135     private static boolean areSiblingsOfNodeElements(Node node) {
1136         NodeList list = node.getParentNode().getChildNodes();
1137         int count = 0;
1138         for (int i = 0; i < list.getLength(); i++) {
1139             Node item = list.item(i);
1140             if (item.getNodeType() == Node.ELEMENT_NODE) {
1141                 count++;
1142             }
1143             // the first child node of type element of <ldml> should be <identity>
1144             // here we figure out if any additional elements are there
1145             if (count > 1) {
1146                 return true;
1147             }
1148         }
1149         return false;
1150     }
1151 
isLocaleAlias(Document doc)1152     public static boolean isLocaleAlias(Document doc) {
1153         return (getAliasNode(doc) != null);
1154     }
1155 
getAliasNode(Document doc)1156     private static Node getAliasNode(Document doc) {
1157         NodeList elements = doc.getElementsByTagName(LDMLConstants.IDENTITY);
1158         if (elements.getLength() == 1) {
1159             Node id = elements.item(0);
1160             Node sib = id;
1161             while ((sib = sib.getNextSibling()) != null) {
1162                 if (sib.getNodeType() != Node.ELEMENT_NODE) {
1163                     continue;
1164                 }
1165                 if (sib.getNodeName().equals(LDMLConstants.ALIAS)) {
1166                     return sib;
1167                 }
1168             }
1169         } else {
1170             System.out.println("Error: elements returned more than 1 identity element!");
1171         }
1172         return null;
1173     }
1174 
1175     /**
1176      * Determines if the whole locale is marked draft. To accomplish this the method traverses all
1177      * leaf nodes to determine if all nodes are marked draft
1178      */
1179     private static boolean seenElementsOtherThanIdentity = false;
1180 
isLocaleDraft(Node node)1181     public static final boolean isLocaleDraft(Node node) {
1182         boolean isDraft = true;
1183         // fast path to check if <ldml> element is draft
1184         if (isNodeDraft(node) == true) {
1185             return true;
1186         }
1187         for (Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
1188             if (child.getNodeType() != Node.ELEMENT_NODE) {
1189                 continue;
1190             }
1191             String name = child.getNodeName();
1192             // fast path to check if <ldml> element is draft
1193             if (name.equals(LDMLConstants.LDML) && isNodeDraft(child) == true) {
1194                 return true;
1195             }
1196             if (name.equals(LDMLConstants.IDENTITY)) {
1197                 seenElementsOtherThanIdentity = areSiblingsOfNodeElements(child);
1198                 continue;
1199             }
1200 
1201             if (child.hasChildNodes() && areChildNodesElements(child)) {
1202                 isDraft = isLocaleDraft(child);
1203             } else {
1204                 if (isNodeDraft(child) == false) {
1205                     isDraft = false;
1206                 }
1207             }
1208             if (isDraft == false) {
1209                 break;
1210             }
1211         }
1212         if (!seenElementsOtherThanIdentity) {
1213             return false;
1214         }
1215         return isDraft;
1216     }
1217 
1218     /**
1219      * Appends the attribute values that make differentiate 2 siblings in LDML
1220      *
1221      * @param node
1222      * @param xpath
1223      * @deprecated - use version that takes StringBuilder instead
1224      */
1225     @Deprecated
appendXPathAttribute(Node node, StringBuffer xpath)1226     public static final void appendXPathAttribute(Node node, StringBuffer xpath) {
1227         appendXPathAttribute(node, xpath, false, false);
1228     }
1229 
1230     /**
1231      * @deprecated
1232      */
1233     @Deprecated
appendXPathAttribute( Node node, StringBuffer xpath, boolean ignoreAlt, boolean ignoreDraft)1234     public static void appendXPathAttribute(
1235             Node node, StringBuffer xpath, boolean ignoreAlt, boolean ignoreDraft) {
1236         StringBuilder sb = new StringBuilder(xpath.toString());
1237         appendXPathAttribute(node, sb, ignoreAlt, ignoreDraft);
1238     }
1239 
appendXPathAttribute(Node node, StringBuilder xpath)1240     public static final void appendXPathAttribute(Node node, StringBuilder xpath) {
1241         appendXPathAttribute(node, xpath, false, false);
1242     }
1243 
appendXPathAttribute( Node node, StringBuilder xpath, boolean ignoreAlt, boolean ignoreDraft)1244     public static void appendXPathAttribute(
1245             Node node, StringBuilder xpath, boolean ignoreAlt, boolean ignoreDraft) {
1246         boolean terminate = false;
1247         String val = getAttributeValue(node, LDMLConstants.TYPE);
1248         String and = "]["; // " and ";
1249         boolean isStart = true;
1250         String name = node.getNodeName();
1251         if (val != null && !name.equals(LDMLConstants.DEFAULT) && !name.equals(LDMLConstants.MS)) {
1252             if (!(val.equals("standard") && name.equals(LDMLConstants.PATTERN))) {
1253 
1254                 if (isStart) {
1255                     xpath.append("[");
1256                     isStart = false;
1257                 }
1258                 xpath.append("@type='");
1259                 xpath.append(val);
1260                 xpath.append("'");
1261                 terminate = true;
1262             }
1263         }
1264         if (!ignoreAlt) {
1265             val = getAttributeValue(node, LDMLConstants.ALT);
1266             if (val != null) {
1267                 if (isStart) {
1268                     xpath.append("[");
1269                     isStart = false;
1270                 } else {
1271                     xpath.append(and);
1272                 }
1273                 xpath.append("@alt='");
1274                 xpath.append(val);
1275                 xpath.append("'");
1276                 terminate = true;
1277             }
1278         }
1279 
1280         if (!ignoreDraft) {
1281             val = getAttributeValue(node, LDMLConstants.DRAFT);
1282             if (val != null && !name.equals(LDMLConstants.LDML)) {
1283                 if (isStart) {
1284                     xpath.append("[");
1285                     isStart = false;
1286                 } else {
1287                     xpath.append(and);
1288                 }
1289                 xpath.append("@draft='");
1290                 xpath.append(val);
1291                 xpath.append("'");
1292                 terminate = true;
1293             }
1294         }
1295 
1296         val = getAttributeValue(node, LDMLConstants.KEY);
1297         if (val != null) {
1298             if (isStart) {
1299                 xpath.append("[");
1300                 isStart = false;
1301             } else {
1302                 xpath.append(and);
1303             }
1304             xpath.append("@key='");
1305             xpath.append(val);
1306             xpath.append("'");
1307             terminate = true;
1308         }
1309         val = getAttributeValue(node, LDMLConstants.REGISTRY);
1310         if (val != null) {
1311             if (isStart) {
1312                 xpath.append("[");
1313                 isStart = false;
1314             } else {
1315                 xpath.append(and);
1316             }
1317             xpath.append("@registry='");
1318             xpath.append(val);
1319             xpath.append("'");
1320             terminate = true;
1321         }
1322         val = getAttributeValue(node, LDMLConstants.ID);
1323         if (val != null) {
1324             if (isStart) {
1325                 xpath.append("[");
1326                 isStart = false;
1327             } else {
1328                 xpath.append(and);
1329             }
1330             xpath.append("@id='");
1331             xpath.append(val);
1332             xpath.append("'");
1333             terminate = true;
1334         }
1335         if (terminate) {
1336             xpath.append("]");
1337         }
1338     }
1339 
1340     /**
1341      * Ascertains if the children of the given node are element nodes.
1342      *
1343      * @param node
1344      * @return
1345      */
areChildrenElementNodes(Node node)1346     public static boolean areChildrenElementNodes(Node node) {
1347         NodeList list = node.getChildNodes();
1348         for (int i = 0; i < list.getLength(); i++) {
1349             if (list.item(i).getNodeType() == Node.ELEMENT_NODE) {
1350                 return true;
1351             }
1352         }
1353         return false;
1354     }
1355 
getNodeListAsArray(Node doc, String xpath)1356     public static Node[] getNodeListAsArray(Node doc, String xpath) {
1357         try {
1358             NodeList list = XPathAPI_selectNodeList(doc, xpath);
1359             int length = list.getLength();
1360             if (length > 0) {
1361                 Node[] array = new Node[length];
1362                 for (int i = 0; i < length; i++) {
1363                     array[i] = list.item(i);
1364                 }
1365                 return array;
1366             }
1367             return null;
1368         } catch (TransformerException ex) {
1369             throw new RuntimeException(ex);
1370         }
1371     }
1372 
getChildNodeListAsArray(Node parent, boolean exceptAlias)1373     private static Object[] getChildNodeListAsArray(Node parent, boolean exceptAlias) {
1374 
1375         NodeList list = parent.getChildNodes();
1376         int length = list.getLength();
1377 
1378         List<Node> al = new ArrayList<>();
1379         for (int i = 0; i < length; i++) {
1380             Node item = list.item(i);
1381             if (item.getNodeType() != Node.ELEMENT_NODE) {
1382                 continue;
1383             }
1384             if (exceptAlias && item.getNodeName().equals(LDMLConstants.ALIAS)) {
1385                 continue;
1386             }
1387             al.add(item);
1388         }
1389         return al.toArray();
1390     }
1391 
toNodeArray(NodeList list)1392     public static Node[] toNodeArray(NodeList list) {
1393         int length = list.getLength();
1394         if (length > 0) {
1395             Node[] array = new Node[length];
1396             for (int i = 0; i < length; i++) {
1397                 array[i] = list.item(i);
1398             }
1399             return array;
1400         }
1401         return null;
1402     }
1403 
getElementsByTagName(Document doc, String tagName)1404     public static Node[] getElementsByTagName(Document doc, String tagName) {
1405         try {
1406             NodeList list = doc.getElementsByTagName(tagName);
1407             int length = list.getLength();
1408             if (length > 0) {
1409                 Node[] array = new Node[length];
1410                 for (int i = 0; i < length; i++) {
1411                     array[i] = list.item(i);
1412                 }
1413                 return array;
1414             }
1415             return null;
1416         } catch (Exception ex) {
1417             throw new RuntimeException(ex);
1418         }
1419     }
1420 
1421     /**
1422      * Fetches the list of nodes that match the given xpath
1423      *
1424      * @param doc
1425      * @param xpath
1426      * @return
1427      */
getNodeList(Document doc, String xpath)1428     public static NodeList getNodeList(Document doc, String xpath) {
1429         try {
1430             return XPathAPI_selectNodeList(doc, xpath);
1431 
1432         } catch (TransformerException ex) {
1433             throw new RuntimeException(ex);
1434         }
1435     }
1436 
isAlternate(Node node)1437     public static final boolean isAlternate(Node node) {
1438         NamedNodeMap attributes = node.getAttributes();
1439         Node attr = attributes.getNamedItem(LDMLConstants.ALT);
1440         if (attr != null) {
1441             return true;
1442         }
1443         return false;
1444     }
1445 
getNonAltNodeIfPossible(NodeList list)1446     private static final Node getNonAltNodeIfPossible(NodeList list) {
1447         // A nonalt node is one which .. does not have alternate
1448         // attribute set
1449         Node node = null;
1450         for (int i = 0; i < list.getLength(); i++) {
1451             node = list.item(i);
1452             if (
1453             /* !isDraft(node, xpath)&& */ !isAlternate(node)) {
1454                 return node;
1455             }
1456         }
1457         if (list.getLength() > 0)
1458             return list.item(0); // if all have alt=.... then return the first one
1459         return null;
1460     }
1461 
getNonAltNodeLike(Node parent, Node child)1462     public static Node getNonAltNodeLike(Node parent, Node child) {
1463         StringBuffer childXpath = new StringBuffer(child.getNodeName());
1464         appendXPathAttribute(child, childXpath, true /* ignore alt */, true /* ignore draft */);
1465         String childXPathString = childXpath.toString();
1466         for (Node other = parent.getFirstChild(); other != null; other = other.getNextSibling()) {
1467             if ((other.getNodeType() != Node.ELEMENT_NODE) || (other == child)) {
1468                 continue;
1469             }
1470             StringBuffer otherXpath = new StringBuffer(other.getNodeName());
1471             appendXPathAttribute(other, otherXpath);
1472             // System.out.println("Compare: " + childXpath + " to " + otherXpath);
1473             if (childXPathString.equals(otherXpath.toString())) {
1474                 // System.out.println("Match!");
1475                 return other;
1476             }
1477         }
1478         return null;
1479     }
1480 
1481     /**
1482      * Fetches the node from the document that matches the given xpath. The context namespace node
1483      * is required if the xpath contains namespace elments
1484      *
1485      * @param doc
1486      * @param xpath
1487      * @param namespaceNode
1488      * @return
1489      */
getNode(Document doc, String xpath, Node namespaceNode)1490     public static Node getNode(Document doc, String xpath, Node namespaceNode) {
1491         try {
1492             NodeList nl = XPathAPI_selectNodeList(doc, xpath, namespaceNode);
1493             int len = nl.getLength();
1494             // TODO watch for attribute "alt"
1495             if (len > 1) {
1496                 throw new IllegalArgumentException(
1497                         "The XPATH returned more than 1 node!. Check XPATH: " + xpath);
1498             }
1499             if (len == 0) {
1500                 return null;
1501             }
1502             return nl.item(0);
1503 
1504         } catch (TransformerException ex) {
1505             ex.printStackTrace();
1506             throw new RuntimeException(ex);
1507         }
1508     }
1509 
getNode(Node context, String resToFetch, Node namespaceNode)1510     public static Node getNode(Node context, String resToFetch, Node namespaceNode) {
1511         try {
1512             NodeList nl = XPathAPI_selectNodeList(context, "./" + resToFetch, namespaceNode);
1513             int len = nl.getLength();
1514             // TODO watch for attribute "alt"
1515             if (len > 1) {
1516                 throw new IllegalArgumentException(
1517                         "The XPATH returned more than 1 node!. Check XPATH: " + resToFetch);
1518             }
1519             if (len == 0) {
1520                 return null;
1521             }
1522             return nl.item(0);
1523 
1524         } catch (TransformerException ex) {
1525             throw new RuntimeException(ex);
1526         }
1527     }
1528 
1529     /**
1530      * Fetches the node from the document which matches the xpath
1531      *
1532      * @param node
1533      * @param xpath
1534      * @return
1535      */
getNode(Node node, String xpath)1536     public static Node getNode(Node node, String xpath) {
1537         try {
1538             NodeList nl = XPathAPI_selectNodeList(node, xpath);
1539             int len = nl.getLength();
1540             // TODO watch for attribute "alt"
1541             if (len > 1) {
1542                 // PN Node best = getNonAltNode(nl);
1543                 Node best = getNonAltNodeIfPossible(nl); // PN
1544                 if (best != null) {
1545                     // System.err.println("Chose best node from " + xpath);
1546                     return best;
1547                 }
1548                 /* else complain */
1549                 String all = "";
1550                 int i;
1551                 for (i = 0; i < len; i++) {
1552                     all = all + ", " + nl.item(i);
1553                 }
1554                 throw new IllegalArgumentException(
1555                         "The XPATH returned more than 1 node!. Check XPATH: "
1556                                 + xpath
1557                                 + " = "
1558                                 + all);
1559             }
1560             if (len == 0) {
1561                 return null;
1562             }
1563             return nl.item(0);
1564 
1565         } catch (TransformerException ex) {
1566             throw new RuntimeException(ex);
1567         }
1568     }
1569 
getNode(Node node, String xpath, boolean preferDraft, boolean preferAlt)1570     public static Node getNode(Node node, String xpath, boolean preferDraft, boolean preferAlt) {
1571         try {
1572             NodeList nl = XPathAPI_selectNodeList(node, xpath);
1573             return getNode(nl, xpath, preferDraft, preferAlt);
1574 
1575         } catch (TransformerException ex) {
1576             throw new RuntimeException(ex);
1577         }
1578     }
1579 
getVettedNode(NodeList list, StringBuffer xpath, boolean ignoreDraft)1580     private static Node getVettedNode(NodeList list, StringBuffer xpath, boolean ignoreDraft) {
1581         // A vetted node is one which is not draft and does not have alternate
1582         // attribute set
1583         Node node = null;
1584         for (int i = 0; i < list.getLength(); i++) {
1585             node = list.item(i);
1586             if (isDraft(node, xpath) && !ignoreDraft) {
1587                 continue;
1588             }
1589             if (isAlternate(node)) {
1590                 continue;
1591             }
1592             return node;
1593         }
1594         return null;
1595     }
1596 
getVettedNode( Document fullyResolvedDoc, Node parent, String childName, StringBuffer xpath, boolean ignoreDraft)1597     public static Node getVettedNode(
1598             Document fullyResolvedDoc,
1599             Node parent,
1600             String childName,
1601             StringBuffer xpath,
1602             boolean ignoreDraft) {
1603         NodeList list = getNodeList(parent, childName, fullyResolvedDoc, xpath.toString());
1604         int oldLength = xpath.length();
1605         Node ret = null;
1606 
1607         if (list != null && list.getLength() > 0) {
1608             xpath.append("/");
1609             xpath.append(childName);
1610             ret = getVettedNode(list, xpath, ignoreDraft);
1611         }
1612         xpath.setLength(oldLength);
1613         return ret;
1614     }
1615 
getNode(NodeList nl, String xpath, boolean preferDraft, boolean preferAlt)1616     public static Node getNode(NodeList nl, String xpath, boolean preferDraft, boolean preferAlt) {
1617         int len = nl.getLength();
1618         // TODO watch for attribute "alt"
1619         if (len > 1) {
1620             Node best = null;
1621             for (int i = 0; i < len; i++) {
1622                 Node current = nl.item(i);
1623                 if (!preferDraft && !preferAlt) {
1624                     if (!isNodeDraft(current) && !isAlternate(current)) {
1625                         best = current;
1626                         break;
1627                     }
1628                     continue;
1629                 } else if (preferDraft && !preferAlt) {
1630                     if (isNodeDraft(current) && !isAlternate(current)) {
1631                         best = current;
1632                         break;
1633                     }
1634                     continue;
1635                 } else if (!preferDraft && preferAlt) {
1636                     if (!isNodeDraft(current) && isAlternate(current)) {
1637                         best = current;
1638                         break;
1639                     }
1640                     continue;
1641                 } else {
1642                     if (isNodeDraft(current) || isAlternate(current)) {
1643                         best = current;
1644                         break;
1645                     }
1646                     continue;
1647                 }
1648             }
1649             if (best == null && preferDraft == true) {
1650                 best = getVettedNode(nl, new StringBuffer(xpath), false);
1651             }
1652             if (best != null) {
1653                 return best;
1654             }
1655             /* else complain */
1656             String all = "";
1657             int i;
1658             for (i = 0; i < len; i++) {
1659                 all = all + ", " + nl.item(i);
1660             }
1661             throw new IllegalArgumentException(
1662                     "The XPATH returned more than 1 node!. Check XPATH: " + xpath + " = " + all);
1663         }
1664         if (len == 0) {
1665             return null;
1666         }
1667         return nl.item(0);
1668     }
1669 
1670     /**
1671      * @param context
1672      * @param resToFetch
1673      * @param fullyResolved
1674      * @param xpath
1675      * @return
1676      */
getNode( Node context, String resToFetch, Document fullyResolved, String xpath)1677     public static Node getNode(
1678             Node context, String resToFetch, Document fullyResolved, String xpath) {
1679         String ctx = "./" + resToFetch;
1680         Node node = getNode(context, ctx);
1681         if (node == null && fullyResolved != null) {
1682             // try from fully resolved
1683             String path = xpath + "/" + resToFetch;
1684             node = getNode(fullyResolved, path);
1685         }
1686         return node;
1687     }
1688 
1689     /**
1690      * @param context
1691      * @param resToFetch
1692      * @return
1693      */
getChildNodes(Node context, String resToFetch)1694     public static NodeList getChildNodes(Node context, String resToFetch) {
1695         String ctx = "./" + resToFetch;
1696         NodeList list = getNodeList(context, ctx);
1697         return list;
1698     }
1699 
1700     /**
1701      * Fetches the node from the document that matches the given xpath. The context namespace node
1702      * is required if the xpath contains namespace elments
1703      *
1704      * @param doc
1705      * @param xpath
1706      * @param namespaceNode
1707      * @return
1708      */
getNodeList(Document doc, String xpath, Node namespaceNode)1709     public static NodeList getNodeList(Document doc, String xpath, Node namespaceNode) {
1710         try {
1711             NodeList nl = XPathAPI_selectNodeList(doc, xpath, namespaceNode);
1712             if (nl.getLength() == 0) {
1713                 return null;
1714             }
1715             return nl;
1716 
1717         } catch (TransformerException ex) {
1718             throw new RuntimeException(ex);
1719         }
1720     }
1721 
1722     /**
1723      * Fetches the node from the document which matches the xpath
1724      *
1725      * @param node
1726      * @param xpath
1727      * @return
1728      */
getNodeList(Node node, String xpath)1729     public static NodeList getNodeList(Node node, String xpath) {
1730         try {
1731             NodeList nl = XPathAPI_selectNodeList(node, xpath);
1732             int len = nl.getLength();
1733             if (len == 0) {
1734                 return null;
1735             }
1736             return nl;
1737         } catch (TransformerException ex) {
1738             throw new RuntimeException(ex);
1739         }
1740     }
1741 
1742     /**
1743      * Fetches node list from the children of the context node.
1744      *
1745      * @param context
1746      * @param resToFetch
1747      * @param fullyResolved
1748      * @param xpath
1749      * @return
1750      */
getNodeList( Node context, String resToFetch, Document fullyResolved, String xpath)1751     public static NodeList getNodeList(
1752             Node context, String resToFetch, Document fullyResolved, String xpath) {
1753         String ctx = "./" + resToFetch;
1754         NodeList list = getNodeList(context, ctx);
1755         if ((list == null || list.getLength() > 0) && fullyResolved != null) {
1756             // try from fully resolved
1757             String path = xpath + "/" + resToFetch;
1758             list = getNodeList(fullyResolved, path);
1759         }
1760         return list;
1761     }
1762 
1763     /**
1764      * Decide if the node is text, and so must be handled specially
1765      *
1766      * @param n
1767      * @return
1768      */
getAttributeNode(Node sNode, String attribName)1769     public static Node getAttributeNode(Node sNode, String attribName) {
1770         NamedNodeMap attrs = sNode.getAttributes();
1771         if (attrs != null) {
1772             return attrs.getNamedItem(attribName);
1773         }
1774         return null;
1775     }
1776 
1777     /**
1778      * Utility method to fetch the attribute value from the given element node
1779      *
1780      * @param sNode
1781      * @param attribName
1782      * @return
1783      */
getAttributeValue(Node sNode, String attribName)1784     public static String getAttributeValue(Node sNode, String attribName) {
1785         String value = null;
1786         NamedNodeMap attrs = sNode.getAttributes();
1787         if (attrs != null) {
1788             Node attr = attrs.getNamedItem(attribName);
1789             if (attr != null) {
1790                 value = attr.getNodeValue();
1791             }
1792         }
1793         return value;
1794     }
1795 
1796     /**
1797      * Utility method to set the attribute value on the given element node
1798      *
1799      * @param sNode
1800      * @param attribName
1801      * @param val
1802      */
setAttributeValue(Node sNode, String attribName, String val)1803     public static void setAttributeValue(Node sNode, String attribName, String val) {
1804 
1805         Node attr = sNode.getAttributes().getNamedItem(attribName);
1806         if (attr != null) {
1807             attr.setNodeValue(val);
1808         } else {
1809             attr = sNode.getOwnerDocument().createAttribute(attribName);
1810             attr.setNodeValue(val);
1811             sNode.getAttributes().setNamedItem(attr);
1812         }
1813     }
1814 
1815     /**
1816      * Utility method to fetch the value of the element node
1817      *
1818      * @param node
1819      * @return
1820      */
getNodeValue(Node node)1821     public static String getNodeValue(Node node) {
1822         for (Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
1823             if (child.getNodeType() == Node.TEXT_NODE) {
1824                 return child.getNodeValue();
1825             }
1826         }
1827         return null;
1828     }
1829 
1830     /** Parse & resolve file level alias */
parseAndResolveAlias(String filename, String locale, boolean ignoreError)1831     public static Document parseAndResolveAlias(String filename, String locale, boolean ignoreError)
1832             throws RuntimeException {
1833         // Force filerefs to be URI's if needed: note this is independent of any other files
1834         String docURI = filenameToURL(filename);
1835         Document doc = parse(new InputSource(docURI), filename, ignoreError);
1836         NodeList elements = doc.getElementsByTagName(LDMLConstants.IDENTITY);
1837         if (elements.getLength() == 1) {
1838             Node id = elements.item(0);
1839             Node sib = id;
1840             while ((sib = sib.getNextSibling()) != null) {
1841                 if (sib.getNodeType() != Node.ELEMENT_NODE) {
1842                     continue;
1843                 }
1844                 if (sib.getNodeName().equals(LDMLConstants.ALIAS)) {
1845                     resolveAliases(
1846                             doc,
1847                             filename.substring(0, filename.lastIndexOf(File.separator) + 1),
1848                             locale,
1849                             false,
1850                             null);
1851                 }
1852             }
1853         } else {
1854             System.out.println("Error: elements returned more than 1 identity element!");
1855         }
1856         if (DEBUG) {
1857             try {
1858                 java.io.OutputStreamWriter writer =
1859                         new java.io.OutputStreamWriter(
1860                                 new java.io.FileOutputStream(
1861                                         "./" + File.separator + locale + "_debug_1.xml"),
1862                                 "UTF-8");
1863                 LDMLUtilities.printDOMTree(
1864                         doc,
1865                         new PrintWriter(writer),
1866                         "http://www.unicode.org/cldr/dtd/1.3/ldml.dtd",
1867                         null);
1868                 writer.flush();
1869             } catch (IOException e) {
1870                 // throw the exceptionaway .. this is for debugging
1871             }
1872         }
1873         return doc;
1874     }
1875 
1876     /**
1877      * Simple worker method to parse filename to a Document.
1878      *
1879      * <p>Attempts XML parse, then HTML parse (when parser available), then just parses as text and
1880      * sticks into a text node.
1881      *
1882      * @param filename to parse as a local path
1883      * @return Document object with contents of the file; otherwise throws an unchecked
1884      *     RuntimeException if there is any fatal problem
1885      */
parse(String filename, boolean ignoreError)1886     public static Document parse(String filename, boolean ignoreError) throws RuntimeException {
1887         // Force filerefs to be URI's if needed: note this is independent of any other files
1888         String docURI = filenameToURL(filename);
1889         return parse(new InputSource(docURI), filename, ignoreError);
1890     }
1891 
parse(String filename, boolean ignoreError, boolean validating)1892     public static Document parse(String filename, boolean ignoreError, boolean validating)
1893             throws RuntimeException {
1894         // Force filerefs to be URI's if needed: note this is independent of any other files
1895         String docURI = filenameToURL(filename);
1896         return parse(new InputSource(docURI), filename, ignoreError, validating);
1897     }
1898 
parseAndResolveAliases( String locale, String sourceDir, boolean ignoreError, boolean ignoreDraft)1899     public static Document parseAndResolveAliases(
1900             String locale, String sourceDir, boolean ignoreError, boolean ignoreDraft) {
1901         try {
1902             Document full = parse(sourceDir + File.separator + locale, ignoreError);
1903             if (full != null) {
1904                 full = resolveAliases(full, sourceDir, locale, ignoreDraft, null);
1905             }
1906             /*
1907              * Debugging
1908              *
1909              * Node[] list = getNodeArray(full, LDMLConstants.ALIAS);
1910              * if(list.length>0){
1911              * System.err.println("Aliases not resolved!. list.getLength() returned "+ list.length);
1912              * }
1913              */
1914             return full;
1915         } catch (Exception ex) {
1916             if (!ignoreError) {
1917                 ex.printStackTrace();
1918                 throw new RuntimeException(ex);
1919             }
1920         }
1921         return null;
1922     }
1923 
getNullErrorHandler( final String filename2, final boolean ignoreError)1924     private static ErrorHandler getNullErrorHandler(
1925             final String filename2, final boolean ignoreError) {
1926         // Local class: cheap non-printing ErrorHandler
1927         // This is used to suppress validation warnings
1928         ErrorHandler nullHandler =
1929                 new ErrorHandler() {
1930                     @Override
1931                     public void warning(SAXParseException e) throws SAXException {
1932                         int col = e.getColumnNumber();
1933                         String msg =
1934                                 (filename2
1935                                         + ":"
1936                                         + e.getLineNumber()
1937                                         + (col >= 0 ? ":" + col : "")
1938                                         + ": WARNING: "
1939                                         + e.getMessage());
1940 
1941                         System.err.println(msg);
1942                         if (!ignoreError) {
1943                             throw new RuntimeException(msg);
1944                         }
1945                     }
1946 
1947                     @Override
1948                     public void error(SAXParseException e) throws SAXException {
1949                         int col = e.getColumnNumber();
1950                         String msg =
1951                                 (filename2
1952                                         + ":"
1953                                         + e.getLineNumber()
1954                                         + (col >= 0 ? ":" + col : "")
1955                                         + ": ERROR: "
1956                                         + e.getMessage());
1957                         System.err.println(msg);
1958                         if (!ignoreError) {
1959                             throw new RuntimeException(msg);
1960                         }
1961                     }
1962 
1963                     @Override
1964                     public void fatalError(SAXParseException e) throws SAXException {
1965                         throw e;
1966                     }
1967                 };
1968         return nullHandler;
1969     }
1970 
newDocument()1971     public static Document newDocument() {
1972         return newDocumentBuilder(false).newDocument();
1973     }
1974 
newDocumentBuilder(boolean validating)1975     private static DocumentBuilder newDocumentBuilder(boolean validating) {
1976         try {
1977             DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance();
1978             // Always set namespaces on
1979             dfactory.setNamespaceAware(true);
1980             dfactory.setValidating(validating);
1981             dfactory.setIgnoringComments(false);
1982             dfactory.setExpandEntityReferences(true);
1983             // Set other attributes here as needed
1984             // applyAttributes(dfactory, attributes);
1985 
1986             DocumentBuilder docBuilder = dfactory.newDocumentBuilder();
1987             return docBuilder;
1988         } catch (Throwable se) {
1989             System.err.println(": ERROR : trying to create documentBuilder: " + se.getMessage());
1990             se.printStackTrace();
1991             throw new RuntimeException(se);
1992         }
1993     }
1994 
parse(InputSource docSrc, String filename, boolean ignoreError)1995     public static Document parse(InputSource docSrc, String filename, boolean ignoreError) {
1996         return parse(docSrc, filename, ignoreError, true);
1997     }
1998 
parse( InputSource docSrc, String filename, boolean ignoreError, boolean validating)1999     public static Document parse(
2000             InputSource docSrc, String filename, boolean ignoreError, boolean validating) {
2001         Document doc = null;
2002         try {
2003             // First, attempt to parse as XML (preferred)...
2004             DocumentBuilder docBuilder = newDocumentBuilder(validating);
2005             docBuilder.setErrorHandler(getNullErrorHandler(filename, ignoreError));
2006             doc = docBuilder.parse(docSrc);
2007         } catch (Throwable se) {
2008             // ... if we couldn't parse as XML, attempt parse as HTML...
2009             System.err.println(filename + ": ERROR :" + se.getMessage());
2010             se.printStackTrace();
2011             if (!ignoreError) {
2012                 throw new RuntimeException(se);
2013             }
2014         }
2015         return doc;
2016     } // end of parse()
2017 
2018     /*
2019      * Utility method to translate a String filename to URL.
2020      *
2021      * Note: This method is not necessarily proven to get the
2022      * correct URL for every possible kind of filename; it should
2023      * be improved. It handles the most common cases that we've
2024      * encountered when running Conformance tests on Xalan.
2025      * Also note, this method does not handle other non-file:
2026      * flavors of URLs at all.
2027      *
2028      * If the name is null, return null.
2029      * If the name starts with a common URI scheme (namely the ones
2030      * found in the examples of RFC2396), then simply return the
2031      * name as-is (the assumption is that it's already a URL)
2032      * Otherwise we attempt (cheaply) to convert to a file:/// URL.
2033      */
filenameToURL(String filename)2034     public static String filenameToURL(String filename) {
2035         // null begets null - something like the commutative property
2036         if (null == filename) {
2037             return null;
2038         }
2039 
2040         // Don't translate a string that already looks like a URL
2041         if (filename.startsWith("file:")
2042                 || filename.startsWith("http:")
2043                 || filename.startsWith("ftp:")
2044                 || filename.startsWith("gopher:")
2045                 || filename.startsWith("mailto:")
2046                 || filename.startsWith("news:")
2047                 || filename.startsWith("telnet:")) {
2048             return filename;
2049         }
2050 
2051         File f = new File(filename);
2052         String tmp = PathUtilities.getNormalizedPathString(f);
2053 
2054         // URLs must explicitly use only forward slashes
2055         if (File.separatorChar == '\\') {
2056             tmp = tmp.replace('\\', '/');
2057         }
2058         // Note the presumption that it's a file reference
2059         // Ensure we have the correct number of slashes at the
2060         // start: we always want 3 /// if it's absolute
2061         // (which we should have forced above)
2062         if (tmp.startsWith("/")) {
2063             return "file://" + tmp;
2064         } else {
2065             return "file:///" + tmp;
2066         }
2067     }
2068 
2069     /**
2070      * Method for printing out the DOM Tree. Prints the specified node, recursively.
2071      *
2072      * @param node
2073      * @param out
2074      * @throws IOException
2075      */
printDOMTree(Node node, PrintWriter out, String docType, String copyright)2076     public static void printDOMTree(Node node, PrintWriter out, String docType, String copyright)
2077             throws IOException {
2078         try {
2079             Transformer transformer = TransformerFactory.newInstance().newTransformer();
2080             transformer.setOutputProperty(OutputKeys.METHOD, "xml");
2081             transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
2082             out.print("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
2083             if (copyright != null) {
2084                 out.print(copyright);
2085             }
2086             if (docType != null) {
2087                 out.print(docType);
2088             }
2089             transformer.transform(new DOMSource(node), new StreamResult(out));
2090         } catch (TransformerException te) {
2091             throw new IOException(te.getMessage());
2092         }
2093     }
2094 
2095     // Document readMergeCache(String sourceDir, String last, String loc)
2096     // {
2097     // File cacheName = getCacheName(String sourceDir, last, loc);
2098     // System.out.println(" M:  " + cacheName);
2099     // File cacheFile = new File(xCacheDir, cacheName + ".xml");
2100     // if(cacheFile.exists()) { // && is newer than last, loc
2101     // doc = parse(cacheFile.getPath(),ignoreUnavailable);
2102     // }
2103     // if(doc!=null) {
2104     // System.out.println("Cache hit for " + cacheName);
2105     // }
2106     // return doc;
2107     // }
2108 
2109     // void writeMergeCache(String sourceDir, String last, String loc, Document full)
2110     // {
2111     // try {
2112     // OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(cacheFile),"UTF-8");
2113     // PrintWriter pw = new PrintWriter(writer);
2114     // printDOMTree(full,pw);
2115     // long stop = System.currentTimeMillis();
2116     // long total = (stop-start);
2117     // double s = total/1000.;
2118     // System.out.println(" " + cacheName + " parse time: " + s + "s");
2119     // pw.println("<!-- " + cacheName + " parse time: " + s + "s -->");
2120     // writer.close();
2121     // } catch (Throwable t) {
2122     // System.err.println(t.toString() + " while trying to write cache file " + cacheName);
2123     // }
2124     // }
2125 
getFullPath(int fileType, String fName, String dir)2126     public static String getFullPath(int fileType, String fName, String dir) {
2127         String str = null;
2128         int lastIndex1 =
2129                 fName.lastIndexOf(File.separator, fName.length())
2130                         + 1 /* add 1 to skip past the separator */;
2131         int lastIndex2 = fName.lastIndexOf('.', fName.length());
2132         if (fileType == TXT) {
2133             if (lastIndex2 == -1) {
2134                 fName = fName.trim() + ".txt";
2135             } else {
2136                 if (!fName.substring(lastIndex2).equalsIgnoreCase(".txt")) {
2137                     fName = fName.substring(lastIndex1, lastIndex2) + ".txt";
2138                 }
2139             }
2140             if (dir != null && fName != null) {
2141                 str = dir + "/" + fName.trim();
2142             } else {
2143                 str = System.getProperty("user.dir") + "/" + fName.trim();
2144             }
2145         } else if (fileType == XML) {
2146             if (lastIndex2 == -1) {
2147                 fName = fName.trim() + ".xml";
2148             } else {
2149                 if (!fName.substring(lastIndex2).equalsIgnoreCase(".xml")
2150                         && fName.substring(lastIndex2).equalsIgnoreCase(".xlf")) {
2151                     fName = fName.substring(lastIndex1, lastIndex2) + ".xml";
2152                 }
2153             }
2154             if (dir != null && fName != null) {
2155                 str = dir + "/" + fName;
2156             } else if (lastIndex1 > 0) {
2157                 str = fName;
2158             } else {
2159                 str = System.getProperty("user.dir") + "/" + fName;
2160             }
2161         } else {
2162             System.err.println("Invalid file type.");
2163             System.exit(-1);
2164         }
2165         return str;
2166     }
2167 
2168     /**
2169      * split an alt= tag into pieces. Any piece can be missing (== null) Piece 0: 'alt type'. null
2170      * means this is the normal (non-alt) item. Possible values are 'alternate', 'colloquial', etc.
2171      * Piece 1: 'proposed type'. If non-null, this is a string beginning with 'proposed' and
2172      * containing arbitrary other text.
2173      *
2174      * <p>STRING 0 1 ------------------------------- ""/null null null something something null
2175      * something-proposed something proposed something-proposed3 something proposed3 proposed null
2176      * proposed somethingproposed somethingproposed null
2177      *
2178      * @param alt the alt tag to parse
2179      * @return a 2-element array containing piece 0 and piece 1
2180      * @see formatAlt
2181      */
parseAlt(String alt)2182     public static String[] parseAlt(String alt) {
2183         String[] ret = new String[2];
2184         if (alt == null) {
2185             ret[0] = null;
2186             ret[1] = null;
2187         } else {
2188             int l = alt.indexOf(LDMLConstants.PROPOSED);
2189             if (l == -1) {
2190                 /* no PROPOSED */
2191                 ret[0] = alt; // all alt,
2192                 ret[1] = null; // no kind
2193             } else if (l == 0) {
2194                 /* begins with */
2195                 ret[0] = null; // all properties
2196                 ret[1] = alt;
2197             } else {
2198                 if (alt.charAt(l - 1) != '-') {
2199                     throw new InternalError(
2200                             "Expected '-' before " + LDMLConstants.PROPOSED + " in " + alt);
2201                 }
2202                 ret[0] = alt.substring(0, l - 1);
2203                 ret[1] = alt.substring(l);
2204 
2205                 if (ret[0].length() == 0) {
2206                     ret[0] = null;
2207                 }
2208                 if (ret[1].length() == 0) {
2209                     ret[1] = null;
2210                 }
2211             }
2212         }
2213         return ret;
2214     }
2215 
2216     /**
2217      * Format aan alt string from components.
2218      *
2219      * @param altType optional alternate type (i.e. 'alternate' or 'colloquial').
2220      * @param proposedType
2221      * @see parseAlt
2222      */
formatAlt(String altType, String proposedType)2223     public static String formatAlt(String altType, String proposedType) {
2224 
2225         if (((altType == null) || (altType.length() == 0))
2226                 && ((proposedType == null) || (proposedType.length() == 0))) {
2227             return null;
2228         }
2229 
2230         if ((proposedType == null) || (proposedType.length() == 0)) {
2231             return altType; // no proposed type: 'alternate'
2232         } else if (!proposedType.startsWith(LDMLConstants.PROPOSED)) {
2233             throw new InternalError("proposedType must begin with " + LDMLConstants.PROPOSED);
2234         }
2235 
2236         if ((altType == null) || (altType.length() == 0)) {
2237             return proposedType; // Just a proposed type: "proposed" or "proposed-3"
2238         } else {
2239             return altType + "-" + proposedType; // 'alternate-proposed'
2240         }
2241     }
2242 
2243     /**
2244      * Compatibility.
2245      *
2246      * @param node
2247      * @param xpath
2248      * @return
2249      * @throws TransformerException
2250      */
XPathAPI_selectNodeList(Node node, String xpath)2251     private static NodeList XPathAPI_selectNodeList(Node node, String xpath)
2252             throws TransformerException {
2253         XPathFactory factory = XPathFactory.newInstance();
2254         XPath xPath = factory.newXPath();
2255         setNamespace(xPath, xpath);
2256         try {
2257             XPathExpression xPathExpression = xPath.compile(xpath);
2258             return (NodeList) xPathExpression.evaluate(node, XPathConstants.NODESET);
2259         } catch (XPathExpressionException e) {
2260             throw new TransformerException("Exception in XPathAPI_selectNodeList: " + xpath, e);
2261         }
2262     }
2263 
XPathAPI_selectNodeList(Document doc, String xpath, Node namespaceNode)2264     private static NodeList XPathAPI_selectNodeList(Document doc, String xpath, Node namespaceNode)
2265             throws TransformerException {
2266         XPathFactory factory = XPathFactory.newInstance();
2267         XPath xPath = factory.newXPath();
2268         setNamespace(xPath, xpath);
2269         try {
2270             XPathExpression xPathExpression = xPath.compile(xpath);
2271             return (NodeList) xPathExpression.evaluate(doc, XPathConstants.NODESET);
2272         } catch (XPathExpressionException e) {
2273             throw new TransformerException("Exception in XPathAPI_selectNodeList: " + xpath, e);
2274         }
2275     }
2276 
XPathAPI_selectNodeList(Node context, String xpath, Node namespaceNode)2277     private static NodeList XPathAPI_selectNodeList(Node context, String xpath, Node namespaceNode)
2278             throws TransformerException {
2279         XPathFactory factory = XPathFactory.newInstance();
2280         XPath xPath = factory.newXPath();
2281         setNamespace(xPath, xpath);
2282         try {
2283             XPathExpression xPathExpression = xPath.compile(xpath);
2284             return (NodeList) xPathExpression.evaluate(context, XPathConstants.NODESET);
2285         } catch (XPathExpressionException e) {
2286             throw new TransformerException("Exception in XPathAPI_selectNodeList: " + xpath, e);
2287         }
2288     }
2289 
XPathAPI_eval(Node context, String string, Node namespaceNode)2290     private static void XPathAPI_eval(Node context, String string, Node namespaceNode)
2291             throws TransformerException {
2292         XPathAPI_selectNodeList(context, string, namespaceNode);
2293     }
2294 
XPathAPI_eval(Node context, String string)2295     private static void XPathAPI_eval(Node context, String string) throws TransformerException {
2296         XPathAPI_selectNodeList(context, string);
2297     }
2298 
setNamespace(XPath xpath, String string)2299     private static void setNamespace(XPath xpath, String string) {
2300         if (string.contains("special/icu:")) {
2301             xpath.setNamespaceContext(
2302                     new javax.xml.namespace.NamespaceContext() {
2303                         @Override
2304                         public String getNamespaceURI(String prefix) {
2305                             if (prefix.equals("icu")) {
2306                                 return "http://www.icu-project.org";
2307                             }
2308                             return null;
2309                         }
2310 
2311                         @Override
2312                         public String getPrefix(String namespaceURI) {
2313                             return null;
2314                         }
2315 
2316                         @Override
2317                         public Iterator<String> getPrefixes(String namespaceURI) {
2318                             return null;
2319                         }
2320                     });
2321         }
2322     }
2323 }
2324