You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ScssStylesheet.java 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. /*
  2. * Copyright 2000-2013 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.sass.internal;
  17. import java.io.File;
  18. import java.io.IOException;
  19. import java.util.ArrayList;
  20. import java.util.Arrays;
  21. import java.util.Collections;
  22. import java.util.HashMap;
  23. import java.util.HashSet;
  24. import java.util.Map;
  25. import java.util.logging.Logger;
  26. import org.w3c.css.sac.CSSException;
  27. import org.w3c.css.sac.InputSource;
  28. import com.vaadin.sass.internal.handler.SCSSDocumentHandler;
  29. import com.vaadin.sass.internal.handler.SCSSDocumentHandlerImpl;
  30. import com.vaadin.sass.internal.handler.SCSSErrorHandler;
  31. import com.vaadin.sass.internal.parser.ParseException;
  32. import com.vaadin.sass.internal.parser.Parser;
  33. import com.vaadin.sass.internal.parser.SCSSParseException;
  34. import com.vaadin.sass.internal.resolver.ScssStylesheetResolver;
  35. import com.vaadin.sass.internal.resolver.VaadinResolver;
  36. import com.vaadin.sass.internal.tree.BlockNode;
  37. import com.vaadin.sass.internal.tree.MixinDefNode;
  38. import com.vaadin.sass.internal.tree.Node;
  39. import com.vaadin.sass.internal.tree.VariableNode;
  40. import com.vaadin.sass.internal.tree.controldirective.IfElseDefNode;
  41. import com.vaadin.sass.internal.visitor.ExtendNodeHandler;
  42. import com.vaadin.sass.internal.visitor.ImportNodeHandler;
  43. public class ScssStylesheet extends Node {
  44. private static final long serialVersionUID = 3849790204404961608L;
  45. private static ScssStylesheet mainStyleSheet = null;
  46. private static final HashMap<String, VariableNode> variables = new HashMap<String, VariableNode>();
  47. private static final Map<String, MixinDefNode> mixinDefs = new HashMap<String, MixinDefNode>();
  48. private static final HashSet<IfElseDefNode> ifElseDefNodes = new HashSet<IfElseDefNode>();
  49. private static HashMap<Node, Node> lastNodeAdded = new HashMap<Node, Node>();
  50. private String fileName;
  51. private String charset;
  52. /**
  53. * Read in a file SCSS and parse it into a ScssStylesheet
  54. *
  55. * @param file
  56. * @throws IOException
  57. */
  58. public ScssStylesheet() {
  59. super();
  60. }
  61. /**
  62. * Main entry point for the SASS compiler. Takes in a file and builds up a
  63. * ScssStylesheet tree out of it. Calling compile() on it will transform
  64. * SASS into CSS. Calling toString() will print out the SCSS/CSS.
  65. *
  66. * @param identifier
  67. * The file path. If null then null is returned.
  68. * @return
  69. * @throws CSSException
  70. * @throws IOException
  71. */
  72. public static ScssStylesheet get(String identifier) throws CSSException,
  73. IOException {
  74. return get(identifier, null);
  75. }
  76. /**
  77. * Main entry point for the SASS compiler. Takes in a file and encoding then
  78. * builds up a ScssStylesheet tree out of it. Calling compile() on it will
  79. * transform SASS into CSS. Calling toString() will print out the SCSS/CSS.
  80. *
  81. * @param identifier
  82. * The file path. If null then null is returned.
  83. * @param encoding
  84. * @return
  85. * @throws CSSException
  86. * @throws IOException
  87. */
  88. public static ScssStylesheet get(String identifier, String encoding)
  89. throws CSSException, IOException {
  90. /*
  91. * The encoding to be used is passed through "encoding" parameter. the
  92. * imported children scss node will have the same encoding as their
  93. * parent, ultimately the root scss file. The root scss node has this
  94. * "encoding" parameter to be null. Its encoding is determined by the
  95. *
  96. * @charset declaration, the default one is ASCII.
  97. */
  98. if (identifier == null) {
  99. return null;
  100. }
  101. // FIXME Is this actually intended? /John 1.3.2013
  102. File file = new File(identifier);
  103. file = file.getCanonicalFile();
  104. SCSSDocumentHandler handler = new SCSSDocumentHandlerImpl();
  105. ScssStylesheet stylesheet = handler.getStyleSheet();
  106. InputSource source = stylesheet.resolveStylesheet(identifier);
  107. if (source == null) {
  108. return null;
  109. }
  110. source.setEncoding(encoding);
  111. Parser parser = new Parser();
  112. parser.setErrorHandler(new SCSSErrorHandler());
  113. parser.setDocumentHandler(handler);
  114. try {
  115. parser.parseStyleSheet(source);
  116. } catch (ParseException e) {
  117. // catch ParseException, re-throw a SCSSParseException which has
  118. // file name info.
  119. throw new SCSSParseException(e, identifier);
  120. }
  121. stylesheet.setCharset(parser.getInputSource().getEncoding());
  122. return stylesheet;
  123. }
  124. private static ScssStylesheetResolver[] resolvers = null;
  125. public static void setStylesheetResolvers(
  126. ScssStylesheetResolver... styleSheetResolvers) {
  127. resolvers = Arrays.copyOf(styleSheetResolvers,
  128. styleSheetResolvers.length);
  129. }
  130. public InputSource resolveStylesheet(String identifier) {
  131. if (resolvers == null) {
  132. setStylesheetResolvers(new VaadinResolver());
  133. }
  134. for (ScssStylesheetResolver resolver : resolvers) {
  135. InputSource source = resolver.resolve(identifier);
  136. if (source != null) {
  137. File f = new File(source.getURI());
  138. setFileName(f.getParent());
  139. return source;
  140. }
  141. }
  142. return null;
  143. }
  144. /**
  145. * Applies all the visitors and compiles SCSS into Css.
  146. *
  147. * @throws Exception
  148. */
  149. public void compile() throws Exception {
  150. mainStyleSheet = this;
  151. mixinDefs.clear();
  152. variables.clear();
  153. ifElseDefNodes.clear();
  154. lastNodeAdded.clear();
  155. ExtendNodeHandler.clear();
  156. importOtherFiles(this);
  157. populateDefinitions(this);
  158. traverse(this);
  159. removeEmptyBlocks(this);
  160. }
  161. private void importOtherFiles(ScssStylesheet node) {
  162. ImportNodeHandler.traverse(node);
  163. }
  164. private void populateDefinitions(Node node) {
  165. if (node instanceof MixinDefNode) {
  166. mixinDefs.put(((MixinDefNode) node).getName(), (MixinDefNode) node);
  167. node.getParentNode().removeChild(node);
  168. } else if (node instanceof IfElseDefNode) {
  169. ifElseDefNodes.add((IfElseDefNode) node);
  170. }
  171. for (final Node child : new ArrayList<Node>(node.getChildren())) {
  172. populateDefinitions(child);
  173. }
  174. }
  175. /**
  176. * Prints out the current state of the node tree. Will return SCSS before
  177. * compile and CSS after.
  178. *
  179. * For now this is an own method with it's own implementation that most node
  180. * types will implement themselves.
  181. */
  182. @Override
  183. public String toString() {
  184. StringBuilder string = new StringBuilder("");
  185. String delimeter = "\n\n";
  186. // add charset declaration, if it is not default "ASCII".
  187. if (!"ASCII".equals(getCharset())) {
  188. string.append("@charset \"").append(getCharset()).append("\";")
  189. .append(delimeter);
  190. }
  191. if (children.size() > 0) {
  192. string.append(children.get(0).toString());
  193. }
  194. if (children.size() > 1) {
  195. for (int i = 1; i < children.size(); i++) {
  196. String childString = children.get(i).toString();
  197. if (childString != null) {
  198. string.append(delimeter).append(childString);
  199. }
  200. }
  201. }
  202. String output = string.toString();
  203. return output;
  204. }
  205. public void addChild(int index, VariableNode node) {
  206. if (node != null) {
  207. children.add(index, node);
  208. }
  209. }
  210. public static ScssStylesheet get() {
  211. return mainStyleSheet;
  212. }
  213. @Override
  214. public void traverse() {
  215. // Not used for ScssStylesheet
  216. }
  217. /**
  218. * Traverses a node and its children recursively, calling all the
  219. * appropriate handlers via {@link Node#traverse()}.
  220. *
  221. * The node itself may be removed during the traversal and replaced with
  222. * other nodes at the same position or later on the child list of its
  223. * parent.
  224. *
  225. * @param node
  226. * node to traverse
  227. * @return true if the node was removed (and possibly replaced by others),
  228. * false if not
  229. */
  230. public boolean traverse(Node node) {
  231. Node originalParent = node.getParentNode();
  232. node.traverse();
  233. Map<String, VariableNode> variableScope = openVariableScope();
  234. // the size of the child list may change on each iteration: current node
  235. // may get deleted and possibly other nodes have been inserted where it
  236. // was or after that position
  237. for (int i = 0; i < node.getChildren().size(); i++) {
  238. Node current = node.getChildren().get(i);
  239. if (traverse(current)) {
  240. // current has been removed
  241. --i;
  242. }
  243. }
  244. closeVariableScope(variableScope);
  245. // clean up insert point so that processing of the next block will
  246. // insert after that block
  247. lastNodeAdded.remove(originalParent);
  248. // has the node been removed from its parent?
  249. if (originalParent != null) {
  250. boolean removed = !originalParent.getChildren().contains(node);
  251. return removed;
  252. } else {
  253. return false;
  254. }
  255. }
  256. /**
  257. * Start a new scope for variables. Any variables set or modified after
  258. * opening a new scope are only valid until the scope is closed, at which
  259. * time they are replaced with their old values.
  260. *
  261. * @return old scope to give to a paired {@link #closeVariableScope(Map)}
  262. * call at the end of the scope (unmodifiable map).
  263. */
  264. public static Map<String, VariableNode> openVariableScope() {
  265. @SuppressWarnings("unchecked")
  266. HashMap<String, VariableNode> variableScope = (HashMap<String, VariableNode>) variables
  267. .clone();
  268. return Collections.unmodifiableMap(variableScope);
  269. }
  270. /**
  271. * End a scope for variables, replacing all active variables with those from
  272. * the original scope (obtained from {@link #openVariableScope()}).
  273. *
  274. * @param originalScope
  275. * original scope
  276. */
  277. public static void closeVariableScope(
  278. Map<String, VariableNode> originalScope) {
  279. variables.clear();
  280. variables.putAll(originalScope);
  281. }
  282. public void removeEmptyBlocks(Node node) {
  283. // depth first for avoiding re-checking parents of removed nodes
  284. for (Node child : new ArrayList<Node>(node.getChildren())) {
  285. removeEmptyBlocks(child);
  286. }
  287. Node parent = node.getParentNode();
  288. if (node instanceof BlockNode && node.getChildren().isEmpty()
  289. && parent != null) {
  290. // remove empty block
  291. parent.removeChild(node);
  292. }
  293. }
  294. public static void addVariable(VariableNode node) {
  295. variables.put(node.getName(), node);
  296. }
  297. public static VariableNode getVariable(String string) {
  298. return variables.get(string);
  299. }
  300. public static ArrayList<VariableNode> getVariables() {
  301. return new ArrayList<VariableNode>(variables.values());
  302. }
  303. public static MixinDefNode getMixinDefinition(String name) {
  304. return mixinDefs.get(name);
  305. }
  306. public void setFileName(String fileName) {
  307. this.fileName = fileName;
  308. }
  309. public String getFileName() {
  310. return fileName;
  311. }
  312. public static HashMap<Node, Node> getLastNodeAdded() {
  313. return lastNodeAdded;
  314. }
  315. public static final void warning(String msg) {
  316. Logger.getLogger(ScssStylesheet.class.getName()).warning(msg);
  317. }
  318. public String getCharset() {
  319. return charset;
  320. }
  321. public void setCharset(String charset) {
  322. this.charset = charset;
  323. }
  324. }