/*
 * (C) Copyright IBM Corp. 1997-1998  All rights reserved.
 *
 * US Government Users Restricted Rights Use, duplication or
 * disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
 *
 * The program is provided "as is" without any warranty express or
 * implied, including the warranty of non-infringement and the implied
 * warranties of merchantibility and fitness for a particular purpose.
 * IBM will not be liable for any damages suffered by you as a result
 * of using the Program. In no event will IBM be liable for any
 * special, indirect or consequential damages or lost profits even if
 * IBM has been advised of the possibility of their occurrence. IBM
 * will not be liable for any third party claims against you.
 */

package com.ibm.xml.parser;

import java.io.*;
import java.util.*;

/**
 * Subroutines of parser.
 *
 * @version 1.1
 * @author TAMURA Kent &lt;kent@trl.ibm.co.jp&gt;
 */
final class Token implements FileScanner {
    private Reading m_r;
    private Parser m_p;
    int m_next = -1;

    static final int ST_NORMAL = 0;
    static final int ST_INTERNALDTD = 1;
    static final int ST_EXTERNALDTD = 2;
    static final int ST_TAG = 3;
    static final int ST_VALUE = 4;
    static final int ST_CONTENTMODEL = 5;
    int state = ST_NORMAL;

    int level = 0;

    Token(Parser pr, String fn, Source src) throws IOException {
        m_p = pr;
        m_r = new XMLReader(pr, fn, src);
        m_next = next();
    }
    Token(Parser pr, String content) throws IOException {
        m_p = pr;
        m_r = new StringReading(content);
        m_next = next();
    }

    int setState(int state) {
        int ret = this.state;
        /*
        if (!(ret == ST_INTERNALDTD
        && (state == ST_TAG || state == ST_VALUE || state == ST_CONTENTMODEL)))*/
            this.state = state;
        return ret;
    }
    int getState() {
        return this.state;
    }
    int getLevel() {
        return this.level;
    }

    final int getChar() throws IOException {
        int ch = m_next;
        //getChar();
        //checkEOF();

        m_next = next();
        //System.err.print("["+(char)ch+"]"); System.err.flush();
        return ch;
    }

    private final void checkEOF() throws IOException {
        if (0 > m_r.readNext()) {
            String pename = m_p.popPE();
            String ekey = m_r.getErrorKey();
            if (ekey != null)
                m_p.format1(this, ekey, pename);
            m_r.close();
            this.level --;
            if (m_follow && null != m_r.getNext())
                m_r = m_r.getNext();
        }
    }

    private final int next() throws IOException {
        if (m_r == null)  return -1;
        if (0 > m_r.readNext()) {
            String pename = m_p.popPE();
            String ekey = m_r.getErrorKey();
            if (ekey != null)
                m_p.format1(this, ekey, pename);
            m_r.close();
            this.level --;
            if (m_follow && null != m_r.getNext())
                m_r = m_r.getNext();
        }
        int direct = m_r.getChar();
        if (this.state != ST_NORMAL && direct == '%' && m_p.dtd.isParsingExternal()) {
            if (this.state == ST_TAG && XMLChar.isSpace(m_r.readNext())) {
                return direct;
            }
            String pename = getNameDirect();
            if (null == pename) {
                m_p.format0(this, "E_PEREF0");
            } else {
                if (m_r.readNext() != ';')
                    m_p.format1(this, "E_PEREF1", pename);
                else
                    m_r.getChar();
                if (!m_p.pushPE(pename)) {
                    m_p.format2(this, "E_PEREF5", pename,
                                Parser.makeReferencePath(m_p.parameterEntityReferences, pename));
                    direct = m_r.readNext();
                } else {
                    Entity ev = m_p.entityPool.referPara(pename);
                    if (ev == null)
                        m_p.format1(this, "E_PEREF2", pename);
                    else {
                        Reading reading;
                        if (!ev.isExternal()) {
                            if (this.state == ST_TAG || this.state == ST_CONTENTMODEL) {
                                addReading(reading = new StringReading(" "+ev.getValue()+" "));
                            } else
                                addReading(reading = new StringReading(ev.getValue()));
                        } else {
                            ev.load(m_p);
                            String name = ev.getExternalID().getSystemLiteral();
                            if (name == null) name = ev.getExternalID().getPubidLiteral();
                            XMLReader xr;
                            if (this.state == ST_TAG || this.state == ST_CONTENTMODEL) {
                                xr = new XMLReader(m_p, name, ev.getInputStream(), " ");
                                xr.addTail(" ");
                            } else {
                                xr = new XMLReader(m_p, name, ev.getInputStream());
                            }
                            addReading(reading = xr);
                        }
                        reading.setChecking(this.state);
                    }
                    direct = m_r.getChar();
                }
            }
        }
        return direct;
    }

    void addReading(Reading r) throws IOException {
        this.level ++;
        r.setNext(m_r);
        m_r = r;
        checkEOF();
    }

    boolean m_follow = true;
    void setFollow(boolean f) {
        m_follow = f;
    }

    Reading getReading() {
        return m_r;
    }
    void close() {
        if (null != m_r) {
            try {
                m_r.close();
            } catch (IOException e) {}
            m_r = null;
            m_p = null;
        }
    }
    public String getFileName() {
        return m_r.getFileName();
    }
    public int getLineNumber() {
        return m_r.getLineNumber();
    }
    public int getCharLocation() {
        return m_r.getCharLocation();
    }
    

    void skipSpaces() throws IOException {
        while (0 <= m_next && XMLChar.isSpace(m_next))
            getChar();
    }
    void nextTagStart() throws IOException {
        while (0 <= m_next && '<' != m_next)
            getChar();
    }
    void skipTagEnd() throws IOException {
        while (0 <= m_next) {
            if ('>' == getChar())  break;
        }
    }

    final private void ensureCapacity() {
        if (m_bufpos >= m_strbuf.length) {
            char[] ac = new char[m_strbuf.length+S_INCREMENTBUFSIZE];
            System.arraycopy(m_strbuf, 0, ac, 0, m_bufpos);
            m_strbuf = ac;
        }
    }

    static final int S_INITIALBUFSIZE = 1024;
    static final int S_INCREMENTBUFSIZE = 1024;
    private char[] m_strbuf = new char[S_INITIALBUFSIZE];
    private int m_bufpos = 0;
    String getName() throws IOException {
        int n = m_next;
        if (XMLChar.isLetter(n) || '_' == n || ':' == n) {
            m_strbuf[0] = (char)getChar();
            m_bufpos = 1;
            while (XMLChar.isNameChar(m_next)) {
                if (m_bufpos >= m_strbuf.length) {
                    char[] ac = new char[m_strbuf.length+S_INCREMENTBUFSIZE];
                    System.arraycopy(m_strbuf, 0, ac, 0, m_bufpos);
                    m_strbuf = ac;
                }
                //ensureCapacity();
                m_strbuf[m_bufpos++] = (char)getChar();
            }
            String ret = new String(m_strbuf, 0, m_bufpos);
//System.err.println("["+ret+"]");
            return ret.intern();
        }
        return null;
    }

    private char[] directBuffer = new char[S_INITIALBUFSIZE];
    private int directPosition = 0;
    /**
     * readNext() returns the first letter of Name.
     */
    private String getNameDirect() throws IOException {
        int n = m_r.readNext();
        if (XMLChar.isLetter(n) || '_' == n || ':' == n) {
            this.directBuffer[0] = (char)m_r.getChar();
            this.directPosition = 1;
            while (XMLChar.isNameChar(n = m_r.readNext())) {
                if (this.directPosition >= this.directBuffer.length) {
                    char[] ac = new char[this.directBuffer.length+S_INCREMENTBUFSIZE];
                    System.arraycopy(this.directBuffer, 0, ac, 0, this.directPosition);
                    this.directBuffer = ac;
                }
                this.directBuffer[this.directPosition++] = (char)m_r.getChar();
            }
            String ret = new String(this.directBuffer, 0, this.directPosition);

            return ret.intern();
        }
        return null;
    }

    String getNmtoken() throws IOException {
        int n = m_next;
        if (XMLChar.isNameChar(n)) {
            StringBuffer sb = new StringBuffer(32);
            do {
                sb.append((char)getChar());
            } while (XMLChar.isNameChar(m_next));
            return sb.toString().intern();
        }
        return null;
    }


    /*
     * Return String to be processed Reference.
     * m_next points quotation character.
     */
    String getAttValue() throws IOException {
        int lv = this.level;
        int start = (char)getChar();
        if ('"' == start || '\'' == start) {
            StringBuffer sb = new StringBuffer(256);
            while (lv == this.level && m_next != start) {
                Child chi = m_p.parseAttributeContent(this, start);
                if (null == chi)  break;
                sb.append(chi.getText());
            }
            if (m_next == start)  getChar();
            return sb.toString();
        } else {
            m_p.format0(this, "E_ATTVAL0");
            while ('>' != m_next && !XMLChar.isSpace(m_next))
                getChar();
            return "";
        }
        
    }

    TXAttribute[] getPIAttributes(boolean doencoding) throws IOException {
        TXAttribute[] aa = getAttributes(false);
        if (doencoding) {
            for (int i = 0;  i < aa.length;  i ++) {
                String aname = aa[i].getName();
                if ("encoding".equals(aname)) {
                    String enc = aa[i].getValue();
                    if (!Util.checkEncoding(enc)) {
                        m_p.format1(this, "E_PI4", enc);
                        enc = enc.trim();
                        if (Util.checkEncoding(enc))
                            getReading().setEncoding(enc, m_p.isAllowJavaEncodingName);
                    } else
                        getReading().setEncoding(enc, m_p.isAllowJavaEncodingName);
                    
                }
            }
        }
        int ch = getChar();
        if (0 > ch)
            throw new EOFException("com.ibm.xml.parser.Token#getPIAttributes(): "+m_p.getString("E_EOF"));
        if ('?' != ch) {
            m_p.format0(this, "E_XML0");
            nextTagStart();
        } else {
            ch = m_next;
            if (0 > ch)
                throw new EOFException("com.ibm.xml.parser.Token#getPIAttributes(): "+m_p.getString("E_EOF"));
            if ('>' != ch) {
                m_p.format0(this, "E_XML0");
                nextTagStart();
            }
        }
        return aa;
    }

    TXAttribute[] getAttributes(boolean donamespace) throws IOException {
        Vector v = new Vector();
        while (XMLChar.isSpace(m_next)) {
            skipSpaces();                       // S
            String n = getName();               // Attribute name
            if (null == n)  break;
            skipSpaces();                       // skip S?
            if ('=' != m_next) {
                m_p.format1(this, "E_ATTEQ0", n);
            } else {
                getChar();                      // =
                skipSpaces();                   // skip S?
                TXAttribute a = m_p.elementFactory.createAttribute(n, getAttValue());
                if (donamespace) {
                    String ret = m_p.document.setNamespaceParameters(a);
                    if (null != ret)  m_p.format1(this, ret, n);
                }
                if (0 <= v.indexOf(a)) {
                    m_p.format1(this, "E_ATTEQ1", n);
                } else
                    v.addElement(a);
                if (n.equals("xml:lang") && !Util.checkLanguageID(a.getValue()))
                    m_p.format1(this, "E_ATTEQ2", a.getValue());
            }
        }
        TXAttribute[] aa = new TXAttribute[v.size()];
        v.copyInto(aa);
        return aa;
    }

    String m_lastreference;
    Entity m_lastentity;
    /*
     * last character = '&', m_next is a character after '&'.
     */
    String doReference() throws IOException {
        return doReference(true);
    }
    String doReference(boolean allowexternal) throws IOException {
        this.m_lastreference = null;
        this.m_tbufpos = 0;
        if ('#' == m_next) {                // CharRef
            getChar();
            if ('x' == m_next) {
                getChar();
                int ret = 0;
                int keta = 0;
                StringBuffer sb = new StringBuffer(8);
                while (true) {
                    int ch = m_next;
                    if ('0' <= ch && ch <= '9') {
                        getChar();
                        ret = (ret<<4)+(ch-'0');
                        if (keta != 0 || ch != '0')  keta ++;
                    } else if ('a' <= ch && ch <= 'f') {
                        getChar();
                        ret = (ret<<4)+(ch+10-'a');
                        keta ++;
                    } else if ('A' <= ch && ch <= 'F') {
                        getChar();
                        ret = (ret<<4)+(ch+10-'A');
                        keta ++;
                    } else
                        break;
                }
                if (';' != m_next) {
                    m_p.format0(this, "E_REFER1");
                } else
                    getChar();
                if (0 == sb.length()) {
                    m_p.format0(this, "E_REFER2");
                    return "?";
                }
                if (keta > 8) {
                    m_p.format0(this, "E_REFER3");
                }
                if (0x0010ffff < ret || 0 > ret
                    || (0x10000 > ret && !XMLChar.isChar(ret))
                    || (0xd800 <= ret && ret <= 0xdfff)) {
                    m_p.format1(this, "E_REFER3", Integer.toString(ret, 16));
                    return "?";
                }
                this.m_tbufpos = 0;
                char[] ac;
                if (0x10000 <= ret) {           // UTF-16 surrogate
                    this.m_tstrbuf[0] = (char)(((ret-0x00010000)>>10)+0xd800);
                    this.m_tstrbuf[1] = (char)(((ret-0x00010000)&0x3ff)+0xdc00);
                    this.m_tbufpos = 2;
                } else {
                    this.m_tstrbuf[0] = (char)ret;
                    this.m_tbufpos = 1;
                }
                return null;
            } else if ('0' <= m_next && m_next <= '9') {
                int ret = 0;
                while (true) {
                    int ch = m_next;
                    if ('0' <= ch && ch <= '9') {
                        ret = ret*10+(ch-'0');
                        getChar();
                    } else
                        break;
                }
                if (';' != m_next) {
                    m_p.format0(this, "E_REFER1");
                } else
                    getChar();
                if (0x0010ffff < ret || 0 > ret || (0x10000 > ret && !XMLChar.isChar(ret))
                    || (0xd800 <= ret && ret <= 0xdfff)) {
                    m_p.format1(this, "E_REFER4", Integer.toString(ret, 10));
                    return "?";
                }
                if (0x10000 <= ret) {           // UTF-16 surrogate
                    this.m_tstrbuf[0] = (char)(((ret-0x00010000)>>10)+0xd800);
                    this.m_tstrbuf[1] = (char)(((ret-0x00010000)&0x3ff)+0xdc00);
                    this.m_tbufpos = 2;
                } else {
                    this.m_tstrbuf[0] = (char)ret;
                    this.m_tbufpos = 1;
                }
                return null;
            } else {
                m_p.format1(this, "E_REFER0", new Character((char)m_next).toString());
                return "?";
            }
        } else {                                // EntityRef
            String n = getName();
            m_lastreference = n;
            int ch = getChar();
            if (';' != ch) {
                m_p.format0(this, "E_REFER1");
            }
            if (null == n) {
                m_p.format1(this, "E_REFER0", new Character((char)ch).toString());
                return "?";
            }
            Entity e = m_p.entityPool.refer(n);
            m_lastentity = e;
            if (null == e) {
                m_p.format1(this, "E_ENTITY0", n);
                return "?";
            }

            if (e.isExternal() && !m_p.doExternalDTD) {
                m_p.format1(this, "E_ENTITY9", n);
                return "?";
            }
            if (e.isNDATA()) {
                m_p.format1(this, "E_ENTITY2", n);
                return "?";
            }

            if ("amp".equals(n) || "lt".equals(n)) {
                return e.getValue();
            }
            try {
                e.load(m_p);
            } catch (Exception ex) {
                e.setValue("[error]");
                m_p.error(this, ex);
            }
            if (!allowexternal && e.isExternal())
                m_p.format1(this, "E_ENTITY7", n);
            return e.getValue();
        }
    }

    String getPIData() throws IOException {
        StringBuffer sb = new StringBuffer(100);
        int state = 0;
        while (0 <= m_next && XMLChar.isChar(m_next) && 2 != state) {
            int ch = getChar();
            sb.append((char)ch);
            if ('?' == ch)
                state = 1;
            else if (1 == state && '>' == ch)
                state = 2;
            else
                state = 0;
        }
        if (2 > sb.length() || 0 > m_next) {
            m_p.format0(this, "E_PI3");
            return null;
        } else {
            sb.setLength(sb.length()-2);        // remove ?>
            return sb.toString();
        }
    }

    String getComment() throws IOException {
        StringBuffer sb = new StringBuffer(100);
        int state = 0;
        while (0 <= m_next && XMLChar.isChar(m_next)) {
            int ch = getChar();
            sb.append((char)ch);
            if (0 == state) {
                if ('-' == ch)
                    state = 1;
            } else if (1 == state) {
                if ('-' == ch)
                    state = 2;
                else
                    state = 0;
            } else if (2 == state) {
                if ('>' == ch)
                    break;
                else {
                    m_p.format0(this, "E_COM2");
                    state = 0;
                }
            }
        }
        if (3 > sb.length() || !sb.toString().endsWith("-->")) {
            m_p.format0(this, "E_COM1");
            return "";
        } else {
            sb.setLength(sb.length()-3);        // remove -->
            return sb.toString();
        }
    }

    String getCDATA() throws IOException {
        StringBuffer sb = new StringBuffer(100);
        int state = 0;
        while (0 <= m_next && XMLChar.isChar(m_next)) {
            int ch = getChar();
            sb.append((char)ch);
            if (0 == state) {
                if (']' == ch)
                    state = 1;
            } else if (1 == state) {
                if (']' == ch)
                    state = 2;
                else
                    state = 0;
            } else if (2 == state) {
                if (']' == ch)
                    state = 2;
                else if ('>' == ch)
                    break;
                else
                    state = 0;
            }

        }
        if (3 > sb.length() || !sb.toString().endsWith("]]>")) {
            m_p.format0(this, "E_CDATA1");
            return sb.toString();
        } else {
            sb.setLength(sb.length()-3);        // remove ]]>
            return sb.toString();
        }
    }

    // ExternalID ::= ('SYSTEM' | 'PUBLIC' S PubidLiteral) S SystemLiteral
    ExternalID parseExternalID() throws IOException {
        return parseExternalID(false);
    }
    ExternalID parseExternalID(boolean allownosystempublic) throws IOException {
        String n = getName();
        if (null != n) {
            String pubid = null;
            if ("SYSTEM".equals(n)) {
            } else if ("PUBLIC".equals(n)) {
                if (!XMLChar.isSpace(m_next)) {
                    m_p.format0(this, "E_EXT0");
                } else
                    skipSpaces();               // S
                                                // PubidLiteral
                int start = m_next;
                if ('"' != start && '\'' != start) {
                    m_p.format0(this, "E_EXT1");
                    skipTagEnd();
                    return null;
                }
                getChar();                      // '"' or '\''
                StringBuffer sb = new StringBuffer(128);
                int ch;
                while ((ch = m_next) != start) {
                    if (0 > ch)  throw new EOFException("com.ibm.xml.parser.Token#parseExternalID(): "+m_p.getString("E_EOF"));
                    if ('a' <= ch && ch <= 'z'   
                        || 'A' <= ch && ch <= 'Z'
                        || '0' <= ch && ch <= '9'
                        || 0 <= " \r\n-'()+,./:=?;!*#@$_%".indexOf(ch)) {
                        sb.append((char)ch);
                    } else {
                        m_p.format1(this, "E_EXT2", new Character((char)ch).toString());
                        skipTagEnd();
                        return null;
                    }
                    getChar();
                }
                getChar();                      // == start
                pubid = sb.toString();
            } else {
                m_p.format1(this, "E_DOCTYPE2", n);
                skipTagEnd();
                return null;
            } // End of parsing PubidLiteral

            if (null != pubid && allownosystempublic) {
                if (XMLChar.isSpace(m_next)) {
                    skipSpaces();
                    if ('>' == m_next) {
                        getChar();
                        return new ExternalID(pubid, null);
                    }
                } else {
                    if ('>' == m_next) {
                        getChar();
                    } else {
                        // S or >
                        m_p.format0(this, "E_EXT5");
                    }
                    return new ExternalID(pubid, null);
                }
            } else {
                if (!XMLChar.isSpace(m_next)) {
                    m_p.format0(this, "E_EXT0");
                } else
                    skipSpaces();               // S
            }
                                                // SystemLiteral
            int start = m_next;
            if ('"' != start && '\'' != start) {
                m_p.format0(this, "E_EXT3");
                skipTagEnd();
                return null;
            }
            getChar();                      // '"' or '\''
            StringBuffer sb = new StringBuffer(128);
            int ch;
            while ((ch = m_next) != start) {
                if (0 > ch)  throw new EOFException("com.ibm.xml.parser.Token#parseExternalID(): "+m_p.getString("E_EOF"));
                                                // valid characters in URL by RFC1808:
                                                //      alpha digit +-.:;/?@&=%$_!*'(),
                sb.append((char)ch);
                getChar();
            }
            getChar();                          // == start

            String uri = sb.toString();
            int iv = Util.getInvalidURIChar(uri);
            if (0 <= iv)
                m_p.format2(this, "E_EXT4", Parser.intstring(uri.charAt(iv)), uri);
            return new ExternalID(pubid, uri);
        }
        return null;
    }

    String[] getNames(String src) {
        StringTokenizer st = new StringTokenizer(src, XMLChar.S_SPACES);
        Vector v = new Vector();
        while (st.hasMoreTokens()) {
            String t = st.nextToken();
            if (!Util.checkName(t)) {
                m_p.format1(this, "E_NAMES0", t);
            } else {
                v.addElement(t);
            }
        }
        String[] as = new String[v.size()];
        v.copyInto(as);
        return as;
    }
    String[] getNmtokens(String src) {
        StringTokenizer st = new StringTokenizer(src, XMLChar.S_SPACES);
        Vector v = new Vector();
        while (st.hasMoreTokens()) {
            String t = st.nextToken();
            if (!Util.checkNmtoken(t)) {
                m_p.format1(this, "E_NMTOK0", t);
            } else {
                v.addElement(t);
            }
        }
        String[] as = new String[v.size()];
        v.copyInto(as);
        return as;
    }

    static final int S_TINITIALBUFSIZE = 1024;
    static final int S_TINCREMENTBUFSIZE = 1024;
    char[] m_tstrbuf = new char[S_TINITIALBUFSIZE];
    int m_tbufpos = 0;
    boolean allspacepcdata;
    boolean includebrabragt;
    final private void ensureTextCapacity() {
        if (m_tbufpos >= m_tstrbuf.length) {
            char[] ac = new char[m_tstrbuf.length+S_TINCREMENTBUFSIZE];
            System.arraycopy(m_tstrbuf, 0, ac, 0, m_tbufpos);
            m_tstrbuf = ac;
        }
    }
    synchronized String getPCData() throws IOException {
        int bufpos = 0;
        this.allspacepcdata = true;
        this.includebrabragt = false;
        int state = 0;

        boolean previouscr = false;
        while (0 <= m_next && XMLChar.isChar(m_next)
               && '<' != m_next && '&' != m_next) {
            int ch = getChar();
            switch (state) {
              case 0:
                if (ch == ']')
                    state = 1;
                break;
              case 1:
                if (ch == ']')
                    state = 2;
                else
                    state = 0;
                break;
              case 2:
                if (ch == ']')
                    state = 2;
                else if (ch == '>') {
                    this.includebrabragt = true;
                    state = 0;
                } else
                    state = 0;
            }

            if (previouscr && '\n' != ch) {
                if (bufpos >= m_tstrbuf.length) {
                    char[] ac = new char[m_tstrbuf.length+S_TINCREMENTBUFSIZE];
                    System.arraycopy(m_tstrbuf, 0, ac, 0, bufpos);
                    m_tstrbuf = ac;
                }
                //ensureTextCapacity();
                m_tstrbuf[bufpos++] = '\n';
            }
            if ('\r' == ch)
                previouscr = true;
            else {
                previouscr = false;
                if (bufpos >= m_tstrbuf.length) {
                    char[] ac = new char[m_tstrbuf.length+S_TINCREMENTBUFSIZE];
                    System.arraycopy(m_tstrbuf, 0, ac, 0, bufpos);
                    m_tstrbuf = ac;
                }
                //ensureTextCapacity();
                m_tstrbuf[bufpos++] = (char)ch;
                if (!XMLChar.isSpace(ch))  this.allspacepcdata = false;
            }
        }
        if (previouscr) {
            ensureTextCapacity();
            m_tstrbuf[bufpos++] = '\n';
        }
        this.m_tbufpos = bufpos;
        
        //return new String(m_tstrbuf, 0, m_tbufpos).intern();
        return null;
    }

    /**
     * Used in Document and DTD.
     */
    String getWhitespaces() throws IOException {
        int ch;
        m_tbufpos = 0;

        boolean previouscr = false;
        while (0 <= m_next && XMLChar.isSpace(m_next)
               && '<' != m_next && '&' != m_next) {
            ch = getChar();

            if (previouscr && '\n' != ch) {
                if (m_tbufpos >= m_tstrbuf.length) {
                    char[] ac = new char[m_tstrbuf.length+S_TINCREMENTBUFSIZE];
                    System.arraycopy(m_tstrbuf, 0, ac, 0, m_tbufpos);
                    m_tstrbuf = ac;
                }
                //ensureTextCapacity();
                m_tstrbuf[m_tbufpos++] = '\n';
            }
            if ('\r' == ch)
                previouscr = true;
            else {
                previouscr = false;
                if (m_tbufpos >= m_tstrbuf.length) {
                    char[] ac = new char[m_tstrbuf.length+S_TINCREMENTBUFSIZE];
                    System.arraycopy(m_tstrbuf, 0, ac, 0, m_tbufpos);
                    m_tstrbuf = ac;
                }
                //ensureTextCapacity();
                m_tstrbuf[m_tbufpos++] = (char)ch;
            }
        }
        if (previouscr) {
            ensureTextCapacity();
            m_tstrbuf[m_tbufpos++] = '\n';
        }

        return null;
    }

    String getPCDataForAttribute(int startchar) throws IOException {
        int ch;
        boolean prevcr = false;
        m_tbufpos = 0;
        while (0 <= m_next && XMLChar.isChar(m_next)
               && startchar != m_next && '&' != m_next) {
            if (startchar >= 0 && '<' == m_next) {
                m_p.format0(this, "E_ATTVAL1");
            }
            ch = getChar();
            // normalize
            if (startchar < 0) {                // whether in entity reference
                if (ch == '\r') {
                    ch = ' ';
                    prevcr = true;
                } else {
                    if (prevcr && ch == '\n') {
                        prevcr = false;
                        continue;
                    }
                    prevcr = false;
                    if (XMLChar.isSpace(ch))
                        ch = ' ';
                }
            } else {
                if (XMLChar.isSpace(ch))
                    ch = ' ';
            }
            ensureTextCapacity();
            m_tstrbuf[m_tbufpos++] = (char)ch;
        }
        return new String(m_tstrbuf, 0, m_tbufpos);
    }
}
