Skip to content

Commit

Permalink
Less escaping: #54 #21
Browse files Browse the repository at this point in the history
  • Loading branch information
meri committed Dec 12, 2012
1 parent 7529561 commit f180919
Show file tree
Hide file tree
Showing 15 changed files with 255 additions and 14 deletions.
37 changes: 35 additions & 2 deletions src/main/antlr3/com/github/sommeri/less4j/core/parser/Less.g
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@ tokens {
NESTED_APPENDER;
SELECTOR;
SIMPLE_SELECTOR;
ESCAPED_SELECTOR;
EXPRESSION;
EXPRESSION_PARENTHESES;
ESCAPED_VALUE;
STYLE_SHEET;
//ANTLR seems to generate null pointers exceptions on malformed css if
//rules match nothig and generate an empty token
Expand Down Expand Up @@ -353,15 +355,17 @@ ruleset_body
selector
@init {enterRule(retval, RULE_SELECTOR);}
: ((combinator)=>a+=combinator | )
(a+=simpleSelector | a+=nestedAppender)
(a+=simpleSelector | a+=nestedAppender | a+=escapedSelector)
(
((combinator)=>a+=combinator | )
(a+=simpleSelector | a+=nestedAppender)
(a+=simpleSelector | a+=nestedAppender | a+=escapedSelector)
)*
-> ^(SELECTOR $a* )
;
finally { leaveRule(); }

escapedSelector: SELECTOR_ESCAPE -> ^(ESCAPED_SELECTOR SELECTOR_ESCAPE);

simpleSelector
: ( (a+=elementName ( {!predicates.onEmptyCombinator(input)}?=>a+=elementSubsequent)*)
| (a+=elementSubsequent ( {!predicates.onEmptyCombinator(input)}?=>a+=elementSubsequent)*)
Expand Down Expand Up @@ -604,9 +608,12 @@ term
)
) | (a+=unsigned_value_term
| a+=hexColor
| a+=escapedValue
| a+=special_function))
-> ^(TERM $a*)
;

escapedValue: VALUE_ESCAPE -> ^(ESCAPED_VALUE VALUE_ESCAPE);

expr_in_parentheses
: LPAREN expr RPAREN -> ^(EXPRESSION_PARENTHESES LPAREN expr RPAREN );
Expand Down Expand Up @@ -1029,6 +1036,32 @@ STRING : '\'' ( ESCAPED_SIMBOL | ~('\n'|'\r'|'\f'|'\\'|'\'') )*
| { $type = INVALID; }
)
;

VALUE_ESCAPE : '~' '\'' ( ESCAPED_SIMBOL | ~('\n'|'\r'|'\f'|'\\'|'\'') )*
(
'\''
| { $type = INVALID; }
)

| '~' '"' ( ESCAPED_SIMBOL | ~('\n'|'\r'|'\f'|'\\'|'"') )*
(
'"'
| { $type = INVALID; }
)
;

SELECTOR_ESCAPE : '(' '~' '\'' ( ESCAPED_SIMBOL | ~('\n'|'\r'|'\f'|'\\'|'\'') )*
(
'\'' ')'
| { $type = INVALID; }
)

| '(' '~' '"' ( ESCAPED_SIMBOL | ~('\n'|'\r'|'\f'|'\\'|'"') )*
(
'"' ')'
| { $type = INVALID; }
)
;

// -------------
// Identifier. Identifier tokens pick up properties names and values
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package com.github.sommeri.less4j.core.ast;

public enum ASTCssNodeType {
UNKNOWN, CSS_CLASS, DECLARATION, STYLE_SHEET, RULE_SET, SELECTOR, SIMPLE_SELECTOR, PSEUDO_CLASS, PSEUDO_ELEMENT, SELECTOR_ATTRIBUTE, ID_SELECTOR, CHARSET_DECLARATION, FONT_FACE, IDENTIFIER_EXPRESSION, COMPOSED_EXPRESSION, STRING_EXPRESSION, NUMBER, COLOR_EXPRESSION, UNCTION, FUNCTION, MEDIA, COMMENT, DECLARATIONS_BODY, SELECTOR_OPERATOR, SELECTOR_COMBINATOR, EXPRESSION_OPERATOR, NTH, NAMED_EXPRESSION, MEDIA_QUERY, MEDIA_EXPRESSION, MEDIUM, MEDIUM_MODIFIER, MEDIUM_TYPE, MEDIUM_EX_FEATURE, VARIABLE_DECLARATION, VARIABLE, INDIRECT_VARIABLE, PARENTHESES_EXPRESSION, SIGNED_EXPRESSION, ARGUMENT_DECLARATION, MIXIN_REFERENCE, GUARD_CONDITION, COMPARISON_EXPRESSION, GUARD, NAMESPACE_REFERENCE, NESTED_SELECTOR_APPENDER, REUSABLE_STRUCTURE, FAULTY_EXPRESSION
UNKNOWN, CSS_CLASS, DECLARATION, STYLE_SHEET, RULE_SET, SELECTOR, SIMPLE_SELECTOR, PSEUDO_CLASS, PSEUDO_ELEMENT, SELECTOR_ATTRIBUTE, ID_SELECTOR, CHARSET_DECLARATION, FONT_FACE, IDENTIFIER_EXPRESSION, COMPOSED_EXPRESSION, STRING_EXPRESSION, NUMBER, COLOR_EXPRESSION, UNCTION, FUNCTION, MEDIA, COMMENT, DECLARATIONS_BODY, SELECTOR_OPERATOR, SELECTOR_COMBINATOR, EXPRESSION_OPERATOR, NTH, NAMED_EXPRESSION, MEDIA_QUERY, MEDIA_EXPRESSION, MEDIUM, MEDIUM_MODIFIER, MEDIUM_TYPE, MEDIUM_EX_FEATURE, VARIABLE_DECLARATION, VARIABLE, INDIRECT_VARIABLE, PARENTHESES_EXPRESSION, SIGNED_EXPRESSION, ARGUMENT_DECLARATION, MIXIN_REFERENCE, GUARD_CONDITION, COMPARISON_EXPRESSION, GUARD, NAMESPACE_REFERENCE, NESTED_SELECTOR_APPENDER, REUSABLE_STRUCTURE, FAULTY_EXPRESSION, ESCAPED_SELECTOR, ESCAPED_VALUE
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.github.sommeri.less4j.core.ast;

import java.util.Collections;
import java.util.List;

import com.github.sommeri.less4j.core.parser.HiddenTokenAwareTree;

public class EscapedSelector extends SelectorPart {

private String value;

public EscapedSelector(HiddenTokenAwareTree underlyingStructure, String value) {
super(underlyingStructure);
this.value = value;
}

public String getValue() {
return value;
}

public void setValue(String value) {
this.value = value;
}

@Override
public String toString() {
return value;
}

@Override
public EscapedSelector clone() {
return (EscapedSelector)super.clone();
}


@Override
public List<? extends ASTCssNode> getChilds() {
return Collections.emptyList();
}

@Override
public ASTCssNodeType getType() {
return ASTCssNodeType.ESCAPED_SELECTOR;
}

}
44 changes: 44 additions & 0 deletions src/main/java/com/github/sommeri/less4j/core/ast/EscapedValue.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.github.sommeri.less4j.core.ast;

import java.util.Collections;
import java.util.List;

import com.github.sommeri.less4j.core.parser.HiddenTokenAwareTree;

public class EscapedValue extends Expression {

private String value;

public EscapedValue(HiddenTokenAwareTree token, String value) {
super(token);
this.value = value;
}

public String getValue() {
return value;
}

public void setValue(String value) {
this.value = value;
}

@Override
public ASTCssNodeType getType() {
return ASTCssNodeType.ESCAPED_VALUE;
}

@Override
public List<? extends ASTCssNode> getChilds() {
return Collections.emptyList();
}

@Override
public String toString() {
return value;
}

@Override
public EscapedValue clone() {
return (EscapedValue) super.clone();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ public Expression evaluate(Expression input) {
case COLOR_EXPRESSION:
case NUMBER:
case FAULTY_EXPRESSION:
case ESCAPED_VALUE:
return input;

default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import com.github.sommeri.less4j.core.ast.ArgumentDeclaration;
import com.github.sommeri.less4j.core.ast.CssString;
import com.github.sommeri.less4j.core.ast.Declaration;
import com.github.sommeri.less4j.core.ast.EscapedSelector;
import com.github.sommeri.less4j.core.ast.EscapedValue;
import com.github.sommeri.less4j.core.ast.Expression;
import com.github.sommeri.less4j.core.ast.IndirectVariable;
import com.github.sommeri.less4j.core.ast.MixinReference;
Expand Down Expand Up @@ -74,6 +76,16 @@ private void doSolveReferences(ASTCssNode node, IteratedScope scope) {
manipulator.replace(node, replacement);
break;
}
case ESCAPED_SELECTOR: {
EscapedSelector replacement = replaceInEscapedSelector((EscapedSelector) node, expressionEvaluator);
manipulator.replace(node, replacement);
break;
}
case ESCAPED_VALUE: {
EscapedValue replacement = replaceInEscapedValue((EscapedValue) node, expressionEvaluator);
manipulator.replace(node, replacement);
break;
}
}

if (node.getType() != ASTCssNodeType.NAMESPACE_REFERENCE) {
Expand All @@ -92,6 +104,16 @@ private CssString replaceInString(CssString input, ExpressionEvaluator expressio
String value = stringInterpolator.replaceInterpolatedVariables(input.getValue(), expressionEvaluator, input.getUnderlyingStructure());
return new CssString(input.getUnderlyingStructure(), value, input.getQuoteType());
}

private EscapedSelector replaceInEscapedSelector(EscapedSelector input, ExpressionEvaluator expressionEvaluator) {
String value = stringInterpolator.replaceInterpolatedVariables(input.getValue(), expressionEvaluator, input.getUnderlyingStructure());
return new EscapedSelector(input.getUnderlyingStructure(), value);
}

private EscapedValue replaceInEscapedValue(EscapedValue input, ExpressionEvaluator expressionEvaluator) {
String value = stringInterpolator.replaceInterpolatedVariables(input.getValue(), expressionEvaluator, input.getUnderlyingStructure());
return new EscapedValue(input.getUnderlyingStructure(), value);
}

private RuleSetsBody resolveMixinReference(MixinReference reference, Scope scope) {
List<FullMixinDefinition> sameNameMixins = scope.getNearestMixins(reference);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import com.github.sommeri.less4j.core.ast.CssClass;
import com.github.sommeri.less4j.core.ast.Declaration;
import com.github.sommeri.less4j.core.ast.ElementSubsequent;
import com.github.sommeri.less4j.core.ast.EscapedSelector;
import com.github.sommeri.less4j.core.ast.EscapedValue;
import com.github.sommeri.less4j.core.ast.Expression;
import com.github.sommeri.less4j.core.ast.ExpressionOperator;
import com.github.sommeri.less4j.core.ast.FontFace;
Expand Down Expand Up @@ -56,6 +58,7 @@
import com.github.sommeri.less4j.core.problems.ProblemsHandler;

//FIXME: better error message for required (...)+ loop did not match anything at input errors
//FIXME: better error message missing EOF at blahblah
class ASTBuilderSwitch extends TokenTypeSwitch<ASTCssNode> {

private static final String GRAMMAR_MISMATCH = "ASTBuilderSwitch grammar mismatch";
Expand Down Expand Up @@ -732,8 +735,16 @@ public ASTCssNode handleSimpleSelector(HiddenTokenAwareTree token) {
return result;
}

public EscapedSelector handleEscapedSelector(HiddenTokenAwareTree token) {
token.pushHiddenToKids();
HiddenTokenAwareTree valueToken = token.getChild(0);
String quotedText = valueToken.getText();
return new EscapedSelector(valueToken, quotedText.substring(3, quotedText.length() - 2));
}

private boolean isMeaningfullWhitespace(HiddenTokenAwareTree kid) {
int type = kid.getChild(0).getType();
return type == LessLexer.MEANINGFULL_WHITESPACE || type == LessLexer.DUMMY_MEANINGFULL_WHITESPACE;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

import java.util.List;


import com.github.sommeri.less4j.core.parser.LessLexer;
import com.github.sommeri.less4j.core.problems.BugHappened;
import com.github.sommeri.less4j.core.ast.ColorExpression;
import com.github.sommeri.less4j.core.ast.CssString;
import com.github.sommeri.less4j.core.ast.EscapedValue;
import com.github.sommeri.less4j.core.ast.Expression;
import com.github.sommeri.less4j.core.ast.FunctionExpression;
import com.github.sommeri.less4j.core.ast.IdentifierExpression;
Expand Down Expand Up @@ -79,6 +79,9 @@ public Expression buildFromTerm(HiddenTokenAwareTree token, int offsetChildIndx)
case LessLexer.EXPRESSION_PARENTHESES:
return buildFromParentheses(offsetChild);

case LessLexer.ESCAPED_VALUE:
return buildFromEscapedValue(token, offsetChild);

default:
throw new BugHappened("type number: " + PrintUtils.toName(offsetChild.getType()) + "(" + offsetChild.getType() + ") for " + offsetChild.getText(), offsetChild);

Expand Down Expand Up @@ -112,8 +115,8 @@ private Expression negate(Expression value, HiddenTokenAwareTree sign) {

if (sign.getType() == LessLexer.MINUS) {
return new SignedExpression(sign, SignedExpression.Sign.MINUS, value);
}
}

return new SignedExpression(sign, SignedExpression.Sign.PLUS, value);
}

Expand Down Expand Up @@ -165,6 +168,14 @@ private Dimension toDimension(HiddenTokenAwareTree actual) {
}
}

public EscapedValue buildFromEscapedValue(HiddenTokenAwareTree token, HiddenTokenAwareTree offsetChild) {
token.pushHiddenToKids();
offsetChild.pushHiddenToKids();
HiddenTokenAwareTree valueToken = offsetChild.getChild(0);
String quotedText = valueToken.getText();
return new EscapedValue(valueToken, quotedText.substring(2, quotedText.length() - 1));
}

private Expression buildFromString(HiddenTokenAwareTree token, HiddenTokenAwareTree first) {
String text = first.getText();
return createCssString(token, text);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,14 @@ public T switchOn(HiddenTokenAwareTree token) {
if (type == LessLexer.SIMPLE_SELECTOR)
return handleSimpleSelector(token);

if (type == LessLexer.ESCAPED_SELECTOR)
return handleEscapedSelector(token);

throw new BugHappened("Unexpected token type: " + type + " for " + token.getText(), token);
}

public abstract T handleEscapedSelector(HiddenTokenAwareTree token);

public abstract T handleSimpleSelector(HiddenTokenAwareTree token);

public abstract T handleNestedAppender(HiddenTokenAwareTree token);
Expand Down
20 changes: 20 additions & 0 deletions src/main/java/com/github/sommeri/less4j/utils/CssPrinter.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
import com.github.sommeri.less4j.core.ast.CssString;
import com.github.sommeri.less4j.core.ast.Declaration;
import com.github.sommeri.less4j.core.ast.ElementSubsequent;
import com.github.sommeri.less4j.core.ast.EscapedSelector;
import com.github.sommeri.less4j.core.ast.EscapedValue;
import com.github.sommeri.less4j.core.ast.ExpressionOperator;
import com.github.sommeri.less4j.core.ast.FaultyExpression;
import com.github.sommeri.less4j.core.ast.FontFace;
Expand Down Expand Up @@ -169,6 +171,12 @@ public boolean switchOnType(ASTCssNode node) {
case FAULTY_EXPRESSION:
return appendFaultyExpression((FaultyExpression) node);

case ESCAPED_SELECTOR:
return appendEscapedSelector((EscapedSelector) node);

case ESCAPED_VALUE:
return appendEscapedValue((EscapedValue) node);

case PARENTHESES_EXPRESSION:
case SIGNED_EXPRESSION:
case VARIABLE:
Expand Down Expand Up @@ -488,6 +496,18 @@ public boolean appendCssString(CssString expression) {
return true;
}

public boolean appendEscapedSelector(EscapedSelector escaped) {
builder.append(escaped.getValue());

return true;
}

public boolean appendEscapedValue(EscapedValue escaped) {
builder.append(escaped.getValue());

return true;
}

public boolean appendIdentifierExpression(IdentifierExpression expression) {
builder.append(expression.getValue());

Expand Down
23 changes: 23 additions & 0 deletions src/test/java/com/github/sommeri/less4j/compiler/EscapingTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.github.sommeri.less4j.compiler;

import java.io.File;
import java.util.Collection;

import org.junit.runners.Parameterized.Parameters;

import com.github.sommeri.less4j.utils.TestFileUtils;

public class EscapingTest extends BasicFeaturesTest {

private static final String standardCases = "src/test/resources/compile-basic-features/escaping/";

public EscapingTest(File inputFile, File outputFile, String testName) {
super(inputFile, outputFile, testName);
}

@Parameters()
public static Collection<Object[]> allTestsParameters() {
return (new TestFileUtils()).loadTestFiles(standardCases);
}

}
4 changes: 0 additions & 4 deletions src/test/resources/command-line/errors.css

This file was deleted.

0 comments on commit f180919

Please sign in to comment.