Package com.bazaarvoice.jless.ast.visitor

Source Code of com.bazaarvoice.jless.ast.visitor.FlattenNestedRuleSets

/**
* Copyright 2010 Bazaarvoice, Inc.
*
* 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.
*
* @author J. Ryan Stinnett (ryan.stinnett@bazaarvoice.com)
*/

package com.bazaarvoice.jless.ast.visitor;

import com.bazaarvoice.jless.ast.node.Node;
import com.bazaarvoice.jless.ast.node.PropertyNode;
import com.bazaarvoice.jless.ast.node.RuleSetNode;
import com.bazaarvoice.jless.ast.node.ScopeNode;
import com.bazaarvoice.jless.ast.node.SelectorGroupNode;
import com.bazaarvoice.jless.ast.node.SelectorNode;
import com.bazaarvoice.jless.ast.util.NodeTreeUtils;
import com.bazaarvoice.jless.ast.util.RandomAccessListIterator;

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

/**
* This visitor flattens nested rule sets since nesting is not allowed in CSS.  When a rule set move up the
* hierarchy in this manner, each of its selectors is combined with the selectors from the parent rule set.
*
* Example input:
*   .cat, .dog {
*       :hover, .bob {
*           color: blue;
*       }
*   }
*
* Resulting output (comments not shown):
*  .cat:hover, .dog:hover, .cat .bob, .dog .bob {
        color: blue;
*  }
*/
public class FlattenNestedRuleSets extends InclusiveNodeVisitor {

    private RuleSetNode _parentRuleSet;
    private SelectorGroupNode _parentSelectorGroup;

    @Override
    public boolean enter(ScopeNode scope) {
        // If there's already a parent rule set, then use a new visitor for nested rule sets in this scope.
        if (_parentRuleSet != null) {
            scope.traverse(new NestedRuleSetVisitor(_parentSelectorGroup, scope));

            // Don't enter the scope in this visitor
            return false;
        }

        return true;
    }

    @Override
    public boolean enter(RuleSetNode ruleSet) {
        // Entering the parent rule set
        _parentRuleSet = ruleSet;
        return true;
    }

    @Override
    public boolean exit(RuleSetNode ruleSet) {
        _parentRuleSet = null;
        return true;
    }

    @Override
    public boolean enter(SelectorGroupNode node) {
        // This visitor doesn't use the parent's selector, but passes it down to the nested visitor
        _parentSelectorGroup = node;
        return false;
    }

    private static class NestedRuleSetVisitor extends InclusiveNodeVisitor {

        private final SelectorGroupNode _parentSelectorGroup;
        private final ScopeNode _parentScope;

        private boolean _foundNestedRuleSets = false;
        private SelectorGroupNode _nestedSelectorGroup;
        private PropertyGroup _currentPropertyGroup = null;
        private Stack<PropertyGroup> _propertyGroups = new Stack<PropertyGroup>();

        private NestedRuleSetVisitor(SelectorGroupNode parentSelectorGroup, ScopeNode parentScope) {
            _parentSelectorGroup = parentSelectorGroup;
            _parentScope = parentScope;
        }

        @Override
        public boolean enter(RuleSetNode node) {
            _foundNestedRuleSets = true;
            recordCurrentPropertyGroup();
            return true;
        }

        @Override
        public boolean enter(SelectorGroupNode node) {
            _nestedSelectorGroup = node;
            return true;
        }

        @Override
        public boolean enter(final SelectorNode selector) {
            final RandomAccessListIterator<Node> selectorGroupIterator = selector.getParent().getLatestChildIterator();

            // Remove current selector node from its parent
            selectorGroupIterator.remove();

            // Visit the parent selector group and clone its selector nodes
            _parentSelectorGroup.traverse(new InclusiveNodeVisitor() {
                @Override
                public boolean exit(SelectorNode parentSelector) {
                    SelectorNode parentSelectorClone = parentSelector.clone();

                    // Add the current selector from the nested rule set to the cloned selector node from the parent.
                    // SelectorNode listens for additions of other SelectorNodes, and will absorb its children properly.
                    parentSelectorClone.addChild(selector.clone());

                    // Add the new selector to the selector group
                    selectorGroupIterator.add(parentSelectorClone);
                    return true;
                }
            });

            return false;
        }

        @Override
        public boolean enter(ScopeNode node) {
            // Only enter the initial scope (inside the parent rule set)
            return node == _parentScope;
        }

        @Override
        public boolean exit(ScopeNode scope) {
            if (scope == _parentScope) { // Leaving the initial scope (inside the parent rule set)
                // Nothing to do if there are no nested rule sets present
                if (!_foundNestedRuleSets) {
                    return true;
                }

                // Record a possible property group at the end of the scope
                recordCurrentPropertyGroup();

                // Surround all properties
                surroundPropertyGroups();

                // Hide the parent rule set's selector and scope brackets
                _parentSelectorGroup.setVisible(false);
                _parentScope.setBracketsDisplayed(false);
            } else { // Leaving a nested rule set's scope
                scope.traverse(new NestedRuleSetVisitor(_nestedSelectorGroup, scope));
            }

            return true;
        }

        @Override
        public boolean enter(PropertyNode node) {
            // Get the current node's index in its parent
            int index = node.getParent().getLatestChildIterator().previousIndex();

            if (_currentPropertyGroup == null) {
                _currentPropertyGroup = new PropertyGroup(index);
            }

            _currentPropertyGroup.setEnd(index);

            return false;
        }

        @Override
        public boolean visitInvisible(Node node) {
            return false;
        }

        private void recordCurrentPropertyGroup() {
            // Only have work to do if we have encountered property nodes previously
            if (_currentPropertyGroup == null) {
                return;
            }

            // Note this property group in the map
            _propertyGroups.push(_currentPropertyGroup);

            _currentPropertyGroup = null;
        }

        private void surroundPropertyGroups() {
            while (!_propertyGroups.empty()) {
                PropertyGroup group = _propertyGroups.pop();

                // Construct a simple rule set using the parent's selector
                RuleSetNode propertyRuleSet = new RuleSetNode();
                propertyRuleSet.addChild(NodeTreeUtils.filterLineBreaks(_parentSelectorGroup.clone()));

                // Move all the properties into this new rule set
                List<Node> propertyNodes = new ArrayList<Node>(_parentScope.getChildren().subList(group.getStart(), group.getEnd() + 1));
                propertyRuleSet.addChild(new ScopeNode(propertyNodes));

                // Insert the property rule set
                _parentScope.addChild(group.getStart(), propertyRuleSet);
            }
        }

        private static class PropertyGroup {
            private int _start;
            private int _end;

            private PropertyGroup(int start) {
                _start = start;
            }

            public int getStart() {
                return _start;
            }

            public int getEnd() {
                return _end;
            }

            public void setEnd(int end) {
                _end = end;
            }
        }
    }
}
TOP

Related Classes of com.bazaarvoice.jless.ast.visitor.FlattenNestedRuleSets

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.