
com.vaadin.sass.internal.ScssStylesheet Maven / Gradle / Ivy
/*
* 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;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import org.w3c.css.sac.CSSException;
import org.w3c.css.sac.InputSource;
import com.vaadin.sass.internal.handler.SCSSDocumentHandler;
import com.vaadin.sass.internal.handler.SCSSDocumentHandlerImpl;
import com.vaadin.sass.internal.handler.SCSSErrorHandler;
import com.vaadin.sass.internal.parser.ParseException;
import com.vaadin.sass.internal.parser.Parser;
import com.vaadin.sass.internal.parser.SCSSParseException;
import com.vaadin.sass.internal.resolver.ClassloaderResolver;
import com.vaadin.sass.internal.resolver.FilesystemResolver;
import com.vaadin.sass.internal.resolver.ScssStylesheetResolver;
import com.vaadin.sass.internal.tree.BlockNode;
import com.vaadin.sass.internal.tree.FunctionDefNode;
import com.vaadin.sass.internal.tree.MixinDefNode;
import com.vaadin.sass.internal.tree.Node;
import com.vaadin.sass.internal.tree.VariableNode;
import com.vaadin.sass.internal.tree.controldirective.IfElseDefNode;
import com.vaadin.sass.internal.visitor.ExtendNodeHandler;
import com.vaadin.sass.internal.visitor.ImportNodeHandler;
public class ScssStylesheet extends Node {
private static final long serialVersionUID = 3849790204404961608L;
private static ScssStylesheet mainStyleSheet = null;
private static final HashMap variables = new HashMap();
private static final Map mixinDefs = new HashMap();
private static final Map functionDefs = new HashMap();
private static final HashSet ifElseDefNodes = new HashSet();
private File file;
private String charset;
private List resolvers = new ArrayList();
/**
* Read in a file SCSS and parse it into a ScssStylesheet
*
* @param file
* @throws IOException
*/
public ScssStylesheet() {
super();
}
/**
* Main entry point for the SASS compiler. Takes in a file and builds up a
* ScssStylesheet tree out of it. Calling compile() on it will transform
* SASS into CSS. Calling printState() will print out the SCSS/CSS.
*
* @param identifier
* The file path. If null then null is returned.
* @return
* @throws CSSException
* @throws IOException
*/
public static ScssStylesheet get(String identifier) throws CSSException,
IOException {
return get(identifier, null);
}
/**
* Main entry point for the SASS compiler. Takes in a file and an optional
* parent style sheet, then builds up a ScssStylesheet tree out of it.
* Calling compile() on it will transform SASS into CSS. Calling
* printState() will print out the SCSS/CSS.
*
* @param identifier
* The file path. If null then null is returned.
* @param parentStylesheet
* Style sheet from which to inherit resolvers and encoding. May
* be null.
* @return
* @throws CSSException
* @throws IOException
*/
public static ScssStylesheet get(String identifier,
ScssStylesheet parentStylesheet) throws CSSException, IOException {
return get(identifier, parentStylesheet, new SCSSDocumentHandlerImpl(),
new SCSSErrorHandler());
}
/**
* Main entry point for the SASS compiler. Takes in a file, an optional
* parent stylesheet, and document and error handlers. Then builds up a
* ScssStylesheet tree out of it. Calling compile() on it will transform
* SASS into CSS. Calling printState() will print out the SCSS/CSS.
*
* @param identifier
* The file path. If null then null is returned.
* @param parentStylesheet
* Style sheet from which to inherit resolvers and encoding. May
* be null.
* @param documentHandler
* Instance of document handler. May not be null.
* @param errorHandler
* Instance of error handler. May not be null.
* @return
* @throws CSSException
* @throws IOException
*/
public static ScssStylesheet get(String identifier,
ScssStylesheet parentStylesheet,
SCSSDocumentHandler documentHandler, SCSSErrorHandler errorHandler)
throws CSSException, IOException {
/*
* The encoding to be used is passed through "encoding" parameter. the
* imported children scss node will have the same encoding as their
* parent, ultimately the root scss file. The root scss node has this
* "encoding" parameter to be null. Its encoding is determined by the
*
* @charset declaration, the default one is ASCII.
*/
if (identifier == null) {
return null;
}
// FIXME Is this actually intended? /John 1.3.2013
File file = new File(identifier);
file = file.getCanonicalFile();
ScssStylesheet stylesheet = documentHandler.getStyleSheet();
if (parentStylesheet == null) {
// Use default resolvers
stylesheet.addResolver(new FilesystemResolver());
stylesheet.addResolver(new ClassloaderResolver());
} else {
// Use parent resolvers
stylesheet.setResolvers(parentStylesheet.getResolvers());
}
InputSource source = stylesheet.resolveStylesheet(identifier,
parentStylesheet);
if (source == null) {
return null;
}
if (parentStylesheet != null) {
source.setEncoding(parentStylesheet.getCharset());
}
Parser parser = new Parser();
parser.setErrorHandler(errorHandler);
parser.setDocumentHandler(documentHandler);
try {
parser.parseStyleSheet(source);
} catch (ParseException e) {
// catch ParseException, re-throw a SCSSParseException which has
// file name info.
throw new SCSSParseException(e, identifier);
}
stylesheet.setCharset(parser.getInputSource().getEncoding());
return stylesheet;
}
public InputSource resolveStylesheet(String identifier,
ScssStylesheet parentStylesheet) {
for (ScssStylesheetResolver resolver : getResolvers()) {
InputSource source = resolver.resolve(parentStylesheet, identifier);
if (source != null) {
File f = new File(source.getURI());
setFile(f);
return source;
}
}
return null;
}
/**
* Retrieves a list of resolvers to use when resolving imports
*
* @return the resolvers used to resolving imports
*/
public List getResolvers() {
return Collections.unmodifiableList(resolvers);
}
/**
* Sets the list of resolvers to use when resolving imports
*
* @param resolvers
* the resolvers to set
*/
public void setResolvers(List resolvers) {
this.resolvers = new ArrayList(resolvers);
}
/**
* Adds the given resolver to the resolver list
*
* @param resolver
* The resolver to add
*/
public void addResolver(ScssStylesheetResolver resolver) {
resolvers.add(resolver);
}
/**
* Applies all the visitors and compiles SCSS into Css.
*
* @throws Exception
*/
public void compile() throws Exception {
mainStyleSheet = this;
mixinDefs.clear();
functionDefs.clear();
variables.clear();
ifElseDefNodes.clear();
ExtendNodeHandler.clear();
importOtherFiles(this);
populateDefinitions(this);
traverse();
ExtendNodeHandler.modifyTree(this);
removeEmptyBlocks(this);
}
private void importOtherFiles(ScssStylesheet node) {
ImportNodeHandler.traverse(node);
}
private void populateDefinitions(Node node) {
if (node instanceof MixinDefNode) {
mixinDefs.put(((MixinDefNode) node).getName(), (MixinDefNode) node);
node.removeFromParent();
} else if (node instanceof FunctionDefNode) {
functionDefs.put(((FunctionDefNode) node).getName(),
(FunctionDefNode) node);
node.removeFromParent();
} else if (node instanceof IfElseDefNode) {
ifElseDefNodes.add((IfElseDefNode) node);
}
for (final Node child : new ArrayList(node.getChildren())) {
populateDefinitions(child);
}
}
/**
* Prints out the current state of the node tree. Will return SCSS before
* compile and CSS after.
*
* For now this is an own method with it's own implementation that most node
* types will implement themselves.
*/
@Override
public String printState() {
return buildString(PRINT_STRATEGY);
}
@Override
public String toString() {
return "Stylesheet node [" + buildString(TO_STRING_STRATEGY) + "]";
}
public static ScssStylesheet get() {
return mainStyleSheet;
}
/**
* Traverses a node and its children recursively, calling all the
* appropriate handlers via {@link Node#traverse()}.
*
* The node itself may be removed during the traversal and replaced with
* other nodes at the same position or later on the child list of its
* parent.
*/
@Override
public void traverse() {
traverseChildren();
}
/**
* Start a new scope for variables. Any variables set or modified after
* opening a new scope are only valid until the scope is closed, at which
* time they are replaced with their old values.
*
* @return old scope to give to a paired {@link #closeVariableScope(Map)}
* call at the end of the scope (unmodifiable map).
*/
public static Map openVariableScope() {
@SuppressWarnings("unchecked")
HashMap variableScope = (HashMap) variables
.clone();
return Collections.unmodifiableMap(variableScope);
}
/**
* End a scope for variables, replacing all active variables with those from
* the original scope (obtained from {@link #openVariableScope()}).
*
* @param originalScope
* original scope
*/
public static void closeVariableScope(
Map originalScope) {
variables.clear();
variables.putAll(originalScope);
}
public void removeEmptyBlocks(Node node) {
// depth first for avoiding re-checking parents of removed nodes
for (Node child : new ArrayList(node.getChildren())) {
removeEmptyBlocks(child);
}
if (node instanceof BlockNode && node.getChildren().isEmpty()) {
// remove empty block
node.removeFromParent();
}
}
public static void addVariable(VariableNode node) {
variables.put(node.getName(), node);
}
public static VariableNode getVariable(String string) {
return variables.get(string);
}
public static ArrayList getVariables() {
return new ArrayList(variables.values());
}
public static MixinDefNode getMixinDefinition(String name) {
return mixinDefs.get(name);
}
public static FunctionDefNode getFunctionDefinition(String name) {
return functionDefs.get(name);
}
public void setFile(File file) {
this.file = file;
}
/**
* Returns the directory containing this style sheet
*
* @return The directory containing this style sheet
*/
public String getDirectory() {
return file.getParent();
}
/**
* Returns the full file name for this style sheet
*
* @return The full file name for this style sheet
*/
public String getFileName() {
return file.getPath();
}
public static final void warning(String msg) {
Logger.getLogger(ScssStylesheet.class.getName()).warning(msg);
}
public String getCharset() {
return charset;
}
public void setCharset(String charset) {
this.charset = charset;
}
private String buildString(BuildStringStrategy strategy) {
StringBuilder string = new StringBuilder("");
String delimeter = "\n\n";
// add charset declaration, if it is not default "ASCII".
if (!"ASCII".equals(getCharset())) {
string.append("@charset \"").append(getCharset()).append("\";")
.append(delimeter);
}
List children = getChildren();
if (children.size() > 0) {
string.append(strategy.build(children.get(0)));
}
if (children.size() > 1) {
for (int i = 1; i < children.size(); i++) {
String childString = strategy.build(children.get(i));
if (childString != null) {
string.append(delimeter).append(childString);
}
}
}
String output = string.toString();
return output;
}
static {
String logFile = System.getProperty("java.util.logging.config.file");
if (logFile == null) {
try {
LogManager.getLogManager().readConfiguration(
ScssStylesheet.class
.getResourceAsStream("/logging.properties"));
} catch (SecurityException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy