Skip to content

Commit e6b58ee

Browse files
committed
add CSSValueExpression.js
1 parent d88db21 commit e6b58ee

File tree

2 files changed

+334
-0
lines changed

2 files changed

+334
-0
lines changed

lib/CSSValue.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
//.CommonJS
2+
var CSSOM = {};
3+
///CommonJS
4+
5+
6+
/**
7+
* @constructor
8+
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSValue
9+
*
10+
* TODO: add if needed
11+
*/
12+
CSSOM.CSSValue = function CSSValue() {
13+
};
14+
15+
CSSOM.CSSValue.prototype = {
16+
constructor: CSSOM.CSSValue,
17+
18+
// @see: http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSValue
19+
set cssText() {
20+
var c = getConstructorName(this);
21+
22+
throw new Exception('DOMException: property "cssText" of "' + name + '" is readonly!');
23+
},
24+
25+
get cssText() {
26+
var name = getConstructorName(this);
27+
28+
throw new Exception('getter "cssText" of "' + name + '" is not implemented!');
29+
}
30+
};
31+
32+
function getConstructorName(instance) {
33+
var s = instance.constructor.toString(),
34+
c = s.match(/function\s([^\(]+)/),
35+
name = c[1];
36+
37+
return name;
38+
}
39+
40+
41+
//.CommonJS
42+
exports.CSSValue = CSSOM.CSSValue;
43+
///CommonJS

lib/CSSValueExpression.js

Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
//.CommonJS
2+
var CSSOM = {
3+
CSSValue: require('./CSSValue').CSSValue
4+
};
5+
///CommonJS
6+
7+
8+
/**
9+
* @constructor
10+
* @see http://msdn.microsoft.com/en-us/library/ms537634(v=vs.85).aspx
11+
*
12+
*/
13+
CSSOM.CSSValueExpression = function CSSValueExpression(token, idx) {
14+
this._token = token;
15+
this._idx = idx;
16+
};
17+
18+
CSSOM.CSSValueExpression.prototype = new CSSOM.CSSValue;
19+
CSSOM.CSSValueExpression.prototype.constructor = CSSOM.CSSValueExpression;
20+
21+
/**
22+
* parse css expression() value
23+
*
24+
* @return {Object}
25+
* - error:
26+
* or
27+
* - idx:
28+
* - expression:
29+
*
30+
* Example:
31+
*
32+
* .selector {
33+
* zoom: expression(documentElement.clientWidth > 1000 ? '1000px' : 'auto');
34+
* }
35+
*/
36+
CSSOM.CSSValueExpression.prototype.parse = function() {
37+
var token = this._token,
38+
idx = this._idx;
39+
40+
var character = '',
41+
STATES = {
42+
LITERAL: 0,
43+
STRING: 1,
44+
REGEXP: 2
45+
},
46+
state = STATES.LITERAL;
47+
string_sep = '',
48+
expression = '',
49+
error = '',
50+
paren = [];
51+
52+
53+
for (; ; ++idx) {
54+
character = token.charAt(idx);
55+
56+
// end of token
57+
if (character == '') {
58+
error = 'css expression error: unfinished expression!';
59+
break;
60+
}
61+
62+
switch(character) {
63+
case '(':
64+
if (state == STATES.LITERAL) {
65+
paren.push(character);
66+
}
67+
expression += character;
68+
break;
69+
70+
case ')':
71+
if (state == STATES.LITERAL) {
72+
paren.pop(character);
73+
}
74+
expression += character;
75+
break;
76+
77+
case '/':
78+
if (state == STATES.LITERAL) {
79+
var nextChar = token.charAt(idx + 1),
80+
regExp;
81+
if (nextChar == '/' || nextChar == '*') { // comment
82+
var commentEndChar,
83+
commentEndIdx;
84+
85+
if (nextChar == '/') { // line comment
86+
commentEndChar = '\n';
87+
} else if (nextChar == '*') { // block comment
88+
commentEndChar = '*/';
89+
}
90+
91+
commentEndIdx = token.indexOf(commentEndChar, idx + 1 + 1);
92+
if (commentEndIdx !== -1) {
93+
idx = commentEndIdx + commentEndChar.length - 1;
94+
} else {
95+
error = 'css expression error: unfinished comment in expression!';
96+
}
97+
} else if (regExp = parseJSRexExp(token, idx)) { // regexp?
98+
idx = regExp.idx;
99+
expression += regExp.regExp;
100+
} else {
101+
expression += character;
102+
}
103+
} else {
104+
expression += character;
105+
}
106+
break;
107+
108+
// TODO: string_sep in regexp
109+
case "'":
110+
case '"':
111+
if (state == STATES.LITERAL) {
112+
state = STATES.STRING;
113+
string_sep = character;
114+
} else if (state == STATES.STRING) {
115+
if (string_sep == character) {
116+
var matched = expression.match(/\\+$/);
117+
if (!matched || matched[0].length % 2 == 0) {
118+
state = STATES.LITERAL;
119+
string_sep = '';
120+
}
121+
}
122+
}
123+
expression += character;
124+
break;
125+
126+
default:
127+
expression += character;
128+
break;
129+
}
130+
131+
if (error) {
132+
break;
133+
}
134+
135+
// end of expression
136+
if (paren.length == 0) {
137+
break;
138+
}
139+
}
140+
141+
var ret;
142+
if (error) {
143+
ret = {
144+
error: error
145+
}
146+
} else {
147+
ret = {
148+
idx: idx,
149+
expression: expression
150+
}
151+
}
152+
153+
return ret;
154+
};
155+
156+
157+
/**
158+
* parse regexp in css expression
159+
*
160+
* @return {Object|false}
161+
* - idx:
162+
* - regExp:
163+
* or
164+
* false
165+
*/
166+
167+
/*
168+
169+
all legal RegExp
170+
171+
/a/
172+
(/a/)
173+
[/a/]
174+
[12, /a/]
175+
176+
!/a/
177+
178+
+/a/
179+
-/a/
180+
* /a/
181+
/ /a/
182+
%/a/
183+
184+
===/a/
185+
!==/a/
186+
==/a/
187+
!=/a/
188+
>/a/
189+
>=/a/
190+
</a/
191+
<=/a/
192+
193+
&/a/
194+
|/a/
195+
^/a/
196+
~/a/
197+
<</a/
198+
>>/a/
199+
>>>/a/
200+
201+
&&/a/
202+
||/a/
203+
?/a/
204+
=/a/
205+
,/a/
206+
207+
delete /a/
208+
in /a/
209+
instanceof /a/
210+
new /a/
211+
typeof /a/
212+
void /a/
213+
214+
*/
215+
function parseJSRexExp(token, idx) {
216+
var before = token.substring(0, idx).trimRight(),
217+
legalRegx = [
218+
/^$/,
219+
/\($/,
220+
/\[$/,
221+
/\!$/,
222+
/\+$/,
223+
/\-$/,
224+
/\*$/,
225+
/\/\s+/,
226+
/\%$/,
227+
/\=$/,
228+
/\>$/,
229+
/\<$/,
230+
/\&$/,
231+
/\|$/,
232+
/\^$/,
233+
/\~$/,
234+
/\?$/,
235+
/\,$/,
236+
/delete$/,
237+
/in$/,
238+
/instanceof$/,
239+
/new$/,
240+
/typeof$/,
241+
/void$/,
242+
];
243+
244+
var isLegal = legalRegx.some(function(reg) {
245+
return reg.test(before);
246+
});
247+
248+
if (!isLegal) {
249+
return false;
250+
} else {
251+
var startIdx = idx,
252+
regexp;
253+
254+
while(true) {
255+
var regEndIdx = token.indexOf('/', startIdx + 1);
256+
257+
if (regEndIdx === -1) {
258+
return false;
259+
} else {
260+
regexp = token.substring(idx + 1, regEndIdx);
261+
var matched = regexp.match(/\\+$/);
262+
if (!matched || matched[0] % 2 == 0) {
263+
regexp = '/' + regexp + '/';
264+
break;
265+
} else {
266+
startIdx = regEndIdx;
267+
}
268+
}
269+
}
270+
}
271+
272+
// validate
273+
// RegExp boundary(/) must be in the same line
274+
var nextNewLineIdx = token.indexOf('\n', idx + 1);
275+
if (nextNewLineIdx < regEndIdx) {
276+
return false;
277+
}
278+
279+
280+
return {
281+
idx: regEndIdx,
282+
regExp: regexp
283+
};
284+
}
285+
286+
287+
288+
289+
//.CommonJS
290+
exports.CSSValueExpression = CSSOM.CSSValueExpression;
291+
///CommonJS

0 commit comments

Comments
 (0)