/*****************************************************************************
 * Copyright (C) The Apache Software Foundation. All rights reserved.        *
 * ------------------------------------------------------------------------- *
 * This software is published under the terms of the Apache Software License *
 * version 1.1, a copy of which has been included with this distribution in  *
 * the LICENSE file.                                                         *
 *****************************************************************************/

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.3 2003/04/11 13:59:38 vhardy 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);

    /**
     * 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);
    }

}
