0001: /*
0002:
0003: ============================================================================
0004: The Apache Software License, Version 1.1
0005: ============================================================================
0006:
0007: Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved.
0008:
0009: Redistribution and use in source and binary forms, with or without modifica-
0010: tion, are permitted provided that the following conditions are met:
0011:
0012: 1. Redistributions of source code must retain the above copyright notice,
0013: this list of conditions and the following disclaimer.
0014:
0015: 2. Redistributions in binary form must reproduce the above copyright notice,
0016: this list of conditions and the following disclaimer in the documentation
0017: and/or other materials provided with the distribution.
0018:
0019: 3. The end-user documentation included with the redistribution, if any, must
0020: include the following acknowledgment: "This product includes software
0021: developed by the Apache Software Foundation (http://www.apache.org/)."
0022: Alternately, this acknowledgment may appear in the software itself, if
0023: and wherever such third-party acknowledgments normally appear.
0024:
0025: 4. The names "Batik" and "Apache Software Foundation" must not be
0026: used to endorse or promote products derived from this software without
0027: prior written permission. For written permission, please contact
0028: apache@apache.org.
0029:
0030: 5. Products derived from this software may not be called "Apache", nor may
0031: "Apache" appear in their name, without prior written permission of the
0032: Apache Software Foundation.
0033:
0034: THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
0035: INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
0036: FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
0037: APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
0038: INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU-
0039: DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
0040: OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
0041: ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
0042: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
0043: THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0044:
0045: This software consists of voluntary contributions made by many individuals
0046: on behalf of the Apache Software Foundation. For more information on the
0047: Apache Software Foundation, please see <http://www.apache.org/>.
0048:
0049: */
0050:
0051: package org.apache.batik.css.parser;
0052:
0053: import java.io.IOException;
0054: import java.io.InputStream;
0055: import java.io.Reader;
0056:
0057: import org.apache.batik.util.io.NormalizingReader;
0058: import org.apache.batik.util.io.StreamNormalizingReader;
0059: import org.apache.batik.util.io.StringNormalizingReader;
0060:
0061: /**
0062: * This class represents a CSS scanner - an object which decodes CSS lexical
0063: * units.
0064: *
0065: * @author <a href="mailto:stephane@hillion.org">Stephane Hillion</a>
0066: * @version $Id$
0067: */
0068: public class Scanner {
0069:
0070: /**
0071: * The reader.
0072: */
0073: protected NormalizingReader reader;
0074:
0075: /**
0076: * The current char.
0077: */
0078: protected int current;
0079:
0080: /**
0081: * The recording buffer.
0082: */
0083: protected char[] buffer = new char[128];
0084:
0085: /**
0086: * The current position in the buffer.
0087: */
0088: protected int position;
0089:
0090: /**
0091: * The type of the current lexical unit.
0092: */
0093: protected int type;
0094:
0095: /**
0096: * The start offset of the last lexical unit.
0097: */
0098: protected int start;
0099:
0100: /**
0101: * The end offset of the last lexical unit.
0102: */
0103: protected int end;
0104:
0105: /**
0106: * The characters to skip to create the string which represents the
0107: * current token.
0108: */
0109: protected int blankCharacters;
0110:
0111: /**
0112: * Creates a new Scanner object.
0113: * @param r The reader to scan.
0114: */
0115: public Scanner(Reader r) throws ParseException {
0116: try {
0117: reader = new StreamNormalizingReader(r);
0118: current = nextChar();
0119: } catch (IOException e) {
0120: throw new ParseException(e);
0121: }
0122: }
0123:
0124: /**
0125: * Creates a new Scanner object.
0126: * @param is The input stream to scan.
0127: * @param enc The encoding to use to decode the input stream, or null.
0128: */
0129: public Scanner(InputStream is, String enc) throws ParseException {
0130: try {
0131: reader = new StreamNormalizingReader(is, enc);
0132: current = nextChar();
0133: } catch (IOException e) {
0134: throw new ParseException(e);
0135: }
0136: }
0137:
0138: /**
0139: * Creates a new Scanner object.
0140: * @param r The reader to scan.
0141: */
0142: public Scanner(String s) throws ParseException {
0143: try {
0144: reader = new StringNormalizingReader(s);
0145: current = nextChar();
0146: } catch (IOException e) {
0147: throw new ParseException(e);
0148: }
0149: }
0150:
0151: /**
0152: * Returns the current line.
0153: */
0154: public int getLine() {
0155: return reader.getLine();
0156: }
0157:
0158: /**
0159: * Returns the current column.
0160: */
0161: public int getColumn() {
0162: return reader.getColumn();
0163: }
0164:
0165: /**
0166: * Returns the buffer used to store the chars.
0167: */
0168: public char[] getBuffer() {
0169: return buffer;
0170: }
0171:
0172: /**
0173: * Returns the start offset of the last lexical unit.
0174: */
0175: public int getStart() {
0176: return start;
0177: }
0178:
0179: /**
0180: * Returns the end offset of the last lexical unit.
0181: */
0182: public int getEnd() {
0183: return end;
0184: }
0185:
0186: /**
0187: * Clears the buffer.
0188: */
0189: public void clearBuffer() {
0190: if (position <= 0) {
0191: position = 0;
0192: } else {
0193: buffer[0] = buffer[position - 1];
0194: position = 1;
0195: }
0196: }
0197:
0198: /**
0199: * The current lexical unit type like defined in LexicalUnits.
0200: */
0201: public int getType() {
0202: return type;
0203: }
0204:
0205: /**
0206: * Returns the string representation of the current lexical unit.
0207: */
0208: public String getStringValue() {
0209: return new String(buffer, start, end - start);
0210: }
0211:
0212: /**
0213: * Scans a @rule value. This method assumes that the current
0214: * lexical unit is a at keyword.
0215: */
0216: public void scanAtRule() throws ParseException {
0217: try {
0218: // waiting for EOF, ';' or '{'
0219: loop: for (;;) {
0220: switch (current) {
0221: case '{':
0222: int brackets = 1;
0223: for (;;) {
0224: nextChar();
0225: switch (current) {
0226: case '}':
0227: if (--brackets > 0) {
0228: break;
0229: }
0230: case -1:
0231: break loop;
0232: case '{':
0233: brackets++;
0234: }
0235: }
0236: case -1:
0237: case ';':
0238: break loop;
0239: }
0240: nextChar();
0241: }
0242: end = position;
0243: } catch (IOException e) {
0244: throw new ParseException(e);
0245: }
0246: }
0247:
0248: /**
0249: * Returns the next token.
0250: */
0251: public int next() throws ParseException {
0252: blankCharacters = 0;
0253: start = position - 1;
0254: nextToken();
0255: end = position - endGap();
0256: return type;
0257: }
0258:
0259: /**
0260: * Returns the end gap of the current lexical unit.
0261: */
0262: protected int endGap() {
0263: int result = (current == -1) ? 0 : 1;
0264: switch (type) {
0265: case LexicalUnits.FUNCTION:
0266: case LexicalUnits.STRING:
0267: case LexicalUnits.S:
0268: case LexicalUnits.PERCENTAGE:
0269: result += 1;
0270: break;
0271: case LexicalUnits.COMMENT:
0272: case LexicalUnits.HZ:
0273: case LexicalUnits.EM:
0274: case LexicalUnits.EX:
0275: case LexicalUnits.PC:
0276: case LexicalUnits.PT:
0277: case LexicalUnits.PX:
0278: case LexicalUnits.CM:
0279: case LexicalUnits.MM:
0280: case LexicalUnits.IN:
0281: case LexicalUnits.MS:
0282: result += 2;
0283: break;
0284: case LexicalUnits.KHZ:
0285: case LexicalUnits.DEG:
0286: case LexicalUnits.RAD:
0287: result += 3;
0288: break;
0289: case LexicalUnits.GRAD:
0290: result += 4;
0291: }
0292: return result + blankCharacters;
0293: }
0294:
0295: /**
0296: * Returns the next token.
0297: */
0298: protected void nextToken() throws ParseException {
0299: try {
0300: switch (current) {
0301: case -1:
0302: type = LexicalUnits.EOF;
0303: return;
0304: case '{':
0305: nextChar();
0306: type = LexicalUnits.LEFT_CURLY_BRACE;
0307: return;
0308: case '}':
0309: nextChar();
0310: type = LexicalUnits.RIGHT_CURLY_BRACE;
0311: return;
0312: case '=':
0313: nextChar();
0314: type = LexicalUnits.EQUAL;
0315: return;
0316: case '+':
0317: nextChar();
0318: type = LexicalUnits.PLUS;
0319: return;
0320: case ',':
0321: nextChar();
0322: type = LexicalUnits.COMMA;
0323: return;
0324: case ';':
0325: nextChar();
0326: type = LexicalUnits.SEMI_COLON;
0327: return;
0328: case '>':
0329: nextChar();
0330: type = LexicalUnits.PRECEDE;
0331: return;
0332: case '[':
0333: nextChar();
0334: type = LexicalUnits.LEFT_BRACKET;
0335: return;
0336: case ']':
0337: nextChar();
0338: type = LexicalUnits.RIGHT_BRACKET;
0339: return;
0340: case '*':
0341: nextChar();
0342: type = LexicalUnits.ANY;
0343: return;
0344: case '(':
0345: nextChar();
0346: type = LexicalUnits.LEFT_BRACE;
0347: return;
0348: case ')':
0349: nextChar();
0350: type = LexicalUnits.RIGHT_BRACE;
0351: return;
0352: case ':':
0353: nextChar();
0354: type = LexicalUnits.COLON;
0355: return;
0356: case ' ':
0357: case '\t':
0358: case '\r':
0359: case '\n':
0360: case '\f':
0361: do {
0362: nextChar();
0363: } while (ScannerUtilities.isCSSSpace((char) current));
0364: type = LexicalUnits.SPACE;
0365: return;
0366: case '/':
0367: nextChar();
0368: if (current != '*') {
0369: type = LexicalUnits.DIVIDE;
0370: return;
0371: }
0372: // Comment
0373: nextChar();
0374: start = position - 1;
0375: do {
0376: while (current != -1 && current != '*') {
0377: nextChar();
0378: }
0379: do {
0380: nextChar();
0381: } while (current != -1 && current == '*');
0382: } while (current != -1 && current != '/');
0383: if (current == -1) {
0384: throw new ParseException("eof", reader.getLine(),
0385: reader.getColumn());
0386: }
0387: nextChar();
0388: type = LexicalUnits.COMMENT;
0389: return;
0390: case '\'': // String1
0391: type = string1();
0392: return;
0393: case '"': // String2
0394: type = string2();
0395: return;
0396: case '<':
0397: nextChar();
0398: if (current != '!') {
0399: throw new ParseException("character", reader
0400: .getLine(), reader.getColumn());
0401: }
0402: nextChar();
0403: if (current == '-') {
0404: nextChar();
0405: if (current == '-') {
0406: nextChar();
0407: type = LexicalUnits.CDO;
0408: return;
0409: }
0410: }
0411: throw new ParseException("character", reader.getLine(),
0412: reader.getColumn());
0413: case '-':
0414: nextChar();
0415: if (current != '-') {
0416: // BEGIN RAVE MODIFICATIONS
0417: // Why did I add this? To handle embedded -'s in names?
0418: //type = LexicalUnits.MINUS;
0419: if (ScannerUtilities
0420: .isCSSIdentifierStartCharacter((char) current)) {
0421: // This is super ugly! I couldn't figure out how to get the scanner
0422: // to jump back and continue on the same token... so I just duplicated
0423: // the logic from regular characters below to create a token
0424:
0425: if (ScannerUtilities
0426: .isCSSIdentifierStartCharacter((char) current)) {
0427: // Identifier
0428: // Track whether or not we just did an escape in the loop
0429: // since in that case, current is pointing to a possibly non-css-name-character
0430: boolean escaped;
0431: do {
0432: escaped = false;
0433: nextChar();
0434: if (current == '\\') {
0435: escaped = true;
0436: // We don't want the actual \ in the string!
0437: // It's been inserted above so remove it, and just insert
0438: // the next (escaped) character
0439: position--;
0440: //nextChar();
0441: escape();
0442: }
0443: } while (current != -1
0444: && (escaped ||
0445:
0446: ScannerUtilities
0447: .isCSSNameCharacter((char) current)));
0448: if (current == '(') {
0449: nextChar();
0450: type = LexicalUnits.FUNCTION;
0451: return;
0452: }
0453: type = LexicalUnits.IDENTIFIER;
0454: return;
0455: }
0456: nextChar();
0457: throw new ParseException(
0458: "identifier.character", reader
0459: .getLine(), reader.getColumn());
0460:
0461: } else {
0462: type = LexicalUnits.MINUS;
0463: }
0464: // END RAVE MODIFICATIONS
0465: return;
0466: }
0467: nextChar();
0468: if (current == '>') {
0469: nextChar();
0470: type = LexicalUnits.CDC;
0471: return;
0472: }
0473: throw new ParseException("character", reader.getLine(),
0474: reader.getColumn());
0475: case '|':
0476: nextChar();
0477: if (current == '=') {
0478: nextChar();
0479: type = LexicalUnits.DASHMATCH;
0480: return;
0481: }
0482: throw new ParseException("character", reader.getLine(),
0483: reader.getColumn());
0484: case '~':
0485: nextChar();
0486: if (current == '=') {
0487: nextChar();
0488: type = LexicalUnits.INCLUDES;
0489: return;
0490: }
0491: throw new ParseException("character", reader.getLine(),
0492: reader.getColumn());
0493: case '#':
0494: nextChar();
0495: if (ScannerUtilities.isCSSNameCharacter((char) current)) {
0496: start = position - 1;
0497: do {
0498: nextChar();
0499: if (current == '\\') {
0500: nextChar();
0501: escape();
0502: }
0503: } while (current != -1
0504: && ScannerUtilities
0505: .isCSSNameCharacter((char) current));
0506: type = LexicalUnits.HASH;
0507: return;
0508: }
0509: throw new ParseException("character", reader.getLine(),
0510: reader.getColumn());
0511: case '@':
0512: nextChar();
0513: switch (current) {
0514: case 'c':
0515: case 'C':
0516: start = position - 1;
0517: if (isEqualIgnoreCase(nextChar(), 'h')
0518: && isEqualIgnoreCase(nextChar(), 'a')
0519: && isEqualIgnoreCase(nextChar(), 'r')
0520: && isEqualIgnoreCase(nextChar(), 's')
0521: && isEqualIgnoreCase(nextChar(), 'e')
0522: && isEqualIgnoreCase(nextChar(), 't')) {
0523: nextChar();
0524: type = LexicalUnits.CHARSET_SYMBOL;
0525: return;
0526: }
0527: break;
0528: case 'f':
0529: case 'F':
0530: start = position - 1;
0531: if (isEqualIgnoreCase(nextChar(), 'o')
0532: && isEqualIgnoreCase(nextChar(), 'n')
0533: && isEqualIgnoreCase(nextChar(), 't')
0534: && isEqualIgnoreCase(nextChar(), '-')
0535: && isEqualIgnoreCase(nextChar(), 'f')
0536: && isEqualIgnoreCase(nextChar(), 'a')
0537: && isEqualIgnoreCase(nextChar(), 'c')
0538: && isEqualIgnoreCase(nextChar(), 'e')) {
0539: nextChar();
0540: type = LexicalUnits.FONT_FACE_SYMBOL;
0541: return;
0542: }
0543: break;
0544: case 'i':
0545: case 'I':
0546: start = position - 1;
0547: if (isEqualIgnoreCase(nextChar(), 'm')
0548: && isEqualIgnoreCase(nextChar(), 'p')
0549: && isEqualIgnoreCase(nextChar(), 'o')
0550: && isEqualIgnoreCase(nextChar(), 'r')
0551: && isEqualIgnoreCase(nextChar(), 't')) {
0552: nextChar();
0553: type = LexicalUnits.IMPORT_SYMBOL;
0554: return;
0555: }
0556: break;
0557: case 'm':
0558: case 'M':
0559: start = position - 1;
0560: if (isEqualIgnoreCase(nextChar(), 'e')
0561: && isEqualIgnoreCase(nextChar(), 'd')
0562: && isEqualIgnoreCase(nextChar(), 'i')
0563: && isEqualIgnoreCase(nextChar(), 'a')) {
0564: nextChar();
0565: type = LexicalUnits.MEDIA_SYMBOL;
0566: return;
0567: }
0568: break;
0569: case 'p':
0570: case 'P':
0571: start = position - 1;
0572: if (isEqualIgnoreCase(nextChar(), 'a')
0573: && isEqualIgnoreCase(nextChar(), 'g')
0574: && isEqualIgnoreCase(nextChar(), 'e')) {
0575: nextChar();
0576: type = LexicalUnits.PAGE_SYMBOL;
0577: return;
0578: }
0579: break;
0580: default:
0581: if (!ScannerUtilities
0582: .isCSSIdentifierStartCharacter((char) current)) {
0583: throw new ParseException(
0584: "identifier.character", reader
0585: .getLine(), reader.getColumn());
0586: }
0587: start = position - 1;
0588: }
0589: do {
0590: nextChar();
0591: if (current == '\\') {
0592: nextChar();
0593: escape();
0594: }
0595: } while (current != -1
0596: && ScannerUtilities
0597: .isCSSNameCharacter((char) current));
0598: type = LexicalUnits.AT_KEYWORD;
0599: return;
0600: case '!':
0601: do {
0602: nextChar();
0603: } while (current != -1
0604: && ScannerUtilities.isCSSSpace((char) current));
0605: if (isEqualIgnoreCase(current, 'i')
0606: && isEqualIgnoreCase(nextChar(), 'm')
0607: && isEqualIgnoreCase(nextChar(), 'p')
0608: && isEqualIgnoreCase(nextChar(), 'o')
0609: && isEqualIgnoreCase(nextChar(), 'r')
0610: && isEqualIgnoreCase(nextChar(), 't')
0611: && isEqualIgnoreCase(nextChar(), 'a')
0612: && isEqualIgnoreCase(nextChar(), 'n')
0613: && isEqualIgnoreCase(nextChar(), 't')) {
0614: nextChar();
0615: type = LexicalUnits.IMPORTANT_SYMBOL;
0616: return;
0617: }
0618: if (current == -1) {
0619: throw new ParseException("eof", reader.getLine(),
0620: reader.getColumn());
0621: } else {
0622: throw new ParseException("character", reader
0623: .getLine(), reader.getColumn());
0624: }
0625: case '0':
0626: case '1':
0627: case '2':
0628: case '3':
0629: case '4':
0630: case '5':
0631: case '6':
0632: case '7':
0633: case '8':
0634: case '9':
0635: type = number();
0636: return;
0637: case '.':
0638: switch (nextChar()) {
0639: case '0':
0640: case '1':
0641: case '2':
0642: case '3':
0643: case '4':
0644: case '5':
0645: case '6':
0646: case '7':
0647: case '8':
0648: case '9':
0649: type = dotNumber();
0650: return;
0651: default:
0652: type = LexicalUnits.DOT;
0653: return;
0654: }
0655: case 'u':
0656: case 'U':
0657: nextChar();
0658: switch (current) {
0659: case '+':
0660: boolean range = false;
0661: for (int i = 0; i < 6; i++) {
0662: nextChar();
0663: switch (current) {
0664: case '?':
0665: range = true;
0666: break;
0667: default:
0668: if (range
0669: && !ScannerUtilities
0670: .isCSSHexadecimalCharacter((char) current)) {
0671: throw new ParseException("character",
0672: reader.getLine(), reader
0673: .getColumn());
0674: }
0675: }
0676: }
0677: nextChar();
0678: if (range) {
0679: type = LexicalUnits.UNICODE_RANGE;
0680: return;
0681: }
0682: if (current == '-') {
0683: nextChar();
0684: if (!ScannerUtilities
0685: .isCSSHexadecimalCharacter((char) current)) {
0686: throw new ParseException("character",
0687: reader.getLine(), reader
0688: .getColumn());
0689: }
0690: nextChar();
0691: if (!ScannerUtilities
0692: .isCSSHexadecimalCharacter((char) current)) {
0693: type = LexicalUnits.UNICODE_RANGE;
0694: return;
0695: }
0696: nextChar();
0697: if (!ScannerUtilities
0698: .isCSSHexadecimalCharacter((char) current)) {
0699: type = LexicalUnits.UNICODE_RANGE;
0700: return;
0701: }
0702: nextChar();
0703: if (!ScannerUtilities
0704: .isCSSHexadecimalCharacter((char) current)) {
0705: type = LexicalUnits.UNICODE_RANGE;
0706: return;
0707: }
0708: nextChar();
0709: if (!ScannerUtilities
0710: .isCSSHexadecimalCharacter((char) current)) {
0711: type = LexicalUnits.UNICODE_RANGE;
0712: return;
0713: }
0714: nextChar();
0715: if (!ScannerUtilities
0716: .isCSSHexadecimalCharacter((char) current)) {
0717: type = LexicalUnits.UNICODE_RANGE;
0718: return;
0719: }
0720: nextChar();
0721: type = LexicalUnits.UNICODE_RANGE;
0722: return;
0723: }
0724: case 'r':
0725: case 'R':
0726: nextChar();
0727: switch (current) {
0728: case 'l':
0729: case 'L':
0730: nextChar();
0731: switch (current) {
0732: case '(':
0733: do {
0734: nextChar();
0735: } while (current != -1
0736: && ScannerUtilities
0737: .isCSSSpace((char) current));
0738: switch (current) {
0739: case '\'':
0740: string1();
0741: blankCharacters += 2;
0742: while (current != -1
0743: && ScannerUtilities
0744: .isCSSSpace((char) current)) {
0745: blankCharacters++;
0746: nextChar();
0747: }
0748: if (current == -1) {
0749: throw new ParseException("eof",
0750: reader.getLine(), reader
0751: .getColumn());
0752: }
0753: if (current != ')') {
0754: throw new ParseException(
0755: "character", reader
0756: .getLine(), reader
0757: .getColumn());
0758: }
0759: nextChar();
0760: type = LexicalUnits.URI;
0761: return;
0762: case '"':
0763: string2();
0764: blankCharacters += 2;
0765: while (current != -1
0766: && ScannerUtilities
0767: .isCSSSpace((char) current)) {
0768: blankCharacters++;
0769: nextChar();
0770: }
0771: if (current == -1) {
0772: throw new ParseException("eof",
0773: reader.getLine(), reader
0774: .getColumn());
0775: }
0776: if (current != ')') {
0777: throw new ParseException(
0778: "character", reader
0779: .getLine(), reader
0780: .getColumn());
0781: }
0782: nextChar();
0783: type = LexicalUnits.URI;
0784: return;
0785: case ')':
0786: throw new ParseException("character",
0787: reader.getLine(), reader
0788: .getColumn());
0789: default:
0790: if (!ScannerUtilities
0791: .isCSSURICharacter((char) current)) {
0792: throw new ParseException(
0793: "character", reader
0794: .getLine(), reader
0795: .getColumn());
0796: }
0797: start = position - 1;
0798: do {
0799: nextChar();
0800: } while (current != -1
0801: && ScannerUtilities
0802: .isCSSURICharacter((char) current));
0803: blankCharacters++;
0804: while (current != -1
0805: && ScannerUtilities
0806: .isCSSSpace((char) current)) {
0807: blankCharacters++;
0808: nextChar();
0809: }
0810: if (current == -1) {
0811: throw new ParseException("eof",
0812: reader.getLine(), reader
0813: .getColumn());
0814: }
0815: if (current != ')') {
0816: throw new ParseException(
0817: "character", reader
0818: .getLine(), reader
0819: .getColumn());
0820: }
0821: nextChar();
0822: type = LexicalUnits.URI;
0823: return;
0824: }
0825: }
0826: }
0827: }
0828: while (current != -1
0829: && ScannerUtilities
0830: .isCSSNameCharacter((char) current)) {
0831: nextChar();
0832: }
0833: if (current == '(') {
0834: nextChar();
0835: type = LexicalUnits.FUNCTION;
0836: return;
0837: }
0838: type = LexicalUnits.IDENTIFIER;
0839: return;
0840: default:
0841: if (ScannerUtilities
0842: .isCSSIdentifierStartCharacter((char) current)) {
0843: // Identifier
0844: // BEGIN RAVE MODIFICATIONS
0845: // Track whether or not we just did an escape in the loop
0846: // since in that case, current is pointing to a possibly non-css-name-character
0847: boolean escaped;
0848: // END RAVE MODIFICATIONS
0849: do {
0850: // BEGIN RAVE MODIFICATIONS
0851: escaped = false;
0852: // END RAVE MODIFICATIONS
0853: nextChar();
0854: if (current == '\\') {
0855: // BEGIN RAVE MODIFICATIONS
0856: escaped = true;
0857: // We don't want the actual \ in the string!
0858: // It's been inserted above so remove it, and just insert
0859: // the next (escaped) character
0860: position--;
0861: //nextChar();
0862: // END RAVE MODIFICATIONS
0863: escape();
0864: }
0865: } while (current != -1 &&
0866: // BEGIN RAVE MODIFICATIONS
0867: (escaped ||
0868: // END RAVE MODIFICATIONS
0869:
0870: ScannerUtilities
0871: .isCSSNameCharacter((char) current)
0872: // BEGIN RAVE MODIFICATIONS
0873: )
0874: // END RAVE MODIFICATIONS
0875: );
0876: if (current == '(') {
0877: nextChar();
0878: type = LexicalUnits.FUNCTION;
0879: return;
0880: }
0881: type = LexicalUnits.IDENTIFIER;
0882: return;
0883: }
0884: nextChar();
0885: throw new ParseException("identifier.character", reader
0886: .getLine(), reader.getColumn());
0887: }
0888: } catch (IOException e) {
0889: throw new ParseException(e);
0890: }
0891: }
0892:
0893: /**
0894: * Scans a single quoted string.
0895: */
0896: protected int string1() throws IOException {
0897: nextChar();
0898: start = position - 1;
0899: loop: for (;;) {
0900: switch (nextChar()) {
0901: case -1:
0902: throw new ParseException("eof", reader.getLine(),
0903: reader.getColumn());
0904: case '\'':
0905: break loop;
0906: case '"':
0907: break;
0908: case '\\':
0909: switch (nextChar()) {
0910: case '\n':
0911: case '\f':
0912: break;
0913: default:
0914: escape();
0915: }
0916: break;
0917: default:
0918: if (!ScannerUtilities
0919: .isCSSStringCharacter((char) current)) {
0920: throw new ParseException("character", reader
0921: .getLine(), reader.getColumn());
0922: }
0923: }
0924: }
0925: nextChar();
0926: return LexicalUnits.STRING;
0927: }
0928:
0929: /**
0930: * Scans a double quoted string.
0931: */
0932: protected int string2() throws IOException {
0933: nextChar();
0934: start = position - 1;
0935: loop: for (;;) {
0936: switch (nextChar()) {
0937: case -1:
0938: throw new ParseException("eof", reader.getLine(),
0939: reader.getColumn());
0940: case '\'':
0941: break;
0942: case '"':
0943: break loop;
0944: case '\\':
0945: switch (nextChar()) {
0946: case '\n':
0947: case '\f':
0948: break;
0949: default:
0950: escape();
0951: }
0952: break;
0953: default:
0954: if (!ScannerUtilities
0955: .isCSSStringCharacter((char) current)) {
0956: throw new ParseException("character", reader
0957: .getLine(), reader.getColumn());
0958: }
0959: }
0960: }
0961: nextChar();
0962: return LexicalUnits.STRING;
0963: }
0964:
0965: /**
0966: * Scans a number.
0967: */
0968: protected int number() throws IOException {
0969: loop: for (;;) {
0970: switch (nextChar()) {
0971: case '.':
0972: switch (nextChar()) {
0973: case '0':
0974: case '1':
0975: case '2':
0976: case '3':
0977: case '4':
0978: case '5':
0979: case '6':
0980: case '7':
0981: case '8':
0982: case '9':
0983: return dotNumber();
0984: }
0985: throw new ParseException("character", reader.getLine(),
0986: reader.getColumn());
0987: default:
0988: break loop;
0989: case '0':
0990: case '1':
0991: case '2':
0992: case '3':
0993: case '4':
0994: case '5':
0995: case '6':
0996: case '7':
0997: case '8':
0998: case '9':
0999: }
1000: }
1001: return numberUnit(true);
1002: }
1003:
1004: /**
1005: * Scans the decimal part of a number.
1006: */
1007: protected int dotNumber() throws IOException {
1008: loop: for (;;) {
1009: switch (nextChar()) {
1010: default:
1011: break loop;
1012: case '0':
1013: case '1':
1014: case '2':
1015: case '3':
1016: case '4':
1017: case '5':
1018: case '6':
1019: case '7':
1020: case '8':
1021: case '9':
1022: }
1023: }
1024: return numberUnit(false);
1025: }
1026:
1027: /**
1028: * Scans the unit of a number.
1029: */
1030: protected int numberUnit(boolean integer) throws IOException {
1031: switch (current) {
1032: case '%':
1033: nextChar();
1034: return LexicalUnits.PERCENTAGE;
1035: case 'c':
1036: case 'C':
1037: switch (nextChar()) {
1038: case 'm':
1039: case 'M':
1040: nextChar();
1041: if (current != -1
1042: && ScannerUtilities
1043: .isCSSNameCharacter((char) current)) {
1044: do {
1045: nextChar();
1046: } while (current != -1
1047: && ScannerUtilities
1048: .isCSSNameCharacter((char) current));
1049: return LexicalUnits.DIMENSION;
1050: }
1051: return LexicalUnits.CM;
1052: default:
1053: while (current != -1
1054: && ScannerUtilities
1055: .isCSSNameCharacter((char) current)) {
1056: nextChar();
1057: }
1058: return LexicalUnits.DIMENSION;
1059: }
1060: case 'd':
1061: case 'D':
1062: switch (nextChar()) {
1063: case 'e':
1064: case 'E':
1065: switch (nextChar()) {
1066: case 'g':
1067: case 'G':
1068: nextChar();
1069: if (current != -1
1070: && ScannerUtilities
1071: .isCSSNameCharacter((char) current)) {
1072: do {
1073: nextChar();
1074: } while (current != -1
1075: && ScannerUtilities
1076: .isCSSNameCharacter((char) current));
1077: return LexicalUnits.DIMENSION;
1078: }
1079: return LexicalUnits.DEG;
1080: }
1081: default:
1082: while (current != -1
1083: && ScannerUtilities
1084: .isCSSNameCharacter((char) current)) {
1085: nextChar();
1086: }
1087: return LexicalUnits.DIMENSION;
1088: }
1089: case 'e':
1090: case 'E':
1091: switch (nextChar()) {
1092: case 'm':
1093: case 'M':
1094: nextChar();
1095: if (current != -1
1096: && ScannerUtilities
1097: .isCSSNameCharacter((char) current)) {
1098: do {
1099: nextChar();
1100: } while (current != -1
1101: && ScannerUtilities
1102: .isCSSNameCharacter((char) current));
1103: return LexicalUnits.DIMENSION;
1104: }
1105: return LexicalUnits.EM;
1106: case 'x':
1107: case 'X':
1108: nextChar();
1109: if (current != -1
1110: && ScannerUtilities
1111: .isCSSNameCharacter((char) current)) {
1112: do {
1113: nextChar();
1114: } while (current != -1
1115: && ScannerUtilities
1116: .isCSSNameCharacter((char) current));
1117: return LexicalUnits.DIMENSION;
1118: }
1119: return LexicalUnits.EX;
1120: default:
1121: while (current != -1
1122: && ScannerUtilities
1123: .isCSSNameCharacter((char) current)) {
1124: nextChar();
1125: }
1126: return LexicalUnits.DIMENSION;
1127: }
1128: case 'g':
1129: case 'G':
1130: switch (nextChar()) {
1131: case 'r':
1132: case 'R':
1133: switch (nextChar()) {
1134: case 'a':
1135: case 'A':
1136: switch (nextChar()) {
1137: case 'd':
1138: case 'D':
1139: nextChar();
1140: if (current != -1
1141: && ScannerUtilities
1142: .isCSSNameCharacter((char) current)) {
1143: do {
1144: nextChar();
1145: } while (current != -1
1146: && ScannerUtilities
1147: .isCSSNameCharacter((char) current));
1148: return LexicalUnits.DIMENSION;
1149: }
1150: return LexicalUnits.GRAD;
1151: }
1152: }
1153: default:
1154: while (current != -1
1155: && ScannerUtilities
1156: .isCSSNameCharacter((char) current)) {
1157: nextChar();
1158: }
1159: return LexicalUnits.DIMENSION;
1160: }
1161: case 'h':
1162: case 'H':
1163: nextChar();
1164: switch (current) {
1165: case 'z':
1166: case 'Z':
1167: nextChar();
1168: if (current != -1
1169: && ScannerUtilities
1170: .isCSSNameCharacter((char) current)) {
1171: do {
1172: nextChar();
1173: } while (current != -1
1174: && ScannerUtilities
1175: .isCSSNameCharacter((char) current));
1176: return LexicalUnits.DIMENSION;
1177: }
1178: return LexicalUnits.HZ;
1179: default:
1180: while (current != -1
1181: && ScannerUtilities
1182: .isCSSNameCharacter((char) current)) {
1183: nextChar();
1184: }
1185: return LexicalUnits.DIMENSION;
1186: }
1187: case 'i':
1188: case 'I':
1189: switch (nextChar()) {
1190: case 'n':
1191: case 'N':
1192: nextChar();
1193: if (current != -1
1194: && ScannerUtilities
1195: .isCSSNameCharacter((char) current)) {
1196: do {
1197: nextChar();
1198: } while (current != -1
1199: && ScannerUtilities
1200: .isCSSNameCharacter((char) current));
1201: return LexicalUnits.DIMENSION;
1202: }
1203: return LexicalUnits.IN;
1204: default:
1205: while (current != -1
1206: && ScannerUtilities
1207: .isCSSNameCharacter((char) current)) {
1208: nextChar();
1209: }
1210: return LexicalUnits.DIMENSION;
1211: }
1212: case 'k':
1213: case 'K':
1214: switch (nextChar()) {
1215: case 'h':
1216: case 'H':
1217: switch (nextChar()) {
1218: case 'z':
1219: case 'Z':
1220: nextChar();
1221: if (current != -1
1222: && ScannerUtilities
1223: .isCSSNameCharacter((char) current)) {
1224: do {
1225: nextChar();
1226: } while (current != -1
1227: && ScannerUtilities
1228: .isCSSNameCharacter((char) current));
1229: return LexicalUnits.DIMENSION;
1230: }
1231: return LexicalUnits.KHZ;
1232: }
1233: default:
1234: while (current != -1
1235: && ScannerUtilities
1236: .isCSSNameCharacter((char) current)) {
1237: nextChar();
1238: }
1239: return LexicalUnits.DIMENSION;
1240: }
1241: case 'm':
1242: case 'M':
1243: switch (nextChar()) {
1244: case 'm':
1245: case 'M':
1246: nextChar();
1247: if (current != -1
1248: && ScannerUtilities
1249: .isCSSNameCharacter((char) current)) {
1250: do {
1251: nextChar();
1252: } while (current != -1
1253: && ScannerUtilities
1254: .isCSSNameCharacter((char) current));
1255: return LexicalUnits.DIMENSION;
1256: }
1257: return LexicalUnits.MM;
1258: case 's':
1259: case 'S':
1260: nextChar();
1261: if (current != -1
1262: && ScannerUtilities
1263: .isCSSNameCharacter((char) current)) {
1264: do {
1265: nextChar();
1266: } while (current != -1
1267: && ScannerUtilities
1268: .isCSSNameCharacter((char) current));
1269: return LexicalUnits.DIMENSION;
1270: }
1271: return LexicalUnits.MS;
1272: default:
1273: while (current != -1
1274: && ScannerUtilities
1275: .isCSSNameCharacter((char) current)) {
1276: nextChar();
1277: }
1278: return LexicalUnits.DIMENSION;
1279: }
1280: case 'p':
1281: case 'P':
1282: switch (nextChar()) {
1283: case 'c':
1284: case 'C':
1285: nextChar();
1286: if (current != -1
1287: && ScannerUtilities
1288: .isCSSNameCharacter((char) current)) {
1289: do {
1290: nextChar();
1291: } while (current != -1
1292: && ScannerUtilities
1293: .isCSSNameCharacter((char) current));
1294: return LexicalUnits.DIMENSION;
1295: }
1296: return LexicalUnits.PC;
1297: case 't':
1298: case 'T':
1299: nextChar();
1300: if (current != -1
1301: && ScannerUtilities
1302: .isCSSNameCharacter((char) current)) {
1303: do {
1304: nextChar();
1305: } while (current != -1
1306: && ScannerUtilities
1307: .isCSSNameCharacter((char) current));
1308: return LexicalUnits.DIMENSION;
1309: }
1310: return LexicalUnits.PT;
1311: case 'x':
1312: case 'X':
1313: nextChar();
1314: if (current != -1
1315: && ScannerUtilities
1316: .isCSSNameCharacter((char) current)) {
1317: do {
1318: nextChar();
1319: } while (current != -1
1320: && ScannerUtilities
1321: .isCSSNameCharacter((char) current));
1322: return LexicalUnits.DIMENSION;
1323: }
1324: return LexicalUnits.PX;
1325: default:
1326: while (current != -1
1327: && ScannerUtilities
1328: .isCSSNameCharacter((char) current)) {
1329: nextChar();
1330: }
1331: return LexicalUnits.DIMENSION;
1332: }
1333: case 'r':
1334: case 'R':
1335: switch (nextChar()) {
1336: case 'a':
1337: case 'A':
1338: switch (nextChar()) {
1339: case 'd':
1340: case 'D':
1341: nextChar();
1342: if (current != -1
1343: && ScannerUtilities
1344: .isCSSNameCharacter((char) current)) {
1345: do {
1346: nextChar();
1347: } while (current != -1
1348: && ScannerUtilities
1349: .isCSSNameCharacter((char) current));
1350: return LexicalUnits.DIMENSION;
1351: }
1352: return LexicalUnits.RAD;
1353: }
1354: default:
1355: while (current != -1
1356: && ScannerUtilities
1357: .isCSSNameCharacter((char) current)) {
1358: nextChar();
1359: }
1360: return LexicalUnits.DIMENSION;
1361: }
1362: case 's':
1363: case 'S':
1364: nextChar();
1365: return LexicalUnits.S;
1366: default:
1367: if (current != -1
1368: && ScannerUtilities
1369: .isCSSIdentifierStartCharacter((char) current)) {
1370: do {
1371: nextChar();
1372: } while (current != -1
1373: && ScannerUtilities
1374: .isCSSNameCharacter((char) current));
1375: return LexicalUnits.DIMENSION;
1376: }
1377: return (integer) ? LexicalUnits.INTEGER : LexicalUnits.REAL;
1378: }
1379: }
1380:
1381: /**
1382: * Scans an escape sequence, if one.
1383: */
1384: protected void escape() throws IOException {
1385: if (ScannerUtilities.isCSSHexadecimalCharacter((char) current)) {
1386: nextChar();
1387: if (!ScannerUtilities
1388: .isCSSHexadecimalCharacter((char) current)) {
1389: if (ScannerUtilities.isCSSSpace((char) current)) {
1390: nextChar();
1391: }
1392: return;
1393: }
1394: nextChar();
1395: if (!ScannerUtilities
1396: .isCSSHexadecimalCharacter((char) current)) {
1397: if (ScannerUtilities.isCSSSpace((char) current)) {
1398: nextChar();
1399: }
1400: return;
1401: }
1402: nextChar();
1403: if (!ScannerUtilities
1404: .isCSSHexadecimalCharacter((char) current)) {
1405: if (ScannerUtilities.isCSSSpace((char) current)) {
1406: nextChar();
1407: }
1408: return;
1409: }
1410: nextChar();
1411: if (!ScannerUtilities
1412: .isCSSHexadecimalCharacter((char) current)) {
1413: if (ScannerUtilities.isCSSSpace((char) current)) {
1414: nextChar();
1415: }
1416: return;
1417: }
1418: nextChar();
1419: if (!ScannerUtilities
1420: .isCSSHexadecimalCharacter((char) current)) {
1421: if (ScannerUtilities.isCSSSpace((char) current)) {
1422: nextChar();
1423: }
1424: return;
1425: }
1426: }
1427: if ((current >= ' ' && current <= '~') || current >= 128) {
1428: nextChar();
1429: return;
1430: }
1431: throw new ParseException("character", reader.getLine(), reader
1432: .getColumn());
1433: }
1434:
1435: /**
1436: * Compares the given int with the given character, ignoring case.
1437: */
1438: protected static boolean isEqualIgnoreCase(int i, char c) {
1439: return (i == -1) ? false : Character.toLowerCase((char) i) == c;
1440: }
1441:
1442: /**
1443: * Sets the value of the current char to the next character or -1 if the
1444: * end of stream has been reached.
1445: */
1446: protected int nextChar() throws IOException {
1447: current = reader.read();
1448:
1449: if (current == -1) {
1450: return current;
1451: }
1452:
1453: if (position == buffer.length) {
1454: char[] t = new char[position * 3 / 2];
1455: for (int i = 0; i < position; i++) {
1456: t[i] = buffer[i];
1457: }
1458: buffer = t;
1459: }
1460:
1461: return buffer[position++] = (char) current;
1462: }
1463: }
|