source: extensions/FCKEditor/editor/_source/internals/fckxhtml.js @ 3295

Last change on this file since 3295 was 3295, checked in by patdenice, 15 years ago

New extension added:
FCK Editor (2.0.a)

File size: 16.5 KB
Line 
1/*
2 * FCKeditor - The text editor for Internet - http://www.fckeditor.net
3 * Copyright (C) 2003-2009 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( FCKTools.GetElementDocument( node ), 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        // According to the doctype set the proper end for self-closing tags
74        // HTML: <br>
75        // XHTML: Add a space, like <br/> -> <br />
76        if (FCKConfig.DocType.length > 0 && FCKRegexLib.HtmlDocType.test( FCKConfig.DocType ) )
77                sXHTML = sXHTML.replace( FCKRegexLib.SpaceNoClose, '>');
78        else
79                sXHTML = sXHTML.replace( FCKRegexLib.SpaceNoClose, ' />');
80
81        if ( FCKConfig.ForceSimpleAmpersand )
82                sXHTML = sXHTML.replace( FCKRegexLib.ForceSimpleAmpersand, '&' ) ;
83
84        if ( format )
85                sXHTML = FCKCodeFormatter.Format( sXHTML ) ;
86
87        // Now we put back the SpecialBlocks contents.
88        for ( var i = 0 ; i < FCKXHtml.SpecialBlocks.length ; i++ )
89        {
90                var oRegex = new RegExp( '___FCKsi___' + i ) ;
91                sXHTML = sXHTML.replace( oRegex, FCKXHtml.SpecialBlocks[i] ) ;
92        }
93
94        // Replace entities marker with the ampersand.
95        sXHTML = sXHTML.replace( FCKRegexLib.GeckoEntitiesMarker, '&' ) ;
96
97        // Restore the IsDirty state if it was not dirty.
98        if ( !bIsDirty )
99                FCK.ResetIsDirty() ;
100
101        FCKDomTools.EnforcePaddingNode( FCKTools.GetElementDocument( node ), FCKConfig.EnterMode ) ;
102        return sXHTML ;
103}
104
105FCKXHtml._AppendAttribute = function( xmlNode, attributeName, attributeValue )
106{
107        try
108        {
109                if ( attributeValue == undefined || attributeValue == null )
110                        attributeValue = '' ;
111                else if ( attributeValue.replace )
112                {
113                        if ( FCKConfig.ForceSimpleAmpersand )
114                                attributeValue = attributeValue.replace( /&/g, '___FCKAmp___' ) ;
115
116                        // Entities must be replaced in the attribute values.
117                        attributeValue = attributeValue.replace( FCKXHtmlEntities.EntitiesRegex, FCKXHtml_GetEntity ) ;
118                }
119
120                // Create the attribute.
121                var oXmlAtt = this.XML.createAttribute( attributeName ) ;
122                oXmlAtt.value = attributeValue ;
123
124                // Set the attribute in the node.
125                xmlNode.attributes.setNamedItem( oXmlAtt ) ;
126        }
127        catch (e)
128        {}
129}
130
131FCKXHtml._AppendChildNodes = function( xmlNode, htmlNode, isBlockElement )
132{
133        var oNode = htmlNode.firstChild ;
134
135        while ( oNode )
136        {
137                this._AppendNode( xmlNode, oNode ) ;
138                oNode = oNode.nextSibling ;
139        }
140
141        // Trim block elements. This is also needed to avoid Firefox leaving extra
142        // BRs at the end of them.
143        if ( isBlockElement && htmlNode.tagName && htmlNode.tagName.toLowerCase() != 'pre' )
144        {
145                FCKDomTools.TrimNode( xmlNode ) ;
146
147                if ( FCKConfig.FillEmptyBlocks )
148                {
149                        var lastChild = xmlNode.lastChild ;
150                        if ( lastChild && lastChild.nodeType == 1 && lastChild.nodeName == 'br' )
151                                this._AppendEntity( xmlNode, this._NbspEntity ) ;
152                }
153        }
154
155        // If the resulting node is empty.
156        if ( xmlNode.childNodes.length == 0 )
157        {
158                if ( isBlockElement && FCKConfig.FillEmptyBlocks )
159                {
160                        this._AppendEntity( xmlNode, this._NbspEntity ) ;
161                        return xmlNode ;
162                }
163
164                var sNodeName = xmlNode.nodeName ;
165
166                // Some inline elements are required to have something inside (span, strong, etc...).
167                if ( FCKListsLib.InlineChildReqElements[ sNodeName ] )
168                        return null ;
169
170                // We can't use short representation of empty elements that are not marked
171                // as empty in th XHTML DTD.
172                if ( !FCKListsLib.EmptyElements[ sNodeName ] )
173                        xmlNode.appendChild( this.XML.createTextNode('') ) ;
174        }
175
176        return xmlNode ;
177}
178
179FCKXHtml._AppendNode = function( xmlNode, htmlNode )
180{
181        if ( !htmlNode )
182                return false ;
183
184        switch ( htmlNode.nodeType )
185        {
186                // Element Node.
187                case 1 :
188                        // If we detect a <br> inside a <pre> in Gecko, turn it into a line break instead.
189                        // This is a workaround for the Gecko bug here: https://bugzilla.mozilla.org/show_bug.cgi?id=92921
190                        if ( FCKBrowserInfo.IsGecko
191                                        && htmlNode.tagName.toLowerCase() == 'br'
192                                        && htmlNode.parentNode.tagName.toLowerCase() == 'pre' )
193                        {
194                                var val = '\r' ;
195                                if ( htmlNode == htmlNode.parentNode.firstChild )
196                                        val += '\r' ;
197                                return FCKXHtml._AppendNode( xmlNode, this.XML.createTextNode( val ) ) ;
198                        }
199
200                        // Here we found an element that is not the real element, but a
201                        // fake one (like the Flash placeholder image), so we must get the real one.
202                        if ( htmlNode.getAttribute('_fckfakelement') )
203                                return FCKXHtml._AppendNode( xmlNode, FCK.GetRealElement( htmlNode ) ) ;
204
205                        // Ignore bogus BR nodes in the DOM.
206                        if ( FCKBrowserInfo.IsGecko &&
207                                        ( htmlNode.hasAttribute('_moz_editor_bogus_node') || htmlNode.getAttribute( 'type' ) == '_moz' ) )
208                        {
209                                if ( htmlNode.nextSibling )
210                                        return false ;
211                                else
212                                {
213                                        htmlNode.removeAttribute( '_moz_editor_bogus_node' ) ;
214                                        htmlNode.removeAttribute( 'type' ) ;
215                                }
216                        }
217
218                        // This is for elements that are instrumental to FCKeditor and
219                        // must be removed from the final HTML.
220                        if ( htmlNode.getAttribute('_fcktemp') )
221                                return false ;
222
223                        // Get the element name.
224                        var sNodeName = htmlNode.tagName.toLowerCase()  ;
225
226                        if ( FCKBrowserInfo.IsIE )
227                        {
228                                // IE doens't include the scope name in the nodeName. So, add the namespace.
229                                if ( htmlNode.scopeName && htmlNode.scopeName != 'HTML' && htmlNode.scopeName != 'FCK' )
230                                        sNodeName = htmlNode.scopeName.toLowerCase() + ':' + sNodeName ;
231                        }
232                        else
233                        {
234                                if ( sNodeName.StartsWith( 'fck:' ) )
235                                        sNodeName = sNodeName.Remove( 0,4 ) ;
236                        }
237
238                        // Check if the node name is valid, otherwise ignore this tag.
239                        // If the nodeName starts with a slash, it is a orphan closing tag.
240                        // On some strange cases, the nodeName is empty, even if the node exists.
241                        if ( !FCKRegexLib.ElementName.test( sNodeName ) )
242                                return false ;
243
244                        // The already processed nodes must be marked to avoid then to be duplicated (bad formatted HTML).
245                        // So here, the "mark" is checked... if the element is Ok, then mark it.
246                        if ( htmlNode._fckxhtmljob && htmlNode._fckxhtmljob == FCKXHtml.CurrentJobNum )
247                                return false ;
248
249                        var oNode = this.XML.createElement( sNodeName ) ;
250
251                        // Add all attributes.
252                        FCKXHtml._AppendAttributes( xmlNode, htmlNode, oNode, sNodeName ) ;
253
254                        htmlNode._fckxhtmljob = FCKXHtml.CurrentJobNum ;
255
256                        // Tag specific processing.
257                        var oTagProcessor = FCKXHtml.TagProcessors[ sNodeName ] ;
258
259                        if ( oTagProcessor )
260                                oNode = oTagProcessor( oNode, htmlNode, xmlNode ) ;
261                        else
262                                oNode = this._AppendChildNodes( oNode, htmlNode, Boolean( FCKListsLib.NonEmptyBlockElements[ sNodeName ] ) ) ;
263
264                        if ( !oNode )
265                                return false ;
266
267                        xmlNode.appendChild( oNode ) ;
268
269                        break ;
270
271                // Text Node.
272                case 3 :
273                        if ( htmlNode.parentNode && htmlNode.parentNode.nodeName.IEquals( 'pre' ) )
274                                return this._AppendTextNode( xmlNode, htmlNode.nodeValue ) ;
275                        return this._AppendTextNode( xmlNode, htmlNode.nodeValue.ReplaceNewLineChars(' ') ) ;
276
277                // Comment
278                case 8 :
279                        // IE catches the <!DOTYPE ... > as a comment, but it has no
280                        // innerHTML, so we can catch it, and ignore it.
281                        if ( FCKBrowserInfo.IsIE && !htmlNode.innerHTML )
282                                break ;
283
284                        try { xmlNode.appendChild( this.XML.createComment( htmlNode.nodeValue ) ) ; }
285                        catch (e) { /* Do nothing... probably this is a wrong format comment. */ }
286                        break ;
287
288                // Unknown Node type.
289                default :
290                        xmlNode.appendChild( this.XML.createComment( "Element not supported - Type: " + htmlNode.nodeType + " Name: " + htmlNode.nodeName ) ) ;
291                        break ;
292        }
293        return true ;
294}
295
296// Append an item to the SpecialBlocks array and returns the tag to be used.
297FCKXHtml._AppendSpecialItem = function( item )
298{
299        return '___FCKsi___' + ( FCKXHtml.SpecialBlocks.push( item ) - 1 ) ;
300}
301
302FCKXHtml._AppendEntity = function( xmlNode, entity )
303{
304        xmlNode.appendChild( this.XML.createTextNode( '#?-:' + entity + ';' ) ) ;
305}
306
307FCKXHtml._AppendTextNode = function( targetNode, textValue )
308{
309        var bHadText = textValue.length > 0 ;
310        if ( bHadText )
311                targetNode.appendChild( this.XML.createTextNode( textValue.replace( FCKXHtmlEntities.EntitiesRegex, FCKXHtml_GetEntity ) ) ) ;
312        return bHadText ;
313}
314
315// Retrieves a entity (internal format) for a given character.
316function FCKXHtml_GetEntity( character )
317{
318        // We cannot simply place the entities in the text, because the XML parser
319        // will translate & to &amp;. So we use a temporary marker which is replaced
320        // in the end of the processing.
321        var sEntity = FCKXHtmlEntities.Entities[ character ] || ( '#' + character.charCodeAt(0) ) ;
322        return '#?-:' + sEntity + ';' ;
323}
324
325// An object that hold tag specific operations.
326FCKXHtml.TagProcessors =
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        area : function( node, htmlNode )
353        {
354                var sSavedUrl = htmlNode.getAttribute( '_fcksavedurl' ) ;
355                if ( sSavedUrl != null )
356                        FCKXHtml._AppendAttribute( node, 'href', sSavedUrl ) ;
357
358                // IE ignores the "COORDS" and "SHAPE" attribute so we must add it manually.
359                if ( FCKBrowserInfo.IsIE )
360                {
361                        if ( ! node.attributes.getNamedItem( 'coords' ) )
362                        {
363                                var sCoords = htmlNode.getAttribute( 'coords', 2 ) ;
364                                if ( sCoords && sCoords != '0,0,0' )
365                                        FCKXHtml._AppendAttribute( node, 'coords', sCoords ) ;
366                        }
367
368                        if ( ! node.attributes.getNamedItem( 'shape' ) )
369                        {
370                                var sShape = htmlNode.getAttribute( 'shape', 2 ) ;
371                                if ( sShape && sShape.length > 0 )
372                                        FCKXHtml._AppendAttribute( node, 'shape', sShape.toLowerCase() ) ;
373                        }
374                }
375
376                return node ;
377        },
378
379        body : function( node, htmlNode )
380        {
381                node = FCKXHtml._AppendChildNodes( node, htmlNode, false ) ;
382                // Remove spellchecker attributes added for Firefox when converting to HTML code (Bug #1351).
383                node.removeAttribute( 'spellcheck' ) ;
384                return node ;
385        },
386
387        // IE loses contents of iframes, and Gecko does give it back HtmlEncoded
388        // Note: Opera does lose the content and doesn't provide it in the innerHTML string
389        iframe : function( node, htmlNode )
390        {
391                var sHtml = htmlNode.innerHTML ;
392
393                // Gecko does give back the encoded html
394                if ( FCKBrowserInfo.IsGecko )
395                        sHtml = FCKTools.HTMLDecode( sHtml );
396
397                // Remove the saved urls here as the data won't be processed as nodes
398                sHtml = sHtml.replace( /\s_fcksavedurl="[^"]*"/g, '' ) ;
399
400                node.appendChild( FCKXHtml.XML.createTextNode( FCKXHtml._AppendSpecialItem( sHtml ) ) ) ;
401
402                return node ;
403        },
404
405        img : function( node, htmlNode )
406        {
407                // The "ALT" attribute is required in XHTML.
408                if ( ! node.attributes.getNamedItem( 'alt' ) )
409                        FCKXHtml._AppendAttribute( node, 'alt', '' ) ;
410
411                var sSavedUrl = htmlNode.getAttribute( '_fcksavedurl' ) ;
412                if ( sSavedUrl != null )
413                        FCKXHtml._AppendAttribute( node, 'src', sSavedUrl ) ;
414
415                // Bug #768 : If the width and height are defined inline CSS,
416                // don't define it again in the HTML attributes.
417                if ( htmlNode.style.width )
418                        node.removeAttribute( 'width' ) ;
419                if ( htmlNode.style.height )
420                        node.removeAttribute( 'height' ) ;
421
422                return node ;
423        },
424
425        // Fix orphaned <li> nodes (Bug #503).
426        li : function( node, htmlNode, targetNode )
427        {
428                // If the XML parent node is already a <ul> or <ol>, then add the <li> as usual.
429                if ( targetNode.nodeName.IEquals( ['ul', 'ol'] ) )
430                        return FCKXHtml._AppendChildNodes( node, htmlNode, true ) ;
431
432                var newTarget = FCKXHtml.XML.createElement( 'ul' ) ;
433
434                // Reset the _fckxhtmljob so the HTML node is processed again.
435                htmlNode._fckxhtmljob = null ;
436
437                // Loop through all sibling LIs, adding them to the <ul>.
438                do
439                {
440                        FCKXHtml._AppendNode( newTarget, htmlNode ) ;
441
442                        // Look for the next element following this <li>.
443                        do
444                        {
445                                htmlNode = FCKDomTools.GetNextSibling( htmlNode ) ;
446
447                        } while ( htmlNode && htmlNode.nodeType == 3 && htmlNode.nodeValue.Trim().length == 0 )
448
449                }       while ( htmlNode && htmlNode.nodeName.toLowerCase() == 'li' )
450
451                return newTarget ;
452        },
453
454        // Fix nested <ul> and <ol>.
455        ol : function( node, htmlNode, targetNode )
456        {
457                if ( htmlNode.innerHTML.Trim().length == 0 )
458                        return false ;
459
460                var ePSibling = targetNode.lastChild ;
461
462                if ( ePSibling && ePSibling.nodeType == 3 )
463                        ePSibling = ePSibling.previousSibling ;
464
465                if ( ePSibling && ePSibling.nodeName.toUpperCase() == 'LI' )
466                {
467                        htmlNode._fckxhtmljob = null ;
468                        FCKXHtml._AppendNode( ePSibling, htmlNode ) ;
469                        return false ;
470                }
471
472                node = FCKXHtml._AppendChildNodes( node, htmlNode ) ;
473
474                return node ;
475        },
476
477        pre : function ( node, htmlNode )
478        {
479                var firstChild = htmlNode.firstChild ;
480
481                if ( firstChild && firstChild.nodeType == 3 )
482                        node.appendChild( FCKXHtml.XML.createTextNode( FCKXHtml._AppendSpecialItem( '\r\n' ) ) ) ;
483
484                FCKXHtml._AppendChildNodes( node, htmlNode, true ) ;
485
486                return node ;
487        },
488
489        script : function( node, htmlNode )
490        {
491                // The "TYPE" attribute is required in XHTML.
492                if ( ! node.attributes.getNamedItem( 'type' ) )
493                        FCKXHtml._AppendAttribute( node, 'type', 'text/javascript' ) ;
494
495                node.appendChild( FCKXHtml.XML.createTextNode( FCKXHtml._AppendSpecialItem( htmlNode.text ) ) ) ;
496
497                return node ;
498        },
499
500        span : function( node, htmlNode )
501        {
502                // Firefox may create empty tags when deleting the selection in some special cases (SF-BUG 1084404).
503                if ( htmlNode.innerHTML.length == 0 )
504                        return false ;
505
506                node = FCKXHtml._AppendChildNodes( node, htmlNode, false ) ;
507
508                return node ;
509        },
510
511        style : function( node, htmlNode )
512        {
513                // The "TYPE" attribute is required in XHTML.
514                if ( ! node.attributes.getNamedItem( 'type' ) )
515                        FCKXHtml._AppendAttribute( node, 'type', 'text/css' ) ;
516
517                var cssText = htmlNode.innerHTML ;
518                if ( FCKBrowserInfo.IsIE )      // Bug #403 : IE always appends a \r\n to the beginning of StyleNode.innerHTML
519                        cssText = cssText.replace( /^(\r\n|\n|\r)/, '' ) ;
520
521                node.appendChild( FCKXHtml.XML.createTextNode( FCKXHtml._AppendSpecialItem( cssText ) ) ) ;
522
523                return node ;
524        },
525
526        title : function( node, htmlNode )
527        {
528                node.appendChild( FCKXHtml.XML.createTextNode( FCK.EditorDocument.title ) ) ;
529
530                return node ;
531        }
532} ;
533
534FCKXHtml.TagProcessors.ul = FCKXHtml.TagProcessors.ol ;
Note: See TracBrowser for help on using the repository browser.