1 package org.unicode.cldr.util;
2 
3 import java.io.File;
4 import java.io.FileInputStream;
5 import java.io.IOException;
6 import java.io.InputStream;
7 import java.io.InputStreamReader;
8 import java.nio.charset.Charset;
9 import java.util.ArrayList;
10 import java.util.Arrays;
11 import java.util.HashSet;
12 import java.util.List;
13 import java.util.Map;
14 import java.util.Map.Entry;
15 import java.util.Objects;
16 import java.util.Set;
17 import java.util.TreeMap;
18 import java.util.regex.Matcher;
19 import java.util.regex.Pattern;
20 
21 import org.unicode.cldr.util.CLDRFile.DraftStatus;
22 import org.unicode.cldr.util.XMLFileReader.AllHandler;
23 import org.xml.sax.Attributes;
24 import org.xml.sax.Locator;
25 import org.xml.sax.SAXException;
26 import org.xml.sax.SAXParseException;
27 
28 import com.google.common.cache.CacheBuilder;
29 import com.google.common.cache.CacheLoader;
30 import com.google.common.cache.LoadingCache;
31 import com.google.common.collect.ImmutableSet;
32 import com.ibm.icu.impl.Utility;
33 import com.ibm.icu.text.UnicodeSet;
34 import com.ibm.icu.util.ICUUncheckedIOException;
35 import com.ibm.icu.util.VersionInfo;
36 
37 /**
38  * Loading Normalized XMLSource
39  */
40 public class XMLNormalizingLoader{
41 
42     private static final int CACHE_LIMIT = 700;
43     private static LoadingCache<XMLSourceCacheKey, XMLSource> cache = CacheBuilder.newBuilder()
44         .maximumSize(CACHE_LIMIT)
45         .softValues()   // will garbage-collected in LRU manner in response to memory demand
46         .build(
47             new CacheLoader<XMLSourceCacheKey, XMLSource>() {
48                 @Override
49                 public XMLSource load(XMLSourceCacheKey key) {
50                     return makeXMLSource(key);
51                 }
52             });
53 
54     private static final boolean LOG_PROGRESS = false;
55     private static final boolean DEBUG = false;
56     enum SupplementalStatus {
57         NEVER_SET, IS_SUMPPLEMENTAL, NOT_SUPPLEMENTAL
58     };
59 
60     private static class XMLSourceCacheKey {
61         private final String localeId;
62         private final Set<File> dirs;
63         private final DraftStatus minimalDraftStatus;
64         private final int hashCode;
XMLSourceCacheKey(String localeId, List<File> dirs, DraftStatus minimalDraftStatus)65         public XMLSourceCacheKey(String localeId, List<File> dirs, DraftStatus minimalDraftStatus) {
66             this.localeId = localeId;
67             // Parameter check: the directory/file supplied must be non-null and readable.
68             if (dirs == null || dirs.isEmpty()) {
69                 throw new ICUUncheckedIOException("Attempt to create a XMLSourceCacheKey with a null directory, please supply a non-null one.");
70             }
71             ImmutableSet.Builder<File> _dirs = ImmutableSet.builder();
72             for (File dir : dirs) {
73                 if (!dir.canRead()) {
74                     throw new ICUUncheckedIOException("The directory specified, " + dir.getPath() + ", cannot be read");
75                 }
76                 _dirs.add(dir);
77             }
78             this.dirs = _dirs.build();
79             this.minimalDraftStatus = minimalDraftStatus;
80             this.hashCode = Objects.hash(this.localeId, this.dirs, this.minimalDraftStatus);
81         }
82 
83         @Override
hashCode()84         public int hashCode() {
85             return hashCode;
86         }
87 
88         @Override
equals(Object obj)89         public boolean equals(Object obj) {
90             if (this == obj) {
91                 return true;
92             }
93             if (obj == null) {
94                 return false;
95             }
96             if (getClass() != obj.getClass()) {
97                 return false;
98             }
99             XMLSourceCacheKey other = (XMLSourceCacheKey) obj;
100             if(hashCode != other.hashCode) {
101                 return false;
102             }
103             if (!Objects.equals(dirs, other.dirs)) {
104                 return false;
105             }
106             if (minimalDraftStatus != other.minimalDraftStatus) {
107                 return false;
108             }
109             if (!Objects.equals(localeId, other.localeId)) {
110                 return false;
111             }
112             return true;
113         }
114     }
115 
getFrozenInstance(String localeId, List<File> dirs, DraftStatus minimalDraftStatus)116     public static XMLSource getFrozenInstance(String localeId, List<File> dirs, DraftStatus minimalDraftStatus) {
117         XMLSourceCacheKey key = new XMLSourceCacheKey(localeId, dirs, minimalDraftStatus);
118         return cache.getUnchecked(key);
119     }
120 
makeXMLSource(XMLSourceCacheKey key)121     private static XMLSource makeXMLSource(XMLSourceCacheKey key) {
122         XMLSource source = null;
123         if (key.dirs.size() == 1) {
124             File file = new File(key.dirs.iterator().next(), key.localeId + ".xml");
125             source = loadXMLFile(file, key.localeId, key.minimalDraftStatus);
126             source.freeze();
127             return source;
128         }
129 
130         // if contains more than one file, make XMLSource from each file and then combine them to a combined XMLSource,
131         // so that can cache single file XMLSource as well as combined XMLSource
132         List<XMLSource> list = new ArrayList<>();
133         List<File> dirList = new ArrayList<>();
134         for (File dir: key.dirs) {
135             dirList.clear();
136             dirList.add(dir);
137             XMLSourceCacheKey singleKey = new XMLSourceCacheKey(key.localeId, dirList, key.minimalDraftStatus);
138             XMLSource singleSource = cache.getUnchecked(singleKey);
139             list.add(singleSource);
140         }
141 
142         source = list.get(0).cloneAsThawed();
143         for (int i = 1; i < list.size(); i++) {
144             XMLSource other = list.get(i);
145             source.putAll(other, 0); // 0 --> merge_keep_mine
146             source.getXpathComments().joinAll(other.getXpathComments());
147         }
148         source.freeze();
149         return source;
150     }
151 
loadXMLFile(File f, String localeId, DraftStatus minimalDraftStatus)152     public static XMLSource loadXMLFile(File f, String localeId, DraftStatus minimalDraftStatus) {
153         // use try-with-resources statement
154         try (
155             InputStream fis = new StripUTF8BOMInputStream(new FileInputStream(f));
156             InputStreamReader reader = new InputStreamReader(fis, Charset.forName("UTF-8"))
157         ) {
158             String fullFileName = f.getCanonicalPath();
159             XMLSource source = new SimpleXMLSource(localeId);
160             XMLNormalizingHandler XML_HANDLER = new XMLNormalizingHandler(source, minimalDraftStatus);
161             XMLFileReader.read(fullFileName, reader, -1, true, XML_HANDLER);
162             if (XML_HANDLER.supplementalStatus == SupplementalStatus.NEVER_SET) {
163                 throw new IllegalArgumentException("root of file must be either ldml or supplementalData");
164             }
165             source.setNonInheriting(XML_HANDLER.supplementalStatus == SupplementalStatus.NOT_SUPPLEMENTAL);
166             if (XML_HANDLER.overrideCount > 0) {
167                 throw new IllegalArgumentException("Internal problems: either data file has duplicate path, or" +
168                     " CLDRFile.isDistinguishing() or CLDRFile.isOrdered() need updating: "
169                     + XML_HANDLER.overrideCount
170                     + "; The exact problems are printed on the console above.");
171             }
172             return source;
173         } catch (IOException e) {
174             throw new ICUUncheckedIOException("Cannot read the file " + f, e);
175         }
176     }
177 
178     private static class XMLNormalizingHandler implements AllHandler {
179         private DraftStatus minimalDraftStatus;
180         private static final boolean SHOW_START_END = false;
181         private int commentStackIndex;
182         private boolean justPopped = false;
183         private String lastChars = "";
184         private StringBuilder currentFullXPathSb = new StringBuilder("/");
185         private String comment = null;
186         private Map<String, String> attributeOrder;
187         private DtdData dtdData;
188         private XMLSource source;
189         private String lastActiveLeafNode;
190         private String lastLeafNode;
191         private SupplementalStatus supplementalStatus = SupplementalStatus.NEVER_SET;
192         private final static int MAX_DEPTH = 30; // just make deep enough to handle any CLDR file.
193         // orderedCounter, orderedString, and level logically form a single class that allows adding elements, but never removed.
194         private int[] orderedCounter = new int[MAX_DEPTH];
195         private String[] orderedString = new String[MAX_DEPTH];
196         private int level = 0;
197         private int overrideCount = 0;
198         // Types which changed from 'type' to 'choice', but not in supplemental data.
199         private static final Set<String> CHANGED_TYPES = new HashSet<>(Arrays.asList(new String[] {
200             "abbreviationFallback",
201             "default", "mapping", "measurementSystem", "preferenceOrdering" }));
202         private static final Pattern DRAFT_PATTERN = PatternCache.get("\\[@draft=\"([^\"]*)\"\\]");
203         private static final Pattern WHITESPACE_WITH_LF = PatternCache.get("\\s*\\u000a\\s*");
204         private Matcher draftMatcher = DRAFT_PATTERN.matcher("");
205         private Matcher whitespaceWithLf = WHITESPACE_WITH_LF.matcher("");
206         private static final UnicodeSet CONTROLS = new UnicodeSet("[:cc:]").freeze();
207         private static final UnicodeSet WHITESPACE = new UnicodeSet("[:whitespace:]").freeze();
208 
XMLNormalizingHandler(XMLSource source, DraftStatus minimalDraftStatus)209         XMLNormalizingHandler(XMLSource source, DraftStatus minimalDraftStatus) {
210             this.source = source;
211             this.minimalDraftStatus = minimalDraftStatus;
212         }
213 
show(Attributes attributes)214         private String show(Attributes attributes) {
215             if (attributes == null) return "null";
216             StringBuilder result = new StringBuilder();
217             for (int i = 0; i < attributes.getLength(); ++i) {
218                 String attribute = attributes.getQName(i);
219                 String value = attributes.getValue(i);
220                 result.append( "[@" + attribute + "=\"" + value + "\"]"); // TODO quote the value??
221             }
222             return result.toString();
223         }
224 
push(String qName, Attributes attributes)225         private void push(String qName, Attributes attributes) {
226             Log.logln(LOG_PROGRESS, "push\t" + qName + "\t" + show(attributes));
227             ++level;
228             if (!qName.equals(orderedString[level])) {
229                 orderedString[level] = qName;
230             }
231             if (lastChars.length() != 0) {
232                 if (WHITESPACE.containsAll(lastChars))
233                     lastChars = "";
234                 else
235                     throw new IllegalArgumentException("Must not have mixed content: " + qName + ", "
236                         + show(attributes) + ", Content: " + lastChars);
237             }
238 
239             currentFullXPathSb.append("/" + qName);
240             if (dtdData.isOrdered(qName)) {
241                 currentFullXPathSb.append(orderingAttribute());
242             }
243             if (attributes.getLength() > 0) {
244                 attributeOrder.clear();
245                 for (int i = 0; i < attributes.getLength(); ++i) {
246                     String attribute = attributes.getQName(i);
247                     String value = attributes.getValue(i);
248 
249                     if (attribute.equals("cldrVersion")
250                         && (qName.equals("version"))) {
251                         ((SimpleXMLSource) source).setDtdVersionInfo(VersionInfo.getInstance(value));
252                     } else {
253                         putAndFixDeprecatedAttribute(qName, attribute, value);
254                     }
255                 }
256                 for (Entry<String, String> entry : attributeOrder.entrySet()) {
257                     String attribute = entry.getKey();
258                     String value = entry.getValue();
259                     String both = "[@" + attribute + "=\"" + value + "\"]"; // TODO quote the value??
260                     currentFullXPathSb.append(both);
261                 }
262             }
263             if (comment != null) {
264                 String currentFullXPath = currentFullXPathSb.toString();
265                 if (currentFullXPath.equals("//ldml") || currentFullXPath.equals("//supplementalData")) {
266                     source.setInitialComment(comment);
267                 } else {
268                     source.addComment(currentFullXPath, comment, XPathParts.Comments.CommentType.PREBLOCK);
269                 }
270                 comment = null;
271             }
272             justPopped = false;
273             lastActiveLeafNode = null;
274             Log.logln(LOG_PROGRESS, "currentFullXPath\t" + currentFullXPathSb.toString());
275         }
276 
277 
orderingAttribute()278         private String orderingAttribute() {
279             return "[@_q=\"" + (orderedCounter[level]++) + "\"]";
280         }
281 
putAndFixDeprecatedAttribute(String element, String attribute, String value)282         private void putAndFixDeprecatedAttribute(String element, String attribute, String value) {
283             if (attribute.equals("draft")) {
284                 if (value.equals("true")) {
285                     value = "approved";
286                 }
287                 else if (value.equals("false")) {
288                     value = "unconfirmed";
289                 }
290             } else if (attribute.equals("type")) {
291                 if (CHANGED_TYPES.contains(element) &&  supplementalStatus!= SupplementalStatus.NOT_SUPPLEMENTAL) { // measurementSystem for example did not
292                     // change from 'type' to 'choice'.
293                     attribute = "choice";
294                 }
295             }
296 
297             attributeOrder.put(attribute, value);
298         }
299 
300         /**
301          * Adds a parsed XPath to the CLDRFile.
302          *
303          * @param fullXPath
304          * @param value
305          */
addPath(String fullXPath, String value)306         private void addPath(String fullXPath, String value) {
307             String former = source.getValueAtPath(fullXPath);
308             if (former != null) {
309                 String formerPath = source.getFullXPath(fullXPath);
310                 if (!former.equals(value) || !fullXPath.equals(formerPath)) {
311                     if (!fullXPath.startsWith("//ldml/identity/version") && !fullXPath.startsWith("//ldml/identity/generation")) {
312                         warnOnOverride(former, formerPath);
313                     }
314                 }
315             }
316             value = trimWhitespaceSpecial(value);
317             source.add(fullXPath, value);
318         }
319 
pop(String qName)320         private void pop(String qName) {
321             Log.logln(LOG_PROGRESS, "pop\t" + qName);
322             --level;
323             String currentFullXPath = currentFullXPathSb.toString();
324             if (!lastChars.isEmpty() || justPopped == false) {
325                 boolean acceptItem = minimalDraftStatus == DraftStatus.unconfirmed;
326                 if (!acceptItem) {
327                     if (draftMatcher.reset(currentFullXPath).find()) {
328                         DraftStatus foundStatus = DraftStatus.valueOf(draftMatcher.group(1));
329                         if (minimalDraftStatus.compareTo(foundStatus) <= 0) {
330                             // what we found is greater than or equal to our status
331                             acceptItem = true;
332                         }
333                     } else {
334                         acceptItem = true; // if not found, then the draft status is approved, so it is always ok
335                     }
336                 }
337                 if (acceptItem) {
338                     // Change any deprecated orientation attributes into values
339                     // for backwards compatibility.
340                     boolean skipAdd = false;
341                     if (currentFullXPath.startsWith("//ldml/layout/orientation")) {
342                         XPathParts parts = XPathParts.getFrozenInstance(currentFullXPath);
343                         String value = parts.getAttributeValue(-1, "characters");
344                         if (value != null) {
345                             addPath("//ldml/layout/orientation/characterOrder", value);
346                             skipAdd = true;
347                         }
348                         value = parts.getAttributeValue(-1, "lines");
349                         if (value != null) {
350                             addPath("//ldml/layout/orientation/lineOrder", value);
351                             skipAdd = true;
352                         }
353                     }
354                     if (!skipAdd) {
355                         addPath(currentFullXPath, lastChars);
356                     }
357                     lastLeafNode = lastActiveLeafNode = currentFullXPath;
358                 }
359                 lastChars = "";
360             } else {
361                 Log.logln(LOG_PROGRESS && lastActiveLeafNode != null, "pop: zeroing last leafNode: "
362                     + lastActiveLeafNode);
363                 lastActiveLeafNode = null;
364                 if (comment != null) {
365                     source.addComment(lastLeafNode, comment, XPathParts.Comments.CommentType.POSTBLOCK);
366                     comment = null;
367                 }
368             }
369             currentFullXPathSb.setLength(0);
370             currentFullXPathSb.append(stripAfter(currentFullXPath, qName));
371             justPopped = true;
372         }
373 
374         /**
375          * Trim leading whitespace if there is a linefeed among them, then the same with trailing.
376          *
377          * @param source
378          * @return
379          */
trimWhitespaceSpecial(String source)380         private String trimWhitespaceSpecial(String source) {
381             if (DEBUG && CONTROLS.containsSome(source)) {
382                 System.out.println("*** " + source);
383             }
384             if (!source.contains("\n")) {
385                 return source;
386             }
387             source = whitespaceWithLf.reset(source).replaceAll("\n");
388             return source;
389         }
390 
warnOnOverride(String former, String formerPath)391         private void warnOnOverride(String former, String formerPath) {
392             String distinguishing = CLDRFile.getDistinguishingXPath(formerPath, null);
393             System.out.println("\tERROR in " + source.getLocaleID()
394                 + ";\toverriding old value <" + former + "> at path " + distinguishing +
395                 "\twith\t<" + lastChars + ">" +
396                 CldrUtility.LINE_SEPARATOR + "\told fullpath: " + formerPath +
397                 CldrUtility.LINE_SEPARATOR + "\tnew fullpath: " + currentFullXPathSb.toString());
398             overrideCount += 1;
399         }
400 
stripAfter(String input, String qName)401         private static String stripAfter(String input, String qName) {
402             int pos = findLastSlash(input);
403             if (qName != null) {
404                 // assert input.substring(pos+1).startsWith(qName);
405                 if (!input.substring(pos + 1).startsWith(qName)) {
406                     throw new IllegalArgumentException("Internal Error: should never get here.");
407                 }
408             }
409             return input.substring(0, pos);
410         }
411 
findLastSlash(String input)412         private static int findLastSlash(String input) {
413             int braceStack = 0;
414             char inQuote = 0;
415             for (int i = input.length() - 1; i >= 0; --i) {
416                 char ch = input.charAt(i);
417                 switch (ch) {
418                 case '\'':  // treat single and double quotes in same way
419                 case '"':
420                     if (inQuote == 0) {
421                         inQuote = ch;
422                     } else if (inQuote == ch) {
423                         inQuote = 0; // come out of quote
424                     }
425                     break;
426                 case '/':
427                     if (inQuote == 0 && braceStack == 0) {
428                         return i;
429                     }
430                     break;
431                 case '[':
432                     if (inQuote == 0) {
433                         --braceStack;
434                     }
435                     break;
436                 case ']':
437                     if (inQuote == 0) {
438                         ++braceStack;
439                     }
440                     break;
441                 }
442             }
443             return -1;
444         }
445 
446         // SAX items we need to catch
447 
448         @Override
startElement( String uri, String localName, String qName, Attributes attributes)449         public void startElement(
450             String uri,
451             String localName,
452             String qName,
453             Attributes attributes)
454             throws SAXException {
455             Log.logln(LOG_PROGRESS || SHOW_START_END, "startElement uri\t" + uri
456                 + "\tlocalName " + localName
457                 + "\tqName " + qName
458                 + "\tattributes " + show(attributes));
459             try {
460                 if (supplementalStatus == SupplementalStatus.NEVER_SET) { // set by first element
461                     attributeOrder = new TreeMap<>(
462                         // HACK for ldmlIcu
463                         dtdData.dtdType == DtdType.ldml
464                             ? CLDRFile.getAttributeOrdering()
465                             : dtdData.getAttributeComparator());
466                     supplementalStatus = source.getXMLNormalizingDtdType() == DtdType.ldml ?
467                         SupplementalStatus.IS_SUMPPLEMENTAL : SupplementalStatus.NOT_SUPPLEMENTAL;
468                 }
469                 push(qName, attributes);
470             } catch (RuntimeException e) {
471                 e.printStackTrace();
472                 throw e;
473             }
474         }
475 
476         @Override
endElement(String uri, String localName, String qName)477         public void endElement(String uri, String localName, String qName)
478             throws SAXException {
479             Log.logln(LOG_PROGRESS || SHOW_START_END, "endElement uri\t" + uri + "\tlocalName " + localName
480                 + "\tqName " + qName);
481             try {
482                 pop(qName);
483             } catch (RuntimeException e) {
484                 throw e;
485             }
486         }
487 
488         @Override
characters(char[] ch, int start, int length)489         public void characters(char[] ch, int start, int length)
490             throws SAXException {
491             try {
492                 String value = new String(ch, start, length);
493                 Log.logln(LOG_PROGRESS, "characters:\t" + value);
494                 // we will strip leading and trailing line separators in another place.
495                 // if (value.indexOf(XML_LINESEPARATOR) >= 0) {
496                 // value = value.replace(XML_LINESEPARATOR, '\u0020');
497                 // }
498                 lastChars += value;
499                 justPopped = false;
500             } catch (RuntimeException e) {
501                 e.printStackTrace();
502                 throw e;
503             }
504         }
505 
506         @Override
startDTD(String name, String publicId, String systemId)507         public void startDTD(String name, String publicId, String systemId) throws SAXException {
508             Log.logln(LOG_PROGRESS, "startDTD name: " + name
509                 + ", publicId: " + publicId
510                 + ", systemId: " + systemId);
511             commentStackIndex++;
512             source.setXMLNormalizingDtdType(DtdType.valueOf(name));
513             dtdData = DtdData.getInstance(source.getXMLNormalizingDtdType());
514         }
515 
516         @Override
endDTD()517         public void endDTD() throws SAXException {
518             Log.logln(LOG_PROGRESS, "endDTD");
519             commentStackIndex--;
520         }
521 
522         @Override
comment(char[] ch, int start, int length)523         public void comment(char[] ch, int start, int length) throws SAXException {
524             final String string = new String(ch, start, length);
525             Log.logln(LOG_PROGRESS, commentStackIndex + " comment " + string);
526             try {
527                 if (commentStackIndex != 0) return;
528                 String comment0 = trimWhitespaceSpecial(string).trim();
529                 if (lastActiveLeafNode != null) {
530                     source.addComment(lastActiveLeafNode, comment0, XPathParts.Comments.CommentType.LINE);
531                 } else {
532                     comment = (comment == null ? comment0 : comment + XPathParts.NEWLINE + comment0);
533                 }
534             } catch (RuntimeException e) {
535                 e.printStackTrace();
536                 throw e;
537             }
538         }
539 
540         @Override
ignorableWhitespace(char[] ch, int start, int length)541         public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
542             if (LOG_PROGRESS)
543                 Log.logln(LOG_PROGRESS,
544                     "ignorableWhitespace length: " + length + ": " + Utility.hex(new String(ch, start, length)));
545             for (int i = start; i < start + length; ++i) {
546                 if (ch[i] == '\n') {
547                     Log.logln(LOG_PROGRESS && lastActiveLeafNode != null, "\\n: zeroing last leafNode: "
548                         + lastActiveLeafNode);
549                     lastActiveLeafNode = null;
550                     break;
551                 }
552             }
553         }
554 
555         @Override
startDocument()556         public void startDocument() throws SAXException {
557             Log.logln(LOG_PROGRESS, "startDocument");
558             commentStackIndex = 0; // initialize
559         }
560 
561         @Override
endDocument()562         public void endDocument() throws SAXException {
563             Log.logln(LOG_PROGRESS, "endDocument");
564             try {
565                 if (comment != null) {
566                     source.addComment(null, comment, XPathParts.Comments.CommentType.LINE);
567                 }
568             } catch (RuntimeException e) {
569                 e.printStackTrace();
570                 throw e;
571             }
572         }
573 
574         // ==== The following are just for debugging =====
575 
576         @Override
elementDecl(String name, String model)577         public void elementDecl(String name, String model) throws SAXException {
578             Log.logln(LOG_PROGRESS, "Attribute\t" + name + "\t" + model);
579         }
580 
581         @Override
attributeDecl(String eName, String aName, String type, String mode, String value)582         public void attributeDecl(String eName, String aName, String type, String mode, String value)
583             throws SAXException {
584             Log.logln(LOG_PROGRESS, "Attribute\t" + eName + "\t" + aName + "\t" + type + "\t" + mode + "\t" + value);
585         }
586 
587         @Override
internalEntityDecl(String name, String value)588         public void internalEntityDecl(String name, String value) throws SAXException {
589             Log.logln(LOG_PROGRESS, "Internal Entity\t" + name + "\t" + value);
590         }
591 
592         @Override
externalEntityDecl(String name, String publicId, String systemId)593         public void externalEntityDecl(String name, String publicId, String systemId) throws SAXException {
594             Log.logln(LOG_PROGRESS, "Internal Entity\t" + name + "\t" + publicId + "\t" + systemId);
595         }
596 
597         @Override
processingInstruction(String target, String data)598         public void processingInstruction(String target, String data)
599             throws SAXException {
600             Log.logln(LOG_PROGRESS, "processingInstruction: " + target + ", " + data);
601         }
602 
603         @Override
skippedEntity(String name)604         public void skippedEntity(String name)
605             throws SAXException {
606             Log.logln(LOG_PROGRESS, "skippedEntity: " + name);
607         }
608 
609         @Override
setDocumentLocator(Locator locator)610         public void setDocumentLocator(Locator locator) {
611             Log.logln(LOG_PROGRESS, "setDocumentLocator Locator " + locator);
612         }
613 
614         @Override
startPrefixMapping(String prefix, String uri)615         public void startPrefixMapping(String prefix, String uri) throws SAXException {
616             Log.logln(LOG_PROGRESS, "startPrefixMapping prefix: " + prefix +
617                 ", uri: " + uri);
618         }
619 
620         @Override
endPrefixMapping(String prefix)621         public void endPrefixMapping(String prefix) throws SAXException {
622             Log.logln(LOG_PROGRESS, "endPrefixMapping prefix: " + prefix);
623         }
624 
625         @Override
startEntity(String name)626         public void startEntity(String name) throws SAXException {
627             Log.logln(LOG_PROGRESS, "startEntity name: " + name);
628         }
629 
630         @Override
endEntity(String name)631         public void endEntity(String name) throws SAXException {
632             Log.logln(LOG_PROGRESS, "endEntity name: " + name);
633         }
634 
635         @Override
startCDATA()636         public void startCDATA() throws SAXException {
637             Log.logln(LOG_PROGRESS, "startCDATA");
638         }
639 
640         @Override
endCDATA()641         public void endCDATA() throws SAXException {
642             Log.logln(LOG_PROGRESS, "endCDATA");
643         }
644 
645         /*
646          * (non-Javadoc)
647          *
648          * @see org.xml.sax.ErrorHandler#error(org.xml.sax.SAXParseException)
649          */
650         @Override
error(SAXParseException exception)651         public void error(SAXParseException exception) throws SAXException {
652             Log.logln(LOG_PROGRESS || true, "error: " + XMLFileReader.showSAX(exception));
653             throw exception;
654         }
655 
656         /*
657          * (non-Javadoc)
658          *
659          * @see org.xml.sax.ErrorHandler#fatalError(org.xml.sax.SAXParseException)
660          */
661         @Override
fatalError(SAXParseException exception)662         public void fatalError(SAXParseException exception) throws SAXException {
663             Log.logln(LOG_PROGRESS, "fatalError: " + XMLFileReader.showSAX(exception));
664             throw exception;
665         }
666 
667         /*
668          * (non-Javadoc)
669          *
670          * @see org.xml.sax.ErrorHandler#warning(org.xml.sax.SAXParseException)
671          */
672         @Override
warning(SAXParseException exception)673         public void warning(SAXParseException exception) throws SAXException {
674             Log.logln(LOG_PROGRESS, "warning: " + XMLFileReader.showSAX(exception));
675             throw exception;
676         }
677     }
678 }
679