private char readCh() throws SAXException, IOException {
        // As long as there's nothing in the
        // read buffer, try reading more data
        // (for an external entity) or popping
        // the entity stack (for either).
        fillBuffer();

        char c = readBuffer[readBufferPos++];
        advanceLocation();
        // copied from fi.iki.hsivonen.htmlparser
        if ((c & 0xFC00) == 0xDC00) {
            // Got a low surrogate. See if prev was high surrogate
            if ((prev & 0xFC00) == 0xD800) {
                int intVal = (prev << 10) + c + SURROGATE_OFFSET;
                if (isNonCharacter(intVal)) {
                    handler.warn("Astral non-character.");
                }
                if (isAstralPrivateUse(intVal)) {
                    warnAboutPrivateUseChar();
                }
            } else {
                if (prev != 0) {
                    // the prev == 0 situation is bogus, but better not to fail
                    fatal("Unmatched low surrogate.");
                }
            }
            prev = c;
        } else {
            // see if there was a lone high surrogate
            if ((prev & 0xFC00) == 0xD800) {
                fatal("Unmatched high surrogate.");
            }
        }

        if (c == '\n') {
            nextCharOnNewLine = true;
        } else {
            if (c == '<') {
                /* the most common return to parseContent () ... NOP */
            } else if ((((c < 0x0020) && (c != '\t') && (c != '\r')) || (c > 0xFFFD))
                    || ((c >= 0x007f) && (c <= 0x009f) && (c != 0x0085) && (xmlVersion == XML_11))) {
                fatal("illegal XML character U+" + Integer.toHexString(c));
            } else if ((c >= '\u007F') && (c <= '\u009F')) // 2006-04-25 hsivonen
            {
                handler.warn("Saw a control character: U+00"
                        + Integer.toHexString(c) + ".");
            }

            if (isPrivateUse(c)) {
                warnAboutPrivateUseChar();
            }
            // If we're in the DTD and in a context where PEs get expanded,
            // do so ... 1/14/2000 errata identify those contexts. There
            // are also spots in the internal subset where PE refs are fatal
            // errors, hence yet another flag.
            else if ((c == '%') && expandPE) {
                if (peIsError) {
                    fatal("PE reference within decl in internal subset.");
                }
                parsePEReference();
                return readCh();
            }
        }

        return c;
    }
Read a single character from the readBuffer.
<p>
The readDataChunk () method maintains the buffer.
<p>
If we hit the end of an entity, try to pop the stack and keep going.
<p>
(This approach doesn't really enforce XML's rules about entity
boundaries, but this is not currently a validating parser).
<p>
This routine also attempts to keep track of the current position in
external entities, but it's not entirely accurate.

@return The next available input character.
@see #unread (char)
@see #readDataChunk
@see #readBuffer
@see #line
@return The next character from the current input source.
src/nu/validator/gnu/xml/aelfred2/XmlParser.java