4 * Copyright, Moxiecode Systems AB
5 * Released under LGPL License.
7 * License: http://www.tinymce.com/license
8 * Contributing: http://www.tinymce.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 define("tinymce/html/Styles", [], function() {
30 return function(settings, schema) {
31 /*jshint maxlen:255 */
32 var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,
33 urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
34 styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
35 trimRightRegExp = /\s+$/,
36 undef, i, encodingLookup = {}, encodingItems, invisibleChar = '\uFEFF';
38 settings = settings || {};
40 encodingItems = ('\\" \\\' \\; \\: ; : ' + invisibleChar).split(' ');
41 for (i = 0; i < encodingItems.length; i++) {
42 encodingLookup[encodingItems[i]] = invisibleChar + i;
43 encodingLookup[invisibleChar + i] = encodingItems[i];
46 function toHex(match, r, g, b) {
48 val = parseInt(val, 10).toString(16);
50 return val.length > 1 ? val : '0' + val; // 0 -> 00
53 return '#' + hex(r) + hex(g) + hex(b);
58 * Parses the specified RGB color value and returns a hex version of that color.
61 * @param {String} color RGB string value like rgb(1,2,3)
62 * @return {String} Hex version of that RGB value like #FF00FF.
64 toHex: function(color) {
65 return color.replace(rgbRegExp, toHex);
69 * Parses the specified style value into an object collection. This parser will also
70 * merge and remove any redundant items that browsers might have added. It will also convert non hex
71 * colors to hex values. Urls inside the styles will also be converted to absolute/relative based on settings.
74 * @param {String} css Style value to parse for example: border:1px solid red;.
75 * @return {Object} Object representation of that style like {border: '1px solid red'}
77 parse: function(css) {
78 var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this;
80 function compress(prefix, suffix) {
81 var top, right, bottom, left;
83 // Get values and check it it needs compressing
84 top = styles[prefix + '-top' + suffix];
89 right = styles[prefix + '-right' + suffix];
94 bottom = styles[prefix + '-bottom' + suffix];
95 if (right != bottom) {
99 left = styles[prefix + '-left' + suffix];
100 if (bottom != left) {
105 styles[prefix + suffix] = left;
106 delete styles[prefix + '-top' + suffix];
107 delete styles[prefix + '-right' + suffix];
108 delete styles[prefix + '-bottom' + suffix];
109 delete styles[prefix + '-left' + suffix];
113 * Checks if the specific style can be compressed in other words if all border-width are equal.
115 function canCompress(key) {
116 var value = styles[key], i;
118 if (!value || value.indexOf(' ') < 0) {
122 value = value.split(' ');
125 if (value[i] !== value[0]) {
130 styles[key] = value[0];
136 * Compresses multiple styles into one style.
138 function compress2(target, a, b, c) {
139 if (!canCompress(a)) {
143 if (!canCompress(b)) {
147 if (!canCompress(c)) {
152 styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];
158 // Encodes the specified string by replacing all \" \' ; : with _<num>
159 function encode(str) {
162 return encodingLookup[str];
165 // Decodes the specified string by replacing all _<num> with it's original value \" \' etc
166 // It will also decode the \" \' if keep_slashes is set to fale or omitted
167 function decode(str, keep_slashes) {
169 str = str.replace(/\uFEFF[0-9]/g, function(str) {
170 return encodingLookup[str];
175 str = str.replace(/\\([\'\";:])/g, "$1");
181 function processUrl(match, url, url2, url3, str, str2) {
187 // Force strings into single quote format
188 return "'" + str.replace(/\'/g, "\\'") + "'";
191 url = decode(url || url2 || url3);
193 // Convert the URL to relative/absolute depending on config
195 url = urlConverter.call(urlConverterScope, url, 'style');
198 // Output new URL format
199 return "url('" + url.replace(/\'/g, "\\'") + "')";
203 // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
204 css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
205 return str.replace(/[;:]/g, encode);
209 while ((matches = styleRegExp.exec(css))) {
210 name = matches[1].replace(trimRightRegExp, '').toLowerCase();
211 value = matches[2].replace(trimRightRegExp, '');
213 if (name && value.length > 0) {
214 // Opera will produce 700 instead of bold in their style values
215 if (name === 'font-weight' && value === '700') {
217 } else if (name === 'color' || name === 'background-color') { // Lowercase colors like RED
218 value = value.toLowerCase();
221 // Convert RGB colors to HEX
222 value = value.replace(rgbRegExp, toHex);
224 // Convert URLs and force them into url('value') format
225 value = value.replace(urlOrStrRegExp, processUrl);
226 styles[name] = isEncoded ? decode(value, true) : value;
229 styleRegExp.lastIndex = matches.index + matches[0].length;
232 // Compress the styles to reduce it's size for example IE will expand styles
233 compress("border", "");
234 compress("border", "-width");
235 compress("border", "-color");
236 compress("border", "-style");
237 compress("padding", "");
238 compress("margin", "");
239 compress2('border', 'border-width', 'border-style', 'border-color');
241 // Remove pointless border, IE produces these
242 if (styles.border === 'medium none') {
243 delete styles.border;
251 * Serializes the specified style object into a string.
254 * @param {Object} styles Object to serialize as string for example: {border: '1px solid red'}
255 * @param {String} element_name Optional element name, if specified only the styles that matches the schema will be serialized.
256 * @return {String} String representation of the style object for example: border: 1px solid red.
258 serialize: function(styles, element_name) {
259 var css = '', name, value;
261 function serializeStyles(name) {
262 var styleList, i, l, value;
264 styleList = schema.styles[name];
266 for (i = 0, l = styleList.length; i < l; i++) {
268 value = styles[name];
270 if (value !== undef && value.length > 0) {
271 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
277 // Serialize styles according to schema
278 if (element_name && schema && schema.styles) {
279 // Serialize global styles and element specific styles
280 serializeStyles('*');
281 serializeStyles(element_name);
283 // Output the styles in the order they are inside the object
284 for (name in styles) {
285 value = styles[name];
287 if (value !== undef && value.length > 0) {
288 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';