]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/javascript/tiny_mce/classes/dom/Serializer.js
Release 6.5.0
[Github/sugarcrm.git] / include / javascript / tiny_mce / classes / dom / Serializer.js
1 /**
2  * Serializer.js
3  *
4  * Copyright 2009, Moxiecode Systems AB
5  * Released under LGPL License.
6  *
7  * License: http://tinymce.moxiecode.com/license
8  * Contributing: http://tinymce.moxiecode.com/contributing
9  */
10
11 (function(tinymce) {
12         /**
13          * This class is used to serialize DOM trees into a string. Consult the TinyMCE Wiki API for more details and examples on how to use this class. 
14          *
15          * @class tinymce.dom.Serializer
16          */
17
18         /**
19          * Constucts a new DOM serializer class.
20          *
21          * @constructor
22          * @method Serializer
23          * @param {Object} settings Serializer settings object.
24          * @param {tinymce.dom.DOMUtils} dom DOMUtils instance reference.
25          * @param {tinymce.html.Schema} schema Optional schema reference.
26          */
27         tinymce.dom.Serializer = function(settings, dom, schema) {
28                 var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser;
29
30                 // Support the old apply_source_formatting option
31                 if (!settings.apply_source_formatting)
32                         settings.indent = false;
33
34                 settings.remove_trailing_brs = true;
35
36                 // Default DOM and Schema if they are undefined
37                 dom = dom || tinymce.DOM;
38                 schema = schema || new tinymce.html.Schema(settings);
39                 settings.entity_encoding = settings.entity_encoding || 'named';
40
41                 /**
42                  * This event gets executed before a HTML fragment gets serialized into a HTML string. This event enables you to do modifications to the DOM before the serialization occurs. It's important to know that the element that is getting serialized is cloned so it's not inside a document.
43                  *
44                  * @event onPreProcess
45                  * @param {tinymce.dom.Serializer} sender object/Serializer instance that is serializing an element.
46                  * @param {Object} args Object containing things like the current node.
47                  * @example
48                  * // Adds an observer to the onPreProcess event
49                  * serializer.onPreProcess.add(function(se, o) {
50                  *     // Add a class to each paragraph
51                  *     se.dom.addClass(se.dom.select('p', o.node), 'myclass');
52                  * });
53                  */
54                 onPreProcess = new tinymce.util.Dispatcher(self);
55
56                 /**
57                  * This event gets executed after a HTML fragment has been serialized into a HTML string. This event enables you to do modifications to the HTML string like regexp replaces etc. 
58                  *
59                  * @event onPreProcess
60                  * @param {tinymce.dom.Serializer} sender object/Serializer instance that is serializing an element.
61                  * @param {Object} args Object containing things like the current contents. 
62                  * @example
63                  * // Adds an observer to the onPostProcess event
64                  * serializer.onPostProcess.add(function(se, o) {
65                  *    // Remove all paragraphs and replace with BR
66                  *    o.content = o.content.replace(/<p[^>]+>|<p>/g, '');
67                  *    o.content = o.content.replace(/<\/p>/g, '<br />');
68                  * });
69                  */
70                 onPostProcess = new tinymce.util.Dispatcher(self);
71
72                 htmlParser = new tinymce.html.DomParser(settings, schema);
73
74                 // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed
75                 htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {
76                         var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef;
77
78                         while (i--) {
79                                 node = nodes[i];
80
81                                 value = node.attributes.map[internalName];
82                                 if (value !== undef) {
83                                         // Set external name to internal value and remove internal
84                                         node.attr(name, value.length > 0 ? value : null);
85                                         node.attr(internalName, null);
86                                 } else {
87                                         // No internal attribute found then convert the value we have in the DOM
88                                         value = node.attributes.map[name];
89
90                                         if (name === "style")
91                                                 value = dom.serializeStyle(dom.parseStyle(value), node.name);
92                                         else if (urlConverter)
93                                                 value = urlConverter.call(urlConverterScope, value, name, node.name);
94
95                                         node.attr(name, value.length > 0 ? value : null);
96                                 }
97                         }
98                 });
99
100                 // Remove internal classes mceItem<..>
101                 htmlParser.addAttributeFilter('class', function(nodes, name) {
102                         var i = nodes.length, node, value;
103
104                         while (i--) {
105                                 node = nodes[i];
106                                 value = node.attr('class').replace(/\s*mce(Item\w+|Selected)\s*/g, '');
107                                 node.attr('class', value.length > 0 ? value : null);
108                         }
109                 });
110
111                 // Remove bookmark elements
112                 htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) {
113                         var i = nodes.length, node;
114
115                         while (i--) {
116                                 node = nodes[i];
117
118                                 if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup)
119                                         node.remove();
120                         }
121                 });
122
123                 // Force script into CDATA sections and remove the mce- prefix also add comments around styles
124                 htmlParser.addNodeFilter('script,style', function(nodes, name) {
125                         var i = nodes.length, node, value;
126
127                         function trim(value) {
128                                 return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
129                                                 .replace(/^[\r\n]*|[\r\n]*$/g, '')
130                                                 .replace(/^\s*(\/\/\s*<!--|\/\/\s*<!\[CDATA\[|<!--|<!\[CDATA\[)[\r\n]*/g, '')
131                                                 .replace(/\s*(\/\/\s*\]\]>|\/\/\s*-->|\]\]>|-->|\]\]-->)\s*$/g, '');
132                         };
133
134                         while (i--) {
135                                 node = nodes[i];
136                                 value = node.firstChild ? node.firstChild.value : '';
137
138                                 if (name === "script") {
139                                         // Remove mce- prefix from script elements
140                                         node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, ''));
141
142                                         if (value.length > 0)
143                                                 node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';
144                                 } else {
145                                         if (value.length > 0)
146                                                 node.firstChild.value = '<!--\n' + trim(value) + '\n-->';
147                                 }
148                         }
149                 });
150
151                 // Convert comments to cdata and handle protected comments
152                 htmlParser.addNodeFilter('#comment', function(nodes, name) {
153                         var i = nodes.length, node;
154
155                         while (i--) {
156                                 node = nodes[i];
157
158                                 if (node.value.indexOf('[CDATA[') === 0) {
159                                         node.name = '#cdata';
160                                         node.type = 4;
161                                         node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, '');
162                                 } else if (node.value.indexOf('mce:protected ') === 0) {
163                                         node.name = "#text";
164                                         node.type = 3;
165                                         node.raw = true;
166                                         node.value = unescape(node.value).substr(14);
167                                 }
168                         }
169                 });
170
171                 htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) {
172                         var i = nodes.length, node;
173
174                         while (i--) {
175                                 node = nodes[i];
176                                 if (node.type === 7)
177                                         node.remove();
178                                 else if (node.type === 1) {
179                                         if (name === "input" && !("type" in node.attributes.map))
180                                                 node.attr('type', 'text');
181                                 }
182                         }
183                 });
184
185                 // Fix list elements, TODO: Replace this later
186                 if (settings.fix_list_elements) {
187                         htmlParser.addNodeFilter('ul,ol', function(nodes, name) {
188                                 var i = nodes.length, node, parentNode;
189
190                                 while (i--) {
191                                         node = nodes[i];
192                                         parentNode = node.parent;
193
194                                         if (parentNode.name === 'ul' || parentNode.name === 'ol') {
195                                                 if (node.prev && node.prev.name === 'li') {
196                                                         node.prev.append(node);
197                                                 }
198                                         }
199                                 }
200                         });
201                 }
202
203                 // Remove internal data attributes
204                 htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) {
205                         var i = nodes.length;
206
207                         while (i--) {
208                                 nodes[i].attr(name, null);
209                         }
210                 });
211
212                 // Return public methods
213                 return {
214                         /**
215                          * Schema instance that was used to when the Serializer was constructed.
216                          *
217                          * @field {tinymce.html.Schema} schema
218                          */
219                         schema : schema,
220
221                         /**
222                          * Adds a node filter function to the parser used by the serializer, the parser will collect the specified nodes by name
223                          * and then execute the callback ones it has finished parsing the document.
224                          *
225                          * @example
226                          * parser.addNodeFilter('p,h1', function(nodes, name) {
227                          *              for (var i = 0; i < nodes.length; i++) {
228                          *                      console.log(nodes[i].name);
229                          *              }
230                          * });
231                          * @method addNodeFilter
232                          * @method {String} name Comma separated list of nodes to collect.
233                          * @param {function} callback Callback function to execute once it has collected nodes.
234                          */
235                         addNodeFilter : htmlParser.addNodeFilter,
236
237                         /**
238                          * Adds a attribute filter function to the parser used by the serializer, the parser will collect nodes that has the specified attributes
239                          * and then execute the callback ones it has finished parsing the document.
240                          *
241                          * @example
242                          * parser.addAttributeFilter('src,href', function(nodes, name) {
243                          *              for (var i = 0; i < nodes.length; i++) {
244                          *                      console.log(nodes[i].name);
245                          *              }
246                          * });
247                          * @method addAttributeFilter
248                          * @method {String} name Comma separated list of nodes to collect.
249                          * @param {function} callback Callback function to execute once it has collected nodes.
250                          */
251                         addAttributeFilter : htmlParser.addAttributeFilter,
252
253                         /**
254                          * Fires when the Serializer does a preProcess on the contents.
255                          *
256                          * @event onPreProcess
257                          * @param {tinymce.Editor} sender Editor instance.
258                          * @param {Object} obj PreProcess object.
259                          * @option {Node} node DOM node for the item being serialized.
260                          * @option {String} format The specified output format normally "html".
261                          * @option {Boolean} get Is true if the process is on a getContent operation.
262                          * @option {Boolean} set Is true if the process is on a setContent operation.
263                          * @option {Boolean} cleanup Is true if the process is on a cleanup operation.
264                          */
265                         onPreProcess : onPreProcess,
266
267                         /**
268                          * Fires when the Serializer does a postProcess on the contents.
269                          *
270                          * @event onPostProcess
271                          * @param {tinymce.Editor} sender Editor instance.
272                          * @param {Object} obj PreProcess object.
273                          */
274                         onPostProcess : onPostProcess,
275
276                         /**
277                          * Serializes the specified browser DOM node into a HTML string.
278                          *
279                          * @method serialize
280                          * @param {DOMNode} node DOM node to serialize.
281                          * @param {Object} args Arguments option that gets passed to event handlers.
282                          */
283                         serialize : function(node, args) {
284                                 var impl, doc, oldDoc, htmlSerializer, content;
285
286                                 // Explorer won't clone contents of script and style and the
287                                 // selected index of select elements are cleared on a clone operation.
288                                 if (isIE && dom.select('script,style,select').length > 0) {
289                                         content = node.innerHTML;
290                                         node = node.cloneNode(false);
291                                         dom.setHTML(node, content);
292                                 } else
293                                         node = node.cloneNode(true);
294
295                                 // Nodes needs to be attached to something in WebKit/Opera
296                                 // Older builds of Opera crashes if you attach the node to an document created dynamically
297                                 // and since we can't feature detect a crash we need to sniff the acutal build number
298                                 // This fix will make DOM ranges and make Sizzle happy!
299                                 impl = node.ownerDocument.implementation;
300                                 if (impl.createHTMLDocument) {
301                                         // Create an empty HTML document
302                                         doc = impl.createHTMLDocument("");
303
304                                         // Add the element or it's children if it's a body element to the new document
305                                         each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) {
306                                                 doc.body.appendChild(doc.importNode(node, true));
307                                         });
308
309                                         // Grab first child or body element for serialization
310                                         if (node.nodeName != 'BODY')
311                                                 node = doc.body.firstChild;
312                                         else
313                                                 node = doc.body;
314
315                                         // set the new document in DOMUtils so createElement etc works
316                                         oldDoc = dom.doc;
317                                         dom.doc = doc;
318                                 }
319
320                                 args = args || {};
321                                 args.format = args.format || 'html';
322
323                                 // Pre process
324                                 if (!args.no_events) {
325                                         args.node = node;
326                                         onPreProcess.dispatch(self, args);
327                                 }
328
329                                 // Setup serializer
330                                 htmlSerializer = new tinymce.html.Serializer(settings, schema);
331
332                                 // Parse and serialize HTML
333                                 args.content = htmlSerializer.serialize(
334                                         htmlParser.parse(args.getInner ? node.innerHTML : tinymce.trim(dom.getOuterHTML(node), args), args)
335                                 );
336
337                                 // Replace all BOM characters for now until we can find a better solution
338                                 if (!args.cleanup)
339                                         args.content = args.content.replace(/\uFEFF/g, '');
340
341                                 // Post process
342                                 if (!args.no_events)
343                                         onPostProcess.dispatch(self, args);
344
345                                 // Restore the old document if it was changed
346                                 if (oldDoc)
347                                         dom.doc = oldDoc;
348
349                                 args.node = null;
350
351                                 return args.content;
352                         },
353
354                         /**
355                          * Adds valid elements rules to the serializers schema instance this enables you to specify things
356                          * like what elements should be outputted and what attributes specific elements might have.
357                          * Consult the Wiki for more details on this format.
358                          *
359                          * @method addRules
360                          * @param {String} rules Valid elements rules string to add to schema.
361                          */
362                         addRules : function(rules) {
363                                 schema.addValidElements(rules);
364                         },
365
366                         /**
367                          * Sets the valid elements rules to the serializers schema instance this enables you to specify things
368                          * like what elements should be outputted and what attributes specific elements might have.
369                          * Consult the Wiki for more details on this format.
370                          *
371                          * @method setRules
372                          * @param {String} rules Valid elements rules string.
373                          */
374                         setRules : function(rules) {
375                                 schema.setValidElements(rules);
376                         }
377                 };
378         };
379 })(tinymce);