1var functionNoVendorRegexStr = '[A-Z]+(\\-|[A-Z]|[0-9])+\\(.*?\\)';
2var functionVendorRegexStr = '\\-(\\-|[A-Z]|[0-9])+\\(.*?\\)';
3var variableRegexStr = 'var\\(\\-\\-[^\\)]+\\)';
4var functionAnyRegexStr = '(' + variableRegexStr + '|' + functionNoVendorRegexStr + '|' + functionVendorRegexStr + ')';
5
6var calcRegex = new RegExp('^(\\-moz\\-|\\-webkit\\-)?calc\\([^\\)]+\\)$', 'i');
7var decimalRegex = /[0-9]/;
8var functionAnyRegex = new RegExp('^' + functionAnyRegexStr + '$', 'i');
9var hslColorRegex = /^hsl\(\s{0,31}[\-\.]?\d+\s{0,31},\s{0,31}\.?\d+%\s{0,31},\s{0,31}\.?\d+%\s{0,31}\)|hsla\(\s{0,31}[\-\.]?\d+\s{0,31},\s{0,31}\.?\d+%\s{0,31},\s{0,31}\.?\d+%\s{0,31},\s{0,31}\.?\d+\s{0,31}\)$/i;
10var identifierRegex = /^(\-[a-z0-9_][a-z0-9\-_]*|[a-z][a-z0-9\-_]*)$/i;
11var namedEntityRegex = /^[a-z]+$/i;
12var prefixRegex = /^-([a-z0-9]|-)*$/i;
13var rgbColorRegex = /^rgb\(\s{0,31}[\d]{1,3}\s{0,31},\s{0,31}[\d]{1,3}\s{0,31},\s{0,31}[\d]{1,3}\s{0,31}\)|rgba\(\s{0,31}[\d]{1,3}\s{0,31},\s{0,31}[\d]{1,3}\s{0,31},\s{0,31}[\d]{1,3}\s{0,31},\s{0,31}[\.\d]+\s{0,31}\)$/i;
14var timingFunctionRegex = /^(cubic\-bezier|steps)\([^\)]+\)$/;
15var validTimeUnits = ['ms', 's'];
16var urlRegex = /^url\([\s\S]+\)$/i;
17var variableRegex = new RegExp('^' + variableRegexStr + '$', 'i');
18
19var eightValueColorRegex = /^#[0-9a-f]{8}$/i;
20var fourValueColorRegex = /^#[0-9a-f]{4}$/i;
21var sixValueColorRegex = /^#[0-9a-f]{6}$/i;
22var threeValueColorRegex = /^#[0-9a-f]{3}$/i;
23
24var DECIMAL_DOT = '.';
25var MINUS_SIGN = '-';
26var PLUS_SIGN = '+';
27
28var Keywords = {
29  '^': [
30    'inherit',
31    'initial',
32    'unset'
33  ],
34  '*-style': [
35    'auto',
36    'dashed',
37    'dotted',
38    'double',
39    'groove',
40    'hidden',
41    'inset',
42    'none',
43    'outset',
44    'ridge',
45    'solid'
46  ],
47  '*-timing-function': [
48    'ease',
49    'ease-in',
50    'ease-in-out',
51    'ease-out',
52    'linear',
53    'step-end',
54    'step-start'
55  ],
56  'animation-direction': [
57    'alternate',
58    'alternate-reverse',
59    'normal',
60    'reverse'
61  ],
62  'animation-fill-mode': [
63    'backwards',
64    'both',
65    'forwards',
66    'none'
67  ],
68  'animation-iteration-count': [
69    'infinite'
70  ],
71  'animation-name': [
72    'none'
73  ],
74  'animation-play-state': [
75    'paused',
76    'running'
77  ],
78  'background-attachment': [
79    'fixed',
80    'inherit',
81    'local',
82    'scroll'
83  ],
84  'background-clip': [
85    'border-box',
86    'content-box',
87    'inherit',
88    'padding-box',
89    'text'
90  ],
91  'background-origin': [
92    'border-box',
93    'content-box',
94    'inherit',
95    'padding-box'
96  ],
97  'background-position': [
98    'bottom',
99    'center',
100    'left',
101    'right',
102    'top'
103  ],
104  'background-repeat': [
105    'no-repeat',
106    'inherit',
107    'repeat',
108    'repeat-x',
109    'repeat-y',
110    'round',
111    'space'
112  ],
113  'background-size': [
114    'auto',
115    'cover',
116    'contain'
117  ],
118  'border-collapse': [
119    'collapse',
120    'inherit',
121    'separate'
122  ],
123  'bottom': [
124    'auto'
125  ],
126  'clear': [
127    'both',
128    'left',
129    'none',
130    'right'
131  ],
132  'color': [
133    'transparent'
134  ],
135  'cursor': [
136    'all-scroll',
137    'auto',
138    'col-resize',
139    'crosshair',
140    'default',
141    'e-resize',
142    'help',
143    'move',
144    'n-resize',
145    'ne-resize',
146    'no-drop',
147    'not-allowed',
148    'nw-resize',
149    'pointer',
150    'progress',
151    'row-resize',
152    's-resize',
153    'se-resize',
154    'sw-resize',
155    'text',
156    'vertical-text',
157    'w-resize',
158    'wait'
159  ],
160  'display': [
161    'block',
162    'inline',
163    'inline-block',
164    'inline-table',
165    'list-item',
166    'none',
167    'table',
168    'table-caption',
169    'table-cell',
170    'table-column',
171    'table-column-group',
172    'table-footer-group',
173    'table-header-group',
174    'table-row',
175    'table-row-group'
176  ],
177  'float': [
178    'left',
179    'none',
180    'right'
181  ],
182  'left': [
183    'auto'
184  ],
185  'font': [
186    'caption',
187    'icon',
188    'menu',
189    'message-box',
190    'small-caption',
191    'status-bar',
192    'unset'
193  ],
194  'font-size': [
195    'large',
196    'larger',
197    'medium',
198    'small',
199    'smaller',
200    'x-large',
201    'x-small',
202    'xx-large',
203    'xx-small'
204  ],
205  'font-stretch': [
206    'condensed',
207    'expanded',
208    'extra-condensed',
209    'extra-expanded',
210    'normal',
211    'semi-condensed',
212    'semi-expanded',
213    'ultra-condensed',
214    'ultra-expanded'
215  ],
216  'font-style': [
217    'italic',
218    'normal',
219    'oblique'
220  ],
221  'font-variant': [
222    'normal',
223    'small-caps'
224  ],
225  'font-weight': [
226    '100',
227    '200',
228    '300',
229    '400',
230    '500',
231    '600',
232    '700',
233    '800',
234    '900',
235    'bold',
236    'bolder',
237    'lighter',
238    'normal'
239  ],
240  'line-height': [
241    'normal'
242  ],
243  'list-style-position': [
244    'inside',
245    'outside'
246  ],
247  'list-style-type': [
248    'armenian',
249    'circle',
250    'decimal',
251    'decimal-leading-zero',
252    'disc',
253    'decimal|disc', // this is the default value of list-style-type, see comment in compactable.js
254    'georgian',
255    'lower-alpha',
256    'lower-greek',
257    'lower-latin',
258    'lower-roman',
259    'none',
260    'square',
261    'upper-alpha',
262    'upper-latin',
263    'upper-roman'
264  ],
265  'overflow': [
266    'auto',
267    'hidden',
268    'scroll',
269    'visible'
270  ],
271  'position': [
272    'absolute',
273    'fixed',
274    'relative',
275    'static'
276  ],
277  'right': [
278    'auto'
279  ],
280  'text-align': [
281    'center',
282    'justify',
283    'left',
284    'left|right', // this is the default value of list-style-type, see comment in compactable.js
285    'right'
286  ],
287  'text-decoration': [
288    'line-through',
289    'none',
290    'overline',
291    'underline'
292  ],
293  'text-overflow': [
294    'clip',
295    'ellipsis'
296  ],
297  'top': [
298    'auto'
299  ],
300  'vertical-align': [
301    'baseline',
302    'bottom',
303    'middle',
304    'sub',
305    'super',
306    'text-bottom',
307    'text-top',
308    'top'
309  ],
310  'visibility': [
311    'collapse',
312    'hidden',
313    'visible'
314  ],
315  'white-space': [
316    'normal',
317    'nowrap',
318    'pre'
319  ],
320  'width': [
321    'inherit',
322    'initial',
323    'medium',
324    'thick',
325    'thin'
326  ]
327};
328
329var Units = [
330  '%',
331  'ch',
332  'cm',
333  'em',
334  'ex',
335  'in',
336  'mm',
337  'pc',
338  'pt',
339  'px',
340  'rem',
341  'vh',
342  'vm',
343  'vmax',
344  'vmin',
345  'vw'
346];
347
348function isColor(value) {
349  return value != 'auto' &&
350    (
351      isKeyword('color')(value) ||
352      isHexColor(value) ||
353      isColorFunction(value) ||
354      isNamedEntity(value)
355    );
356}
357
358function isColorFunction(value) {
359  return isRgbColor(value) || isHslColor(value);
360}
361
362function isDynamicUnit(value) {
363  return calcRegex.test(value);
364}
365
366function isFunction(value) {
367  return functionAnyRegex.test(value);
368}
369
370function isHexColor(value) {
371  return threeValueColorRegex.test(value) || fourValueColorRegex.test(value) || sixValueColorRegex.test(value) || eightValueColorRegex.test(value);
372}
373
374function isHslColor(value) {
375  return hslColorRegex.test(value);
376}
377
378function isIdentifier(value) {
379  return identifierRegex.test(value);
380}
381
382function isImage(value) {
383  return value == 'none' || value == 'inherit' || isUrl(value);
384}
385
386function isKeyword(propertyName) {
387  return function(value) {
388    return Keywords[propertyName].indexOf(value) > -1;
389  };
390}
391
392function isNamedEntity(value) {
393  return namedEntityRegex.test(value);
394}
395
396function isNumber(value) {
397  return scanForNumber(value) == value.length;
398}
399
400function isRgbColor(value) {
401  return rgbColorRegex.test(value);
402}
403
404function isPrefixed(value) {
405  return prefixRegex.test(value);
406}
407
408function isPositiveNumber(value) {
409  return isNumber(value) &&
410    parseFloat(value) >= 0;
411}
412
413function isVariable(value) {
414  return variableRegex.test(value);
415}
416
417function isTime(value) {
418  var numberUpTo = scanForNumber(value);
419
420  return numberUpTo == value.length && parseInt(value) === 0 ||
421    numberUpTo > -1 && validTimeUnits.indexOf(value.slice(numberUpTo + 1)) > -1;
422}
423
424function isTimingFunction() {
425  var isTimingFunctionKeyword = isKeyword('*-timing-function');
426
427  return function (value) {
428    return isTimingFunctionKeyword(value) || timingFunctionRegex.test(value);
429  };
430}
431
432function isUnit(validUnits, value) {
433  var numberUpTo = scanForNumber(value);
434
435  return numberUpTo == value.length && parseInt(value) === 0 ||
436    numberUpTo > -1 && validUnits.indexOf(value.slice(numberUpTo + 1)) > -1 ||
437    value == 'auto' ||
438    value == 'inherit';
439}
440
441function isUrl(value) {
442  return urlRegex.test(value);
443}
444
445function isZIndex(value) {
446  return value == 'auto' ||
447    isNumber(value) ||
448    isKeyword('^')(value);
449}
450
451function scanForNumber(value) {
452  var hasDot = false;
453  var hasSign = false;
454  var character;
455  var i, l;
456
457  for (i = 0, l = value.length; i < l; i++) {
458    character = value[i];
459
460    if (i === 0 && (character == PLUS_SIGN || character == MINUS_SIGN)) {
461      hasSign = true;
462    } else if (i > 0 && hasSign && (character == PLUS_SIGN || character == MINUS_SIGN)) {
463      return i - 1;
464    } else if (character == DECIMAL_DOT && !hasDot) {
465      hasDot = true;
466    } else if (character == DECIMAL_DOT && hasDot) {
467      return i - 1;
468    } else if (decimalRegex.test(character)) {
469      continue;
470    } else {
471      return i - 1;
472    }
473  }
474
475  return i;
476}
477
478function validator(compatibility) {
479  var validUnits = Units.slice(0).filter(function (value) {
480    return !(value in compatibility.units) || compatibility.units[value] === true;
481  });
482
483  return {
484    colorOpacity: compatibility.colors.opacity,
485    isAnimationDirectionKeyword: isKeyword('animation-direction'),
486    isAnimationFillModeKeyword: isKeyword('animation-fill-mode'),
487    isAnimationIterationCountKeyword: isKeyword('animation-iteration-count'),
488    isAnimationNameKeyword: isKeyword('animation-name'),
489    isAnimationPlayStateKeyword: isKeyword('animation-play-state'),
490    isTimingFunction: isTimingFunction(),
491    isBackgroundAttachmentKeyword: isKeyword('background-attachment'),
492    isBackgroundClipKeyword: isKeyword('background-clip'),
493    isBackgroundOriginKeyword: isKeyword('background-origin'),
494    isBackgroundPositionKeyword: isKeyword('background-position'),
495    isBackgroundRepeatKeyword: isKeyword('background-repeat'),
496    isBackgroundSizeKeyword: isKeyword('background-size'),
497    isColor: isColor,
498    isColorFunction: isColorFunction,
499    isDynamicUnit: isDynamicUnit,
500    isFontKeyword: isKeyword('font'),
501    isFontSizeKeyword: isKeyword('font-size'),
502    isFontStretchKeyword: isKeyword('font-stretch'),
503    isFontStyleKeyword: isKeyword('font-style'),
504    isFontVariantKeyword: isKeyword('font-variant'),
505    isFontWeightKeyword: isKeyword('font-weight'),
506    isFunction: isFunction,
507    isGlobal: isKeyword('^'),
508    isHslColor: isHslColor,
509    isIdentifier: isIdentifier,
510    isImage: isImage,
511    isKeyword: isKeyword,
512    isLineHeightKeyword: isKeyword('line-height'),
513    isListStylePositionKeyword: isKeyword('list-style-position'),
514    isListStyleTypeKeyword: isKeyword('list-style-type'),
515    isNumber: isNumber,
516    isPrefixed: isPrefixed,
517    isPositiveNumber: isPositiveNumber,
518    isRgbColor: isRgbColor,
519    isStyleKeyword: isKeyword('*-style'),
520    isTime: isTime,
521    isUnit: isUnit.bind(null, validUnits),
522    isUrl: isUrl,
523    isVariable: isVariable,
524    isWidth: isKeyword('width'),
525    isZIndex: isZIndex
526  };
527}
528
529module.exports = validator;
530