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

com.inet.sass.parser.SassExpression Maven / Gradle / Ivy

/*
 * Copyright 2023 i-net software
 * 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.inet.sass.parser;

import static com.inet.sass.parser.SCSSLexicalUnit.SCSS_EXPRESSION;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import com.inet.sass.ScssContext;
import com.inet.sass.expression.ArithmeticExpressionEvaluator;
import com.inet.sass.expression.BinaryOperator;
import com.inet.sass.tree.Node;
import com.inet.sass.tree.Node.BuildStringStrategy;

/**
 * SassExpressions are used for representing and evaluating arithmetic
 * expressions.
 * 
 * @author Vaadin
 * 
 */
public class SassExpression implements SassListItem {

    private List items;
    private int line = 0;
    private int column = 0;

    /**
     * Constructs a SassExpression from a list of items. The list is not copied
     * but used directly.
     * 
     * @param items
     *            list of items (not copied but used directly)
     */
    private SassExpression(List items) {
        if (!items.isEmpty()) {
            line = items.get(0).getLineNumber();
            column = items.get(0).getColumnNumber();
        }

        this.items = items;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public short getItemType() {
        return SCSS_EXPRESSION;
    }

    /**
     * Creates a new expression containing the elements of the parameter items
     * but with trailing whitespace items eliminated. If items contains only one
     * element excluding the trailing whitespace, returns the only contained
     * element. Otherwise returns a SassExpression.
     * 
     * @param items
     *            one or more SassListItems.
     * @return A SassExpression corresponding to items. If there is only one
     *         item after the removal of whitespace, returns that item instead
     *         of a SassExpression.
     */
    public static SassListItem createExpression(SassListItem... items) {
        return createExpression(Arrays.asList(items));
    }

    /**
     * Creates a new expression containing the elements of the parameter items
     * but with trailing whitespace items eliminated. If items contains only one
     * element excluding the trailing whitespace, returns the only contained
     * element. Otherwise returns a SassExpression.
     * 
     * @param items
     *            A list of SassListItems.
     * @return A SassExpression corresponding to items. If there is only one
     *         item after the removal of whitespace, returns that item instead
     *         of a SassExpression.
     */
    public static SassListItem createExpression(List items) {
        // filter out trailing whitespace
        int lastNonWhitespace = items.size() - 1;
        while (lastNonWhitespace > 0
                && items.get(lastNonWhitespace) == LexicalUnitImpl.WHITESPACE) {
            --lastNonWhitespace;
        }
        if (lastNonWhitespace < items.size() - 1) {
            // copy needed as subList() returns a non-serializable list
            items = new ArrayList(items.subList(0,
                    lastNonWhitespace + 1));
        }
        if (items.size() == 1) {
            return items.get(0);
        } else {
            return new SassExpression(items);
        }
    }

    @Override
    public int getLineNumber() {
        return line;
    }

    @Override
    public int getColumnNumber() {
        return column;
    }

    public boolean containsArithmeticalOperator() {
        for (SassListItem item : items) {
            if (item.containsArithmeticalOperator()) {
                return true;
            }
        }
        int previousIndex = getNextNonspaceIndex(items, 0);
        int currentIndex = getNextNonspaceIndex(items, previousIndex + 1);
        int nextIndex = getNextNonspaceIndex(items, currentIndex + 1);
        if (nextIndex >= items.size()) {
            return false;
        }
        while (nextIndex < items.size()) {
            SassListItem previous = items.get(previousIndex);
            SassListItem current = items.get(currentIndex);
            SassListItem next = items.get(nextIndex);
            previousIndex = currentIndex;
            currentIndex = nextIndex;
            nextIndex = getNextNonspaceIndex(items, nextIndex + 1);
            short currentType = current.getItemType();
            if (currentType == BinaryOperator.DIV.type) {
                /*
                 * '/' is treated as an arithmetical operator when one of its
                 * operands is Variable, or there is another binary operator.
                 * Otherwise, '/' is treated as a CSS operator. If interpolation
                 * occurs on either side of a symbol '/', '*', '+' or ´-', the
                 * symbol is not treated as an arithmetical operator.
                 */
                if ((isVariable(previous) || isVariable(next))
                        && !containsInterpolation(previous)
                        && !containsInterpolation(next)) {
                    return true;
                }
            } else if (isOperator(currentType)
                    && !containsInterpolation(previous)
                    && !containsInterpolation(next)) {
                return true;
            }
        }
        return false;
    }

    private boolean isOperator(short type) {
        for (BinaryOperator operator : BinaryOperator.values()) {
            if (type == operator.type) {
                return true;
            }
        }
        return false;
    }

    private boolean isVariable(SassListItem item) {
        return item.getItemType() == LexicalUnitImpl.SCSS_VARIABLE;
    }

    private boolean containsInterpolation(SassListItem item) {
        if( item.getClass() == LexicalUnitImpl.class ) {
            return ((LexicalUnitImpl)item).containsInterpolation();
        }
        return false;
    }

    /**
     * Returns the index of the next non-whitespace item in list, starting from
     * startIndex (inclusive). If there are no non-whitespace items in
     * list[startIndex...list.size() - 1], returns list.size().
     * 
     * @param list
     *            A list.
     * @param startIndex
     *            The first index included in the search.
     * @return The smallest index i such that i <= startIndex && list.get(i)
     *         does not represent whitespace. If no such index exists, returns
     *         list.size().
     */
    public static int getNextNonspaceIndex(List list,
            int startIndex) {
        for (int i = startIndex; i < list.size(); ++i) {
            if (!(list.get(i) == LexicalUnitImpl.WHITESPACE)) {
                return i;
            }
        }
        return list.size();
    }

    @Override
    public SassListItem evaluateFunctionsAndExpressions( ScssContext context, boolean evaluateArithmetics ) {
        if( evaluateArithmetics && !items.isEmpty() ) {
            return ArithmeticExpressionEvaluator.evaluate( context, items );
        }
        List list = new ArrayList();
        for( SassListItem item : items ) {
            list.add( item.evaluateFunctionsAndExpressions( context, evaluateArithmetics ) );
        }
        return new SassExpression( list );
    }

    @Override
    public SassExpression updateUrl(String prefix) {
        List newItems = new ArrayList(items.size());
        for (SassListItem item : items) {
            newItems.add(item.updateUrl(prefix));
        }
        return new SassExpression(newItems);
    }

    @Override
    public String printState() {
        return buildString(Node.PRINT_STRATEGY);
    }

    @Override
    public String buildString(BuildStringStrategy strategy) {
        StringBuilder result = new StringBuilder();
        Iterator it = items.iterator();
        while (it.hasNext()) {
            SassListItem item = it.next();
            result.append(strategy.build(item));
        }
        return result.toString();
    }

    @Override
    public String toString() {
        String result = "SassExpression[";
        result += buildString(Node.TO_STRING_STRATEGY);
        return result + "]";
    }

    @Override
    public String unquotedString() {
        if (items.size() == 1 && items.get(0) instanceof LexicalUnitImpl) {
            return ((LexicalUnitImpl) items.get(0)).unquotedString();
        }
        return printState();
    }

    @Override
    public LexicalUnitImpl getContainedValue() {
        if (items.size() != 1 || !(items.get(0) instanceof LexicalUnitImpl)) {
            throw new ParseException(
                    "getContainedValue() can only be used for an expression that contains one simple value. Actual value: "
                            + toString());
        }
        return (LexicalUnitImpl) items.get(0);
    }

    /**
     * Tests whether this and o are equal expressions. Two expressions are
     * considered to be equal only if they have equal operands and operators in
     * the same order.
     * 
     * In most cases the results of the expressions should be compared to each
     * other instead of the expressions themselves. For this the expressions can
     * be evaluated using evaluateFunctionsAndExpressions after replacing any
     * variables occurring in the expressions.
     */
    @Override
    public boolean equals(Object o) {
        if (!(o instanceof SassExpression)) {
            return false;
        }
        SassExpression other = (SassExpression) o;
        if (items.size() != other.items.size()) {
            return false;
        }
        for (int i = 0; i < items.size(); i++) {
            if (!items.get(i).equals(other.items.get(i))) {
                return false;
            }
        }
        return true;
    }

    @Override
    public int hashCode() {
        int result = 1;
        for (int i = 0; i < items.size(); i++) {
            int currentHash = 0;
            if (items.get(i) != null) {
                currentHash = items.get(i).hashCode();
            }
            result = 41 * result + currentHash;
        }
        return result;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy