Merge pull request #3 from alexsaveliev/master
Dealing with CSS comments
diff --git a/css/declaration.go b/css/declaration.go
index 2560c71..eeb2118 100644
--- a/css/declaration.go
+++ b/css/declaration.go
@@ -18,25 +18,43 @@
// Returns string representation of the Declaration
func (decl *Declaration) String() string {
- return decl.StringWithImportant(true)
+ return decl.Str(false)
+}
+
+// Returns string representation of the Declaration
+func (decl *Declaration) Str(diff bool) string {
+ return decl.stringWithImportant(true, diff)
}
// StringWithImportant returns string representation with optional !important part
func (decl *Declaration) StringWithImportant(option bool) string {
+ return decl.stringWithImportant(option, false)
+}
+
+// StringWithImportant returns string representation with optional !important part
+func (decl *Declaration) stringWithImportant(option bool, diff bool) string {
result := fmt.Sprintf("%s: %s", decl.Property, decl.Value)
if option && decl.Important {
result += " !important"
}
- result += ";"
+ if diff {
+ result += fmt.Sprintf(" (%d, %d)", decl.Line, decl.Column)
+ } else {
+ result += ";"
+ }
return result
}
// Equal returns true if both Declarations are equals
func (decl *Declaration) Equal(other *Declaration) bool {
- return (decl.Property == other.Property) && (decl.Value == other.Value) && (decl.Important == other.Important)
+ return decl.Property == other.Property &&
+ decl.Value == other.Value &&
+ decl.Important == other.Important &&
+ decl.Line == other.Line &&
+ decl.Column == other.Column
}
//
diff --git a/css/rule.go b/css/rule.go
index 44e3111..067c452 100644
--- a/css/rule.go
+++ b/css/rule.go
@@ -31,7 +31,15 @@
}
func (selector Selector) String() string {
- return fmt.Sprintf("Selector: %s (%d, %d)", selector.Value, selector.Line, selector.Column)
+ return selector.str(false)
+}
+
+func (selector Selector) str(diff bool) string {
+ if diff {
+ return fmt.Sprintf("Selector: %s (%d, %d)", selector.Value, selector.Line, selector.Column)
+ } else {
+ return selector.Value
+ }
}
// Rule represents a parsed CSS rule
@@ -147,7 +155,7 @@
} else {
for i, sel := range rule.Selectors {
if sel != other.Selectors[i] {
- result = append(result, fmt.Sprintf("Selector: \"%s\" | \"%s\"", sel, other.Selectors[i]))
+ result = append(result, fmt.Sprintf("Selector: \"%s\" | \"%s\"", sel.str(true), other.Selectors[i].str(true)))
}
}
}
@@ -157,7 +165,7 @@
} else {
for i, decl := range rule.Declarations {
if !decl.Equal(other.Declarations[i]) {
- result = append(result, fmt.Sprintf("Declaration: \"%s\" | \"%s\"", decl.String(), other.Declarations[i].String()))
+ result = append(result, fmt.Sprintf("Declaration: \"%s\" | \"%s\"", decl.Str(true), other.Declarations[i].Str(true)))
}
}
}
@@ -168,7 +176,7 @@
for i, rule := range rule.Rules {
if !rule.Equal(other.Rules[i]) {
- result = append(result, fmt.Sprintf("Rule: \"%s\" | \"%s\"", rule.String(), other.Rules[i].String()))
+ result = append(result, fmt.Sprintf("Rule: \"%s\" | \"%s\"", rule.str(true), other.Rules[i].str(true)))
}
}
}
@@ -178,6 +186,11 @@
// Returns the string representation of a rule
func (rule *Rule) String() string {
+ return rule.str(false)
+}
+
+// Returns the string representation of a rule
+func (rule *Rule) str(diff bool) string {
result := ""
if rule.Kind == QualifiedRule {
@@ -206,11 +219,11 @@
if rule.EmbedsRules() {
for _, subRule := range rule.Rules {
- result += fmt.Sprintf("%s%s\n", rule.indent(), subRule.String())
+ result += fmt.Sprintf("%s%s\n", rule.indent(), subRule.str(diff))
}
} else {
for _, decl := range rule.Declarations {
- result += fmt.Sprintf("%s%s\n", rule.indent(), decl.String())
+ result += fmt.Sprintf("%s%s\n", rule.indent(), decl.Str(diff))
}
}
@@ -222,24 +235,12 @@
// Returns identation spaces for declarations and rules
func (rule *Rule) indent() string {
- result := ""
-
- for i := 0; i < ((rule.EmbedLevel + 1) * indentSpace); i++ {
- result += " "
- }
-
- return result
+ return strings.Repeat(" ", (rule.EmbedLevel+1)*indentSpace)
}
// Returns identation spaces for end of block character
func (rule *Rule) indentEndBlock() string {
- result := ""
-
- for i := 0; i < (rule.EmbedLevel * indentSpace); i++ {
- result += " "
- }
-
- return result
+ return strings.Repeat(" ", rule.EmbedLevel*indentSpace)
}
func (rule *Rule) Sel() []string {
diff --git a/parser/parser.go b/parser/parser.go
index 95a9bf3..a0132ea 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -127,7 +127,6 @@
if parser.tokenAtKeyword() {
return parser.parseAtRule()
}
-
return parser.parseQualifiedRule()
}
@@ -195,11 +194,16 @@
// finished
break
- } else {
+ } else if !parser.tokenIgnorable() {
token := parser.shiftToken()
curValue += token.Value
curColumn = token.Column
curLine = token.Line
+ } else {
+ token := parser.shiftToken()
+ if token.Type != scanner.TokenComment {
+ curValue += token.Value
+ }
}
}
@@ -281,6 +285,10 @@
// finished
break
+ } else if parser.tokenChar(";") {
+ parser.shiftToken()
+ // finished
+ break
} else {
// parse prelude
prelude, err, tokens := parser.parsePrelude()
@@ -310,7 +318,7 @@
selectorStart = true
selectorValue = ""
}
- case tok.Type != scanner.TokenS:
+ case tok.Type != scanner.TokenS && tok.Type != scanner.TokenComment:
{
selectorValue += tok.Value
if selectorStart {
diff --git a/parser/parser_test.go b/parser/parser_test.go
index baed705..89d0ecc 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -11,11 +11,11 @@
func MustParse(t *testing.T, txt string, nbRules int) *css.Stylesheet {
stylesheet, err := Parse(txt)
if err != nil {
- t.Fatal("Failed to parse css", err, txt)
+ t.Fatal("Failed to parse CSS", err, txt)
}
if len(stylesheet.Rules) != nbRules {
- t.Fatal("Failed to parse Qualified Rules", txt)
+ t.Fatal(fmt.Sprintf("Failed to parse CSS \"%s\", expected %d rules but got %d", txt, nbRules, len(stylesheet.Rules)))
}
return stylesheet
@@ -46,7 +46,7 @@
Kind: css.QualifiedRule,
Prelude: "p > a",
Selectors: []*css.Selector{
- &css.Selector{
+ {
Value: "p > a",
Line: 2,
Column: 1,
@@ -56,10 +56,14 @@
{
Property: "color",
Value: "blue",
+ Line: 3,
+ Column: 5,
},
{
Property: "text-decoration",
Value: "underline",
+ Line: 4,
+ Column: 5,
},
},
}
@@ -89,7 +93,7 @@
Kind: css.QualifiedRule,
Prelude: "p > a",
Selectors: []*css.Selector{
- &css.Selector{
+ {
Value: "p > a",
Line: 2,
Column: 1,
@@ -100,16 +104,22 @@
Property: "color",
Value: "blue",
Important: false,
+ Line: 3,
+ Column: 5,
},
{
Property: "text-decoration",
Value: "underline",
Important: true,
+ Line: 4,
+ Column: 5,
},
{
Property: "font-weight",
Value: "normal",
Important: true,
+ Line: 5,
+ Column: 5,
},
},
}
@@ -143,17 +153,17 @@
Kind: css.QualifiedRule,
Prelude: "table, tr, td",
Selectors: []*css.Selector{
- &css.Selector{
+ {
Value: "table",
Line: 1,
Column: 1,
},
- &css.Selector{
+ {
Value: "tr",
Line: 1,
Column: 8,
},
- &css.Selector{
+ {
Value: "td",
Line: 1,
Column: 12,
@@ -163,6 +173,8 @@
{
Property: "padding",
Value: "0",
+ Line: 2,
+ Column: 3,
},
},
}
@@ -173,33 +185,34 @@
h1, h2,
h3`,
Selectors: []*css.Selector{
- &css.Selector{
+ {
Value: "body",
Line: 5,
Column: 1,
},
- &css.Selector{
+ {
Value: "h1",
Line: 6,
Column: 3,
},
- &css.Selector{
+ {
Value: "h2",
Line: 6,
Column: 9,
},
- &css.Selector{
+ {
Value: "h3",
Line: 7,
Column: 5,
},
},
-
Declarations: []*css.Declaration{
{
Property: "color",
Value: "#fff",
+ Line: 8,
+ Column: 3,
},
},
}
@@ -253,14 +266,20 @@
{
Property: "system",
Value: "symbolic",
+ Line: 2,
+ Column: 3,
},
{
Property: "symbols",
Value: "'*' ⁑ † ‡",
+ Line: 3,
+ Column: 3,
},
{
Property: "suffix",
Value: "''",
+ Line: 4,
+ Column: 3,
},
},
}
@@ -302,7 +321,7 @@
Kind: css.QualifiedRule,
Prelude: "body",
Selectors: []*css.Selector{
- &css.Selector{
+ {
Value: "body",
Line: 14,
Column: 3,
@@ -312,10 +331,14 @@
{
Property: "color",
Value: "purple",
+ Line: 14,
+ Column: 10,
},
{
Property: "background",
Value: "yellow",
+ Line: 14,
+ Column: 25,
},
},
},
@@ -356,16 +379,22 @@
{
Property: "font-family",
Value: "MyHelvetica",
+ Line: 2,
+ Column: 3,
},
{
Property: "src",
Value: `local("Helvetica Neue Bold"),
local("HelveticaNeue-Bold"),
url(MgOpenModernaBold.ttf)`,
+ Line: 3,
+ Column: 3,
},
{
Property: "font-weight",
Value: "bold",
+ Line: 6,
+ Column: 3,
},
},
}
@@ -396,6 +425,8 @@
{
Property: "nice-style",
Value: "4",
+ Line: 3,
+ Column: 5,
},
},
},
@@ -454,7 +485,7 @@
Kind: css.QualifiedRule,
Prelude: "0%",
Selectors: []*css.Selector{
- &css.Selector{
+ {
Value: "0%",
Line: 2,
Column: 3,
@@ -464,10 +495,14 @@
{
Property: "top",
Value: "0",
+ Line: 2,
+ Column: 8,
},
{
Property: "left",
Value: "0",
+ Line: 2,
+ Column: 16,
},
},
},
@@ -475,7 +510,7 @@
Kind: css.QualifiedRule,
Prelude: "100%",
Selectors: []*css.Selector{
- &css.Selector{
+ {
Value: "100%",
Line: 3,
Column: 3,
@@ -485,10 +520,14 @@
{
Property: "top",
Value: "100px",
+ Line: 3,
+ Column: 10,
},
{
Property: "left",
Value: "100%",
+ Line: 3,
+ Column: 22,
},
},
},
@@ -527,7 +566,7 @@
Kind: css.QualifiedRule,
Prelude: "body",
Selectors: []*css.Selector{
- &css.Selector{
+ {
Value: "body",
Line: 2,
Column: 3,
@@ -537,6 +576,8 @@
{
Property: "line-height",
Value: "1.2",
+ Line: 2,
+ Column: 10,
},
},
},
@@ -586,10 +627,14 @@
{
Property: "margin-left",
Value: "4cm",
+ Line: 2,
+ Column: 3,
},
{
Property: "margin-right",
Value: "3cm",
+ Line: 3,
+ Column: 3,
},
},
}
@@ -623,7 +668,7 @@
Kind: css.QualifiedRule,
Prelude: "0%",
Selectors: []*css.Selector{
- &css.Selector{
+ {
Value: "0%",
Line: 4,
Column: 7,
@@ -633,10 +678,14 @@
{
Property: "top",
Value: "0",
+ Line: 4,
+ Column: 12,
},
{
Property: "left",
Value: "0",
+ Line: 4,
+ Column: 20,
},
},
},
@@ -644,7 +693,7 @@
Kind: css.QualifiedRule,
Prelude: "100%",
Selectors: []*css.Selector{
- &css.Selector{
+ {
Value: "100%",
Line: 5,
Column: 7,
@@ -654,10 +703,14 @@
{
Property: "top",
Value: "100px",
+ Line: 5,
+ Column: 14,
},
{
Property: "left",
Value: "100%",
+ Line: 5,
+ Column: 26,
},
},
},
@@ -699,10 +752,14 @@
{
Property: "color",
Value: "blue",
+ Line: 1,
+ Column: 1,
},
{
Property: "text-decoration",
Value: "underline",
+ Line: 1,
+ Column: 14,
},
}
@@ -712,7 +769,7 @@
for i, decl := range declarations {
if !decl.Equal(expectedOutput[i]) {
- t.Fatal("Failed to parse Declarations: ", decl.String(), expectedOutput[i].String())
+ t.Fatal("Failed to parse Declarations: ", decl.Str(true), expectedOutput[i].Str(true))
}
}
}
@@ -726,7 +783,7 @@
.btn.active.focus {
}`
expectedRule := &css.Rule{
- Kind: css.QualifiedRule,
+ Kind: css.QualifiedRule,
Prelude: `.btn:focus,
.btn:active:focus,
.btn.active:focus,
@@ -734,32 +791,32 @@
.btn:active.focus,
.btn.active.focus`,
Selectors: []*css.Selector{
- &css.Selector{
+ {
Value: ".btn:focus",
Line: 1,
Column: 1,
},
- &css.Selector{
+ {
Value: ".btn:active:focus",
Line: 2,
Column: 1,
},
- &css.Selector{
+ {
Value: ".btn.active:focus",
Line: 3,
Column: 1,
},
- &css.Selector{
+ {
Value: ".btn.focus",
Line: 4,
Column: 1,
},
- &css.Selector{
+ {
Value: ".btn:active.focus",
Line: 5,
Column: 1,
},
- &css.Selector{
+ {
Value: ".btn.active.focus",
Line: 6,
Column: 1,
@@ -773,3 +830,53 @@
MustEqualRule(t, rule, expectedRule)
}
+
+func TestComments(t *testing.T) {
+ input := "td /* © */ { color /* © */: red; }"
+ expectedRule := &css.Rule{
+ Kind: css.QualifiedRule,
+ Prelude: "td /* © */",
+ Selectors: []*css.Selector{
+ {
+ Value: "td",
+ Line: 1,
+ Column: 1,
+ },
+ },
+ Declarations: []*css.Declaration{
+ {
+ Property: "color",
+ Value: "red",
+ Line: 1,
+ Column: 14,
+ },
+ },
+ }
+
+ stylesheet := MustParse(t, input, 1)
+ rule := stylesheet.Rules[0]
+
+ MustEqualRule(t, rule, expectedRule)
+}
+
+func TestInfiniteLoop(t *testing.T) {
+ input := "{;}"
+
+ expectedRule := &css.Rule{
+ Kind: css.QualifiedRule,
+ Prelude: "",
+ Selectors: []*css.Selector{
+ {
+ Value: "",
+ Line: 0,
+ Column: 0,
+ },
+ },
+ Declarations: []*css.Declaration{},
+ }
+
+ stylesheet := MustParse(t, input, 1)
+ rule := stylesheet.Rules[0]
+
+ MustEqualRule(t, rule, expectedRule)
+}