2002/08/18 05:30:45
[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         
232         public static final int inSkipping = 0;
233         public static final int inSTag = 1;
234         public static final int inPossiblyAttribute = 2;
235         public static final int inNextAttribute = 3;
236         public static final int inAttribute = 4;
237         public static final int inAttribute1 = 5;
238         public static final int inAttributeValue = 6;
239         public static final int inAttributeQuoteValue = 7;
240         public static final int inAttributeQuotesValue = 8;
241         public static final int inETag = 9;
242         public static final int inETag1 = 10;
243         public static final int inMTTag = 11;
244         public static final int inTag = 12;
245         public static final int inTag1 = 13;
246         public static final int inPI = 14;
247         public static final int inPI1 = 15;
248         public static final int inPossiblySkipping = 16;
249         public static final int inCharData = 17;
250         public static final int inCDATA = 18;
251         public static final int inCDATA1 = 19;
252         public static final int inComment =20;
253         public static final int inDTD = 21;
254         
255         public MinML(final int initialBufferSize, final int bufferIncrement) {
256             this.initialBufferSize = initialBufferSize;
257             this.bufferIncrement = bufferIncrement;
258         }
259         
260         public MinML() {
261             this(256, 128);
262         }
263         
264         public void parse(final Reader in) throws SAXException, IOException {
265             final Vector attributeNames = new Vector();
266             final Vector attributeValues = new Vector();
267             
268             final AttributeList attrs = new AttributeList() {
269                     public int getLength() {
270                         return attributeNames.size();
271                     }
272                     
273                     public String getName(final int i) {
274                         return (String)attributeNames.elementAt(i);
275                     }
276                     
277                     public String getType(final int i) {
278                         return "CDATA";
279                     }
280                     
281                     public String getValue(final int i) {
282                         return (String)attributeValues.elementAt(i);
283                     }
284                     
285                     public String getType(final String name) {
286                         return "CDATA";
287                     }
288                     
289                     public String getValue(final String name) {
290                         final int index = attributeNames.indexOf(name);
291                         
292                         return (index == -1) ? null : (String)attributeValues.elementAt(index);
293                     }
294                 };
295             
296             final MinMLBuffer buffer = new MinMLBuffer(in);
297             int currentChar = 0, charCount = 0;
298             int level = 0;
299             int mixedContentLevel = -1;
300             String elementName = null;
301             String state = operands[inSkipping];
302             
303             this.lineNumber = 1;
304             this.columnNumber = 0;
305             
306             try {
307                 while(true) {
308                     charCount++;
309                     
310                     //
311                     // this is to try and make the loop a bit faster
312                     // currentChar = buffer.read(); is simpler but is a bit slower.
313                     //
314                     currentChar = (buffer.nextIn == buffer.lastIn) ? buffer.read() : buffer.chars[buffer.nextIn++];
315                     
316                     final int transition;
317                     
318                     if (currentChar > ']') {
319                         transition = state.charAt(14);
320                     } else {
321                         final int charClass = charClasses[currentChar + 1];
322                         
323                         if (charClass == -1) fatalError("Document contains illegal control character with value " + currentChar, this.lineNumber, this.columnNumber);
324                         
325                         if (charClass == 12) {
326                             if (currentChar == '\r') {
327                                 currentChar = '\n';
328                                 charCount = -1;
329                             }
330                             
331                             if (currentChar == '\n') {
332                                 if (charCount == 0) continue;  // preceeded by '\r' so ignore
333                                 
334                                 if (charCount != -1) charCount = 0;
335                                 
336                                 this.lineNumber++;
337                                 this.columnNumber = 0;
338                             }
339                         }
340                         
341                         transition = state.charAt(charClass);
342                     }
343                     
344                     this.columnNumber++;
345                     
346                     final String operand = operands[transition >>> 8];
347                     
348                     switch (transition & 0XFF) {
349                     case endStartName:
350                         // end of start element name
351                         elementName = buffer.getString();
352                         if (currentChar != '>' && currentChar != '/') break;  // change state to operand
353                         // drop through to emit start element (we have no attributes)
354                         
355                     case emitStartElement:
356                         // emit start element
357                         
358                         final Writer newWriter = this.extDocumentHandler.startElement(elementName, attrs,
359                                                                                       (this.tags.empty()) ?
360                                                                                       this.extDocumentHandler.startDocument(buffer)
361                                                                                       :
362                                                                                       buffer.getWriter());
363                         
364                         buffer.pushWriter(newWriter);
365                         this.tags.push(elementName);
366                         
367                         attributeValues.removeAllElements();
368                         attributeNames.removeAllElements();
369                         
370                         if (mixedContentLevel != -1) mixedContentLevel++;
371                         
372                         if (currentChar != '/') break;  // change state to operand
373                         
374                         // <element/> drop through
375                         
376                     case emitEndElement:
377                         // emit end element
378                         
379                         try {
380                             final String begin = (String)this.tags.pop();
381                             
382                             buffer.popWriter();
383                             elementName = buffer.getString();
384                             
385                             if (currentChar != '/' && !elementName.equals(begin)) {
386                                 fatalError("end tag </" + elementName + "> does not match begin tag <" + begin + ">",
387                                            this.lineNumber, this.columnNumber);
388                             } else {
389                                 this.documentHandler.endElement(begin);
390                                 
391                                 if (this.tags.empty()) {
392                                     this.documentHandler.endDocument();
393                                     return;
394                                 }
395                             }
396                         }
397                         catch (final EmptyStackException e) {
398                             fatalError("end tag at begining of document", this.lineNumber, this.columnNumber);
399                         }
400                         
401                         if (mixedContentLevel != -1) --mixedContentLevel;
402                         
403                         break;  // change state to operand
404                         
405                     case emitCharacters:
406                         // emit characters
407                         
408                         buffer.flush();
409                         break;  // change state to operand
410                         
411                     case emitCharactersSave:
412                         // emit characters and save current character
413                         
414                         if (mixedContentLevel == -1) mixedContentLevel = 0;
415                         
416                         buffer.flush();
417                         
418                         buffer.saveChar((char)currentChar);
419                         
420                         break;  // change state to operand
421                         
422                     case possiblyEmitCharacters:
423                         // write any skipped whitespace if in mixed content
424                         
425                         if (mixedContentLevel != -1) buffer.flush();
426                         break;  // change state to operand
427                         
428                     case saveAttributeName:
429                         // save attribute name
430                         
431                         attributeNames.addElement(buffer.getString());
432                         break;  // change state to operand
433                         
434                     case saveAttributeValue:
435                         // save attribute value
436                         
437                         attributeValues.addElement(buffer.getString());
438                         break;  // change state to operand
439                         
440                     case startComment:
441                         // change state if we have found "<!--"
442                         
443                         if (buffer.read() != '-') continue; // not "<!--"
444
445                         char[] lastthree = new char[3];
446                         int pos = 0;
447                         while(true) {
448                             currentChar = buffer.read();
449                             lastthree[pos] = (char)currentChar; 
450                             if (lastthree[pos] == '>' && lastthree[(pos + 2) % 3] == '-' && lastthree[(pos + 1) % 3] == '-') break;
451                             pos = (pos + 1) % 3;
452                         }
453
454                         state = operands[inCharData];
455                         continue;  // change state to operand
456
457                     case endComment:
458                         // change state if we find "-->"
459                         
460                         if ((currentChar = buffer.read()) == '-') {
461                             // deal with the case where we might have "------->"
462                             while ((currentChar = buffer.read()) == '-');
463                             
464                             if (currentChar == '>') break;  // end of comment, change state to operand
465                         }
466                         
467                         continue;   // not end of comment, don't change state
468                         
469                     case incLevel:
470                         
471                         level++;
472                         
473                         break;
474                         
475                     case decLevel:
476                         
477                         if (level == 0) break; // outer level <> change state
478                         
479                         level--;
480                         
481                         continue; // in nested <>, don't change state
482                         
483                     case startCDATA:
484                         // change state if we have found "<![CDATA["
485                         
486                         if (buffer.read() != 'C') continue;   // don't change state
487                         if (buffer.read() != 'D') continue;   // don't change state
488                         if (buffer.read() != 'A') continue;   // don't change state
489                         if (buffer.read() != 'T') continue;   // don't change state
490                         if (buffer.read() != 'A') continue;   // don't change state
491                         if (buffer.read() != '[') continue;   // don't change state
492                         break;  // change state to operand
493                         
494                     case endCDATA:
495                         // change state if we find "]]>"
496                         
497                         if ((currentChar = buffer.read()) == ']') {
498                             // deal with the case where we might have "]]]]]]]>"
499                             while ((currentChar = buffer.read()) == ']') buffer.write(']');
500                             
501                             if (currentChar == '>') break;  // end of CDATA section, change state to operand
502                             
503                             buffer.write(']');
504                         }
505                         
506                         buffer.write(']');
507                         buffer.write(currentChar);
508                         continue;   // not end of CDATA section, don't change state
509                         
510                     case processCharRef:
511                         // process character entity
512                         
513                         int crefState = 0;
514                         
515                         currentChar = buffer.read();
516                         
517                         while (true) {
518                             if ("#amp;&pos;'quot;\"gt;>lt;<".charAt(crefState) == currentChar) {
519                                 crefState++;
520                                 
521                                 if (currentChar == ';') {
522                                     buffer.write("#amp;&pos;'quot;\"gt;>lt;<".charAt(crefState));
523                                     break;
524                                     
525                                 } else if (currentChar == '#') {
526                                     final int radix;
527                                     
528                                     currentChar = buffer.read();
529                                     
530                                     if (currentChar == 'x') {
531                                         radix = 16;
532                                         currentChar = buffer.read();
533                                     } else {
534                                         radix = 10;
535                                     }
536                                     
537                                     int charRef = Character.digit((char)currentChar, radix);
538                                     
539                                     while (true) {
540                                         currentChar = buffer.read();
541                                         
542                                         final int digit = Character.digit((char)currentChar, radix);
543                                         
544                                         if (digit == -1) break;
545                                         
546                                         charRef = (char)((charRef * radix) + digit);
547                                     }
548                                     
549                                     if (currentChar == ';' && charRef != -1) {
550                                         buffer.write(charRef);
551                                         break;
552                                     }
553                                     
554                                     fatalError("invalid Character Entitiy", this.lineNumber, this.columnNumber);
555                                 } else {
556                                     currentChar = buffer.read();
557                                 }
558                             } else {
559                                 crefState = ("\u0001\u000b\u0006\u00ff\u00ff\u00ff\u00ff\u00ff\u00ff\u00ff\u00ff" +
560                                              //                               #     a     m     p     ;     &     p     o     s     ;     '
561                                              //                               0     1     2     3     4     5     6     7     8     9     a
562                                              "\u0011\u00ff\u00ff\u00ff\u00ff\u00ff\u0015\u00ff\u00ff\u00ff" +
563                                              //                               q     u     o     t     ;     "     g     t     ;     >
564                                              //                               b     b     d     e     f     10    11    12    13    14
565                                              "\u00ff\u00ff\u00ff").charAt(crefState);
566                                 //                               l     t     ;
567                                 //                               15    16    17
568                                 
569                                 if (crefState == 255) fatalError("invalid Character Entitiy", this.lineNumber, this.columnNumber);
570                             }
571                         }
572                         
573                         break;
574                         
575                     case parseError:
576                         // report fatal error
577                         
578                         fatalError(operand, this.lineNumber, this.columnNumber);
579                         // drop through to exit parser
580                         
581                     case exitParser:
582                         // exit parser
583                         
584                         return;
585                         
586                     case writeCdata:
587                         // write character data
588                         // this will also write any skipped whitespace
589                         
590                         buffer.write(currentChar);
591                         break;  // change state to operand
592                         
593                     case discardAndChange:
594                         // throw saved characters away and change state
595                         
596                         buffer.reset();
597                         break;  // change state to operand
598                         
599                     case discardSaveAndChange:
600                         // throw saved characters away, save character and change state
601                         
602                         buffer.reset();
603                         // drop through to save character and change state
604                         
605                     case saveAndChange:
606                         // save character and change state
607                         
608                         buffer.saveChar((char)currentChar);
609                         break;  // change state to operand
610                         
611                     case change:
612                         // change state to operand
613                         
614                         break;  // change state to operand
615                     }
616                     
617                     state = operand;
618                 }
619             }
620             catch (final IOException e) {
621                 this.errorHandler.fatalError(new SAXParseException(e.toString(), null, null, this.lineNumber, this.columnNumber, e));
622             }
623             finally {
624                 this.errorHandler = this;
625                 this.documentHandler = this.extDocumentHandler = this;
626                 this.tags.removeAllElements();
627             }
628         }
629         
630         public void parse(final InputSource source) throws SAXException, IOException {
631             if (source.getCharacterStream() != null)
632                 parse(source.getCharacterStream());
633             else if (source.getByteStream() != null)
634                 parse(new InputStreamReader(source.getByteStream()));
635             else
636                 parse(new InputStreamReader(new URL(source.getSystemId()).openStream()));
637         }
638         
639         public void parse(final String systemId) throws SAXException, IOException {
640             parse(new InputSource(systemId));
641         }
642         
643         public void setLocale(final Locale locale) throws SAXException {
644             throw new SAXException("Not supported");
645         }
646         
647         public void setEntityResolver(final EntityResolver resolver) {
648             // not supported
649         }
650         
651         public void setDTDHandler(final DTDHandler handler) {
652             // not supported
653         }
654         
655         public void setDocumentHandler(final DocumentHandler handler) {
656             this.documentHandler = (handler == null) ? this : handler;
657             if (handler != null) handler.setDocumentLocator(this);
658             this.extDocumentHandler = this;
659         }
660
661         public void setErrorHandler(final ErrorHandler handler) {
662             this.errorHandler = (handler == null) ? this : handler;
663         }
664         
665         public void setDocumentLocator(final Locator locator) {
666         }
667         
668         public void startDocument() throws SAXException {
669         }
670         
671         public Writer startDocument(final Writer writer) throws SAXException {
672             this.documentHandler.startDocument();
673             return writer;
674         }
675         
676         public void endDocument() throws SAXException {
677         }
678         
679         public void startElement(final String name, final AttributeList attributes) throws SAXException {
680         }
681         
682         public Writer startElement(final String name, final AttributeList attributes, final Writer writer)
683             throws SAXException
684         {
685             this.documentHandler.startElement(name, attributes);
686             return writer;
687         }
688         
689         public void endElement(final String name) throws SAXException {
690         }
691         
692         public void characters(final char ch[], final int start, final int length) throws SAXException {
693         }
694         
695         public void ignorableWhitespace(final char ch[], final int start, final int length) throws SAXException {
696         }
697         
698         public void processingInstruction(final String target, final String data) throws SAXException {
699         }
700         
701         public void warning(final SAXParseException e) throws SAXException {
702         }
703         
704         public void error(final SAXParseException e) throws SAXException {
705         }
706         
707         public void fatalError(final SAXParseException e) throws SAXException {
708             throw e;
709         }
710         
711         public String getPublicId() {
712             return "";
713         }
714         
715         
716         public String getSystemId() {
717             return "";
718         }
719         
720         public int getLineNumber () {
721             return this.lineNumber;
722         }
723         
724         public int getColumnNumber () {
725             return this.columnNumber;
726         }
727         
728         private void fatalError(final String msg, final int lineNumber, final int columnNumber) throws SAXException {
729             this.errorHandler.fatalError(new SAXParseException(msg, null, null, lineNumber, columnNumber));
730         }
731         
732         private class MinMLBuffer extends Writer {
733             public MinMLBuffer(final Reader in) {
734                 this.in = in;
735             }
736             
737             public void close() throws IOException {
738                 flush();
739             }
740             
741             public void flush() throws IOException {
742                 try {
743                     _flush();
744                     if (writer != this) writer.flush();
745                 }
746                 finally {
747                     flushed = true;
748                 }
749             }
750             
751             public void write(final int c) throws IOException {
752                 written = true;
753                 chars[count++] = (char)c;
754             }
755             
756             public void write(final char[] cbuf, final int off, final int len) throws IOException {
757                 written = true;
758                 System.arraycopy(cbuf, off, chars, count, len);
759                 count += len;
760             }
761             
762             public void saveChar(final char c) {
763                 written = false;
764                 chars[count++] = c;
765             }
766             
767             public void pushWriter(final Writer writer) {
768                 MinML.this.tags.push(this.writer);
769                 
770                 this.writer = (writer == null) ? this : writer;
771                 
772                 flushed = written = false;
773             }
774             
775             public Writer getWriter() {
776                 return writer;
777             }
778             
779             public void popWriter() throws IOException {
780                 try {
781                     if (!flushed && writer != this) writer.flush();
782                 }
783                 finally {
784                     writer = (Writer)MinML.this.tags.pop();
785                     flushed = written = false;
786                 }
787             }
788             
789             public String getString() {
790                 final String result = new String(chars, 0, count);
791                 
792                 count = 0;
793                 return result;
794             }
795             
796             public void reset() {
797                 count = 0;
798             }
799             
800             public int read() throws IOException {
801                 if (nextIn == lastIn) {
802                     if (count != 0) {
803                         if (written) {
804                             _flush();
805                         } else if (count >= (chars.length - MinML.this.bufferIncrement)) {
806                             final char[] newChars = new char[chars.length + MinML.this.bufferIncrement];
807                             
808                             System.arraycopy(chars, 0, newChars, 0, count);
809                             chars = newChars;
810                         }
811                     }
812                     
813                     final int numRead = in.read(chars, count, chars.length - count);
814                     
815                     if (numRead == -1) return -1;
816                     
817                     nextIn = count;
818                     lastIn = count + numRead;
819                 }
820                 
821                 return chars[nextIn++];
822             }
823             
824             private void _flush() throws IOException {
825                 if (count != 0) {
826                     try {
827                         if (writer == this) {
828                             try {
829                                 MinML.this.documentHandler.characters(chars, 0, count);
830                             }
831                             catch (final SAXException e) {
832                                 throw new IOException(e.toString());
833                             }
834                         } else {
835                             writer.write(chars, 0, count);
836                         }
837                     }
838                     finally {
839                         count = 0;
840                     }
841                 }
842             }
843             
844             private int nextIn = 0, lastIn = 0;
845             private char[] chars = new char[MinML.this.initialBufferSize];
846             private final Reader in;
847             private int count = 0;
848             private Writer writer = this;
849             private boolean flushed = false;
850             private boolean written = false;
851         }
852         
853         private DocumentHandler extDocumentHandler = this;
854         private DocumentHandler documentHandler = this;
855         private ErrorHandler errorHandler = this;
856         private final Stack tags = new Stack();
857         private int lineNumber = 1;
858         private int columnNumber = 0;
859         private final int initialBufferSize;
860         private final int bufferIncrement;
861         
862         private static final byte[] charClasses = {
863             //  EOF
864             13,
865             //                                      \t  \n          \r
866             -1, -1, -1, -1, -1, -1, -1, -1, -1, 12, 12, -1, -1, 12, -1, -1,
867             //
868             -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
869             //  SP   !   "   #   $   %   &   '   (   )   *   +   ,   -   .   /
870             12,  8,  7, 14, 14, 14,  3,  6, 14, 14, 14, 14, 14, 11, 14,  2,
871             //   0   1   2   3   4   5   6   7   8   9   :   ;   <   =   >   ?
872             14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,  0,  5,  1,  4,
873             //
874             14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
875             //                                               [   \   ]
876             14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,  9, 14, 10
877         };
878         
879         private static final String[] operands = {
880             "\u0d15\u1611\u1611\u1611\u1611\u1611\u1611\u1611\u1611\u1611\u1611\u1611\u0015\u0010\u1611",
881             "\u1711\u1000\u0b00\u1711\u1711\u1711\u1711\u1711\u1711\u1711\u1711\u0114\u0200\u1811\u0114",
882             "\u1711\u1001\u0b01\u1711\u1711\u1711\u1711\u1711\u1711\u1711\u1711\u1711\u0215\u1811\u0414",
883             "\u1711\u1001\u0b01\u1711\u1911\u1911\u1911\u1911\u1911\u1911\u1911\u1911\u0315\u1811\u0414",
884             "\u1911\u1911\u1911\u1911\u1911\u0606\u1911\u1911\u1911\u1911\u1911\u0414\u0515\u1811\u0414",
885             "\u1911\u1911\u1911\u1911\u1911\u0606\u1911\u1911\u1911\u1911\u1911\u1911\u0515\u1811\u1911",
886             "\u1a11\u1a11\u1a11\u1a11\u1a11\u1a11\u0715\u0815\u1a11\u1a11\u1a11\u1a11\u0615\u1811\u1a11",
887             "\u0714\u0714\u0714\u070e\u0714\u0714\u0307\u0714\u0714\u0714\u0714\u0714\u0714\u1811\u0714",
888             "\u0814\u0814\u0814\u080e\u0814\u0814\u0814\u0307\u0814\u0814\u0814\u0814\u0814\u1811\u0814",
889             "\u1711\u1002\u1711\u1711\u1711\u1711\u1711\u1711\u1711\u1711\u1711\u0914\u0915\u1811\u0914",
890             "\u1b11\u1b11\u0904\u1b11\u1b11\u1b11\u1b11\u1b11\u1215\u1b11\u1b11\u1b11\u1b11\u1811\u0105",
891             "\u1711\u1012\u1711\u1711\u1711\u1711\u1711\u1711\u1711\u1711\u1711\u1711\u1711\u1811\u1711",
892             "\u1711\u1c11\u0912\u1711\u0e12\u1711\u1711\u1711\u1212\u1711\u1711\u1711\u1711\u1811\u0113",
893             "\u1711\u1c11\u0912\u1711\u0e12\u1711\u1711\u1711\u1212\u1711\u1711\u1711\u1711\u1811\u0113",
894             "\u0e15\u0e15\u0e15\u0e15\u0f15\u0e15\u0e15\u0e15\u0e15\u0e15\u0e15\u0e15\u0e15\u1811\u0e15",
895             "\u0e15\u0015\u0e15\u0e15\u0f15\u0e15\u0e15\u0e15\u0e15\u0e15\u0e15\u0e15\u0e15\u1811\u0e15",
896             "\u0c03\u110f\u110f\u110e\u110f\u110f\u110f\u110f\u110f\u110f\u110f\u110f\u1014\u1811\u110f",
897             "\u0a15\u110f\u110f\u110e\u110f\u110f\u110f\u110f\u110f\u110f\u110f\u110f\u110f\u1811\u110f",
898             "\u1d11\u1d11\u1d11\u1d11\u1d11\u1d11\u1d11\u1d11\u1d11\u130c\u1d11\u1408\u1d11\u1811\u1515",
899             "\u130f\u130f\u130f\u130f\u130f\u130f\u130f\u130f\u130f\u130f\u110d\u130f\u130f\u1811\u130f",
900             "\u1415\u1415\u1415\u1415\u1415\u1415\u1415\u1415\u1415\u1415\u1415\u0009\u1415\u1811\u1415",
901             "\u150a\u000b\u1515\u1515\u1515\u1515\u1515\u1515\u1515\u1515\u1515\u1515\u1515\u1811\u1515",
902             "expected Element",
903             "unexpected character in tag",
904             "unexpected end of file found",
905             "attribute name not followed by '='",
906             "invalid attribute value",
907             "expecting end tag",
908             "empty tag",
909             "unexpected character after <!"
910         };
911     }
912     ///////////////////////////////////////////////////////////////////////////////
913
914     private class XMLHelper implements DocumentHandler {
915         private MinML minml = new MinML();
916         XMLHelper() { }
917         public void parse(Reader r) throws IOException, XML.SAXException {
918             minml.setDocumentHandler(this);
919             minml.parse(new InputSource(r));
920         }
921         
922         public void startDocument() throws SAXException { }
923         public void endDocument() throws SAXException { }
924         public void processingInstruction (String target, String data) throws SAXException { }
925         public Writer startDocument(final Writer writer) throws SAXException { return null; }
926         public Writer startElement(final String name, final AttributeList attributes, final Writer writer) throws SAXException { return null; }
927
928         public void setDocumentLocator (Locator locator) {
929             this.locator = locator;
930         }
931
932         private Locator locator = null;
933
934         public void startElement(String name, AttributeList atts) throws SAXException {
935             String[] keys = new String[atts.getLength()];
936             Object[] vals = new Object[atts.getLength()];
937             for (int i=0; i <atts.getLength(); i++) {
938                 keys[i] = atts.getName(i);
939                 vals[i] = atts.getValue(i).toString();
940             }
941             XML.this.startElement(name, keys, vals,
942                                   locator == null ? 0 : locator.getLineNumber(), locator == null ? 0 : locator.getColumnNumber());
943         }
944
945         public void endElement(String name) throws SAXException {
946             XML.this.endElement(name, locator == null ? 0 : locator.getLineNumber(), locator == null ? 0 : locator.getColumnNumber());
947         }
948
949         public void characters(char ch[], int start, int length) throws SAXException {
950             XML.this.content(ch, start, length, locator == null ? 0 : locator.getLineNumber(), locator == null ? 0 : locator.getColumnNumber());
951         }
952         public void ignorableWhitespace(char ch[], int start, int length) throws SAXException {
953             XML.this.content(ch, start, length, locator == null ? 0 : locator.getLineNumber(), locator == null ? 0 : locator.getColumnNumber());
954         }
955     }
956
957     public XML() { }
958     public void parse(Reader r) throws IOException, XML.SAXException {
959         XMLHelper helper = new XMLHelper();
960         helper.parse(r);
961         helper = null;
962     }
963
964     /** indicates the start of an element with name <tt>name</tt>, and attributes <tt>attributes</tt>, starting on line <tt>line</tt> */
965     public abstract void startElement(String name, String[] keys, Object[] vals, int line, int col) throws SAXException;
966
967     /** indicates the end of an element with name <tt>name</tt>, starting on line <tt>line</tt> */
968     public abstract void endElement(String name, int line, int col) throws SAXException;
969
970     /** indicates a chunk of CDATA content, starting on line <tt>line</tt> */
971     public abstract void content(char[] content, int start, int length, int line, int col) throws SAXException; 
972 }