2 Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.
3 For licensing, see LICENSE.html or http://ckeditor.com/license
8 // Regex to scan for at the end of blocks, which are actually placeholders.
9 // Safari transforms the to \xa0. (#4172)
10 var tailNbspRegex
= /^[\t\r\n ]*(?: |\xa0)$/;
12 var protectedSourceMarker
= '{cke_protected}';
14 // Return the last non-space child node of the block (#4344).
15 function lastNoneSpaceChild( block
)
17 var lastIndex
= block
.children
.length
,
18 last
= block
.children
[ lastIndex
- 1 ];
19 while ( last
&& last
.type
== CKEDITOR
.NODE_TEXT
&& !CKEDITOR
.tools
.trim( last
.value
) )
20 last
= block
.children
[ --lastIndex
];
24 function trimFillers( block
, fromSource
)
26 // If the current node is a block, and if we're converting from source or
27 // we're not in IE then search for and remove any tailing BR node.
29 // Also, any at the end of blocks are fillers, remove them as well.
31 var children
= block
.children
, lastChild
= lastNoneSpaceChild( block
);
34 if ( ( fromSource
|| !CKEDITOR
.env
.ie
) && lastChild
.type
== CKEDITOR
.NODE_ELEMENT
&& lastChild
.name
== 'br' )
36 if ( lastChild
.type
== CKEDITOR
.NODE_TEXT
&& tailNbspRegex
.test( lastChild
.value
) )
41 function blockNeedsExtension( block
, fromSource
, extendEmptyBlock
)
43 if( !fromSource
&& ( !extendEmptyBlock
||
44 typeof extendEmptyBlock
== 'function' && ( extendEmptyBlock( block
) === false ) ) )
47 // 1. For IE version >=8, empty blocks are displayed correctly themself in wysiwiyg;
48 // 2. For the rest, at least table cell and list item need no filler space.
50 if ( fromSource
&& CKEDITOR
.env
.ie
&&
51 ( document
.documentMode
> 7
52 || block
.name
in CKEDITOR
.dtd
.tr
53 || block
.name
in CKEDITOR
.dtd
.$listItem
) )
56 var lastChild
= lastNoneSpaceChild( block
);
58 return !lastChild
|| lastChild
&&
59 ( lastChild
.type
== CKEDITOR
.NODE_ELEMENT
&& lastChild
.name
== 'br'
60 // Some of the controls in form needs extension too,
61 // to move cursor at the end of the form. (#4791)
62 || block
.name
== 'form' && lastChild
.name
== 'input' );
65 function getBlockExtension( isOutput
, emptyBlockFiller
)
67 return function( node
)
69 trimFillers( node
, !isOutput
);
71 if ( blockNeedsExtension( node
, !isOutput
, emptyBlockFiller
) )
73 if ( isOutput
|| CKEDITOR
.env
.ie
)
74 node
.add( new CKEDITOR
.htmlParser
.text( '\xa0' ) );
76 node
.add( new CKEDITOR
.htmlParser
.element( 'br', {} ) );
81 var dtd
= CKEDITOR
.dtd
;
83 // Define orders of table elements.
84 var tableOrder
= [ 'caption', 'colgroup', 'col', 'thead', 'tfoot', 'tbody' ];
86 // Find out the list of block-like tags that can contain <br>.
87 var blockLikeTags
= CKEDITOR
.tools
.extend( {}, dtd
.$block
, dtd
.$listItem
, dtd
.$tableContent
);
88 for ( var i
in blockLikeTags
)
90 if ( ! ( 'br' in dtd
[i
] ) )
91 delete blockLikeTags
[i
];
93 // We just avoid filler in <pre> right now.
94 // TODO: Support filler for <pre>, line break is also occupy line height.
95 delete blockLikeTags
.pre
;
96 var defaultDataFilterRules
=
101 // Event attributes (onXYZ) must not be directly set. They can become
102 // active in the editing area (IE|WebKit).
103 [ ( /^on/ ), 'data-cke-pa-on' ]
107 var defaultDataBlockFilterRules
= { elements
: {} };
109 for ( i
in blockLikeTags
)
110 defaultDataBlockFilterRules
.elements
[ i
] = getBlockExtension();
112 var defaultHtmlFilterRules
=
116 // Remove the "cke:" namespace prefix.
119 // Ignore <?xml:namespace> tags.
120 [ ( /^\?xml:namespace$/ ), '' ]
125 // Attributes saved for changes and protected attributes.
126 [ ( /^data-cke-(saved|pa)-/ ), '' ],
128 // All "data-cke-" attributes are to be ignored.
129 [ ( /^data-cke-.*/ ), '' ],
136 $ : function( element
)
138 var attribs
= element
.attributes
;
142 // Elements marked as temporary are to be ignored.
143 if ( attribs
[ 'data-cke-temp' ] )
146 // Remove duplicated attributes - #3789.
147 var attributeNames
= [ 'name', 'href', 'src' ],
149 for ( var i
= 0 ; i
< attributeNames
.length
; i
++ )
151 savedAttributeName
= 'data-cke-saved-' + attributeNames
[ i
];
152 savedAttributeName
in attribs
&& ( delete attribs
[ attributeNames
[ i
] ] );
159 // The contents of table should be in correct order (#4809).
160 table : function( element
)
162 var children
= element
.children
;
163 children
.sort( function ( node1
, node2
)
165 return node1
.type
== CKEDITOR
.NODE_ELEMENT
&& node2
.type
== node1
.type
?
166 CKEDITOR
.tools
.indexOf( tableOrder
, node1
.name
) > CKEDITOR
.tools
.indexOf( tableOrder
, node2
.name
) ? 1 : -1 : 0;
170 embed : function( element
)
172 var parent
= element
.parent
;
174 // If the <embed> is child of a <object>, copy the width
175 // and height attributes from it.
176 if ( parent
&& parent
.name
== 'object' )
178 var parentWidth
= parent
.attributes
.width
,
179 parentHeight
= parent
.attributes
.height
;
180 parentWidth
&& ( element
.attributes
.width
= parentWidth
);
181 parentHeight
&& ( element
.attributes
.height
= parentHeight
);
184 // Restore param elements into self-closing.
185 param : function( param
)
188 param
.isEmpty
= true;
192 // Remove empty link but not empty anchor.(#3829)
193 a : function( element
)
195 if ( !( element
.children
.length
||
196 element
.attributes
.name
||
197 element
.attributes
[ 'data-cke-saved-name' ] ) )
203 // Remove dummy span in webkit.
204 span: function( element
)
206 if ( element
.attributes
[ 'class' ] == 'Apple-style-span' )
210 // Empty <pre> in IE is reported with filler node ( ).
211 pre : function( element
) { CKEDITOR
.env
.ie
&& trimFillers( element
); },
213 html : function( element
)
215 delete element
.attributes
.contenteditable
;
216 delete element
.attributes
[ 'class' ];
219 body : function( element
)
221 delete element
.attributes
.spellcheck
;
222 delete element
.attributes
.contenteditable
;
225 style : function( element
)
227 var child
= element
.children
[ 0 ];
228 child
&& child
.value
&& ( child
.value
= CKEDITOR
.tools
.trim( child
.value
));
230 if ( !element
.attributes
.type
)
231 element
.attributes
.type
= 'text/css';
234 title : function( element
)
236 var titleText
= element
.children
[ 0 ];
237 titleText
&& ( titleText
.value
= element
.attributes
[ 'data-cke-title' ] || '' );
243 'class' : function( value
, element
)
245 // Remove all class names starting with "cke_".
246 return CKEDITOR
.tools
.ltrim( value
.replace( /(?:^|\s+)cke_[^\s]*/g, '' ) ) || false;
251 if ( CKEDITOR
.env
.ie
)
253 // IE outputs style attribute in capital letters. We should convert
254 // them back to lower case, while not hurting the values (#5930)
255 defaultHtmlFilterRules
.attributes
.style = function( value
, element
)
257 return value
.replace( /(^|;)([^\:]+)/g, function( match
)
259 return match
.toLowerCase();
264 function protectReadOnly( element
)
266 var attrs
= element
.attributes
;
268 // We should flag that the element was locked by our code so
269 // it'll be editable by the editor functions (#6046).
270 if ( attrs
.contenteditable
!= "false" )
271 attrs
[ 'data-cke-editable' ] = attrs
.contenteditable
? 'true' : 1;
273 attrs
.contenteditable
= "false";
275 function unprotectReadyOnly( element
)
277 var attrs
= element
.attributes
;
278 switch( attrs
[ 'data-cke-editable' ] )
280 case 'true': attrs
.contenteditable
= 'true'; break;
281 case '1': delete attrs
.contenteditable
; break;
284 // Disable form elements editing mode provided by some browers. (#5746)
285 for ( i
in { input
: 1, textarea
: 1 } )
287 defaultDataFilterRules
.elements
[ i
] = protectReadOnly
;
288 defaultHtmlFilterRules
.elements
[ i
] = unprotectReadyOnly
;
291 var protectElementRegex
= /<(a|area|img|input)\b([^>]*)>/gi,
292 protectAttributeRegex
= /\b(href|src|name)\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|(?:[^ "'>]+))/gi;
294 var protectElementsRegex
= /(?:<style(?=[ >])[^>]*>[\s\S]*<\/style>)|(?:<(:?link|meta|base)[^>]*>)/gi,
295 encodedElementsRegex
= /<cke:encoded>([^<]*)<\/cke:encoded>/gi;
297 var protectElementNamesRegex
= /(<\/?)((?:object|embed|param|html|body|head|title)[^>]*>)/gi,
298 unprotectElementNamesRegex
= /(<\/?)cke:((?:html|body|head|title)[^>]*>)/gi;
300 var protectSelfClosingRegex
= /<cke:(param|embed)([^>]*?)\/?>(?!\s*<\/cke:\1)/gi;
302 function protectAttributes( html
)
304 return html
.replace( protectElementRegex
, function( element
, tag
, attributes
)
306 return '<' + tag
+ attributes
.replace( protectAttributeRegex
, function( fullAttr
, attrName
)
308 // We should not rewrite the existed protected attributes, e.g. clipboard content from editor. (#5218)
309 if ( attributes
.indexOf( 'data-cke-saved-' + attrName
) == -1 )
310 return ' data-cke-saved-' + fullAttr
+ ' ' + fullAttr
;
317 function protectElements( html
)
319 return html
.replace( protectElementsRegex
, function( match
)
321 return '<cke:encoded>' + encodeURIComponent( match
) + '</cke:encoded>';
325 function unprotectElements( html
)
327 return html
.replace( encodedElementsRegex
, function( match
, encoded
)
329 return decodeURIComponent( encoded
);
333 function protectElementsNames( html
)
335 return html
.replace( protectElementNamesRegex
, '$1cke:$2');
338 function unprotectElementNames( html
)
340 return html
.replace( unprotectElementNamesRegex
, '$1$2' );
343 function protectSelfClosingElements( html
)
345 return html
.replace( protectSelfClosingRegex
, '<cke:$1$2></cke:$1>' );
348 function protectPreFormatted( html
)
350 return html
.replace( /(<pre\b[^>]*>)(\r\n|\n)/g, '$1$2$2' );
353 function protectRealComments( html
)
355 return html
.replace( /<!--(?!{cke_protected})[\s\S]+?-->/g, function( match
)
357 return '<!--' + protectedSourceMarker
+
359 encodeURIComponent( match
).replace( /--/g, '%2D%2D' ) +
364 function unprotectRealComments( html
)
366 return html
.replace( /<!--\{cke_protected\}\{C\}([\s\S]+?)-->/g, function( match
, data
)
368 return decodeURIComponent( data
);
372 function unprotectSource( html
, editor
)
374 var store
= editor
._
.dataStore
;
376 return html
.replace( /<!--\{cke_protected\}([\s\S]+?)-->/g, function( match
, data
)
378 return decodeURIComponent( data
);
379 }).replace( /\{cke_protected_(\d+)\}/g, function( match
, id
)
381 return store
&& store
[ id
] || '';
385 function protectSource( data
, editor
)
387 var protectedHtml
= [],
388 protectRegexes
= editor
.config
.protectedSource
,
389 store
= editor
._
.dataStore
|| ( editor
._
.dataStore
= { id
: 1 } ),
390 tempRegex
= /<\!--\{cke_temp(comment)?\}(\d*?)-->/g;
394 // Script tags will also be forced to be protected, otherwise
395 // IE will execute them.
396 ( /<script[\s\S]*?<\/script>/gi ),
398 // <noscript> tags (get lost in IE and messed up in FF).
399 /<noscript[\s\S]*?<\/noscript>/gi
401 .concat( protectRegexes
);
403 // First of any other protection, we must protect all comments
404 // to avoid loosing them (of course, IE related).
405 // Note that we use a different tag for comments, as we need to
406 // transform them when applying filters.
407 data
= data
.replace( (/<!--[\s\S]*?-->/g), function( match
)
409 return '<!--{cke_tempcomment}' + ( protectedHtml
.push( match
) - 1 ) + '-->';
412 for ( var i
= 0 ; i
< regexes
.length
; i
++ )
414 data
= data
.replace( regexes
[i
], function( match
)
416 match
= match
.replace( tempRegex
, // There could be protected source inside another one. (#3869).
417 function( $, isComment
, id
)
419 return protectedHtml
[ id
];
423 // Avoid protecting over protected, e.g. /\{.*?\}/
424 return ( /cke_temp(comment)?/ ).test( match
) ? match
425 : '<!--{cke_temp}' + ( protectedHtml
.push( match
) - 1 ) + '-->';
428 data
= data
.replace( tempRegex
, function( $, isComment
, id
)
430 return '<!--' + protectedSourceMarker
+
431 ( isComment
? '{C}' : '' ) +
432 encodeURIComponent( protectedHtml
[ id
] ).replace( /--/g, '%2D%2D' ) +
437 // Different protection pattern is used for those that
438 // live in attributes to avoid from being HTML encoded.
439 return data
.replace( /(['"]).*?\1/g, function ( match
)
441 return match
.replace( /<!--\{cke_protected\}([\s\S]+?)-->/g, function( match
, data
)
443 store
[ store
.id
] = decodeURIComponent( data
);
444 return '{cke_protected_'+ ( store
.id
++ ) + '}';
449 CKEDITOR
.plugins
.add( 'htmldataprocessor',
451 requires
: [ 'htmlwriter' ],
453 init : function( editor
)
455 var dataProcessor
= editor
.dataProcessor
= new CKEDITOR
.htmlDataProcessor( editor
);
457 dataProcessor
.writer
.forceSimpleAmpersand
= editor
.config
.forceSimpleAmpersand
;
459 dataProcessor
.dataFilter
.addRules( defaultDataFilterRules
);
460 dataProcessor
.dataFilter
.addRules( defaultDataBlockFilterRules
);
461 dataProcessor
.htmlFilter
.addRules( defaultHtmlFilterRules
);
463 var defaultHtmlBlockFilterRules
= { elements
: {} };
464 for ( i
in blockLikeTags
)
465 defaultHtmlBlockFilterRules
.elements
[ i
] = getBlockExtension( true, editor
.config
.fillEmptyBlocks
);
467 dataProcessor
.htmlFilter
.addRules( defaultHtmlBlockFilterRules
);
472 ! ( 'fillEmptyBlocks' in CKEDITOR
.config
) && ( CKEDITOR
.config
.fillEmptyBlocks
= 1 );
476 CKEDITOR
.htmlDataProcessor = function( editor
)
478 this.editor
= editor
;
480 this.writer
= new CKEDITOR
.htmlWriter();
481 this.dataFilter
= new CKEDITOR
.htmlParser
.filter();
482 this.htmlFilter
= new CKEDITOR
.htmlParser
.filter();
485 CKEDITOR
.htmlDataProcessor
.prototype =
487 toHtml : function( data
, fixForBody
)
489 // The source data is already HTML, but we need to clean
490 // it up and apply the filter.
492 data
= protectSource( data
, this.editor
);
494 // Before anything, we must protect the URL attributes as the
495 // browser may changing them when setting the innerHTML later in
497 data
= protectAttributes( data
);
499 // Protect elements than can't be set inside a DIV. E.g. IE removes
500 // style tags from innerHTML. (#3710)
501 data
= protectElements( data
);
503 // Certain elements has problem to go through DOM operation, protect
504 // them by prefixing 'cke' namespace. (#3591)
505 data
= protectElementsNames( data
);
507 // All none-IE browsers ignore self-closed custom elements,
508 // protecting them into open-close. (#3591)
509 data
= protectSelfClosingElements( data
);
511 // Compensate one leading line break after <pre> open as browsers
512 // eat it up. (#5789)
513 data
= protectPreFormatted( data
);
515 // Call the browser to help us fixing a possibly invalid HTML
517 var div
= new CKEDITOR
.dom
.element( 'div' );
518 // Add fake character to workaround IE comments bug. (#3801)
519 div
.setHtml( 'a' + data
);
520 data
= div
.getHtml().substr( 1 );
522 // Unprotect "some" of the protected elements at this point.
523 data
= unprotectElementNames( data
);
525 data
= unprotectElements( data
);
527 // Restore the comments that have been protected, in this way they
528 // can be properly filtered.
529 data
= unprotectRealComments( data
);
531 // Now use our parser to make further fixes to the structure, as
532 // well as apply the filter.
533 var fragment
= CKEDITOR
.htmlParser
.fragment
.fromHtml( data
, fixForBody
),
534 writer
= new CKEDITOR
.htmlParser
.basicWriter();
536 fragment
.writeHtml( writer
, this.dataFilter
);
537 data
= writer
.getHtml( true );
539 // Protect the real comments again.
540 data
= protectRealComments( data
);
545 toDataFormat : function( html
, fixForBody
)
547 var writer
= this.writer
,
548 fragment
= CKEDITOR
.htmlParser
.fragment
.fromHtml( html
, fixForBody
);
552 fragment
.writeHtml( writer
, this.htmlFilter
);
554 var data
= writer
.getHtml( true );
556 // Restore those non-HTML protected source. (#4475,#4880)
557 data
= unprotectRealComments( data
);
558 data
= unprotectSource( data
, this.editor
);
566 * Whether to force using "&" instead of "&amp;" in elements attributes
567 * values, it's not recommended to change this setting for compliance with the
568 * W3C XHTML 1.0 standards (<a href="http://www.w3.org/TR/xhtml1/#C_12">C.12, XHTML 1.0</a>).
569 * @name CKEDITOR.config.forceSimpleAmpersand
570 * @name CKEDITOR.config.forceSimpleAmpersand
574 * config.forceSimpleAmpersand = false;
578 * Whether a filler text (non-breaking space entity - ) will be inserted into empty block elements in HTML output,
579 * this is used to render block elements properly with line-height; When a function is instead specified,
580 * it'll be passed a {@link CKEDITOR.htmlParser.element} to decide whether adding the filler text
581 * by expecting a boolean return value.
582 * @name CKEDITOR.config.fillEmptyBlocks
587 * config.fillEmptyBlocks = false; // Prevent filler nodes in all empty blocks.
589 * // Prevent filler node only in float cleaners.
590 * config.fillEmptyBlocks = function( element )
592 * if ( element.attributes[ 'class' ].indexOf ( 'clear-both' ) != -1 )