001: /*
002: * CSSFormatter.java
003: *
004: * Copyright (C) 2002 Peter Graves
005: * $Id: CSSFormatter.java,v 1.1.1.1 2002/09/24 16:08:12 piso Exp $
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * as published by the Free Software Foundation; either version 2
010: * of the License, or (at your option) any later version.
011: *
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with this program; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
020: */
021:
022: package org.armedbear.j;
023:
024: public final class CSSFormatter extends Formatter implements Constants {
025: // States (used in line flags).
026: private static final int CSS_STATE_QUOTE = 0x0001;
027: private static final int CSS_STATE_SINGLEQUOTE = 0x0002;
028: private static final int CSS_STATE_COMMENT = 0x0004;
029: private static final int CSS_STATE_BRACE = 0x0008;
030: private static final int CSS_STATE_IN_BLOCK = 0x0010;
031: private static final int CSS_STATE_NUMBER = 0x0020;
032: private static final int CSS_STATE_PROPERTY = 0x0040;
033: private static final int CSS_STATE_VALUE = 0x0080;
034: private static final int CSS_STATE_SELECTOR = 0x0100;
035:
036: // Formats.
037: private static final int CSS_FORMAT_TEXT = 0;
038: private static final int CSS_FORMAT_COMMENT = 1;
039: private static final int CSS_FORMAT_STRING = 2;
040: private static final int CSS_FORMAT_PROPERTY = 3;
041: private static final int CSS_FORMAT_BRACE = 4;
042: private static final int CSS_FORMAT_NUMBER = 5;
043: private static final int CSS_FORMAT_SELECTOR = 6;
044:
045: private static final CSSMode mode = CSSMode.getMode();
046:
047: public CSSFormatter(Buffer buffer) {
048: this .buffer = buffer;
049: }
050:
051: private int tokenBegin = 0;
052:
053: private void endToken(String text, int tokenEnd, int state) {
054: if (tokenEnd - tokenBegin > 0) {
055: int format = CSS_FORMAT_TEXT;
056: if ((state & (CSS_STATE_QUOTE | CSS_STATE_SINGLEQUOTE)) != 0)
057: format = CSS_FORMAT_STRING;
058: else if ((state & CSS_STATE_COMMENT) != 0)
059: format = CSS_FORMAT_COMMENT;
060: else if ((state & CSS_STATE_BRACE) != 0)
061: format = CSS_FORMAT_BRACE;
062: else if ((state & CSS_STATE_NUMBER) != 0)
063: format = CSS_FORMAT_NUMBER;
064: else if ((state & CSS_STATE_PROPERTY) != 0)
065: format = CSS_FORMAT_PROPERTY;
066: else if ((state & CSS_STATE_VALUE) != 0)
067: format = CSS_FORMAT_TEXT;
068: else if ((state & CSS_STATE_SELECTOR) != 0)
069: format = CSS_FORMAT_SELECTOR;
070: addSegment(text, tokenBegin, tokenEnd, format);
071: tokenBegin = tokenEnd;
072: }
073: }
074:
075: private void parseLine(Line line) {
076: final String text;
077: if (Editor.tabsAreVisible())
078: text = Utilities.makeTabsVisible(line.getText(), buffer
079: .getTabWidth());
080: else
081: text = Utilities
082: .detab(line.getText(), buffer.getTabWidth());
083: tokenBegin = 0;
084: int state = line.flags();
085: int i = 0;
086: final int limit = text.length();
087: // Skip whitespace at start of line.
088: while (i < limit) {
089: if (Character.isWhitespace(text.charAt(i)))
090: ++i;
091: else {
092: endToken(text, i, state);
093: break;
094: }
095: }
096: while (i < limit) {
097: final char c = text.charAt(i);
098: if (c == '\\' && i < limit - 1) {
099: // Escape character.
100: i += 2;
101: continue;
102: }
103: if ((state & CSS_STATE_COMMENT) != 0) {
104: if (i < limit - 1 && c == '*'
105: && text.charAt(i + 1) == '/') {
106: endToken(text, i + 2, state);
107: state &= ~CSS_STATE_COMMENT;
108: i += 2;
109: } else
110: ++i;
111: continue;
112: }
113: if ((state & CSS_STATE_QUOTE) != 0) {
114: if (c == '"') {
115: endToken(text, i + 1, state);
116: state &= ~CSS_STATE_QUOTE;
117: }
118: ++i;
119: continue;
120: }
121: if ((state & CSS_STATE_SINGLEQUOTE) != 0) {
122: if (c == '"') {
123: endToken(text, i + 1, state);
124: state &= ~CSS_STATE_SINGLEQUOTE;
125: }
126: ++i;
127: continue;
128: }
129: // Reaching here, we're not in a comment or a quoted string.
130: if (c == '"') {
131: endToken(text, i, state);
132: state |= CSS_STATE_QUOTE;
133: ++i;
134: continue;
135: }
136: if (c == '\'') {
137: endToken(text, i, state);
138: state |= CSS_STATE_SINGLEQUOTE;
139: ++i;
140: continue;
141: }
142: if (c == '/') {
143: if (i < limit - 1) {
144: if (text.charAt(i + 1) == '*') {
145: endToken(text, i, state);
146: state |= CSS_STATE_COMMENT;
147: i += 2;
148: } else
149: ++i;
150: } else
151: ++i;
152: continue;
153: }
154: if (c == '{') {
155: endToken(text, i, state);
156: endToken(text, ++i, CSS_STATE_BRACE);
157: state |= CSS_STATE_IN_BLOCK;
158: continue;
159: }
160: if (c == '}') {
161: endToken(text, i, state);
162: endToken(text, ++i, CSS_STATE_BRACE);
163: state &= ~CSS_STATE_IN_BLOCK;
164: continue;
165: }
166: if ((state & CSS_STATE_IN_BLOCK) != 0) {
167: if ((state & CSS_STATE_NUMBER) != 0) {
168: boolean isNumeric;
169: if ("0123456789".indexOf(c) >= 0) {
170: // Definitely a number.
171: isNumeric = true;
172: } else if ("abcdefABCDEF".indexOf(c) >= 0) {
173: if (c == 'e' && i < limit - 1
174: && text.charAt(i + 1) == 'm') {
175: // Not a number ("em").
176: isNumeric = false;
177: } else {
178: // Hex digit.
179: isNumeric = true;
180: }
181: } else
182: isNumeric = false;
183: if (!isNumeric) {
184: // Not a number.
185: endToken(text, i, state);
186: state &= ~CSS_STATE_NUMBER;
187: }
188: ++i;
189: continue;
190: }
191: if ((state & CSS_STATE_VALUE) != 0) {
192: if (c == '#' || Character.isDigit(c)) {
193: endToken(text, i, state);
194: state |= CSS_STATE_NUMBER;
195: ++i;
196: continue;
197: }
198: if (c == '-' && i < limit - 1
199: && Character.isDigit(text.charAt(i + 1))) {
200: endToken(text, i, state);
201: state |= CSS_STATE_NUMBER;
202: i += 2;
203: continue;
204: }
205: if (c == ';') {
206: // End of value.
207: endToken(text, i, state);
208: state &= ~CSS_STATE_VALUE;
209: ++i;
210: continue;
211: }
212: ++i;
213: continue;
214: }
215: if (c == ':') {
216: endToken(text, i, CSS_STATE_PROPERTY);
217: state |= CSS_STATE_VALUE;
218: ++i;
219: continue;
220: }
221: }
222: if ((state & CSS_STATE_SELECTOR) != 0) {
223: if (!mode.isIdentifierPart(c)) {
224: endToken(text, i, state);
225: state &= ~CSS_STATE_SELECTOR;
226: }
227: ++i;
228: continue;
229: }
230: if (state == 0) {
231: if (mode.isIdentifierStart(c)) {
232: endToken(text, i, state);
233: state |= CSS_STATE_SELECTOR;
234: }
235: }
236: ++i;
237: }
238: // Reached end of line.
239: endToken(text, i, state);
240: }
241:
242: public LineSegmentList formatLine(Line line) {
243: clearSegmentList();
244: if (line == null) {
245: addSegment("", CSS_FORMAT_TEXT);
246: return segmentList;
247: }
248: parseLine(line);
249: return segmentList;
250: }
251:
252: public boolean parseBuffer() {
253: int state = 0;
254: Line line = buffer.getFirstLine();
255: boolean changed = false;
256: while (line != null) {
257: int oldflags = line.flags();
258: // Quoted strings can't span lines. (Can they?)
259: state &= ~(CSS_STATE_QUOTE | CSS_STATE_SINGLEQUOTE);
260: if (state != oldflags) {
261: line.setFlags(state);
262: changed = true;
263: }
264: final int limit = line.length();
265: for (int i = 0; i < limit; i++) {
266: char c = line.charAt(i);
267: if (c == '\\' && i < limit - 1) {
268: // Escape.
269: ++i;
270: continue;
271: }
272: if ((state & CSS_STATE_COMMENT) != 0) {
273: if (c == '*' && i < limit - 1) {
274: c = line.charAt(i + 1);
275: if (c == '/') {
276: ++i;
277: state &= ~CSS_STATE_COMMENT;
278: }
279: }
280: continue;
281: }
282: if ((state & CSS_STATE_QUOTE) != 0) {
283: if (c == '"')
284: state &= ~CSS_STATE_QUOTE;
285: continue;
286: }
287: if ((state & CSS_STATE_SINGLEQUOTE) != 0) {
288: if (c == '"')
289: state &= ~CSS_STATE_SINGLEQUOTE;
290: continue;
291: }
292: // Not in comment or quoted string.
293: if (c == '{') {
294: state |= CSS_STATE_IN_BLOCK;
295: continue;
296: }
297: if (c == '}') {
298: state &= ~CSS_STATE_IN_BLOCK;
299: continue;
300: }
301: if (c == '/' && i < limit - 1) {
302: c = line.charAt(++i);
303: if (c == '*')
304: state |= CSS_STATE_COMMENT;
305: } else if (c == '"') {
306: state |= CSS_STATE_QUOTE;
307: } else if (c == '\'') {
308: state |= CSS_STATE_SINGLEQUOTE;
309: }
310: }
311: line = line.next();
312: }
313: buffer.setNeedsParsing(false);
314: return changed;
315: }
316:
317: public FormatTable getFormatTable() {
318: if (formatTable == null) {
319: formatTable = new FormatTable("CSSMode");
320: formatTable.addEntryFromPrefs(CSS_FORMAT_TEXT, "text");
321: formatTable
322: .addEntryFromPrefs(CSS_FORMAT_COMMENT, "comment");
323: formatTable.addEntryFromPrefs(CSS_FORMAT_STRING, "string");
324: formatTable.addEntryFromPrefs(CSS_FORMAT_PROPERTY,
325: "property", "keyword");
326: formatTable.addEntryFromPrefs(CSS_FORMAT_BRACE, "brace");
327: formatTable.addEntryFromPrefs(CSS_FORMAT_NUMBER, "number");
328: formatTable.addEntryFromPrefs(CSS_FORMAT_SELECTOR,
329: "selector", "function");
330: }
331: return formatTable;
332: }
333: }
|