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

com.vaadin.sass.internal.visitor.ExtendNodeHandler Maven / Gradle / Ivy

There is a newer version: 0.9.13
Show newest version
/*
 * Copyright 2000-2014 Vaadin Ltd.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.vaadin.sass.internal.visitor;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import com.vaadin.sass.internal.parser.ParseException;
import com.vaadin.sass.internal.selector.Selector;
import com.vaadin.sass.internal.selector.SelectorSet;
import com.vaadin.sass.internal.selector.SimpleSelectorSequence;
import com.vaadin.sass.internal.tree.BlockNode;
import com.vaadin.sass.internal.tree.ExtendNode;
import com.vaadin.sass.internal.tree.Node;

public class ExtendNodeHandler {
    /*
     * TODOs:
     * 
     * - Print a warning when unification of an @extend-clause fails, unless
     * !optional specified. This may require some rework of the way the extends
     * map is built, as currently the connection to the @extend declaration is
     * lost.
     */

    /**
     * An immutable data object for an @extend, describing the mapping from the
     * parameter of @extend to the selector selector of the block containing the
     * @extend.
     */
    private static class Extension implements Serializable {
        /**
         * The parameter of @extend, e.g. "b" in "a { @extend b; ... }".
         * 
         * This is the selector whose occurrences will be augmented with new
         * entries generated by replacing {@link #extendSelector} with
         * {@link #replacingSelectors}.
         */
        private final SimpleSelectorSequence extendSelector;

        /**
         * A selectors that is extended by {@link #extendSelector}, e.g. "a" in
         * "a { @extend b; ... }".
         */
        private final Selector replacingSelector;

        public Extension(SimpleSelectorSequence extendSelector,
                Selector replacingSelector) {
            this.extendSelector = extendSelector;
            this.replacingSelector = replacingSelector;
        }
    }

    /**
     * Collection of mappings from an @extend-selector (its simple selector
     * sequence) to a containing block's selectors. E.g. the following
     * extensions:
     * 
     * a { @extend b; ... }; b { @extend b,c; ... }
     * 
     * corresponds to the following set of mappings:
     * 
     * { (b, a), (b, b), (c, b) }
     */
    private static Set extendsMap = new LinkedHashSet();

    public static void traverse(ExtendNode node) throws Exception {
        for (Selector s : node.getList()) {
            if (!s.isSimple()) {
                // @extend-selectors must not be nested
                throw new ParseException(
                        "Nested selector not allowed in @extend-clause");
            }
            if (node.getParentNode() instanceof BlockNode) {
                SimpleSelectorSequence extendSelector = s.firstSimple();
                for (Selector sel : ((BlockNode) node.getParentNode())
                        .getSelectorList()) {
                    extendsMap.add(new Extension(extendSelector, sel));
                }
            }
        }
    }

    public static void clear() {
        if (extendsMap != null) {
            extendsMap.clear();
        }
    }

    public static void modifyTree(Node node) throws Exception {
        Iterator nodeIt = node.getChildren().iterator();

        while (nodeIt.hasNext()) {
            final Node child = nodeIt.next();

            if (child instanceof BlockNode) {
                BlockNode blockNode = (BlockNode) child;
                List selectorList = blockNode.getSelectorList();
                SelectorSet newSelectors = new SelectorSet();
                for (Selector selector : selectorList) {
                    newSelectors.addAll(createSelectorsForExtensions(selector,
                            extendsMap));
                }

                // remove any selector duplicated in the initial list of
                // selectors
                newSelectors.removeAll(selectorList);

                selectorList.addAll(newSelectors);

                // remove all placeholder selectors
                Iterator it = selectorList.iterator();
                while (it.hasNext()) {
                    Selector s = it.next();
                    if (s.isPlaceholder()) {
                        it.remove();
                    }
                }

                // remove block if selector list is empty
                if (selectorList.isEmpty()) {
                    nodeIt.remove();
                } else {
                    blockNode.setSelectorList(selectorList);
                }
            }
        }

    }

    /**
     * Try to unify argument selector with each selector specified in an
     * extend-clause. For each match found, add the enclosing block's selectors
     * with substitutions (see examples below). Finally eliminates redundant
     * selectors (a selector is redundant in a set if subsumed by another
     * selector in the set).
     * 
     * .a {...}; .b { @extend .a } ---> .b
     * 
     * .a.b {...}; .c { @extend .a } ---> .b.c
     * 
     * .a.b {...}; .c .c { @extend .a } ---> .c .b.c
     * 
     * @param target
     *            the selector to match
     * @param extendsMap
     *            mapping from the simple selector sequence of the
     *            extend-selector to an extending selector
     * 
     * @return the generated selectors (may contain duplicates)
     */
    public static SelectorSet createSelectorsForExtensions(Selector target,
            Set extendsMap) {
        SelectorSet newSelectors = new SelectorSet();
        createSelectorsForExtensionsRecursively(target, newSelectors,
                extendsMap);
        return newSelectors.eliminateRedundantSelectors();
    }

    /**
     * Create all selector extensions matching target. Mutable collection for
     * efficiency. Recursively applied to generated selectors.
     * 
     * Optimization: May be inefficient since we may unify the same selector
     * several times. It would be a good idea to cache unifications in a map as
     * we go along.
     */
    private static void createSelectorsForExtensionsRecursively(
            Selector target, SelectorSet current,
            Collection extendsMap) {

        List newSelectors = new ArrayList();
        List extensionsForNewSelectors = new ArrayList();
        for (Extension extension : extendsMap) {
            newSelectors.add(target.replace(extension.extendSelector,
                    extension.replacingSelector));
            extensionsForNewSelectors.add(extension);
        }
        current.addAll(newSelectors);

        for (Extension extension : extendsMap) {
            Collection singleExt = Collections.singleton(extension);
            for (int i = 0; i < newSelectors.size(); ++i) {
                // avoid infinite loops
                if (extensionsForNewSelectors.get(i) != extension) {
                    createSelectorsForExtensionsRecursively(
                            newSelectors.get(i), current, singleExt);
                }
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy