0N/A/*
2362N/A * Copyright (c) 1999, 2000, Oracle and/or its affiliates. All rights reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
2362N/A * published by the Free Software Foundation. Oracle designates this
0N/A * particular file as subject to the "Classpath" exception as provided
2362N/A * by Oracle in the LICENSE file that accompanied this code.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0N/A *
2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2362N/A * or visit www.oracle.com if you need additional information or have any
2362N/A * questions.
0N/A */
0N/Apackage javax.swing.text.html;
0N/A
0N/Aimport java.io.*;
0N/A
0N/A/**
0N/A * A CSS parser. This works by way of a delegate that implements the
0N/A * CSSParserCallback interface. The delegate is notified of the following
0N/A * events:
0N/A * <ul>
0N/A * <li>Import statement: <code>handleImport</code>
0N/A * <li>Selectors <code>handleSelector</code>. This is invoked for each
0N/A * string. For example if the Reader contained p, bar , a {}, the delegate
0N/A * would be notified 4 times, for 'p,' 'bar' ',' and 'a'.
0N/A * <li>When a rule starts, <code>startRule</code>
0N/A * <li>Properties in the rule via the <code>handleProperty</code>. This
0N/A * is invoked one per property/value key, eg font size: foo;, would
0N/A * cause the delegate to be notified once with a value of 'font size'.
0N/A * <li>Values in the rule via the <code>handleValue</code>, this is notified
0N/A * for the total value.
0N/A * <li>When a rule ends, <code>endRule</code>
0N/A * </ul>
0N/A * This will parse much more than CSS 1, and loosely implements the
0N/A * recommendation for <i>Forward-compatible parsing</i> in section
0N/A * 7.1 of the CSS spec found at:
0N/A * <a href=http://www.w3.org/TR/REC-CSS1>http://www.w3.org/TR/REC-CSS1</a>.
0N/A * If an error results in parsing, a RuntimeException will be thrown.
0N/A * <p>
0N/A * This will preserve case. If the callback wishes to treat certain poritions
0N/A * case insensitively (such as selectors), it should use toLowerCase, or
0N/A * something similar.
0N/A *
0N/A * @author Scott Violet
0N/A */
0N/Aclass CSSParser {
0N/A // Parsing something like the following:
0N/A // (@rule | ruleset | block)*
0N/A //
0N/A // @rule (block | identifier)*; (block with {} ends @rule)
0N/A // block matching [] () {} (that is, [()] is a block, [(){}{[]}]
0N/A // is a block, ()[] is two blocks)
0N/A // identifier "*" | '*' | anything but a [](){} and whitespace
0N/A //
0N/A // ruleset selector decblock
0N/A // selector (identifier | (block, except block '{}') )*
0N/A // declblock declaration* block*
0N/A // declaration (identifier* stopping when identifier ends with :)
0N/A // (identifier* stopping when identifier ends with ;)
0N/A //
0N/A // comments /* */ can appear any where, and are stripped.
0N/A
0N/A
0N/A // identifier - letters, digits, dashes and escaped characters
0N/A // block starts with { ends with matching }, () [] and {} always occur
0N/A // in matching pairs, '' and "" also occur in pairs, except " may be
0N/A
0N/A
0N/A // Indicates the type of token being parsed.
0N/A private static final int IDENTIFIER = 1;
0N/A private static final int BRACKET_OPEN = 2;
0N/A private static final int BRACKET_CLOSE = 3;
0N/A private static final int BRACE_OPEN = 4;
0N/A private static final int BRACE_CLOSE = 5;
0N/A private static final int PAREN_OPEN = 6;
0N/A private static final int PAREN_CLOSE = 7;
0N/A private static final int END = -1;
0N/A
0N/A private static final char[] charMapping = { 0, 0, '[', ']', '{', '}', '(',
0N/A ')', 0};
0N/A
0N/A
0N/A /** Set to true if one character has been read ahead. */
0N/A private boolean didPushChar;
0N/A /** The read ahead character. */
0N/A private int pushedChar;
0N/A /** Temporary place to hold identifiers. */
0N/A private StringBuffer unitBuffer;
0N/A /** Used to indicate blocks. */
0N/A private int[] unitStack;
0N/A /** Number of valid blocks. */
0N/A private int stackCount;
0N/A /** Holds the incoming CSS rules. */
0N/A private Reader reader;
0N/A /** Set to true when the first non @ rule is encountered. */
0N/A private boolean encounteredRuleSet;
0N/A /** Notified of state. */
0N/A private CSSParserCallback callback;
0N/A /** nextToken() inserts the string here. */
0N/A private char[] tokenBuffer;
0N/A /** Current number of chars in tokenBufferLength. */
0N/A private int tokenBufferLength;
0N/A /** Set to true if any whitespace is read. */
0N/A private boolean readWS;
0N/A
0N/A
0N/A // The delegate interface.
0N/A static interface CSSParserCallback {
0N/A /** Called when an @import is encountered. */
0N/A void handleImport(String importString);
0N/A // There is currently no way to distinguish between '"foo,"' and
0N/A // 'foo,'. But this generally isn't valid CSS. If it becomes
0N/A // a problem, handleSelector will have to be told if the string is
0N/A // quoted.
0N/A void handleSelector(String selector);
0N/A void startRule();
0N/A // Property names are mapped to lower case before being passed to
0N/A // the delegate.
0N/A void handleProperty(String property);
0N/A void handleValue(String value);
0N/A void endRule();
0N/A }
0N/A
0N/A CSSParser() {
0N/A unitStack = new int[2];
0N/A tokenBuffer = new char[80];
0N/A unitBuffer = new StringBuffer();
0N/A }
0N/A
0N/A void parse(Reader reader, CSSParserCallback callback,
0N/A boolean inRule) throws IOException {
0N/A this.callback = callback;
0N/A stackCount = tokenBufferLength = 0;
0N/A this.reader = reader;
0N/A encounteredRuleSet = false;
0N/A try {
0N/A if (inRule) {
0N/A parseDeclarationBlock();
0N/A }
0N/A else {
0N/A while (getNextStatement());
0N/A }
0N/A } finally {
0N/A callback = null;
0N/A reader = null;
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Gets the next statement, returning false if the end is reached. A
0N/A * statement is either an @rule, or a ruleset.
0N/A */
0N/A private boolean getNextStatement() throws IOException {
0N/A unitBuffer.setLength(0);
0N/A
0N/A int token = nextToken((char)0);
0N/A
0N/A switch (token) {
0N/A case IDENTIFIER:
0N/A if (tokenBufferLength > 0) {
0N/A if (tokenBuffer[0] == '@') {
0N/A parseAtRule();
0N/A }
0N/A else {
0N/A encounteredRuleSet = true;
0N/A parseRuleSet();
0N/A }
0N/A }
0N/A return true;
0N/A case BRACKET_OPEN:
0N/A case BRACE_OPEN:
0N/A case PAREN_OPEN:
0N/A parseTillClosed(token);
0N/A return true;
0N/A
0N/A case BRACKET_CLOSE:
0N/A case BRACE_CLOSE:
0N/A case PAREN_CLOSE:
0N/A // Shouldn't happen...
0N/A throw new RuntimeException("Unexpected top level block close");
0N/A
0N/A case END:
0N/A return false;
0N/A }
0N/A return true;
0N/A }
0N/A
0N/A /**
0N/A * Parses an @ rule, stopping at a matching brace pair, or ;.
0N/A */
0N/A private void parseAtRule() throws IOException {
0N/A // PENDING: make this more effecient.
0N/A boolean done = false;
0N/A boolean isImport = (tokenBufferLength == 7 &&
0N/A tokenBuffer[0] == '@' && tokenBuffer[1] == 'i' &&
0N/A tokenBuffer[2] == 'm' && tokenBuffer[3] == 'p' &&
0N/A tokenBuffer[4] == 'o' && tokenBuffer[5] == 'r' &&
0N/A tokenBuffer[6] == 't');
0N/A
0N/A unitBuffer.setLength(0);
0N/A while (!done) {
0N/A int nextToken = nextToken(';');
0N/A
0N/A switch (nextToken) {
0N/A case IDENTIFIER:
0N/A if (tokenBufferLength > 0 &&
0N/A tokenBuffer[tokenBufferLength - 1] == ';') {
0N/A --tokenBufferLength;
0N/A done = true;
0N/A }
0N/A if (tokenBufferLength > 0) {
0N/A if (unitBuffer.length() > 0 && readWS) {
0N/A unitBuffer.append(' ');
0N/A }
0N/A unitBuffer.append(tokenBuffer, 0, tokenBufferLength);
0N/A }
0N/A break;
0N/A
0N/A case BRACE_OPEN:
0N/A if (unitBuffer.length() > 0 && readWS) {
0N/A unitBuffer.append(' ');
0N/A }
0N/A unitBuffer.append(charMapping[nextToken]);
0N/A parseTillClosed(nextToken);
0N/A done = true;
0N/A // Skip a tailing ';', not really to spec.
0N/A {
0N/A int nextChar = readWS();
0N/A if (nextChar != -1 && nextChar != ';') {
0N/A pushChar(nextChar);
0N/A }
0N/A }
0N/A break;
0N/A
0N/A case BRACKET_OPEN: case PAREN_OPEN:
0N/A unitBuffer.append(charMapping[nextToken]);
0N/A parseTillClosed(nextToken);
0N/A break;
0N/A
0N/A case BRACKET_CLOSE: case BRACE_CLOSE: case PAREN_CLOSE:
0N/A throw new RuntimeException("Unexpected close in @ rule");
0N/A
0N/A case END:
0N/A done = true;
0N/A break;
0N/A }
0N/A }
0N/A if (isImport && !encounteredRuleSet) {
0N/A callback.handleImport(unitBuffer.toString());
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Parses the next rule set, which is a selector followed by a
0N/A * declaration block.
0N/A */
0N/A private void parseRuleSet() throws IOException {
0N/A if (parseSelectors()) {
0N/A callback.startRule();
0N/A parseDeclarationBlock();
0N/A callback.endRule();
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Parses a set of selectors, returning false if the end of the stream
0N/A * is reached.
0N/A */
0N/A private boolean parseSelectors() throws IOException {
0N/A // Parse the selectors
0N/A int nextToken;
0N/A
0N/A if (tokenBufferLength > 0) {
0N/A callback.handleSelector(new String(tokenBuffer, 0,
0N/A tokenBufferLength));
0N/A }
0N/A
0N/A unitBuffer.setLength(0);
0N/A for (;;) {
0N/A while ((nextToken = nextToken((char)0)) == IDENTIFIER) {
0N/A if (tokenBufferLength > 0) {
0N/A callback.handleSelector(new String(tokenBuffer, 0,
0N/A tokenBufferLength));
0N/A }
0N/A }
0N/A switch (nextToken) {
0N/A case BRACE_OPEN:
0N/A return true;
0N/A
0N/A case BRACKET_OPEN: case PAREN_OPEN:
0N/A parseTillClosed(nextToken);
0N/A // Not too sure about this, how we handle this isn't very
0N/A // well spec'd.
0N/A unitBuffer.setLength(0);
0N/A break;
0N/A
0N/A case BRACKET_CLOSE: case BRACE_CLOSE: case PAREN_CLOSE:
0N/A throw new RuntimeException("Unexpected block close in selector");
0N/A
0N/A case END:
0N/A // Prematurely hit end.
0N/A return false;
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Parses a declaration block. Which a number of declarations followed
0N/A * by a })].
0N/A */
0N/A private void parseDeclarationBlock() throws IOException {
0N/A for (;;) {
0N/A int token = parseDeclaration();
0N/A switch (token) {
0N/A case END: case BRACE_CLOSE:
0N/A return;
0N/A
0N/A case BRACKET_CLOSE: case PAREN_CLOSE:
0N/A // Bail
0N/A throw new RuntimeException("Unexpected close in declaration block");
0N/A case IDENTIFIER:
0N/A break;
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Parses a single declaration, which is an identifier a : and another
0N/A * identifier. This returns the last token seen.
0N/A */
0N/A // identifier+: identifier* ;|}
0N/A private int parseDeclaration() throws IOException {
0N/A int token;
0N/A
0N/A if ((token = parseIdentifiers(':', false)) != IDENTIFIER) {
0N/A return token;
0N/A }
0N/A // Make the property name to lowercase
0N/A for (int counter = unitBuffer.length() - 1; counter >= 0; counter--) {
0N/A unitBuffer.setCharAt(counter, Character.toLowerCase
0N/A (unitBuffer.charAt(counter)));
0N/A }
0N/A callback.handleProperty(unitBuffer.toString());
0N/A
0N/A token = parseIdentifiers(';', true);
0N/A callback.handleValue(unitBuffer.toString());
0N/A return token;
0N/A }
0N/A
0N/A /**
0N/A * Parses identifiers until <code>extraChar</code> is encountered,
0N/A * returning the ending token, which will be IDENTIFIER if extraChar
0N/A * is found.
0N/A */
0N/A private int parseIdentifiers(char extraChar,
0N/A boolean wantsBlocks) throws IOException {
0N/A int nextToken;
0N/A int ubl;
0N/A
0N/A unitBuffer.setLength(0);
0N/A for (;;) {
0N/A nextToken = nextToken(extraChar);
0N/A
0N/A switch (nextToken) {
0N/A case IDENTIFIER:
0N/A if (tokenBufferLength > 0) {
0N/A if (tokenBuffer[tokenBufferLength - 1] == extraChar) {
0N/A if (--tokenBufferLength > 0) {
0N/A if (readWS && unitBuffer.length() > 0) {
0N/A unitBuffer.append(' ');
0N/A }
0N/A unitBuffer.append(tokenBuffer, 0,
0N/A tokenBufferLength);
0N/A }
0N/A return IDENTIFIER;
0N/A }
0N/A if (readWS && unitBuffer.length() > 0) {
0N/A unitBuffer.append(' ');
0N/A }
0N/A unitBuffer.append(tokenBuffer, 0, tokenBufferLength);
0N/A }
0N/A break;
0N/A
0N/A case BRACKET_OPEN:
0N/A case BRACE_OPEN:
0N/A case PAREN_OPEN:
0N/A ubl = unitBuffer.length();
0N/A if (wantsBlocks) {
0N/A unitBuffer.append(charMapping[nextToken]);
0N/A }
0N/A parseTillClosed(nextToken);
0N/A if (!wantsBlocks) {
0N/A unitBuffer.setLength(ubl);
0N/A }
0N/A break;
0N/A
0N/A case BRACE_CLOSE:
0N/A // No need to throw for these two, we return token and
0N/A // caller can do whatever.
0N/A case BRACKET_CLOSE:
0N/A case PAREN_CLOSE:
0N/A case END:
0N/A // Hit the end
0N/A return nextToken;
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Parses till a matching block close is encountered. This is only
0N/A * appropriate to be called at the top level (no nesting).
0N/A */
0N/A private void parseTillClosed(int openToken) throws IOException {
0N/A int nextToken;
0N/A boolean done = false;
0N/A
0N/A startBlock(openToken);
0N/A while (!done) {
0N/A nextToken = nextToken((char)0);
0N/A switch (nextToken) {
0N/A case IDENTIFIER:
0N/A if (unitBuffer.length() > 0 && readWS) {
0N/A unitBuffer.append(' ');
0N/A }
0N/A if (tokenBufferLength > 0) {
0N/A unitBuffer.append(tokenBuffer, 0, tokenBufferLength);
0N/A }
0N/A break;
0N/A
0N/A case BRACKET_OPEN: case BRACE_OPEN: case PAREN_OPEN:
0N/A if (unitBuffer.length() > 0 && readWS) {
0N/A unitBuffer.append(' ');
0N/A }
0N/A unitBuffer.append(charMapping[nextToken]);
0N/A startBlock(nextToken);
0N/A break;
0N/A
0N/A case BRACKET_CLOSE: case BRACE_CLOSE: case PAREN_CLOSE:
0N/A if (unitBuffer.length() > 0 && readWS) {
0N/A unitBuffer.append(' ');
0N/A }
0N/A unitBuffer.append(charMapping[nextToken]);
0N/A endBlock(nextToken);
0N/A if (!inBlock()) {
0N/A done = true;
0N/A }
0N/A break;
0N/A
0N/A case END:
0N/A // Prematurely hit end.
0N/A throw new RuntimeException("Unclosed block");
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Fetches the next token.
0N/A */
0N/A private int nextToken(char idChar) throws IOException {
0N/A readWS = false;
0N/A
0N/A int nextChar = readWS();
0N/A
0N/A switch (nextChar) {
0N/A case '\'':
0N/A readTill('\'');
0N/A if (tokenBufferLength > 0) {
0N/A tokenBufferLength--;
0N/A }
0N/A return IDENTIFIER;
0N/A case '"':
0N/A readTill('"');
0N/A if (tokenBufferLength > 0) {
0N/A tokenBufferLength--;
0N/A }
0N/A return IDENTIFIER;
0N/A case '[':
0N/A return BRACKET_OPEN;
0N/A case ']':
0N/A return BRACKET_CLOSE;
0N/A case '{':
0N/A return BRACE_OPEN;
0N/A case '}':
0N/A return BRACE_CLOSE;
0N/A case '(':
0N/A return PAREN_OPEN;
0N/A case ')':
0N/A return PAREN_CLOSE;
0N/A case -1:
0N/A return END;
0N/A default:
0N/A pushChar(nextChar);
0N/A getIdentifier(idChar);
0N/A return IDENTIFIER;
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Gets an identifier, returning true if the length of the string is greater than 0,
0N/A * stopping when <code>stopChar</code>, whitespace, or one of {}()[] is
0N/A * hit.
0N/A */
0N/A // NOTE: this could be combined with readTill, as they contain somewhat
0N/A // similiar functionality.
0N/A private boolean getIdentifier(char stopChar) throws IOException {
0N/A boolean lastWasEscape = false;
0N/A boolean done = false;
0N/A int escapeCount = 0;
0N/A int escapeChar = 0;
0N/A int nextChar;
0N/A int intStopChar = (int)stopChar;
0N/A // 1 for '\', 2 for valid escape char [0-9a-fA-F], 3 for
0N/A // stop character (white space, ()[]{}) 0 otherwise
0N/A short type;
0N/A int escapeOffset = 0;
0N/A
0N/A tokenBufferLength = 0;
0N/A while (!done) {
0N/A nextChar = readChar();
0N/A switch (nextChar) {
0N/A case '\\':
0N/A type = 1;
0N/A break;
0N/A
0N/A case '0': case '1': case '2': case '3': case '4': case '5':
0N/A case '6': case '7': case '8': case '9':
0N/A type = 2;
0N/A escapeOffset = nextChar - '0';
0N/A break;
0N/A
0N/A case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
0N/A type = 2;
0N/A escapeOffset = nextChar - 'a' + 10;
0N/A break;
0N/A
0N/A case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
0N/A type = 2;
0N/A escapeOffset = nextChar - 'A' + 10;
0N/A break;
0N/A
0N/A case '\'': case '"': case '[': case ']': case '{': case '}':
0N/A case '(': case ')':
0N/A case ' ': case '\n': case '\t': case '\r':
0N/A type = 3;
0N/A break;
0N/A
0N/A case '/':
0N/A type = 4;
0N/A break;
0N/A
0N/A case -1:
0N/A // Reached the end
0N/A done = true;
0N/A type = 0;
0N/A break;
0N/A
0N/A default:
0N/A type = 0;
0N/A break;
0N/A }
0N/A if (lastWasEscape) {
0N/A if (type == 2) {
0N/A // Continue with escape.
0N/A escapeChar = escapeChar * 16 + escapeOffset;
0N/A if (++escapeCount == 4) {
0N/A lastWasEscape = false;
0N/A append((char)escapeChar);
0N/A }
0N/A }
0N/A else {
0N/A // no longer escaped
0N/A lastWasEscape = false;
0N/A if (escapeCount > 0) {
0N/A append((char)escapeChar);
0N/A // Make this simpler, reprocess the character.
0N/A pushChar(nextChar);
0N/A }
0N/A else if (!done) {
0N/A append((char)nextChar);
0N/A }
0N/A }
0N/A }
0N/A else if (!done) {
0N/A if (type == 1) {
0N/A lastWasEscape = true;
0N/A escapeChar = escapeCount = 0;
0N/A }
0N/A else if (type == 3) {
0N/A done = true;
0N/A pushChar(nextChar);
0N/A }
0N/A else if (type == 4) {
0N/A // Potential comment
0N/A nextChar = readChar();
0N/A if (nextChar == '*') {
0N/A done = true;
0N/A readComment();
0N/A readWS = true;
0N/A }
0N/A else {
0N/A append('/');
0N/A if (nextChar == -1) {
0N/A done = true;
0N/A }
0N/A else {
0N/A pushChar(nextChar);
0N/A }
0N/A }
0N/A }
0N/A else {
0N/A append((char)nextChar);
0N/A if (nextChar == intStopChar) {
0N/A done = true;
0N/A }
0N/A }
0N/A }
0N/A }
0N/A return (tokenBufferLength > 0);
0N/A }
0N/A
0N/A /**
0N/A * Reads till a <code>stopChar</code> is encountered, escaping characters
0N/A * as necessary.
0N/A */
0N/A private void readTill(char stopChar) throws IOException {
0N/A boolean lastWasEscape = false;
0N/A int escapeCount = 0;
0N/A int escapeChar = 0;
0N/A int nextChar;
0N/A boolean done = false;
0N/A int intStopChar = (int)stopChar;
0N/A // 1 for '\', 2 for valid escape char [0-9a-fA-F], 0 otherwise
0N/A short type;
0N/A int escapeOffset = 0;
0N/A
0N/A tokenBufferLength = 0;
0N/A while (!done) {
0N/A nextChar = readChar();
0N/A switch (nextChar) {
0N/A case '\\':
0N/A type = 1;
0N/A break;
0N/A
0N/A case '0': case '1': case '2': case '3': case '4':case '5':
0N/A case '6': case '7': case '8': case '9':
0N/A type = 2;
0N/A escapeOffset = nextChar - '0';
0N/A break;
0N/A
0N/A case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
0N/A type = 2;
0N/A escapeOffset = nextChar - 'a' + 10;
0N/A break;
0N/A
0N/A case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
0N/A type = 2;
0N/A escapeOffset = nextChar - 'A' + 10;
0N/A break;
0N/A
0N/A case -1:
0N/A // Prematurely reached the end!
0N/A throw new RuntimeException("Unclosed " + stopChar);
0N/A
0N/A default:
0N/A type = 0;
0N/A break;
0N/A }
0N/A if (lastWasEscape) {
0N/A if (type == 2) {
0N/A // Continue with escape.
0N/A escapeChar = escapeChar * 16 + escapeOffset;
0N/A if (++escapeCount == 4) {
0N/A lastWasEscape = false;
0N/A append((char)escapeChar);
0N/A }
0N/A }
0N/A else {
0N/A // no longer escaped
0N/A if (escapeCount > 0) {
0N/A append((char)escapeChar);
0N/A if (type == 1) {
0N/A lastWasEscape = true;
0N/A escapeChar = escapeCount = 0;
0N/A }
0N/A else {
0N/A if (nextChar == intStopChar) {
0N/A done = true;
0N/A }
0N/A append((char)nextChar);
0N/A lastWasEscape = false;
0N/A }
0N/A }
0N/A else {
0N/A append((char)nextChar);
0N/A lastWasEscape = false;
0N/A }
0N/A }
0N/A }
0N/A else if (type == 1) {
0N/A lastWasEscape = true;
0N/A escapeChar = escapeCount = 0;
0N/A }
0N/A else {
0N/A if (nextChar == intStopChar) {
0N/A done = true;
0N/A }
0N/A append((char)nextChar);
0N/A }
0N/A }
0N/A }
0N/A
0N/A private void append(char character) {
0N/A if (tokenBufferLength == tokenBuffer.length) {
0N/A char[] newBuffer = new char[tokenBuffer.length * 2];
0N/A System.arraycopy(tokenBuffer, 0, newBuffer, 0, tokenBuffer.length);
0N/A tokenBuffer = newBuffer;
0N/A }
0N/A tokenBuffer[tokenBufferLength++] = character;
0N/A }
0N/A
0N/A /**
0N/A * Parses a comment block.
0N/A */
0N/A private void readComment() throws IOException {
0N/A int nextChar;
0N/A
0N/A for(;;) {
0N/A nextChar = readChar();
0N/A switch (nextChar) {
0N/A case -1:
0N/A throw new RuntimeException("Unclosed comment");
0N/A case '*':
0N/A nextChar = readChar();
0N/A if (nextChar == '/') {
0N/A return;
0N/A }
0N/A else if (nextChar == -1) {
0N/A throw new RuntimeException("Unclosed comment");
0N/A }
0N/A else {
0N/A pushChar(nextChar);
0N/A }
0N/A break;
0N/A default:
0N/A break;
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Called when a block start is encountered ({[.
0N/A */
0N/A private void startBlock(int startToken) {
0N/A if (stackCount == unitStack.length) {
0N/A int[] newUS = new int[stackCount * 2];
0N/A
0N/A System.arraycopy(unitStack, 0, newUS, 0, stackCount);
0N/A unitStack = newUS;
0N/A }
0N/A unitStack[stackCount++] = startToken;
0N/A }
0N/A
0N/A /**
0N/A * Called when an end block is encountered )]}
0N/A */
0N/A private void endBlock(int endToken) {
0N/A int startToken;
0N/A
0N/A switch (endToken) {
0N/A case BRACKET_CLOSE:
0N/A startToken = BRACKET_OPEN;
0N/A break;
0N/A case BRACE_CLOSE:
0N/A startToken = BRACE_OPEN;
0N/A break;
0N/A case PAREN_CLOSE:
0N/A startToken = PAREN_OPEN;
0N/A break;
0N/A default:
0N/A // Will never happen.
0N/A startToken = -1;
0N/A break;
0N/A }
0N/A if (stackCount > 0 && unitStack[stackCount - 1] == startToken) {
0N/A stackCount--;
0N/A }
0N/A else {
0N/A // Invalid state, should do something.
0N/A throw new RuntimeException("Unmatched block");
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * @return true if currently in a block.
0N/A */
0N/A private boolean inBlock() {
0N/A return (stackCount > 0);
0N/A }
0N/A
0N/A /**
0N/A * Skips any white space, returning the character after the white space.
0N/A */
0N/A private int readWS() throws IOException {
0N/A int nextChar;
0N/A while ((nextChar = readChar()) != -1 &&
0N/A Character.isWhitespace((char)nextChar)) {
0N/A readWS = true;
0N/A }
0N/A return nextChar;
0N/A }
0N/A
0N/A /**
0N/A * Reads a character from the stream.
0N/A */
0N/A private int readChar() throws IOException {
0N/A if (didPushChar) {
0N/A didPushChar = false;
0N/A return pushedChar;
0N/A }
0N/A return reader.read();
0N/A // Uncomment the following to do case insensitive parsing.
0N/A /*
0N/A if (retValue != -1) {
0N/A return (int)Character.toLowerCase((char)retValue);
0N/A }
0N/A return retValue;
0N/A */
0N/A }
0N/A
0N/A /**
0N/A * Supports one character look ahead, this will throw if called twice
0N/A * in a row.
0N/A */
0N/A private void pushChar(int tempChar) {
0N/A if (didPushChar) {
0N/A // Should never happen.
0N/A throw new RuntimeException("Can not handle look ahead of more than one character");
0N/A }
0N/A didPushChar = true;
0N/A pushedChar = tempChar;
0N/A }
0N/A}