/*
 * The Apache Software License, Version 1.1
 *
 *
 * Copyright (c) 1999 The Apache Software Foundation.  All rights 
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, 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 "Xerces" 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 (INCLUDING, 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 and was
 * originally based on software copyright (c) 1999, International
 * Business Machines, Inc., http://www.apache.org.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */

package org.apache.xerces.readers;

import org.apache.xerces.framework.XMLErrorReporter;
import org.apache.xerces.utils.StringPool;
import org.apache.xerces.utils.XMLCharacterProperties;

import org.xml.sax.Locator;
import org.xml.sax.InputSource;
import java.io.IOException;

/**
 * Reader for processing internal entity replacement text.
 *
 * This reader processes data contained within strings kept
 * in the string pool.  It provides the support for both
 * general and parameter entities.  The location support
 * as we are processing the replacement text is somewhat
 * poor and needs to be updated when "nested locations"
 * have been implemented.
 *
 * For efficiency, we return instances of this class to a
 * free list and reuse those instances to process other
 * strings.
 *
 * @version
 */
final class StringReader extends XMLEntityReader {
    /**
     * Allocate a string reader
     *
     * @param entityHandler The current entity handler.
     * @param errorReporter The current error reporter.
     * @param sendCharDataAsCharArray true if char data should be reported using
     *                                char arrays instead of string handles.
     * @param lineNumber The line number to return as our position.
     * @param columnNumber The column number to return as our position.
     * @param stringHandle The StringPool handle for the data to process.
     * @param stringPool The string pool.
     * @param addEnclosingSpaces If true, treat the data to process as if
     *                           there were a leading and trailing space
     *                           character enclosing the string data.
     * @return The reader that will process the string data.
     */
    public static StringReader createStringReader(XMLEntityHandler entityHandler,
                                                  XMLErrorReporter errorReporter,
                                                  boolean sendCharDataAsCharArray,
                                                  int lineNumber,
                                                  int columnNumber,
                                                  int stringHandle,
                                                  StringPool stringPool,
                                                  boolean addEnclosingSpaces)
    {
        StringReader reader = null;
        synchronized (StringReader.class) {
            reader = fgFreeReaders;
            if (reader == null) {
                return new StringReader(entityHandler, errorReporter, sendCharDataAsCharArray, lineNumber, columnNumber,
                                        stringHandle, stringPool, addEnclosingSpaces);
            }
            fgFreeReaders = reader.fNextFreeReader;
        }
        reader.init(entityHandler, errorReporter, sendCharDataAsCharArray, lineNumber, columnNumber,
                    stringHandle, stringPool, addEnclosingSpaces);
        return reader;
    }
    //
    //
    //
    private StringReader(XMLEntityHandler entityHandler, XMLErrorReporter errorReporter,
                         boolean sendCharDataAsCharArray, int lineNumber, int columnNumber,
                         int stringHandle, StringPool stringPool, boolean addEnclosingSpaces)
    {
        super(entityHandler, errorReporter, sendCharDataAsCharArray, lineNumber, columnNumber);
        fStringPool = stringPool;
        fData = fStringPool.toString(stringHandle);
        fCurrentOffset = 0;
        fEndOffset = fData.length();
        if (addEnclosingSpaces) {
            fMostRecentChar = ' ';
            fCurrentOffset--;
            oweTrailingSpace = hadTrailingSpace = true;
        } else {
            fMostRecentChar = fEndOffset == 0 ? -1 : fData.charAt(0);
        }
    }
    private void init(XMLEntityHandler entityHandler, XMLErrorReporter errorReporter,
                      boolean sendCharDataAsCharArray, int lineNumber, int columnNumber,
                      int stringHandle, StringPool stringPool, boolean addEnclosingSpaces)
    {
        super.init(entityHandler, errorReporter, sendCharDataAsCharArray, lineNumber, columnNumber);
        fStringPool = stringPool;
        fData = fStringPool.toString(stringHandle);
        fCurrentOffset = 0;
        fEndOffset = fData.length();
        fNextFreeReader = null;
        if (addEnclosingSpaces) {
            fMostRecentChar = ' ';
            fCurrentOffset--;
            oweTrailingSpace = hadTrailingSpace = true;
        } else {
            fMostRecentChar = fEndOffset == 0 ? -1 : fData.charAt(0);
            oweTrailingSpace = hadTrailingSpace = false;
        }
    }
    //
    //
    //
    public int addString(int offset, int length) {
        if (length == 0)
            return 0;
        return fStringPool.addString(fData.substring(offset, offset + length));
    }
    //
    //
    //
    public int addSymbol(int offset, int length) {
        if (length == 0)
            return 0;
        return fStringPool.addSymbol(fData.substring(offset, offset + length));
    }
    //
    //
    //
    public void append(XMLEntityHandler.CharBuffer charBuffer, int offset, int length) {
        boolean addSpace = false;
        for (int i = 0; i < length; i++) {
            try {
                charBuffer.append(fData.charAt(offset++));
            } catch (StringIndexOutOfBoundsException ex) {
                if (offset == fEndOffset + 1 && hadTrailingSpace) {
                    charBuffer.append(' ');
                } else {
                    System.err.println("StringReader.append()");
                    throw ex;
                }
            }
        }
    }
    //
    //
    //
    private int loadNextChar() {
        if (++fCurrentOffset >= fEndOffset) {
            if (oweTrailingSpace) {
                oweTrailingSpace = false;
                fMostRecentChar = ' ';
            } else {
                fMostRecentChar = -1;
            }
        } else {
            fMostRecentChar = fData.charAt(fCurrentOffset);
        }
        return fMostRecentChar;
    }
    //
    //
    //
    public XMLEntityHandler.EntityReader changeReaders() throws Exception {
        XMLEntityHandler.EntityReader nextReader = super.changeReaders();
        synchronized (StringReader.class) {
            fNextFreeReader = fgFreeReaders;
            fgFreeReaders = this;
        }
        return nextReader;
    }
    //
    //
    //
    public boolean lookingAtChar(char chr, boolean skipPastChar) throws Exception {
        int ch = fMostRecentChar;
        if (ch != chr) {
            if (ch == -1) {
                return changeReaders().lookingAtChar(chr, skipPastChar);
            }
            return false;
        }
        if (skipPastChar) {
            if (++fCurrentOffset >= fEndOffset) {
                if (oweTrailingSpace) {
                    oweTrailingSpace = false;
                    fMostRecentChar = ' ';
                } else {
                    fMostRecentChar = -1;
                }
            } else {
                fMostRecentChar = fData.charAt(fCurrentOffset);
            }
        }
        return true;
    }
    //
    //
    //
    public boolean lookingAtValidChar(boolean skipPastChar) throws Exception {
        int ch = fMostRecentChar;
        if (ch < 0xD800) {
            if (ch < 0x20 && ch != 0x09 && ch != 0x0A && ch != 0x0D) {
                if (ch == -1)
                    return changeReaders().lookingAtValidChar(skipPastChar);
                return false;
            }
            if (skipPastChar) {
                if (++fCurrentOffset >= fEndOffset) {
                    if (oweTrailingSpace) {
                        oweTrailingSpace = false;
                        fMostRecentChar = ' ';
                    } else {
                        fMostRecentChar = -1;
                    }
                } else {
                    fMostRecentChar = fData.charAt(fCurrentOffset);
                }
            }
            return true;
        }
        if (ch > 0xFFFD) {
            return false;
        }
        if (ch < 0xDC00) {
            if (fCurrentOffset + 1 >= fEndOffset) {
                return false;
            }
            ch = fData.charAt(fCurrentOffset + 1);
            if (ch < 0xDC00 || ch >= 0xE000) {
                return false;
            } else if (!skipPastChar) {
                return true;
            } else {
                fCurrentOffset++;
            }
        } else if (ch < 0xE000) {
            return false;
        }
        if (skipPastChar) {
            if (++fCurrentOffset >= fEndOffset) {
                if (oweTrailingSpace) {
                    oweTrailingSpace = false;
                    fMostRecentChar = ' ';
                } else {
                    fMostRecentChar = -1;
                }
            } else {
                fMostRecentChar = fData.charAt(fCurrentOffset);
            }
        }
        return true;
    }
    //
    //
    //
    public boolean lookingAtSpace(boolean skipPastChar) throws Exception {
        int ch = fMostRecentChar;
        if (ch > 0x20)
            return false;
        if (ch == 0x20 || ch == 0x0A || ch == 0x0D || ch == 0x09) {
            if (skipPastChar) {
                loadNextChar();
            }
            return true;
        }
        if (ch == -1) {
            return changeReaders().lookingAtSpace(skipPastChar);
        }
        return false;
    }
    //
    //
    //
    public void skipToChar(char chr) throws Exception {
        //
        // REVISIT - this will skip invalid characters without reporting them.
        //
        int ch = fMostRecentChar;
        while (true) {
            if (ch == chr)
                return;
            if (ch == -1) {
                changeReaders().skipToChar(chr);
                return;
            }
            ch = loadNextChar();
        }
    }
    //
    //
    //
    public void skipPastSpaces() throws Exception {
        int ch = fMostRecentChar;
        if (ch == -1) {
            changeReaders().skipPastSpaces();
            return;
        }
        while (true) {
            if (ch > 0x20 || (ch != 0x20 && ch != 0x0A && ch != 0x09 && ch != 0x0D)) {
                fMostRecentChar = ch;
                return;
            }
            if (++fCurrentOffset >= fEndOffset) {
                changeReaders().skipPastSpaces();
                return;
            }
            ch = fData.charAt(fCurrentOffset);
        }
    }
    //
    //
    //
    public void skipPastName(char fastcheck) throws Exception {
        int ch = fMostRecentChar;
        if (ch < 0x80) {
            if (ch == -1 || XMLCharacterProperties.fgAsciiInitialNameChar[ch] == 0)
                return;
        } else {
            if ((XMLCharacterProperties.fgCharFlags[ch] & XMLCharacterProperties.E_InitialNameCharFlag) == 0)
                return;
        }
        while (true) {
            ch = loadNextChar();
            if (fastcheck == ch)
                return;
            if (ch < 0x80) {
                if (ch == -1 || XMLCharacterProperties.fgAsciiNameChar[ch] == 0)
                    return;
            } else {
                if ((XMLCharacterProperties.fgCharFlags[ch] & XMLCharacterProperties.E_NameCharFlag) == 0)
                    return;
            }
        }
    }
    //
    //
    //
    public void skipPastNmtoken(char fastcheck) throws Exception {
        int ch = fMostRecentChar;
        while (true) {
            if (fastcheck == ch)
                return;
            if (ch < 0x80) {
                if (ch == -1 || XMLCharacterProperties.fgAsciiNameChar[ch] == 0)
                    return;
            } else {
                if ((XMLCharacterProperties.fgCharFlags[ch] & XMLCharacterProperties.E_NameCharFlag) == 0)
                    return;
            }
            ch = loadNextChar();
        }
    }
    //
    //
    //
    public boolean skippedString(char[] s) throws Exception {
        int ch = fMostRecentChar;
        if (ch != s[0]) {
            if (ch == -1)
                return changeReaders().skippedString(s);
            return false;
        }
        if (fCurrentOffset + s.length > fEndOffset)
            return false;
        for (int i = 1; i < s.length; i++) {
            if (fData.charAt(fCurrentOffset + i) != s[i])
                return false;
        }
        fCurrentOffset += (s.length - 1);
        loadNextChar();
        return true;
    }
    //
    //
    //
    public int scanInvalidChar() throws Exception {
        int ch = fMostRecentChar;
        if (ch == -1)
            return changeReaders().scanInvalidChar();
        loadNextChar();
        return ch;
    }
    //
    //
    //
    public int scanCharRef(boolean hex) throws Exception {
        int ch = fMostRecentChar;
        if (ch == -1)
            return changeReaders().scanCharRef(hex);
        int num = 0;
        if (hex) {
            if (ch > 'f' || XMLCharacterProperties.fgAsciiXDigitChar[ch] == 0)
                return XMLEntityHandler.CHARREF_RESULT_INVALID_CHAR;
            num = ch - (ch < 'A' ? '0' : (ch < 'a' ? 'A' : 'a') - 10);
        } else {
            if (ch < '0' || ch > '9')
                return XMLEntityHandler.CHARREF_RESULT_INVALID_CHAR;
            num = ch - '0';
        }
        boolean toobig = false;
        while (true) {
            ch = loadNextChar();
            if (ch == -1)
                return XMLEntityHandler.CHARREF_RESULT_SEMICOLON_REQUIRED;
            if (hex) {
                if (ch > 'f' || XMLCharacterProperties.fgAsciiXDigitChar[ch] == 0)
                    break;
            } else {
                if (ch < '0' || ch > '9')
                    break;
            }
            if (hex) {
                int dig = ch - (ch < 'A' ? '0' : (ch < 'a' ? 'A' : 'a') - 10);
                num = (num << 4) + dig;
            } else {
                int dig = ch - '0';
                num = (num * 10) + dig;
            }
            if (num > 0x10FFFF) {
                toobig = true;
                num = 0;
            }
        }
        if (ch != ';')
            return XMLEntityHandler.CHARREF_RESULT_SEMICOLON_REQUIRED;
        loadNextChar();
        if (toobig)
            return XMLEntityHandler.CHARREF_RESULT_OUT_OF_RANGE;
        return num;
    }
    //
    //
    //
    public int scanStringLiteral() throws Exception {
        boolean single;
        if (!(single = lookingAtChar('\'', true)) && !lookingAtChar('\"', true)) {
            return XMLEntityHandler.STRINGLIT_RESULT_QUOTE_REQUIRED;
        }
        int offset = fCurrentOffset;
        char qchar = single ? '\'' : '\"';
        while (!lookingAtChar(qchar, false)) {
            if (!lookingAtValidChar(true)) {
                return XMLEntityHandler.STRINGLIT_RESULT_INVALID_CHAR;
            }
        }
        int stringIndex = addString(offset, fCurrentOffset - offset);
        lookingAtChar(qchar, true); // move past qchar
        return stringIndex;
    }
    //
    // [10] AttValue ::= '"' ([^<&"] | Reference)* '"'
    //                   | "'" ([^<&'] | Reference)* "'"
    //
    public int scanAttValue(char qchar, boolean asSymbol) throws Exception
    {
        int offset = fCurrentOffset;
        while (true) {
            if (lookingAtChar(qchar, false)) {
                break;
            }
            if (lookingAtChar(' ', true)) {
                continue;
            }
            if (lookingAtSpace(false)) {
                return XMLEntityHandler.ATTVALUE_RESULT_COMPLEX;
            }
            if (lookingAtChar('&', false)) {
                return XMLEntityHandler.ATTVALUE_RESULT_COMPLEX;
            }
            if (lookingAtChar('<', false)) {
                return XMLEntityHandler.ATTVALUE_RESULT_LESSTHAN;
            }
            if (!lookingAtValidChar(true)) {
                return XMLEntityHandler.ATTVALUE_RESULT_INVALID_CHAR;
            }
        }
        int result = asSymbol ? addSymbol(offset, fCurrentOffset - offset) : addString(offset, fCurrentOffset - offset);
        lookingAtChar(qchar, true);
        return result;
    }
    //
    //  [9] EntityValue ::= '"' ([^%&"] | PEReference | Reference)* '"'
    //                      | "'" ([^%&'] | PEReference | Reference)* "'"
    //
    // The values in the following table are defined as:
    //
    //      0 - not special
    //      1 - quote character
    //      2 - reference
    //      3 - peref
    //      4 - invalid
    //
    public static final byte fgAsciiEntityValueChar[] = {
        4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 4,
        4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
        0, 0, 1, 0, 0, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, // '\"', '%', '&', '\''
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
    };
    public int scanEntityValue(int qchar, boolean createString) throws Exception
    {
        int offset = fCurrentOffset;
        int ch = fMostRecentChar;
        while (true) {
            if (ch == -1) {
                changeReaders(); // do not call next reader, our caller may need to change the parameters
                return XMLEntityHandler.ENTITYVALUE_RESULT_END_OF_INPUT;
            }
            if (ch < 0x80) {
                switch (fgAsciiEntityValueChar[ch]) {
                case 1: // quote char
                    if (ch == qchar) {
                        if (!createString)
                            return XMLEntityHandler.ENTITYVALUE_RESULT_FINISHED;
                        int length = fCurrentOffset - offset;
                        int result = length == 0 ? StringPool.EMPTY_STRING : addString(offset, length);
                        loadNextChar();
                        return result;
                    }
                    // the other quote character is not special
                    // fall through
                case 0: // non-special char
                    if (++fCurrentOffset >= fEndOffset) {
                        if (oweTrailingSpace) {
                            oweTrailingSpace = false;
                            ch = fMostRecentChar = ' ';
                        } else {
                            ch = fMostRecentChar = -1;
                        }
                    } else {
                        ch = fMostRecentChar = fData.charAt(fCurrentOffset);
                    }
                    continue;
                case 2: // reference
                    return XMLEntityHandler.ENTITYVALUE_RESULT_REFERENCE;
                case 3: // peref
                    return XMLEntityHandler.ENTITYVALUE_RESULT_PEREF;
                case 4: // invalid
                    return XMLEntityHandler.ENTITYVALUE_RESULT_INVALID_CHAR;
                }
            } else if (ch < 0xD800) {
                ch = loadNextChar();
            } else if (ch >= 0xE000 && (ch <= 0xFFFD || (ch >= 0x10000 && ch <= 0x10FFFF))) {
                //
                // REVISIT - needs more code to check surrogates.
                //
                ch = loadNextChar();
            } else {
                return XMLEntityHandler.ENTITYVALUE_RESULT_INVALID_CHAR;
            }
        }
    }
    //
    //
    //
    public boolean scanExpectedName(char fastcheck, StringPool.CharArrayRange expectedName) throws Exception {
        int ch = fMostRecentChar;
        if (ch == -1) {
            return changeReaders().scanExpectedName(fastcheck, expectedName);
        }
        int nameOffset = fCurrentOffset;
        if ((XMLCharacterProperties.fgCharFlags[ch] & XMLCharacterProperties.E_InitialNameCharFlag) == 0)
            return false;
        while (true) {
            ch = loadNextChar();
            if (fastcheck == ch)
                break;
            if (ch == -1)
                break;
            if ((XMLCharacterProperties.fgCharFlags[ch] & XMLCharacterProperties.E_NameCharFlag) == 0)
                break;
        }
        int nameIndex = fStringPool.addSymbol(fData.substring(nameOffset, fCurrentOffset));
        // DEFECT !! check name against expected name

        return true;
    }
    //
    //
    //
    public int scanQName(char fastcheck) throws Exception {
        // DEFECT !! no code for namespace support
        int ch = fMostRecentChar;
        if (ch == -1) {
            return changeReaders().scanQName(fastcheck);
        }
        int nameOffset = fCurrentOffset;
        if ((XMLCharacterProperties.fgCharFlags[ch] & XMLCharacterProperties.E_InitialNameCharFlag) == 0)
            return -1;
        while (true) {
            ch = loadNextChar();
            if (fastcheck == ch)
                break;
            if (ch == -1)
                break;
            if ((XMLCharacterProperties.fgCharFlags[ch] & XMLCharacterProperties.E_NameCharFlag) == 0)
                break;
        }
        int nameIndex = fStringPool.addSymbol(fData.substring(nameOffset, fCurrentOffset));
        return nameIndex;
    }
    //
    //
    //
    public int scanName(char fastcheck) throws Exception {
        int ch = fMostRecentChar;
        if (ch == -1) {
            return changeReaders().scanName(fastcheck);
        }
        int nameOffset = fCurrentOffset;
        if ((XMLCharacterProperties.fgCharFlags[ch] & XMLCharacterProperties.E_InitialNameCharFlag) == 0)
            return -1;
        while (true) {
            if (++fCurrentOffset >= fEndOffset) {
                if (oweTrailingSpace) {
                    oweTrailingSpace = false;
                    fMostRecentChar = ' ';
                } else {
                    fMostRecentChar = -1;
                }
                break;
            }
            ch = fMostRecentChar = fData.charAt(fCurrentOffset);
            if (fastcheck == ch)
                break;
            if ((XMLCharacterProperties.fgCharFlags[ch] & XMLCharacterProperties.E_NameCharFlag) == 0)
                break;
        }
        int nameIndex = fStringPool.addSymbol(fData.substring(nameOffset, fCurrentOffset));
        return nameIndex;
    }
    //
    // There are no leading/trailing space checks here because scanContent cannot
    // be called on a parameter entity reference value.
    //
    private int recognizeMarkup(int ch) throws Exception {
        if (ch == -1) {
            return XMLEntityHandler.CONTENT_RESULT_MARKUP_END_OF_INPUT;
        }
        switch (ch) {
        case '?':
            loadNextChar();
            return XMLEntityHandler.CONTENT_RESULT_START_OF_PI;
        case '!':
            ch = loadNextChar();
            if (ch == -1) {
                fCurrentOffset -= 2;
                loadNextChar();
                return XMLEntityHandler.CONTENT_RESULT_MARKUP_END_OF_INPUT;
            }
            if (ch == '-') {
                ch = loadNextChar();
                if (ch == -1) {
                    fCurrentOffset -= 3;
                    loadNextChar();
                    return XMLEntityHandler.CONTENT_RESULT_MARKUP_END_OF_INPUT;
                }
                if (ch == '-') {
                    loadNextChar();
                    return XMLEntityHandler.CONTENT_RESULT_START_OF_COMMENT;
                }
                break;
            }
            if (ch == '[') {
                for (int i = 0; i < 6; i++) {
                    ch = loadNextChar();
                    if (ch == -1) {
                        fCurrentOffset -= (3 + i);
                        loadNextChar();
                        return XMLEntityHandler.CONTENT_RESULT_MARKUP_END_OF_INPUT;
                    }
                    if (ch != cdata_string[i]) {
                        return XMLEntityHandler.CONTENT_RESULT_MARKUP_NOT_RECOGNIZED;
                    }
                }
                loadNextChar();
                return XMLEntityHandler.CONTENT_RESULT_START_OF_CDSECT;
            }
            break;
        case '/':
            loadNextChar();
            return XMLEntityHandler.CONTENT_RESULT_START_OF_ETAG;
        default:
            return XMLEntityHandler.CONTENT_RESULT_START_OF_ELEMENT;
        }
        return XMLEntityHandler.CONTENT_RESULT_MARKUP_NOT_RECOGNIZED;
    }
    private int recognizeReference(int ch) throws Exception {
        if (ch == -1) {
            return XMLEntityHandler.CONTENT_RESULT_MARKUP_END_OF_INPUT;
        }
        //
        // [67] Reference ::= EntityRef | CharRef
        // [68] EntityRef ::= '&' Name ';'
        // [66] CharRef ::= '&#' [0-9]+ ';' | '&#x' [0-9a-fA-F]+ ';'
        //
        if (ch == '#') {
            loadNextChar();
            return XMLEntityHandler.CONTENT_RESULT_START_OF_CHARREF;
        } else {
            return XMLEntityHandler.CONTENT_RESULT_START_OF_ENTITYREF;
        }
    }
    public int scanContent(int elementType) throws Exception {
        int ch = fMostRecentChar;
        if (ch == -1) {
            return changeReaders().scanContent(elementType);
        }
        int offset = fCurrentOffset;
        if (ch < 0x80) {
            switch (XMLCharacterProperties.fgAsciiWSCharData[ch]) {
            case 0:
                ch = loadNextChar();
                break;
            case 1:
                ch = loadNextChar();
                if (!fInCDSect) {
                    return recognizeMarkup(ch);
                }
                break;
            case 2:
                ch = loadNextChar();
                if (!fInCDSect) {
                    return recognizeReference(ch);
                }
                break;
            case 3:
                ch = loadNextChar();
                if (ch == ']' && fCurrentOffset + 1 < fEndOffset && fData.charAt(fCurrentOffset + 1) == '>') {
                    loadNextChar();
                    loadNextChar();
                    return XMLEntityHandler.CONTENT_RESULT_END_OF_CDSECT;
                }
                break;
            case 4:
                return XMLEntityHandler.CONTENT_RESULT_INVALID_CHAR;
            case 5:
                do {
                    ch = loadNextChar();
                    if (ch == -1) {
                        callCharDataHandler(offset, fEndOffset, true);
                        return changeReaders().scanContent(elementType);
                    }
                } while (ch == 0x20 || ch == 0x0A || ch == 0x0D || ch == 0x09);
                if (ch < 0x80) {
                    switch (XMLCharacterProperties.fgAsciiCharData[ch]) {
                    case 0:
                        ch = loadNextChar();
                        break;
                    case 1:
                        ch = loadNextChar();
                        if (!fInCDSect) {
                            callCharDataHandler(offset, fCurrentOffset - 1, true);
                            return recognizeMarkup(ch);
                        }
                        break;
                    case 2:
                        ch = loadNextChar();
                        if (!fInCDSect) {
                            callCharDataHandler(offset, fCurrentOffset - 1, true);
                            return recognizeReference(ch);
                        }
                        break;
                    case 3:
                        ch = loadNextChar();
                        if (ch == ']' && fCurrentOffset + 1 < fEndOffset && fData.charAt(fCurrentOffset + 1) == '>') {
                            callCharDataHandler(offset, fCurrentOffset - 1, true);
                            loadNextChar();
                            loadNextChar();
                            return XMLEntityHandler.CONTENT_RESULT_END_OF_CDSECT;
                        }
                        break;
                    case 4:
                        callCharDataHandler(offset, fCurrentOffset, true);
                        return XMLEntityHandler.CONTENT_RESULT_INVALID_CHAR;
                    }
                } else {
                    if (ch == 0xFFFE || ch == 0xFFFF) {
                        callCharDataHandler(offset, fCurrentOffset, true);
                        return XMLEntityHandler.CONTENT_RESULT_INVALID_CHAR;
                    }
                    ch = loadNextChar();
                }
            }
        } else {
            if (ch == 0xFFFE || ch == 0xFFFF) {
                callCharDataHandler(offset, fCurrentOffset, false);
                return XMLEntityHandler.CONTENT_RESULT_INVALID_CHAR;
            }
            ch = loadNextChar();
        }
        while (true) {
            if (ch == -1) {
                callCharDataHandler(offset, fEndOffset, false);
                return changeReaders().scanContent(elementType);
            }
            if (ch >= 0x80)
                break;
            if (XMLCharacterProperties.fgAsciiCharData[ch] != 0)
                break;
            ch = loadNextChar();
        }
        while (true) { // REVISIT - EOF check ?
            if (ch < 0x80) {
                switch (XMLCharacterProperties.fgAsciiCharData[ch]) {
                case 0:
                    ch = loadNextChar();
                    break;
                case 1:
                    ch = loadNextChar();
                    if (!fInCDSect) {
                        callCharDataHandler(offset, fCurrentOffset - 1, false);
                        return recognizeMarkup(ch);
                    }
                    break;
                case 2:
                    ch = loadNextChar();
                    if (!fInCDSect) {
                        callCharDataHandler(offset, fCurrentOffset - 1, false);
                        return recognizeReference(ch);
                    }
                    break;
                case 3:
                    ch = loadNextChar();
                    if (ch == ']' && fCurrentOffset + 1 < fEndOffset && fData.charAt(fCurrentOffset + 1) == '>') {
                        callCharDataHandler(offset, fCurrentOffset - 1, false);
                        loadNextChar();
                        loadNextChar();
                        return XMLEntityHandler.CONTENT_RESULT_END_OF_CDSECT;
                    }
                    break;
                case 4:
                    callCharDataHandler(offset, fCurrentOffset, false);
                    return XMLEntityHandler.CONTENT_RESULT_INVALID_CHAR;
                }
            } else {
                if (ch == 0xFFFE || ch == 0xFFFF) {
                    callCharDataHandler(offset, fCurrentOffset, false);
                    return XMLEntityHandler.CONTENT_RESULT_INVALID_CHAR;
                }
                ch = loadNextChar();
            }
            if (ch == -1) {
                callCharDataHandler(offset, fCurrentOffset, false);
                return changeReaders().scanContent(elementType);
            }
        }
    }
    //
    //
    //
    private void callCharDataHandler(int offset, int endOffset, boolean isWhitespace) throws Exception {
        int length = endOffset - offset;
        if (!fSendCharDataAsCharArray) {
            int stringIndex = addString(offset, length);
            if (isWhitespace)
                fEntityHandler.processWhitespace(stringIndex);
            else
                fEntityHandler.processCharacters(stringIndex);
            return;
        }
        if (isWhitespace)
            fEntityHandler.processWhitespace(fData.toCharArray(), offset, length);
        else
            fEntityHandler.processCharacters(fData.toCharArray(), offset, length);
    }
    //
    //
    //
    private static final char[] cdata_string = { 'C','D','A','T','A','[' };
    //
    //
    //
    private StringPool fStringPool = null;
    private String fData = null;
    private int fEndOffset;
    private boolean hadTrailingSpace = false;
    private boolean oweTrailingSpace = false;
    private int fMostRecentChar;
    private StringReader fNextFreeReader = null;
    private static StringReader fgFreeReaders = null;
}
