2003/04/24 14:33:27
[org.ibex.core.git] / src / org / xwt / XML.java
1 package org.xwt;
2
3 import java.io.*;
4 import java.net.*;
5 import java.util.*;
6
7 /** an event-driven XML parser, derived from MinML (http://www.wilson.co.uk/xml/minml.htm) */
8 public abstract class XML {
9     
10     /////////////////////////////////////////////////////////////////////////////////////////////
11     // The following code was copied from the w3c's org.xml.sax.* classes
12     /////////////////////////////////////////////////////////////////////////////////////////////
13     
14     protected static interface AttributeList {
15         public abstract int getLength ();
16         public abstract String getName (int i);
17         public abstract String getType (int i);
18         public abstract String getValue (int i);
19         public abstract String getType (String name);
20         public abstract String getValue (String name);
21     }
22     
23     protected static interface DTDHandler {
24         public abstract void notationDecl (String name, String publicId, String systemId) throws SAXException;
25         public abstract void unparsedEntityDecl (String name, String publicId, String systemId, String notationName) throws SAXException;
26     }
27     
28     protected static interface EntityResolver {
29         public abstract InputSource resolveEntity (String publicId, String systemId) throws SAXException, IOException;
30     }
31     
32     protected static interface ErrorHandler {
33         public abstract void warning (SAXParseException exception) throws SAXException;
34         public abstract void error (SAXParseException exception) throws SAXException;
35         public abstract void fatalError (SAXParseException exception) throws SAXException;
36     }
37     
38     private static class InputSource {
39         public InputSource () { }
40         public InputSource (String systemId) { setSystemId(systemId); }
41         public InputSource (InputStream byteStream) { setByteStream(byteStream); }
42         public InputSource (Reader characterStream) { setCharacterStream(characterStream); }
43         public void setPublicId (String publicId) { this.publicId = publicId; }
44         public String getPublicId () { return publicId; }
45         public void setSystemId (String systemId) { this.systemId = systemId; }
46         public String getSystemId() { return systemId; }
47         public void setByteStream (InputStream byteStream) { this.byteStream = byteStream; }
48         public InputStream getByteStream() { return byteStream; }
49         public void setEncoding (String encoding) { this.encoding = encoding; }
50         public String getEncoding() { return encoding; }
51         public void setCharacterStream (Reader characterStream) { this.characterStream = characterStream; }
52         public Reader getCharacterStream () { return characterStream; }
53         private String publicId;
54         private String systemId;
55         private InputStream byteStream;
56         private String encoding;
57         private Reader characterStream;
58     }
59     
60     protected static interface Locator {
61         public abstract String getPublicId ();
62         public abstract String getSystemId ();
63         public abstract int getLineNumber ();
64         public abstract int getColumnNumber ();
65     }
66     
67     protected static interface Parser {
68         public abstract void setLocale (Locale locale) throws SAXException;
69         public abstract void setEntityResolver (EntityResolver resolver);
70         public abstract void setDTDHandler (DTDHandler handler);
71         public abstract void setDocumentHandler (DocumentHandler handler);
72         public abstract void setErrorHandler (ErrorHandler handler);
73         public abstract void parse (InputSource source) throws SAXException, IOException;
74         public abstract void parse (String systemId) throws SAXException, IOException;
75     }
76     
77     public static class SAXException extends Exception {
78         public SAXException (String message) {
79             super();
80             this.message = message;
81             this.exception = null;
82         }
83         
84         public SAXException (Exception e) {
85             super();
86             this.message = null;
87             this.exception = e;
88         }
89         
90         public SAXException (String message, Exception e) {
91             super();
92             this.message = message;
93             this.exception = e;
94         }
95         
96         public String getMessage () {
97             if (message == null && exception != null) {
98                 return exception.getMessage();
99             } else {
100                 return this.message;
101             }
102         }
103         
104         public Exception getException () { return exception; }
105         public String toString () { return getMessage(); }
106         private String message;
107         private Exception exception;
108     }
109     
110     static class SAXParseException extends SAXException {
111         public SAXParseException (String message, Locator locator) {
112             super(message);
113             this.publicId = locator.getPublicId();
114             this.systemId = locator.getSystemId();
115             this.lineNumber = locator.getLineNumber();
116             this.columnNumber = locator.getColumnNumber();
117         }
118         public SAXParseException (String message, Locator locator, Exception e) {
119             super(message, e);
120             this.publicId = locator.getPublicId();
121             this.systemId = locator.getSystemId();
122             this.lineNumber = locator.getLineNumber();
123             this.columnNumber = locator.getColumnNumber();
124         }
125         public SAXParseException (String message, String publicId, String systemId, int lineNumber, int columnNumber) {
126             super(message);
127             this.publicId = publicId;
128             this.systemId = systemId;
129             this.lineNumber = lineNumber;
130             this.columnNumber = columnNumber;
131         }
132         public SAXParseException (String message, String publicId, String systemId, int lineNumber, int columnNumber, Exception e) {
133             super(message, e);
134             this.publicId = publicId;
135             this.systemId = systemId;
136             this.lineNumber = lineNumber;
137             this.columnNumber = columnNumber;
138         }
139         public String getPublicId() { return this.publicId; }
140         public String getSystemId() { return this.systemId; }
141         public int getLineNumber () { return this.lineNumber; }
142         public int getColumnNumber () { return this.columnNumber; }
143         private String publicId;
144         private String systemId;
145         private int lineNumber;
146         private int columnNumber;
147     }
148     
149     protected static interface DocumentHandler {
150         public abstract void setDocumentLocator (Locator locator);
151         public abstract void startDocument() throws SAXException;
152         public abstract void endDocument() throws SAXException;
153         public abstract void startElement(String name, AttributeList atts) throws SAXException;
154         public abstract void endElement(String name) throws SAXException;
155         public abstract void characters(char ch[], int start, int length) throws SAXException;
156         public abstract void ignorableWhitespace(char ch[], int start, int length) throws SAXException;
157         public abstract void processingInstruction (String target, String data) throws SAXException;
158         Writer startDocument(final Writer writer) throws SAXException;
159         Writer startElement(final String name, final AttributeList attributes, final Writer writer) throws SAXException;
160     }
161     
162     
163     
164     /////////////////////////////////////////////////////////////////////////////////////////////
165     // Everything from here down is copied verbatim from the MinML 1.7
166     // distribution, except for these modifications:
167     //   - some classes have been changed from 'public' to 'private static'
168     //   - extraneous import and package declarations have been removed
169     //   - the advertising clause of the copyright notice has been removed
170     //     as approved by John Wilson in an email to Adam Megacz.
171     /////////////////////////////////////////////////////////////////////////////////////////////
172     
173     // Copyright (c) 2000, 2001, 2002 The Wilson Partnership.
174     // All Rights Reserved.
175     // @(#)MinML.java, 1.8(provisional), 2nd March 2002
176     // Author: John Wilson - tug@wilson.co.uk
177     
178     /*
179       Copyright (c) 2000, 2001 John Wilson (tug@wilson.co.uk).
180       All rights reserved.
181       Redistribution and use in source and binary forms,
182       with or without modification, are permitted provided
183       that the following conditions are met:
184       
185       Redistributions of source code must retain the above
186       copyright notice, this list of conditions and the
187       following disclaimer.
188       
189       Redistributions in binary form must reproduce the
190       above copyright notice, this list of conditions and
191       the following disclaimer in the documentation and/or
192       other materials provided with the distribution.
193       
194       THIS SOFTWARE IS PROVIDED BY JOHN WILSON ``AS IS'' AND ANY
195       EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
196       THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
197       PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JOHN WILSON
198       BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
199       EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
200       TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
201       DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
202       ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
203       LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
204       IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
205       OF THE POSSIBILITY OF SUCH DAMAGE
206     */
207     
208     private static class MinML implements Parser, Locator, DocumentHandler, ErrorHandler {
209         public static final int endStartName = 0;
210         public static final int emitStartElement = 1;
211         public static final int emitEndElement = 2;
212         public static final int possiblyEmitCharacters = 3;
213         public static final int emitCharacters = 4;
214         public static final int emitCharactersSave = 5;
215         public static final int saveAttributeName = 6;
216         public static final int saveAttributeValue = 7;
217         public static final int startComment = 8;
218         public static final int endComment = 9;
219         public static final int incLevel = 10;
220         public static final int decLevel = 11;
221         public static final int startCDATA = 12;
222         public static final int endCDATA = 13;
223         public static final int processCharRef = 14;
224         public static final int writeCdata = 15;
225         public static final int exitParser = 16;
226         public static final int parseError = 17;
227         public static final int discardAndChange = 18;
228         public static final int discardSaveAndChange = 19;
229         public static final int saveAndChange = 20;
230         public static final int change = 21;
231         public static final int writeAsWS = 22;
232         
233         public static final int inSkipping = 0;
234         public static final int inSTag = 1;
235         public static final int inPossiblyAttribute = 2;
236         public static final int inNextAttribute = 3;
237         public static final int inAttribute = 4;
238         public static final int inAttribute1 = 5;
239         public static final int inAttributeValue = 6;
240         public static final int inAttributeQuoteValue = 7;
241         public static final int inAttributeQuotesValue = 8;
242         public static final int inETag = 9;
243         public static final int inETag1 = 10;
244         public static final int inMTTag = 11;
245         public static final int inTag = 12;
246         public static final int inTag1 = 13;
247         public static final int inPI = 14;
248         public static final int inPI1 = 15;
249         public static final int inPossiblySkipping = 16;
250         public static final int inCharData = 17;
251         public static final int inCDATA = 18;
252         public static final int inCDATA1 = 19;
253         public static final int inComment =20;
254         public static final int inDTD = 21;
255         
256         public MinML(final int initialBufferSize, final int bufferIncrement) {
257             this.initialBufferSize = initialBufferSize;
258             this.bufferIncrement = bufferIncrement;
259         }
260         
261         public MinML() {
262             this(256, 128);
263         }
264         
265         public void parse(final Reader in) throws SAXException, IOException {
266             final Vector attributeNames = new Vector();
267             final Vector attributeValues = new Vector();
268             
269             final AttributeList attrs = new AttributeList() {
270                     public int getLength() {
271                         return attributeNames.size();
272                     }
273                     
274                     public String getName(final int i) {
275                         return (String)attributeNames.elementAt(i);
276                     }
277                     
278                     public String getType(final int i) {
279                         return "CDATA";
280                     }
281                     
282                     public String getValue(final int i) {
283                         return (String)attributeValues.elementAt(i);
284                     }
285                     
286                     public String getType(final String name) {
287                         return "CDATA";
288                     }
289                     
290                     public String getValue(final String name) {
291                         final int index = attributeNames.indexOf(name);
292                         
293                         return (index == -1) ? null : (String)attributeValues.elementAt(index);
294                     }
295                 };
296             
297             final MinMLBuffer buffer = new MinMLBuffer(in);
298             int currentChar = 0, charCount = 0;
299             int level = 0;
300             int mixedContentLevel = -1;
301             String elementName = null;
302             String state = operands[inSkipping];
303             
304             this.lineNumber = 1;
305             this.columnNumber = 0;
306             
307             try {
308                 while(true) {
309                     charCount++;
310                     
311                     //
312                     // this is to try and make the loop a bit faster
313                     // currentChar = buffer.read(); is simpler but is a bit slower.
314                     //
315                     currentChar = (buffer.nextIn == buffer.lastIn) ? buffer.read() : buffer.chars[buffer.nextIn++];
316                     
317                     final int transition;
318                     
319                     if (currentChar > ']') {
320                         transition = state.charAt(14);
321                     } else {
322                         final int charClass = charClasses[currentChar + 1];
323                         
324                         if (charClass == -1) fatalError("Document contains illegal control character with value " + currentChar, this.lineNumber, this.columnNumber);
325                         
326                         if (charClass == 12) {
327                             if (currentChar == '\r') {
328                                 currentChar = '\n';
329                                 charCount = -1;
330                             }
331                             
332                             if (currentChar == '\n') {
333                                 if (charCount == 0) continue;  // preceeded by '\r' so ignore
334                                 
335                                 if (charCount != -1) charCount = 0;
336                                 
337                                 this.lineNumber++;
338                                 this.columnNumber = 0;
339                             }
340                         }
341                         
342                         transition = state.charAt(charClass);
343                     }
344                     
345                     this.columnNumber++;
346                     
347                     final String operand = operands[transition >>> 8];
348                     
349                     switch (transition & 0XFF) {
350                     case endStartName:
351                         // end of start element name
352                         elementName = buffer.getString();
353                         if (currentChar != '>' && currentChar != '/') break;  // change state to operand
354                         // drop through to emit start element (we have no attributes)
355                         
356                     case emitStartElement:
357                         // emit start element
358                         
359                         final Writer newWriter = this.extDocumentHandler.startElement(elementName, attrs,
360                                                                                       (this.tags.empty()) ?
361                                                                                       this.extDocumentHandler.startDocument(buffer)
362                                                                                       :
363                                                                                       buffer.getWriter());
364                         
365                         buffer.pushWriter(newWriter);
366                         this.tags.push(elementName);
367                         
368                         attributeValues.removeAllElements();
369                         attributeNames.removeAllElements();
370                         
371                         if (mixedContentLevel != -1) mixedContentLevel++;
372                         
373                         if (currentChar != '/') break;  // change state to operand
374                         
375                         // <element/> drop through
376                         
377                     case emitEndElement:
378                         // emit end element
379                         
380                         try {
381                             final String begin = (String)this.tags.pop();
382                             
383                             buffer.popWriter();
384                             elementName = buffer.getString();
385                             
386                             if (currentChar != '/' && !elementName.equals(begin)) {
387                                 fatalError("end tag </" + elementName + "> does not match begin tag <" + begin + ">",
388                                            this.lineNumber, this.columnNumber);
389                             } else {
390                                 this.documentHandler.endElement(begin);
391                                 
392                                 if (this.tags.empty()) {
393                                     this.documentHandler.endDocument();
394                                     return;
395                                 }
396                             }
397                         }
398                         catch (final EmptyStackException e) {
399                             fatalError("end tag at begining of document", this.lineNumber, this.columnNumber);
400                         }
401                         
402                         if (mixedContentLevel != -1) --mixedContentLevel;
403                         
404                         break;  // change state to operand
405                         
406                     case emitCharacters:
407                         // emit characters
408                         
409                         buffer.flush();
410                         break;  // change state to operand
411                         
412                     case emitCharactersSave:
413                         // emit characters and save current character
414                         
415                         if (mixedContentLevel == -1) mixedContentLevel = 0;
416                         
417                         buffer.flush();
418                         
419                         buffer.saveChar((char)currentChar);
420                         
421                         break;  // change state to operand
422                         
423                     case possiblyEmitCharacters:
424                         // write any skipped whitespace if in mixed content
425                         
426                         if (mixedContentLevel != -1) buffer.flush();
427                         break;  // change state to operand
428                         
429                     case saveAttributeName:
430                         // save attribute name
431                         
432                         attributeNames.addElement(buffer.getString());
433                         break;  // change state to operand
434                         
435                     case saveAttributeValue:
436                         // save attribute value
437                         
438                         attributeValues.addElement(buffer.getString());
439                         break;  // change state to operand
440                         
441                     case startComment:
442                         // change state if we have found "<!--"
443                         
444                         if (buffer.read() != '-') continue; // not "<!--"
445
446                         state = operands[inComment];
447                         continue;  // change state to operand
448
449                     case endComment:
450                         // change state if we find "-->"
451                         
452                         if ((currentChar = buffer.read()) == '-') {
453                             // deal with the case where we might have "------->"
454                             while ((currentChar = buffer.read()) == '-');
455                             
456                             if (currentChar == '>') {
457                                 state = operands[inCharData];
458                             }
459                         }
460                         
461                         continue;   // not end of comment, don't change state
462                         
463                     case incLevel:
464                         
465                         level++;
466                         
467                         break;
468                         
469                     case decLevel:
470                         
471                         if (level == 0) break; // outer level <> change state
472                         
473                         level--;
474                         
475                         continue; // in nested <>, don't change state
476                         
477                     case startCDATA:
478                         // change state if we have found "<![CDATA["
479                         
480                         if (buffer.read() != 'C') continue;   // don't change state
481                         if (buffer.read() != 'D') continue;   // don't change state
482                         if (buffer.read() != 'A') continue;   // don't change state
483                         if (buffer.read() != 'T') continue;   // don't change state
484                         if (buffer.read() != 'A') continue;   // don't change state
485                         if (buffer.read() != '[') continue;   // don't change state
486                         break;  // change state to operand
487                         
488                     case endCDATA:
489                         // change state if we find "]]>"
490                         
491                         if ((currentChar = buffer.read()) == ']') {
492                             // deal with the case where we might have "]]]]]]]>"
493                             while ((currentChar = buffer.read()) == ']') buffer.write(']');
494                             
495                             if (currentChar == '>') break;  // end of CDATA section, change state to operand
496                             
497                             buffer.write(']');
498                         }
499                         
500                         buffer.write(']');
501                         buffer.write(currentChar);
502                         continue;   // not end of CDATA section, don't change state
503                         
504                     case processCharRef:
505                         // process character entity
506                         
507                         int crefState = 0;
508                         
509                         currentChar = buffer.read();
510                         
511                         while (true) {
512                             if ("#amp;&pos;'quot;\"gt;>lt;<".charAt(crefState) == currentChar) {
513                                 crefState++;
514                                 
515                                 if (currentChar == ';') {
516                                     buffer.write("#amp;&pos;'quot;\"gt;>lt;<".charAt(crefState));
517                                     break;
518                                     
519                                 } else if (currentChar == '#') {
520                                     final int radix;
521                                     
522                                     currentChar = buffer.read();
523                                     
524                                     if (currentChar == 'x') {
525                                         radix = 16;
526                                         currentChar = buffer.read();
527                                     } else {
528                                         radix = 10;
529                                     }
530                                     
531                                     int charRef = Character.digit((char)currentChar, radix);
532                                     
533                                     while (true) {
534                                         currentChar = buffer.read();
535                                         
536                                         final int digit = Character.digit((char)currentChar, radix);
537                                         
538                                         if (digit == -1) break;
539                                         
540                                         charRef = (char)((charRef * radix) + digit);
541                                     }
542                                     
543                                     if (currentChar == ';' && charRef != -1) {
544                                         buffer.write(charRef);
545                                         break;
546                                     }
547                                     
548                                     fatalError("invalid Character Entitiy", this.lineNumber, this.columnNumber);
549                                 } else {
550                                     currentChar = buffer.read();
551                                 }
552                             } else {
553                                 crefState = ("\u0001\u000b\u0006\u00ff\u00ff\u00ff\u00ff\u00ff\u00ff\u00ff\u00ff" +
554                                              //                               #     a     m     p     ;     &     p     o     s     ;     '
555                                              //                               0     1     2     3     4     5     6     7     8     9     a
556                                              "\u0011\u00ff\u00ff\u00ff\u00ff\u00ff\u0015\u00ff\u00ff\u00ff" +
557                                              //                               q     u     o     t     ;     "     g     t     ;     >
558                                              //                               b     b     d     e     f     10    11    12    13    14
559                                              "\u00ff\u00ff\u00ff").charAt(crefState);
560                                 //                               l     t     ;
561                                 //                               15    16    17
562                                 
563                                 if (crefState == 255) fatalError("invalid Character Entitiy", this.lineNumber, this.columnNumber);
564                             }
565                         }
566                         
567                         break;
568                         
569                     case parseError:
570                         // report fatal error
571                         
572                         fatalError(operand, this.lineNumber, this.columnNumber);
573                         // drop through to exit parser
574                         
575                     case exitParser:
576                         // exit parser
577                         
578                         return;
579                         
580                     case writeCdata:
581                         // write character data
582                         // this will also write any skipped whitespace
583                         
584                         buffer.write(currentChar);
585                         break;  // change state to operand
586                         
587                     case writeAsWS:
588                         buffer.write((currentChar == '\n' ? currentChar : (char)32));
589                         break;
590
591                     case discardAndChange:
592                         // throw saved characters away and change state
593                         
594                         buffer.reset();
595                         break;  // change state to operand
596                         
597                     case discardSaveAndChange:
598                         // throw saved characters away, save character and change state
599                         
600                         buffer.reset();
601                         // drop through to save character and change state
602                         
603                     case saveAndChange:
604                         // save character and change state
605                         
606                         buffer.saveChar((char)currentChar);
607                         break;  // change state to operand
608                         
609                     case change:
610                         // change state to operand
611                         
612                         break;  // change state to operand
613                     }
614                     
615                     state = operand;
616                 }
617             }
618             catch (final IOException e) {
619                 this.errorHandler.fatalError(new SAXParseException(e.toString(), null, null, this.lineNumber, this.columnNumber, e));
620             }
621             finally {
622                 this.errorHandler = this;
623                 this.documentHandler = this.extDocumentHandler = this;
624                 this.tags.removeAllElements();
625             }
626         }
627         
628         public void parse(final InputSource source) throws SAXException, IOException {
629             if (source.getCharacterStream() != null)
630                 parse(source.getCharacterStream());
631             else if (source.getByteStream() != null)
632                 parse(new InputStreamReader(source.getByteStream()));
633             else
634                 parse(new InputStreamReader(new URL(source.getSystemId()).openStream()));
635         }
636         
637         public void parse(final String systemId) throws SAXException, IOException {
638             parse(new InputSource(systemId));
639         }
640         
641         public void setLocale(final Locale locale) throws SAXException {
642             throw new SAXException("Not supported");
643         }
644         
645         public void setEntityResolver(final EntityResolver resolver) {
646             // not supported
647         }
648         
649         public void setDTDHandler(final DTDHandler handler) {
650             // not supported
651         }
652         
653         public void setDocumentHandler(final DocumentHandler handler) {
654             this.documentHandler = (handler == null) ? this : handler;
655             if (handler != null) handler.setDocumentLocator(this);
656             this.extDocumentHandler = this;
657         }
658
659         public void setErrorHandler(final ErrorHandler handler) {
660             this.errorHandler = (handler == null) ? this : handler;
661         }
662         
663         public void setDocumentLocator(final Locator locator) {
664         }
665         
666         public void startDocument() throws SAXException {
667         }
668         
669         public Writer startDocument(final Writer writer) throws SAXException {
670             this.documentHandler.startDocument();
671             return writer;
672         }
673         
674         public void endDocument() throws SAXException {
675         }
676         
677         public void startElement(final String name, final AttributeList attributes) throws SAXException {
678         }
679         
680         public Writer startElement(final String name, final AttributeList attributes, final Writer writer)
681             throws SAXException
682         {
683             this.documentHandler.startElement(name, attributes);
684             return writer;
685         }
686         
687         public void endElement(final String name) throws SAXException {
688         }
689         
690         public void characters(final char ch[], final int start, final int length) throws SAXException {
691         }
692         
693         public void ignorableWhitespace(final char ch[], final int start, final int length) throws SAXException {
694         }
695         
696         public void processingInstruction(final String target, final String data) throws SAXException {
697         }
698         
699         public void warning(final SAXParseException e) throws SAXException {
700         }
701         
702         public void error(final SAXParseException e) throws SAXException {
703         }
704         
705         public void fatalError(final SAXParseException e) throws SAXException {
706             throw e;
707         }
708         
709         public String getPublicId() {
710             return "";
711         }
712         
713         
714         public String getSystemId() {
715             return "";
716         }
717         
718         public int getLineNumber () {
719             return this.lineNumber;
720         }
721         
722         public int getColumnNumber () {
723             return this.columnNumber;
724         }
725         
726         private void fatalError(final String msg, final int lineNumber, final int columnNumber) throws SAXException {
727             this.errorHandler.fatalError(new SAXParseException(msg, null, null, lineNumber, columnNumber));
728         }
729         
730         private class MinMLBuffer extends Writer {
731             public MinMLBuffer(final Reader in) {
732                 this.in = in;
733             }
734             
735             public void close() throws IOException {
736                 flush();
737             }
738             
739             public void flush() throws IOException {
740                 try {
741                     _flush();
742                     if (writer != this) writer.flush();
743                 }
744                 finally {
745                     flushed = true;
746                 }
747             }
748             
749             public void write(final int c) throws IOException {
750                 written = true;
751                 chars[count++] = (char)c;
752             }
753             
754             public void write(final char[] cbuf, final int off, final int len) throws IOException {
755                 written = true;
756                 System.arraycopy(cbuf, off, chars, count, len);
757                 count += len;
758             }
759             
760             public void saveChar(final char c) {
761                 written = false;
762                 chars[count++] = c;
763             }
764             
765             public void pushWriter(final Writer writer) {
766                 MinML.this.tags.push(this.writer);
767                 
768                 this.writer = (writer == null) ? this : writer;
769                 
770                 flushed = written = false;
771             }
772             
773             public Writer getWriter() {
774                 return writer;
775             }
776             
777             public void popWriter() throws IOException {
778                 try {
779                     if (!flushed && writer != this) writer.flush();
780                 }
781                 finally {
782                     writer = (Writer)MinML.this.tags.pop();
783                     flushed = written = false;
784                 }
785             }
786             
787             public String getString() {
788                 final String result = new String(chars, 0, count);
789                 
790                 count = 0;
791                 return result;
792             }
793             
794             public void reset() {
795                 count = 0;
796             }
797             
798             public int read() throws IOException {
799                 if (nextIn == lastIn) {
800                     if (count != 0) {
801                         if (written) {
802                             _flush();
803                         } else if (count >= (chars.length - MinML.this.bufferIncrement)) {
804                             final char[] newChars = new char[chars.length + MinML.this.bufferIncrement];
805                             
806                             System.arraycopy(chars, 0, newChars, 0, count);
807                             chars = newChars;
808                         }
809                     }
810                     
811                     final int numRead = in.read(chars, count, chars.length - count);
812                     
813                     if (numRead == -1) return -1;
814                     
815                     nextIn = count;
816                     lastIn = count + numRead;
817                 }
818                 
819                 return chars[nextIn++];
820             }
821             
822             private void _flush() throws IOException {
823                 if (count != 0) {
824                     try {
825                         if (writer == this) {
826                             try {
827                                 MinML.this.documentHandler.characters(chars, 0, count);
828                             }
829                             catch (final SAXException e) {
830                                 throw new IOException(e.toString());
831                             }
832                         } else {
833                             writer.write(chars, 0, count);
834                         }
835                     }
836                     finally {
837                         count = 0;
838                     }
839                 }
840             }
841             
842             private int nextIn = 0, lastIn = 0;
843             private char[] chars = new char[MinML.this.initialBufferSize];
844             private final Reader in;
845             private int count = 0;
846             private Writer writer = this;
847             private boolean flushed = false;
848             private boolean written = false;
849         }
850         
851         private DocumentHandler extDocumentHandler = this;
852         private DocumentHandler documentHandler = this;
853         private ErrorHandler errorHandler = this;
854         private final Stack tags = new Stack();
855         private int lineNumber = 1;
856         private int columnNumber = 0;
857         private final int initialBufferSize;
858         private final int bufferIncrement;
859         
860         private static final byte[] charClasses = {
861             //  EOF
862             13,
863             //                                      \t  \n          \r
864             -1, -1, -1, -1, -1, -1, -1, -1, -1, 12, 12, -1, -1, 12, -1, -1,
865             //
866             -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
867             //  SP   !   "   #   $   %   &   '   (   )   *   +   ,   -   .   /
868             12,  8,  7, 14, 14, 14,  3,  6, 14, 14, 14, 14, 14, 11, 14,  2,
869             //   0   1   2   3   4   5   6   7   8   9   :   ;   <   =   >   ?
870             14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,  0,  5,  1,  4,
871             //
872             14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
873             //                                               [   \   ]
874             14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,  9, 14, 10
875         };
876         
877         private static final String[] operands = {
878             "\u0d15\u1611\u1611\u1611\u1611\u1611\u1611\u1611\u1611\u1611\u1611\u1611\u0015\u0010\u1611",
879             "\u1711\u1000\u0b00\u1711\u1711\u1711\u1711\u1711\u1711\u1711\u1711\u0114\u0200\u1811\u0114",
880             "\u1711\u1001\u0b01\u1711\u1711\u1711\u1711\u1711\u1711\u1711\u1711\u1711\u0215\u1811\u0414",
881             "\u1711\u1001\u0b01\u1711\u1911\u1911\u1911\u1911\u1911\u1911\u1911\u1911\u0315\u1811\u0414",
882             "\u1911\u1911\u1911\u1911\u1911\u0606\u1911\u1911\u1911\u1911\u1911\u0414\u0515\u1811\u0414",
883             "\u1911\u1911\u1911\u1911\u1911\u0606\u1911\u1911\u1911\u1911\u1911\u1911\u0515\u1811\u1911",
884             "\u1a11\u1a11\u1a11\u1a11\u1a11\u1a11\u0715\u0815\u1a11\u1a11\u1a11\u1a11\u0615\u1811\u1a11",
885             "\u0714\u0714\u0714\u070e\u0714\u0714\u0307\u0714\u0714\u0714\u0714\u0714\u0714\u1811\u0714",
886             "\u0814\u0814\u0814\u080e\u0814\u0814\u0814\u0307\u0814\u0814\u0814\u0814\u0814\u1811\u0814",
887             "\u1711\u1002\u1711\u1711\u1711\u1711\u1711\u1711\u1711\u1711\u1711\u0914\u0915\u1811\u0914",
888             "\u1b11\u1b11\u0904\u1b11\u1b11\u1b11\u1b11\u1b11\u1215\u1b11\u1b11\u1b11\u1b11\u1811\u0105",
889             "\u1711\u1012\u1711\u1711\u1711\u1711\u1711\u1711\u1711\u1711\u1711\u1711\u1711\u1811\u1711",
890             "\u1711\u1c11\u0912\u1711\u0e12\u1711\u1711\u1711\u1212\u1711\u1711\u1711\u1711\u1811\u0113",
891             "\u1711\u1c11\u0912\u1711\u0e12\u1711\u1711\u1711\u1212\u1711\u1711\u1711\u1711\u1811\u0113",
892             "\u0e15\u0e15\u0e15\u0e15\u0f15\u0e15\u0e15\u0e15\u0e15\u0e15\u0e15\u0e15\u0e15\u1811\u0e15",
893             "\u0e15\u0015\u0e15\u0e15\u0f15\u0e15\u0e15\u0e15\u0e15\u0e15\u0e15\u0e15\u0e15\u1811\u0e15",
894             "\u0c03\u110f\u110f\u110e\u110f\u110f\u110f\u110f\u110f\u110f\u110f\u110f\u1014\u1811\u110f",
895             "\u0a15\u110f\u110f\u110e\u110f\u110f\u110f\u110f\u110f\u110f\u110f\u110f\u110f\u1811\u110f",
896             "\u1d11\u1d11\u1d11\u1d11\u1d11\u1d11\u1d11\u1d11\u1d11\u130c\u1d11\u1408\u1d11\u1811\u1515",
897             "\u130f\u130f\u130f\u130f\u130f\u130f\u130f\u130f\u130f\u130f\u110d\u130f\u130f\u1811\u130f",
898             "\u1416\u1416\u1416\u1416\u1416\u1416\u1416\u1416\u1416\u1416\u1416\u0009\u1416\u1811\u1416",
899 //            "\u1415\u1415\u1415\u1415\u1415\u1415\u1415\u1415\u1415\u1415\u1415\u0009\u1415\u1811\u1415",
900             "\u150a\u000b\u1515\u1515\u1515\u1515\u1515\u1515\u1515\u1515\u1515\u1515\u1515\u1811\u1515",
901             "expected Element",
902             "unexpected character in tag",
903             "unexpected end of file found",
904             "attribute name not followed by '='",
905             "invalid attribute value",
906             "expecting end tag",
907             "empty tag",
908             "unexpected character after <!"
909         };
910     }
911     ///////////////////////////////////////////////////////////////////////////////
912
913     private class XMLHelper implements DocumentHandler {
914         private MinML minml = new MinML();
915         XMLHelper() { }
916         public void parse(Reader r) throws IOException, XML.SAXException {
917             minml.setDocumentHandler(this);
918             minml.parse(new InputSource(r));
919         }
920         
921         public void startDocument() throws SAXException { }
922         public void endDocument() throws SAXException { }
923         public void processingInstruction (String target, String data) throws SAXException { }
924         public Writer startDocument(final Writer writer) throws SAXException { return null; }
925         public Writer startElement(final String name, final AttributeList attributes, final Writer writer) throws SAXException { return null; }
926
927         public void setDocumentLocator (Locator locator) {
928             this.locator = locator;
929         }
930
931         private Locator locator = null;
932
933         public void startElement(String name, AttributeList atts) throws SAXException {
934             String[] keys = new String[atts.getLength()];
935             Object[] vals = new Object[atts.getLength()];
936             for (int i=0; i <atts.getLength(); i++) {
937                 keys[i] = atts.getName(i);
938                 vals[i] = atts.getValue(i).toString();
939             }
940             XML.this.startElement(name, keys, vals,
941                                   locator == null ? 0 : locator.getLineNumber(), locator == null ? 0 : locator.getColumnNumber());
942         }
943
944         public void endElement(String name) throws SAXException {
945             XML.this.endElement(name, locator == null ? 0 : locator.getLineNumber(), locator == null ? 0 : locator.getColumnNumber());
946         }
947
948         public void characters(char ch[], int start, int length) throws SAXException {
949             XML.this.content(ch, start, length, locator == null ? 0 : locator.getLineNumber(), locator == null ? 0 : locator.getColumnNumber());
950         }
951         public void ignorableWhitespace(char ch[], int start, int length) throws SAXException {
952             XML.this.content(ch, start, length, locator == null ? 0 : locator.getLineNumber(), locator == null ? 0 : locator.getColumnNumber());
953         }
954     }
955
956     public XML() { }
957     public void parse(Reader r) throws IOException, XML.SAXException {
958         XMLHelper helper = new XMLHelper();
959         helper.parse(r);
960         helper = null;
961     }
962
963     /** indicates the start of an element with name <tt>name</tt>, and attributes <tt>attributes</tt>, starting on line <tt>line</tt> */
964     public abstract void startElement(String name, String[] keys, Object[] vals, int line, int col) throws SAXException;
965
966     /** indicates the end of an element with name <tt>name</tt>, starting on line <tt>line</tt> */
967     public abstract void endElement(String name, int line, int col) throws SAXException;
968
969     /** indicates a chunk of CDATA content, starting on line <tt>line</tt> */
970     public abstract void content(char[] content, int start, int length, int line, int col) throws SAXException; 
971 }