Nation Notes module contributed by Z&H Healthcare.
[openemr.git] / library / custom_template / ckeditor / _source / core / htmlparser / fragment.js
blob576b50b56291b82c014bd6254fe4b0a83018dee0
1 /*
2 Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.
3 For licensing, see LICENSE.html or http://ckeditor.com/license
4 */
6 /**
7  * A lightweight representation of an HTML DOM structure.
8  * @constructor
9  * @example
10  */
11 CKEDITOR.htmlParser.fragment = function()
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 = [];
22         /**
23          * Get the fragment parent. Should always be null.
24          * @type Object
25          * @default null
26          * @example
27          */
28         this.parent = null;
30         /** @private */
31         this._ =
32         {
33                 isBlockLike : true,
34                 hasInlineStarted : false
35         };
38 (function()
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};
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;
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;
73                 function checkPending( newTagName )
74                 {
75                         var pendingBRsSent;
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 ];
86                                         if ( ( !currentDtd || currentDtd[ pendingName ] ) && ( !newTagName || !pendingDtd || pendingDtd[ newTagName ] || !CKEDITOR.dtd[ newTagName ] ) )
87                                         {
88                                                 if ( !pendingBRsSent )
89                                                 {
90                                                         sendPendingBRs();
91                                                         pendingBRsSent = 1;
92                                                 }
94                                                 // Get a clone for the pending element.
95                                                 pendingElement = pendingElement.clone();
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;
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                 }
111                 function sendPendingBRs( brsToIgnore )
112                 {
113                         while ( pendingBRs.length - ( brsToIgnore || 0 ) > 0 )
114                                 currentNode.add( pendingBRs.shift() );
115                 }
117                 function addElement( element, target, enforceCurrent )
118                 {
119                         target = target || currentNode || fragment;
121                         // If the target is the fragment and this inline 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[ 'data-cke-real-element-type' ] ) )
129                                         elementName = realElementName;
130                                 else
131                                         elementName =  element.name;
133                                 if ( elementName && elementName in CKEDITOR.dtd.$inline )
134                                 {
135                                         var savedCurrent = currentNode;
137                                         // Create a <p> in the fragment.
138                                         currentNode = target;
139                                         parser.onTagOpen( fixForBody, {} );
141                                         // The new target now is the <p>.
142                                         target = currentNode;
144                                         if ( enforceCurrent )
145                                                 currentNode = savedCurrent;
146                                 }
147                         }
149                         // Rtrim empty spaces on block end boundary. (#3585)
150                         if ( element._.isBlockLike
151                                  && element.name != 'pre' )
152                         {
154                                 var length = element.children.length,
155                                         lastChild = element.children[ length - 1 ],
156                                         text;
157                                 if ( lastChild && lastChild.type == CKEDITOR.NODE_TEXT )
158                                 {
159                                         if ( !( text = CKEDITOR.tools.rtrim( lastChild.value ) ) )
160                                                 element.children.length = length -1;
161                                         else
162                                                 lastChild.value = text;
163                                 }
164                         }
166                         target.add( element );
168                         if ( element.returnPoint )
169                         {
170                                 currentNode = element.returnPoint;
171                                 delete element.returnPoint;
172                         }
173                 }
175                 parser.onTagOpen = function( tagName, attributes, selfClosing )
176                 {
177                         var element = new CKEDITOR.htmlParser.element( tagName, attributes );
179                         // "isEmpty" will be always "false" for unknown elements, so we
180                         // must force it if the parser has identified it as a selfClosing tag.
181                         if ( element.isUnknown && selfClosing )
182                                 element.isEmpty = true;
184                         // This is a tag to be removed if empty, so do not add it immediately.
185                         if ( CKEDITOR.dtd.$removeEmpty[ tagName ] )
186                         {
187                                 pendingInline.push( element );
188                                 return;
189                         }
190                         else if ( tagName == 'pre' )
191                                 inPre = true;
192                         else if ( tagName == 'br' && inPre )
193                         {
194                                 currentNode.add( new CKEDITOR.htmlParser.text( '\n' ) );
195                                 return;
196                         }
198                         if ( tagName == 'br' )
199                         {
200                                 pendingBRs.push( element );
201                                 return;
202                         }
204                         var currentName = currentNode.name;
206                         var currentDtd = currentName
207                                 && ( CKEDITOR.dtd[ currentName ]
208                                         || ( currentNode._.isBlockLike ? CKEDITOR.dtd.div : CKEDITOR.dtd.span ) );
210                         // If the element cannot be child of the current element.
211                         if ( currentDtd   // Fragment could receive any elements.
212                                  && !element.isUnknown && !currentNode.isUnknown && !currentDtd[ tagName ] )
213                         {
215                                 var reApply = false,
216                                         addPoint;   // New position to start adding nodes.
218                                 // Fixing malformed nested lists by moving it into a previous list item. (#3828)
219                                 if ( tagName in listBlocks
220                                         && currentName in listBlocks )
221                                 {
222                                         var children = currentNode.children,
223                                                 lastChild = children[ children.length - 1 ];
225                                         // Establish the list item if it's not existed.
226                                         if ( !( lastChild && lastChild.name in listItems ) )
227                                                 addElement( ( lastChild = new CKEDITOR.htmlParser.element( 'li' ) ), currentNode );
229                                         returnPoint = currentNode, addPoint = lastChild;
230                                 }
231                                 // If the element name is the same as the current element name,
232                                 // then just close the current one and append the new one to the
233                                 // parent. This situation usually happens with <p>, <li>, <dt> and
234                                 // <dd>, specially in IE. Do not enter in this if block in this case.
235                                 else if ( tagName == currentName )
236                                 {
237                                         addElement( currentNode, currentNode.parent );
238                                 }
239                                 else if ( tagName in CKEDITOR.dtd.$listItem )
240                                 {
241                                         parser.onTagOpen( 'ul', {} );
242                                         addPoint = currentNode;
243                                         reApply = true;
244                                 }
245                                 else
246                                 {
247                                         if ( nonBreakingBlocks[ currentName ] )
248                                         {
249                                                 if ( !returnPoint )
250                                                         returnPoint = currentNode;
251                                         }
252                                         else
253                                         {
254                                                 addElement( currentNode, currentNode.parent, true );
256                                                 if ( !optionalClose[ currentName ] )
257                                                 {
258                                                         // The current element is an inline element, which
259                                                         // cannot hold the new one. Put it in the pending list,
260                                                         // and try adding the new one after it.
261                                                         pendingInline.unshift( currentNode );
262                                                 }
263                                         }
265                                         reApply = true;
266                                 }
268                                 if ( addPoint )
269                                         currentNode = addPoint;
270                                 // Try adding it to the return point, or the parent element.
271                                 else
272                                         currentNode = currentNode.returnPoint || currentNode.parent;
274                                 if ( reApply )
275                                 {
276                                         parser.onTagOpen.apply( this, arguments );
277                                         return;
278                                 }
279                         }
281                         checkPending( tagName );
282                         sendPendingBRs();
284                         element.parent = currentNode;
285                         element.returnPoint = returnPoint;
286                         returnPoint = 0;
288                         if ( element.isEmpty )
289                                 addElement( element );
290                         else
291                                 currentNode = element;
292                 };
294                 parser.onTagClose = function( tagName )
295                 {
296                         // Check if there is any pending tag to be closed.
297                         for ( var i = pendingInline.length - 1 ; i >= 0 ; i-- )
298                         {
299                                 // If found, just remove it from the list.
300                                 if ( tagName == pendingInline[ i ].name )
301                                 {
302                                         pendingInline.splice( i, 1 );
303                                         return;
304                                 }
305                         }
307                         var pendingAdd = [],
308                                 newPendingInline = [],
309                                 candidate = currentNode;
311                         while ( candidate.type && candidate.name != tagName )
312                         {
313                                 // If this is an inline element, add it to the pending list, if we're
314                                 // really closing one of the parents element later, they will continue
315                                 // after it.
316                                 if ( !candidate._.isBlockLike )
317                                         newPendingInline.unshift( candidate );
319                                 // This node should be added to it's parent at this point. But,
320                                 // it should happen only if the closing tag is really closing
321                                 // one of the nodes. So, for now, we just cache it.
322                                 pendingAdd.push( candidate );
324                                 candidate = candidate.parent;
325                         }
327                         if ( candidate.type )
328                         {
329                                 // Add all elements that have been found in the above loop.
330                                 for ( i = 0 ; i < pendingAdd.length ; i++ )
331                                 {
332                                         var node = pendingAdd[ i ];
333                                         addElement( node, node.parent );
334                                 }
336                                 currentNode = candidate;
338                                 if ( currentNode.name == 'pre' )
339                                         inPre = false;
341                                 if ( candidate._.isBlockLike )
342                                         sendPendingBRs();
344                                 addElement( candidate, candidate.parent );
346                                 // The parent should start receiving new nodes now, except if
347                                 // addElement changed the currentNode.
348                                 if ( candidate == currentNode )
349                                         currentNode = currentNode.parent;
351                                 pendingInline = pendingInline.concat( newPendingInline );
352                         }
354                         if ( tagName == 'body' )
355                                 fixForBody = false;
356                 };
358                 parser.onText = function( text )
359                 {
360                         // Trim empty spaces at beginning of element contents except <pre>.
361                         if ( !currentNode._.hasInlineStarted && !inPre )
362                         {
363                                 text = CKEDITOR.tools.ltrim( text );
365                                 if ( text.length === 0 )
366                                         return;
367                         }
369                         sendPendingBRs();
370                         checkPending();
372                         if ( fixForBody
373                                  && ( !currentNode.type || currentNode.name == 'body' )
374                                  && CKEDITOR.tools.trim( text ) )
375                         {
376                                 this.onTagOpen( fixForBody, {} );
377                         }
379                         // Shrinking consequential spaces into one single for all elements
380                         // text contents.
381                         if ( !inPre )
382                                 text = text.replace( /[\t\r\n ]{2,}|[\t\r\n]/g, ' ' );
384                         currentNode.add( new CKEDITOR.htmlParser.text( text ) );
385                 };
387                 parser.onCDATA = function( cdata )
388                 {
389                         currentNode.add( new CKEDITOR.htmlParser.cdata( cdata ) );
390                 };
392                 parser.onComment = function( comment )
393                 {
394                         sendPendingBRs();
395                         checkPending();
396                         currentNode.add( new CKEDITOR.htmlParser.comment( comment ) );
397                 };
399                 // Parse it.
400                 parser.parse( fragmentHtml );
402                 // Send all pending BRs except one, which we consider a unwanted bogus. (#5293)
403                 sendPendingBRs( !CKEDITOR.env.ie && 1 );
405                 // Close all pending nodes.
406                 while ( currentNode.type )
407                 {
408                         var parent = currentNode.parent,
409                                 node = currentNode;
411                         if ( fixForBody
412                                  && ( !parent.type || parent.name == 'body' )
413                                  && !CKEDITOR.dtd.$body[ node.name ] )
414                         {
415                                 currentNode = parent;
416                                 parser.onTagOpen( fixForBody, {} );
417                                 parent = currentNode;
418                         }
420                         parent.add( node );
421                         currentNode = parent;
422                 }
424                 return fragment;
425         };
427         CKEDITOR.htmlParser.fragment.prototype =
428         {
429                 /**
430                  * Adds a node to this fragment.
431                  * @param {Object} node The node to be added. It can be any of of the
432                  *              following types: {@link CKEDITOR.htmlParser.element},
433                  *              {@link CKEDITOR.htmlParser.text} and
434                  *              {@link CKEDITOR.htmlParser.comment}.
435                  * @example
436                  */
437                 add : function( node )
438                 {
439                         var len = this.children.length,
440                                 previous = len > 0 && this.children[ len - 1 ] || null;
442                         if ( previous )
443                         {
444                                 // If the block to be appended is following text, trim spaces at
445                                 // the right of it.
446                                 if ( node._.isBlockLike && previous.type == CKEDITOR.NODE_TEXT )
447                                 {
448                                         previous.value = CKEDITOR.tools.rtrim( previous.value );
450                                         // If we have completely cleared the previous node.
451                                         if ( previous.value.length === 0 )
452                                         {
453                                                 // Remove it from the list and add the node again.
454                                                 this.children.pop();
455                                                 this.add( node );
456                                                 return;
457                                         }
458                                 }
460                                 previous.next = node;
461                         }
463                         node.previous = previous;
464                         node.parent = this;
466                         this.children.push( node );
468                         this._.hasInlineStarted = node.type == CKEDITOR.NODE_TEXT || ( node.type == CKEDITOR.NODE_ELEMENT && !node._.isBlockLike );
469                 },
471                 /**
472                  * Writes the fragment HTML to a CKEDITOR.htmlWriter.
473                  * @param {CKEDITOR.htmlWriter} writer The writer to which write the HTML.
474                  * @example
475                  * var writer = new CKEDITOR.htmlWriter();
476                  * var fragment = CKEDITOR.htmlParser.fragment.fromHtml( '&lt;P&gt;&lt;B&gt;Example' );
477                  * fragment.writeHtml( writer )
478                  * alert( writer.getHtml() );  "&lt;p&gt;&lt;b&gt;Example&lt;/b&gt;&lt;/p&gt;"
479                  */
480                 writeHtml : function( writer, filter )
481                 {
482                         var isChildrenFiltered;
483                         this.filterChildren = function()
484                         {
485                                 var writer = new CKEDITOR.htmlParser.basicWriter();
486                                 this.writeChildrenHtml.call( this, writer, filter, true );
487                                 var html = writer.getHtml();
488                                 this.children = new CKEDITOR.htmlParser.fragment.fromHtml( html ).children;
489                                 isChildrenFiltered = 1;
490                         };
492                         // Filtering the root fragment before anything else.
493                         !this.name && filter && filter.onFragment( this );
495                         this.writeChildrenHtml( writer, isChildrenFiltered ? null : filter );
496                 },
498                 writeChildrenHtml : function( writer, filter )
499                 {
500                         for ( var i = 0 ; i < this.children.length ; i++ )
501                                 this.children[i].writeHtml( writer, filter );
502                 }
503         };
504 })();