4 * Copyright 2010, Moxiecode Systems AB
5 * Released under LGPL License.
7 * License: http://tinymce.moxiecode.com/license
8 * Contributing: http://tinymce.moxiecode.com/contributing
12 * This class is used to parse CSS styles it also compresses styles to reduce the output size.
15 * var Styles = new tinymce.html.Styles({
16 * url_converter: function(url) {
21 * styles = Styles.parse('border: 1px solid red');
22 * styles.color = 'red';
24 * console.log(new tinymce.html.StyleSerializer().serialize(styles));
26 * @class tinymce.html.Styles
29 tinymce.html.Styles = function(settings, schema) {
30 var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,
31 urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
32 styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
33 trimRightRegExp = /\s+$/,
34 urlColorRegExp = /rgb/,
35 undef, i, encodingLookup = {}, encodingItems;
37 settings = settings || {};
39 encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' ');
40 for (i = 0; i < encodingItems.length; i++) {
41 encodingLookup[encodingItems[i]] = '\uFEFF' + i;
42 encodingLookup['\uFEFF' + i] = encodingItems[i];
45 function toHex(match, r, g, b) {
47 val = parseInt(val).toString(16);
49 return val.length > 1 ? val : '0' + val; // 0 -> 00
52 return '#' + hex(r) + hex(g) + hex(b);
57 * Parses the specified RGB color value and returns a hex version of that color.
60 * @param {String} color RGB string value like rgb(1,2,3)
61 * @return {String} Hex version of that RGB value like #FF00FF.
63 toHex : function(color) {
64 return color.replace(rgbRegExp, toHex);
68 * Parses the specified style value into an object collection. This parser will also
69 * merge and remove any redundant items that browsers might have added. It will also convert non hex
70 * colors to hex values. Urls inside the styles will also be converted to absolute/relative based on settings.
73 * @param {String} css Style value to parse for example: border:1px solid red;.
74 * @return {Object} Object representation of that style like {border : '1px solid red'}
76 parse : function(css) {
77 var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this;
79 function compress(prefix, suffix) {
80 var top, right, bottom, left;
82 // Get values and check it it needs compressing
83 top = styles[prefix + '-top' + suffix];
87 right = styles[prefix + '-right' + suffix];
91 bottom = styles[prefix + '-bottom' + suffix];
95 left = styles[prefix + '-left' + suffix];
100 styles[prefix + suffix] = left;
101 delete styles[prefix + '-top' + suffix];
102 delete styles[prefix + '-right' + suffix];
103 delete styles[prefix + '-bottom' + suffix];
104 delete styles[prefix + '-left' + suffix];
108 * Checks if the specific style can be compressed in other words if all border-width are equal.
110 function canCompress(key) {
111 var value = styles[key], i;
113 if (!value || value.indexOf(' ') < 0)
116 value = value.split(' ');
119 if (value[i] !== value[0])
123 styles[key] = value[0];
129 * Compresses multiple styles into one style.
131 function compress2(target, a, b, c) {
142 styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];
148 // Encodes the specified string by replacing all \" \' ; : with _<num>
149 function encode(str) {
152 return encodingLookup[str];
155 // Decodes the specified string by replacing all _<num> with it's original value \" \' etc
156 // It will also decode the \" \' if keep_slashes is set to fale or omitted
157 function decode(str, keep_slashes) {
159 str = str.replace(/\uFEFF[0-9]/g, function(str) {
160 return encodingLookup[str];
165 str = str.replace(/\\([\'\";:])/g, "$1");
171 // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
172 css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
173 return str.replace(/[;:]/g, encode);
177 while (matches = styleRegExp.exec(css)) {
178 name = matches[1].replace(trimRightRegExp, '').toLowerCase();
179 value = matches[2].replace(trimRightRegExp, '');
181 if (name && value.length > 0) {
182 // Opera will produce 700 instead of bold in their style values
183 if (name === 'font-weight' && value === '700')
185 else if (name === 'color' || name === 'background-color') // Lowercase colors like RED
186 value = value.toLowerCase();
188 // Convert RGB colors to HEX
189 value = value.replace(rgbRegExp, toHex);
191 // Convert URLs and force them into url('value') format
192 value = value.replace(urlOrStrRegExp, function(match, url, url2, url3, str, str2) {
198 // Force strings into single quote format
199 return "'" + str.replace(/\'/g, "\\'") + "'";
202 url = decode(url || url2 || url3);
204 // Convert the URL to relative/absolute depending on config
206 url = urlConverter.call(urlConverterScope, url, 'style');
208 // Output new URL format
209 return "url('" + url.replace(/\'/g, "\\'") + "')";
212 styles[name] = isEncoded ? decode(value, true) : value;
215 styleRegExp.lastIndex = matches.index + matches[0].length;
218 // Compress the styles to reduce it's size for example IE will expand styles
219 compress("border", "");
220 compress("border", "-width");
221 compress("border", "-color");
222 compress("border", "-style");
223 compress("padding", "");
224 compress("margin", "");
225 compress2('border', 'border-width', 'border-style', 'border-color');
227 // Remove pointless border, IE produces these
228 if (styles.border === 'medium none')
229 delete styles.border;
236 * Serializes the specified style object into a string.
239 * @param {Object} styles Object to serialize as string for example: {border : '1px solid red'}
240 * @param {String} element_name Optional element name, if specified only the styles that matches the schema will be serialized.
241 * @return {String} String representation of the style object for example: border: 1px solid red.
243 serialize : function(styles, element_name) {
244 var css = '', name, value;
246 function serializeStyles(name) {
247 var styleList, i, l, value;
249 styleList = schema.styles[name];
251 for (i = 0, l = styleList.length; i < l; i++) {
253 value = styles[name];
255 if (value !== undef && value.length > 0)
256 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
261 // Serialize styles according to schema
262 if (element_name && schema && schema.styles) {
263 // Serialize global styles and element specific styles
264 serializeStyles('*');
265 serializeStyles(element_name);
267 // Output the styles in the order they are inside the object
268 for (name in styles) {
269 value = styles[name];
271 if (value !== undef && value.length > 0)
272 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';