854 lines · 27.5 KB · Java
RawDownload
/* * Copyright (c) 1999, 2000, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation.  Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */package javax.swing.text.html; import java.io.*; /** * A CSS parser. This works by way of a delegate that implements the * CSSParserCallback interface. The delegate is notified of the following * events: * <ul> *   <li>Import statement: <code>handleImport</code> *   <li>Selectors <code>handleSelector</code>. This is invoked for each *       string. For example if the Reader contained p, bar , a {}, the delegate *       would be notified 4 times, for 'p,' 'bar' ',' and 'a'. *   <li>When a rule starts, <code>startRule</code> *   <li>Properties in the rule via the <code>handleProperty</code>. This *       is invoked one per property/value key, eg font size: foo;, would *       cause the delegate to be notified once with a value of 'font size'. *   <li>Values in the rule via the <code>handleValue</code>, this is notified *       for the total value. *   <li>When a rule ends, <code>endRule</code> * </ul> * This will parse much more than CSS 1, and loosely implements the * recommendation for <i>Forward-compatible parsing</i> in section * 7.1 of the CSS spec found at: * <a href=http://www.w3.org/TR/REC-CSS1>http://www.w3.org/TR/REC-CSS1</a>. * If an error results in parsing, a RuntimeException will be thrown. * <p> * This will preserve case. If the callback wishes to treat certain poritions * case insensitively (such as selectors), it should use toLowerCase, or * something similar. * * @author Scott Violet */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      // Indicates the type of token being parsed.    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};      /** Set to true if one character has been read ahead. */    private boolean        didPushChar;    /** The read ahead character. */    private int            pushedChar;    /** Temporary place to hold identifiers. */    private StringBuffer   unitBuffer;    /** Used to indicate blocks. */    private int[]          unitStack;    /** Number of valid blocks. */    private int            stackCount;    /** Holds the incoming CSS rules. */    private Reader         reader;    /** Set to true when the first non @ rule is encountered. */    private boolean        encounteredRuleSet;    /** Notified of state. */    private CSSParserCallback callback;    /** nextToken() inserts the string here. */    private char[]         tokenBuffer;    /** Current number of chars in tokenBufferLength. */    private int            tokenBufferLength;    /** Set to true if any whitespace is read. */    private boolean        readWS;      // The delegate interface.    static interface CSSParserCallback {        /** Called when an @import is encountered. */        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        // quoted.        void handleSelector(String selector);        void startRule();        // Property names are mapped to lower case before being passed to        // the delegate.        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;        }    }     /**     * Gets the next statement, returning false if the end is reached. A     * statement is either an @rule, or a ruleset.     */    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:            // Shouldn't happen...            throw new RuntimeException("Unexpected top level block close");         case END:            return false;        }        return true;    }     /**     * Parses an @ rule, stopping at a matching brace pair, or ;.     */    private void parseAtRule() throws IOException {        // PENDING: make this more effecient.        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;                // Skip a tailing ';', not really to spec.                {                    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());        }    }     /**     * Parses the next rule set, which is a selector followed by a     * declaration block.     */    private void parseRuleSet() throws IOException {        if (parseSelectors()) {            callback.startRule();            parseDeclarationBlock();            callback.endRule();        }    }     /**     * Parses a set of selectors, returning false if the end of the stream     * is reached.     */    private boolean parseSelectors() throws IOException {        // Parse the selectors        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                // well spec'd.                unitBuffer.setLength(0);                break;             case BRACKET_CLOSE: case BRACE_CLOSE: case PAREN_CLOSE:                throw new RuntimeException("Unexpected block close in selector");             case END:                // Prematurely hit end.                return false;            }        }    }     /**     * Parses a declaration block. Which a number of declarations followed     * by a })].     */    private void parseDeclarationBlock() throws IOException {        for (;;) {            int token = parseDeclaration();            switch (token) {            case END: case BRACE_CLOSE:                return;             case BRACKET_CLOSE: case PAREN_CLOSE:                // Bail                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.     */    // identifier+: identifier* ;|}    private int parseDeclaration() throws IOException {        int    token;         if ((token = parseIdentifiers(':', false)) != IDENTIFIER) {            return token;        }        // Make the property name to lowercase        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;    }     /**     * Parses identifiers until <code>extraChar</code> is encountered,     * returning the ending token, which will be IDENTIFIER if extraChar     * is found.     */    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                // caller can do whatever.            case BRACKET_CLOSE:            case PAREN_CLOSE:            case END:                // Hit the end                return nextToken;            }        }    }     /**     * Parses till a matching block close is encountered. This is only     * appropriate to be called at the top level (no nesting).     */    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:                // Prematurely hit end.                throw new RuntimeException("Unclosed block");            }        }    }     /**     * Fetches the next token.     */    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;

Lines 1–500 of 854.