RTEditor/src/main/java/com/onegravity/rteditor/converter/tagsoup/HTMLScanner.java
public void scan(Reader r0, ScanHandler h) throws IOException, SAXException {
        theState = S_PCDATA;
        PushbackReader r;
        if (r0 instanceof PushbackReader) {
            r = (PushbackReader) r0;
        } else if (r0 instanceof BufferedReader) {
            r = new PushbackReader(r0);
        } else {
            r = new PushbackReader(new BufferedReader(r0, 200));
        }

        int firstChar = r.read(); // Remove any leading BOM
        if (firstChar != '\uFEFF')
            unread(r, firstChar);

        while (theState != S_DONE) {
            int c1 = r.read();
            char c = (char) c1;
            boolean is32BitChar = Character.isHighSurrogate(c);
            int c2 = is32BitChar ? r.read() : -1;
            String s = is32BitChar ? new StringBuffer().append(c).append((char) c2).toString() : null;

            // Process control characters
            if (!is32BitChar && c1 >= 0x80 && c1 <= 0x9F)
                c1 = theWinMap[c1 - 0x80];

            if (!is32BitChar && c1 == '\r') {
                c1 = r.read(); // expect LF next
                if (c1 != '\n') {
                    unread(r, c1); // nope
                    c1 = '\n';
                }
            }

            if (!is32BitChar && c1 == '\n') {
                theCurrentLine++;
                theCurrentColumn = 0;
            } else {
                theCurrentColumn++;
            }

            if (!!is32BitChar && !(c1 >= 0x20 || c1 == '\n' || c1 == '\t' || c1 == -1))
                continue;

            // Search state table
            int action = 0;
            for (int i = 0; i < statetable.length; i += 4) {
                if (theState != statetable[i]) {
                    if (action != 0)
                        break;
                    continue;
                }
                if (statetable[i + 1] == 0) {
                    action = statetable[i + 2];
                    theNextState = statetable[i + 3];
                } else if (!is32BitChar && statetable[i + 1] == c1) {
                    action = statetable[i + 2];
                    theNextState = statetable[i + 3];
                    break;
                }
            }
            switch (action) {
                case 0:
                    throw new Error("HTMLScanner can't cope with " + Integer.toString(c1) + " in state " + Integer.toString(theState));
                case A_ADUP:
                    h.adup(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    break;
                case A_ADUP_SAVE:
                    h.adup(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    if (s != null)
                        save(s, c1, h);
                    break;
                case A_ADUP_STAGC:
                    h.adup(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    h.stagc(theOutputBuffer, 0, theSize);
                    break;
                case A_ANAME:
                    h.aname(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    break;
                case A_ANAME_ADUP:
                    h.aname(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    h.adup(theOutputBuffer, 0, theSize);
                    break;
                case A_ANAME_ADUP_STAGC:
                    h.aname(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    h.adup(theOutputBuffer, 0, theSize);
                    h.stagc(theOutputBuffer, 0, theSize);
                    break;
                case A_AVAL:
                    h.aval(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    break;
                case A_AVAL_STAGC:
                    h.aval(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    h.stagc(theOutputBuffer, 0, theSize);
                    break;
                case A_CDATA:
                    mark();
                    // suppress the final "]]" in the buffer
                    if (theSize > 1)
                        theSize -= 2;
                    h.pcdata(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    break;
                case A_ENTITY_START:
                    h.pcdata(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    save(s, c1, h);
                    break;
                case A_ENTITY:
                    mark();
                    if (theState == S_ENT && c == '#') {
                        theNextState = S_NCR;
                        save(s, c1, h);
                        break;
                    } else if (theState == S_NCR && (c == 'x' || c == 'X')) {
                        theNextState = S_XNCR;
                        save(s, c1, h);
                        break;
                    } else if (theState == S_ENT && Character.isLetterOrDigit(c)) {
                        save(s, c1, h);
                        break;
                    } else if (theState == S_NCR && Character.isDigit(c)) {
                        save(s, c1, h);
                        break;
                    } else if (theState == S_XNCR && (Character.isDigit(c) || "abcdefABCDEF".indexOf(c) != -1)) {
                        save(s, c1, h);
                        break;
                    }

                    // The whole entity reference has been collected
                    h.entity(theOutputBuffer, 1, theSize - 1);
                    int ent = h.getEntity();
                    if (ent != 0) {
                        theSize = 0;
                        if (ent >= 0x80 && ent <= 0x9F) {
                            ent = theWinMap[ent - 0x80];
                        }
                        if (ent < 0x20) {
                            // Control becomes space
                            ent = 0x20;
                        } else if (ent >= 0xD800 && ent <= 0xDFFF) {
                            // Surrogates get dropped
                            ent = 0;
                        } else if (ent <= 0xFFFF) {
                            // BMP character
                            save(ent, h);
                        } else {
                            // Astral converted to two surrogates
                            ent -= 0x10000;
                            save((ent >> 10) + 0xD800, h);
                            save((ent & 0x3FF) + 0xDC00, h);
                        }
                        if (is32BitChar || c1 != ';') {
                            if (is32BitChar) {
                                unread(r, c2);
                                theCurrentColumn--;
                            }
                            unread(r, c1);
                            theCurrentColumn--;
                        }
                    } else {
                        if (is32BitChar) {
                            unread(r, c2);
                            theCurrentColumn--;
                        }
                        unread(r, c1);
                        theCurrentColumn--;
                    }
                    theNextState = S_PCDATA;
                    break;
                case A_ETAG:
                    h.etag(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    break;
                case A_DECL:
                    h.decl(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    break;
                case A_GI:
                    h.gi(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    break;
                case A_GI_STAGC:
                    h.gi(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    h.stagc(theOutputBuffer, 0, theSize);
                    break;
                case A_LT:
                    mark();
                    save('<', h);
                    save(s, c1, h);
                    break;
                case A_LT_PCDATA:
                    mark();
                    save('<', h);
                    h.pcdata(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    break;
                case A_PCDATA:
                    mark();
                    h.pcdata(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    break;
                case A_CMNT:
                    mark();
                    h.cmnt(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    break;
                case A_MINUS3:
                    save('-', h);
                    save(' ', h);
                    break;
                case A_MINUS2:
                    save('-', h);
                    save(' ', h);
                    // fall through into A_MINUS
                case A_MINUS:
                    save('-', h);
                    save(s, c1, h);
                    break;
                case A_PI:
                    mark();
                    h.pi(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    break;
                case A_PITARGET:
                    h.pitarget(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    break;
                case A_PITARGET_PI:
                    h.pitarget(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    h.pi(theOutputBuffer, 0, theSize);
                    break;
                case A_SAVE:
                    save(s, c1, h);
                    break;
                case A_SKIP:
                    break;
                case A_SP:
                    save(' ', h);
                    break;
                case A_STAGC:
                    h.stagc(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    break;
                case A_EMPTYTAG:
                    mark();
                    if (theSize > 0)
                        h.gi(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    h.stage(theOutputBuffer, 0, theSize);
                    break;
                case A_UNGET:
                    unread(r, c1);
                    theCurrentColumn--;
                    break;
                case A_UNSAVE_PCDATA:
                    if (theSize > 0)
                        theSize--;
                    h.pcdata(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    break;
                default:
                    throw new Error("Can't process state " + action);
            }
            theState = theNextState;
        }
        h.eof(theOutputBuffer, 0, 0);
    }
public void scan(Reader r0, ScanHandler h) throws IOException, SAXException {
        theState = S_PCDATA;
        PushbackReader r;
        if (r0 instanceof PushbackReader) {
            r = (PushbackReader) r0;
        } else if (r0 instanceof BufferedReader) {
            r = new PushbackReader(r0);
        } else {
            r = new PushbackReader(new BufferedReader(r0, 200));
        }

        int firstChar = r.read(); // Remove any leading BOM
        if (firstChar != '\uFEFF')
            unread(r, firstChar);

        while (theState != S_DONE) {
            int c1 = r.read();
            char c = (char) c1;
            boolean is32BitChar = Character.isHighSurrogate(c);
            int c2 = is32BitChar ? r.read() : -1;
            String s = is32BitChar ? new StringBuffer().append(c).append((char) c2).toString() : null;

            // Process control characters
            if (!is32BitChar && c1 >= 0x80 && c1 <= 0x9F)
                c1 = theWinMap[c1 - 0x80];

            if (!is32BitChar && c1 == '\r') {
                c1 = r.read(); // expect LF next
                if (c1 != '\n') {
                    unread(r, c1); // nope
                    c1 = '\n';
                }
            }

            if (!is32BitChar && c1 == '\n') {
                theCurrentLine++;
                theCurrentColumn = 0;
            } else {
                theCurrentColumn++;
            }

            if (!!is32BitChar && !(c1 >= 0x20 || c1 == '\n' || c1 == '\t' || c1 == -1))
                continue;

            // Search state table
            int action = 0;
            for (int i = 0; i < statetable.length; i += 4) {
                if (theState != statetable[i]) {
                    if (action != 0)
                        break;
                    continue;
                }
                if (statetable[i + 1] == 0) {
                    action = statetable[i + 2];
                    theNextState = statetable[i + 3];
                } else if (!is32BitChar && statetable[i + 1] == c1) {
                    action = statetable[i + 2];
                    theNextState = statetable[i + 3];
                    break;
                }
            }
            switch (action) {
                case 0:
                    throw new Error("HTMLScanner can't cope with " + Integer.toString(c1) + " in state " + Integer.toString(theState));
                case A_ADUP:
                    h.adup(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    break;
                case A_ADUP_SAVE:
                    h.adup(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    if (s != null)
                        save(s, c1, h);
                    break;
                case A_ADUP_STAGC:
                    h.adup(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    h.stagc(theOutputBuffer, 0, theSize);
                    break;
                case A_ANAME:
                    h.aname(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    break;
                case A_ANAME_ADUP:
                    h.aname(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    h.adup(theOutputBuffer, 0, theSize);
                    break;
                case A_ANAME_ADUP_STAGC:
                    h.aname(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    h.adup(theOutputBuffer, 0, theSize);
                    h.stagc(theOutputBuffer, 0, theSize);
                    break;
                case A_AVAL:
                    h.aval(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    break;
                case A_AVAL_STAGC:
                    h.aval(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    h.stagc(theOutputBuffer, 0, theSize);
                    break;
                case A_CDATA:
                    mark();
                    // suppress the final "]]" in the buffer
                    if (theSize > 1)
                        theSize -= 2;
                    h.pcdata(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    break;
                case A_ENTITY_START:
                    h.pcdata(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    save(s, c1, h);
                    break;
                case A_ENTITY:
                    mark();
                    if (theState == S_ENT && c == '#') {
                        theNextState = S_NCR;
                        save(s, c1, h);
                        break;
                    } else if (theState == S_NCR && (c == 'x' || c == 'X')) {
                        theNextState = S_XNCR;
                        save(s, c1, h);
                        break;
                    } else if (theState == S_ENT && Character.isLetterOrDigit(c)) {
                        save(s, c1, h);
                        break;
                    } else if (theState == S_NCR && Character.isDigit(c)) {
                        save(s, c1, h);
                        break;
                    } else if (theState == S_XNCR && (Character.isDigit(c) || "abcdefABCDEF".indexOf(c) != -1)) {
                        save(s, c1, h);
                        break;
                    }

                    // The whole entity reference has been collected
                    h.entity(theOutputBuffer, 1, theSize - 1);
                    int ent = h.getEntity();
                    if (ent != 0) {
                        theSize = 0;
                        if (ent >= 0x80 && ent <= 0x9F) {
                            ent = theWinMap[ent - 0x80];
                        }
                        if (ent < 0x20) {
                            // Control becomes space
                            ent = 0x20;
                        } else if (ent >= 0xD800 && ent <= 0xDFFF) {
                            // Surrogates get dropped
                            ent = 0;
                        } else if (ent <= 0xFFFF) {
                            // BMP character
                            save(ent, h);
                        } else {
                            // Astral converted to two surrogates
                            ent -= 0x10000;
                            save((ent >> 10) + 0xD800, h);
                            save((ent & 0x3FF) + 0xDC00, h);
                        }
                        if (is32BitChar || c1 != ';') {
                            if (is32BitChar) {
                                unread(r, c2);
                                theCurrentColumn--;
                            }
                            unread(r, c1);
                            theCurrentColumn--;
                        }
                    } else {
                        if (is32BitChar) {
                            unread(r, c2);
                            theCurrentColumn--;
                        }
                        unread(r, c1);
                        theCurrentColumn--;
                    }
                    theNextState = S_PCDATA;
                    break;
                case A_ETAG:
                    h.etag(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    break;
                case A_DECL:
                    h.decl(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    break;
                case A_GI:
                    h.gi(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    break;
                case A_GI_STAGC:
                    h.gi(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    h.stagc(theOutputBuffer, 0, theSize);
                    break;
                case A_LT:
                    mark();
                    save('<', h);
                    save(s, c1, h);
                    break;
                case A_LT_PCDATA:
                    mark();
                    save('<', h);
                    h.pcdata(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    break;
                case A_PCDATA:
                    mark();
                    h.pcdata(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    break;
                case A_CMNT:
                    mark();
                    h.cmnt(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    break;
                case A_MINUS3:
                    save('-', h);
                    save(' ', h);
                    break;
                case A_MINUS2:
                    save('-', h);
                    save(' ', h);
                    // fall through into A_MINUS
                case A_MINUS:
                    save('-', h);
                    save(s, c1, h);
                    break;
                case A_PI:
                    mark();
                    h.pi(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    break;
                case A_PITARGET:
                    h.pitarget(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    break;
                case A_PITARGET_PI:
                    h.pitarget(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    h.pi(theOutputBuffer, 0, theSize);
                    break;
                case A_SAVE:
                    save(s, c1, h);
                    break;
                case A_SKIP:
                    break;
                case A_SP:
                    save(' ', h);
                    break;
                case A_STAGC:
                    h.stagc(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    break;
                case A_EMPTYTAG:
                    mark();
                    if (theSize > 0)
                        h.gi(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    h.stage(theOutputBuffer, 0, theSize);
                    break;
                case A_UNGET:
                    unread(r, c1);
                    theCurrentColumn--;
                    break;
                case A_UNSAVE_PCDATA:
                    if (theSize > 0)
                        theSize--;
                    h.pcdata(theOutputBuffer, 0, theSize);
                    theSize = 0;
                    break;
                default:
                    throw new Error("Can't process state " + action);
            }
            theState = theNextState;
        }
        h.eof(theOutputBuffer, 0, 0);
    }
Scan HTML source, reporting lexical events.

@param r0 Reader that provides characters
@param h  ScanHandler that accepts lexical events.