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