All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cz.vutbr.web.csskit.antlr4.CSSParserVisitorImpl Maven / Gradle / Ivy

Go to download

jStyleParser is a CSS parser written in Java. It has its own application interface that is designed to allow an efficient CSS processing in Java and mapping the values to the Java data types. It parses CSS 2.1 style sheets into structures that can be efficiently assigned to DOM elements. It is intended be the primary CSS parser for the CSSBox library. While handling errors, it is user agent conforming according to the CSS specification.

The newest version!
package cz.vutbr.web.csskit.antlr4;

import cz.vutbr.web.css.*;
import cz.vutbr.web.css.Selector.PseudoElement;
import cz.vutbr.web.csskit.RuleArrayList;
import cz.vutbr.web.csskit.antlr4.CSSParser.Bracketed_identsContext;
import cz.vutbr.web.csskit.antlr4.CSSParser.Ident_list_itemContext;
import cz.vutbr.web.csskit.antlr4.CSSParser.Keyframe_blockContext;
import cz.vutbr.web.csskit.antlr4.CSSParser.Keyframe_selectorContext;
import cz.vutbr.web.csskit.antlr4.CSSParser.Keyframes_nameContext;

import org.antlr.v4.runtime.CommonToken;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.RuleContext;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.*;

import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;


public class CSSParserVisitorImpl implements CSSParserVisitor, CSSParserExtractor {
    // factories for building structures
    private RuleFactory rf = CSSFactory.getRuleFactory();
    private TermFactory tf = CSSFactory.getTermFactory();

    private enum MediaQueryState {START, TYPE, AND, EXPR, TYPEOREXPR}

    //logger
    private org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(getClass());
    //counter of spaces for pretty debug printing
    private int spacesCounter = 0;

    // block preparator
    private Preparator preparator;

    // list of media queries to wrap rules
    private List wrapMedia;

    // structures after parsing
    private List importPaths = new ArrayList<>();
    private List> importMedia = new ArrayList<>();
    private RuleList rules = null;
    private List mediaQueryList = null;

    //prevent imports inside the style sheet
    private boolean preventImports = false;

    private void logEnter(String entry) {
        if (log.isTraceEnabled())
            log.trace("Enter: {}{}", generateSpaces(spacesCounter), entry);
    }

    private void logEnter(String entry, RuleContext ctx) {
        if (log.isTraceEnabled())
            log.trace("Enter: {}{}: >{}<", generateSpaces(spacesCounter), entry, ctx.getText());
    }
    
    private void logLeave(String leaving) {
        if (log.isTraceEnabled())
            log.trace("Leave: {}{}", generateSpaces(spacesCounter), leaving);
    }

    private String extractTextUnescaped(String text) {
        return org.unbescape.css.CssEscape.unescapeCss(text);
    }

    private Declaration.Source extractSource(CSSToken ct) {
        return new Declaration.Source(ct.getBase(), ct.getLine(), ct.getCharPositionInLine());
    }

    /**
     * extract base from parse tree node
     */
    private URL extractBase(TerminalNode node) {
        CSSToken ct = (CSSToken) node.getSymbol();
        return ct.getBase();
    }

    /**
     * check if string is valid ID
     *
     * @param id ID to validate and unescapes
     * @return unescaped id or null
     */
    private String extractIdUnescaped(String id) {
        if (!id.isEmpty() && !Character.isDigit(id.charAt(0))) {
            return org.unbescape.css.CssEscape.unescapeCss(id);
        }
        return null;
    }

    /**
     * generate spaces for pretty debug printing
     *
     * @param count number of generated spaces
     * @return string with spaces
     */
    private String generateSpaces(int count) {
        String spaces = "";
        for (int i = 0; i < count; i++) {
            spaces += " ";
        }
        return spaces;
    }

    /**
     * remove terminal node emtpy tokens from input list
     *
     * @param inputArrayList original list
     * @return list without terminal node type = S (space)
     */
    private List filterSpaceTokens(List inputArrayList) {
        List ret = new ArrayList(inputArrayList.size());
        for (ParseTree item : inputArrayList) {
            if (!(item instanceof TerminalNode) || ((TerminalNodeImpl) item).getSymbol().getType() != CSSLexer.S) {
                ret.add(item);
            }
        }
        return ret;
    }

    /**
     * check if rule context contains error node
     *
     * @param ctx rule context
     * @return contains context error node
     */
    private boolean ctxHasErrorNode(ParserRuleContext ctx) {
        for (int i = 0; i < ctx.children.size(); i++) {
            if (ctx.getChild(i) instanceof ErrorNode) {
                return true;
            }
        }
        return false;
    }

    /**
     * Tries to convert generic terms to more specific value types. Currently, colors (TermColor) and
     * rectangles (TermRect) are supported.
     * @param term the term to be converted
     * @return the corresponding more specific term type or {@code null} when nothing was found.
     */
    private Term findSpecificType(Term term)
    {
        TermColor colorTerm = null;
        TermRect rectTerm = null;
        if (term instanceof TermIdent) { //idents - try to convert colors
            colorTerm = tf.createColor((TermIdent) term);
        } else if (term instanceof TermFunction) { // rgba(0,0,0)
            colorTerm = tf.createColor((TermFunction) term);
            if (colorTerm == null)
                rectTerm = tf.createRect((TermFunction) term);
        }
        //replace with more specific value
        if (colorTerm != null) {
            if (log.isDebugEnabled()) {
                log.debug("term color is OK - creating - " + colorTerm.toString());
            }
            return colorTerm;
        } else if (rectTerm != null) {
            if (log.isDebugEnabled()) {
                log.debug("term rect is OK - creating - " + rectTerm.toString());
            }
            return rectTerm;
        } else
            return null;
    }
    
    /**
     * get parsed rulelist
     *
     * @return parsed rules
     */
    public RuleList getRules() {
        return rules;
    }

    /**
     * get mediaquery list
     *
     * @return media query list
     */
    public List getMedia() {
        return mediaQueryList;
    }

    /**
     * get import list
     *
     * @return list of urls to import
     */
    public List getImportPaths() {
        return importPaths;
    }

    /**
     * get media for imports
     *
     * @return list of media for imports
     */
    public List> getImportMedia() {
        return importMedia;
    }


    /**
     * Constructor
     *
     * @param preparator The preparator to be used for creating the rules.
     * @param wrapMedia  The media queries to be used for wrapping the created rules (e.g. in case
     *                   of parsing and imported style sheet) or null when no wrapping is required.
     */
    public CSSParserVisitorImpl(Preparator preparator, List wrapMedia) {
        this.preparator = preparator;
        this.wrapMedia = wrapMedia;
    }

    //used in parseMediaQuery
    public CSSParserVisitorImpl() {

    }
    /******************************************************************
     /******************************************************************
     /******************************************************************
     /******************************************************************
     /******************************************************************
     /******************************************************************
     /******************************************************************
     /******************************************************************
     /******************************************************************/


    /**
     * @param ctx the parse tree
     * @return RuleList
     * inlinestyle: S* (declarations | inlineset+ )
     */
    @Override
    public RuleList visitInlinestyle(CSSParser.InlinestyleContext ctx) {
        logEnter("inlinestyle");
        this.rules = new cz.vutbr.web.csskit.RuleArrayList();
        if (ctx.declarations() != null) {
            //declarations
            List decl = visitDeclarations(ctx.declarations());
            cz.vutbr.web.css.RuleBlock rb = preparator.prepareInlineRuleSet(decl, null);
            if (rb != null) {
                //rb is valid,add to rules
                this.rules.add(rb);
            }
        } else {
            //inlineset
            for (CSSParser.InlinesetContext ctxis : ctx.inlineset()) {
                cz.vutbr.web.css.RuleBlock irs = visitInlineset(ctxis);
                if (irs != null) {
                    //irs is valid, add to rules
                    this.rules.add(irs);
                }
            }
        }
        if (log.isDebugEnabled()) {
            log.debug("\n***\n{}\n***\n", this.rules);
        }
        logLeave("inlinestyle");
        return this.rules;
    }

    /**
     * Stylesheet, main rule
     * stylesheet: ( CDO | CDC  | S | nostatement | statement )*
     * statement* is only processed
     */
    @Override
    public RuleList visitStylesheet(CSSParser.StylesheetContext ctx) {
        logEnter("stylesheet: ", ctx);
        this.rules = new RuleArrayList();
        //statement*
        for (CSSParser.StatementContext stmt : ctx.statement()) {
            RuleBlock s = visitStatement(stmt);
            if (s != null) {
                //add statement to rules
                this.rules.add(s);
            }
        }
        if (log.isDebugEnabled()) {
            log.debug("\n***\n{}\n***\n", this.rules);
        }
        logLeave("stylesheet");
        return this.rules;
    }

    /**
     * scope and stack for statement
     * - this is for accessing statement scope from inner rules
     * e.g. - used for invalidate statement from selector
     */
    protected static class statement_scope {
        boolean invalid = false;
    }

    /**
     * stack for posibly recursion
     */
    protected Stack statement_stack = new Stack<>();

    @Override
    /**
     * Statement, main contents unit
     * statement : ruleset | atstatement
     */
    public RuleBlock visitStatement(CSSParser.StatementContext ctx) {
        if (ctxHasErrorNode(ctx)) {
            //context is invalid
            return null;
        }
        logEnter("statement: ", ctx);
        //create new scope and push it to stack
        statement_stack.push(new statement_scope());
        RuleBlock stmt = null;
        if (ctx.ruleset() != null) {
            //ruleset
            stmt = visitRuleset(ctx.ruleset());
        } else if (ctx.atstatement() != null) {
            //atstatement
            stmt = visitAtstatement(ctx.atstatement());
        }
        if (statement_stack.peek().invalid) {
            //stmt == null - is invalid
            if (log.isDebugEnabled()) {
                log.debug("Statement is invalid");
            }
        }
        statement_stack.pop();
        logLeave("statement");
        //could be null
        return stmt;
    }

    @Override
    /**
     *

     atstatement
     : CHARSET
     | IMPORT S* import_uri S* media? SEMICOLON
     | page
     | VIEWPORT S* LCURLY S* declarations RCURLY
     | FONTFACE S* LCURLY S* declarations RCURLY
     | MEDIA S* media? LCURLY S* (media_rule S*)* RCURLY
     | unknown_atrule
     ;

     */
    public RuleBlock visitAtstatement(CSSParser.AtstatementContext ctx) {
        logEnter("atstatement: ", ctx);
        RuleBlock atstmt = null;
        //noinspection StatementWithEmptyBody
        if (ctx.CHARSET() != null) {
            //charset is served in lexer
        }
        //import
        else if (ctx.IMPORT() != null) {
            List im = null;
            if (ctx.media() != null) {
                im = visitMedia(ctx.media());
            }
            ctx.import_uri();
            String iuri = visitImport_uri(ctx.import_uri());
            if (!this.preventImports && iuri != null) {
                if (log.isDebugEnabled()) {
                    log.debug("Adding import: {}", iuri);
                }
                importMedia.add(im);
                importPaths.add(iuri);
            } else {
                if (log.isDebugEnabled()) {
                    log.debug("Ignoring import: {}", iuri);
                }
            }
        }
        //page
        else if (ctx.page() != null) {

            atstmt = visitPage(ctx.page());
        }
        //viewport
        else if (ctx.VIEWPORT() != null) {

            List declarations = visitDeclarations(ctx.declarations());
            atstmt = preparator.prepareRuleViewport(declarations);
            if (atstmt != null)
                this.preventImports = true;
        }
        //fontface
        else if (ctx.FONTFACE() != null) {

            List declarations = visitDeclarations(ctx.declarations());
            atstmt = preparator.prepareRuleFontFace(declarations);
            if (atstmt != null)
                this.preventImports = true;
        }
        //media
        else if (ctx.MEDIA() != null) {

            List mediaList = null;
            List rules = null;
            if (ctx.media() != null) {
                mediaList = visitMedia(ctx.media());
            }
            if (ctx.media_rule() != null) {
                rules = new ArrayList<>();
                for (CSSParser.Media_ruleContext mr : ctx.media_rule()) {
                    RuleBlock rs = visitMedia_rule(mr);
                    if (rs != null) {
                        rules.add((RuleSet) rs);
                    }
                }
            }
            atstmt = preparator.prepareRuleMedia(rules, mediaList);
            if (atstmt != null)
                this.preventImports = true;
        }
        //keyframes
        else if (ctx.KEYFRAMES() != null) {
            String name = null;
            List keyframes = null;
            if (ctx.keyframes_name() != null) {
                name = visitKeyframes_name(ctx.keyframes_name());
            }
            if (ctx.keyframe_block() != null) {
                keyframes = new ArrayList<>();
                for (CSSParser.Keyframe_blockContext kfctx : ctx.keyframe_block()) {
                    KeyframeBlock block = visitKeyframe_block(kfctx);
                    if (block != null) {
                        keyframes.add(block);
                    }
                }
            }
            atstmt = preparator.prepareRuleKeyframes(keyframes, name);
            if (atstmt != null)
                this.preventImports = true;
        }
        //unknown
        else {
            if (log.isDebugEnabled()) {
                log.debug("Skipping invalid at statement");
            }
        }
        logLeave("atstatement");
        return atstmt;
    }

    @Override
    /**
     * import_uri : (STRING | URI)
     */
    public String visitImport_uri(CSSParser.Import_uriContext ctx) {
        if (ctx != null)
            return extractTextUnescaped(ctx.getText());
        else
            return null;
    }

    @Override
    /**
     *
     page
     : PAGE S* IDENT? pseudo? S*
     LCURLY S*
     declarations margin_rule*
     RCURLY
     */
    public RuleBlock visitPage(CSSParser.PageContext ctx) {
        boolean invalid = false;
        String name = null;
        if (ctx.IDENT() != null) {
            name = extractTextUnescaped(ctx.IDENT().getText());
        }
        Selector.PseudoPage pseudo = null;
        if (ctx.pseudo() != null) {
            Selector.SelectorPart p = visitPseudo(ctx.pseudo());
            if (p != null && p instanceof Selector.PseudoPage) {
                pseudo = (Selector.PseudoPage) p;
            } else { // Invalid pseudo
                if (log.isDebugEnabled()) {
                    log.debug("skipping RulePage with invalid pseudo-class: " + pseudo);
                }
                invalid = true;
            }
        }
        List declarations = visitDeclarations(ctx.declarations());
        List margins = null;
        if (ctx.margin_rule() != null) {
            margins = new ArrayList<>();
            for (CSSParser.Margin_ruleContext mctx : ctx.margin_rule()) {
                RuleMargin m = visitMargin_rule(mctx);
                margins.add(m);
                if (log.isDebugEnabled()) {
                    log.debug("Inserted margin rule #{} into @page", margins.size() + 1);
                }
            }
        }

        if (invalid) {
            return null;
        } else {
            RuleBlock rb = preparator.prepareRulePage(declarations, margins, name, pseudo);
            if (rb != null)
                this.preventImports = true;
            return rb;
        }
    }

    @Override
    /**
     * margin_rule : MARGIN_AREA S* LCURLY S* declarations RCURLY S*
     */
    public RuleMargin visitMargin_rule(CSSParser.Margin_ruleContext ctx) {
        logEnter("margin_rule");
        RuleMargin m;
        String area = ctx.MARGIN_AREA().getText();
        List decl = visitDeclarations(ctx.declarations());
        m = preparator.prepareRuleMargin(extractTextUnescaped(area).substring(1), decl);
        logLeave("margin_rule");
        return m;
    }

    @Override
    /**
     *
     inlineset
     : (pseudo S* (COMMA S* pseudo S*)*)?
     LCURLY
     declarations
     RCURLY
     ;
     */
    public cz.vutbr.web.css.RuleBlock visitInlineset(CSSParser.InlinesetContext ctx) {
        logEnter("inlineset");
        List pplist = new ArrayList<>();
        if (ctx.pseudo() != null) {
            for (CSSParser.PseudoContext pctx : ctx.pseudo()) {
                Selector.SelectorPart p = visitPseudo(pctx);
                pplist.add(p);
            }
        }
        List decl = visitDeclarations(ctx.declarations());
        RuleBlock is = preparator.prepareInlineRuleSet(decl, pplist);
        logLeave("inlineset");
        return is;
    }

    @Override
    /**
     media : media_query (COMMA S* media_query)*
     */
    public List visitMedia(CSSParser.MediaContext ctx) {
        logEnter("media: ", ctx);
        List queries = mediaQueryList = new ArrayList<>();
        for (CSSParser.Media_queryContext mqc : ctx.media_query()) {
            queries.add(visitMedia_query(mqc));
        }
        if (log.isDebugEnabled()) {
            log.debug("Totally returned {} media queries.", queries.size());
        }
        logLeave("media");
        return queries;
    }

    protected static class mediaquery_scope {
        cz.vutbr.web.css.MediaQuery q;
        MediaQueryState state;
        boolean invalid;
    }

    mediaquery_scope mq;

    @Override
    /**
     * media_query : (media_term S*)+
     */
    public MediaQuery visitMedia_query(CSSParser.Media_queryContext ctx) {
        logEnter("mediaquery: ", ctx);
        mq = new mediaquery_scope();
        mq.q = rf.createMediaQuery();
        mq.q.unlock();
        mq.state = MediaQueryState.START;
        mq.invalid = false;
        logLeave("mediaquery");
        for (CSSParser.Media_termContext mtc : ctx.media_term()) {
            visitMedia_term(mtc);
        }
        if (mq.invalid) {
            log.trace("Skipping invalid rule {}", mq.q);
            mq.q.setType("all"); //change the malformed media queries to "not all"
            mq.q.setNegative(true);
        }
        logLeave("mediaquery");
        return mq.q;
    }

    @Override
    /**
     *
     media_term
     : (IDENT | media_expression)
     | nomediaquery
     */
    public Object visitMedia_term(CSSParser.Media_termContext ctx) {
        //IDENT
        if (ctx.IDENT() != null) {
            String m = extractTextUnescaped(ctx.IDENT().getText());
            MediaQueryState state = mq.state;
            if (m.equalsIgnoreCase("ONLY") && state == MediaQueryState.START) {
                mq.state = MediaQueryState.TYPEOREXPR;
            } else if (m.equalsIgnoreCase("NOT") && state == MediaQueryState.START) {
                mq.q.setNegative(true);
                mq.state = MediaQueryState.TYPEOREXPR;
            } else if (m.equalsIgnoreCase("AND") && state == MediaQueryState.AND) {
                mq.state = MediaQueryState.EXPR;
            } else if (state == MediaQueryState.START
                    || state == MediaQueryState.TYPE
                    || state == MediaQueryState.TYPEOREXPR) {
                mq.q.setType(m);
                mq.state = MediaQueryState.AND;
            } else {
                log.trace("Invalid media query: found ident: {} state: {}", m, state);
                mq.invalid = true;
            }
        }
        //media_expression
        else if (ctx.media_expression() != null) {
            MediaExpression e = visitMedia_expression(ctx.media_expression());
            if (mq.state == MediaQueryState.START
                    || mq.state == MediaQueryState.EXPR
                    || mq.state == MediaQueryState.TYPEOREXPR) {
                if (e != null && e.getFeature() != null) //the expression is valid
                {
                    mq.q.add(e);
                    mq.state = MediaQueryState.AND;
                } else {
                    log.trace("Invalidating media query for invalud expression");
                    mq.invalid = true;
                }
            } else {
                log.trace("Invalid media query: found expr, state: {}", mq.state);
                mq.invalid = true;
            }
        }
        //nomediaquery
        else {
            mq.invalid = true;
        }
        return null;
    }

    @Override
    /**
     * media_expression : LPAREN S* IDENT S* (COLON S* terms)? RPAREN
     */
    public MediaExpression visitMedia_expression(CSSParser.Media_expressionContext ctx) {
        logEnter("mediaexpression: ", ctx);
        if (ctxHasErrorNode(ctx)) {
            mq.invalid = true;
            return null;
        }
        MediaExpression expr = rf.createMediaExpression();
        Declaration decl;
        declaration_stack.push(new declaration_scope());
        declaration_stack.peek().d = decl = rf.createDeclaration();
        declaration_stack.peek().invalid = false;

        String property = extractTextUnescaped(ctx.IDENT().getText());
        decl.setProperty(property);
        Token token = ctx.IDENT().getSymbol();
        decl.setSource(extractSource((CSSToken) token));
        if (ctx.terms() != null) {
            List> t = visitTerms(ctx.terms());
            decl.replaceAll(t);
        }

        if (declaration_stack.peek().d != null && !declaration_stack.peek().invalid) { //if the declaration is valid
            expr.setFeature(decl.getProperty());
            expr.replaceAll(decl);
        }
        declaration_stack.pop();

        logLeave("mediaexpression");
        return expr;
    }

    @Override
    /**
     *
     media_rule
     : ruleset
     | atstatement //invalid statement
     ;
     */
    public RuleBlock visitMedia_rule(CSSParser.Media_ruleContext ctx) {
        logEnter("media_rule: ", ctx);
        RuleBlock rules = null;
        if (ctx.ruleset() != null) {
            statement_stack.push(new statement_scope());
            rules = visitRuleset(ctx.ruleset());
            statement_stack.pop();
        } else {
            if (log.isDebugEnabled()) {
                log.debug("Skiping invalid statement in media");
            }
        }
        logLeave("media_rule");
        //could be null
        return rules;
    }

    @Override
    public String visitKeyframes_name(Keyframes_nameContext ctx) {
        if (ctx.IDENT() != null)
            return extractTextUnescaped(ctx.IDENT().getText());
        else if (ctx.string() != null)
            return visitString(ctx.string());
        else
            return null;
    }

    @Override
    public KeyframeBlock visitKeyframe_block(Keyframe_blockContext ctx) {
        List selectors = null;
        if (ctx.keyframe_selector() != null) {
            selectors = new ArrayList<>();
            for (Keyframe_selectorContext selctx : ctx.keyframe_selector()) {
                TermPercent perc = visitKeyframe_selector(selctx);
                if (perc != null)
                    selectors.add(perc);
            }
        }

        List declarations = null;
        if (ctx.declarations() != null) {
            statement_stack.push(new statement_scope());
            declarations = visitDeclarations(ctx.declarations());
            statement_stack.pop();
        }
        
        if (declarations != null && selectors != null && !selectors.isEmpty()) {
            KeyframeBlock block = rf.createKeyframeBlock();
            block.setPercentages(selectors);
            block.replaceAll(declarations);
            return block;
        } else {
            return null;
        }
    }

    @Override
    public TermPercent visitKeyframe_selector(Keyframe_selectorContext ctx) {
        if (ctx.IDENT() != null) {
            final String idtext = ctx.IDENT().getText();
            if (idtext != null) {
                if (idtext.equalsIgnoreCase("from")) {
                    return tf.createPercent(0.0f);
                } else if (idtext.equalsIgnoreCase("to")) {
                    return tf.createPercent(100.0f);
                } else {
                    return null;
                }
            } else {
                return null;
            }
        } else if (ctx.PERCENTAGE() != null) {
            return tf.createPercent(ctx.PERCENTAGE().getText(), 1);
        } else {
            return null;
        }
    }

    @Override
    public Object visitUnknown_atrule(CSSParser.Unknown_atruleContext ctx) {
        //done in atstatement else section
        return null;
    }

    @Override
    public Object visitUnknown_atrule_body(CSSParser.Unknown_atrule_bodyContext ctx) {
        //not used - the unknown atrules are skipped
        return null;
    }
    
    @Override
    /**
     * The most common block in CSS file,
     * set of declarations with selector
     ruleset
     : combined_selector (COMMA S* combined_selector)*
     LCURLY S*
     declarations
     RCURLY
     | norule
     */
    public RuleBlock visitRuleset(CSSParser.RulesetContext ctx) {
        logEnter("ruleset");
        if (ctxHasErrorNode(ctx) || ctx.norule() != null) {
            log.trace("Leaving ruleset with error {} {}", ctxHasErrorNode(ctx), (ctx.norule() != null)); 
            return null;
        }
        List cslist = new ArrayList<>();
        // body
        for (CSSParser.Combined_selectorContext csctx : ctx.combined_selector()) {
            CombinedSelector cs = visitCombined_selector(csctx);
            if (cs != null && !cs.isEmpty() && !statement_stack.peek().invalid) {
                cslist.add(cs);
                if (log.isDebugEnabled()) {
                    log.debug("Inserted combined selector ({}) into ruleset", cslist.size());
                }
            }
        }
        List decl = visitDeclarations(ctx.declarations());
        RuleBlock stmnt;
        if (statement_stack.peek().invalid) {
            stmnt = null;
            if (log.isDebugEnabled()) {
                log.debug("Ruleset not valid, so not created");
            }
        } else {
            stmnt = preparator.prepareRuleSet(cslist, decl, (this.wrapMedia != null && !this.wrapMedia.isEmpty()), this.wrapMedia);
            this.preventImports = true;
        }
        logLeave("ruleset");
        return stmnt;
    }

    @Override
    /**
     * Multiple CSS declarations
     * declarations : declaration? (SEMICOLON S* declaration? )*
     */
    public List visitDeclarations(CSSParser.DeclarationsContext ctx) {
        logEnter("declarations");
        List decl = new ArrayList<>();
        if (ctx != null && ctx.declaration() != null) {
            for (CSSParser.DeclarationContext declctx : ctx.declaration()) {
                Declaration d = visitDeclaration(declctx);
                if (d != null) {
                    decl.add(d);
                    if (log.isDebugEnabled()) {
                        log.debug("Inserted declaration #{} ", decl.size() + 1);
                    }
                } else {
                    if (log.isDebugEnabled()) {
                        log.debug("Null declaration was omitted");
                    }
                }
            }
        }
        logLeave("declarations");
        return decl;
    }

    protected static class declaration_scope {
        cz.vutbr.web.css.Declaration d;
        boolean invalid;
    }

    protected Stack declaration_stack = new Stack<>();

    @Override
    /**
     *
     declaration : property COLON S* terms? important?
     | noprop any*
     */
    public Declaration visitDeclaration(CSSParser.DeclarationContext ctx) {
        logEnter("declaration");
        Declaration decl;
        declaration_stack.push(new declaration_scope());
        declaration_stack.peek().d = decl = rf.createDeclaration();
        declaration_stack.peek().invalid = false;

        if (ctx.noprop() == null && !ctxHasErrorNode(ctx)) {
            if (ctx.important() != null) {
                visitImportant(ctx.important());
            }
            visitProperty(ctx.property());
            if (ctx.terms() != null) {
                List> t = visitTerms(ctx.terms());
                decl.replaceAll(t);
            }
        } else {
            if (log.isDebugEnabled()) {
                log.debug("invalidating declaration");
            }
            declaration_stack.peek().invalid = true;
        }

        if (declaration_stack.peek().invalid || declaration_stack.isEmpty()) {
            decl = null;
            if (log.isDebugEnabled()) {
                log.debug("Declaration was invalidated or already invalid");
            }
        } else {
            if (log.isDebugEnabled()) {
                log.debug("Returning declaration: {}.", decl);
            }
        }
        logLeave("declaration");
        declaration_stack.pop();

        return decl;
    }

    @Override
    /**
     * important
     : EXCLAMATION S* IMPORTANT S*
     */
    public Object visitImportant(CSSParser.ImportantContext ctx) {
        if (ctxHasErrorNode(ctx)) {
            declaration_stack.peek().invalid = true;
        } else {
            declaration_stack.peek().d.setImportant(true);
            if (log.isDebugEnabled()) {
                log.debug("IMPORTANT");
            }
        }
        //returns null
        return null;
    }

    @Override
    /**
     * Setting property of declaration
     property
     : MINUS? IDENT S*

     returns null - processed via declaration_sctack
     */
    public Object visitProperty(CSSParser.PropertyContext ctx) {
        logEnter("property");

        String property = extractTextUnescaped(ctx.IDENT().getText());
        if (ctx.MINUS() != null) {
            property = ctx.MINUS().getText() + property;
        }
        declaration_stack.peek().d.setProperty(property);
        Token token = ctx.IDENT().getSymbol();
        declaration_stack.peek().d.setSource(extractSource((CSSToken) token));

        if (log.isDebugEnabled()) {
            log.debug("Setting property: {}", declaration_stack.peek().d.getProperty());
        }
        logLeave("property");
        //returns null
        return null;
    }


    protected static class terms_scope {
        List> list;
        cz.vutbr.web.css.Term term;
        cz.vutbr.web.css.Term.Operator op;
        int unary;
        boolean dash;
    }

    protected Stack terms_stack = new Stack<>();

    @Override
    /**
     * Term of CSSDeclaration
     *
     * terms : term+
     */
    public List> visitTerms(CSSParser.TermsContext ctx) {
        terms_stack.push(new terms_scope());
        List> tlist;
        logEnter("terms");
        terms_stack.peek().list = tlist = new ArrayList<>();
        terms_stack.peek().term = null;
        terms_stack.peek().op = null;
        terms_stack.peek().unary = 1;
        terms_stack.peek().dash = false;
        if (ctx.term() != null)
        {
            for (CSSParser.TermContext trmCtx : ctx.term()) {
                if (trmCtx instanceof CSSParser.TermValuePartContext) {
                    visitTermValuePart((CSSParser.TermValuePartContext) trmCtx);
                    // set operator, store and create next
                    if (!declaration_stack.peek().invalid && terms_stack.peek().term != null) {
                        terms_stack.peek().term.setOperator(terms_stack.peek().op);
                        terms_stack.peek().list.add(terms_stack.peek().term);
                        // reinitialization
                        terms_stack.peek().op = cz.vutbr.web.css.Term.Operator.SPACE;
                        terms_stack.peek().unary = 1;
                        terms_stack.peek().dash = false;
                        terms_stack.peek().term = null;
                    }
                } else {
                    visitTermInvalid((CSSParser.TermInvalidContext) trmCtx);
                }
            }
        }
        if (log.isDebugEnabled()) {
            log.debug("Totally added {} terms", tlist.size());
        }
        logLeave("terms");
        terms_stack.pop();
        return tlist;
    }

    /**
     * term
     * : valuepart #termValuePart
     * | LCURLY S* (any | SEMICOLON S*)* RCURLY #termInvalid // invalid term
     * | ATKEYWORD S* #termInvalid // invalid term
     * ;
     */
    //
    @Override
    public Object visitTermValuePart(CSSParser.TermValuePartContext ctx) {
        logEnter("term");
        visitValuepart(ctx.valuepart());
        //returns null
        return null;
    }

    @Override
    public Object visitTermInvalid(CSSParser.TermInvalidContext ctx) {
        logEnter("term");
        declaration_stack.peek().invalid = true;
        //returns null
        return null;
    }


    @Override
    public Object visitFunct(CSSParser.FunctContext ctx) {
        if (ctx.EXPRESSION() != null) {
            log.warn("Omitting expression " + ctx.getText() + ", expressions are not supported");
            return null;
        }
        Term ret = null;
        final String fname = extractTextUnescaped(ctx.FUNCTION().getText()).toLowerCase();
        if (ctx.funct_args() != null)
        {
            List> t = visitFunct_args(ctx.funct_args());
            if (fname.equals("url")) {
                // the function name is url() after escaping - create an URI
                if (t == null || t.size() != 1)
                    ret = null;
                else {
                    cz.vutbr.web.css.Term term = t.get(0);
                    if (term instanceof cz.vutbr.web.css.TermString && term.getOperator() == null)
                        ret = tf.createURI(((cz.vutbr.web.css.TermString) term).getValue(), extractBase(ctx.FUNCTION()));
                    else
                        ret = null;
                }
            } else if (fname.equals("calc")) {
                // create calc() of the given type: , , ,