[ Index ]

PHP Cross Reference of Drupal 6 (gatewave)

title

Body

[close]

/sites/all/libraries/ckeditor/_source/core/htmlparser/ -> fragment.js (source)

   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( '&lt;P&gt;&lt;B&gt;Example' );

 470           * fragment.writeHtml( writer )

 471           * alert( writer.getHtml() );  "&lt;p&gt;&lt;b&gt;Example&lt;/b&gt;&lt;/p&gt;"

 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  })();


Generated: Thu Mar 24 11:18:33 2011 Cross-referenced by PHPXref 0.7