ruby-clean-css 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitmodules +3 -0
- data/EXAMPLE.md +25 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +30 -0
- data/LICENSE +19 -0
- data/README.md +106 -0
- data/lib/javascript/clean-css/.gitignore +4 -0
- data/lib/javascript/clean-css/.jshintignore +3 -0
- data/lib/javascript/clean-css/.jshintrc +14 -0
- data/lib/javascript/clean-css/.travis.yml +11 -0
- data/lib/javascript/clean-css/History.md +556 -0
- data/lib/javascript/clean-css/LICENSE +19 -0
- data/lib/javascript/clean-css/README.md +218 -0
- data/lib/javascript/clean-css/bin/cleancss +157 -0
- data/lib/javascript/clean-css/index.js +1 -0
- data/lib/javascript/clean-css/lib/clean.js +426 -0
- data/lib/javascript/clean-css/lib/colors/hsl-to-hex.js +64 -0
- data/lib/javascript/clean-css/lib/colors/long-to-short-hex.js +12 -0
- data/lib/javascript/clean-css/lib/colors/rgb-to-hex.js +14 -0
- data/lib/javascript/clean-css/lib/colors/shortener.js +174 -0
- data/lib/javascript/clean-css/lib/images/url-rebase.js +32 -0
- data/lib/javascript/clean-css/lib/images/url-rewriter.js +60 -0
- data/lib/javascript/clean-css/lib/imports/inliner.js +319 -0
- data/lib/javascript/clean-css/lib/properties/optimizer.js +276 -0
- data/lib/javascript/clean-css/lib/properties/override-compactor.js +116 -0
- data/lib/javascript/clean-css/lib/properties/processable.js +859 -0
- data/lib/javascript/clean-css/lib/properties/scanner.js +20 -0
- data/lib/javascript/clean-css/lib/properties/shorthand-compactor.js +244 -0
- data/lib/javascript/clean-css/lib/properties/token.js +184 -0
- data/lib/javascript/clean-css/lib/properties/validator.js +140 -0
- data/lib/javascript/clean-css/lib/selectors/empty-removal.js +30 -0
- data/lib/javascript/clean-css/lib/selectors/optimizer.js +341 -0
- data/lib/javascript/clean-css/lib/selectors/tokenizer.js +168 -0
- data/lib/javascript/clean-css/lib/text/comments.js +83 -0
- data/lib/javascript/clean-css/lib/text/escape-store.js +32 -0
- data/lib/javascript/clean-css/lib/text/expressions.js +73 -0
- data/lib/javascript/clean-css/lib/text/free.js +26 -0
- data/lib/javascript/clean-css/lib/text/name-quotes.js +37 -0
- data/lib/javascript/clean-css/lib/text/quote-scanner.js +91 -0
- data/lib/javascript/clean-css/lib/text/splitter.js +35 -0
- data/lib/javascript/clean-css/lib/text/urls.js +41 -0
- data/lib/javascript/clean-css/package.json +50 -0
- data/lib/javascript/clean-css/test/batch-test.js +56 -0
- data/lib/javascript/clean-css/test/bench.js +11 -0
- data/lib/javascript/clean-css/test/binary-test.js +323 -0
- data/lib/javascript/clean-css/test/data-bench/_partial.css +1 -0
- data/lib/javascript/clean-css/test/data-bench/complex.css +4 -0
- data/lib/javascript/clean-css/test/data/129-assets/assets/ui.css +2 -0
- data/lib/javascript/clean-css/test/data/129-assets/components/bootstrap/css/bootstrap.css +3 -0
- data/lib/javascript/clean-css/test/data/129-assets/components/bootstrap/images/glyphs.gif +0 -0
- data/lib/javascript/clean-css/test/data/129-assets/components/jquery-ui/css/style.css +6 -0
- data/lib/javascript/clean-css/test/data/129-assets/components/jquery-ui/images/next.gif +0 -0
- data/lib/javascript/clean-css/test/data/129-assets/components/jquery-ui/images/prev.gif +0 -0
- data/lib/javascript/clean-css/test/data/960-min.css +125 -0
- data/lib/javascript/clean-css/test/data/960.css +602 -0
- data/lib/javascript/clean-css/test/data/big-min.css +2984 -0
- data/lib/javascript/clean-css/test/data/big.css +13794 -0
- data/lib/javascript/clean-css/test/data/blueprint-min.css +245 -0
- data/lib/javascript/clean-css/test/data/blueprint.css +556 -0
- data/lib/javascript/clean-css/test/data/charset-mixed-with-fonts-min.css +3 -0
- data/lib/javascript/clean-css/test/data/charset-mixed-with-fonts.css +14 -0
- data/lib/javascript/clean-css/test/data/font-awesome-ie7-min.css +392 -0
- data/lib/javascript/clean-css/test/data/font-awesome-ie7.css +1203 -0
- data/lib/javascript/clean-css/test/data/font-awesome-min.css +319 -0
- data/lib/javascript/clean-css/test/data/font-awesome.css +540 -0
- data/lib/javascript/clean-css/test/data/imports-min.css +5 -0
- data/lib/javascript/clean-css/test/data/imports.css +4 -0
- data/lib/javascript/clean-css/test/data/issue-117-snippet-min.css +3 -0
- data/lib/javascript/clean-css/test/data/issue-117-snippet.css +19 -0
- data/lib/javascript/clean-css/test/data/issue-159-snippet-min.css +7 -0
- data/lib/javascript/clean-css/test/data/issue-159-snippet.css +7 -0
- data/lib/javascript/clean-css/test/data/issue-192-min.css +1 -0
- data/lib/javascript/clean-css/test/data/issue-192.css +8 -0
- data/lib/javascript/clean-css/test/data/issue-198-min.css +3 -0
- data/lib/javascript/clean-css/test/data/issue-198.css +4 -0
- data/lib/javascript/clean-css/test/data/issue-232-min.css +2 -0
- data/lib/javascript/clean-css/test/data/issue-232.css +17 -0
- data/lib/javascript/clean-css/test/data/issue-241-min.css +2 -0
- data/lib/javascript/clean-css/test/data/issue-241.css +2 -0
- data/lib/javascript/clean-css/test/data/issue-304-min.css +1 -0
- data/lib/javascript/clean-css/test/data/issue-304.css +4 -0
- data/lib/javascript/clean-css/test/data/issue-305-min.css +1 -0
- data/lib/javascript/clean-css/test/data/issue-305.css +3 -0
- data/lib/javascript/clean-css/test/data/issue-308-min.css +1 -0
- data/lib/javascript/clean-css/test/data/issue-308.css +5 -0
- data/lib/javascript/clean-css/test/data/issue-312-min.css +1 -0
- data/lib/javascript/clean-css/test/data/issue-312.css +7 -0
- data/lib/javascript/clean-css/test/data/issue-337-min.css +1 -0
- data/lib/javascript/clean-css/test/data/issue-337.css +4 -0
- data/lib/javascript/clean-css/test/data/line-breaks-in-attributes-min.css +2 -0
- data/lib/javascript/clean-css/test/data/line-breaks-in-attributes.css +8 -0
- data/lib/javascript/clean-css/test/data/partials-absolute/base.css +3 -0
- data/lib/javascript/clean-css/test/data/partials-absolute/base2.css +1 -0
- data/lib/javascript/clean-css/test/data/partials-absolute/extra/sub.css +3 -0
- data/lib/javascript/clean-css/test/data/partials-relative/base.css +3 -0
- data/lib/javascript/clean-css/test/data/partials-relative/extra/included.css +1 -0
- data/lib/javascript/clean-css/test/data/partials/comment.css +2 -0
- data/lib/javascript/clean-css/test/data/partials/extra/down.gif +0 -0
- data/lib/javascript/clean-css/test/data/partials/extra/four.css +3 -0
- data/lib/javascript/clean-css/test/data/partials/extra/three.css +1 -0
- data/lib/javascript/clean-css/test/data/partials/five.css +1 -0
- data/lib/javascript/clean-css/test/data/partials/four.css +1 -0
- data/lib/javascript/clean-css/test/data/partials/one.css +1 -0
- data/lib/javascript/clean-css/test/data/partials/three.css +1 -0
- data/lib/javascript/clean-css/test/data/partials/two.css +5 -0
- data/lib/javascript/clean-css/test/data/reset-min.css +12 -0
- data/lib/javascript/clean-css/test/data/reset.css +64 -0
- data/lib/javascript/clean-css/test/data/sample1-min.css +4 -0
- data/lib/javascript/clean-css/test/data/sample1.css +12 -0
- data/lib/javascript/clean-css/test/data/unsupported/selectors-ie7.css +20 -0
- data/lib/javascript/clean-css/test/data/unsupported/selectors-ie8.css +17 -0
- data/lib/javascript/clean-css/test/module-test.js +185 -0
- data/lib/javascript/clean-css/test/protocol-imports-test.js +409 -0
- data/lib/javascript/clean-css/test/text/splitter-test.js +20 -0
- data/lib/javascript/clean-css/test/unit-test.js +2071 -0
- data/lib/ruby-clean-css.rb +6 -0
- data/lib/ruby-clean-css/compressor.rb +142 -0
- data/lib/ruby-clean-css/exports.rb +258 -0
- data/lib/ruby-clean-css/railtie.rb +27 -0
- data/lib/ruby-clean-css/sprockets.rb +19 -0
- data/lib/ruby-clean-css/version.rb +5 -0
- data/ruby-clean-css.gemspec +30 -0
- data/test/test_compressor.rb +84 -0
- metadata +203 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
(function() {
|
|
2
|
+
var OPEN_BRACE = '{';
|
|
3
|
+
var SEMICOLON = ';';
|
|
4
|
+
var COLON = ':';
|
|
5
|
+
|
|
6
|
+
var PropertyScanner = function PropertyScanner(data) {
|
|
7
|
+
this.data = data;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
PropertyScanner.prototype.nextAt = function(cursor) {
|
|
11
|
+
var lastColon = this.data.lastIndexOf(COLON, cursor);
|
|
12
|
+
var lastOpenBrace = this.data.lastIndexOf(OPEN_BRACE, cursor);
|
|
13
|
+
var lastSemicolon = this.data.lastIndexOf(SEMICOLON, cursor);
|
|
14
|
+
var startAt = Math.max(lastOpenBrace, lastSemicolon);
|
|
15
|
+
|
|
16
|
+
return this.data.substring(startAt + 1, lastColon).trim();
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
module.exports = PropertyScanner;
|
|
20
|
+
})();
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
|
|
2
|
+
// Compacts the tokens by transforming properties into their shorthand notations when possible
|
|
3
|
+
|
|
4
|
+
module.exports = (function () {
|
|
5
|
+
var isHackValue = function (t) { return t.value === '__hack'; };
|
|
6
|
+
|
|
7
|
+
var compactShorthands = function(tokens, isImportant, processable, Token) {
|
|
8
|
+
// Contains the components found so far, grouped by shorthand name
|
|
9
|
+
var componentsSoFar = { };
|
|
10
|
+
|
|
11
|
+
// Initializes a prop in componentsSoFar
|
|
12
|
+
var initSoFar = function (shprop, last, clearAll) {
|
|
13
|
+
var found = {};
|
|
14
|
+
var shorthandPosition;
|
|
15
|
+
|
|
16
|
+
if (!clearAll && componentsSoFar[shprop]) {
|
|
17
|
+
for (var i = 0; i < processable[shprop].components.length; i++) {
|
|
18
|
+
var prop = processable[shprop].components[i];
|
|
19
|
+
found[prop] = [];
|
|
20
|
+
|
|
21
|
+
if (!(componentsSoFar[shprop].found[prop]))
|
|
22
|
+
continue;
|
|
23
|
+
|
|
24
|
+
for (var ii = 0; ii < componentsSoFar[shprop].found[prop].length; ii++) {
|
|
25
|
+
var comp = componentsSoFar[shprop].found[prop][ii];
|
|
26
|
+
|
|
27
|
+
if (comp.isMarkedForDeletion)
|
|
28
|
+
continue;
|
|
29
|
+
|
|
30
|
+
found[prop].push(comp);
|
|
31
|
+
|
|
32
|
+
if (comp.position && (!shorthandPosition || comp.position < shorthandPosition))
|
|
33
|
+
shorthandPosition = comp.position;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
componentsSoFar[shprop] = {
|
|
38
|
+
lastShorthand: last,
|
|
39
|
+
found: found,
|
|
40
|
+
shorthandPosition: shorthandPosition
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Adds a component to componentsSoFar
|
|
45
|
+
var addComponentSoFar = function (token, index) {
|
|
46
|
+
var shprop = processable[token.prop].componentOf;
|
|
47
|
+
if (!componentsSoFar[shprop])
|
|
48
|
+
initSoFar(shprop);
|
|
49
|
+
if (!componentsSoFar[shprop].found[token.prop])
|
|
50
|
+
componentsSoFar[shprop].found[token.prop] = [];
|
|
51
|
+
|
|
52
|
+
// Add the newfound component to componentsSoFar
|
|
53
|
+
componentsSoFar[shprop].found[token.prop].push(token);
|
|
54
|
+
|
|
55
|
+
if (!componentsSoFar[shprop].shorthandPosition && index) {
|
|
56
|
+
// If the haven't decided on where the shorthand should go, put it in the place of this component
|
|
57
|
+
componentsSoFar[shprop].shorthandPosition = index;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// Tries to compact a prop in componentsSoFar
|
|
62
|
+
var compactSoFar = function (prop) {
|
|
63
|
+
var i;
|
|
64
|
+
var componentsCount = processable[prop].components.length;
|
|
65
|
+
|
|
66
|
+
// Check basics
|
|
67
|
+
if (!componentsSoFar[prop] || !componentsSoFar[prop].found)
|
|
68
|
+
return false;
|
|
69
|
+
|
|
70
|
+
// Find components for the shorthand
|
|
71
|
+
var components = [];
|
|
72
|
+
var realComponents = [];
|
|
73
|
+
for (i = 0 ; i < componentsCount; i++) {
|
|
74
|
+
// Get property name
|
|
75
|
+
var pp = processable[prop].components[i];
|
|
76
|
+
|
|
77
|
+
if (componentsSoFar[prop].found[pp] && componentsSoFar[prop].found[pp].length) {
|
|
78
|
+
// We really found it
|
|
79
|
+
var foundRealComp = componentsSoFar[prop].found[pp][0];
|
|
80
|
+
components.push(foundRealComp);
|
|
81
|
+
if (foundRealComp.isReal !== false) {
|
|
82
|
+
realComponents.push(foundRealComp);
|
|
83
|
+
}
|
|
84
|
+
} else if (componentsSoFar[prop].lastShorthand) {
|
|
85
|
+
// It's defined in the previous shorthand
|
|
86
|
+
var c = componentsSoFar[prop].lastShorthand.components[i].clone(isImportant);
|
|
87
|
+
components.push(c);
|
|
88
|
+
} else {
|
|
89
|
+
// Couldn't find this component at all
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (realComponents.length === 0) {
|
|
95
|
+
// Couldn't find enough components, sorry
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (realComponents.length === componentsCount) {
|
|
100
|
+
// When all the components are from real values, only allow shorthanding if their understandability allows it
|
|
101
|
+
// This is the case when every component can override their default values, or when all of them use the same function
|
|
102
|
+
|
|
103
|
+
var canOverrideDefault = true;
|
|
104
|
+
var functionNameMatches = true;
|
|
105
|
+
var functionName;
|
|
106
|
+
|
|
107
|
+
for (var ci = 0; ci < realComponents.length; ci++) {
|
|
108
|
+
var rc = realComponents[ci];
|
|
109
|
+
|
|
110
|
+
if (!processable[rc.prop].canOverride(processable[rc.prop].defaultValue, rc.value)) {
|
|
111
|
+
canOverrideDefault = false;
|
|
112
|
+
}
|
|
113
|
+
var iop = rc.value.indexOf('(');
|
|
114
|
+
if (iop >= 0) {
|
|
115
|
+
var otherFunctionName = rc.value.substring(0, iop);
|
|
116
|
+
if (functionName)
|
|
117
|
+
functionNameMatches = functionNameMatches && otherFunctionName === functionName;
|
|
118
|
+
else
|
|
119
|
+
functionName = otherFunctionName;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!canOverrideDefault || !functionNameMatches)
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Compact the components into a shorthand
|
|
128
|
+
var compacted = processable[prop].putTogether(prop, components, isImportant);
|
|
129
|
+
if (!(compacted instanceof Array)) {
|
|
130
|
+
compacted = [compacted];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
var compactedLength = Token.getDetokenizedLength(compacted);
|
|
134
|
+
var authenticLength = Token.getDetokenizedLength(realComponents);
|
|
135
|
+
|
|
136
|
+
if (realComponents.length === componentsCount || compactedLength < authenticLength || components.some(isHackValue)) {
|
|
137
|
+
compacted[0].isShorthand = true;
|
|
138
|
+
compacted[0].components = processable[prop].breakUp(compacted[0]);
|
|
139
|
+
|
|
140
|
+
// Mark the granular components for deletion
|
|
141
|
+
for (i = 0; i < realComponents.length; i++) {
|
|
142
|
+
realComponents[i].isMarkedForDeletion = true;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Mark the position of the new shorthand
|
|
146
|
+
tokens[componentsSoFar[prop].shorthandPosition].replaceWith = compacted;
|
|
147
|
+
|
|
148
|
+
// Reinitialize the thing for further compacting
|
|
149
|
+
initSoFar(prop, compacted[0]);
|
|
150
|
+
for (i = 1; i < compacted.length; i++) {
|
|
151
|
+
addComponentSoFar(compacted[i]);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Yes, we can keep the new shorthand!
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return false;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// Tries to compact all properties currently in componentsSoFar
|
|
162
|
+
var compactAllSoFar = function () {
|
|
163
|
+
for (var i in componentsSoFar) {
|
|
164
|
+
if (componentsSoFar.hasOwnProperty(i)) {
|
|
165
|
+
while (compactSoFar(i)) { }
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
var i, token;
|
|
171
|
+
|
|
172
|
+
// Go through each token and collect components for each shorthand as we go on
|
|
173
|
+
for (i = 0; i < tokens.length; i++) {
|
|
174
|
+
token = tokens[i];
|
|
175
|
+
if (token.isMarkedForDeletion) {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (!processable[token.prop]) {
|
|
179
|
+
// We don't know what it is, move on
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (processable[token.prop].isShorthand) {
|
|
183
|
+
// Found an instance of a full shorthand
|
|
184
|
+
// NOTE: we should NOT mix together tokens that come before and after the shorthands
|
|
185
|
+
|
|
186
|
+
if (token.isImportant === isImportant || (token.isImportant && !isImportant)) {
|
|
187
|
+
// Try to compact what we've found so far
|
|
188
|
+
while (compactSoFar(token.prop)) { }
|
|
189
|
+
// Reset
|
|
190
|
+
initSoFar(token.prop, token, true);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// TODO: when the old optimizer is removed, take care of this corner case:
|
|
194
|
+
// div{background-color:#111;background-image:url(aaa);background:linear-gradient(aaa);background-repeat:no-repeat;background-position:1px 2px;background-attachment:scroll}
|
|
195
|
+
// -> should not be shorthanded / minified at all because the result wouldn't be equivalent to the original in any browser
|
|
196
|
+
} else if (processable[token.prop].componentOf) {
|
|
197
|
+
// Found a component of a shorthand
|
|
198
|
+
if (token.isImportant === isImportant) {
|
|
199
|
+
// Same importantness
|
|
200
|
+
token.position = i;
|
|
201
|
+
addComponentSoFar(token, i);
|
|
202
|
+
} else if (!isImportant && token.isImportant) {
|
|
203
|
+
// Use importants for optimalization opportunities
|
|
204
|
+
// https://github.com/GoalSmashers/clean-css/issues/184
|
|
205
|
+
var importantTrickComp = new Token(token.prop, token.value, isImportant);
|
|
206
|
+
importantTrickComp.isIrrelevant = true;
|
|
207
|
+
importantTrickComp.isReal = false;
|
|
208
|
+
addComponentSoFar(importantTrickComp);
|
|
209
|
+
}
|
|
210
|
+
} else {
|
|
211
|
+
// This is not a shorthand and not a component, don't care about it
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Perform all possible compactions
|
|
217
|
+
compactAllSoFar();
|
|
218
|
+
|
|
219
|
+
// Process the results - throw away stuff marked for deletion, insert compacted things, etc.
|
|
220
|
+
var result = [];
|
|
221
|
+
for (i = 0; i < tokens.length; i++) {
|
|
222
|
+
token = tokens[i];
|
|
223
|
+
|
|
224
|
+
if (token.replaceWith) {
|
|
225
|
+
for (var ii = 0; ii < token.replaceWith.length; ii++) {
|
|
226
|
+
result.push(token.replaceWith[ii]);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (!token.isMarkedForDeletion) {
|
|
230
|
+
result.push(token);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
token.isMarkedForDeletion = false;
|
|
234
|
+
token.replaceWith = null;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return result;
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
compactShorthands: compactShorthands
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
})();
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
|
|
2
|
+
// Helper for tokenizing the contents of a CSS selector block
|
|
3
|
+
|
|
4
|
+
module.exports = (function() {
|
|
5
|
+
var createTokenPrototype = function (processable) {
|
|
6
|
+
var important = '!important';
|
|
7
|
+
|
|
8
|
+
// Constructor for tokens
|
|
9
|
+
function Token (prop, p2, p3) {
|
|
10
|
+
this.prop = prop;
|
|
11
|
+
if (typeof(p2) === 'string') {
|
|
12
|
+
this.value = p2;
|
|
13
|
+
this.isImportant = p3;
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
this.value = processable[prop].defaultValue;
|
|
17
|
+
this.isImportant = p2;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
Token.prototype.prop = null;
|
|
22
|
+
Token.prototype.value = null;
|
|
23
|
+
Token.prototype.granularValues = null;
|
|
24
|
+
Token.prototype.components = null;
|
|
25
|
+
Token.prototype.position = null;
|
|
26
|
+
Token.prototype.isImportant = false;
|
|
27
|
+
Token.prototype.isDirty = false;
|
|
28
|
+
Token.prototype.isShorthand = false;
|
|
29
|
+
Token.prototype.isIrrelevant = false;
|
|
30
|
+
Token.prototype.isReal = true;
|
|
31
|
+
Token.prototype.isMarkedForDeletion = false;
|
|
32
|
+
|
|
33
|
+
// Tells if this token is a component of the other one
|
|
34
|
+
Token.prototype.isComponentOf = function (other) {
|
|
35
|
+
if (!processable[this.prop] || !processable[other.prop])
|
|
36
|
+
return false;
|
|
37
|
+
if (!(processable[other.prop].components instanceof Array) || !processable[other.prop].components.length)
|
|
38
|
+
return false;
|
|
39
|
+
|
|
40
|
+
return processable[other.prop].components.indexOf(this.prop) >= 0;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Clones a token
|
|
44
|
+
Token.prototype.clone = function (isImportant) {
|
|
45
|
+
var token = new Token(this.prop, this.value, (typeof(isImportant) !== 'undefined' ? isImportant : this.isImportant));
|
|
46
|
+
return token;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Creates an irrelevant token with the same prop
|
|
50
|
+
Token.prototype.cloneIrrelevant = function (isImportant) {
|
|
51
|
+
var token = Token.makeDefault(this.prop, (typeof(isImportant) !== 'undefined' ? isImportant : this.isImportant));
|
|
52
|
+
token.isIrrelevant = true;
|
|
53
|
+
return token;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// Creates an array of property tokens with their default values
|
|
57
|
+
Token.makeDefaults = function (props, important) {
|
|
58
|
+
return props.map(function(prop) {
|
|
59
|
+
return new Token(prop, important);
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// Parses one CSS property declaration into a token
|
|
64
|
+
Token.tokenizeOne = function (fullProp) {
|
|
65
|
+
// Find first colon
|
|
66
|
+
var colonPos = fullProp.indexOf(':');
|
|
67
|
+
|
|
68
|
+
if (colonPos < 0) {
|
|
69
|
+
// This property doesn't have a colon, it's invalid. Let's keep it intact anyway.
|
|
70
|
+
return new Token('', fullProp);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Parse parts of the property
|
|
74
|
+
var prop = fullProp.substr(0, colonPos).trim();
|
|
75
|
+
var value = fullProp.substr(colonPos + 1).trim();
|
|
76
|
+
var isImportant = false;
|
|
77
|
+
var importantPos = value.indexOf(important);
|
|
78
|
+
|
|
79
|
+
// Check if the property is important
|
|
80
|
+
if (importantPos >= 1 && importantPos === value.length - important.length) {
|
|
81
|
+
value = value.substr(0, importantPos).trim();
|
|
82
|
+
isImportant = true;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Return result
|
|
86
|
+
var result = new Token(prop, value, isImportant);
|
|
87
|
+
|
|
88
|
+
// If this is a shorthand, break up its values
|
|
89
|
+
// NOTE: we need to do this for all shorthands because otherwise we couldn't remove default values from them
|
|
90
|
+
if (processable[prop] && processable[prop].isShorthand) {
|
|
91
|
+
result.isShorthand = true;
|
|
92
|
+
result.components = processable[prop].breakUp(result);
|
|
93
|
+
result.isDirty = true;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return result;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// Breaks up a string of CSS property declarations into tokens so that they can be handled more easily
|
|
100
|
+
Token.tokenize = function (input) {
|
|
101
|
+
// Split the input by semicolons and parse the parts
|
|
102
|
+
var tokens = input.split(';').map(Token.tokenizeOne);
|
|
103
|
+
return tokens;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// Transforms tokens back into CSS properties
|
|
107
|
+
Token.detokenize = function (tokens) {
|
|
108
|
+
// If by mistake the input is not an array, make it an array
|
|
109
|
+
if (!(tokens instanceof Array)) {
|
|
110
|
+
tokens = [tokens];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
var result = '';
|
|
114
|
+
|
|
115
|
+
// This step takes care of putting together the components of shorthands
|
|
116
|
+
// NOTE: this is necessary to do for every shorthand, otherwise we couldn't remove their default values
|
|
117
|
+
for (var i = 0; i < tokens.length; i++) {
|
|
118
|
+
var t = tokens[i];
|
|
119
|
+
if (t.isShorthand && t.isDirty) {
|
|
120
|
+
var news = processable[t.prop].putTogether(t.prop, t.components, t.isImportant);
|
|
121
|
+
Array.prototype.splice.apply(tokens, [i, 1].concat(news));
|
|
122
|
+
t.isDirty = false;
|
|
123
|
+
i--;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (t.prop)
|
|
128
|
+
result += t.prop + ':';
|
|
129
|
+
|
|
130
|
+
if (t.value)
|
|
131
|
+
result += t.value;
|
|
132
|
+
|
|
133
|
+
if (t.isImportant)
|
|
134
|
+
result += important;
|
|
135
|
+
|
|
136
|
+
result += ';';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return result.substr(0, result.length - 1);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// Gets the final (detokenized) length of the given tokens
|
|
143
|
+
Token.getDetokenizedLength = function (tokens) {
|
|
144
|
+
// If by mistake the input is not an array, make it an array
|
|
145
|
+
if (!(tokens instanceof Array)) {
|
|
146
|
+
tokens = [tokens];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
var result = 0;
|
|
150
|
+
|
|
151
|
+
// This step takes care of putting together the components of shorthands
|
|
152
|
+
// NOTE: this is necessary to do for every shorthand, otherwise we couldn't remove their default values
|
|
153
|
+
for (var i = 0; i < tokens.length; i++) {
|
|
154
|
+
var t = tokens[i];
|
|
155
|
+
if (t.isShorthand && t.isDirty) {
|
|
156
|
+
var news = processable[t.prop].putTogether(t.prop, t.components, t.isImportant);
|
|
157
|
+
Array.prototype.splice.apply(tokens, [i, 1].concat(news));
|
|
158
|
+
t.isDirty = false;
|
|
159
|
+
i--;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (t.prop) {
|
|
164
|
+
result += t.prop.length + 1;
|
|
165
|
+
}
|
|
166
|
+
if (t.value) {
|
|
167
|
+
result += t.value.length;
|
|
168
|
+
}
|
|
169
|
+
if (t.isImportant) {
|
|
170
|
+
result += important.length;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return result;
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
return Token;
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
createTokenPrototype: createTokenPrototype
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
})();
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
|
|
2
|
+
// Validates various CSS property values
|
|
3
|
+
|
|
4
|
+
var Splitter = require('../text/splitter');
|
|
5
|
+
|
|
6
|
+
module.exports = (function () {
|
|
7
|
+
// Regexes used for stuff
|
|
8
|
+
var widthKeywords = ['thin', 'thick', 'medium', 'inherit', 'initial'];
|
|
9
|
+
var cssUnitRegexStr = '(\\-?\\.?\\d+\\.?\\d*(px|%|em|rem|in|cm|mm|ex|pt|pc|vw|vh|vmin|vmax|)|auto|inherit)';
|
|
10
|
+
var cssFunctionNoVendorRegexStr = '[A-Z]+(\\-|[A-Z]|[0-9])+\\(([A-Z]|[0-9]|\\ |\\,|\\#|\\+|\\-|\\%|\\.|\\(|\\))*\\)';
|
|
11
|
+
var cssFunctionVendorRegexStr = '\\-(\\-|[A-Z]|[0-9])+\\(([A-Z]|[0-9]|\\ |\\,|\\#|\\+|\\-|\\%|\\.|\\(|\\))*\\)';
|
|
12
|
+
var cssVariableRegexStr = 'var\\(\\-\\-[^\\)]+\\)';
|
|
13
|
+
var cssFunctionAnyRegexStr = '(' + cssVariableRegexStr + '|' + cssFunctionNoVendorRegexStr + '|' + cssFunctionVendorRegexStr + ')';
|
|
14
|
+
var cssUnitAnyRegexStr = '(none|' + widthKeywords.join('|') + '|' + cssUnitRegexStr + '|' + cssVariableRegexStr + '|' + cssFunctionNoVendorRegexStr + '|' + cssFunctionVendorRegexStr + ')';
|
|
15
|
+
|
|
16
|
+
var cssFunctionNoVendorRegex = new RegExp('^' + cssFunctionNoVendorRegexStr + '$', 'i');
|
|
17
|
+
var cssFunctionVendorRegex = new RegExp('^' + cssFunctionVendorRegexStr + '$', 'i');
|
|
18
|
+
var cssVariableRegex = new RegExp('^' + cssVariableRegexStr + '$', 'i');
|
|
19
|
+
var cssFunctionAnyRegex = new RegExp('^' + cssFunctionAnyRegexStr + '$', 'i');
|
|
20
|
+
var cssUnitRegex = new RegExp('^' + cssUnitRegexStr + '$', 'i');
|
|
21
|
+
var cssUnitAnyRegex = new RegExp('^' + cssUnitAnyRegexStr + '$', 'i');
|
|
22
|
+
|
|
23
|
+
var backgroundRepeatKeywords = ['repeat', 'no-repeat', 'repeat-x', 'repeat-y', 'inherit'];
|
|
24
|
+
var backgroundAttachmentKeywords = ['inherit', 'scroll', 'fixed', 'local'];
|
|
25
|
+
var backgroundPositionKeywords = ['center', 'top', 'bottom', 'left', 'right'];
|
|
26
|
+
var backgroundSizeKeywords = ['contain', 'cover'];
|
|
27
|
+
var listStyleTypeKeywords = ['armenian', 'circle', 'cjk-ideographic', 'decimal', 'decimal-leading-zero', 'disc', 'georgian', 'hebrew', 'hiragana', 'hiragana-iroha', 'inherit', 'katakana', 'katakana-iroha', 'lower-alpha', 'lower-greek', 'lower-latin', 'lower-roman', 'none', 'square', 'upper-alpha', 'upper-latin', 'upper-roman'];
|
|
28
|
+
var listStylePositionKeywords = ['inside', 'outside', 'inherit'];
|
|
29
|
+
var outlineStyleKeywords = ['auto', 'inherit', 'hidden', 'none', 'dotted', 'dashed', 'solid', 'double', 'groove', 'ridge', 'inset', 'outset'];
|
|
30
|
+
|
|
31
|
+
var validator = {
|
|
32
|
+
isValidHexColor: function (s) {
|
|
33
|
+
return (s.length === 4 || s.length === 7) && s[0] === '#';
|
|
34
|
+
},
|
|
35
|
+
isValidRgbaColor: function (s) {
|
|
36
|
+
s = s.split(' ').join('');
|
|
37
|
+
return s.length > 0 && s.indexOf('rgba(') === 0 && s.indexOf(')') === s.length - 1;
|
|
38
|
+
},
|
|
39
|
+
isValidHslaColor: function (s) {
|
|
40
|
+
s = s.split(' ').join('');
|
|
41
|
+
return s.length > 0 && s.indexOf('hsla(') === 0 && s.indexOf(')') === s.length - 1;
|
|
42
|
+
},
|
|
43
|
+
isValidNamedColor: function (s) {
|
|
44
|
+
// We don't really check if it's a valid color value, but allow any letters in it
|
|
45
|
+
return s !== 'auto' && (s === 'transparent' || s === 'inherit' || /^[a-zA-Z]+$/.test(s));
|
|
46
|
+
},
|
|
47
|
+
isValidVariable: function(s) {
|
|
48
|
+
return cssVariableRegex.test(s);
|
|
49
|
+
},
|
|
50
|
+
isValidColor: function (s) {
|
|
51
|
+
return validator.isValidNamedColor(s) || validator.isValidHexColor(s) || validator.isValidRgbaColor(s) || validator.isValidHslaColor(s) || validator.isValidVariable(s);
|
|
52
|
+
},
|
|
53
|
+
isValidUrl: function (s) {
|
|
54
|
+
// NOTE: at this point all URLs are replaced with placeholders by clean-css, so we check for those placeholders
|
|
55
|
+
return s.indexOf('__ESCAPED_URL_CLEAN_CSS') === 0;
|
|
56
|
+
},
|
|
57
|
+
isValidUnit: function (s) {
|
|
58
|
+
return cssUnitAnyRegex.test(s);
|
|
59
|
+
},
|
|
60
|
+
isValidUnitWithoutFunction: function (s) {
|
|
61
|
+
return cssUnitRegex.test(s);
|
|
62
|
+
},
|
|
63
|
+
isValidFunctionWithoutVendorPrefix: function (s) {
|
|
64
|
+
return cssFunctionNoVendorRegex.test(s);
|
|
65
|
+
},
|
|
66
|
+
isValidFunctionWithVendorPrefix: function (s) {
|
|
67
|
+
return cssFunctionVendorRegex.test(s);
|
|
68
|
+
},
|
|
69
|
+
isValidFunction: function (s) {
|
|
70
|
+
return cssFunctionAnyRegex.test(s);
|
|
71
|
+
},
|
|
72
|
+
isValidBackgroundRepeat: function (s) {
|
|
73
|
+
return backgroundRepeatKeywords.indexOf(s) >= 0 || validator.isValidVariable(s);
|
|
74
|
+
},
|
|
75
|
+
isValidBackgroundAttachment: function (s) {
|
|
76
|
+
return backgroundAttachmentKeywords.indexOf(s) >= 0 || validator.isValidVariable(s);
|
|
77
|
+
},
|
|
78
|
+
isValidBackgroundPositionPart: function (s) {
|
|
79
|
+
if (backgroundPositionKeywords.indexOf(s) >= 0)
|
|
80
|
+
return true;
|
|
81
|
+
|
|
82
|
+
return cssUnitRegex.test(s) || validator.isValidVariable(s);
|
|
83
|
+
},
|
|
84
|
+
isValidBackgroundPosition: function (s) {
|
|
85
|
+
if (s === 'inherit')
|
|
86
|
+
return true;
|
|
87
|
+
|
|
88
|
+
var parts = s.split(' ');
|
|
89
|
+
for (var i = 0, l = parts.length; i < l; i++) {
|
|
90
|
+
if (parts[i] === '')
|
|
91
|
+
continue;
|
|
92
|
+
if (validator.isValidBackgroundPositionPart(parts[i]) || validator.isValidVariable(parts[i]))
|
|
93
|
+
continue;
|
|
94
|
+
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return true;
|
|
99
|
+
},
|
|
100
|
+
isValidBackgroundSizePart: function(s) {
|
|
101
|
+
return backgroundSizeKeywords.indexOf(s) >= 0 || cssUnitRegex.test(s) || validator.isValidVariable(s);
|
|
102
|
+
},
|
|
103
|
+
isValidBackgroundPositionAndSize: function(s) {
|
|
104
|
+
if (s.indexOf('/') < 0)
|
|
105
|
+
return false;
|
|
106
|
+
|
|
107
|
+
var twoParts = new Splitter('/').split(s);
|
|
108
|
+
return validator.isValidBackgroundSizePart(twoParts.pop()) && validator.isValidBackgroundPositionPart(twoParts.pop());
|
|
109
|
+
},
|
|
110
|
+
isValidListStyleType: function (s) {
|
|
111
|
+
return listStyleTypeKeywords.indexOf(s) >= 0 || validator.isValidVariable(s);
|
|
112
|
+
},
|
|
113
|
+
isValidListStylePosition: function (s) {
|
|
114
|
+
return listStylePositionKeywords.indexOf(s) >= 0 || validator.isValidVariable(s);
|
|
115
|
+
},
|
|
116
|
+
isValidOutlineColor: function (s) {
|
|
117
|
+
return s === 'invert' || validator.isValidColor(s) || validator.isValidVendorPrefixedValue(s);
|
|
118
|
+
},
|
|
119
|
+
isValidOutlineStyle: function (s) {
|
|
120
|
+
return outlineStyleKeywords.indexOf(s) >= 0 || validator.isValidVariable(s);
|
|
121
|
+
},
|
|
122
|
+
isValidOutlineWidth: function (s) {
|
|
123
|
+
return validator.isValidUnit(s) || widthKeywords.indexOf(s) >= 0 || validator.isValidVariable(s);
|
|
124
|
+
},
|
|
125
|
+
isValidVendorPrefixedValue: function (s) {
|
|
126
|
+
return /^-([A-Za-z0-9]|-)*$/gi.test(s);
|
|
127
|
+
},
|
|
128
|
+
areSameFunction: function (a, b) {
|
|
129
|
+
if (!validator.isValidFunction(a) || !validator.isValidFunction(b))
|
|
130
|
+
return false;
|
|
131
|
+
|
|
132
|
+
var f1name = a.substring(0, a.indexOf('('));
|
|
133
|
+
var f2name = b.substring(0, b.indexOf('('));
|
|
134
|
+
|
|
135
|
+
return f1name === f2name;
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
return validator;
|
|
140
|
+
})();
|