1 /* 2 Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. 3 For licensing, see LICENSE.html or http://ckeditor.com/license 4 */ 5 6 (function() 7 { 8 // Regex to scan for at the end of blocks, which are actually placeholders. 9 var tailNbspRegex = /^[\t\r\n ]* $/; 10 11 var protectedSourceMarker = '{cke_protected}'; 12 13 function trimFillers( block, fromSource ) 14 { 15 // If the current node is a block, and if we're converting from source or 16 // we're not in IE then search for and remove any tailing BR node. 17 // 18 // Also, any at the end of blocks are fillers, remove them as well. 19 // (#2886) 20 var children = block.children; 21 var lastChild = children[ children.length - 1 ]; 22 if ( lastChild ) 23 { 24 if ( ( fromSource || !CKEDITOR.env.ie ) && lastChild.type == CKEDITOR.NODE_ELEMENT && lastChild.name == 'br' ) 25 children.pop(); 26 if ( lastChild.type == CKEDITOR.NODE_TEXT && tailNbspRegex.test( lastChild.value ) ) 27 children.pop(); 28 } 29 } 30 31 function blockNeedsExtension( block ) 32 { 33 if ( block.children.length < 1 ) 34 return true; 35 36 var lastChild = block.children[ block.children.length - 1 ]; 37 return lastChild.type == CKEDITOR.NODE_ELEMENT && lastChild.name == 'br'; 38 } 39 40 function extendBlockForDisplay( block ) 41 { 42 trimFillers( block, true ); 43 44 if ( blockNeedsExtension( block ) ) 45 { 46 if ( CKEDITOR.env.ie ) 47 block.add( new CKEDITOR.htmlParser.text( '\xa0' ) ); 48 else 49 block.add( new CKEDITOR.htmlParser.element( 'br', {} ) ); 50 } 51 } 52 53 function extendBlockForOutput( block ) 54 { 55 trimFillers( block ); 56 57 if ( blockNeedsExtension( block ) ) 58 block.add( new CKEDITOR.htmlParser.text( '\xa0' ) ); 59 } 60 61 var dtd = CKEDITOR.dtd; 62 63 // Find out the list of block-like tags that can contain <br>. 64 var blockLikeTags = CKEDITOR.tools.extend( {}, dtd.$block, dtd.$listItem, dtd.$tableContent ); 65 for ( var i in blockLikeTags ) 66 { 67 if ( ! ( 'br' in dtd[i] ) ) 68 delete blockLikeTags[i]; 69 } 70 // We just avoid filler in <pre> right now. 71 // TODO: Support filler for <pre>, line break is also occupy line height. 72 delete blockLikeTags.pre; 73 var defaultDataFilterRules = 74 { 75 elementNames : 76 [ 77 // Elements that cause problems in wysiwyg mode. 78 [ ( /^(object|embed|param)$/ ), 'cke:$1' ] 79 ], 80 81 attributeNames : 82 [ 83 // Event attributes (onXYZ) must not be directly set. They can become 84 // active in the editing area (IE|WebKit). 85 [ ( /^on/ ), '_cke_pa_on' ] 86 ] 87 }; 88 89 var defaultDataBlockFilterRules = { elements : {} }; 90 91 for ( i in blockLikeTags ) 92 defaultDataBlockFilterRules.elements[ i ] = extendBlockForDisplay; 93 94 /** 95 * IE sucks with dynamic 'name' attribute after element is created, '_cke_saved_name' is used instead for this attribute. 96 */ 97 var removeName = function( element ) 98 { 99 var attribs = element.attributes; 100 101 if ( attribs._cke_saved_name ) 102 delete attribs.name; 103 }; 104 105 var defaultHtmlFilterRules = 106 { 107 elementNames : 108 [ 109 // Remove the "cke:" namespace prefix. 110 [ ( /^cke:/ ), '' ], 111 112 // Ignore <?xml:namespace> tags. 113 [ ( /^\?xml:namespace$/ ), '' ] 114 ], 115 116 attributeNames : 117 [ 118 // Attributes saved for changes and protected attributes. 119 [ ( /^_cke_(saved|pa)_/ ), '' ], 120 121 // All "_cke" attributes are to be ignored. 122 [ ( /^_cke.*/ ), '' ] 123 ], 124 125 elements : 126 { 127 embed : function( element ) 128 { 129 var parent = element.parent; 130 131 // If the <embed> is child of a <object>, copy the width 132 // and height attributes from it. 133 if ( parent && parent.name == 'object' ) 134 { 135 element.attributes.width = parent.attributes.width; 136 element.attributes.height = parent.attributes.height; 137 } 138 }, 139 140 img : function( element ) 141 { 142 var attribs = element.attributes; 143 144 if ( attribs._cke_saved_name ) 145 delete attribs.name; 146 if ( attribs._cke_saved_src ) 147 delete attribs.src; 148 }, 149 150 a : function( element ) 151 { 152 var attribs = element.attributes; 153 154 if ( attribs._cke_saved_name ) 155 delete attribs.name; 156 if ( attribs._cke_saved_href ) 157 delete attribs.href; 158 }, 159 160 input : removeName, 161 textarea : removeName, 162 select : removeName, 163 form : removeName 164 }, 165 166 attributes : 167 { 168 'class' : function( value, element ) 169 { 170 // Remove all class names starting with "cke_". 171 return CKEDITOR.tools.ltrim( value.replace( /(?:^|\s+)cke_[^\s]*/g, '' ) ) || false; 172 } 173 }, 174 175 comment : function( contents ) 176 { 177 if ( contents.substr( 0, protectedSourceMarker.length ) == protectedSourceMarker ) 178 return new CKEDITOR.htmlParser.cdata( decodeURIComponent( contents.substr( protectedSourceMarker.length ) ) ); 179 180 return contents; 181 } 182 }; 183 184 var defaultHtmlBlockFilterRules = { elements : {} }; 185 186 for ( i in blockLikeTags ) 187 defaultHtmlBlockFilterRules.elements[ i ] = extendBlockForOutput; 188 189 if ( CKEDITOR.env.ie ) 190 { 191 // IE outputs style attribute in capital letters. We should convert 192 // them back to lower case. 193 defaultHtmlFilterRules.attributes.style = function( value, element ) 194 { 195 return value.toLowerCase(); 196 }; 197 } 198 199 var protectAttributeRegex = /<(?:a|area|img|input).*?\s((?:href|src|name)\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|(?:[^ "'>]+)))/gi; 200 201 function protectAttributes( html ) 202 { 203 return html.replace( protectAttributeRegex, '$& _cke_saved_$1' ); 204 } 205 206 var protectStyleTagsRegex = /<(style)(?=[ >])[^>]*>[^<]*<\/\1>/gi; 207 var encodedTagsRegex = /<cke:encoded>([^<]*)<\/cke:encoded>/gi; 208 209 function protectStyleTagsMatch( match ) 210 { 211 return '<cke:encoded>' + encodeURIComponent( match ) + '</cke:encoded>'; 212 } 213 214 function protectStyleTags( html ) 215 { 216 return html.replace( protectStyleTagsRegex, protectStyleTagsMatch ); 217 } 218 219 function unprotectEncodedTagsMatch( match, encoded ) 220 { 221 return decodeURIComponent( encoded ); 222 } 223 224 function unprotectEncodedTags( html ) 225 { 226 return html.replace( encodedTagsRegex, unprotectEncodedTagsMatch ); 227 } 228 229 function protectSource( data, protectRegexes ) 230 { 231 var regexes = 232 [ 233 // First of any other protection, we must protect all comments 234 // to avoid loosing them (of course, IE related). 235 /<!--[\s\S]*?-->/g, 236 237 // Script tags will also be forced to be protected, otherwise 238 // IE will execute them. 239 /<script[\s\S]*?<\/script>/gi, 240 241 // <noscript> tags (get lost in IE and messed up in FF). 242 /<noscript[\s\S]*?<\/noscript>/gi 243 ] 244 .concat( protectRegexes ); 245 246 for ( var i = 0 ; i < regexes.length ; i++ ) 247 { 248 data = data.replace( regexes[i], function( match ) 249 { 250 return '<!--' + protectedSourceMarker + encodeURIComponent( match ).replace( /--/g, '%2D%2D' ) + '-->'; 251 }); 252 } 253 254 return data; 255 } 256 257 CKEDITOR.plugins.add( 'htmldataprocessor', 258 { 259 requires : [ 'htmlwriter' ], 260 261 init : function( editor ) 262 { 263 var dataProcessor = editor.dataProcessor = new CKEDITOR.htmlDataProcessor( editor ); 264 265 dataProcessor.writer.forceSimpleAmpersand = editor.config.forceSimpleAmpersand; 266 267 dataProcessor.dataFilter.addRules( defaultDataFilterRules ); 268 dataProcessor.dataFilter.addRules( defaultDataBlockFilterRules ); 269 dataProcessor.htmlFilter.addRules( defaultHtmlFilterRules ); 270 dataProcessor.htmlFilter.addRules( defaultHtmlBlockFilterRules ); 271 } 272 }); 273 274 CKEDITOR.htmlDataProcessor = function( editor ) 275 { 276 this.editor = editor; 277 278 this.writer = new CKEDITOR.htmlWriter(); 279 this.dataFilter = new CKEDITOR.htmlParser.filter(); 280 this.htmlFilter = new CKEDITOR.htmlParser.filter(); 281 }; 282 283 CKEDITOR.htmlDataProcessor.prototype = 284 { 285 toHtml : function( data, fixForBody ) 286 { 287 // The source data is already HTML, but we need to clean 288 // it up and apply the filter. 289 290 data = protectSource( data, this.editor.config.protectedSource ); 291 292 // Before anything, we must protect the URL attributes as the 293 // browser may changing them when setting the innerHTML later in 294 // the code. 295 data = protectAttributes( data ); 296 297 // IE remvoes style tags from innerHTML. (#3710). 298 if ( CKEDITOR.env.ie ) 299 data = protectStyleTags( data ); 300 301 // Call the browser to help us fixing a possibly invalid HTML 302 // structure. 303 var div = document.createElement( 'div' ); 304 div.innerHTML = data; 305 data = div.innerHTML; 306 307 if ( CKEDITOR.env.ie ) 308 data = unprotectEncodedTags( data ); 309 310 // Now use our parser to make further fixes to the structure, as 311 // well as apply the filter. 312 var fragment = CKEDITOR.htmlParser.fragment.fromHtml( data, fixForBody ), 313 writer = new CKEDITOR.htmlParser.basicWriter(); 314 315 fragment.writeHtml( writer, this.dataFilter ); 316 317 return writer.getHtml( true ); 318 }, 319 320 toDataFormat : function( html, fixForBody ) 321 { 322 var writer = this.writer, 323 fragment = CKEDITOR.htmlParser.fragment.fromHtml( html, fixForBody ); 324 325 writer.reset(); 326 327 fragment.writeHtml( writer, this.htmlFilter ); 328 329 return writer.getHtml( true ); 330 } 331 }; 332 })(); 333 334 CKEDITOR.config.forceSimpleAmpersand = false; 335