src/pyams_skin/resources/js/ext/tinymce/dev/classes/html/Styles.js
changeset 566 a1707c607eec
parent 565 318533413200
child 567 bca1726b1d85
equal deleted inserted replaced
565:318533413200 566:a1707c607eec
     1 /**
       
     2  * Styles.js
       
     3  *
       
     4  * Copyright, Moxiecode Systems AB
       
     5  * Released under LGPL License.
       
     6  *
       
     7  * License: http://www.tinymce.com/license
       
     8  * Contributing: http://www.tinymce.com/contributing
       
     9  */
       
    10 
       
    11 /**
       
    12  * This class is used to parse CSS styles it also compresses styles to reduce the output size.
       
    13  *
       
    14  * @example
       
    15  * var Styles = new tinymce.html.Styles({
       
    16  *    url_converter: function(url) {
       
    17  *       return url;
       
    18  *    }
       
    19  * });
       
    20  *
       
    21  * styles = Styles.parse('border: 1px solid red');
       
    22  * styles.color = 'red';
       
    23  *
       
    24  * console.log(new tinymce.html.StyleSerializer().serialize(styles));
       
    25  *
       
    26  * @class tinymce.html.Styles
       
    27  * @version 3.4
       
    28  */
       
    29 define("tinymce/html/Styles", [], function() {
       
    30 	return function(settings, schema) {
       
    31 		/*jshint maxlen:255 */
       
    32 		/*eslint max-len:0 */
       
    33 		var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,
       
    34 			urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
       
    35 			styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
       
    36 			trimRightRegExp = /\s+$/,
       
    37 			undef, i, encodingLookup = {}, encodingItems, validStyles, invalidStyles, invisibleChar = '\uFEFF';
       
    38 
       
    39 		settings = settings || {};
       
    40 
       
    41 		if (schema) {
       
    42 			validStyles = schema.getValidStyles();
       
    43 			invalidStyles = schema.getInvalidStyles();
       
    44 		}
       
    45 
       
    46 		encodingItems = ('\\" \\\' \\; \\: ; : ' + invisibleChar).split(' ');
       
    47 		for (i = 0; i < encodingItems.length; i++) {
       
    48 			encodingLookup[encodingItems[i]] = invisibleChar + i;
       
    49 			encodingLookup[invisibleChar + i] = encodingItems[i];
       
    50 		}
       
    51 
       
    52 		function toHex(match, r, g, b) {
       
    53 			function hex(val) {
       
    54 				val = parseInt(val, 10).toString(16);
       
    55 
       
    56 				return val.length > 1 ? val : '0' + val; // 0 -> 00
       
    57 			}
       
    58 
       
    59 			return '#' + hex(r) + hex(g) + hex(b);
       
    60 		}
       
    61 
       
    62 		return {
       
    63 			/**
       
    64 			 * Parses the specified RGB color value and returns a hex version of that color.
       
    65 			 *
       
    66 			 * @method toHex
       
    67 			 * @param {String} color RGB string value like rgb(1,2,3)
       
    68 			 * @return {String} Hex version of that RGB value like #FF00FF.
       
    69 			 */
       
    70 			toHex: function(color) {
       
    71 				return color.replace(rgbRegExp, toHex);
       
    72 			},
       
    73 
       
    74 			/**
       
    75 			 * Parses the specified style value into an object collection. This parser will also
       
    76 			 * merge and remove any redundant items that browsers might have added. It will also convert non hex
       
    77 			 * colors to hex values. Urls inside the styles will also be converted to absolute/relative based on settings.
       
    78 			 *
       
    79 			 * @method parse
       
    80 			 * @param {String} css Style value to parse for example: border:1px solid red;.
       
    81 			 * @return {Object} Object representation of that style like {border: '1px solid red'}
       
    82 			 */
       
    83 			parse: function(css) {
       
    84 				var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter;
       
    85 				var urlConverterScope = settings.url_converter_scope || this;
       
    86 
       
    87 				function compress(prefix, suffix, noJoin) {
       
    88 					var top, right, bottom, left;
       
    89 
       
    90 					top = styles[prefix + '-top' + suffix];
       
    91 					if (!top) {
       
    92 						return;
       
    93 					}
       
    94 
       
    95 					right = styles[prefix + '-right' + suffix];
       
    96 					if (!right) {
       
    97 						return;
       
    98 					}
       
    99 
       
   100 					bottom = styles[prefix + '-bottom' + suffix];
       
   101 					if (!bottom) {
       
   102 						return;
       
   103 					}
       
   104 
       
   105 					left = styles[prefix + '-left' + suffix];
       
   106 					if (!left) {
       
   107 						return;
       
   108 					}
       
   109 
       
   110 					var box = [top, right, bottom, left];
       
   111 					i = box.length - 1;
       
   112 					while (i--) {
       
   113 						if (box[i] !== box[i + 1]) {
       
   114 							break;
       
   115 						}
       
   116 					}
       
   117 
       
   118 					if (i > -1 && noJoin) {
       
   119 						return;
       
   120 					}
       
   121 
       
   122 					styles[prefix + suffix] = i == -1 ? box[0] : box.join(' ');
       
   123 					delete styles[prefix + '-top' + suffix];
       
   124 					delete styles[prefix + '-right' + suffix];
       
   125 					delete styles[prefix + '-bottom' + suffix];
       
   126 					delete styles[prefix + '-left' + suffix];
       
   127 				}
       
   128 
       
   129 				/**
       
   130 				 * Checks if the specific style can be compressed in other words if all border-width are equal.
       
   131 				 */
       
   132 				function canCompress(key) {
       
   133 					var value = styles[key], i;
       
   134 
       
   135 					if (!value) {
       
   136 						return;
       
   137 					}
       
   138 
       
   139 					value = value.split(' ');
       
   140 					i = value.length;
       
   141 					while (i--) {
       
   142 						if (value[i] !== value[0]) {
       
   143 							return false;
       
   144 						}
       
   145 					}
       
   146 
       
   147 					styles[key] = value[0];
       
   148 
       
   149 					return true;
       
   150 				}
       
   151 
       
   152 				/**
       
   153 				 * Compresses multiple styles into one style.
       
   154 				 */
       
   155 				function compress2(target, a, b, c) {
       
   156 					if (!canCompress(a)) {
       
   157 						return;
       
   158 					}
       
   159 
       
   160 					if (!canCompress(b)) {
       
   161 						return;
       
   162 					}
       
   163 
       
   164 					if (!canCompress(c)) {
       
   165 						return;
       
   166 					}
       
   167 
       
   168 					// Compress
       
   169 					styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];
       
   170 					delete styles[a];
       
   171 					delete styles[b];
       
   172 					delete styles[c];
       
   173 				}
       
   174 
       
   175 				// Encodes the specified string by replacing all \" \' ; : with _<num>
       
   176 				function encode(str) {
       
   177 					isEncoded = true;
       
   178 
       
   179 					return encodingLookup[str];
       
   180 				}
       
   181 
       
   182 				// Decodes the specified string by replacing all _<num> with it's original value \" \' etc
       
   183 				// It will also decode the \" \' if keep_slashes is set to fale or omitted
       
   184 				function decode(str, keep_slashes) {
       
   185 					if (isEncoded) {
       
   186 						str = str.replace(/\uFEFF[0-9]/g, function(str) {
       
   187 							return encodingLookup[str];
       
   188 						});
       
   189 					}
       
   190 
       
   191 					if (!keep_slashes) {
       
   192 						str = str.replace(/\\([\'\";:])/g, "$1");
       
   193 					}
       
   194 
       
   195 					return str;
       
   196 				}
       
   197 
       
   198 				function processUrl(match, url, url2, url3, str, str2) {
       
   199 					str = str || str2;
       
   200 
       
   201 					if (str) {
       
   202 						str = decode(str);
       
   203 
       
   204 						// Force strings into single quote format
       
   205 						return "'" + str.replace(/\'/g, "\\'") + "'";
       
   206 					}
       
   207 
       
   208 					url = decode(url || url2 || url3);
       
   209 
       
   210 					if (!settings.allow_script_urls) {
       
   211 						var scriptUrl = url.replace(/[\s\r\n]+/, '');
       
   212 
       
   213 						if (/(java|vb)script:/i.test(scriptUrl)) {
       
   214 							return "";
       
   215 						}
       
   216 
       
   217 						if (!settings.allow_svg_data_urls && /^data:image\/svg/i.test(scriptUrl)) {
       
   218 							return "";
       
   219 						}
       
   220 					}
       
   221 
       
   222 					// Convert the URL to relative/absolute depending on config
       
   223 					if (urlConverter) {
       
   224 						url = urlConverter.call(urlConverterScope, url, 'style');
       
   225 					}
       
   226 
       
   227 					// Output new URL format
       
   228 					return "url('" + url.replace(/\'/g, "\\'") + "')";
       
   229 				}
       
   230 
       
   231 				if (css) {
       
   232 					css = css.replace(/[\u0000-\u001F]/g, '');
       
   233 
       
   234 					// Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
       
   235 					css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
       
   236 						return str.replace(/[;:]/g, encode);
       
   237 					});
       
   238 
       
   239 					// Parse styles
       
   240 					while ((matches = styleRegExp.exec(css))) {
       
   241 						name = matches[1].replace(trimRightRegExp, '').toLowerCase();
       
   242 						value = matches[2].replace(trimRightRegExp, '');
       
   243 
       
   244 						// Decode escaped sequences like \65 -> e
       
   245 						/*jshint loopfunc:true*/
       
   246 						/*eslint no-loop-func:0 */
       
   247 						value = value.replace(/\\[0-9a-f]+/g, function(e) {
       
   248 							return String.fromCharCode(parseInt(e.substr(1), 16));
       
   249 						});
       
   250 
       
   251 						if (name && value.length > 0) {
       
   252 							// Don't allow behavior name or expression/comments within the values
       
   253 							if (!settings.allow_script_urls && (name == "behavior" || /expression\s*\(|\/\*|\*\//.test(value))) {
       
   254 								continue;
       
   255 							}
       
   256 
       
   257 							// Opera will produce 700 instead of bold in their style values
       
   258 							if (name === 'font-weight' && value === '700') {
       
   259 								value = 'bold';
       
   260 							} else if (name === 'color' || name === 'background-color') { // Lowercase colors like RED
       
   261 								value = value.toLowerCase();
       
   262 							}
       
   263 
       
   264 							// Convert RGB colors to HEX
       
   265 							value = value.replace(rgbRegExp, toHex);
       
   266 
       
   267 							// Convert URLs and force them into url('value') format
       
   268 							value = value.replace(urlOrStrRegExp, processUrl);
       
   269 							styles[name] = isEncoded ? decode(value, true) : value;
       
   270 						}
       
   271 
       
   272 						styleRegExp.lastIndex = matches.index + matches[0].length;
       
   273 					}
       
   274 					// Compress the styles to reduce it's size for example IE will expand styles
       
   275 					compress("border", "", true);
       
   276 					compress("border", "-width");
       
   277 					compress("border", "-color");
       
   278 					compress("border", "-style");
       
   279 					compress("padding", "");
       
   280 					compress("margin", "");
       
   281 					compress2('border', 'border-width', 'border-style', 'border-color');
       
   282 
       
   283 					// Remove pointless border, IE produces these
       
   284 					if (styles.border === 'medium none') {
       
   285 						delete styles.border;
       
   286 					}
       
   287 
       
   288 					// IE 11 will produce a border-image: none when getting the style attribute from <p style="border: 1px solid red"></p>
       
   289 					// So lets asume it shouldn't be there
       
   290 					if (styles['border-image'] === 'none') {
       
   291 						delete styles['border-image'];
       
   292 					}
       
   293 				}
       
   294 
       
   295 				return styles;
       
   296 			},
       
   297 
       
   298 			/**
       
   299 			 * Serializes the specified style object into a string.
       
   300 			 *
       
   301 			 * @method serialize
       
   302 			 * @param {Object} styles Object to serialize as string for example: {border: '1px solid red'}
       
   303 			 * @param {String} elementName Optional element name, if specified only the styles that matches the schema will be serialized.
       
   304 			 * @return {String} String representation of the style object for example: border: 1px solid red.
       
   305 			 */
       
   306 			serialize: function(styles, elementName) {
       
   307 				var css = '', name, value;
       
   308 
       
   309 				function serializeStyles(name) {
       
   310 					var styleList, i, l, value;
       
   311 
       
   312 					styleList = validStyles[name];
       
   313 					if (styleList) {
       
   314 						for (i = 0, l = styleList.length; i < l; i++) {
       
   315 							name = styleList[i];
       
   316 							value = styles[name];
       
   317 
       
   318 							if (value !== undef && value.length > 0) {
       
   319 								css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
       
   320 							}
       
   321 						}
       
   322 					}
       
   323 				}
       
   324 
       
   325 				function isValid(name, elementName) {
       
   326 					var styleMap;
       
   327 
       
   328 					styleMap = invalidStyles['*'];
       
   329 					if (styleMap && styleMap[name]) {
       
   330 						return false;
       
   331 					}
       
   332 
       
   333 					styleMap = invalidStyles[elementName];
       
   334 					if (styleMap && styleMap[name]) {
       
   335 						return false;
       
   336 					}
       
   337 
       
   338 					return true;
       
   339 				}
       
   340 
       
   341 				// Serialize styles according to schema
       
   342 				if (elementName && validStyles) {
       
   343 					// Serialize global styles and element specific styles
       
   344 					serializeStyles('*');
       
   345 					serializeStyles(elementName);
       
   346 				} else {
       
   347 					// Output the styles in the order they are inside the object
       
   348 					for (name in styles) {
       
   349 						value = styles[name];
       
   350 
       
   351 						if (value !== undef && value.length > 0) {
       
   352 							if (!invalidStyles || isValid(name, elementName)) {
       
   353 								css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
       
   354 							}
       
   355 						}
       
   356 					}
       
   357 				}
       
   358 
       
   359 				return css;
       
   360 			}
       
   361 		};
       
   362 	};
       
   363 });