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