Skip to content

Commit 2d8e98e

Browse files
committed
parse and stringify decl values
1 parent f27e548 commit 2d8e98e

File tree

6 files changed

+288
-0
lines changed

6 files changed

+288
-0
lines changed

lib/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
exports.parse = require("./parse");
22
exports.stringify = require("./stringify");
3+
exports.parseValues = require("./parseValues");
4+
exports.stringifyValues = require("./stringifyValues");

lib/parseValues.js

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
"use strict";
2+
3+
var Parser = require("fastparse");
4+
5+
function commentMatch(match, content) {
6+
this.value.nodes.push({
7+
type: "comment",
8+
content: content
9+
});
10+
}
11+
12+
function spacingMatch(match) {
13+
var item = this.value.nodes[this.value.nodes.length - 1];
14+
item.after = (item.after || "") + match;
15+
}
16+
17+
function initialSpacingMatch(match) {
18+
this.value.before = match;
19+
}
20+
21+
function endSpacingMatch(match) {
22+
this.value.after = match;
23+
}
24+
25+
function unescapeString(content) {
26+
return content.replace(/\\./g, function(escaped) {
27+
return escaped.substr(1);
28+
});
29+
}
30+
31+
function stringMatch(match, content) {
32+
var value = unescapeString(content);
33+
this.value.nodes.push({
34+
type: "string",
35+
value: value,
36+
stringType: match[0]
37+
});
38+
}
39+
40+
function commaMatch(match, spacing) {
41+
var newValue = {
42+
type: "value",
43+
nodes: []
44+
};
45+
if(spacing) {
46+
newValue.before = spacing;
47+
}
48+
this.root.nodes.push(newValue);
49+
this.value = newValue;
50+
}
51+
52+
function itemMatch(match) {
53+
this.value.nodes.push({
54+
type: "item",
55+
name: match
56+
});
57+
}
58+
59+
function urlMatch(match, innerSpacingBefore, content, innerSpacingAfter) {
60+
var item = {
61+
type: "url"
62+
};
63+
if(innerSpacingBefore) {
64+
item.innerSpacingBefore = innerSpacingBefore;
65+
}
66+
if(innerSpacingAfter) {
67+
item.innerSpacingAfter = innerSpacingAfter;
68+
}
69+
switch(content[0]) {
70+
case "\"":
71+
item.stringType = "\"";
72+
item.url = unescapeString(content.substr(1, content.length - 2));
73+
break;
74+
case "'":
75+
item.stringType = "'";
76+
item.url = unescapeString(content.substr(1, content.length - 2));
77+
break;
78+
default:
79+
item.url = unescapeString(content);
80+
break;
81+
}
82+
this.value.nodes.push(item);
83+
}
84+
85+
var parser = new Parser({
86+
decl: {
87+
"^\\s+": initialSpacingMatch,
88+
"/\\*([\\s\\S]*?)\\*/": commentMatch,
89+
"\"((?:[^\\\\\"]|\\\\.)*)\"": stringMatch,
90+
"'((?:[^\\\\']|\\\\.)*)'": stringMatch,
91+
"url\\((\\s*)(\"(?:[^\\\\\"]|\\\\.)*\")(\\s*)\\)": urlMatch,
92+
"url\\((\\s*)('(?:[^\\\\']|\\\\.)*')(\\s*)\\)": urlMatch,
93+
"url\\((\\s*)((?:[^\\\\)'\"]|\\\\.)*)(\\s*)\\)": urlMatch,
94+
",(\\s*)": commaMatch,
95+
"\\s+$": endSpacingMatch,
96+
"\\s+": spacingMatch,
97+
"[^\\s,]+": itemMatch
98+
}
99+
});
100+
101+
function parseValues(str) {
102+
var valueNode = {
103+
type: "value",
104+
nodes: []
105+
};
106+
var rootNode = {
107+
type: "values",
108+
nodes: [
109+
valueNode
110+
]
111+
};
112+
parser.parse("decl", str, {
113+
root: rootNode,
114+
value: valueNode
115+
});
116+
return rootNode;
117+
}
118+
119+
module.exports = parseValues;

lib/stringifyValues.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"use strict";
2+
3+
var stringify;
4+
5+
function stringifyWithoutBeforeAfter(tree) {
6+
switch(tree.type) {
7+
case "values":
8+
return tree.nodes.map(stringify).join(",");
9+
case "value":
10+
return tree.nodes.map(stringify).join("");
11+
case "item":
12+
return tree.name;
13+
case "comment":
14+
return "/*" + tree.content + "*/";
15+
case "string":
16+
switch(tree.stringType) {
17+
case "'":
18+
return "'" + tree.value.replace(/'/g, "\\'") + "'";
19+
case "\"":
20+
return "\"" + tree.value.replace(/"/g, "\\\"") + "\"";
21+
}
22+
/* istanbul ignore next */
23+
throw new Error("Invalid stringType");
24+
case "url":
25+
var start = "url(" + (tree.innerSpacingBefore || "");
26+
var end = (tree.innerSpacingAfter || "") + ")";
27+
switch(tree.stringType) {
28+
case "'":
29+
return start + "'" + tree.url.replace(/'/g, "\\'") + "'" + end;
30+
case "\"":
31+
return start + "\"" + tree.url.replace(/"/g, "\\\"") + "\"" + end;
32+
default:
33+
return start + tree.url.replace(/("|'|\))/g, "\\$1") + end;
34+
}
35+
}
36+
}
37+
38+
39+
stringify = function stringify(tree) {
40+
var str = stringifyWithoutBeforeAfter(tree);
41+
if(tree.before) {
42+
str = tree.before + str;
43+
}
44+
if(tree.after) {
45+
str = str + tree.after;
46+
}
47+
return str;
48+
};
49+
50+
module.exports = stringify;

test/parseValues.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
"use strict";
2+
3+
/*globals describe it */
4+
5+
var assert = require("assert");
6+
var Tokenizer = require("../");
7+
8+
describe("parseValues", function() {
9+
var testCases = require("./test-cases-values");
10+
Object.keys(testCases).forEach(function(testCase) {
11+
it("should parse values " + testCase, function() {
12+
var input = testCases[testCase][0];
13+
var expected = testCases[testCase][1];
14+
assert.deepEqual(Tokenizer.parseValues(input), expected);
15+
});
16+
});
17+
});

test/stringifyValues.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
"use strict";
2+
3+
/*globals describe it */
4+
5+
var assert = require("assert");
6+
var Tokenizer = require("../");
7+
8+
describe("stringifyValues", function() {
9+
var testCases = require("./test-cases-values");
10+
Object.keys(testCases).forEach(function(testCase) {
11+
it("should stringify values " + testCase, function() {
12+
var input = testCases[testCase][1];
13+
var expected = testCases[testCase][0];
14+
assert.deepEqual(Tokenizer.stringifyValues(input), expected);
15+
});
16+
});
17+
});

test/test-cases-values.js

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
"use strict";
2+
3+
function singleValue(nodes) {
4+
return {
5+
type: "values",
6+
nodes: [
7+
{
8+
type: "value",
9+
nodes: nodes
10+
}
11+
]
12+
};
13+
}
14+
15+
module.exports = {
16+
"item": [
17+
"item",
18+
singleValue([
19+
{ type: "item", name: "item" }
20+
])
21+
],
22+
"items": [
23+
"item other-item",
24+
singleValue([
25+
{ type: "item", name: "item", after: " " },
26+
{ type: "item", name: "other-item" }
27+
])
28+
],
29+
"multiple values": [
30+
"item other-item, second-value 3rd-value ,item",
31+
{
32+
type: "values",
33+
nodes: [
34+
{ type: "value", nodes: [
35+
{ type: "item", name: "item", after: " " },
36+
{ type: "item", name: "other-item" }
37+
] },
38+
{ type: "value", nodes: [
39+
{ type: "item", name: "second-value", after: " " },
40+
{ type: "item", name: "3rd-value", after: " " }
41+
], before: " " },
42+
{ type: "value", nodes: [
43+
{ type: "item", name: "item" }
44+
] }
45+
]
46+
}
47+
],
48+
"strings": [
49+
"'ab\\'\"c d' \"e\\\" f\"",
50+
singleValue([
51+
{ type: "string", value: "ab'\"c d", stringType: "'", after: " " },
52+
{ type: "string", value: "e\" f", stringType: "\"" }
53+
])
54+
],
55+
"comment": [
56+
"item /* hello world */ item",
57+
singleValue([
58+
{ type: "item", name: "item", after: " " },
59+
{ type: "comment", content: " hello world ", after: " " },
60+
{ type: "item", name: "item" }
61+
])
62+
],
63+
"urls": [
64+
"url('ab\\'\"c d') url( \"e\\\" f\" ) url( ghi\\)j\\\"k)",
65+
singleValue([
66+
{ type: "url", url: "ab'\"c d", stringType: "'", after: " " },
67+
{ type: "url", url: "e\" f", stringType: "\"", innerSpacingBefore: " ", innerSpacingAfter: " ", after: " " },
68+
{ type: "url", url: "ghi)j\"k", innerSpacingBefore: " " }
69+
])
70+
],
71+
"spacing": [
72+
" hello\n\t world\t",
73+
{
74+
type: "values",
75+
nodes: [
76+
{ type: "value", nodes: [
77+
{ type: "item", name: "hello", after: "\n\t " },
78+
{ type: "item", name: "world" }
79+
], before: " ", after: "\t" }
80+
]
81+
}
82+
]
83+
};

0 commit comments

Comments
 (0)