2 * Copyright (c) 2003-2004 Christopher Lenz and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Common Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/cpl-v10.html
9 * Christopher Lenz - initial API and implementation
11 * $Id: DefaultCssParser.java,v 1.1 2004-09-02 18:07:13 jsurfer Exp $
14 package net.sourceforge.phpeclipse.css.core.internal.parser;
16 import java.util.ArrayList;
17 import java.util.Arrays;
18 import java.util.List;
20 import net.sourceforge.phpeclipse.core.model.ISourceReference;
21 import net.sourceforge.phpeclipse.core.model.SourceReference;
22 import net.sourceforge.phpeclipse.css.core.internal.model.AtRule;
23 import net.sourceforge.phpeclipse.css.core.internal.model.Declaration;
24 import net.sourceforge.phpeclipse.css.core.internal.model.StyleRule;
25 import net.sourceforge.phpeclipse.css.core.internal.text.CssTextUtils;
26 import net.sourceforge.phpeclipse.css.core.model.IAtRule;
27 import net.sourceforge.phpeclipse.css.core.model.IDeclaration;
28 import net.sourceforge.phpeclipse.css.core.model.IRule;
29 import net.sourceforge.phpeclipse.css.core.model.IStyleRule;
30 import net.sourceforge.phpeclipse.css.core.model.IStyleSheet;
31 import net.sourceforge.phpeclipse.css.core.parser.ICssParser;
32 import net.sourceforge.phpeclipse.css.core.parser.ICssScanner;
33 import net.sourceforge.phpeclipse.css.core.parser.ICssTokens;
34 import net.sourceforge.phpeclipse.css.core.parser.IProblem;
35 import net.sourceforge.phpeclipse.css.core.parser.IProblemCollector;
36 import net.sourceforge.phpeclipse.css.core.parser.LexicalErrorException;
37 import net.sourceforge.phpeclipse.css.core.parser.SyntaxErrorException;
39 import org.eclipse.jface.text.IDocument;
42 * A simple CSS parser implementation.
44 * TODO At-rules can contain child rules as well as declarations. Currently we
45 * only handle child rules.
47 public class DefaultCssParser extends AbstractProblemReporter
48 implements ICssParser {
50 // Instance Variables ------------------------------------------------------
53 * The underlying CSS token scanner.
55 private ICssScanner scanner;
58 * The token being currently processed.
60 private int currentToken;
63 * Whether the parser is currently initializer. Is <code>true</code> as soon
64 * as currentToken has been initialized.
66 private boolean initialized;
68 // ICssParser Implementation -----------------------------------------------
71 * @see ICssParser#parseRules(IStyleSheet)
73 public IRule[] parseRules(IStyleSheet styleSheet)
74 throws LexicalErrorException, SyntaxErrorException {
75 List rules = new ArrayList();
77 if (currentToken == ICssTokens.CDO) {
78 // ignore an opening SGML comment delimiter at the start of the
82 while (currentToken != ICssTokens.EOF) {
83 if (currentToken == ICssTokens.CDC) {
84 // ignore a closing SGML comment delimiter at the end of the
86 IProblem problem = createWarning(
87 "illegalPositionForCDC", //$NON-NLS-1$
88 scanner.getTokenRegion());
89 if (nextToken() != ICssTokens.EOF) {
90 reportProblem(problem);
92 } else if (currentToken == ICssTokens.CDO) {
93 reportWarning("illegalPositionForCDO", //$NON-NLS-1$
94 scanner.getTokenRegion());
97 IRule rule = parseRule(styleSheet, null);
105 return (IRule[]) rules.toArray(new IRule[rules.size()]);
109 * Parses a CSS rule, which can be either an at-rule like
110 * <code>@import</code> or a normal style rule.
112 * @param parent the parent rule in which the rule to parse is nested, or
113 * <code>null</code> if the rule to parse is at the top level of the
115 * @return the parsed rule
116 * @throws LexicalErrorException if a lexical error is reported by the
118 * @throws SyntaxErrorException if a syntax error has been encountered from
119 * which recovery is not possible
121 public IRule parseRule(IStyleSheet styleSheet, IRule parent)
122 throws LexicalErrorException, SyntaxErrorException {
126 if ((currentToken == -1) || (currentToken == '}')) {
127 reportError("expectedRule", //$NON-NLS-1$
128 scanner.getTokenRegion());
131 switch (currentToken) {
132 case ICssTokens.AT: {
133 return parseAtRule(styleSheet, parent);
136 return parseStyleRule(styleSheet, parent);
142 * Parses a CSS selector. The provided reader is expected to be positioned
143 * at the beginning of the selector (leading whitespace will be ignored).
145 * @return the parsed selector
146 * @throws LexicalErrorException if a lexical error is reported by the
148 * @throws SyntaxErrorException if a syntax error has been encountered from
149 * which recovery is not possible
151 public ISourceReference parseSelector(IRule rule)
152 throws LexicalErrorException, SyntaxErrorException {
156 return parseAnythingUpto(new int[] { ICssTokens.LBRACE });
160 * Parses a declaration. The provided reader is expected to be positioned at
161 * the beginning of the property name (leading whitespace will be ignored).
162 * The return declaration will start with the start of the property, and
163 * will end at the end of the value, excluding the semicolon, if one is
166 * @return the parsed style declaration
167 * @throws LexicalErrorException if a lexical error is reported by the
169 * @throws SyntaxErrorException if a syntax error has been encountered from
170 * which recovery is not possible
172 public IDeclaration parseDeclaration(IRule rule)
173 throws LexicalErrorException, SyntaxErrorException {
177 if ((currentToken == -1) || (currentToken == '}')) {
180 Declaration declaration = new Declaration(document, rule);
181 MutableRegion declarationRegion = newRegion();
183 // parse the property
184 if (currentToken == ICssTokens.IDENT) {
185 SourceReference property = new SourceReference(document);
186 property.setSourceRegion(scanner.getTokenRegion());
187 declaration.setProperty(property);
188 nextToken(declarationRegion);
190 reportError("expectedProperty", //$NON-NLS-1$
191 scanner.getTokenRegion());
194 // expect a colon to delimit the property from the value
195 if (currentToken == ICssTokens.COLON) {
196 nextToken(declarationRegion);
198 reportError("expectedToken", ":", //$NON-NLS-1$ //$NON-NLS-2$
199 scanner.getTokenRegion());
202 // parse the declaration value (excluding the priority)
203 ISourceReference value = parseAnythingUpto(new int[] {
204 ICssTokens.RBRACE, ICssTokens.SEMICOLON, ICssTokens.EXCLAMATION
207 declaration.setValue(value);
208 declarationRegion.add(value.getSourceRegion());
210 reportError("emptyDeclarationValue", //$NON-NLS-1$
211 scanner.getTokenRegion());
214 // if present, parse the declaration priority
215 if (currentToken == '!') {
216 SourceReference priority = new SourceReference(document);
217 MutableRegion priorityRegion = newRegion();
218 if (nextToken() == ICssTokens.IDENT) {
219 nextToken(priorityRegion);
221 reportError("priorityExpected", //$NON-NLS-1$
222 scanner.getTokenRegion());
224 priority.setSourceRegion(priorityRegion);
225 declaration.setPriority(priority);
226 declarationRegion.add(priority.getSourceRegion());
229 declaration.setSourceRegion(declarationRegion);
234 * @see ICssParser#setProblemCollector(IProblemCollector)
236 public void setProblemCollector(IProblemCollector problemCollector) {
237 super.setProblemCollector(problemCollector);
238 if (scanner != null) {
239 scanner.setProblemCollector(problemCollector);
244 * @see ICssParser#setSource(IDocument)
246 public void setSource(IDocument document) {
247 setDocument(document);
248 if (scanner != null) {
249 scanner.setSource(document);
253 // Protected Methods -------------------------------------------------------
255 protected ICssScanner createScanner(IDocument document) {
256 ICssScanner retVal = new DefaultCssScanner();
257 retVal.setSource(document);
258 retVal.setProblemCollector(problemCollector);
263 * Parses a region of the source until one of the specified tokens is
264 * encountered. In addition, this method keeps track of opened parenthesis
265 * and brackets, and will report an error if they are not closed.
267 * @param tokens an array containing the tokens that should delimit the
268 * range of source to parse
269 * @return the parsed source reference
270 * @throws LexicalErrorException if a lexical error is reported by the
272 * @throws SyntaxErrorException if a syntax error has been encountered from
273 * which recovery is not possible
275 protected final ISourceReference parseAnythingUpto(int tokens[])
276 throws LexicalErrorException, SyntaxErrorException {
278 SourceReference any = null;
279 MutableRegion region = newRegion();
280 while (currentToken != ICssTokens.EOF) {
281 if (Arrays.binarySearch(tokens, currentToken) >= 0) {
284 any = new SourceReference(document);
285 if ((currentToken == '(') || (currentToken == '[')) {
286 char peer = CssTextUtils.getPeerCharacter(
287 (char) currentToken);
289 ISourceReference ref =
290 parseAnythingUpto(new int[] { peer } );
291 if (currentToken != peer) {
292 reportError("expectedToken", //$NON-NLS-1$
293 String.valueOf(peer), scanner.getTokenRegion());
296 region.add(ref.getSourceRegion());
303 any.setSourceRegion(region);
311 * @param styleSheet the owning style sheet
312 * @param parent the rule in which the rule to parse is nested, or
313 * <code>null</code> if this rule is at the top level of the style
315 * @return the parsed rule
316 * @throws LexicalErrorException if a lexical error is reported by the
318 * @throws SyntaxErrorException if a syntax error has been encountered from
319 * which recovery is not possible
321 protected final IAtRule parseAtRule(IStyleSheet styleSheet, IRule parent)
322 throws LexicalErrorException, SyntaxErrorException {
323 AtRule rule = new AtRule(document, styleSheet, parent);
324 MutableRegion ruleRegion = newRegion();
325 if (currentToken == ICssTokens.AT) {
328 reportError("expectedToken", "@", //$NON-NLS-1$//$NON-NLS-2$
329 scanner.getTokenRegion());
331 if (currentToken == ICssTokens.IDENT) {
332 SourceReference name = new SourceReference(document);
333 name.setSourceRegion(scanner.getTokenRegion());
335 nextToken(ruleRegion);
337 reportError("expectedAtKeyword", //$NON-NLS-1$
338 scanner.getTokenRegion());
340 if ((currentToken != ICssTokens.LBRACE)
341 && (currentToken != ICssTokens.SEMICOLON)
342 && (currentToken != ICssTokens.EOF)) {
343 ISourceReference value = parseAnythingUpto(new int[] {
344 ICssTokens.LBRACE, ICssTokens.SEMICOLON
346 rule.setValue(value);
347 ruleRegion.add(value.getSourceRegion());
349 if (currentToken == ICssTokens.LBRACE) {
350 nextToken(ruleRegion);
351 while (currentToken != ICssTokens.RBRACE) {
352 IRule child = parseRule(styleSheet, rule);
354 rule.addChild(child);
355 ruleRegion.add(child.getSourceRegion());
357 if (currentToken == ICssTokens.EOF) {
358 reportError("unexpectedEndOfFile", //$NON-NLS-1$
359 scanner.getTokenRegion());
363 if (currentToken == ICssTokens.RBRACE) {
364 nextToken(ruleRegion);
366 reportError("expectedToken", "}", //$NON-NLS-1$ //$NON-NLS-2$
367 scanner.getTokenRegion());
370 if ((rule.getValue() == null) && (rule.getChildren().length == 0)) {
371 reportError("expectedValueOrBlock", //$NON-NLS-1$
372 scanner.getTokenRegion());
373 } else if (currentToken != ICssTokens.SEMICOLON) {
374 reportError("expectedToken", ";", //$NON-NLS-1$ //$NON-NLS-2$
375 scanner.getTokenRegion());
377 if (currentToken == ICssTokens.SEMICOLON) {
378 nextToken(ruleRegion);
381 rule.setSourceRegion(ruleRegion);
386 * Parses a style rule.
389 * This corresponds to the <code>ruleset</code> production in the
390 * specification of the CSS 3 syntax module:
393 * selector? '{' S* declaration? [ ';' S* declaration? ]* '}' S*
397 * <strong>Question</strong>: why is the selector optional in the above
398 * production? In section 4.8 the specification says that <em>"A rule set
399 * (also called 'rule') consists of a selector followed by a declaration
400 * block."</em> So we should be able to assume that there
401 * <strong>must</strong> be a selector, and otherwise report an error.
404 * @param parent the parent rule in which the style rule is nested, or
405 * <code>null</code> if the style rule to parse is at the top level
407 * @return the parsed style rule
408 * @throws LexicalErrorException if a lexical error is reported by the
410 * @throws SyntaxErrorException if a syntax error has been encountered from
411 * which recovery is not possible
413 protected final IStyleRule parseStyleRule(IStyleSheet styleSheet,
414 IRule parent) throws LexicalErrorException, SyntaxErrorException {
415 StyleRule rule = new StyleRule(document, styleSheet, parent);
416 MutableRegion ruleRegion = newRegion();
417 ISourceReference selector = parseSelector(rule);
418 if (selector != null) {
419 rule.setSelector(selector);
421 reportError("expectedSelectorOrAtKeyword", //$NON-NLS-1$
422 scanner.getTokenRegion());
424 if (currentToken == ICssTokens.LBRACE) {
425 nextToken(ruleRegion);
427 IDeclaration declaration = parseDeclaration(rule);
428 if (declaration != null) {
429 rule.addDeclaration(declaration);
430 ruleRegion.add(declaration.getSourceRegion());
432 if (currentToken == ';') {
433 nextToken(ruleRegion);
437 } while ((currentToken != ICssTokens.RBRACE)
438 && (currentToken != ICssTokens.EOF));
439 if (currentToken == ICssTokens.RBRACE) {
440 nextToken(ruleRegion);
442 reportError("expectedToken", "}", //$NON-NLS-1$ //$NON-NLS-2$
443 scanner.getTokenRegion());
446 reportError("expectedToken", "{", //$NON-NLS-1$ //$NON-NLS-2$
447 scanner.getTokenRegion());
449 rule.setSourceRegion(ruleRegion);
453 // Private Methods ---------------------------------------------------------
455 private int nextToken() throws LexicalErrorException {
456 return nextToken(null);
459 private int nextToken(MutableRegion region) throws LexicalErrorException {
461 if (scanner == null) {
462 scanner = createScanner(document);
464 if (region != null) {
465 region.add(scanner.getTokenRegion());
467 currentToken = scanner.getNextToken();
471 private MutableRegion newRegion() {
472 return new MutableRegion(scanner.getTokenRegion());