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