| [ Index ] |
PHP Cross Reference of Drupal 6 (gatewave) |
[Summary view] [Print] [Text view]
1 /* 2 Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. 3 For 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 */ 11 CKEDITOR.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( brsToIgnore ) 112 { 113 while ( pendingBRs.length - ( brsToIgnore || 0 ) > 0 ) 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 // Send all pending BRs except one, which we consider a unwanted bogus. (#5293) 396 sendPendingBRs( !CKEDITOR.env.ie && 1 ); 397 398 // Close all pending nodes. 399 while ( currentNode.type ) 400 { 401 var parent = currentNode.parent, 402 node = currentNode; 403 404 if ( fixForBody 405 && ( !parent.type || parent.name == 'body' ) 406 && !CKEDITOR.dtd.$body[ node.name ] ) 407 { 408 currentNode = parent; 409 parser.onTagOpen( fixForBody, {} ); 410 parent = currentNode; 411 } 412 413 parent.add( node ); 414 currentNode = parent; 415 } 416 417 return fragment; 418 }; 419 420 CKEDITOR.htmlParser.fragment.prototype = 421 { 422 /** 423 * Adds a node to this fragment. 424 * @param {Object} node The node to be added. It can be any of of the 425 * following types: {@link CKEDITOR.htmlParser.element}, 426 * {@link CKEDITOR.htmlParser.text} and 427 * {@link CKEDITOR.htmlParser.comment}. 428 * @example 429 */ 430 add : function( node ) 431 { 432 var len = this.children.length, 433 previous = len > 0 && this.children[ len - 1 ] || null; 434 435 if ( previous ) 436 { 437 // If the block to be appended is following text, trim spaces at 438 // the right of it. 439 if ( node._.isBlockLike && previous.type == CKEDITOR.NODE_TEXT ) 440 { 441 previous.value = CKEDITOR.tools.rtrim( previous.value ); 442 443 // If we have completely cleared the previous node. 444 if ( previous.value.length === 0 ) 445 { 446 // Remove it from the list and add the node again. 447 this.children.pop(); 448 this.add( node ); 449 return; 450 } 451 } 452 453 previous.next = node; 454 } 455 456 node.previous = previous; 457 node.parent = this; 458 459 this.children.push( node ); 460 461 this._.hasInlineStarted = node.type == CKEDITOR.NODE_TEXT || ( node.type == CKEDITOR.NODE_ELEMENT && !node._.isBlockLike ); 462 }, 463 464 /** 465 * Writes the fragment HTML to a CKEDITOR.htmlWriter. 466 * @param {CKEDITOR.htmlWriter} writer The writer to which write the HTML. 467 * @example 468 * var writer = new CKEDITOR.htmlWriter(); 469 * var fragment = CKEDITOR.htmlParser.fragment.fromHtml( '<P><B>Example' ); 470 * fragment.writeHtml( writer ) 471 * alert( writer.getHtml() ); "<p><b>Example</b></p>" 472 */ 473 writeHtml : function( writer, filter ) 474 { 475 var isChildrenFiltered; 476 this.filterChildren = function() 477 { 478 var writer = new CKEDITOR.htmlParser.basicWriter(); 479 this.writeChildrenHtml.call( this, writer, filter, true ); 480 var html = writer.getHtml(); 481 this.children = new CKEDITOR.htmlParser.fragment.fromHtml( html ).children; 482 isChildrenFiltered = 1; 483 }; 484 485 // Filtering the root fragment before anything else. 486 !this.name && filter && filter.onFragment( this ); 487 488 this.writeChildrenHtml( writer, isChildrenFiltered ? null : filter ); 489 }, 490 491 writeChildrenHtml : function( writer, filter ) 492 { 493 for ( var i = 0 ; i < this.children.length ; i++ ) 494 this.children[i].writeHtml( writer, filter ); 495 } 496 }; 497 })();
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Thu Mar 24 11:18:33 2011 | Cross-referenced by PHPXref 0.7 |