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 {
variables.clear();
ifElseDefNodes.clear();
lastNodeAdded.clear();
+ ExtendNodeHandler.clear();
importOtherFiles(this);
populateDefinitions(this);
traverse(this);
package com.vaadin.sass.internal.util;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
+import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
* @return true if the text contains the SCSS variable, false if not
*/
public static boolean containsVariable(String text, String varName) {
- StringBuilder builder = new StringBuilder();
- // (?![\\w-]) means lookahead, the next one shouldn't be a word
- // character nor a dash.
- builder.append("\\$").append(Pattern.quote(varName))
- .append("(?![\\w-])");
- Pattern pattern = Pattern.compile(builder.toString());
- Matcher matcher = pattern.matcher(text);
- return matcher.find();
+ return containsSubString(text, "$" + varName);
}
/**
* @param text
* text which contains the SCSS variable
* @param varName
- * SCSS variable name
+ * SCSS variable name (Without '$' sign)
* @param value
* the value of the SCSS variable
* @return the String after replacing
*/
public static String replaceVariable(String text, String varName,
String value) {
+ return replaceSubString(text, "$" + varName, value);
+ }
+
+ /**
+ * Check if a String contains a sub string, using whole word match.
+ *
+ * @param text
+ * text to be checked
+ * @Param sub Sub String to be checked.
+ * @return true if the text contains the sub string, false if not
+ */
+ public static boolean containsSubString(String text, String sub) {
StringBuilder builder = new StringBuilder();
// (?![\\w-]) means lookahead, the next one shouldn't be a word
// character nor a dash.
- builder.append("\\$").append(Pattern.quote(varName))
+ builder.append("(?<![\\w-])").append(Pattern.quote(sub))
+ .append("(?![\\w-])");
+ Pattern pattern = Pattern.compile(builder.toString());
+ Matcher matcher = pattern.matcher(text);
+ return matcher.find();
+ }
+
+ /**
+ * Replace the sub string in a String to a value, using whole word match.
+ *
+ * @param text
+ * text which contains the sub string
+ * @param sub
+ * the sub string
+ * @param value
+ * the new value
+ * @return the String after replacing
+ */
+ public static String replaceSubString(String text, String sub, String value) {
+ StringBuilder builder = new StringBuilder();
+ // (?![\\w-]) means lookahead, the next one shouldn't be a word
+ // character nor a dash.
+ builder.append("(?<![\\w-])").append(Pattern.quote(sub))
.append("(?![\\w-])");
return text.replaceAll(builder.toString(), value);
}
+
+ /**
+ * Remove duplicated sub string in a String given a splitter. Can be used to
+ * removed duplicated selectors, e.g., in ".error.error", one duplicated
+ * ".error" can be removed.
+ *
+ * @param motherString
+ * string which may contains duplicated sub strings
+ * @param splitter
+ * the splitter splits the mother string to sub strings
+ * @return the mother string with duplicated sub strings removed
+ */
+ public static String removeDuplicatedSubString(String motherString,
+ String splitter) {
+ List<String> subStrings = Arrays.asList(motherString.split(Pattern
+ .quote(splitter)));
+ LinkedHashSet<String> uniqueSubStrings = new LinkedHashSet<String>(
+ subStrings);
+ StringBuilder builder = new StringBuilder();
+ int count = 0;
+ for (String uniqueSubString : uniqueSubStrings) {
+ count++;
+ builder.append(uniqueSubString);
+ if (count < uniqueSubStrings.size()) {
+ builder.append(splitter);
+ }
+ }
+ return builder.toString();
+ }
}
import com.vaadin.sass.internal.tree.BlockNode;
import com.vaadin.sass.internal.tree.ExtendNode;
import com.vaadin.sass.internal.tree.Node;
+import com.vaadin.sass.internal.util.StringUtil;
public class ExtendNodeHandler {
private static Map<String, List<ArrayList<String>>> extendsMap = new HashMap<String, List<ArrayList<String>>>();
modifyTree(ScssStylesheet.get());
}
+ public static void clear() {
+ if (extendsMap != null) {
+ extendsMap.clear();
+ }
+ }
+
private static void modifyTree(Node node) throws Exception {
for (Node child : node.getChildren()) {
if (child instanceof BlockNode) {
} else {
for (Entry<String, List<ArrayList<String>>> entry : extendsMap
.entrySet()) {
- if (selectorString.contains(entry.getKey())) {
+ if (StringUtil.containsSubString(selectorString,
+ entry.getKey())) {
for (ArrayList<String> sList : entry.getValue()) {
ArrayList<String> clone = (ArrayList<String>) sList
.clone();
if (extendsMap.get(extendedString) == null) {
extendsMap.put(extendedString, new ArrayList<ArrayList<String>>());
}
- extendsMap.get(extendedString).add(
- ((BlockNode) node.getParentNode()).getSelectorList());
+ // prevent a selector extends itself, e.g. .test{ @extend .test}
+ String parentSelectorString = ((BlockNode) node.getParentNode())
+ .getSelectors();
+ if (!parentSelectorString.equals(extendedString)) {
+ extendsMap.get(extendedString).add(
+ ((BlockNode) node.getParentNode()).getSelectorList());
+ }
}
private static void addAdditionalSelectorListToBlockNode(
- BlockNode blockNode, ArrayList<String> list, String selectorString) {
- if (list != null) {
- for (int i = 0; i < list.size(); i++) {
- if (selectorString == null) {
- blockNode.getSelectorList().add(list.get(i));
+ BlockNode blockNode, ArrayList<String> extendingSelectors,
+ String extendedSelector) {
+ if (extendingSelectors != null) {
+ for (String extendingSelector : extendingSelectors) {
+ if (extendedSelector == null) {
+ blockNode.getSelectorList().add(extendingSelector);
} else {
ArrayList<String> newTags = new ArrayList<String>();
- for (final String existing : blockNode.getSelectorList()) {
- if (existing.contains(selectorString)) {
- newTags.add(existing.replace(selectorString,
- list.get(i)));
+ for (final String selectorString : blockNode
+ .getSelectorList()) {
+ if (StringUtil.containsSubString(selectorString,
+ extendedSelector)) {
+ String newTag = generateExtendingSelectors(
+ selectorString, extendedSelector,
+ extendingSelector);
+ // prevent adding duplicated selector list
+ if (!blockNode.getSelectorList().contains(newTag)
+ && !newTags.contains(newTag)) {
+ newTags.add(newTag);
+ }
}
}
blockNode.getSelectorList().addAll(newTags);
}
}
}
+
+ private static String generateExtendingSelectors(String selectorString,
+ String extendedSelector, String extendingSelector) {
+ String result = StringUtil.replaceSubString(selectorString,
+ extendedSelector, extendingSelector);
+ // remove duplicated class selectors.
+ if (result.startsWith(".")) {
+ result = StringUtil.removeDuplicatedSubString(result, ".");
+ }
+ return result;
+ }
}
--- /dev/null
+.test .error, .test .seriousError {
+ border: 1px #f00;
+ background-color: #fdd;
+}
+.test .seriousError {
+ border-width: 3px;
+}
\ No newline at end of file
--- /dev/null
+.test .middle .error, .test .middle .seriousError {
+ border: 1px #f00;
+ background-color: #fdd;
+}
+.test .seriousError {
+ border-width: 3px;
+}
+
+.test1 .error1, .test1 .middle1 .seriousError1 {
+ border: 1px #f00;
+ background-color: #fdd;
+}
+.test1 .middle1 .seriousError1 {
+ border-width: 3px;
+}
\ No newline at end of file
--- /dev/null
+.test1 {
+ color: blue;
+}
+
+.test2 {
+ background: red;
+}
\ No newline at end of file
--- /dev/null
+.test {
+ color: blue;
+}
+
+.test {
+ background: red;
+}
\ No newline at end of file
--- /dev/null
+.test1, .test2 {
+ color: blue;
+}
+
+.test2 {
+ background: red;
+}
\ No newline at end of file
--- /dev/null
+.test{
+ .error {
+ border: 1px #f00;
+ background-color: #fdd;
+ }
+
+ .seriousError {
+ @extend .error;
+ border-width: 3px;
+ }
+}
\ No newline at end of file
--- /dev/null
+.test{
+ .middle{
+ .error {
+ border: 1px #f00;
+ background-color: #fdd;
+ }
+ }
+
+ .seriousError {
+ @extend .error;
+ border-width: 3px;
+ }
+}
+
+.test1{
+ .error1 {
+ border: 1px #f00;
+ background-color: #fdd;
+ }
+ .middle1{
+ .seriousError1 {
+ @extend .error1;
+ border-width: 3px;
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+.test1 {
+ color: blue;
+}
+
+.test2 {
+ @extend .test;
+ background: red;
+}
\ No newline at end of file
--- /dev/null
+.test {
+ color: blue;
+}
+
+.test {
+ @extend .test;
+ background: red;
+}
\ No newline at end of file
--- /dev/null
+.test1 {
+ color: blue;
+}
+
+.test2 {
+ @extend .test1;
+ background: red;
+}
\ No newline at end of file
--- /dev/null
+.true, .also-true {
+ color: green;
+}
+
+.false, .also-false {
+ color: red;
+}
--- /dev/null
+.true { color: green; }
+.false { color: red; }
+.also-true {
+ @if true { @extend .true; }
+ @else { @extend .false; }
+}
+.also-false {
+ @if false { @extend .true; }
+ @else { @extend .false; }
+}
+++ /dev/null
-.true, .also-true {
- color: green;
-}
-
-.false, .also-false {
- color: red;
-}
+++ /dev/null
-.true { color: green; }
-.false { color: red; }
-.also-true {
- @if true { @extend .true; }
- @else { @extend .false; }
-}
-.also-false {
- @if false { @extend .true; }
- @else { @extend .false; }
-}
Assert.assertEquals(sentence,
StringUtil.replaceVariable(sentence, word, value));
}
+
+ @Test
+ public void testContainsSubString() {
+ String sentence = "var1 var2";
+ String word = "var";
+ Assert.assertFalse(StringUtil.containsSubString(sentence, word));
+
+ word = "var1";
+ Assert.assertTrue(StringUtil.containsSubString(sentence, word));
+
+ String var2 = "var2";
+ Assert.assertTrue(StringUtil.containsSubString(sentence, var2));
+
+ Assert.assertTrue(StringUtil.containsSubString(".error.intrusion",
+ ".error"));
+
+ Assert.assertFalse(StringUtil.containsSubString(".foo", "oo"));
+ }
+
+ @Test
+ public void testContainsSubStringWithDash() {
+ String sentence = "var- var2";
+ String word = "var";
+ Assert.assertFalse(StringUtil.containsSubString(sentence, word));
+ }
+
+ @Test
+ public void testReplaceSubString() {
+ String sentence = "var1 var2";
+ String word = "var";
+ String value = "abc";
+
+ word = "var1";
+ Assert.assertEquals("abc var2",
+ StringUtil.replaceSubString(sentence, word, value));
+
+ String var2 = "var1 abc";
+ Assert.assertEquals(sentence,
+ StringUtil.replaceSubString(sentence, var2, value));
+
+ Assert.assertEquals(".foo",
+ StringUtil.replaceSubString(".foo", "oo", "aa"));
+ }
+
+ @Test
+ public void testReplaceSubStringWithDash() {
+ String sentence = "var- var2";
+ String word = "var";
+ String value = "abc";
+ Assert.assertEquals(sentence,
+ StringUtil.replaceSubString(sentence, word, value));
+ }
+
+ @Test
+ public void testRemoveDuplicatedClassSelector() {
+ Assert.assertEquals(".seriousError", StringUtil
+ .removeDuplicatedSubString(".seriousError.seriousError", "."));
+ }
}