react / wstein / node_modules / jest-cli / node_modules / jsdom / node_modules / cssstyle / lib / parsers.js
81141 views/*********************************************************************1* These are commonly used parsers for CSS Values they take a string *2* to parse and return a string after it's been converted, if needed *3********************************************************************/4'use strict';56exports.TYPES = {7INTEGER: 1,8NUMBER: 2,9LENGTH: 3,10PERCENT: 4,11URL: 5,12COLOR: 6,13STRING: 7,14ANGLE: 8,15KEYWORD: 9,16NULL_OR_EMPTY_STR: 1017};1819/*jslint regexp: true*/20// rough regular expressions21var integerRegEx = /^[\-+]?[0-9]+$/;22var numberRegEx = /^[\-+]?[0-9]*\.[0-9]+$/;23var lengthRegEx = /^(0|[\-+]?[0-9]*\.?[0-9]+(in|cm|em|mm|pt|pc|px))$/;24var percentRegEx = /^[\-+]?[0-9]*\.?[0-9]+%$/;25var urlRegEx = /^url\(\s*([^\)]*)\s*\)$/;26var stringRegEx = /^("[^"]*"|'[^']*')$/;27var colorRegEx1 = /^#[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]([0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])?$/;28var colorRegEx2 = /^rgb\(([^\)]*)\)$/;29var colorRegEx3 = /^rgba\(([^\)]*)\)$/;30var angleRegEx = /^([\-+]?[0-9]*\.?[0-9]+)(deg|grad|rad)$/;31/*jslint regexp: false*/3233// This will return one of the above types based on the passed in string34exports.valueType = function valueType(val) {35if (val === '' || val === null) {36return exports.TYPES.NULL_OR_EMPTY_STR;37}38if (typeof val === 'number') {39val = val.toString();40}4142if (typeof val !== 'string') {43return undefined;44}4546if (integerRegEx.test(val)) {47return exports.TYPES.INTEGER;48}49if (numberRegEx.test(val)) {50return exports.TYPES.NUMBER;51}52if (lengthRegEx.test(val)) {53return exports.TYPES.LENGTH;54}55if (percentRegEx.test(val)) {56return exports.TYPES.PERCENT;57}58if (urlRegEx.test(val)) {59return exports.TYPES.URL;60}61if (stringRegEx.test(val)) {62return exports.TYPES.STRING;63}64if (angleRegEx.test(val)) {65return exports.TYPES.ANGLE;66}67if (colorRegEx1.test(val)) {68return exports.TYPES.COLOR;69}70var res = colorRegEx2.exec(val);71var parts;72if (res !== null) {73parts = res[1].split(/\s*,\s*/);74if (parts.length !== 3) {75return undefined;76}77if (parts.every(percentRegEx.test.bind(percentRegEx)) || parts.every(integerRegEx.test.bind(integerRegEx))) {78return exports.TYPES.COLOR;79}80return undefined;81}82res = colorRegEx3.exec(val);83if (res !== null) {84parts = res[1].split(/\s*,\s*/);85if (parts.length !== 4) {86return undefined;87}88if (parts.slice(0, 3).every(percentRegEx.test.bind(percentRegEx)) || parts.every(integerRegEx.test.bind(integerRegEx))) {89if (numberRegEx.test(parts[3])) {90return exports.TYPES.COLOR;91}92}93return undefined;94}9596// could still be a color, one of the standard keyword colors97val = val.toLowerCase();98switch (val) {99case 'maroon':100case 'red':101case 'orange':102case 'yellow':103case 'olive':104case 'purple':105case 'fuchsia':106case 'white':107case 'lime':108case 'green':109case 'navy':110case 'blue':111case 'aqua':112case 'teal':113case 'black':114case 'silver':115case 'gray':116// the following are deprecated in CSS3117case 'activeborder':118case 'activecaption':119case 'appworkspace':120case 'background':121case 'buttonface':122case 'buttonhighlight':123case 'buttonshadow':124case 'buttontext':125case 'captiontext':126case 'graytext':127case 'highlight':128case 'highlighttext':129case 'inactiveborder':130case 'inactivecaption':131case 'inactivecaptiontext':132case 'infobackground':133case 'infotext':134case 'menu':135case 'menutext':136case 'scrollbar':137case 'threeddarkshadow':138case 'threedface':139case 'threedhighlight':140case 'threedlightshadow':141case 'threedshadow':142case 'window':143case 'windowframe':144case 'windowtext':145return exports.TYPES.COLOR;146default:147return exports.TYPES.KEYWORD;148}149};150151exports.parseInteger = function parseInteger(val) {152var type = exports.valueType(val);153if (type === exports.TYPES.NULL_OR_EMPTY_STR) {154return val;155}156if (type !== exports.TYPES.INTEGER) {157return undefined;158}159return String(parseInt(val, 10));160};161162exports.parseNumber = function parseNumber(val) {163var type = exports.valueType(val);164if (type === exports.TYPES.NULL_OR_EMPTY_STR) {165return val;166}167if (type !== exports.TYPES.NUMBER && type !== exports.TYPES.INTEGER) {168return undefined;169}170return String(parseFloat(val));171};172173exports.parseLength = function parseLength(val) {174if (val === 0 || val === '0') {175return '0px';176}177var type = exports.valueType(val);178if (type === exports.TYPES.NULL_OR_EMPTY_STR) {179return val;180}181if (type !== exports.TYPES.LENGTH) {182return undefined;183}184return val;185};186187exports.parsePercent = function parsePercent(val) {188if (val === 0 || val === '0') {189return '0%';190}191var type = exports.valueType(val);192if (type === exports.TYPES.NULL_OR_EMPTY_STR) {193return val;194}195if (type !== exports.TYPES.PERCENT) {196return undefined;197}198return val;199};200201// either a length or a percent202exports.parseMeasurement = function parseMeasurement(val) {203var length = exports.parseLength(val);204if (length !== undefined) {205return length;206}207return exports.parsePercent(val);208};209210exports.parseUrl = function parseUrl(val) {211var type = exports.valueType(val);212if (type === exports.TYPES.NULL_OR_EMPTY_STR) {213return val;214}215var res = urlRegEx.exec(val);216// does it match the regex?217if (!res) {218return undefined;219}220var str = res[1];221// if it starts with single or double quotes, does it end with the same?222if ((str[1] === '"' || str[1] === "'") && str[1] !== str[str.length - 1]) {223return undefined;224}225if (str[1] === '"' || str[1] === "'") {226str = str.substr(1, -1);227}228229var i;230for (i = 0; i < str.length; i++) {231switch (str[i]) {232case '(':233case ')':234case ' ':235case '\t':236case '\n':237case "'":238case '"':239return undefined;240case '\\':241i++;242break;243}244}245return 'url(' + str + ')';246};247248exports.parseString = function parseString(val) {249var type = exports.valueType(val);250if (type === exports.TYPES.NULL_OR_EMPTY_STR) {251return val;252}253if (type !== exports.TYPES.STRING) {254return undefined;255}256var i;257for (i = 1; i < val.length - 1; i++) {258switch (val[i]) {259case val[0]:260return undefined;261case '\\':262i++;263while (i < val.length - 1 && /[0-9A-Fa-f]/.test(val[i])) {264i++;265}266break;267}268}269if (i >= val.length) {270return undefined;271}272return val;273};274275exports.parseColor = function parseColor(val) {276var type = exports.valueType(val);277if (type === exports.TYPES.NULL_OR_EMPTY_STR) {278return val;279}280var red, green, blue, alpha = 1;281var parts;282var res = colorRegEx1.exec(val);283// is it #aaa or #ababab284if (res) {285var hex = val.substr(1);286if (hex.length === 3) {287hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];288}289red = parseInt(hex.substr(0, 2), 16);290green = parseInt(hex.substr(2, 2), 16);291blue = parseInt(hex.substr(4, 2), 16);292return 'rgb(' + red + ', ' + green + ', ' + blue + ')';293}294295res = colorRegEx2.exec(val);296if (res) {297parts = res[1].split(/\s*,\s*/);298if (parts.length !== 3) {299return undefined;300}301if (parts.every(percentRegEx.test.bind(percentRegEx))) {302red = Math.floor(parseFloat(parts[0].slice(0, -1)) * 255 / 100);303green = Math.floor(parseFloat(parts[1].slice(0, -1)) * 255 / 100);304blue = Math.floor(parseFloat(parts[2].slice(0, -1)) * 255 / 100);305} else if (parts.every(integerRegEx.test.bind(integerRegEx))) {306red = parseInt(parts[0], 10);307green = parseInt(parts[1], 10);308blue = parseInt(parts[2], 10);309} else {310return undefined;311}312red = Math.min(255, Math.max(0, red));313green = Math.min(255, Math.max(0, green));314blue = Math.min(255, Math.max(0, blue));315return 'rgb(' + red + ', ' + green + ', ' + blue + ')';316}317318res = colorRegEx3.exec(val);319if (res) {320parts = res[1].split(/\s*,\s*/);321if (parts.length !== 4) {322return undefined;323}324if (parts.slice(0, 3).every(percentRegEx.test.bind(percentRegEx))) {325red = Math.floor(parseFloat(parts[0].slice(0, -1)) * 255 / 100);326green = Math.floor(parseFloat(parts[1].slice(0, -1)) * 255 / 100);327blue = Math.floor(parseFloat(parts[2].slice(0, -1)) * 255 / 100);328alpha = parseFloat(parts[3]);329} else if (parts.slice(0, 3).every(integerRegEx.test.bind(integerRegEx))) {330red = parseInt(parts[0], 10);331green = parseInt(parts[1], 10);332blue = parseInt(parts[2], 10);333alpha = parseFloat(parts[3]);334} else {335return undefined;336}337if (isNaN(alpha)) {338alpha = 1;339}340red = Math.min(255, Math.max(0, red));341green = Math.min(255, Math.max(0, green));342blue = Math.min(255, Math.max(0, blue));343alpha = Math.min(1, Math.max(0, alpha));344if (alpha === 1) {345return 'rgb(' + red + ', ' + green + ', ' + blue + ')';346}347return 'rgba(' + red + ', ' + green + ', ' + blue + ', ' + alpha + ')';348}349350if (type === exports.TYPES.COLOR) {351return val;352}353return undefined;354};355356exports.parseAngle = function parseAngle(val) {357var type = exports.valueType(val);358if (type === exports.TYPES.NULL_OR_EMPTY_STR) {359return val;360}361if (type !== exports.TYPES.ANGLE) {362return undefined;363}364var res = angleRegEx.exec(val);365var flt = parseFloat(res[1]);366if (res[2] === 'rad') {367flt *= 180 / Math.PI;368} else if (res[2] === 'grad') {369flt *= 360 / 400;370}371372while (flt < 0) {373flt += 360;374}375while (flt > 360) {376flt -= 360;377}378return flt + 'deg';379};380381exports.parseKeyword = function parseKeyword(val, valid_keywords) {382var type = exports.valueType(val);383if (type === exports.TYPES.NULL_OR_EMPTY_STR) {384return val;385}386if (type !== exports.TYPES.KEYWORD) {387return undefined;388}389val = val.toString().toLowerCase();390var i;391for (i = 0; i < valid_keywords.length; i++) {392if (valid_keywords[i].toLowerCase() === val) {393return valid_keywords[i];394}395}396return undefined;397};398399// utility to translate from border-width to borderWidth400var dashedToCamelCase = function (dashed) {401var i;402var camel = '';403var nextCap = false;404for (i = 0; i < dashed.length; i++) {405if (dashed[i] !== '-') {406camel += nextCap ? dashed[i].toUpperCase() : dashed[i];407nextCap = false;408} else {409nextCap = true;410}411}412return camel;413};414exports.dashedToCamelCase = dashedToCamelCase;415416var is_space = /\s/;417var opening_deliminators = ['"', '\'', '('];418var closing_deliminators = ['"', '\'', ')'];419// this splits on whitespace, but keeps quoted and parened parts together420var getParts = function (str) {421var deliminator_stack = [];422var length = str.length;423var i;424var parts = [];425var current_part = '';426var opening_index;427var closing_index;428for (i = 0; i < length; i++) {429opening_index = opening_deliminators.indexOf(str[i]);430closing_index = closing_deliminators.indexOf(str[i]);431if (is_space.test(str[i])) {432if (deliminator_stack.length === 0) {433parts.push(current_part);434current_part = '';435} else {436current_part += str[i];437}438} else {439if (str[i] === '\\') {440i++;441current_part += str[i];442} else {443current_part += str[i];444if (closing_index !== -1 && closing_index === deliminator_stack[deliminator_stack.length - 1]) {445deliminator_stack.pop();446} else if (opening_index !== -1) {447deliminator_stack.push(opening_index);448}449}450}451}452if (current_part !== '') {453parts.push(current_part);454}455return parts;456};457458/*459* this either returns undefined meaning that it isn't valid460* or returns an object where the keys are dashed short461* hand properties and the values are the values to set462* on them463*/464exports.shorthandParser = function parse(v, shorthand_for) {465var obj = {};466var type = exports.valueType(v);467if (type === exports.TYPES.NULL_OR_EMPTY_STR) {468Object.keys(shorthand_for).forEach(function (property) {469obj[property] = v;470});471return obj;472}473474if (typeof v === 'number') {475v = v.toString();476}477478if (typeof v !== 'string') {479return undefined;480}481482if (v.toLowerCase() === 'inherit') {483return {};484}485var parts = getParts(v);486var valid = true;487parts.forEach(function (part) {488var part_valid = false;489Object.keys(shorthand_for).forEach(function (property) {490if (shorthand_for[property].isValid(part)) {491part_valid = true;492obj[property] = part;493}494});495valid = valid && part_valid;496});497if (!valid) {498return undefined;499}500return obj;501};502503exports.shorthandSetter = function (property, shorthand_for) {504return function (v) {505var obj = exports.shorthandParser(v, shorthand_for);506if (obj === undefined) {507return;508}509//console.log('shorthandSetter for:', property, 'obj:', obj);510Object.keys(obj).forEach(function (subprop) {511// in case subprop is an implicit property, this will clear512// *its* subpropertiesX513var camel = dashedToCamelCase(subprop);514this[camel] = obj[subprop];515// in case it gets translated into something else (0 -> 0px)516obj[subprop] = this[camel];517this.removeProperty(subprop);518this._values[subprop] = obj[subprop];519}, this);520Object.keys(shorthand_for).forEach(function (subprop) {521if (!obj.hasOwnProperty(subprop)) {522this.removeProperty(subprop);523delete this._values[subprop];524}525}, this);526// in case the value is something like 'none' that removes all values,527// check that the generated one is not empty, first remove the property528// if it already exists, then call the shorthandGetter, if it's an empty529// string, don't set the property530this.removeProperty(property);531var calculated = exports.shorthandGetter(property, shorthand_for).call(this);532if (calculated !== '') {533this._setProperty(property, calculated);534}535};536};537538exports.shorthandGetter = function (property, shorthand_for) {539return function () {540if (this._values[property] !== undefined) {541return this.getPropertyValue(property);542}543return Object.keys(shorthand_for).map(function (subprop) {544return this.getPropertyValue(subprop);545}, this).filter(function (value) {546return value !== '';547}).join(' ');548};549};550551// isValid(){1,4} | inherit552// if one, it applies to all553// if two, the first applies to the top and bottom, and the second to left and right554// if three, the first applies to the top, the second to left and right, the third bottom555// if four, top, right, bottom, left556exports.implicitSetter = function (property_before, property_after, isValid, parser) {557property_after = property_after || '';558if (property_after !== '') {559property_after = '-' + property_after;560}561562return function (v) {563if (typeof v === 'number') {564v = v.toString();565}566if (typeof v !== 'string') {567return undefined;568}569var parts;570if (v.toLowerCase() === 'inherit' || v === '') {571parts = [v];572} else {573parts = getParts(v);574}575if (parts.length < 1 || parts.length > 4) {576return undefined;577}578579if (!parts.every(isValid)) {580return undefined;581}582583this._setProperty(property_before + property_after, parts.map(function (part) { return parser(part); }).join(' '));584585this.removeProperty(property_before + '-top' + property_after);586this.removeProperty(property_before + '-right' + property_after);587this.removeProperty(property_before + '-bottom' + property_after);588this.removeProperty(property_before + '-left' + property_after);589switch (parts.length) {590case 1:591this._values[property_before + '-top' + property_after] = parser(parts[0]);592this._values[property_before + '-right' + property_after] = parser(parts[0]);593this._values[property_before + '-bottom' + property_after] = parser(parts[0]);594this._values[property_before + '-left' + property_after] = parser(parts[0]);595return v;596case 2:597this._values[property_before + '-top' + property_after] = parser(parts[0]);598this._values[property_before + '-right' + property_after] = parser(parts[1]);599this._values[property_before + '-bottom' + property_after] = parser(parts[0]);600this._values[property_before + '-left' + property_after] = parser(parts[1]);601return v;602case 3:603this._values[property_before + '-top' + property_after] = parser(parts[0]);604this._values[property_before + '-right' + property_after] = parser(parts[1]);605this._values[property_before + '-bottom' + property_after] = parser(parts[2]);606this._values[property_before + '-left' + property_after] = parser(parts[1]);607return v;608case 4:609this._values[property_before + '-top' + property_after] = parser(parts[0]);610this._values[property_before + '-right' + property_after] = parser(parts[1]);611this._values[property_before + '-bottom' + property_after] = parser(parts[2]);612this._values[property_before + '-left' + property_after] = parser(parts[3]);613return v;614}615};616};617618var camel_to_dashed = /[A-Z]/g;619/*jslint regexp: true*/620var first_segment = /^\([^\-]\)-/;621/*jslint regexp: false*/622var vendor_prefixes = ['o', 'moz', 'ms', 'webkit'];623exports.camelToDashed = function (camel_case) {624var match;625var dashed = camel_case.replace(camel_to_dashed, '-$&').toLowerCase();626match = dashed.match(first_segment);627if (match && vendor_prefixes.indexOf(match[1]) !== -1) {628dashed = '-' + dashed;629}630return dashed;631};632633634