/*

 ============================================================================
                   The Apache Software License, Version 1.1
 ============================================================================

 Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved.

 Redistribution and use in source and binary forms, with or without modifica-
 tion, are permitted provided that the following conditions are met:

 1. Redistributions of  source code must  retain the above copyright  notice,
    this list of conditions and the following disclaimer.

 2. Redistributions in binary form must reproduce the above copyright notice,
    this list of conditions and the following disclaimer in the documentation
    and/or other materials provided with the distribution.

 3. The end-user documentation included with the redistribution, if any, must
    include  the following  acknowledgment:  "This product includes  software
    developed  by the  Apache Software Foundation  (http://www.apache.org/)."
    Alternately, this  acknowledgment may  appear in the software itself,  if
    and wherever such third-party acknowledgments normally appear.

 4. The names "Batik" and  "Apache Software Foundation" must  not  be
    used to  endorse or promote  products derived from  this software without
    prior written permission. For written permission, please contact
    apache@apache.org.

 5. Products  derived from this software may not  be called "Apache", nor may
    "Apache" appear  in their name,  without prior written permission  of the
    Apache Software Foundation.

 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 FITNESS  FOR A PARTICULAR  PURPOSE ARE  DISCLAIMED.  IN NO  EVENT SHALL  THE
 APACHE SOFTWARE  FOUNDATION  OR ITS CONTRIBUTORS  BE LIABLE FOR  ANY DIRECT,
 INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLU-
 DING, BUT NOT LIMITED TO, PROCUREMENT  OF SUBSTITUTE GOODS OR SERVICES; LOSS
 OF USE, DATA, OR  PROFITS; OR BUSINESS  INTERRUPTION)  HOWEVER CAUSED AND ON
 ANY  THEORY OF LIABILITY,  WHETHER  IN CONTRACT,  STRICT LIABILITY,  OR TORT
 (INCLUDING  NEGLIGENCE OR  OTHERWISE) ARISING IN  ANY WAY OUT OF THE  USE OF
 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

 This software  consists of voluntary contributions made  by many individuals
 on  behalf of the Apache Software  Foundation. For more  information on the
 Apache Software Foundation, please see <http://www.apache.org/>.

*/

package org.apache.batik.xml;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.Locale;
import java.util.MissingResourceException;

import org.apache.batik.i18n.Localizable;
import org.apache.batik.i18n.LocalizableSupport;
import org.apache.batik.util.io.NormalizingReader;
import org.apache.batik.util.io.StreamNormalizingReader;
import org.apache.batik.util.io.StringNormalizingReader;

/**
 * This class represents a scanner for XML documents.
 *
 * @author <a href="mailto:stephane@hillion.org">Stephane Hillion</a>
 * @version $Id: XMLScanner.java,v 1.5 2003/11/20 10:58:51 deweese Exp $
 */
public class XMLScanner implements Localizable {

    /**
     * The document start context.
     */
    public final static int DOCUMENT_START_CONTEXT = 0;

    /**
     * The top level context.
     */
    public final static int TOP_LEVEL_CONTEXT = 1;

    /**
     * The processing instruction context.
     */
    public final static int PI_CONTEXT = 2;

    /**
     * The XML declaration context.
     */
    public final static int XML_DECL_CONTEXT = 3;

    /**
     * The doctype context.
     */
    public final static int DOCTYPE_CONTEXT = 4;

    /**
     * The start tag context.
     */
    public final static int START_TAG_CONTEXT = 5;

    /**
     * The content context.
     */
    public final static int CONTENT_CONTEXT = 6;

    /**
     * The DTD declarations context.
     */
    public final static int DTD_DECLARATIONS_CONTEXT = 7;

    /**
     * The CDATA section context.
     */
    public final static int CDATA_SECTION_CONTEXT = 8;

    /**
     * The end tag context.
     */
    public final static int END_TAG_CONTEXT = 9;

    /**
     * The attribute value context.
     */
    public final static int ATTRIBUTE_VALUE_CONTEXT = 10;

    /**
     * The ATTLIST context.
     */
    public final static int ATTLIST_CONTEXT = 11;

    /**
     * The element declaration context.
     */
    public final static int ELEMENT_DECLARATION_CONTEXT = 12;

    /**
     * The entity context.
     */
    public final static int ENTITY_CONTEXT = 13;

    /**
     * The notation context.
     */
    public final static int NOTATION_CONTEXT = 14;

    /**
     * The notation type context.
     */
    public final static int NOTATION_TYPE_CONTEXT = 15;

    /**
     * The enumeration context.
     */
    public final static int ENUMERATION_CONTEXT = 16;

    /**
     * The entity value context.
     */
    public final static int ENTITY_VALUE_CONTEXT = 17;

    /**
     * The default resource bundle base name.
     */
    protected final static String BUNDLE_CLASSNAME =
	"org.apache.batik.xml.resources.Messages";

    /**
     * The localizable support.
     */
    protected LocalizableSupport localizableSupport =
        new LocalizableSupport(BUNDLE_CLASSNAME, 
                               XMLScanner.class.getClassLoader());

    /**
     * The reader.
     */
    protected NormalizingReader reader;

    /**
     * The current char.
     */
    protected int current;

    /**
     * The type of the current lexical unit.
     */
    protected int type;

    /**
     * The recording buffer.
     */
    protected char[] buffer = new char[1024];

    /**
     * The current position in the buffer.
     */
    protected int position;

    /**
     * The start offset of the last lexical unit.
     */
    protected int start;

    /**
     * The end offset of the last lexical unit.
     */
    protected int end;

    /**
     * The current scanning context.
     */
    protected int context;

    /**
     * The depth in the xml tree.
     */
    protected int depth;

    /**
     * A PI end has been previously read.
     */
    protected boolean piEndRead;

    /**
     * The scanner is in the internal DTD.
     */
    protected boolean inDTD;

    /**
     * The last attribute delimiter encountered.
     */
    protected char attrDelimiter;

    /**
     * A CDATA section end is the next token
     */
    protected boolean cdataEndRead;

    /**
     * Creates a new XML scanner.
     * @param r The reader to scan.
     */
    public XMLScanner(Reader r) throws XMLException {
        context = DOCUMENT_START_CONTEXT;
        try {
            reader = new StreamNormalizingReader(r);
            current = nextChar();
        } catch (IOException e) {
            throw new XMLException(e);
        }
    }

    /**
     * Creates a new XML scanner.
     * @param r The input stream to scan.
     * @param enc The character encoding to use.
     */
    public XMLScanner(InputStream is, String enc) throws XMLException {
        context = DOCUMENT_START_CONTEXT;
        try {
            reader = new StreamNormalizingReader(is, enc);
            current = nextChar();
        } catch (IOException e) {
            throw new XMLException(e);
        }
    }

    /**
     * Creates a new XML scanner.
     * @param s The string to parse.
     */
    public XMLScanner(String s) throws XMLException {
        context = DOCUMENT_START_CONTEXT;
        try {
            reader = new StringNormalizingReader(s);
            current = nextChar();
        } catch (IOException e) {
            throw new XMLException(e);
        }
    }

    /**
     * Implements {@link org.apache.batik.i18n.Localizable#setLocale(Locale)}.
     */
    public  void setLocale(Locale l) {
	localizableSupport.setLocale(l);
    }

    /**
     * Implements {@link org.apache.batik.i18n.Localizable#getLocale()}.
     */
    public Locale getLocale() {
        return localizableSupport.getLocale();
    }

    /**
     * Implements {@link
     * org.apache.batik.i18n.Localizable#formatMessage(String,Object[])}.
     */
    public String formatMessage(String key, Object[] args)
        throws MissingResourceException {
        return localizableSupport.formatMessage(key, args);
    }

    /**
     * Sets the current depth in the XML tree.
     */
    public void setDepth(int i) {
	depth = i;
    }

    /**
     * Returns the current depth in the XML tree.
     */
    public int getDepth() {
        return depth;
    }

    /**
     * Sets the current context.
     */
    public void setContext(int c) {
	context = c;
    }

    /**
     * Returns the current context.
     */
    public int getContext() {
        return context;
    }

    /**
     * The current lexical unit type like defined in LexicalUnits.
     */
    public int getType() {
        return type;
    }

    /**
     * Returns the current line.
     */
    public int getLine() {
        return reader.getLine();
    }

    /**
     * Returns the current column.
     */
    public int getColumn() {
        return reader.getColumn();
    }

    /**
     * Returns the buffer used to store the chars.
     */
    public char[] getBuffer() {
        return buffer;
    }

    /**
     * Returns the start offset of the last lexical unit.
     */
    public int getStart() {
        return start;
    }

    /**
     * Returns the end offset of the last lexical unit.
     */
    public int getEnd() {
        return end;
    }

    /**
     * Returns the last encountered string delimiter.
     */
    public char getStringDelimiter() {
        return attrDelimiter;
    }

    /**
     * Returns the start offset of the current lexical unit.
     */
    public int getStartOffset() {
        switch (type) {
        case LexicalUnits.SECTION_END:
            return -3;

        case LexicalUnits.PI_END:
            return -2;

        case LexicalUnits.STRING:
        case LexicalUnits.ENTITY_REFERENCE:
        case LexicalUnits.CHARACTER_REFERENCE:
        case LexicalUnits.PARAMETER_ENTITY_REFERENCE:
        case LexicalUnits.START_TAG:
        case LexicalUnits.FIRST_ATTRIBUTE_FRAGMENT:
            return 1;

        case LexicalUnits.PI_START:
        case LexicalUnits.END_TAG:
            return 2;

        case LexicalUnits.COMMENT:
            return 4;

        default:
            return 0;
        }
    }

    /**
     * Returns the end offset of the current lexical unit.
     */
    public int getEndOffset() {
        switch (type) {
        case LexicalUnits.STRING:
        case LexicalUnits.ENTITY_REFERENCE:
        case LexicalUnits.CHARACTER_REFERENCE:
        case LexicalUnits.PARAMETER_ENTITY_REFERENCE:
        case LexicalUnits.LAST_ATTRIBUTE_FRAGMENT:
            return -1;

        case LexicalUnits.PI_DATA:
            return -2;

        case LexicalUnits.COMMENT:
            return -3;

        case LexicalUnits.CHARACTER_DATA:
            if (cdataEndRead) {
                return -3;
            }
            return 0;

        default:
            return 0;
        }
    }

    /**
     * Clears the buffer.
     */
    public void clearBuffer() {
        if (position <= 0) {
            position = 0;
        } else {
            buffer[0] = buffer[position - 1];
            position = 1;
        }
    }

    /**
     * Advances to the next lexical unit.
     * @return The type of the lexical unit like defined in LexicalUnits.
     */
    public int next() throws XMLException {
	return next(context);
    }

    /**
     * Advances to the next lexical unit.
     * @param ctx The context to use for scanning.
     * @return The type of the lexical unit like defined in LexicalUnits.
     */
    public int next(int ctx) throws XMLException {
        start = position - 1;
        try {
            switch (ctx) {
            case DOCUMENT_START_CONTEXT:
                type = nextInDocumentStart();
                break;

	    case TOP_LEVEL_CONTEXT:
		type = nextInTopLevel();
                break;

	    case PI_CONTEXT:
		type = nextInPI();
                break;

	    case START_TAG_CONTEXT:
		type = nextInStartTag();
                break;

	    case ATTRIBUTE_VALUE_CONTEXT:
		type = nextInAttributeValue();
                break;

	    case CONTENT_CONTEXT:
		type = nextInContent();
                break;

	    case END_TAG_CONTEXT:
		type = nextInEndTag();
                break;

	    case CDATA_SECTION_CONTEXT:
		type = nextInCDATASection();
                break;

	    case XML_DECL_CONTEXT:
		type = nextInXMLDecl();
                break;

	    case DOCTYPE_CONTEXT:
		type = nextInDoctype();
                break;

	    case DTD_DECLARATIONS_CONTEXT:
		type = nextInDTDDeclarations();
                break;

	    case ELEMENT_DECLARATION_CONTEXT:
		type = nextInElementDeclaration();
                break;

	    case ATTLIST_CONTEXT:
		type = nextInAttList();
                break;

	    case NOTATION_CONTEXT:
		type = nextInNotation();
                break;

	    case ENTITY_CONTEXT:
		type = nextInEntity();
                break;

	    case ENTITY_VALUE_CONTEXT:
		return nextInEntityValue();

            case NOTATION_TYPE_CONTEXT:
                return nextInNotationType();

            case ENUMERATION_CONTEXT:
                return nextInEnumeration();

            default:
                throw new InternalError();
            }
        } catch (IOException e) {
            throw new XMLException(e);
        }
        end = position - ((current == -1) ? 0 : 1);
        return type;
    }

    /**
     * Reads the first token in the stream.
     */
    protected int nextInDocumentStart() throws IOException, XMLException {
        switch (current) {
	case 0x9:
	case 0xA:
	case 0xD:
	case 0x20:
	    do {
		nextChar();
	    } while (current != -1 && XMLUtilities.isXMLSpace((char)current));
	    context = (depth == 0) ? TOP_LEVEL_CONTEXT : CONTENT_CONTEXT;
	    return LexicalUnits.S;

        case '<':
            switch (nextChar()) {
            case '?':
                int c1 = nextChar();
		if (c1 == -1 ||
                    !XMLUtilities.isXMLNameFirstCharacter((char)c1)) {
		    throw createXMLException("invalid.pi.target");
		}
                context = PI_CONTEXT;
                int c2 = nextChar();
		if (c2 == -1 || !XMLUtilities.isXMLNameCharacter((char)c2)) {
		    return LexicalUnits.PI_START;
		}
                int c3 = nextChar();
		if (c3 == -1 || !XMLUtilities.isXMLNameCharacter((char)c3)) {
		    return LexicalUnits.PI_START;
		}
                int c4 = nextChar();
		if (c4 != -1 && XMLUtilities.isXMLNameCharacter((char)c4)) {
		    do {
			nextChar();
		    } while (current != -1 &&
                             XMLUtilities.isXMLNameCharacter((char)current));
		    return LexicalUnits.PI_START;
		}
		if (c1 == 'x' && c2 == 'm' && c3 == 'l') {
		    context = XML_DECL_CONTEXT;
		    return LexicalUnits.XML_DECL_START;
		}
		if ((c1 == 'x' || c1 == 'X') &&
		    (c2 == 'm' || c2 == 'M') &&
		    (c3 == 'l' || c3 == 'L')) {
		    throw createXMLException("xml.reserved");
		}
		return LexicalUnits.PI_START;
                
            case '!':
                switch (nextChar()) {
                case '-':
                    return readComment();

                case 'D':
                    context = DOCTYPE_CONTEXT;
                    return readIdentifier("OCTYPE",
                                          LexicalUnits.DOCTYPE_START,
                                          -1);

                default:
                    throw createXMLException("invalid.doctype");
                }

            default:
                context = START_TAG_CONTEXT;
                depth++;
                return readName(LexicalUnits.START_TAG);
            }

        case -1:
            return LexicalUnits.EOF;
            
        default:
            if (depth == 0) {
                throw createXMLException("invalid.character");
            } else {
                return nextInContent();
            }
        }
    }

    /**
     * Advances to the next lexical unit in the top level context.
     * @return The type of the lexical unit like defined in LexicalUnits.
     */
    protected int nextInTopLevel() throws IOException, XMLException {
	switch (current) {
	case 0x9:
	case 0xA:
	case 0xD:
	case 0x20:
	    do {
		nextChar();
	    } while (current != -1 &&
                     XMLUtilities.isXMLSpace((char)current));
	    return LexicalUnits.S;

	case '<':
	    switch (nextChar()) {
	    case '?':
		context = PI_CONTEXT;
		return readPIStart();

	    case '!':
		switch (nextChar()) {
		case '-':
		    return readComment();

		case 'D':
		    context = DOCTYPE_CONTEXT;
		    return readIdentifier("OCTYPE",
                                          LexicalUnits.DOCTYPE_START,
                                          -1);

		default:
		    throw createXMLException("invalid.character");
		}
	    default:
		context = START_TAG_CONTEXT;
		depth++;
		return readName(LexicalUnits.START_TAG);
	    }

	case -1:
	    return LexicalUnits.EOF;

	default:
	    throw createXMLException("invalid.character");
	}
    }

    /**
     * Returns the next lexical unit in the context of a processing
     * instruction.
     */
    protected int nextInPI() throws IOException, XMLException {
	if (piEndRead) {
	    piEndRead = false;
	    context = (depth == 0) ? TOP_LEVEL_CONTEXT : CONTENT_CONTEXT;
	    return LexicalUnits.PI_END;
	}

	switch (current) {
	case 0x9:
	case 0xA:
	case 0xD:
	case 0x20:
	    do {
		nextChar();
	    } while (current != -1 &&
                     XMLUtilities.isXMLSpace((char)current));
	    return LexicalUnits.S;
	case '?':
	    if (nextChar() != '>') {
		throw createXMLException("pi.end.expected");
	    }
	    nextChar();
	    if (inDTD) {
		context = DTD_DECLARATIONS_CONTEXT;
	    } else if (depth == 0) {
		context = TOP_LEVEL_CONTEXT;
	    } else {
		context = CONTENT_CONTEXT;
	    }
	    return LexicalUnits.PI_END;

	default:
	    do {
		do {
		    nextChar();
		} while (current != -1 && current != '?');
		nextChar();
	    } while (current != -1 && current != '>');
	    nextChar();
	    piEndRead = true;
	    return LexicalUnits.PI_DATA;
	}
    }

    /**
     * Returns the next lexical unit in the context of a start tag.
     */
    protected int nextInStartTag() throws IOException, XMLException {
	switch (current) {
	case 0x9:
	case 0xA:
	case 0xD:
	case 0x20:
	    do {
		nextChar();
	    } while (current != -1 && XMLUtilities.isXMLSpace((char)current));
	    return LexicalUnits.S;

	case '/':
	    if (nextChar() != '>') {
		throw createXMLException("malformed.tag.end");
	    }
	    nextChar();
	    context = (--depth == 0) ? TOP_LEVEL_CONTEXT : CONTENT_CONTEXT;
	    return LexicalUnits.EMPTY_ELEMENT_END;

	case '>':
	    nextChar();
	    context = CONTENT_CONTEXT;
	    return LexicalUnits.END_CHAR;

	case '=':
	    nextChar();
	    return LexicalUnits.EQ;

	case '"':
	    attrDelimiter = '"';
	    nextChar();

	    for (;;) {
		switch (current) {
		case '"':
                    nextChar();
                    return LexicalUnits.STRING;

		case '&':
                    context = ATTRIBUTE_VALUE_CONTEXT;
                    return LexicalUnits.FIRST_ATTRIBUTE_FRAGMENT;
                    
		case '<':
                    throw createXMLException("invalid.character");

		case -1:
                    throw createXMLException("unexpected.eof");
		}
		nextChar();
	    }

	case '\'':
	    attrDelimiter = '\'';
            nextChar();

	    for (;;) {
		switch (current) {
		case '\'':
                    nextChar();
                    return LexicalUnits.STRING;

		case '&':
                    context = ATTRIBUTE_VALUE_CONTEXT;
                    return LexicalUnits.FIRST_ATTRIBUTE_FRAGMENT;
                    
		case '<':
                    throw createXMLException("invalid.character");

		case -1:
                    throw createXMLException("unexpected.eof");
		}
		nextChar();
	    }

	default:
	    return readName(LexicalUnits.NAME);
	}
    }

    /**
     * Returns the next lexical unit in the context of an attribute value.
     */
    protected int nextInAttributeValue()
        throws IOException, XMLException {
	if (current == -1) {
	    return LexicalUnits.EOF;
	}

	if (current == '&') {
	    return readReference();

	} else {
	    loop: for (;;) {
		switch (current) {
		case '&':
		case '<':
		case -1:
		    break loop;
		case '"':
		case '\'':
		    if (current == attrDelimiter) {
			break loop;
		    }
		}
		nextChar();
	    }

	    switch (current) {
	    case -1:
		break;

	    case '<':
		throw createXMLException("invalid.character");

            case '&':
                return LexicalUnits.ATTRIBUTE_FRAGMENT;

	    case '\'':
	    case '"':
		nextChar();
		if (inDTD) {
		    context = ATTLIST_CONTEXT;
		} else {
		    context = START_TAG_CONTEXT;
		}
	    }
	    return LexicalUnits.LAST_ATTRIBUTE_FRAGMENT;
	}
    }

    /**
     * Returns the next lexical unit in the context of an element content.
     */
    protected int nextInContent() throws IOException, XMLException {
	switch (current) {
	case -1:
	    return LexicalUnits.EOF;

	case '&':
	    return readReference();

	case '<':
	    switch (nextChar()) {
	    case '?':
		context = PI_CONTEXT;
		return readPIStart();

	    case '!':
		switch (nextChar()) {
		case '-':
		    return readComment();
		case '[':
		    context = CDATA_SECTION_CONTEXT;
		    return readIdentifier("CDATA[",
                                          LexicalUnits.CDATA_START,
                                          -1);
		default:
		    throw createXMLException("invalid.character");
		}

	    case '/':
		nextChar();
		context = END_TAG_CONTEXT;
		return readName(LexicalUnits.END_TAG);

	    default:
		depth++;
		context = START_TAG_CONTEXT;
		return readName(LexicalUnits.START_TAG);
	    }

	default:
            loop: for (;;) {
                switch (current) {
                default:
                    nextChar();
                    break;

                case -1:
                case '&':
                case '<':
                    break loop;
                }
	    }
	    return LexicalUnits.CHARACTER_DATA;
	}
    }

    /**
     * Returns the next lexical unit in the context of a end tag.
     */
    protected int nextInEndTag() throws IOException, XMLException {
	switch (current) {
	case 0x9:
	case 0xA:
	case 0xD:
	case 0x20:
	    do {
                nextChar();
	    } while (current != -1 &&
                     XMLUtilities.isXMLSpace((char)current));
	    return LexicalUnits.S;

	case '>':
	    if (--depth < 0) {
		throw createXMLException("unexpected.end.tag");
	    } else if (depth == 0) {
		context = TOP_LEVEL_CONTEXT;
	    } else {
		context = CONTENT_CONTEXT;
	    }
	    nextChar();
	    return LexicalUnits.END_CHAR;

	default:
	    throw createXMLException("invalid.character");
	}
    }

    /**
     * Returns the next lexical unit in the context of a CDATA section.
     */
    protected int nextInCDATASection() throws IOException, XMLException {
	if (cdataEndRead) {
	    cdataEndRead = false;
	    context = CONTENT_CONTEXT;
	    return LexicalUnits.SECTION_END;
	}
	
	while (current != -1) {
	    while (current != ']' && current != -1) {
		nextChar();
	    }
	    if (current != -1) {
		nextChar();
		if (current == ']') {
		    nextChar();
		    if (current == '>') {
			break;
		    }
		}
	    }
	}
	if (current == -1) {
	    throw createXMLException("unexpected.eof");
	}
	nextChar();
	cdataEndRead = true;
	return LexicalUnits.CHARACTER_DATA;
    }

    /**
     * Returns the next lexical unit in the context of an XML declaration.
     */
    protected int nextInXMLDecl() throws IOException, XMLException {
	switch (current) {
	case 0x9:
	case 0xA:
	case 0xD:
	case 0x20:
	    do {
		nextChar();
	    } while (current != -1 &&
                     XMLUtilities.isXMLSpace((char)current));
	    return LexicalUnits.S;
	case 'v':
	    return readIdentifier("ersion",
                                  LexicalUnits.VERSION_IDENTIFIER,
                                  -1);
	case 'e':
	    return readIdentifier("ncoding",
                                  LexicalUnits.ENCODING_IDENTIFIER,
                                  -1);
	case 's':
	    return readIdentifier("tandalone",
                                  LexicalUnits.STANDALONE_IDENTIFIER,
                                  -1);
	case '=':
	    nextChar();
	    return LexicalUnits.EQ;

	case '?':
	    nextChar();
	    if (current != '>') {
		throw createXMLException("pi.end.expected");
	    }
	    nextChar();
	    context = TOP_LEVEL_CONTEXT;
	    return LexicalUnits.PI_END;

	case '"':
            attrDelimiter = '"';
	    return readString();

	case '\'':
            attrDelimiter = '\'';
	    return readString();

	default:
	    throw createXMLException("invalid.character");
	}
    }

    /**
     * Returns the next lexical unit in the context of a doctype.
     */
    protected int nextInDoctype() throws IOException, XMLException {
	switch (current) {
	case 0x9:
	case 0xA:
	case 0xD:
	case 0x20:
	    do {
		nextChar();
	    } while (current != -1 &&
                     XMLUtilities.isXMLSpace((char)current));
	    return LexicalUnits.S;

	case '>':
	    nextChar();
	    context = TOP_LEVEL_CONTEXT;
	    return LexicalUnits.END_CHAR;

	case 'S':
	    return readIdentifier("YSTEM",
				  LexicalUnits.SYSTEM_IDENTIFIER,
				  LexicalUnits.NAME);

	case 'P':
	    return readIdentifier("UBLIC",
				  LexicalUnits.PUBLIC_IDENTIFIER,
				  LexicalUnits.NAME);

	case '"':
            attrDelimiter = '"';
	    return readString();

	case '\'':
            attrDelimiter = '\'';
	    return readString();

	case '[':
	    nextChar();
	    context = DTD_DECLARATIONS_CONTEXT;
	    inDTD = true;
	    return LexicalUnits.LSQUARE_BRACKET;

	default:
	    return readName(LexicalUnits.NAME);
	}
    }

    /**
     * Returns the next lexical unit in the context dtd declarations.
     */
    protected int nextInDTDDeclarations() throws IOException, XMLException {
	switch (current) {
	case 0x9:
	case 0xA:
	case 0xD:
	case 0x20:
	    do {
		nextChar();
	    } while (current != -1 &&
                     XMLUtilities.isXMLSpace((char)current));
	    return LexicalUnits.S;

	case ']':
	    nextChar();
	    context = DOCTYPE_CONTEXT;
	    inDTD = false;
	    return LexicalUnits.RSQUARE_BRACKET;

	case '%':
	    return readPEReference();

	case '<':
	    switch (nextChar()) {
	    case '?':
		context = PI_CONTEXT;
		return readPIStart();

	    case '!':
		switch (nextChar()) {
		case '-':
		    return readComment();

		case 'E':
		    switch (nextChar()) {
		    case 'L':
			context = ELEMENT_DECLARATION_CONTEXT;
			return readIdentifier
                            ("EMENT",
                             LexicalUnits.ELEMENT_DECLARATION_START,
                             -1);
		    case 'N':
			context = ENTITY_CONTEXT;
			return readIdentifier("TITY",
					      LexicalUnits.ENTITY_START,
					      -1);
		    default:
			throw createXMLException("invalid.character");
		    }

		case 'A':
		    context = ATTLIST_CONTEXT;
		    return readIdentifier("TTLIST",
					  LexicalUnits.ATTLIST_START,
					  -1);
		case 'N':
		    context = NOTATION_CONTEXT;
		    return readIdentifier("OTATION",
					  LexicalUnits.NOTATION_START,
					  -1);
		default:
		    throw createXMLException("invalid.character");
		}
	    default:
		throw createXMLException("invalid.character");
	    }
	default:
	    throw createXMLException("invalid.character");
	}
    }

    /**
     * Reads a simple string, like the ones used for version, encoding,
     * public/system identifiers...
     * The current character must be the string delimiter.
     * @return type.
     */
    protected int readString() throws IOException, XMLException {
        do {
	    nextChar();
        } while (current != -1 && current != attrDelimiter);
	if (current == -1) {
	    throw createXMLException("unexpected.eof");
	}
	nextChar();
	return LexicalUnits.STRING;
    }

    /**
     * Reads a comment. '&lt;!-' must have been read.
     */
    protected int readComment() throws IOException, XMLException {
	if (nextChar() != '-') {
	    throw createXMLException("malformed.comment");
	}
	int c = nextChar();
	while (c != -1) {
	    while (c != -1 && c != '-') {
		c = nextChar();
	    }
	    c = nextChar();
	    if (c == '-') {
		break;
	    }
	}
	if (c == -1) {
	    throw createXMLException("unexpected.eof");
	}
	c = nextChar();
	if (c != '>') {
	    throw createXMLException("malformed.comment");
	}
	nextChar();
	return LexicalUnits.COMMENT;
    }

    /**
     * Reads the given identifier.
     * @param s The portion of the identifier to read.
     * @param type The lexical unit type of the identifier.
     * @param ntype The lexical unit type to set if the identifier do not
     * match or -1 if an error must be signaled.
     */
    protected int readIdentifier(String s, int type, int ntype)
	throws IOException, XMLException {
	int len = s.length();
	for (int i = 0; i < len; i++) {
	    nextChar();
	    if (current != s.charAt(i)) {
		if (ntype == -1) {
		    throw createXMLException("invalid.character");
		} else {
		    while (current != -1 &&
                           XMLUtilities.isXMLNameCharacter((char)current)) {
			nextChar();
		    }
		    return ntype;
		}
	    }
	}
	nextChar();
	return type;
    }

    /**
     * Reads a name. The current character must be the first character.
     * @param type The lexical unit type to set.
     * @return type.
     */
    protected int readName(int type) throws IOException, XMLException {
	if (current == -1) {
	    throw createXMLException("unexpected.eof");
	}
	if (!XMLUtilities.isXMLNameFirstCharacter((char)current)) {
	    throw createXMLException("invalid.name");
	}
	do {
	    nextChar();
	} while (current != -1 &&
                 XMLUtilities.isXMLNameCharacter((char)current));
	return type;
    }


    /**
     * Reads a processing instruction start.
     * @return type.
     */
    protected int readPIStart() throws IOException, XMLException {
	int c1 = nextChar();
	if (c1 == -1) {
	    throw createXMLException("unexpected.eof");
	}
	if (!XMLUtilities.isXMLNameFirstCharacter((char)current)) {
	    throw createXMLException("malformed.pi.target");
	}
	int c2 = nextChar();
	if (c2 == -1 || !XMLUtilities.isXMLNameCharacter((char)c2)) {
	    return LexicalUnits.PI_START;
	}
	int c3 = nextChar();
	if (c3 == -1 || !XMLUtilities.isXMLNameCharacter((char)c3)) {
	    return LexicalUnits.PI_START;
	}
	int c4 = nextChar();
	if (c4 != -1 && XMLUtilities.isXMLNameCharacter((char)c4)) {
	    do {
		nextChar();
	    } while (current != -1 &&
                     XMLUtilities.isXMLNameCharacter((char)current));
	    return LexicalUnits.PI_START;
	}
	if ((c1 == 'x' || c1 == 'X') &&
	    (c2 == 'm' || c2 == 'M') &&
	    (c3 == 'l' || c3 == 'L')) {
	    throw createXMLException("xml.reserved");
	}
	return LexicalUnits.PI_START;
    }

    /**
     * Returns the next lexical unit in the context of a element declaration.
     */
    protected int nextInElementDeclaration() throws IOException, XMLException {
	switch (current) {
	case 0x9:
	case 0xA:
	case 0xD:
	case 0x20:
	    do {
		nextChar();
	    } while (current != -1 && XMLUtilities.isXMLSpace((char)current));
	    return LexicalUnits.S;

	case '>':
	    nextChar();
	    context = DTD_DECLARATIONS_CONTEXT;
	    return LexicalUnits.END_CHAR;

	case '%':
            nextChar();
	    int t = readName(LexicalUnits.PARAMETER_ENTITY_REFERENCE);
	    if (current != ';') {
		throw createXMLException("malformed.parameter.entity");
	    }
	    nextChar();
	    return t;

	case 'E':
	    return readIdentifier("MPTY",
				  LexicalUnits.EMPTY_IDENTIFIER,
				  LexicalUnits.NAME);

	case 'A':
	    return readIdentifier("NY",
				  LexicalUnits.ANY_IDENTIFIER,
				  LexicalUnits.NAME);

	case '?':
	    nextChar();
	    return LexicalUnits.QUESTION;

	case '+':
	    nextChar();
	    return LexicalUnits.PLUS;

	case '*':
	    nextChar();
	    return LexicalUnits.STAR;

	case '(':
	    nextChar();
	    return LexicalUnits.LEFT_BRACE;

	case ')':
	    nextChar();
	    return LexicalUnits.RIGHT_BRACE;

	case '|':
	    nextChar();
	    return LexicalUnits.PIPE;

	case ',':
	    nextChar();
	    return LexicalUnits.COMMA;

	case '#':
	    return readIdentifier("PCDATA",
                                  LexicalUnits.PCDATA_IDENTIFIER,
                                  -1);

	default:
	    return readName(LexicalUnits.NAME);
	}
    }

    /**
     * Returns the next lexical unit in the context of an attribute list.
     */
    protected int nextInAttList() throws IOException, XMLException {
	switch (current) {
	case 0x9:
	case 0xA:
	case 0xD:
	case 0x20:
	    do {
		nextChar();
	    } while (current != -1 && XMLUtilities.isXMLSpace((char)current));
	    return LexicalUnits.S;

	case '>':
	    nextChar();
	    context = DTD_DECLARATIONS_CONTEXT;
	    return type = LexicalUnits.END_CHAR;
	    
	case '%':
	    int t = readName(LexicalUnits.PARAMETER_ENTITY_REFERENCE);
	    if (current != ';') {
		throw createXMLException("malformed.parameter.entity");
	    }
	    nextChar();
	    return t;

	case 'C':
	    return readIdentifier("DATA",
				  LexicalUnits.CDATA_IDENTIFIER,
				  LexicalUnits.NAME);

	case 'I':
	    nextChar();
	    if (current != 'D') {
		do {
		    nextChar();
		} while (current != -1 &&
                         XMLUtilities.isXMLNameCharacter((char)current));
		return LexicalUnits.NAME;
	    }
	    nextChar();
	    if (current == -1 ||
                !XMLUtilities.isXMLNameCharacter((char)current)) {
		return LexicalUnits.ID_IDENTIFIER;
	    }
	    if (current != 'R') {
		do {
		    nextChar();
		} while (current != -1 &&
                         XMLUtilities.isXMLNameCharacter((char)current));
		return LexicalUnits.NAME; 
	    }
	    nextChar();
	    if (current == -1 ||
                !XMLUtilities.isXMLNameCharacter((char)current)) {
		return LexicalUnits.NAME;
	    }
	    if (current != 'E') {
		do {
		    nextChar();
		} while (current != -1 &&
                         XMLUtilities.isXMLNameCharacter((char)current));
		return LexicalUnits.NAME;
	    }
	    nextChar();
	    if (current == -1 ||
                !XMLUtilities.isXMLNameCharacter((char)current)) {
		return LexicalUnits.NAME;
	    }
	    if (current != 'F') {
		do {
		    nextChar();
		} while (current != -1 &&
                         XMLUtilities.isXMLNameCharacter((char)current));
		return LexicalUnits.NAME;
	    }
	    nextChar();
	    if (current == -1 ||
                !XMLUtilities.isXMLNameCharacter((char)current)) {
		return LexicalUnits.IDREF_IDENTIFIER;
	    }
	    if (current != 'S') {
		do {
		    nextChar();
		} while (current != -1 &&
                         XMLUtilities.isXMLNameCharacter((char)current));
		return LexicalUnits.NAME;
	    }
	    nextChar();
	    if (current == -1 ||
                !XMLUtilities.isXMLNameCharacter((char)current)) {
		return LexicalUnits.IDREFS_IDENTIFIER;
	    }
	    do {
		nextChar();
	    } while (current != -1 &&
                     XMLUtilities.isXMLNameCharacter((char)current));
	    return type = LexicalUnits.NAME;

	case 'N':
            switch (nextChar()) {
            default:
		do {
		    nextChar();
		} while (current != -1 &&
                         XMLUtilities.isXMLNameCharacter((char)current));
		return LexicalUnits.NAME;

            case 'O':
                context = NOTATION_TYPE_CONTEXT;
                return readIdentifier("TATION",
                                      LexicalUnits.NOTATION_IDENTIFIER,
                                      LexicalUnits.NAME);

            case 'M':
                nextChar();
                if (current == -1 ||
                    !XMLUtilities.isXMLNameCharacter((char)current)) {
                    return LexicalUnits.NAME;
                }
                if (current != 'T') {
                    do {
                        nextChar();
                    } while (current != -1 &&
                             XMLUtilities.isXMLNameCharacter((char)current));
                    return LexicalUnits.NAME;
                }
                nextChar();
                if (current == -1 ||
                    !XMLUtilities.isXMLNameCharacter((char)current)) {
                    return LexicalUnits.NAME;
                }
                if (current != 'O') {
                    do {
                        nextChar();
                    } while (current != -1 &&
                             XMLUtilities.isXMLNameCharacter((char)current));
                    return LexicalUnits.NAME;
                }
                nextChar();
                if (current == -1 ||
                    !XMLUtilities.isXMLNameCharacter((char)current)) {
                    return LexicalUnits.NAME;
                }
                if (current != 'K') {
                    do {
                        nextChar();
                    } while (current != -1 &&
                             XMLUtilities.isXMLNameCharacter((char)current));
                    return LexicalUnits.NAME;
                }
                nextChar();
                if (current == -1 ||
                    !XMLUtilities.isXMLNameCharacter((char)current)) {
                    return LexicalUnits.NAME;
                }
                if (current != 'E') {
                    do {
                        nextChar();
                    } while (current != -1 &&
                             XMLUtilities.isXMLNameCharacter((char)current));
                    return LexicalUnits.NAME;
                }
                nextChar();
                if (current == -1 ||
                    !XMLUtilities.isXMLNameCharacter((char)current)) {
                    return LexicalUnits.NAME;
                }
                if (current != 'N') {
                    do {
                        nextChar();
                    } while (current != -1 &&
                             XMLUtilities.isXMLNameCharacter((char)current));
                    return LexicalUnits.NAME;
                }
                nextChar();
                if (current == -1 ||
                    !XMLUtilities.isXMLNameCharacter((char)current)) {
                    return LexicalUnits.NMTOKEN_IDENTIFIER;
                }
                if (current != 'S') {
                    do {
                        nextChar();
                    } while (current != -1 &&
                             XMLUtilities.isXMLNameCharacter((char)current));
                    return LexicalUnits.NAME; 
                }
                nextChar();
                if (current == -1 ||
                    !XMLUtilities.isXMLNameCharacter((char)current)) {
                    return LexicalUnits.NMTOKENS_IDENTIFIER;
                }
                do {
                    nextChar();
                } while (current != -1 &&
                         XMLUtilities.isXMLNameCharacter((char)current));
                return LexicalUnits.NAME;
            }

	case 'E':
	    nextChar();
	    if (current != 'N') {
		do {
		    nextChar();
		} while (current != -1 &&
                         XMLUtilities.isXMLNameCharacter((char)current));
		return LexicalUnits.NAME;
	    }
	    nextChar();
	    if (current == -1 ||
                !XMLUtilities.isXMLNameCharacter((char)current)) {
		return LexicalUnits.NAME;
	    }
	    if (current != 'T') {
		do {
		    nextChar();
		} while (current != -1 &&
                         XMLUtilities.isXMLNameCharacter((char)current));
		return LexicalUnits.NAME; 
	    }
	    nextChar();
	    if (current == -1 ||
                !XMLUtilities.isXMLNameCharacter((char)current)) {
		return LexicalUnits.NAME;
	    }
	    if (current != 'I') {
		do {
		    nextChar();
		} while (current != -1 &&
                         XMLUtilities.isXMLNameCharacter((char)current));
		return LexicalUnits.NAME;
	    }
	    nextChar();
	    if (current == -1 ||
                !XMLUtilities.isXMLNameCharacter((char)current)) {
		return LexicalUnits.NAME;
	    }
	    if (current != 'T') {
		do {
		    nextChar();
		} while (current != -1 &&
                         XMLUtilities.isXMLNameCharacter((char)current));
		return type = LexicalUnits.NAME; 
	    }
	    nextChar();
	    if (current == -1 ||
                !XMLUtilities.isXMLNameCharacter((char)current)) {
		return LexicalUnits.NAME;
	    }
	    switch (current) {
	    case 'Y':
		nextChar();
		if (current == -1 ||
                    !XMLUtilities.isXMLNameCharacter((char)current)) {
		    return LexicalUnits.ENTITY_IDENTIFIER;
		}
		do {
		    nextChar();
		} while (current != -1 &&
                         XMLUtilities.isXMLNameCharacter((char)current));
		return LexicalUnits.NAME;
	    case 'I':
		nextChar();
		if (current == -1 ||
                    !XMLUtilities.isXMLNameCharacter((char)current)) {
		    return LexicalUnits.NAME;
		}
		if (current != 'E') {
		    do {
			nextChar();
		    } while (current != -1 &&
                             XMLUtilities.isXMLNameCharacter((char)current));
		    return LexicalUnits.NAME; 
		}
		nextChar();
		if (current == -1 ||
                    !XMLUtilities.isXMLNameCharacter((char)current)) {
		    return LexicalUnits.NAME;
		}
		if (current != 'S') {
		    do {
			nextChar();
		    } while (current != -1 &&
                             XMLUtilities.isXMLNameCharacter((char)current));
		    return LexicalUnits.NAME;
		}
		return LexicalUnits.ENTITIES_IDENTIFIER;

	    default:
		if (current == -1 ||
                    !XMLUtilities.isXMLNameCharacter((char)current)) {
		    return LexicalUnits.NAME;
		}
		do {
		    nextChar();
		} while (current != -1 &&
                         XMLUtilities.isXMLNameCharacter((char)current));
		return LexicalUnits.NAME; 
	    }

	case '"':
            attrDelimiter = '"';
	    nextChar();
	    if (current == -1) {
		throw createXMLException("unexpected.eof");
	    }
	    if (current != '"' && current != '&') {
		do {
		    nextChar();
		} while (current != -1 && current != '"' && current != '&');
	    }
	    switch (current) {
	    case '&':
		context = ATTRIBUTE_VALUE_CONTEXT;
                return LexicalUnits.FIRST_ATTRIBUTE_FRAGMENT;

	    case '"':
		nextChar();
                return LexicalUnits.STRING;

	    default:
		throw createXMLException("invalid.character");
	    }

	case '\'':
            attrDelimiter = '\'';
	    nextChar();
	    if (current == -1) {
		throw createXMLException("unexpected.eof");
	    }
	    if (current != '\'' && current != '&') {
		do {
		    nextChar();
		} while (current != -1 && current != '\'' && current != '&');
	    }
	    switch (current) {
	    case '&':
		context = ATTRIBUTE_VALUE_CONTEXT;
                return LexicalUnits.FIRST_ATTRIBUTE_FRAGMENT;

	    case '\'':
		nextChar();
                return LexicalUnits.STRING;

	    default:
		throw createXMLException("invalid.character");
	    }

	case '#':
	    switch (nextChar()) {
	    case 'R':
		return readIdentifier("EQUIRED",
                                      LexicalUnits.REQUIRED_IDENTIFIER,
                                      -1);

	    case 'I':
		return readIdentifier("MPLIED",
                                      LexicalUnits.IMPLIED_IDENTIFIER,
                                      -1);
	    case 'F':
		return readIdentifier("IXED",
                                      LexicalUnits.FIXED_IDENTIFIER,
                                      -1);
	    default:
		throw createXMLException("invalid.character");
	    }

	case '(':
	    nextChar();
            context = ENUMERATION_CONTEXT;
	    return LexicalUnits.LEFT_BRACE;

	default:
	    return readName(LexicalUnits.NAME);
	}
    }

    /**
     * Returns the next lexical unit in the context of a notation.
     */
    protected int nextInNotation() throws IOException, XMLException {
	switch (current) {
	case 0x9:
	case 0xA:
	case 0xD:
	case 0x20:
	    do {
		nextChar();
	    } while (current != -1 &&
                     XMLUtilities.isXMLSpace((char)current));
	    return LexicalUnits.S;

	case '>':
	    nextChar();
	    context = DTD_DECLARATIONS_CONTEXT;
	    return LexicalUnits.END_CHAR;

	case '%':
	    int t = readName(LexicalUnits.PARAMETER_ENTITY_REFERENCE);
	    if (current != ';') {
		throw createXMLException("malformed.parameter.entity");
	    }
	    nextChar();
	    return t;
	case 'S':
	    return readIdentifier("YSTEM",
				  LexicalUnits.SYSTEM_IDENTIFIER,
				  LexicalUnits.NAME);

	case 'P':
	    return readIdentifier("UBLIC",
				  LexicalUnits.PUBLIC_IDENTIFIER,
				  LexicalUnits.NAME);

	case '"':
            attrDelimiter = '"';
	    return readString();

	case '\'':
            attrDelimiter = '\'';
	    return readString();

	default:
	    return readName(LexicalUnits.NAME);
	}
    }

    /**
     * Returns the next lexical unit in the context of an entity.
     */
    protected int nextInEntity() throws IOException, XMLException {
	switch (current) {
	case 0x9:
	case 0xA:
	case 0xD:
	case 0x20:
	    do {
		nextChar();
	    } while (current != -1 &&
                     XMLUtilities.isXMLSpace((char)current));
	    return LexicalUnits.S;

	case '>':
	    nextChar();
	    context = DTD_DECLARATIONS_CONTEXT;
	    return LexicalUnits.END_CHAR;

	case '%':
	    nextChar();
	    return LexicalUnits.PERCENT;

	case 'S':
	    return readIdentifier("YSTEM",
				  LexicalUnits.SYSTEM_IDENTIFIER,
				  LexicalUnits.NAME);

	case 'P':
	    return readIdentifier("UBLIC",
				  LexicalUnits.PUBLIC_IDENTIFIER,
				  LexicalUnits.NAME);

	case 'N':
	    return readIdentifier("DATA",
				  LexicalUnits.NDATA_IDENTIFIER,
				  LexicalUnits.NAME);

	case '"':
            attrDelimiter = '"';
	    nextChar();
	    if (current == -1) {
		throw createXMLException("unexpected.eof");
	    }
            
	    if (current != '"' && current != '&' && current != '%') {
		do {
		    nextChar();
		} while (current != -1 &&
                         current != '"' &&
                         current != '&' &&
                         current != '%');
	    }
	    switch (current) {
	    default:
		throw createXMLException("invalid.character");

	    case '&':
	    case '%':
		context = ENTITY_VALUE_CONTEXT;
		break;

	    case '"':
		nextChar();
                return LexicalUnits.STRING;
	    }
	    return LexicalUnits.FIRST_ATTRIBUTE_FRAGMENT;

	case '\'':
            attrDelimiter = '\'';
	    nextChar();
	    if (current == -1) {
		throw createXMLException("unexpected.eof");
	    }
            
	    if (current != '\'' && current != '&' && current != '%') {
		do {
		    nextChar();
		} while (current != -1 &&
                         current != '\'' &&
                         current != '&' &&
                         current != '%');
	    }
	    switch (current) {
	    default:
		throw createXMLException("invalid.character");

	    case '&':
	    case '%':
		context = ENTITY_VALUE_CONTEXT;
		break;

	    case '\'':
		nextChar();
                return LexicalUnits.STRING;
	    }
	    return LexicalUnits.FIRST_ATTRIBUTE_FRAGMENT;

	default:
	    return readName(LexicalUnits.NAME);
	}
    }

    /**
     * Returns the next lexical unit in the context of an entity value.
     */
    protected int nextInEntityValue() throws IOException, XMLException {
	switch (current) {
	case '&':
	    return readReference();

	case '%':
	    int t = nextChar();
	    readName(LexicalUnits.PARAMETER_ENTITY_REFERENCE);
	    if (current != ';') {
		throw createXMLException("invalid.parameter.entity");
	    }
	    nextChar();
	    return t;

	default:
	    while (current != -1 &&
                   current != attrDelimiter &&
                   current != '&' &&
                   current != '%') {
		nextChar();
	    }
	    switch (current) {
	    case -1:
		throw createXMLException("unexpected.eof");

	    case '\'':
	    case '"':
		nextChar();
		context = ENTITY_CONTEXT;
                return LexicalUnits.STRING;
	    }
	    return LexicalUnits.FIRST_ATTRIBUTE_FRAGMENT;
	}
    }
    
    /**
     * Returns the next lexical unit in the context of a notation type.
     */
    protected int nextInNotationType() throws IOException, XMLException {
        switch (current) {
	case 0x9:
	case 0xA:
	case 0xD:
	case 0x20:
	    do {
		nextChar();
	    } while (current != -1 && XMLUtilities.isXMLSpace((char)current));
	    return LexicalUnits.S;

	case '|':
	    nextChar();
	    return LexicalUnits.PIPE;

	case '(':
	    nextChar();
	    return LexicalUnits.LEFT_BRACE;

	case ')':
	    nextChar();
            context = ATTLIST_CONTEXT;
	    return LexicalUnits.RIGHT_BRACE;

        default:
            return readName(LexicalUnits.NAME);
        }
    }

    /**
     * Returns the next lexical unit in the context of an enumeration.
     */
    protected int nextInEnumeration() throws IOException, XMLException {
        switch (current) {
	case 0x9:
	case 0xA:
	case 0xD:
	case 0x20:
	    do {
		nextChar();
	    } while (current != -1 && XMLUtilities.isXMLSpace((char)current));
	    return LexicalUnits.S;

	case '|':
	    nextChar();
	    return LexicalUnits.PIPE;

	case ')':
	    nextChar();
            context = ATTLIST_CONTEXT;
	    return LexicalUnits.RIGHT_BRACE;

        default:
            return readNmtoken();
        }
    }


    /**
     * Reads an entity or character reference. The current character
     * must be '&'.
     * @return type.
     */
    protected int readReference() throws IOException, XMLException {
	nextChar();
	if (current == '#') {
	    nextChar();
	    int i = 0;
	    switch (current) {
	    case 'x':
		do {
		    i++;
		    nextChar();
		} while ((current >= '0' && current <= '9') ||
			 (current >= 'a' && current <= 'f') ||
			 (current >= 'A' && current <= 'F'));
		break;

	    default:
		do {
		    i++;
		    nextChar();
		} while (current >= '0' && current <= '9');
		break;

	    case -1:
		throw createXMLException("unexpected.eof");
	    }
	    if (i == 1 || current != ';') {
		throw createXMLException("character.reference");
	    }
	    nextChar();
	    return LexicalUnits.CHARACTER_REFERENCE;
	} else {
	    int t = readName(LexicalUnits.ENTITY_REFERENCE);
	    if (current != ';') {
		throw createXMLException("character.reference");
	    }
	    nextChar();
	    return t;
	}
    }

    /**
     * Reads a parameter entity reference. The current character must be '%'.
     * @return type.
     */
    protected int readPEReference() throws IOException, XMLException {
	nextChar();
	if (current == -1) {
	    throw createXMLException("unexpected.eof");
	}
	if (!XMLUtilities.isXMLNameFirstCharacter((char)current)) {
	    throw createXMLException("invalid.parameter.entity");
	}
	do {
	    nextChar();
	} while (current != -1 &&
                 XMLUtilities.isXMLNameCharacter((char)current));
	if (current != ';') {
	    throw createXMLException("invalid.parameter.entity");
	}
	nextChar();
	return LexicalUnits.PARAMETER_ENTITY_REFERENCE;
    }

    /**
     * Reads a Nmtoken. The current character must be the first character.
     * @return LexicalUnits.NMTOKEN.
     */
    protected int readNmtoken() throws IOException, XMLException {
	if (current == -1) {
	    throw createXMLException("unexpected.eof");
	}
	while (XMLUtilities.isXMLNameCharacter((char)current)) {
	    nextChar();
	}
	return LexicalUnits.NMTOKEN;
    }

    /**
     * Sets the value of the current char to the next character or -1 if the
     * end of stream has been reached.
     */
    protected int nextChar() throws IOException {
        current = reader.read();

        if (current == -1) {
            return current;
        }

        if (position == buffer.length) {
            char[] t = new char[position * 3 / 2];
            for (int i = 0; i < position; i++) {
                t[i] = buffer[i];
            }
            buffer = t;
        }

        return buffer[position++] = (char)current;
    }

    /**
     * Returns an XMLException initialized with the given message key.
     */
    protected XMLException createXMLException(String message) {
        String m;
        try {
            m = formatMessage(message,
                              new Object[] {
                                  new Integer(reader.getLine()),
                                  new Integer(reader.getColumn())
                              });
        } catch (MissingResourceException e) {
            m = message;
        }
        return new XMLException(m);
    }

}
