/** * With the start token expected to be the first token of a selector group, create and issue the * group, then invoke handleDeclarationBlock. At exit the last token returned from the iterator is * expected to be '}'. */ private void handleRuleSet( CssToken start, final CssTokenIterator iter, final CssContentHandler doc, CssErrorHandler err) throws CssException { char errChar = '{'; try { List<CssSelector> selectors = handleSelectors(start, iter, err); errChar = '}'; if (selectors == null) { // handleSelectors() has issued errors, we forward iter.next(MATCH_CLOSEBRACE); return; } if (debug) { checkState(iter.last.getChar() == '{'); checkState(!selectors.isEmpty()); } doc.selectors(selectors); handleDeclarationBlock(iter.next(), iter, doc, err); doc.endSelectors(selectors); } catch (NoSuchElementException nse) { err.error( new CssGrammarException(GRAMMAR_PREMATURE_EOF, iter.last.location, "'" + errChar + "'")); throw new PrematureEOFException(); } if (debug) { checkState(iter.last.getChar() == '}'); } }
/** * With start inparam being the first significant token in a selector, build the selector group * (aka comma separated selectors), expected return when iter.last is '{'. On error, issue to * errorlistener, and return (caller will forward). * * @return A syntactically valid CssSelector list, or null if fail. * @throws CssException */ private List<CssSelector> handleSelectors( CssToken start, CssTokenIterator iter, CssErrorHandler err) throws CssException { List<CssSelector> selectors = Lists.newArrayList(); boolean end = false; while (true) { // comma loop CssSelector selector = new CssSelector(start.location); while (true) { // combinator loop CssSimpleSelectorSequence seq = CssSelectorConstructFactory.createSimpleSelectorSequence(start, iter, err); if (seq == null) { // errors already issued return null; } selector.components.add(seq); int idx = iter.index(); start = iter.next(); if (MATCH_OPENBRACE.apply(start)) { end = true; break; } if (MATCH_COMMA.apply(start)) { break; } CssSelectorCombinator comb = CssSelectorConstructFactory.createCombinator(start, iter, err); if (comb != null) { selector.components.add(comb); start = iter.next(); } else if (iter.list.get(idx + 1).type == CssToken.Type.S) { selector.components.add(new CssSelectorCombinator(' ', start.location)); } else { err.error( new CssGrammarException( GRAMMAR_UNEXPECTED_TOKEN, iter.last.location, iter.last.chars)); return null; } } // combinator loop selectors.add(selector); if (end) { break; } if (debug) { checkState(MATCH_COMMA.apply(start)); } start = iter.next(); } // comma loop return selectors; }
/** * Append property value components to declaration.value, return false if fail with iter.last at * the the token which caused the fail. */ private boolean handlePropertyValue( CssDeclaration declaration, CssToken start, CssTokenIterator iter, boolean isStyleAttribute) { // we dont worry about EOF here, throw to caller while (true) { if (start.type == CssToken.Type.IMPORTANT) { declaration.important = true; } else { CssConstruct cc = CssConstructFactory.create( start, iter, MATCH_SEMI_CLOSEBRACE, ContextRestrictions.PROPERTY_VALUE); if (cc == null) { return false; } else { declaration.components.add(cc); } } // if isStyleAttribute, then parse as declaration-list grammar, // i.e. no braces if ((isStyleAttribute && !iter.hasNext()) || (MATCH_SEMI.apply(iter.peek())) || (!isStyleAttribute && MATCH_CLOSEBRACE.apply(iter.peek()))) { return declaration.components.size() > 0; } else { start = iter.next(); } } }
/** Parse a CSS style attribute. */ public void parseStyleAttribute( final Reader reader, String systemID, final CssErrorHandler err, final CssContentHandler doc) throws IOException, CssException { CssTokenIterator iter = scan(reader, systemID, err); doc.startDocument(); while (iter.hasNext()) { CssToken tk = iter.next(); if (MATCH_SEMI.apply(tk)) { continue; // starting with ';' is allowed, Issue 238 } try { CssDeclaration decl = handleDeclaration(tk, iter, doc, err, true); if (decl != null) { doc.declaration(decl); } else { // #handleDeclaration has issued errors return; } } catch (PrematureEOFException te) { // The subroutines report premature EOF to ErrHandler // on occurrence; if the listener rethrows it will // be a CssException so we don't catch it here. break; } } doc.endDocument(); }
/** * With start token being the first non-ignorable token inside the declaration block, iterate * issuing CssDeclaration objects until the block ends. */ private void handleDeclarationBlock( CssToken start, CssTokenIterator iter, final CssContentHandler doc, CssErrorHandler err) throws CssException { while (true) { if (MATCH_CLOSEBRACE.apply(start)) { return; } CssDeclaration decl = handleDeclaration(start, iter, doc, err, false); try { if (decl != null) { doc.declaration(decl); if (debug) { checkState(MATCH_SEMI_CLOSEBRACE.apply(iter.last)); } // continue or return: we may be at "; next decl" or "}" or ";}" if (MATCH_CLOSEBRACE.apply(iter.last)) { return; } else if (MATCH_SEMI.apply(iter.last) && MATCH_CLOSEBRACE.apply(iter.peek())) { iter.next(); return; } else { if (debug) { checkState(MATCH_SEMI.apply(iter.last)); } // we have ';', expect another decl start = iter.next(); // first token after ';' } } else { // #handleDeclaration returned null to signal error // #handleDeclaration has issued errors, we forward start = iter.next(MATCH_SEMI_CLOSEBRACE); if (MATCH_SEMI.apply(start)) { start = iter.next(); } } } catch (NoSuchElementException nse) { err.error( new CssGrammarException( GRAMMAR_PREMATURE_EOF, iter.last.location, "';' " + Messages.get("or") + " '}'")); throw new PrematureEOFException(); } } }
/** Parse a CSS document. */ public void parse( final Reader reader, final String systemID, final CssErrorHandler err, final CssContentHandler doc) throws IOException, CssException { CssTokenIterator iter = scan(reader, systemID, err); doc.startDocument(); while (iter.hasNext(FILTER_S_CMNT_CDO_CDC)) { CssToken tk = iter.next(FILTER_S_CMNT_CDO_CDC); try { if (tk.type == CssToken.Type.ATKEYWORD) { handleAtRule(tk, iter, doc, err); if (debug) { checkArgument(MATCH_SEMI_CLOSEBRACE.apply(iter.last)); } } else { handleRuleSet(tk, iter, doc, err); if (debug) { checkArgument(MATCH_CLOSEBRACE.apply(iter.last)); } } } catch (PrematureEOFException te) { // The subroutines report premature EOF to ErrHandler // on occurrence; if the listener rethrows it will // be a CssException so we don't catch it here. break; } } doc.endDocument(); }
/** * With start token required to be an ATKEYWORD, collect at-rule parameters if any, and if the * at-rule has a block, invoke those handlers. */ private void handleAtRule( CssToken start, CssTokenIterator iter, CssContentHandler doc, CssErrorHandler err) throws CssException { if (debug) { checkArgument(start.type == CssToken.Type.ATKEYWORD); checkArgument(iter.index() == iter.list.indexOf(start)); checkArgument(iter.filter() == FILTER_S_CMNT); } CssAtRule atRule = new CssAtRule(start.getChars(), start.location); CssToken tk; try { while (true) { tk = iter.next(); if (MATCH_SEMI_OPENBRACE.apply(tk)) { // ';' or '{', expected end atRule.hasBlock = tk.getChar() == '{'; break; } else { CssConstruct param = handleAtRuleParam(tk, iter, doc, err); if (param == null) { // issue error, forward, then return err.error( new CssGrammarException( GRAMMAR_UNEXPECTED_TOKEN, iter.last.location, iter.last.chars)); // skip to atrule closebrace, ignoring any inner blocks int stack = 0; while (true) { CssToken tok = iter.next(); if (MATCH_SEMI.apply(tok) && stack == 0) { return; // a non-block at rule } else if (MATCH_OPENBRACE.apply(tok)) { stack++; } else if (MATCH_CLOSEBRACE.apply(tok)) { if (stack == 1) { break; } stack--; } } return; } else { atRule.components.add(param); } } } } catch (NoSuchElementException nse) { // UAs required to close any open constructs on premature EOF doc.startAtRule(atRule); err.error( new CssGrammarException( GRAMMAR_PREMATURE_EOF, iter.last.location, "';' " + Messages.get("or") + " '{'")); doc.endAtRule(atRule.getName().get()); throw new PrematureEOFException(); } if (debug) { checkArgument(MATCH_SEMI_OPENBRACE.apply(iter.last)); checkArgument(iter.filter() == FILTER_S_CMNT); } // ending up here only on expected end doc.startAtRule(atRule); if (atRule.hasBlock) { try { if (hasRuleSet(atRule, iter)) { while (!MATCH_CLOSEBRACE.apply(iter.next())) { if (iter.last.type == CssToken.Type.ATKEYWORD) { handleAtRule(iter.last, iter, doc, err); } else { handleRuleSet(iter.last, iter, doc, err); } } } else { handleDeclarationBlock(iter.next(), iter, doc, err); } } catch (NoSuchElementException nse) { err.error(new CssGrammarException(GRAMMAR_PREMATURE_EOF, iter.last.location, "'}'")); doc.endAtRule(atRule.name.get()); throw new PrematureEOFException(); } } doc.endAtRule(atRule.name.get()); }
/** * With start expected to be an IDENT token representing the property name, build the declaration * and return after hitting ';' or '}'. On error, issue to errhandler, return null, caller * forwards. */ private CssDeclaration handleDeclaration( CssToken name, CssTokenIterator iter, CssContentHandler doc, CssErrorHandler err, boolean isStyleAttribute) throws CssException { if (name.type != CssToken.Type.IDENT) { err.error( new CssGrammarException( GRAMMAR_EXPECTING_TOKEN, name.location, name.getChars(), Messages.get("a_property_name"))); return null; } CssDeclaration declaration = new CssDeclaration(name.getChars(), name.location); try { if (!MATCH_COLON.apply(iter.next())) { err.error( new CssGrammarException( GRAMMAR_EXPECTING_TOKEN, name.location, iter.last.getChars(), ":")); return null; } } catch (NoSuchElementException nse) { err.error(new CssGrammarException(GRAMMAR_PREMATURE_EOF, iter.last.location, ":")); throw new PrematureEOFException(); } try { while (true) { CssToken value = iter.next(); if (MATCH_SEMI_CLOSEBRACE.apply(value)) { if (declaration.components.size() < 1) { err.error( new CssGrammarException( GRAMMAR_EXPECTING_TOKEN, iter.last.location, value.getChar(), Messages.get("a_property_value"))); return null; } else { return declaration; } } else { if (!handlePropertyValue(declaration, value, iter, isStyleAttribute)) { err.error( new CssGrammarException( GRAMMAR_UNEXPECTED_TOKEN, iter.last.location, iter.last.getChars())); return null; } else { if (isStyleAttribute && !iter.hasNext()) { return declaration; } } } } } catch (NoSuchElementException nse) { err.error( new CssGrammarException( GRAMMAR_PREMATURE_EOF, iter.last.location, "';' " + Messages.get("or") + " '}'")); throw new PrematureEOFException(); } }