| 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 */  | 
 | 
package javax.swing.text.html;  | 
 | 
 | 
 | 
import java.io.*;  | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 */  | 
 | 
class CSSParser { | 
 | 
    // Parsing something like the following:  | 
 | 
    // (@rule | ruleset | block)*  | 
 | 
    //  | 
 | 
    // @rule       (block | identifier)*; (block with {} ends @rule) | 
 | 
    // block       matching [] () {} (that is, [()] is a block, [(){}{[]}] | 
 | 
    //                                is a block, ()[] is two blocks)  | 
 | 
    // identifier  "*" | '*' | anything but a [](){} and whitespace | 
 | 
    //  | 
 | 
    // ruleset     selector decblock  | 
 | 
    // selector    (identifier | (block, except block '{}') )* | 
 | 
    // declblock   declaration* block*  | 
 | 
    // declaration (identifier* stopping when identifier ends with :)  | 
 | 
    //             (identifier* stopping when identifier ends with ;)  | 
 | 
    //  | 
 | 
    // comments /* */ can appear any where, and are stripped.  | 
 | 
 | 
 | 
 | 
 | 
    // identifier - letters, digits, dashes and escaped characters  | 
 | 
    // block starts with { ends with matching }, () [] and {} always occur | 
 | 
    //   in matching pairs, '' and "" also occur in pairs, except " may be  | 
 | 
 | 
 | 
 | 
 | 
      | 
 | 
    private static final int   IDENTIFIER = 1;  | 
 | 
    private static final int   BRACKET_OPEN = 2;  | 
 | 
    private static final int   BRACKET_CLOSE = 3;  | 
 | 
    private static final int   BRACE_OPEN = 4;  | 
 | 
    private static final int   BRACE_CLOSE = 5;  | 
 | 
    private static final int   PAREN_OPEN = 6;  | 
 | 
    private static final int   PAREN_CLOSE = 7;  | 
 | 
    private static final int   END = -1;  | 
 | 
 | 
 | 
    private static final char[] charMapping = { 0, 0, '[', ']', '{', '}', '(', | 
 | 
                                               ')', 0};  | 
 | 
 | 
 | 
 | 
 | 
      | 
 | 
    private boolean        didPushChar;  | 
 | 
      | 
 | 
    private int            pushedChar;  | 
 | 
      | 
 | 
    private StringBuffer   unitBuffer;  | 
 | 
      | 
 | 
    private int[]          unitStack;  | 
 | 
      | 
 | 
    private int            stackCount;  | 
 | 
      | 
 | 
    private Reader         reader;  | 
 | 
      | 
 | 
    private boolean        encounteredRuleSet;  | 
 | 
      | 
 | 
    private CSSParserCallback callback;  | 
 | 
      | 
 | 
    private char[]         tokenBuffer;  | 
 | 
      | 
 | 
    private int            tokenBufferLength;  | 
 | 
      | 
 | 
    private boolean        readWS;  | 
 | 
 | 
 | 
 | 
 | 
      | 
 | 
    static interface CSSParserCallback { | 
 | 
          | 
 | 
        void handleImport(String importString);  | 
 | 
        // There is currently no way to distinguish between '"foo,"' and  | 
 | 
        // 'foo,'. But this generally isn't valid CSS. If it becomes  | 
 | 
        // a problem, handleSelector will have to be told if the string is  | 
 | 
          | 
 | 
        void handleSelector(String selector);  | 
 | 
        void startRule();  | 
 | 
        // Property names are mapped to lower case before being passed to  | 
 | 
          | 
 | 
        void handleProperty(String property);  | 
 | 
        void handleValue(String value);  | 
 | 
        void endRule();  | 
 | 
    }  | 
 | 
 | 
 | 
    CSSParser() { | 
 | 
        unitStack = new int[2];  | 
 | 
        tokenBuffer = new char[80];  | 
 | 
        unitBuffer = new StringBuffer();  | 
 | 
    }  | 
 | 
 | 
 | 
    void parse(Reader reader, CSSParserCallback callback,  | 
 | 
               boolean inRule) throws IOException { | 
 | 
        this.callback = callback;  | 
 | 
        stackCount = tokenBufferLength = 0;  | 
 | 
        this.reader = reader;  | 
 | 
        encounteredRuleSet = false;  | 
 | 
        try { | 
 | 
            if (inRule) { | 
 | 
                parseDeclarationBlock();  | 
 | 
            }  | 
 | 
            else { | 
 | 
                while (getNextStatement());  | 
 | 
            }  | 
 | 
        } finally { | 
 | 
            callback = null;  | 
 | 
            reader = null;  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    private boolean getNextStatement() throws IOException { | 
 | 
        unitBuffer.setLength(0);  | 
 | 
 | 
 | 
        int token = nextToken((char)0);  | 
 | 
 | 
 | 
        switch (token) { | 
 | 
        case IDENTIFIER:  | 
 | 
            if (tokenBufferLength > 0) { | 
 | 
                if (tokenBuffer[0] == '@') { | 
 | 
                    parseAtRule();  | 
 | 
                }  | 
 | 
                else { | 
 | 
                    encounteredRuleSet = true;  | 
 | 
                    parseRuleSet();  | 
 | 
                }  | 
 | 
            }  | 
 | 
            return true;  | 
 | 
        case BRACKET_OPEN:  | 
 | 
        case BRACE_OPEN:  | 
 | 
        case PAREN_OPEN:  | 
 | 
            parseTillClosed(token);  | 
 | 
            return true;  | 
 | 
 | 
 | 
        case BRACKET_CLOSE:  | 
 | 
        case BRACE_CLOSE:  | 
 | 
        case PAREN_CLOSE:  | 
 | 
              | 
 | 
            throw new RuntimeException("Unexpected top level block close"); | 
 | 
 | 
 | 
        case END:  | 
 | 
            return false;  | 
 | 
        }  | 
 | 
        return true;  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
     */  | 
 | 
    private void parseAtRule() throws IOException { | 
 | 
          | 
 | 
        boolean        done = false;  | 
 | 
        boolean isImport = (tokenBufferLength == 7 &&  | 
 | 
                            tokenBuffer[0] == '@' && tokenBuffer[1] == 'i' &&  | 
 | 
                            tokenBuffer[2] == 'm' && tokenBuffer[3] == 'p' &&  | 
 | 
                            tokenBuffer[4] == 'o' && tokenBuffer[5] == 'r' &&  | 
 | 
                            tokenBuffer[6] == 't');  | 
 | 
 | 
 | 
        unitBuffer.setLength(0);  | 
 | 
        while (!done) { | 
 | 
            int       nextToken = nextToken(';'); | 
 | 
 | 
 | 
            switch (nextToken) { | 
 | 
            case IDENTIFIER:  | 
 | 
                if (tokenBufferLength > 0 &&  | 
 | 
                    tokenBuffer[tokenBufferLength - 1] == ';') { | 
 | 
                    --tokenBufferLength;  | 
 | 
                    done = true;  | 
 | 
                }  | 
 | 
                if (tokenBufferLength > 0) { | 
 | 
                    if (unitBuffer.length() > 0 && readWS) { | 
 | 
                        unitBuffer.append(' '); | 
 | 
                    }  | 
 | 
                    unitBuffer.append(tokenBuffer, 0, tokenBufferLength);  | 
 | 
                }  | 
 | 
                break;  | 
 | 
 | 
 | 
            case BRACE_OPEN:  | 
 | 
                if (unitBuffer.length() > 0 && readWS) { | 
 | 
                    unitBuffer.append(' '); | 
 | 
                }  | 
 | 
                unitBuffer.append(charMapping[nextToken]);  | 
 | 
                parseTillClosed(nextToken);  | 
 | 
                done = true;  | 
 | 
                  | 
 | 
                { | 
 | 
                    int nextChar = readWS();  | 
 | 
                    if (nextChar != -1 && nextChar != ';') { | 
 | 
                        pushChar(nextChar);  | 
 | 
                    }  | 
 | 
                }  | 
 | 
                break;  | 
 | 
 | 
 | 
            case BRACKET_OPEN: case PAREN_OPEN:  | 
 | 
                unitBuffer.append(charMapping[nextToken]);  | 
 | 
                parseTillClosed(nextToken);  | 
 | 
                break;  | 
 | 
 | 
 | 
            case BRACKET_CLOSE: case BRACE_CLOSE: case PAREN_CLOSE:  | 
 | 
                throw new RuntimeException("Unexpected close in @ rule"); | 
 | 
 | 
 | 
            case END:  | 
 | 
                done = true;  | 
 | 
                break;  | 
 | 
            }  | 
 | 
        }  | 
 | 
        if (isImport && !encounteredRuleSet) { | 
 | 
            callback.handleImport(unitBuffer.toString());  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    private void parseRuleSet() throws IOException { | 
 | 
        if (parseSelectors()) { | 
 | 
            callback.startRule();  | 
 | 
            parseDeclarationBlock();  | 
 | 
            callback.endRule();  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    private boolean parseSelectors() throws IOException { | 
 | 
          | 
 | 
        int       nextToken;  | 
 | 
 | 
 | 
        if (tokenBufferLength > 0) { | 
 | 
            callback.handleSelector(new String(tokenBuffer, 0,  | 
 | 
                                               tokenBufferLength));  | 
 | 
        }  | 
 | 
 | 
 | 
        unitBuffer.setLength(0);  | 
 | 
        for (;;) { | 
 | 
            while ((nextToken = nextToken((char)0)) == IDENTIFIER) { | 
 | 
                if (tokenBufferLength > 0) { | 
 | 
                    callback.handleSelector(new String(tokenBuffer, 0,  | 
 | 
                                                       tokenBufferLength));  | 
 | 
                }  | 
 | 
            }  | 
 | 
            switch (nextToken) { | 
 | 
            case BRACE_OPEN:  | 
 | 
                return true;  | 
 | 
 | 
 | 
            case BRACKET_OPEN: case PAREN_OPEN:  | 
 | 
                parseTillClosed(nextToken);  | 
 | 
                // Not too sure about this, how we handle this isn't very  | 
 | 
                  | 
 | 
                unitBuffer.setLength(0);  | 
 | 
                break;  | 
 | 
 | 
 | 
            case BRACKET_CLOSE: case BRACE_CLOSE: case PAREN_CLOSE:  | 
 | 
                throw new RuntimeException("Unexpected block close in selector"); | 
 | 
 | 
 | 
            case END:  | 
 | 
                  | 
 | 
                return false;  | 
 | 
            }  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    private void parseDeclarationBlock() throws IOException { | 
 | 
        for (;;) { | 
 | 
            int token = parseDeclaration();  | 
 | 
            switch (token) { | 
 | 
            case END: case BRACE_CLOSE:  | 
 | 
                return;  | 
 | 
 | 
 | 
            case BRACKET_CLOSE: case PAREN_CLOSE:  | 
 | 
                  | 
 | 
                throw new RuntimeException("Unexpected close in declaration block"); | 
 | 
            case IDENTIFIER:  | 
 | 
                break;  | 
 | 
            }  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
    /**  | 
 | 
     * Parses a single declaration, which is an identifier a : and another  | 
 | 
     * identifier. This returns the last token seen.  | 
 | 
     */  | 
 | 
      | 
 | 
    private int parseDeclaration() throws IOException { | 
 | 
        int    token;  | 
 | 
 | 
 | 
        if ((token = parseIdentifiers(':', false)) != IDENTIFIER) { | 
 | 
            return token;  | 
 | 
        }  | 
 | 
          | 
 | 
        for (int counter = unitBuffer.length() - 1; counter >= 0; counter--) { | 
 | 
            unitBuffer.setCharAt(counter, Character.toLowerCase  | 
 | 
                                 (unitBuffer.charAt(counter)));  | 
 | 
        }  | 
 | 
        callback.handleProperty(unitBuffer.toString());  | 
 | 
 | 
 | 
        token = parseIdentifiers(';', true); | 
 | 
        callback.handleValue(unitBuffer.toString());  | 
 | 
        return token;  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    private int parseIdentifiers(char extraChar,  | 
 | 
                                 boolean wantsBlocks) throws IOException { | 
 | 
        int   nextToken;  | 
 | 
        int   ubl;  | 
 | 
 | 
 | 
        unitBuffer.setLength(0);  | 
 | 
        for (;;) { | 
 | 
            nextToken = nextToken(extraChar);  | 
 | 
 | 
 | 
            switch (nextToken) { | 
 | 
            case IDENTIFIER:  | 
 | 
                if (tokenBufferLength > 0) { | 
 | 
                    if (tokenBuffer[tokenBufferLength - 1] == extraChar) { | 
 | 
                        if (--tokenBufferLength > 0) { | 
 | 
                            if (readWS && unitBuffer.length() > 0) { | 
 | 
                                unitBuffer.append(' '); | 
 | 
                            }  | 
 | 
                            unitBuffer.append(tokenBuffer, 0,  | 
 | 
                                              tokenBufferLength);  | 
 | 
                        }  | 
 | 
                        return IDENTIFIER;  | 
 | 
                    }  | 
 | 
                    if (readWS && unitBuffer.length() > 0) { | 
 | 
                        unitBuffer.append(' '); | 
 | 
                    }  | 
 | 
                    unitBuffer.append(tokenBuffer, 0, tokenBufferLength);  | 
 | 
                }  | 
 | 
                break;  | 
 | 
 | 
 | 
            case BRACKET_OPEN:  | 
 | 
            case BRACE_OPEN:  | 
 | 
            case PAREN_OPEN:  | 
 | 
                ubl = unitBuffer.length();  | 
 | 
                if (wantsBlocks) { | 
 | 
                    unitBuffer.append(charMapping[nextToken]);  | 
 | 
                }  | 
 | 
                parseTillClosed(nextToken);  | 
 | 
                if (!wantsBlocks) { | 
 | 
                    unitBuffer.setLength(ubl);  | 
 | 
                }  | 
 | 
                break;  | 
 | 
 | 
 | 
            case BRACE_CLOSE:  | 
 | 
                // No need to throw for these two, we return token and  | 
 | 
                  | 
 | 
            case BRACKET_CLOSE:  | 
 | 
            case PAREN_CLOSE:  | 
 | 
            case END:  | 
 | 
                  | 
 | 
                return nextToken;  | 
 | 
            }  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    private void parseTillClosed(int openToken) throws IOException { | 
 | 
        int       nextToken;  | 
 | 
        boolean   done = false;  | 
 | 
 | 
 | 
        startBlock(openToken);  | 
 | 
        while (!done) { | 
 | 
            nextToken = nextToken((char)0);  | 
 | 
            switch (nextToken) { | 
 | 
            case IDENTIFIER:  | 
 | 
                if (unitBuffer.length() > 0 && readWS) { | 
 | 
                    unitBuffer.append(' '); | 
 | 
                }  | 
 | 
                if (tokenBufferLength > 0) { | 
 | 
                    unitBuffer.append(tokenBuffer, 0, tokenBufferLength);  | 
 | 
                }  | 
 | 
                break;  | 
 | 
 | 
 | 
            case BRACKET_OPEN: case BRACE_OPEN: case PAREN_OPEN:  | 
 | 
                if (unitBuffer.length() > 0 && readWS) { | 
 | 
                    unitBuffer.append(' '); | 
 | 
                }  | 
 | 
                unitBuffer.append(charMapping[nextToken]);  | 
 | 
                startBlock(nextToken);  | 
 | 
                break;  | 
 | 
 | 
 | 
            case BRACKET_CLOSE: case BRACE_CLOSE: case PAREN_CLOSE:  | 
 | 
                if (unitBuffer.length() > 0 && readWS) { | 
 | 
                    unitBuffer.append(' '); | 
 | 
                }  | 
 | 
                unitBuffer.append(charMapping[nextToken]);  | 
 | 
                endBlock(nextToken);  | 
 | 
                if (!inBlock()) { | 
 | 
                    done = true;  | 
 | 
                }  | 
 | 
                break;  | 
 | 
 | 
 | 
            case END:  | 
 | 
                  | 
 | 
                throw new RuntimeException("Unclosed block"); | 
 | 
            }  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
     */  | 
 | 
    private int nextToken(char idChar) throws IOException { | 
 | 
        readWS = false;  | 
 | 
 | 
 | 
        int     nextChar = readWS();  | 
 | 
 | 
 | 
        switch (nextChar) { | 
 | 
        case '\'':  | 
 | 
            readTill('\''); | 
 | 
            if (tokenBufferLength > 0) { | 
 | 
                tokenBufferLength--;  | 
 | 
            }  | 
 | 
            return IDENTIFIER;  | 
 | 
        case '"':  | 
 | 
            readTill('"'); | 
 | 
            if (tokenBufferLength > 0) { | 
 | 
                tokenBufferLength--;  | 
 | 
            }  | 
 | 
            return IDENTIFIER;  | 
 | 
        case '[':  | 
 | 
            return BRACKET_OPEN;  | 
 | 
        case ']':  | 
 | 
            return BRACKET_CLOSE;  | 
 | 
        case '{': | 
 | 
            return BRACE_OPEN;  | 
 | 
        case '}':  | 
 | 
            return BRACE_CLOSE;  | 
 | 
        case '(': | 
 | 
            return PAREN_OPEN;  | 
 | 
        case ')':  | 
 | 
            return PAREN_CLOSE;  | 
 | 
        case -1:  | 
 | 
            return END;  | 
 | 
        default:  | 
 | 
            pushChar(nextChar);  | 
 | 
            getIdentifier(idChar);  | 
 | 
            return IDENTIFIER;  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
    /**  | 
 | 
     * Gets an identifier, returning true if the length of the string is greater than 0,  | 
 | 
     * stopping when <code>stopChar</code>, whitespace, or one of {}()[] is | 
 | 
     * hit.  | 
 | 
     */  | 
 | 
    // NOTE: this could be combined with readTill, as they contain somewhat  | 
 | 
      | 
 | 
    private boolean getIdentifier(char stopChar) throws IOException { | 
 | 
        boolean lastWasEscape = false;  | 
 | 
        boolean done = false;  | 
 | 
        int escapeCount = 0;  | 
 | 
        int escapeChar = 0;  | 
 | 
        int nextChar;  | 
 | 
        int intStopChar = (int)stopChar;  | 
 | 
        // 1 for '\', 2 for valid escape char [0-9a-fA-F], 3 for  | 
 | 
          | 
 | 
        short type;  | 
 | 
        int escapeOffset = 0;  | 
 | 
 | 
 | 
        tokenBufferLength = 0;  | 
 | 
        while (!done) { | 
 | 
            nextChar = readChar();  | 
 | 
            switch (nextChar) { | 
 | 
            case '\\':  | 
 | 
                type = 1;  | 
 | 
                break;  | 
 | 
 | 
 | 
            case '0': case '1': case '2': case '3': case '4': case '5':  | 
 | 
            case '6': case '7': case '8': case '9':  | 
 | 
                type = 2;  | 
 | 
                escapeOffset = nextChar - '0';  | 
 | 
                break;  | 
 | 
 | 
 | 
            case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':  | 
 | 
                type = 2;  | 
 | 
                escapeOffset = nextChar - 'a' + 10;  | 
 | 
                break;  | 
 | 
 | 
 | 
            case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':  | 
 | 
                type = 2;  | 
 | 
                escapeOffset = nextChar - 'A' + 10;  | 
 | 
                break;  | 
 | 
 | 
 | 
            case '\'': case '"': case '[': case ']': case '{': case '}': | 
 | 
            case '(': case ')': | 
 | 
            case ' ': case '\n': case '\t': case '\r':  | 
 | 
                type = 3;  | 
 | 
                break;  | 
 | 
 | 
 | 
            case '/':  | 
 | 
                type = 4;  | 
 | 
                break;  | 
 | 
 | 
 | 
            case -1:  | 
 | 
                  | 
 | 
                done = true;  | 
 | 
                type = 0;  | 
 | 
                break;  | 
 | 
 | 
 | 
            default:  | 
 | 
                type = 0;  | 
 | 
                break;  | 
 | 
            }  | 
 | 
            if (lastWasEscape) { | 
 | 
                if (type == 2) { | 
 | 
                      | 
 | 
                    escapeChar = escapeChar * 16 + escapeOffset;  | 
 | 
                    if (++escapeCount == 4) { | 
 | 
                        lastWasEscape = false;  | 
 | 
                        append((char)escapeChar);  | 
 | 
                    }  | 
 | 
                }  | 
 | 
                else { | 
 | 
                      | 
 | 
                    lastWasEscape = false;  | 
 | 
                    if (escapeCount > 0) { | 
 | 
                        append((char)escapeChar);  | 
 | 
                          | 
 | 
                        pushChar(nextChar);  | 
 | 
                    }  | 
 | 
                    else if (!done) { | 
 | 
                        append((char)nextChar);  | 
 | 
                    }  | 
 | 
                }  | 
 | 
            }  | 
 | 
            else if (!done) { | 
 | 
                if (type == 1) { | 
 | 
                    lastWasEscape = true;  | 
 | 
                    escapeChar = escapeCount = 0;  | 
 | 
                }  | 
 | 
                else if (type == 3) { | 
 | 
                    done = true;  | 
 | 
                    pushChar(nextChar);  | 
 | 
                }  | 
 | 
                else if (type == 4) { | 
 | 
                      | 
 | 
                    nextChar = readChar();  | 
 | 
                    if (nextChar == '*') { | 
 | 
                        done = true;  | 
 | 
                        readComment();  | 
 | 
                        readWS = true;  | 
 | 
                    }  | 
 | 
                    else { | 
 | 
                        append('/'); | 
 | 
                        if (nextChar == -1) { | 
 | 
                            done = true;  | 
 | 
                        }  | 
 | 
                        else { | 
 | 
                            pushChar(nextChar);  | 
 | 
                        }  | 
 | 
                    }  | 
 | 
                }  | 
 | 
                else { | 
 | 
                    append((char)nextChar);  | 
 | 
                    if (nextChar == intStopChar) { | 
 | 
                        done = true;  | 
 | 
                    }  | 
 | 
                }  | 
 | 
            }  | 
 | 
        }  | 
 | 
        return (tokenBufferLength > 0);  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    private void readTill(char stopChar) throws IOException { | 
 | 
        boolean lastWasEscape = false;  | 
 | 
        int escapeCount = 0;  | 
 | 
        int escapeChar = 0;  | 
 | 
        int nextChar;  | 
 | 
        boolean done = false;  | 
 | 
        int intStopChar = (int)stopChar;  | 
 | 
          | 
 | 
        short type;  | 
 | 
        int escapeOffset = 0;  | 
 | 
 | 
 | 
        tokenBufferLength = 0;  | 
 | 
        while (!done) { | 
 | 
            nextChar = readChar();  | 
 | 
            switch (nextChar) { | 
 | 
            case '\\':  | 
 | 
                type = 1;  | 
 | 
                break;  | 
 | 
 | 
 | 
            case '0': case '1': case '2': case '3': case '4':case '5':  | 
 | 
            case '6': case '7': case '8': case '9':  | 
 | 
                type = 2;  | 
 | 
                escapeOffset = nextChar - '0';  | 
 | 
                break;  | 
 | 
 | 
 | 
            case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':  | 
 | 
                type = 2;  | 
 | 
                escapeOffset = nextChar - 'a' + 10;  | 
 | 
                break;  | 
 | 
 | 
 | 
            case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':  | 
 | 
                type = 2;  | 
 | 
                escapeOffset = nextChar - 'A' + 10;  | 
 | 
                break;  | 
 | 
 | 
 | 
            case -1:  | 
 | 
                  | 
 | 
                throw new RuntimeException("Unclosed " + stopChar); | 
 | 
 | 
 | 
            default:  | 
 | 
                type = 0;  | 
 | 
                break;  | 
 | 
            }  | 
 | 
            if (lastWasEscape) { | 
 | 
                if (type == 2) { | 
 | 
                      | 
 | 
                    escapeChar = escapeChar * 16 + escapeOffset;  | 
 | 
                    if (++escapeCount == 4) { | 
 | 
                        lastWasEscape = false;  | 
 | 
                        append((char)escapeChar);  | 
 | 
                    }  | 
 | 
                }  | 
 | 
                else { | 
 | 
                      | 
 | 
                    if (escapeCount > 0) { | 
 | 
                        append((char)escapeChar);  | 
 | 
                        if (type == 1) { | 
 | 
                            lastWasEscape = true;  | 
 | 
                            escapeChar = escapeCount = 0;  | 
 | 
                        }  | 
 | 
                        else { | 
 | 
                            if (nextChar == intStopChar) { | 
 | 
                                done = true;  | 
 | 
                            }  | 
 | 
                            append((char)nextChar);  | 
 | 
                            lastWasEscape = false;  | 
 | 
                        }  | 
 | 
                    }  | 
 | 
                    else { | 
 | 
                        append((char)nextChar);  | 
 | 
                        lastWasEscape = false;  | 
 | 
                    }  | 
 | 
                }  | 
 | 
            }  | 
 | 
            else if (type == 1) { | 
 | 
                lastWasEscape = true;  | 
 | 
                escapeChar = escapeCount = 0;  | 
 | 
            }  | 
 | 
            else { | 
 | 
                if (nextChar == intStopChar) { | 
 | 
                    done = true;  | 
 | 
                }  | 
 | 
                append((char)nextChar);  | 
 | 
            }  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
    private void append(char character) { | 
 | 
        if (tokenBufferLength == tokenBuffer.length) { | 
 | 
            char[] newBuffer = new char[tokenBuffer.length * 2];  | 
 | 
            System.arraycopy(tokenBuffer, 0, newBuffer, 0, tokenBuffer.length);  | 
 | 
            tokenBuffer = newBuffer;  | 
 | 
        }  | 
 | 
        tokenBuffer[tokenBufferLength++] = character;  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
     */  | 
 | 
    private void readComment() throws IOException { | 
 | 
        int nextChar;  | 
 | 
 | 
 | 
        for(;;) { | 
 | 
            nextChar = readChar();  | 
 | 
            switch (nextChar) { | 
 | 
            case -1:  | 
 | 
                throw new RuntimeException("Unclosed comment"); | 
 | 
            case '*':  | 
 | 
                nextChar = readChar();  | 
 | 
                if (nextChar == '/') { | 
 | 
                    return;  | 
 | 
                }  | 
 | 
                else if (nextChar == -1) { | 
 | 
                    throw new RuntimeException("Unclosed comment"); | 
 | 
                }  | 
 | 
                else { | 
 | 
                    pushChar(nextChar);  | 
 | 
                }  | 
 | 
                break;  | 
 | 
            default:  | 
 | 
                break;  | 
 | 
            }  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
     */  | 
 | 
    private void startBlock(int startToken) { | 
 | 
        if (stackCount == unitStack.length) { | 
 | 
            int[]     newUS = new int[stackCount * 2];  | 
 | 
 | 
 | 
            System.arraycopy(unitStack, 0, newUS, 0, stackCount);  | 
 | 
            unitStack = newUS;  | 
 | 
        }  | 
 | 
        unitStack[stackCount++] = startToken;  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
     */  | 
 | 
    private void endBlock(int endToken) { | 
 | 
        int    startToken;  | 
 | 
 | 
 | 
        switch (endToken) { | 
 | 
        case BRACKET_CLOSE:  | 
 | 
            startToken = BRACKET_OPEN;  | 
 | 
            break;  | 
 | 
        case BRACE_CLOSE:  | 
 | 
            startToken = BRACE_OPEN;  | 
 | 
            break;  | 
 | 
        case PAREN_CLOSE:  | 
 | 
            startToken = PAREN_OPEN;  | 
 | 
            break;  | 
 | 
        default:  | 
 | 
              | 
 | 
            startToken = -1;  | 
 | 
            break;  | 
 | 
        }  | 
 | 
        if (stackCount > 0 && unitStack[stackCount - 1] == startToken) { | 
 | 
            stackCount--;  | 
 | 
        }  | 
 | 
        else { | 
 | 
              | 
 | 
            throw new RuntimeException("Unmatched block"); | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
     */  | 
 | 
    private boolean inBlock() { | 
 | 
        return (stackCount > 0);  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
     */  | 
 | 
    private int readWS() throws IOException { | 
 | 
        int nextChar;  | 
 | 
        while ((nextChar = readChar()) != -1 &&  | 
 | 
               Character.isWhitespace((char)nextChar)) { | 
 | 
            readWS = true;  | 
 | 
        }  | 
 | 
        return nextChar;  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
     */  | 
 | 
    private int readChar() throws IOException { | 
 | 
        if (didPushChar) { | 
 | 
            didPushChar = false;  | 
 | 
            return pushedChar;  | 
 | 
        }  | 
 | 
        return reader.read();  | 
 | 
        // Uncomment the following to do case insensitive parsing.  | 
 | 
        /*  | 
 | 
        if (retValue != -1) { | 
 | 
            return (int)Character.toLowerCase((char)retValue);  | 
 | 
        }  | 
 | 
        return retValue;  | 
 | 
        */  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    private void pushChar(int tempChar) { | 
 | 
        if (didPushChar) { | 
 | 
              | 
 | 
            throw new RuntimeException("Can not handle look ahead of more than one character"); | 
 | 
        }  | 
 | 
        didPushChar = true;  | 
 | 
        pushedChar = tempChar;  | 
 | 
    }  | 
 | 
}  |