1/* 2 * FCKeditor - The text editor for Internet - http://www.fckeditor.net 3 * Copyright (C) 2003-2007 Frederico Caldeira Knabben 4 * 5 * == BEGIN LICENSE == 6 * 7 * Licensed under the terms of any of the following licenses at your 8 * choice: 9 * 10 * - GNU General Public License Version 2 or later (the "GPL") 11 * http://www.gnu.org/licenses/gpl.html 12 * 13 * - GNU Lesser General Public License Version 2.1 or later (the "LGPL") 14 * http://www.gnu.org/licenses/lgpl.html 15 * 16 * - Mozilla Public License Version 1.1 or later (the "MPL") 17 * http://www.mozilla.org/MPL/MPL-1.1.html 18 * 19 * == END LICENSE == 20 * 21 * Defines the FCKXHtml object, responsible for the XHTML operations. 22 */ 23 24var FCKXHtml = new Object() ; 25 26FCKXHtml.CurrentJobNum = 0 ; 27 28FCKXHtml.GetXHTML = function( node, includeNode, format ) 29{ 30 FCKDomTools.CheckAndRemovePaddingNode( node.ownerDocument, FCKConfig.EnterMode ) ; 31 FCKXHtmlEntities.Initialize() ; 32 33 // Set the correct entity to use for empty blocks. 34 this._NbspEntity = ( FCKConfig.ProcessHTMLEntities? 'nbsp' : '#160' ) ; 35 36 // Save the current IsDirty state. The XHTML processor may change the 37 // original HTML, dirtying it. 38 var bIsDirty = FCK.IsDirty() ; 39 40 // Special blocks are blocks of content that remain untouched during the 41 // process. It is used for SCRIPTs and STYLEs. 42 FCKXHtml.SpecialBlocks = new Array() ; 43 44 // Create the XML DOMDocument object. 45 this.XML = FCKTools.CreateXmlObject( 'DOMDocument' ) ; 46 47 // Add a root element that holds all child nodes. 48 this.MainNode = this.XML.appendChild( this.XML.createElement( 'xhtml' ) ) ; 49 50 FCKXHtml.CurrentJobNum++ ; 51 52// var dTimer = new Date() ; 53 54 if ( includeNode ) 55 this._AppendNode( this.MainNode, node ) ; 56 else 57 this._AppendChildNodes( this.MainNode, node, false ) ; 58 59 // Get the resulting XHTML as a string. 60 var sXHTML = this._GetMainXmlString() ; 61 62// alert( 'Time: ' + ( ( ( new Date() ) - dTimer ) ) + ' ms' ) ; 63 64 this.XML = null ; 65 66 // Safari adds xmlns="http://www.w3.org/1999/xhtml" to the root node (#963) 67 if ( FCKBrowserInfo.IsSafari ) 68 sXHTML = sXHTML.replace( /^<xhtml.*?>/, '<xhtml>' ) ; 69 70 // Strip the "XHTML" root node. 71 sXHTML = sXHTML.substr( 7, sXHTML.length - 15 ).Trim() ; 72 73 // Add a space in the tags with no closing tags, like <br/> -> <br /> 74 sXHTML = sXHTML.replace( FCKRegexLib.SpaceNoClose, ' />'); 75 76 if ( FCKConfig.ForceSimpleAmpersand ) 77 sXHTML = sXHTML.replace( FCKRegexLib.ForceSimpleAmpersand, '&' ) ; 78 79 if ( format ) 80 sXHTML = FCKCodeFormatter.Format( sXHTML ) ; 81 82 // Now we put back the SpecialBlocks contents. 83 for ( var i = 0 ; i < FCKXHtml.SpecialBlocks.length ; i++ ) 84 { 85 var oRegex = new RegExp( '___FCKsi___' + i ) ; 86 sXHTML = sXHTML.replace( oRegex, FCKXHtml.SpecialBlocks[i] ) ; 87 } 88 89 // Replace entities marker with the ampersand. 90 sXHTML = sXHTML.replace( FCKRegexLib.GeckoEntitiesMarker, '&' ) ; 91 92 // Restore the IsDirty state if it was not dirty. 93 if ( !bIsDirty ) 94 FCK.ResetIsDirty() ; 95 96 FCKDomTools.EnforcePaddingNode( FCKTools.GetElementDocument( node ), FCKConfig.EnterMode ) ; 97 return sXHTML ; 98} 99 100FCKXHtml._AppendAttribute = function( xmlNode, attributeName, attributeValue ) 101{ 102 try 103 { 104 if ( attributeValue == undefined || attributeValue == null ) 105 attributeValue = '' ; 106 else if ( attributeValue.replace ) 107 { 108 if ( FCKConfig.ForceSimpleAmpersand ) 109 attributeValue = attributeValue.replace( /&/g, '___FCKAmp___' ) ; 110 111 // Entities must be replaced in the attribute values. 112 attributeValue = attributeValue.replace( FCKXHtmlEntities.EntitiesRegex, FCKXHtml_GetEntity ) ; 113 } 114 115 // Create the attribute. 116 var oXmlAtt = this.XML.createAttribute( attributeName ) ; 117 oXmlAtt.value = attributeValue ; 118 119 // Set the attribute in the node. 120 xmlNode.attributes.setNamedItem( oXmlAtt ) ; 121 } 122 catch (e) 123 {} 124} 125 126FCKXHtml._AppendChildNodes = function( xmlNode, htmlNode, isBlockElement ) 127{ 128 var oNode = htmlNode.firstChild ; 129 130 while ( oNode ) 131 { 132 this._AppendNode( xmlNode, oNode ) ; 133 oNode = oNode.nextSibling ; 134 } 135 136 // Trim block elements. This is also needed to avoid Firefox leaving extra 137 // BRs at the end of them. 138 if ( isBlockElement && htmlNode.tagName && htmlNode.tagName.toLowerCase() != 'pre' ) 139 { 140 FCKDomTools.TrimNode( xmlNode ) ; 141 142 if ( FCKConfig.FillEmptyBlocks ) 143 { 144 var lastChild = xmlNode.lastChild ; 145 if ( lastChild && lastChild.nodeType == 1 && lastChild.nodeName == 'br' ) 146 this._AppendEntity( xmlNode, this._NbspEntity ) ; 147 } 148 } 149 150 // If the resulting node is empty. 151 if ( xmlNode.childNodes.length == 0 ) 152 { 153 if ( isBlockElement && FCKConfig.FillEmptyBlocks ) 154 { 155 this._AppendEntity( xmlNode, this._NbspEntity ) ; 156 return xmlNode ; 157 } 158 159 var sNodeName = xmlNode.nodeName ; 160 161 // Some inline elements are required to have something inside (span, strong, etc...). 162 if ( FCKListsLib.InlineChildReqElements[ sNodeName ] ) 163 return null ; 164 165 // We can't use short representation of empty elements that are not marked 166 // as empty in th XHTML DTD. 167 if ( !FCKListsLib.EmptyElements[ sNodeName ] ) 168 xmlNode.appendChild( this.XML.createTextNode('') ) ; 169 } 170 171 return xmlNode ; 172} 173 174FCKXHtml._AppendNode = function( xmlNode, htmlNode ) 175{ 176 if ( !htmlNode ) 177 return false ; 178 179 switch ( htmlNode.nodeType ) 180 { 181 // Element Node. 182 case 1 : 183 // If we detect a <br> inside a <pre> in Gecko, turn it into a line break instead. 184 // This is a workaround for the Gecko bug here: https://bugzilla.mozilla.org/show_bug.cgi?id=92921 185 if ( FCKBrowserInfo.IsGecko 186 && htmlNode.tagName.toLowerCase() == 'br' 187 && htmlNode.parentNode.tagName.toLowerCase() == 'pre' ) 188 { 189 var val = '\r' ; 190 if ( htmlNode == htmlNode.parentNode.firstChild ) 191 val += '\r' ; 192 return FCKXHtml._AppendNode( xmlNode, this.XML.createTextNode( val ) ) ; 193 } 194 195 // Here we found an element that is not the real element, but a 196 // fake one (like the Flash placeholder image), so we must get the real one. 197 if ( htmlNode.getAttribute('_fckfakelement') ) 198 return FCKXHtml._AppendNode( xmlNode, FCK.GetRealElement( htmlNode ) ) ; 199 200 // Ignore bogus BR nodes in the DOM. 201 if ( FCKBrowserInfo.IsGecko && 202 ( htmlNode.hasAttribute('_moz_editor_bogus_node') || htmlNode.getAttribute( 'type' ) == '_moz' ) ) 203 return false ; 204 205 // This is for elements that are instrumental to FCKeditor and 206 // must be removed from the final HTML. 207 if ( htmlNode.getAttribute('_fcktemp') ) 208 return false ; 209 210 // Get the element name. 211 var sNodeName = htmlNode.tagName.toLowerCase() ; 212 213 if ( FCKBrowserInfo.IsIE ) 214 { 215 // IE doens't include the scope name in the nodeName. So, add the namespace. 216 if ( htmlNode.scopeName && htmlNode.scopeName != 'HTML' && htmlNode.scopeName != 'FCK' ) 217 sNodeName = htmlNode.scopeName.toLowerCase() + ':' + sNodeName ; 218 } 219 else 220 { 221 if ( sNodeName.StartsWith( 'fck:' ) ) 222 sNodeName = sNodeName.Remove( 0,4 ) ; 223 } 224 225 // Check if the node name is valid, otherwise ignore this tag. 226 // If the nodeName starts with a slash, it is a orphan closing tag. 227 // On some strange cases, the nodeName is empty, even if the node exists. 228 if ( !FCKRegexLib.ElementName.test( sNodeName ) ) 229 return false ; 230 231 // The already processed nodes must be marked to avoid then to be duplicated (bad formatted HTML). 232 // So here, the "mark" is checked... if the element is Ok, then mark it. 233 if ( htmlNode._fckxhtmljob && htmlNode._fckxhtmljob == FCKXHtml.CurrentJobNum ) 234 return false ; 235 236 var oNode = this.XML.createElement( sNodeName ) ; 237 238 // Add all attributes. 239 FCKXHtml._AppendAttributes( xmlNode, htmlNode, oNode, sNodeName ) ; 240 241 htmlNode._fckxhtmljob = FCKXHtml.CurrentJobNum ; 242 243 // Tag specific processing. 244 var oTagProcessor = FCKXHtml.TagProcessors[ sNodeName ] ; 245 246 if ( oTagProcessor ) 247 oNode = oTagProcessor( oNode, htmlNode, xmlNode ) ; 248 else 249 oNode = this._AppendChildNodes( oNode, htmlNode, Boolean( FCKListsLib.NonEmptyBlockElements[ sNodeName ] ) ) ; 250 251 if ( !oNode ) 252 return false ; 253 254 xmlNode.appendChild( oNode ) ; 255 256 break ; 257 258 // Text Node. 259 case 3 : 260 if ( htmlNode.parentNode && htmlNode.parentNode.nodeName.IEquals( 'pre' ) ) 261 return this._AppendTextNode( xmlNode, htmlNode.nodeValue ) ; 262 return this._AppendTextNode( xmlNode, htmlNode.nodeValue.ReplaceNewLineChars(' ') ) ; 263 264 // Comment 265 case 8 : 266 // IE catches the <!DOTYPE ... > as a comment, but it has no 267 // innerHTML, so we can catch it, and ignore it. 268 if ( FCKBrowserInfo.IsIE && !htmlNode.innerHTML ) 269 break ; 270 271 try { xmlNode.appendChild( this.XML.createComment( htmlNode.nodeValue ) ) ; } 272 catch (e) { /* Do nothing... probably this is a wrong format comment. */ } 273 break ; 274 275 // Unknown Node type. 276 default : 277 xmlNode.appendChild( this.XML.createComment( "Element not supported - Type: " + htmlNode.nodeType + " Name: " + htmlNode.nodeName ) ) ; 278 break ; 279 } 280 return true ; 281} 282 283// Append an item to the SpecialBlocks array and returns the tag to be used. 284FCKXHtml._AppendSpecialItem = function( item ) 285{ 286 return '___FCKsi___' + FCKXHtml.SpecialBlocks.AddItem( item ) ; 287} 288 289FCKXHtml._AppendEntity = function( xmlNode, entity ) 290{ 291 xmlNode.appendChild( this.XML.createTextNode( '#?-:' + entity + ';' ) ) ; 292} 293 294FCKXHtml._AppendTextNode = function( targetNode, textValue ) 295{ 296 var bHadText = textValue.length > 0 ; 297 if ( bHadText ) 298 targetNode.appendChild( this.XML.createTextNode( textValue.replace( FCKXHtmlEntities.EntitiesRegex, FCKXHtml_GetEntity ) ) ) ; 299 return bHadText ; 300} 301 302// Retrieves a entity (internal format) for a given character. 303function FCKXHtml_GetEntity( character ) 304{ 305 // We cannot simply place the entities in the text, because the XML parser 306 // will translate & to &. So we use a temporary marker which is replaced 307 // in the end of the processing. 308 var sEntity = FCKXHtmlEntities.Entities[ character ] || ( '#' + character.charCodeAt(0) ) ; 309 return '#?-:' + sEntity + ';' ; 310} 311 312// An object that hold tag specific operations. 313FCKXHtml.TagProcessors = 314{ 315 img : function( node, htmlNode ) 316 { 317 // The "ALT" attribute is required in XHTML. 318 if ( ! node.attributes.getNamedItem( 'alt' ) ) 319 FCKXHtml._AppendAttribute( node, 'alt', '' ) ; 320 321 var sSavedUrl = htmlNode.getAttribute( '_fcksavedurl' ) ; 322 if ( sSavedUrl != null ) 323 FCKXHtml._AppendAttribute( node, 'src', sSavedUrl ) ; 324 325 return node ; 326 }, 327 328 a : function( node, htmlNode ) 329 { 330 // Firefox may create empty tags when deleting the selection in some special cases (SF-BUG 1556878). 331 if ( htmlNode.innerHTML.Trim().length == 0 && !htmlNode.name ) 332 return false ; 333 334 var sSavedUrl = htmlNode.getAttribute( '_fcksavedurl' ) ; 335 if ( sSavedUrl != null ) 336 FCKXHtml._AppendAttribute( node, 'href', sSavedUrl ) ; 337 338 339 // Anchors with content has been marked with an additional class, now we must remove it. 340 if ( FCKBrowserInfo.IsIE ) 341 { 342 // Buggy IE, doesn't copy the name of changed anchors. 343 if ( htmlNode.name ) 344 FCKXHtml._AppendAttribute( node, 'name', htmlNode.name ) ; 345 } 346 347 node = FCKXHtml._AppendChildNodes( node, htmlNode, false ) ; 348 349 return node ; 350 }, 351 352 script : function( node, htmlNode ) 353 { 354 // The "TYPE" attribute is required in XHTML. 355 if ( ! node.attributes.getNamedItem( 'type' ) ) 356 FCKXHtml._AppendAttribute( node, 'type', 'text/javascript' ) ; 357 358 node.appendChild( FCKXHtml.XML.createTextNode( FCKXHtml._AppendSpecialItem( htmlNode.text ) ) ) ; 359 360 return node ; 361 }, 362 363 style : function( node, htmlNode ) 364 { 365 // The "TYPE" attribute is required in XHTML. 366 if ( ! node.attributes.getNamedItem( 'type' ) ) 367 FCKXHtml._AppendAttribute( node, 'type', 'text/css' ) ; 368 369 var cssText = htmlNode.innerHTML ; 370 if ( FCKBrowserInfo.IsIE ) // Bug #403 : IE always appends a \r\n to the beginning of StyleNode.innerHTML 371 cssText = cssText.replace( /^(\r\n|\n|\r)/, '' ) ; 372 373 node.appendChild( FCKXHtml.XML.createTextNode( FCKXHtml._AppendSpecialItem( cssText ) ) ) ; 374 375 return node ; 376 }, 377 378 pre : function ( node, htmlNode ) 379 { 380 for ( var i = 0 ; i < htmlNode.childNodes.length ; i++ ) 381 { 382 var item = htmlNode.childNodes[i] ; 383 var val = item.nodeValue ; 384 if ( item.nodeType == 3 && i == 0 ) 385 node.appendChild( FCKXHtml.XML.createTextNode( FCKXHtml._AppendSpecialItem( '\r\n' + val ) ) ) ; 386 else 387 FCKXHtml._AppendNode( node, item ) ; 388 } 389 return node ; 390 }, 391 392 title : function( node, htmlNode ) 393 { 394 node.appendChild( FCKXHtml.XML.createTextNode( FCK.EditorDocument.title ) ) ; 395 396 return node ; 397 }, 398 399 // Fix nested <ul> and <ol>. 400 ol : function( node, htmlNode, targetNode ) 401 { 402 if ( htmlNode.innerHTML.Trim().length == 0 ) 403 return false ; 404 405 var ePSibling = targetNode.lastChild ; 406 407 if ( ePSibling && ePSibling.nodeType == 3 ) 408 ePSibling = ePSibling.previousSibling ; 409 410 if ( ePSibling && ePSibling.nodeName.toUpperCase() == 'LI' ) 411 { 412 htmlNode._fckxhtmljob = null ; 413 FCKXHtml._AppendNode( ePSibling, htmlNode ) ; 414 return false ; 415 } 416 417 node = FCKXHtml._AppendChildNodes( node, htmlNode ) ; 418 419 return node ; 420 }, 421 422 span : function( node, htmlNode ) 423 { 424 // Firefox may create empty tags when deleting the selection in some special cases (SF-BUG 1084404). 425 if ( htmlNode.innerHTML.length == 0 ) 426 return false ; 427 428 node = FCKXHtml._AppendChildNodes( node, htmlNode, false ) ; 429 430 return node ; 431 }, 432 433 // IE loses contents of iframes, and Gecko does give it back HtmlEncoded 434 // Note: Opera does lose the content and doesn't provide it in the innerHTML string 435 iframe : function( node, htmlNode ) 436 { 437 var sHtml = htmlNode.innerHTML ; 438 439 // Gecko does give back the encoded html 440 if ( FCKBrowserInfo.IsGecko ) 441 sHtml = FCKTools.HTMLDecode( sHtml ); 442 443 // Remove the saved urls here as the data won't be processed as nodes 444 sHtml = sHtml.replace( /\s_fcksavedurl="[^"]*"/g, '' ) ; 445 446 node.appendChild( FCKXHtml.XML.createTextNode( FCKXHtml._AppendSpecialItem( sHtml ) ) ) ; 447 448 return node ; 449 }, 450 451 body : function( node, htmlNode ) 452 { 453 node = FCKXHtml._AppendChildNodes( node, htmlNode, false ) ; 454 // Remove spellchecker attributes added for Firefox when converting to HTML code (Bug #1351). 455 node.removeAttribute( 'spellcheck' ) ; 456 return node ; 457 } 458} ; 459 460FCKXHtml.TagProcessors.ul = FCKXHtml.TagProcessors.ol ; 461