basant307/AI_Governance_Project
048
1import * as util from '../util/index.mjs';2import * as is from '../is.mjs';3import * as math from '../math.mjs';4 5let styfn = {};6 7// a caching layer for property parsing8styfn.parse = function( name, value, propIsBypass, propIsFlat ){9 let self = this;10 11 // function values can't be cached in all cases, and there isn't much benefit of caching them anyway12 if( is.fn( value ) ){13 return self.parseImplWarn( name, value, propIsBypass, propIsFlat );14 }15 16 let flatKey = ( propIsFlat === 'mapping' || propIsFlat === true || propIsFlat === false || propIsFlat == null ) ? 'dontcare' : propIsFlat;17 let bypassKey = propIsBypass ? 't' : 'f';18 let valueKey = '' + value;19 let argHash = util.hashStrings( name, valueKey, bypassKey, flatKey );20 let propCache = self.propCache = self.propCache || [];21 let ret;22 23 if( !(ret = propCache[ argHash ]) ){24 ret = propCache[ argHash ] = self.parseImplWarn( name, value, propIsBypass, propIsFlat );25 }26 27 // - bypasses can't be shared b/c the value can be changed by animations or otherwise overridden28 // - mappings can't be shared b/c mappings are per-element29 if( propIsBypass || propIsFlat === 'mapping' ){30 // need a copy since props are mutated later in their lifecycles31 ret = util.copy( ret );32 33 if( ret ){34 ret.value = util.copy( ret.value ); // because it could be an array, e.g. colour35 }36 }37 38 return ret;39};40 41styfn.parseImplWarn = function( name, value, propIsBypass, propIsFlat ){42 let prop = this.parseImpl( name, value, propIsBypass, propIsFlat );43 44 if( !prop && value != null ){45 util.warn(`The style property \`${name}: ${value}\` is invalid`);46 }47 48 if( prop && (prop.name === 'width' || prop.name === 'height') && value === 'label' ){49 util.warn('The style value of `label` is deprecated for `' + prop.name + '`');50 }51 52 return prop;53};54 55// parse a property; return null on invalid; return parsed property otherwise56// fields :57// - name : the name of the property58// - value : the parsed, native-typed value of the property59// - strValue : a string value that represents the property value in valid css60// - bypass : true iff the property is a bypass property61styfn.parseImpl = function( name, value, propIsBypass, propIsFlat ){62 let self = this;63 64 name = util.camel2dash( name ); // make sure the property name is in dash form (e.g. 'property-name' not 'propertyName')65 66 let property = self.properties[ name ];67 let passedValue = value;68 let types = self.types;69 70 if( !property ){ return null; } // return null on property of unknown name71 if( value === undefined ){ return null; } // can't assign undefined72 73 // the property may be an alias74 if( property.alias ){75 property = property.pointsTo;76 name = property.name;77 }78 79 let valueIsString = is.string( value );80 if( valueIsString ){ // trim the value to make parsing easier81 value = value.trim();82 }83 84 let type = property.type;85 if( !type ){ return null; } // no type, no luck86 87 // check if bypass is null or empty string (i.e. indication to delete bypass property)88 if( propIsBypass && (value === '' || value === null) ){89 return {90 name: name,91 value: value,92 bypass: true,93 deleteBypass: true94 };95 }96 97 // check if value is a function used as a mapper98 if( is.fn( value ) ){99 return {100 name: name,101 value: value,102 strValue: 'fn',103 mapped: types.fn,104 bypass: propIsBypass105 };106 }107 108 // check if value is mapped109 let data, mapData;110 if( !valueIsString || propIsFlat || value.length < 7 || value[1] !== 'a' ){111 // then don't bother to do the expensive regex checks112 113 } else if(value.length >= 7 && value[0] === 'd' && ( data = new RegExp( types.data.regex ).exec( value ) )){114 if( propIsBypass ){ return false; } // mappers not allowed in bypass115 116 let mapped = types.data;117 118 return {119 name: name,120 value: data,121 strValue: '' + value,122 mapped: mapped,123 field: data[1],124 bypass: propIsBypass125 };126 127 } else if(value.length >= 10 && value[0] === 'm' && ( mapData = new RegExp( types.mapData.regex ).exec( value ) )){128 if( propIsBypass ){ return false; } // mappers not allowed in bypass129 if( type.multiple ){ return false; } // impossible to map to num130 131 let mapped = types.mapData;132 133 // we can map only if the type is a colour or a number134 if( !(type.color || type.number) ){ return false; }135 136 let valueMin = this.parse( name, mapData[4] ); // parse to validate137 if( !valueMin || valueMin.mapped ){ return false; } // can't be invalid or mapped138 139 let valueMax = this.parse( name, mapData[5] ); // parse to validate140 if( !valueMax || valueMax.mapped ){ return false; } // can't be invalid or mapped141 142 // check if valueMin and valueMax are the same143 if( valueMin.pfValue === valueMax.pfValue || valueMin.strValue === valueMax.strValue ){144 util.warn('`' + name + ': ' + value + '` is not a valid mapper because the output range is zero; converting to `' + name + ': ' + valueMin.strValue + '`');145 146 return this.parse(name, valueMin.strValue); // can't make much of a mapper without a range147 148 } else if( type.color ){149 let c1 = valueMin.value;150 let c2 = valueMax.value;151 152 let same = c1[0] === c2[0] // red153 && c1[1] === c2[1] // green154 && c1[2] === c2[2] // blue155 && ( // optional alpha156 c1[3] === c2[3] // same alpha outright157 || (158 (c1[3] == null || c1[3] === 1) // full opacity for colour 1?159 &&160 (c2[3] == null || c2[3] === 1) // full opacity for colour 2?161 )162 )163 ;164 165 if( same ){ return false; } // can't make a mapper without a range166 }167 168 return {169 name: name,170 value: mapData,171 strValue: '' + value,172 mapped: mapped,173 field: mapData[1],174 fieldMin: parseFloat( mapData[2] ), // min & max are numeric175 fieldMax: parseFloat( mapData[3] ),176 valueMin: valueMin.value,177 valueMax: valueMax.value,178 bypass: propIsBypass179 };180 }181 182 if( type.multiple && propIsFlat !== 'multiple' ){183 let vals;184 185 if( valueIsString ){186 vals = value.split( /\s+/ );187 } else if( is.array( value ) ){188 vals = value;189 } else {190 vals = [ value ];191 }192 193 if( type.evenMultiple && vals.length % 2 !== 0 ){ return null; }194 195 let valArr = [];196 let unitsArr = [];197 let pfValArr = [];198 let strVal = '';199 let hasEnum = false;200 201 for( let i = 0; i < vals.length; i++ ){202 let p = self.parse( name, vals[i], propIsBypass, 'multiple' );203 204 hasEnum = hasEnum || is.string( p.value );205 206 valArr.push( p.value );207 pfValArr.push( p.pfValue != null ? p.pfValue : p.value );208 unitsArr.push( p.units );209 strVal += (i > 0 ? ' ' : '') + p.strValue;210 }211 212 if( type.validate && !type.validate( valArr, unitsArr ) ){213 return null;214 }215 216 if( type.singleEnum && hasEnum ){217 if( valArr.length === 1 && is.string( valArr[0] ) ){218 return {219 name: name,220 value: valArr[0],221 strValue: valArr[0],222 bypass: propIsBypass223 };224 } else {225 return null;226 }227 }228 229 return {230 name: name,231 value: valArr,232 pfValue: pfValArr,233 strValue: strVal,234 bypass: propIsBypass,235 units: unitsArr236 };237 }238 239 // several types also allow enums240 let checkEnums = function(){241 for( let i = 0; i < type.enums.length; i++ ){242 let en = type.enums[ i ];243 244 if( en === value ){245 return {246 name: name,247 value: value,248 strValue: '' + value,249 bypass: propIsBypass250 };251 }252 }253 254 return null;255 };256 257 // check the type and return the appropriate object258 if( type.number ){259 let units;260 let implicitUnits = 'px'; // not set => px261 262 if( type.units ){ // use specified units if set263 units = type.units;264 }265 266 if( type.implicitUnits ){267 implicitUnits = type.implicitUnits;268 }269 270 if( !type.unitless ){271 if( valueIsString ){272 let unitsRegex = 'px|em' + (type.allowPercent ? '|\\%' : '');273 if( units ){ unitsRegex = units; } // only allow explicit units if so set274 let match = value.match( '^(' + util.regex.number + ')(' + unitsRegex + ')?' + '$' );275 276 if( match ){277 value = match[1];278 units = match[2] || implicitUnits;279 }280 281 } else if( !units || type.implicitUnits ){282 units = implicitUnits; // implicitly px if unspecified283 }284 }285 286 value = parseFloat( value );287 288 // if not a number and enums not allowed, then the value is invalid289 if( isNaN( value ) && type.enums === undefined ){290 return null;291 }292 293 // check if this number type also accepts special keywords in place of numbers294 // (i.e. `left`, `auto`, etc)295 if( isNaN( value ) && type.enums !== undefined ){296 value = passedValue;297 298 return checkEnums();299 }300 301 // check if value must be an integer302 if( type.integer && !is.integer( value ) ){303 return null;304 }305 306 // check value is within range307 if( ( type.min !== undefined && ( value < type.min || (type.strictMin && value === type.min) ) )308 || ( type.max !== undefined && ( value > type.max || (type.strictMax && value === type.max) ) )309 ){310 return null;311 }312 313 let ret = {314 name: name,315 value: value,316 strValue: '' + value + (units ? units : ''),317 units: units,318 bypass: propIsBypass319 };320 321 // normalise value in pixels322 if( type.unitless || (units !== 'px' && units !== 'em') ){323 ret.pfValue = value;324 } else {325 ret.pfValue = ( units === 'px' || !units ? (value) : (this.getEmSizeInPixels() * value) );326 }327 328 // normalise value in ms329 if( units === 'ms' || units === 's' ){330 ret.pfValue = units === 'ms' ? value : 1000 * value;331 }332 333 // normalise value in rad334 if( units === 'deg' || units === 'rad' ){335 ret.pfValue = units === 'rad' ? value : math.deg2rad( value );336 }337 338 // normalize value in %339 if( units === '%' ){340 ret.pfValue = value / 100;341 }342 343 return ret;344 345 } else if( type.propList ){346 347 let props = [];348 let propsStr = '' + value;349 350 if( propsStr === 'none' ){351 // leave empty352 353 } else { // go over each prop354 355 let propsSplit = propsStr.split( /\s*,\s*|\s+/ );356 for( let i = 0; i < propsSplit.length; i++ ){357 let propName = propsSplit[ i ].trim();358 359 if( self.properties[ propName ] ){360 props.push( propName );361 } else {362 util.warn('`' + propName + '` is not a valid property name');363 }364 }365 366 if( props.length === 0 ){ return null; }367 }368 369 return {370 name: name,371 value: props,372 strValue: props.length === 0 ? 'none' : props.join(' '),373 bypass: propIsBypass374 };375 376 } else if( type.color ){377 let tuple = util.color2tuple( value );378 379 if( !tuple ){ return null; }380 381 return {382 name: name,383 value: tuple,384 pfValue: tuple,385 strValue: 'rgb(' + tuple[0] + ',' + tuple[1] + ',' + tuple[2] + ')', // n.b. no spaces b/c of multiple support386 bypass: propIsBypass387 };388 389 } else if( type.regex || type.regexes ){390 391 // first check enums392 if( type.enums ){393 let enumProp = checkEnums();394 395 if( enumProp ){ return enumProp; }396 }397 398 let regexes = type.regexes ? type.regexes : [ type.regex ];399 400 for( let i = 0; i < regexes.length; i++ ){401 let regex = new RegExp( regexes[ i ] ); // make a regex from the type string402 let m = regex.exec( value );403 404 if( m ){ // regex matches405 return {406 name: name,407 value: type.singleRegexMatchValue ? m[1] : m,408 strValue: '' + value,409 bypass: propIsBypass410 };411 412 }413 }414 415 return null; // didn't match any416 417 } else if( type.string ){418 // just return419 return {420 name: name,421 value: '' + value,422 strValue: '' + value,423 bypass: propIsBypass424 };425 426 } else if( type.enums ){ // check enums last because it's a combo type in others427 return checkEnums();428 429 } else {430 return null; // not a type we can handle431 }432 433};434 435export default styfn;436 