source: trunk/phpgwapi/js/ckeditor/_source/core/htmlparser/fragment.js @ 2862

Revision 2862, 13.7 KB checked in by rodsouza, 14 years ago (diff)

Ticket #663 - Atualizando e centralizando o CKEditor (v. 3.2.1)

Line 
1/*
2Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.html or http://ckeditor.com/license
4*/
5
6/**
7 * A lightweight representation of an HTML DOM structure.
8 * @constructor
9 * @example
10 */
11CKEDITOR.htmlParser.fragment = function()
12{
13        /**
14         * The nodes contained in the root of this fragment.
15         * @type Array
16         * @example
17         * var fragment = CKEDITOR.htmlParser.fragment.fromHtml( '<b>Sample</b> Text' );
18         * alert( fragment.children.length );  "2"
19         */
20        this.children = [];
21
22        /**
23         * Get the fragment parent. Should always be null.
24         * @type Object
25         * @default null
26         * @example
27         */
28        this.parent = null;
29
30        /** @private */
31        this._ =
32        {
33                isBlockLike : true,
34                hasInlineStarted : false
35        };
36};
37
38(function()
39{
40        // Elements which the end tag is marked as optional in the HTML 4.01 DTD
41        // (expect empty elements).
42        var optionalClose = {colgroup:1,dd:1,dt:1,li:1,option:1,p:1,td:1,tfoot:1,th:1,thead:1,tr:1};
43
44        // Block-level elements whose internal structure should be respected during
45        // parser fixing.
46        var nonBreakingBlocks = CKEDITOR.tools.extend(
47                        {table:1,ul:1,ol:1,dl:1},
48                        CKEDITOR.dtd.table, CKEDITOR.dtd.ul, CKEDITOR.dtd.ol, CKEDITOR.dtd.dl ),
49                listBlocks = CKEDITOR.dtd.$list, listItems = CKEDITOR.dtd.$listItem;
50
51        /**
52         * Creates a {@link CKEDITOR.htmlParser.fragment} from an HTML string.
53         * @param {String} fragmentHtml The HTML to be parsed, filling the fragment.
54         * @param {Number} [fixForBody=false] Wrap body with specified element if needed.
55         * @returns CKEDITOR.htmlParser.fragment The fragment created.
56         * @example
57         * var fragment = CKEDITOR.htmlParser.fragment.fromHtml( '<b>Sample</b> Text' );
58         * alert( fragment.children[0].name );  "b"
59         * alert( fragment.children[1].value );  " Text"
60         */
61        CKEDITOR.htmlParser.fragment.fromHtml = function( fragmentHtml, fixForBody )
62        {
63                var parser = new CKEDITOR.htmlParser(),
64                        html = [],
65                        fragment = new CKEDITOR.htmlParser.fragment(),
66                        pendingInline = [],
67                        pendingBRs = [],
68                        currentNode = fragment,
69                    // Indicate we're inside a <pre> element, spaces should be touched differently.
70                        inPre = false,
71                        returnPoint;
72
73                function checkPending( newTagName )
74                {
75                        var pendingBRsSent;
76
77                        if ( pendingInline.length > 0 )
78                        {
79                                for ( var i = 0 ; i < pendingInline.length ; i++ )
80                                {
81                                        var pendingElement = pendingInline[ i ],
82                                                pendingName = pendingElement.name,
83                                                pendingDtd = CKEDITOR.dtd[ pendingName ],
84                                                currentDtd = currentNode.name && CKEDITOR.dtd[ currentNode.name ];
85
86                                        if ( ( !currentDtd || currentDtd[ pendingName ] ) && ( !newTagName || !pendingDtd || pendingDtd[ newTagName ] || !CKEDITOR.dtd[ newTagName ] ) )
87                                        {
88                                                if ( !pendingBRsSent )
89                                                {
90                                                        sendPendingBRs();
91                                                        pendingBRsSent = 1;
92                                                }
93
94                                                // Get a clone for the pending element.
95                                                pendingElement = pendingElement.clone();
96
97                                                // Add it to the current node and make it the current,
98                                                // so the new element will be added inside of it.
99                                                pendingElement.parent = currentNode;
100                                                currentNode = pendingElement;
101
102                                                // Remove the pending element (back the index by one
103                                                // to properly process the next entry).
104                                                pendingInline.splice( i, 1 );
105                                                i--;
106                                        }
107                                }
108                        }
109                }
110
111                function sendPendingBRs()
112                {
113                        while ( pendingBRs.length )
114                                currentNode.add( pendingBRs.shift() );
115                }
116
117                function addElement( element, target, enforceCurrent )
118                {
119                        target = target || currentNode || fragment;
120
121                        // If the target is the fragment and this element can't go inside
122                        // body (if fixForBody).
123                        if ( fixForBody && !target.type )
124                        {
125                                var elementName, realElementName;
126                                if ( element.attributes
127                                         && ( realElementName =
128                                                  element.attributes[ '_cke_real_element_type' ] ) )
129                                        elementName = realElementName;
130                                else
131                                        elementName =  element.name;
132                                if ( elementName
133                                                && !( elementName in CKEDITOR.dtd.$body )
134                                                && !( elementName in CKEDITOR.dtd.$nonBodyContent )  )
135                                {
136                                        var savedCurrent = currentNode;
137
138                                        // Create a <p> in the fragment.
139                                        currentNode = target;
140                                        parser.onTagOpen( fixForBody, {} );
141
142                                        // The new target now is the <p>.
143                                        target = currentNode;
144
145                                        if ( enforceCurrent )
146                                                currentNode = savedCurrent;
147                                }
148                        }
149
150                        // Rtrim empty spaces on block end boundary. (#3585)
151                        if ( element._.isBlockLike
152                                 && element.name != 'pre' )
153                        {
154
155                                var length = element.children.length,
156                                        lastChild = element.children[ length - 1 ],
157                                        text;
158                                if ( lastChild && lastChild.type == CKEDITOR.NODE_TEXT )
159                                {
160                                        if ( !( text = CKEDITOR.tools.rtrim( lastChild.value ) ) )
161                                                element.children.length = length -1;
162                                        else
163                                                lastChild.value = text;
164                                }
165                        }
166
167                        target.add( element );
168
169                        if ( element.returnPoint )
170                        {
171                                currentNode = element.returnPoint;
172                                delete element.returnPoint;
173                        }
174                }
175
176                parser.onTagOpen = function( tagName, attributes, selfClosing )
177                {
178                        var element = new CKEDITOR.htmlParser.element( tagName, attributes );
179
180                        // "isEmpty" will be always "false" for unknown elements, so we
181                        // must force it if the parser has identified it as a selfClosing tag.
182                        if ( element.isUnknown && selfClosing )
183                                element.isEmpty = true;
184
185                        // This is a tag to be removed if empty, so do not add it immediately.
186                        if ( CKEDITOR.dtd.$removeEmpty[ tagName ] )
187                        {
188                                pendingInline.push( element );
189                                return;
190                        }
191                        else if ( tagName == 'pre' )
192                                inPre = true;
193                        else if ( tagName == 'br' && inPre )
194                        {
195                                currentNode.add( new CKEDITOR.htmlParser.text( '\n' ) );
196                                return;
197                        }
198
199                        if ( tagName == 'br' )
200                        {
201                                pendingBRs.push( element );
202                                return;
203                        }
204
205                        var currentName = currentNode.name;
206
207                        var currentDtd = currentName
208                                && ( CKEDITOR.dtd[ currentName ]
209                                        || ( currentNode._.isBlockLike ? CKEDITOR.dtd.div : CKEDITOR.dtd.span ) );
210
211                        // If the element cannot be child of the current element.
212                        if ( currentDtd   // Fragment could receive any elements.
213                                 && !element.isUnknown && !currentNode.isUnknown && !currentDtd[ tagName ] )
214                        {
215
216                                var reApply = false,
217                                        addPoint;   // New position to start adding nodes.
218
219                                // Fixing malformed nested lists by moving it into a previous list item. (#3828)
220                                if ( tagName in listBlocks
221                                        && currentName in listBlocks )
222                                {
223                                        var children = currentNode.children,
224                                                lastChild = children[ children.length - 1 ];
225
226                                        // Establish the list item if it's not existed.
227                                        if ( !( lastChild && lastChild.name in listItems ) )
228                                                addElement( ( lastChild = new CKEDITOR.htmlParser.element( 'li' ) ), currentNode );
229
230                                        returnPoint = currentNode, addPoint = lastChild;
231                                }
232                                // If the element name is the same as the current element name,
233                                // then just close the current one and append the new one to the
234                                // parent. This situation usually happens with <p>, <li>, <dt> and
235                                // <dd>, specially in IE. Do not enter in this if block in this case.
236                                else if ( tagName == currentName )
237                                {
238                                        addElement( currentNode, currentNode.parent );
239                                }
240                                else
241                                {
242                                        if ( nonBreakingBlocks[ currentName ] )
243                                        {
244                                                if ( !returnPoint )
245                                                        returnPoint = currentNode;
246                                        }
247                                        else
248                                        {
249                                                addElement( currentNode, currentNode.parent, true );
250
251                                                if ( !optionalClose[ currentName ] )
252                                                {
253                                                        // The current element is an inline element, which
254                                                        // cannot hold the new one. Put it in the pending list,
255                                                        // and try adding the new one after it.
256                                                        pendingInline.unshift( currentNode );
257                                                }
258                                        }
259
260                                        reApply = true;
261                                }
262
263                                if ( addPoint )
264                                        currentNode = addPoint;
265                                // Try adding it to the return point, or the parent element.
266                                else
267                                        currentNode = currentNode.returnPoint || currentNode.parent;
268
269                                if ( reApply )
270                                {
271                                        parser.onTagOpen.apply( this, arguments );
272                                        return;
273                                }
274                        }
275
276                        checkPending( tagName );
277                        sendPendingBRs();
278
279                        element.parent = currentNode;
280                        element.returnPoint = returnPoint;
281                        returnPoint = 0;
282
283                        if ( element.isEmpty )
284                                addElement( element );
285                        else
286                                currentNode = element;
287                };
288
289                parser.onTagClose = function( tagName )
290                {
291                        // Check if there is any pending tag to be closed.
292                        for ( var i = pendingInline.length - 1 ; i >= 0 ; i-- )
293                        {
294                                // If found, just remove it from the list.
295                                if ( tagName == pendingInline[ i ].name )
296                                {
297                                        pendingInline.splice( i, 1 );
298                                        return;
299                                }
300                        }
301
302                        var pendingAdd = [],
303                                newPendingInline = [],
304                                candidate = currentNode;
305
306                        while ( candidate.type && candidate.name != tagName )
307                        {
308                                // If this is an inline element, add it to the pending list, if we're
309                                // really closing one of the parents element later, they will continue
310                                // after it.
311                                if ( !candidate._.isBlockLike )
312                                        newPendingInline.unshift( candidate );
313
314                                // This node should be added to it's parent at this point. But,
315                                // it should happen only if the closing tag is really closing
316                                // one of the nodes. So, for now, we just cache it.
317                                pendingAdd.push( candidate );
318
319                                candidate = candidate.parent;
320                        }
321
322                        if ( candidate.type )
323                        {
324                                // Add all elements that have been found in the above loop.
325                                for ( i = 0 ; i < pendingAdd.length ; i++ )
326                                {
327                                        var node = pendingAdd[ i ];
328                                        addElement( node, node.parent );
329                                }
330
331                                currentNode = candidate;
332
333                                if ( currentNode.name == 'pre' )
334                                        inPre = false;
335
336                                if ( candidate._.isBlockLike )
337                                        sendPendingBRs();
338
339                                addElement( candidate, candidate.parent );
340
341                                // The parent should start receiving new nodes now, except if
342                                // addElement changed the currentNode.
343                                if ( candidate == currentNode )
344                                        currentNode = currentNode.parent;
345
346                                pendingInline = pendingInline.concat( newPendingInline );
347                        }
348
349                        if ( tagName == 'body' )
350                                fixForBody = false;
351                };
352
353                parser.onText = function( text )
354                {
355                        // Trim empty spaces at beginning of element contents except <pre>.
356                        if ( !currentNode._.hasInlineStarted && !inPre )
357                        {
358                                text = CKEDITOR.tools.ltrim( text );
359
360                                if ( text.length === 0 )
361                                        return;
362                        }
363
364                        sendPendingBRs();
365                        checkPending();
366
367                        if ( fixForBody
368                                 && ( !currentNode.type || currentNode.name == 'body' )
369                                 && CKEDITOR.tools.trim( text ) )
370                        {
371                                this.onTagOpen( fixForBody, {} );
372                        }
373
374                        // Shrinking consequential spaces into one single for all elements
375                        // text contents.
376                        if ( !inPre )
377                                text = text.replace( /[\t\r\n ]{2,}|[\t\r\n]/g, ' ' );
378
379                        currentNode.add( new CKEDITOR.htmlParser.text( text ) );
380                };
381
382                parser.onCDATA = function( cdata )
383                {
384                        currentNode.add( new CKEDITOR.htmlParser.cdata( cdata ) );
385                };
386
387                parser.onComment = function( comment )
388                {
389                        currentNode.add( new CKEDITOR.htmlParser.comment( comment ) );
390                };
391
392                // Parse it.
393                parser.parse( fragmentHtml );
394
395                sendPendingBRs();
396
397                // Close all pending nodes.
398                while ( currentNode.type )
399                {
400                        var parent = currentNode.parent,
401                                node = currentNode;
402
403                        if ( fixForBody
404                                 && ( !parent.type || parent.name == 'body' )
405                                 && !CKEDITOR.dtd.$body[ node.name ] )
406                        {
407                                currentNode = parent;
408                                parser.onTagOpen( fixForBody, {} );
409                                parent = currentNode;
410                        }
411
412                        parent.add( node );
413                        currentNode = parent;
414                }
415
416                return fragment;
417        };
418
419        CKEDITOR.htmlParser.fragment.prototype =
420        {
421                /**
422                 * Adds a node to this fragment.
423                 * @param {Object} node The node to be added. It can be any of of the
424                 *              following types: {@link CKEDITOR.htmlParser.element},
425                 *              {@link CKEDITOR.htmlParser.text} and
426                 *              {@link CKEDITOR.htmlParser.comment}.
427                 * @example
428                 */
429                add : function( node )
430                {
431                        var len = this.children.length,
432                                previous = len > 0 && this.children[ len - 1 ] || null;
433
434                        if ( previous )
435                        {
436                                // If the block to be appended is following text, trim spaces at
437                                // the right of it.
438                                if ( node._.isBlockLike && previous.type == CKEDITOR.NODE_TEXT )
439                                {
440                                        previous.value = CKEDITOR.tools.rtrim( previous.value );
441
442                                        // If we have completely cleared the previous node.
443                                        if ( previous.value.length === 0 )
444                                        {
445                                                // Remove it from the list and add the node again.
446                                                this.children.pop();
447                                                this.add( node );
448                                                return;
449                                        }
450                                }
451
452                                previous.next = node;
453                        }
454
455                        node.previous = previous;
456                        node.parent = this;
457
458                        this.children.push( node );
459
460                        this._.hasInlineStarted = node.type == CKEDITOR.NODE_TEXT || ( node.type == CKEDITOR.NODE_ELEMENT && !node._.isBlockLike );
461                },
462
463                /**
464                 * Writes the fragment HTML to a CKEDITOR.htmlWriter.
465                 * @param {CKEDITOR.htmlWriter} writer The writer to which write the HTML.
466                 * @example
467                 * var writer = new CKEDITOR.htmlWriter();
468                 * var fragment = CKEDITOR.htmlParser.fragment.fromHtml( '&lt;P&gt;&lt;B&gt;Example' );
469                 * fragment.writeHtml( writer )
470                 * alert( writer.getHtml() );  "&lt;p&gt;&lt;b&gt;Example&lt;/b&gt;&lt;/p&gt;"
471                 */
472                writeHtml : function( writer, filter )
473                {
474                        var isChildrenFiltered;
475                        this.filterChildren = function()
476                        {
477                                var writer = new CKEDITOR.htmlParser.basicWriter();
478                                this.writeChildrenHtml.call( this, writer, filter, true );
479                                var html = writer.getHtml();
480                                this.children = new CKEDITOR.htmlParser.fragment.fromHtml( html ).children;
481                                isChildrenFiltered = 1;
482                        };
483
484                        // Filtering the root fragment before anything else.
485                        !this.name && filter && filter.onFragment( this );
486
487                        this.writeChildrenHtml( writer, isChildrenFiltered ? null : filter );
488                },
489
490                writeChildrenHtml : function( writer, filter )
491                {
492                        for ( var i = 0 ; i < this.children.length ; i++ )
493                                this.children[i].writeHtml( writer, filter );
494                }
495        };
496})();
Note: See TracBrowser for help on using the repository browser.