1 /** 2 * Use style to gen element and wrap range's elements.Modified from CKEditor. 3 * @author yiminghe@gmail.com 4 */ 5 /* 6 Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. 7 For licensing, see LICENSE.html or http://ckeditor.com/license 8 */ 9 KISSY.add("editor/core/styles", function (S, Editor) { 10 11 var TRUE = true, 12 FALSE = false, 13 NULL = null, 14 DOM = S.DOM, 15 /** 16 * enum for style type 17 * @enum {number} 18 */ 19 KEST = { 20 STYLE_BLOCK:1, 21 STYLE_INLINE:2, 22 STYLE_OBJECT:3 23 }, 24 KER = Editor.RANGE, 25 KESelection = Editor.Selection, 26 KEP = Editor.POSITION, 27 KERange = Editor.Range, 28 //Walker = Editor.Walker, 29 Node = S.Node, 30 UA = S.UA, 31 ElementPath = Editor.ElementPath, 32 blockElements = { 33 address:1, 34 div:1, 35 h1:1, 36 h2:1, 37 h3:1, 38 h4:1, 39 h5:1, 40 h6:1, 41 p:1, 42 pre:1 43 }, 44 DTD = Editor.XHTML_DTD, 45 objectElements = { 46 //why? a should be same to inline? 但是不能互相嵌套 47 //a:1, 48 embed:1, 49 hr:1, 50 img:1, 51 li:1, 52 object:1, 53 ol:1, 54 table:1, 55 td:1, 56 tr:1, 57 th:1, 58 ul:1, 59 dl:1, 60 dt:1, 61 dd:1, 62 form:1 63 }, 64 semicolonFixRegex = /\s*(?:;\s*|$)/g, 65 varRegex = /#\((.+?)\)/g; 66 67 Editor.STYLE = KEST; 68 69 function notBookmark(node) { 70 //only get attributes on element nodes by kissy 71 //when textnode attr() return undefined ,wonderful !!! 72 return !DOM.attr(node, "_ke_bookmark"); 73 } 74 75 function replaceVariables(list, variablesValues) { 76 for (var item in list) { 77 if (!list.hasOwnProperty(item)) continue; 78 if (S.isString(list[ item ])) { 79 list[ item ] = list[ item ].replace(varRegex, function (match, varName) { 80 return variablesValues[ varName ]; 81 }); 82 } else { 83 replaceVariables(list[ item ], variablesValues); 84 } 85 } 86 } 87 88 /** 89 * @constructor 90 * @param styleDefinition {Object} 91 * @param [variablesValues] {Object} 92 */ 93 function KEStyle(styleDefinition, variablesValues) { 94 if (variablesValues) { 95 styleDefinition = S.clone(styleDefinition); 96 replaceVariables(styleDefinition, variablesValues); 97 } 98 99 var element = this["element"] = this.element = ( styleDefinition["element"] || '*' ).toLowerCase(); 100 101 this["type"] = this.type = ( element == '#text' || blockElements[ element ] ) ? 102 KEST.STYLE_BLOCK 103 : objectElements[ element ] ? 104 KEST.STYLE_OBJECT : KEST.STYLE_INLINE; 105 106 this._ = { 107 definition:styleDefinition 108 }; 109 } 110 111 /** 112 * 113 * @param {Document} document 114 * @param {Boolean=} remove 115 */ 116 function applyStyle(document, remove) { 117 // Get all ranges from the selection. 118 var self = this, 119 func = remove ? self.removeFromRange : self.applyToRange; 120 // Apply the style to the ranges. 121 //ie select 选中期间document得不到range 122 document.body.focus(); 123 124 var selection = new KESelection(document); 125 // Bookmark the range so we can re-select it after processing. 126 var ranges = selection.getRanges(); 127 for (var i = 0; i < ranges.length; i++) { 128 //格式化后,range进入格式标签内 129 func.call(self, ranges[ i ]); 130 } 131 selection.selectRanges(ranges); 132 } 133 134 KEStyle.prototype = { 135 apply:function (document) { 136 applyStyle.call(this, document, FALSE); 137 }, 138 139 remove:function (document) { 140 applyStyle.call(this, document, TRUE); 141 }, 142 143 applyToRange:function (range) { 144 var self = this; 145 return ( self.applyToRange = 146 this.type == KEST.STYLE_INLINE ? 147 applyInlineStyle 148 : self.type == KEST.STYLE_BLOCK ? 149 applyBlockStyle 150 : self.type == KEST.STYLE_OBJECT ? 151 NULL 152 //yiminghe note:no need! 153 //applyObjectStyle 154 : NULL ).call(self, range); 155 }, 156 157 removeFromRange:function (range) { 158 var self = this; 159 return ( self.removeFromRange = 160 self.type == KEST.STYLE_INLINE ? 161 removeInlineStyle 162 : NULL ).call(self, range); 163 }, 164 165 // applyToObject : function(element) { 166 // setupElement(element, this); 167 // }, 168 // Checks if an element, or any of its attributes, is removable by the 169 // current style definition. 170 checkElementRemovable:function (element, fullMatch) { 171 if (!element) 172 return FALSE; 173 174 var def = this._.definition, 175 attribs, styles; 176 177 // If the element name is the same as the style name. 178 if (element.nodeName() == this.element) { 179 // If no attributes are defined in the element. 180 if (!fullMatch && !element._4e_hasAttributes()) 181 return TRUE; 182 183 attribs = getAttributesForComparison(def); 184 185 if (attribs["_length"]) { 186 for (var attName in attribs) { 187 188 if (attName == '_length') 189 continue; 190 191 if (attribs.hasOwnProperty(attName)) { 192 193 var elementAttr = element.attr(attName) || ''; 194 if (attName == 'style' ? 195 compareCssText(attribs[ attName ], 196 normalizeCssText(elementAttr, FALSE)) 197 : attribs[ attName ] == elementAttr) { 198 if (!fullMatch) 199 return TRUE; 200 } 201 else if (fullMatch) 202 return FALSE; 203 } 204 205 206 } 207 if (fullMatch) 208 return TRUE; 209 } 210 else 211 return TRUE; 212 } 213 214 // Check if the element can be somehow overriden. 215 var overrides = getOverrides(this), 216 override = overrides[ element.nodeName() ] || overrides["*"]; 217 218 if (override) { 219 // If no attributes have been defined, remove the element. 220 if (!( attribs = override.attributes ) 221 && 222 !( styles = override.styles) 223 ) 224 return TRUE; 225 if (attribs) { 226 for (var i = 0; i < attribs.length; i++) { 227 attName = attribs[i][0]; 228 var actualAttrValue = element.attr(attName); 229 if (actualAttrValue) { 230 var attValue = attribs[i][1]; 231 // Remove the attribute if: 232 // - The override definition value is NULL; 233 // - The override definition value is a string that 234 // matches the attribute value exactly. 235 // - The override definition value is a regex that 236 // has matches in the attribute value. 237 if (attValue === NULL || 238 ( typeof attValue == 'string' 239 && actualAttrValue == attValue ) || 240 attValue.test && attValue.test(actualAttrValue)) 241 return TRUE; 242 } 243 } 244 } 245 if (styles) { 246 for (i = 0; i < styles.length; i++) { 247 var styleName = styles[i][0]; 248 var actualStyleValue = element.css(styleName); 249 if (actualStyleValue) { 250 var styleValue = styles[i][1]; 251 if (styleValue === NULL 252 //inherit wildcard ! 253 //|| styleValue == "inherit" 254 || ( typeof styleValue == 'string' 255 && actualStyleValue == styleValue ) || 256 styleValue.test && styleValue.test(actualStyleValue)) 257 return TRUE; 258 } 259 } 260 } 261 } 262 return FALSE; 263 }, 264 265 /** 266 * Get the style state inside an element path. Returns "TRUE" if the 267 * element is active in the path. 268 */ 269 checkActive:function (elementPath) { 270 switch (this.type) { 271 case KEST.STYLE_BLOCK : 272 return this.checkElementRemovable(elementPath.block 273 || elementPath.blockLimit, TRUE); 274 275 case KEST.STYLE_OBJECT : 276 case KEST.STYLE_INLINE : 277 278 var elements = elementPath.elements; 279 280 for (var i = 0, element; i < elements.length; i++) { 281 element = elements[ i ]; 282 283 if (this.type == KEST.STYLE_INLINE 284 && ( DOM.equals(element, elementPath.block) 285 || DOM.equals(element, elementPath.blockLimit) )) 286 continue; 287 288 if (this.type == KEST.STYLE_OBJECT 289 && !( element.nodeName() in objectElements )) 290 continue; 291 292 if (this.checkElementRemovable(element, TRUE)) 293 return TRUE; 294 } 295 } 296 return FALSE; 297 } 298 299 }; 300 301 KEStyle.getStyleText = function (styleDefinition) { 302 // If we have already computed it, just return it. 303 var stylesDef = styleDefinition._ST; 304 if (stylesDef) 305 return stylesDef; 306 307 stylesDef = styleDefinition["styles"]; 308 309 // Builds the StyleText. 310 var stylesText = ( styleDefinition["attributes"] 311 && styleDefinition["attributes"][ 'style' ] ) || '', 312 specialStylesText = ''; 313 314 if (stylesText.length) 315 stylesText = stylesText.replace(semicolonFixRegex, ';'); 316 317 for (var style in stylesDef) { 318 319 if (stylesDef.hasOwnProperty(style)) { 320 321 var styleVal = stylesDef[ style ], 322 text = ( style + ':' + styleVal ).replace(semicolonFixRegex, ';'); 323 324 // Some browsers don't support 'inherit' property value, leave them intact. (#5242) 325 if (styleVal == 'inherit') 326 specialStylesText += text; 327 else 328 stylesText += text; 329 } 330 } 331 332 // Browsers make some changes to the style when applying them. So, here 333 // we normalize it to the browser format. 334 if (stylesText.length) 335 stylesText = normalizeCssText(stylesText); 336 337 stylesText += specialStylesText; 338 339 // Return it, saving it to the next request. 340 return ( styleDefinition._ST = stylesText ); 341 }; 342 343 function getElement(style, targetDocument, element) { 344 var el, 345 //def = style._.definition, 346 elementName = style["element"]; 347 348 // The "*" element name will always be a span for this function. 349 if (elementName == '*') 350 elementName = 'span'; 351 352 // Create the element. 353 el = new Node(targetDocument.createElement(elementName)); 354 355 // #6226: attributes should be copied before the new ones are applied 356 if (element) 357 element._4e_copyAttributes(el); 358 359 return setupElement(el, style); 360 } 361 362 function setupElement(el, style) { 363 var def = style._["definition"], 364 attributes = def["attributes"], 365 styles = KEStyle.getStyleText(def); 366 367 // Assign all defined attributes. 368 if (attributes) { 369 for (var att in attributes) { 370 if (attributes.hasOwnProperty(att)) { 371 el.attr(att, attributes[ att ]); 372 } 373 } 374 } 375 376 // Assign all defined styles. 377 378 if (styles) 379 el[0].style.cssText = styles; 380 381 return el; 382 } 383 384 function applyBlockStyle(range) { 385 // Serializible bookmarks is needed here since 386 // elements may be merged. 387 var bookmark = range.createBookmark(TRUE), 388 iterator = range.createIterator(); 389 iterator.enforceRealBlocks = TRUE; 390 391 // make recognize <br /> tag as a separator in ENTER_BR mode (#5121) 392 //if (this._.enterMode) 393 iterator.enlargeBr = TRUE; 394 395 var block, doc = range.document; 396 // Only one = 397 while (( block = iterator.getNextParagraph() )) { 398 var newBlock = getElement(this, doc, block); 399 replaceBlock(block, newBlock); 400 } 401 range.moveToBookmark(bookmark); 402 } 403 404 // Wrapper function of String::replace without considering of head/tail bookmarks nodes. 405 function replace(str, regexp, replacement) { 406 var headBookmark = '', 407 tailBookmark = ''; 408 409 str = str.replace(/(^<span[^>]+_ke_bookmark.*?\/span>)|(<span[^>]+_ke_bookmark.*?\/span>$)/gi, 410 function (str, m1, m2) { 411 m1 && ( headBookmark = m1 ); 412 m2 && ( tailBookmark = m2 ); 413 return ''; 414 }); 415 return headBookmark + str.replace(regexp, replacement) + tailBookmark; 416 } 417 418 /** 419 * Converting from a non-PRE block to a PRE block in formatting operations. 420 */ 421 function toPre(block, newBlock) { 422 // First trim the block content. 423 var preHtml = block.html(); 424 425 // 1. Trim head/tail spaces, they're not visible. 426 preHtml = replace(preHtml, /(?:^[ \t\n\r]+)|(?:[ \t\n\r]+$)/g, ''); 427 // 2. Delete ANSI whitespaces immediately before and after <BR> because 428 // they are not visible. 429 preHtml = preHtml.replace(/[ \t\r\n]*(<br[^>]*>)[ \t\r\n]*/gi, '$1'); 430 // 3. Compress other ANSI whitespaces since they're only visible as one 431 // single space previously. 432 // 4. Convert to spaces since is no longer needed in <PRE>. 433 preHtml = preHtml.replace(/([ \t\n\r]+| )/g, ' '); 434 // 5. Convert any <BR /> to \n. This must not be done earlier because 435 // the \n would then get compressed. 436 preHtml = preHtml.replace(/<br\b[^>]*>/gi, '\n'); 437 438 // Krugle: IE normalizes innerHTML to <pre>, breaking whitespaces. 439 if (UA['ie']) { 440 var temp = block[0].ownerDocument.createElement('div'); 441 temp.appendChild(newBlock[0]); 442 newBlock[0].outerHTML = '<pre>' + preHtml + '</pre>'; 443 newBlock = new Node(temp.firstChild); 444 newBlock._4e_remove(); 445 } 446 else 447 newBlock.html(preHtml); 448 449 return newBlock; 450 } 451 452 /** 453 * Split into multiple <pre> blocks separated by double line-break. 454 * @param preBlock 455 */ 456 function splitIntoPres(preBlock) { 457 // Exclude the ones at header OR at tail, 458 // and ignore bookmark content between them. 459 var duoBrRegex = /(\S\s*)\n(?:\s|(<span[^>]+_ck_bookmark.*?\/span>))*\n(?!$)/gi, 460 //blockName = preBlock.nodeName(), 461 splittedHtml = replace(preBlock._4e_outerHtml(), 462 duoBrRegex, 463 function (match, charBefore, bookmark) { 464 return charBefore + '</pre>' + bookmark + '<pre>'; 465 }); 466 467 var pres = []; 468 splittedHtml.replace(/<pre\b.*?>([\s\S]*?)<\/pre>/gi, 469 function (match, preContent) { 470 pres.push(preContent); 471 }); 472 return pres; 473 } 474 475 // Replace the original block with new one, with special treatment 476 // for <pre> blocks to make sure content format is well preserved, and merging/splitting adjacent 477 // when necessary.(#3188) 478 function replaceBlock(block, newBlock) { 479 var newBlockIsPre = newBlock.nodeName == ('pre'), 480 blockIsPre = block.nodeName == ('pre'), 481 isToPre = newBlockIsPre && !blockIsPre, 482 isFromPre = !newBlockIsPre && blockIsPre; 483 484 if (isToPre) 485 newBlock = toPre(block, newBlock); 486 else if (isFromPre) 487 // Split big <pre> into pieces before start to convert. 488 newBlock = fromPres(splitIntoPres(block), newBlock); 489 else 490 block._4e_moveChildren(newBlock); 491 492 block[0].parentNode.replaceChild(newBlock[0], block[0]); 493 if (newBlockIsPre) { 494 // Merge previous <pre> blocks. 495 mergePre(newBlock); 496 } 497 } 498 499 /** 500 * Merge a <pre> block with a previous sibling if available. 501 */ 502 function mergePre(preBlock) { 503 var previousBlock; 504 if (!( ( previousBlock = preBlock._4e_previousSourceNode(TRUE, DOM.ELEMENT_NODE) ) 505 && previousBlock.nodeName() == 'pre' )) 506 return; 507 508 // Merge the previous <pre> block contents into the current <pre> 509 // block. 510 // 511 // Another thing to be careful here is that currentBlock might contain 512 // a '\n' at the beginning, and previousBlock might contain a '\n' 513 // towards the end. These new lines are not normally displayed but they 514 // become visible after merging. 515 var mergedHtml = replace(previousBlock.html(), /\n$/, '') + '\n\n' + 516 replace(preBlock.html(), /^\n/, ''); 517 518 // Krugle: IE normalizes innerHTML from <pre>, breaking whitespaces. 519 if (UA['ie']) 520 preBlock[0].outerHTML = '<pre>' + mergedHtml + '</pre>'; 521 else 522 preBlock.html(mergedHtml); 523 524 previousBlock._4e_remove(); 525 } 526 527 /** 528 * Converting a list of <pre> into blocks with format well preserved. 529 */ 530 function fromPres(preHtmls, newBlock) { 531 var docFrag = newBlock[0].ownerDocument.createDocumentFragment(); 532 for (var i = 0; i < preHtmls.length; i++) { 533 var blockHtml = preHtmls[ i ]; 534 535 // 1. Trim the first and last line-breaks immediately after and before <pre>, 536 // they're not visible. 537 blockHtml = blockHtml.replace(/(\r\n|\r)/g, '\n'); 538 blockHtml = replace(blockHtml, /^[ \t]*\n/, ''); 539 blockHtml = replace(blockHtml, /\n$/, ''); 540 // 2. Convert spaces or tabs at the beginning or at the end to 541 blockHtml = replace(blockHtml, /^[ \t]+|[ \t]+$/g, function (match, offset) { 542 if (match.length == 1) // one space, preserve it 543 return ' '; 544 else if (!offset) // beginning of block 545 return new Array(match.length).join(' ') + ' '; 546 else // end of block 547 return ' ' + new Array(match.length).join(' '); 548 }); 549 550 // 3. Convert \n to <BR>. 551 // 4. Convert contiguous (i.e. non-singular) spaces or tabs to 552 blockHtml = blockHtml.replace(/\n/g, '<br>'); 553 blockHtml = blockHtml.replace(/[ \t]{2,}/g, 554 function (match) { 555 return new Array(match.length).join(' ') + ' '; 556 }); 557 558 var newBlockClone = newBlock.clone(); 559 newBlockClone.html(blockHtml); 560 docFrag.appendChild(newBlockClone[0]); 561 } 562 return docFrag; 563 } 564 565 /** 566 * 567 * @param range 568 */ 569 function applyInlineStyle(range) { 570 var self = this, 571 document = range.document; 572 573 if (range.collapsed) { 574 // Create the element to be inserted in the DOM. 575 var collapsedElement = getElement(this, document, undefined); 576 // Insert the empty element into the DOM at the range position. 577 range.insertNode(collapsedElement); 578 // Place the selection right inside the empty element. 579 range.moveToPosition(collapsedElement, KER.POSITION_BEFORE_END); 580 return; 581 } 582 var elementName = this["element"], 583 def = this._["definition"], 584 isUnknownElement, 585 // Get the DTD definition for the element. Defaults to "span". 586 dtd = DTD[ elementName ]; 587 if (!dtd) { 588 isUnknownElement = TRUE; 589 dtd = DTD["span"]; 590 } 591 592 // Bookmark the range so we can re-select it after processing. 593 var bookmark = range.createBookmark(); 594 595 // Expand the range. 596 range.enlarge(KER.ENLARGE_ELEMENT); 597 range.trim(); 598 599 // Get the first node to be processed and the last, which concludes the 600 // processing. 601 var boundaryNodes = range.createBookmark(), 602 firstNode = boundaryNodes.startNode, 603 lastNode = boundaryNodes.endNode, 604 currentNode = firstNode, 605 styleRange; 606 607 while (currentNode && currentNode[0]) { 608 var applyStyle = FALSE; 609 610 if (DOM.equals(currentNode, lastNode)) { 611 currentNode = NULL; 612 applyStyle = TRUE; 613 } 614 else { 615 var nodeType = currentNode[0].nodeType, 616 nodeName = nodeType == DOM.ELEMENT_NODE ? 617 currentNode.nodeName() : NULL; 618 619 if (nodeName && currentNode.attr('_ke_bookmark')) { 620 currentNode = currentNode._4e_nextSourceNode(TRUE); 621 continue; 622 } 623 624 // Check if the current node can be a child of the style element. 625 if (!nodeName || ( dtd[ nodeName ] 626 && ( currentNode._4e_position(lastNode) | 627 ( KEP.POSITION_PRECEDING | 628 KEP.POSITION_IDENTICAL | 629 KEP.POSITION_IS_CONTAINED) ) 630 == ( KEP.POSITION_PRECEDING + 631 KEP.POSITION_IDENTICAL + 632 KEP.POSITION_IS_CONTAINED ) 633 && ( !def["childRule"] || def["childRule"](currentNode) ) )) { 634 var currentParent = currentNode.parent(); 635 636 637 // hack for 638 // 1<a href='http://www.taobao.com'>2</a>3 639 // select all ,set link to http://www.ckeditor.com 640 // expect => <a href='http://www.ckeditor.com'>123</a> (same with tinymce) 641 // but now => <a href="http://www.ckeditor.com">1</a> 642 // <a href="http://www.taobao.com">2</a> 643 // <a href="http://www.ckeditor.com">3</a> 644 // http://dev.ckeditor.com/ticket/8470 645 if (currentParent && 646 elementName == "a" && 647 currentParent.nodeName() == elementName) { 648 var tmpANode = getElement(self, document, undefined); 649 currentParent._4e_moveChildren(tmpANode); 650 currentParent[0].parentNode.replaceChild(tmpANode[0], currentParent[0]); 651 tmpANode._4e_mergeSiblings(); 652 } 653 654 // Check if the style element can be a child of the current 655 // node parent or if the element is not defined in the DTD. 656 else if (currentParent && currentParent[0] 657 && ( ( DTD[currentParent.nodeName()] || 658 DTD["span"] )[ elementName ] || 659 isUnknownElement ) 660 && ( !def["parentRule"] || def["parentRule"](currentParent) )) { 661 // This node will be part of our range, so if it has not 662 // been started, place its start right before the node. 663 // In the case of an element node, it will be included 664 // only if it is entirely inside the range. 665 if (!styleRange && 666 ( !nodeName 667 || !DTD.$removeEmpty[ nodeName ] 668 || ( currentNode._4e_position(lastNode) | 669 ( KEP.POSITION_PRECEDING | 670 KEP.POSITION_IDENTICAL | 671 KEP.POSITION_IS_CONTAINED )) 672 == 673 ( KEP.POSITION_PRECEDING + 674 KEP.POSITION_IDENTICAL + 675 KEP.POSITION_IS_CONTAINED ) 676 )) { 677 styleRange = new KERange(document); 678 styleRange.setStartBefore(currentNode); 679 } 680 681 // Non element nodes, or empty elements can be added 682 // completely to the range. 683 if (nodeType == DOM.TEXT_NODE || 684 ( nodeType == DOM.ELEMENT_NODE && 685 !currentNode[0].childNodes.length )) { 686 var includedNode = currentNode, 687 parentNode = null; 688 689 // This node is about to be included completelly, but, 690 // if this is the last node in its parent, we must also 691 // check if the parent itself can be added completelly 692 // to the range. 693 //2010-11-18 fix ; http://dev.ckeditor.com/ticket/6687 694 //<span><book/>123<book/></span> 直接越过末尾 <book/> 695 696 // If the included node still is the last node in its 697 // parent, it means that the parent can't be included 698 // in this style DTD, so apply the style immediately. 699 while ( 700 (applyStyle = !includedNode.next(notBookmark, 1)) 701 && ( (parentNode = includedNode.parent()) && 702 dtd[ parentNode.nodeName() ] ) 703 && ( parentNode._4e_position(firstNode) | 704 KEP.POSITION_FOLLOWING | 705 KEP.POSITION_IDENTICAL | 706 KEP.POSITION_IS_CONTAINED) == 707 ( KEP.POSITION_FOLLOWING + 708 KEP.POSITION_IDENTICAL + 709 KEP.POSITION_IS_CONTAINED ) 710 && ( !def["childRule"] || def["childRule"](parentNode) )) { 711 includedNode = parentNode; 712 } 713 714 styleRange.setEndAfter(includedNode); 715 716 } 717 } 718 else 719 applyStyle = TRUE; 720 } 721 else 722 applyStyle = TRUE; 723 724 // Get the next node to be processed. 725 currentNode = currentNode._4e_nextSourceNode(); 726 } 727 728 // Apply the style if we have something to which apply it. 729 if (applyStyle && styleRange && !styleRange.collapsed) { 730 // Build the style element, based on the style object definition. 731 var styleNode = getElement(self, document, undefined), 732 733 // Get the element that holds the entire range. 734 parent = styleRange.getCommonAncestor(); 735 736 737 var removeList = { 738 styles:{}, 739 attrs:{}, 740 // Styles cannot be removed. 741 blockedStyles:{}, 742 // Attrs cannot be removed. 743 blockedAttrs:{} 744 }; 745 746 var attName, styleName = null, value; 747 748 // Loop through the parents, removing the redundant attributes 749 // from the element to be applied. 750 while (styleNode && parent && styleNode[0] && parent[0]) { 751 if (parent.nodeName() == elementName) { 752 for (attName in def.attributes) { 753 754 if (def.attributes.hasOwnProperty(attName)) { 755 756 757 if (removeList.blockedAttrs[ attName ] 758 || !( value = parent.attr(styleName) )) 759 continue; 760 761 if (styleNode.attr(attName) == value) { 762 //removeList.attrs[ attName ] = 1; 763 styleNode.removeAttr(attName); 764 } 765 else 766 removeList.blockedAttrs[ attName ] = 1; 767 } 768 769 770 } 771 //bug notice add by yiminghe@gmail.com 772 //<span style="font-size:70px"><span style="font-size:30px">xcxx</span></span> 773 //下一次格式xxx为70px 774 //var exit = FALSE; 775 for (styleName in def.styles) { 776 if (def.styles.hasOwnProperty(styleName)) { 777 778 if (removeList.blockedStyles[ styleName ] 779 || !( value = parent.style(styleName) )) 780 continue; 781 782 if (styleNode.style(styleName) == value) { 783 //removeList.styles[ styleName ] = 1; 784 styleNode.style(styleName, ""); 785 } 786 else 787 removeList.blockedStyles[ styleName ] = 1; 788 } 789 790 } 791 792 if (!styleNode._4e_hasAttributes()) { 793 styleNode = NULL; 794 break; 795 } 796 } 797 798 parent = parent.parent(); 799 } 800 801 if (styleNode) { 802 // Move the contents of the range to the style element. 803 styleNode[0].appendChild(styleRange.extractContents()); 804 805 // Here we do some cleanup, removing all duplicated 806 // elements from the style element. 807 removeFromInsideElement(self, styleNode); 808 809 // Insert it into the range position (it is collapsed after 810 // extractContents. 811 styleRange.insertNode(styleNode); 812 813 // Let's merge our new style with its neighbors, if possible. 814 styleNode._4e_mergeSiblings(); 815 816 // As the style system breaks text nodes constantly, let's normalize 817 // things for performance. 818 // With IE, some paragraphs get broken when calling normalize() 819 // repeatedly. Also, for IE, we must normalize body, not documentElement. 820 // IE is also known for having a "crash effect" with normalize(). 821 // We should try to normalize with IE too in some way, somewhere. 822 if (!UA['ie']) 823 styleNode[0].normalize(); 824 } 825 // Style already inherit from parents, left just to clear up any internal overrides. (#5931) 826 /** 827 * from koubei 828 *1.输入ab 829 2.ctrl-a 设置字体大小 x 830 3.选中b设置字体大小 y 831 4.保持选中b,设置字体大小 x 832 exptected: b 大小为 x 833 actual: b 大小为 y 834 */ 835 else { 836 styleNode = new Node(document.createElement("span")); 837 styleNode[0].appendChild(styleRange.extractContents()); 838 styleRange.insertNode(styleNode); 839 removeFromInsideElement(self, styleNode); 840 styleNode._4e_remove(true); 841 } 842 843 // Style applied, let's release the range, so it gets 844 // re-initialization in the next loop. 845 styleRange = NULL; 846 } 847 } 848 849 firstNode._4e_remove(); 850 lastNode._4e_remove(); 851 range.moveToBookmark(bookmark); 852 // Minimize the result range to exclude empty text nodes. (#5374) 853 range.shrink(KER.SHRINK_TEXT); 854 855 } 856 857 /** 858 * 859 * @param range 860 */ 861 function removeInlineStyle(range) { 862 /* 863 * Make sure our range has included all "collapsed" parent inline nodes so 864 * that our operation logic can be simpler. 865 */ 866 range.enlarge(KER.ENLARGE_ELEMENT); 867 868 var bookmark = range.createBookmark(), 869 startNode = bookmark.startNode; 870 871 if (range.collapsed) { 872 873 var startPath = new ElementPath(startNode.parent()), 874 // The topmost element in elementspatch which we should jump out of. 875 boundaryElement; 876 877 878 for (var i = 0, element; i < startPath.elements.length 879 && ( element = startPath.elements[i] ); i++) { 880 /* 881 * 1. If it's collaped inside text nodes, try to remove the style from the whole element. 882 * 883 * 2. Otherwise if it's collapsed on element boundaries, moving the selection 884 * outside the styles instead of removing the whole tag, 885 * also make sure other inner styles were well preserverd.(#3309) 886 */ 887 if (element == startPath.block || 888 element == startPath.blockLimit) { 889 break; 890 } 891 if (this.checkElementRemovable(element)) { 892 var endOfElement = range.checkBoundaryOfElement(element, KER.END), 893 startOfElement = !endOfElement && 894 range.checkBoundaryOfElement(element, KER.START); 895 if (startOfElement || endOfElement) { 896 boundaryElement = element; 897 boundaryElement.match = startOfElement ? 'start' : 'end'; 898 } else { 899 /* 900 * Before removing the style node, there may be a sibling to the style node 901 * that's exactly the same to the one to be removed. To the user, it makes 902 * no difference that they're separate entities in the DOM tree. So, merge 903 * them before removal. 904 */ 905 element._4e_mergeSiblings(); 906 //yiminghe:note,bug for ckeditor 907 //qc #3700 for chengyu(yiminghe) 908 //从word复制过来的已编辑文本无法使用粗体和斜体等功能取消 909 if (element.nodeName() != this.element) { 910 var _overrides = getOverrides(this); 911 removeOverrides(element, 912 _overrides[ element.nodeName() ] || _overrides["*"]); 913 } else { 914 removeFromElement(this, element); 915 } 916 917 } 918 } 919 } 920 921 // Re-create the style tree after/before the boundary element, 922 // the replication start from bookmark start node to define the 923 // new range. 924 if (boundaryElement) { 925 var clonedElement = startNode; 926 for (i = 0; ; i++) { 927 var newElement = startPath.elements[ i ]; 928 if (DOM.equals(newElement, boundaryElement)) 929 break; 930 // Avoid copying any matched element. 931 else if (newElement.match) 932 continue; 933 else 934 newElement = newElement.clone(); 935 newElement[0].appendChild(clonedElement[0]); 936 clonedElement = newElement; 937 } 938 //脱离当前的元素,将 bookmark 插入到当前元素后面 939 //<strong>xx|</strong> -> 940 //<strong>xx<strong>| 941 clonedElement[ boundaryElement.match == 'start' ? 'insertBefore' : 942 'insertAfter' ](boundaryElement); 943 } 944 } else { 945 /* 946 * Now our range isn't collapsed. Lets walk from the start node to the end 947 * node via DFS and remove the styles one-by-one. 948 */ 949 var endNode = bookmark.endNode, 950 me = this; 951 952 /* 953 * Find out the style ancestor that needs to be broken down at startNode 954 * and endNode. 955 */ 956 function breakNodes() { 957 var startPath = new ElementPath(startNode.parent()), 958 endPath = new ElementPath(endNode.parent()), 959 breakStart = NULL, 960 breakEnd = NULL; 961 for (var i = 0; i < startPath.elements.length; i++) { 962 var element = startPath.elements[ i ]; 963 964 if (element == startPath.block || 965 element == startPath.blockLimit) 966 break; 967 968 if (me.checkElementRemovable(element)) 969 breakStart = element; 970 } 971 for (i = 0; i < endPath.elements.length; i++) { 972 element = endPath.elements[ i ]; 973 974 if (element == endPath.block || 975 element == endPath.blockLimit) 976 break; 977 978 if (me.checkElementRemovable(element)) 979 breakEnd = element; 980 } 981 982 if (breakEnd) 983 endNode._4e_breakParent(breakEnd); 984 if (breakStart) 985 startNode._4e_breakParent(breakStart); 986 } 987 988 breakNodes(); 989 990 // Now, do the DFS walk. 991 var currentNode = new Node(startNode[0].nextSibling); 992 while (currentNode[0] !== endNode[0]) { 993 /* 994 * Need to get the next node first because removeFromElement() can remove 995 * the current node from DOM tree. 996 */ 997 var nextNode = currentNode._4e_nextSourceNode(); 998 if (currentNode[0] && 999 currentNode[0].nodeType == DOM.ELEMENT_NODE && 1000 this.checkElementRemovable(currentNode)) { 1001 // Remove style from element or overriding element. 1002 if (currentNode.nodeName() == this["element"]) 1003 removeFromElement(this, currentNode); 1004 else { 1005 var overrides = getOverrides(this); 1006 removeOverrides(currentNode, 1007 overrides[ currentNode.nodeName() ] || overrides["*"]); 1008 1009 } 1010 1011 /* 1012 * removeFromElement() may have merged the next node with something before 1013 * the startNode via mergeSiblings(). In that case, the nextNode would 1014 * contain startNode and we'll have to call breakNodes() again and also 1015 * reassign the nextNode to something after startNode. 1016 */ 1017 if (nextNode[0].nodeType == DOM.ELEMENT_NODE && 1018 nextNode.contains(startNode)) { 1019 breakNodes(); 1020 nextNode = new Node(startNode[0].nextSibling); 1021 } 1022 } 1023 currentNode = nextNode; 1024 } 1025 } 1026 range.moveToBookmark(bookmark); 1027 } 1028 1029 // Turn inline style text properties into one hash. 1030 /** 1031 * 1032 * @param {string} styleText 1033 */ 1034 function parseStyleText(styleText) { 1035 styleText = String(styleText); 1036 var retval = {}; 1037 styleText.replace(/"/g, '"') 1038 .replace(/\s*([^ :;]+)\s*:\s*([^;]+)\s*(?=;|$)/g, 1039 function (match, name, value) { 1040 retval[ name ] = value; 1041 }); 1042 return retval; 1043 } 1044 1045 function compareCssText(source, target) { 1046 typeof source == 'string' && ( source = parseStyleText(source) ); 1047 typeof target == 'string' && ( target = parseStyleText(target) ); 1048 for (var name in source) { 1049 if (source.hasOwnProperty(name)) { 1050 // Value 'inherit' is treated as a wildcard, 1051 // which will match any value. 1052 if (!( name in target && 1053 ( target[ name ] == source[ name ] 1054 || source[ name ] == 'inherit' 1055 || target[ name ] == 'inherit' ) )) { 1056 return FALSE; 1057 } 1058 } 1059 } 1060 return TRUE; 1061 } 1062 1063 /** 1064 * 1065 * @param {string} unparsedCssText 1066 * @param {Boolean=} nativeNormalize 1067 */ 1068 function normalizeCssText(unparsedCssText, nativeNormalize) { 1069 var styleText = ""; 1070 if (nativeNormalize !== FALSE) { 1071 // Injects the style in a temporary span object, so the browser parses it, 1072 // retrieving its final format. 1073 var temp = document.createElement('span'); 1074 temp.style.cssText = unparsedCssText; 1075 //temp.setAttribute('style', unparsedCssText); 1076 styleText = temp.style.cssText || ''; 1077 } 1078 else 1079 styleText = unparsedCssText; 1080 1081 // Shrinking white-spaces around colon and semi-colon (#4147). 1082 // Compensate tail semi-colon. 1083 return styleText.replace(/\s*([;:])\s*/, '$1') 1084 .replace(/([^\s;])$/, "$1;") 1085 .replace(/,\s+/g, ',')// Trimming spaces after comma (e.g. font-family name)(#4107). 1086 .toLowerCase(); 1087 } 1088 1089 /** 1090 * 把 styles(css配置) 作为 属性 style 统一看待 1091 * 注意对 inherit 的处理 1092 * @param styleDefinition 1093 */ 1094 function getAttributesForComparison(styleDefinition) { 1095 // If we have already computed it, just return it. 1096 var attribs = styleDefinition._AC; 1097 if (attribs) 1098 return attribs; 1099 1100 attribs = {}; 1101 1102 var length = 0, 1103 1104 // Loop through all defined attributes. 1105 styleAttribs = styleDefinition["attributes"]; 1106 if (styleAttribs) { 1107 for (var styleAtt in styleAttribs) { 1108 if (styleAttribs.hasOwnProperty(styleAtt)) { 1109 length++; 1110 attribs[ styleAtt ] = styleAttribs[ styleAtt ]; 1111 } 1112 } 1113 } 1114 1115 // Includes the style definitions. 1116 var styleText = KEStyle.getStyleText(styleDefinition); 1117 if (styleText) { 1118 if (!attribs[ 'style' ]) 1119 length++; 1120 attribs[ 'style' ] = styleText; 1121 } 1122 1123 // Appends the "length" information to the object. 1124 //防止被compiler优化 1125 attribs["_length"] = length; 1126 1127 // Return it, saving it to the next request. 1128 return ( styleDefinition._AC = attribs ); 1129 } 1130 1131 1132 /** 1133 * Get the the collection used to compare the elements and attributes, 1134 * defined in this style overrides, with other element. All information in 1135 * it is lowercased. 1136 * @param style 1137 */ 1138 function getOverrides(style) { 1139 if (style._.overrides) 1140 return style._.overrides; 1141 1142 var overrides = ( style._.overrides = {} ), 1143 definition = style._.definition["overrides"]; 1144 1145 if (definition) { 1146 // The override description can be a string, object or array. 1147 // Internally, well handle arrays only, so transform it if needed. 1148 if (!S.isArray(definition)) 1149 definition = [ definition ]; 1150 1151 // Loop through all override definitions. 1152 for (var i = 0; i < definition.length; i++) { 1153 var override = definition[i]; 1154 var elementName; 1155 var overrideEl; 1156 var attrs, styles; 1157 1158 // If can be a string with the element name. 1159 if (typeof override == 'string') 1160 elementName = override.toLowerCase(); 1161 // Or an object. 1162 else { 1163 elementName = override["element"] ? 1164 override["element"].toLowerCase() : 1165 style.element; 1166 attrs = override["attributes"]; 1167 styles = override["styles"]; 1168 } 1169 1170 // We can have more than one override definition for the same 1171 // element name, so we attempt to simply append information to 1172 // it if it already exists. 1173 overrideEl = overrides[ elementName ] || 1174 ( overrides[ elementName ] = {} ); 1175 1176 if (attrs) { 1177 // The returning attributes list is an array, because we 1178 // could have different override definitions for the same 1179 // attribute name. 1180 var overrideAttrs = ( overrideEl["attributes"] = 1181 overrideEl["attributes"] || new Array() ); 1182 for (var attName in attrs) { 1183 // Each item in the attributes array is also an array, 1184 // where [0] is the attribute name and [1] is the 1185 // override value. 1186 if (attrs.hasOwnProperty(attName)) 1187 overrideAttrs.push([ attName.toLowerCase(), attrs[ attName ] ]); 1188 } 1189 } 1190 1191 1192 if (styles) { 1193 // The returning attributes list is an array, because we 1194 // could have different override definitions for the same 1195 // attribute name. 1196 var overrideStyles = ( overrideEl["styles"] = 1197 overrideEl["styles"] || new Array() ); 1198 for (var styleName in styles) { 1199 // Each item in the styles array is also an array, 1200 // where [0] is the style name and [1] is the 1201 // override value. 1202 if (styles.hasOwnProperty(styleName)) 1203 overrideStyles.push([ styleName.toLowerCase(), 1204 styles[ styleName ] ]); 1205 } 1206 } 1207 } 1208 } 1209 1210 return overrides; 1211 } 1212 1213 1214 // Removes a style from an element itself, don't care about its subtree. 1215 function removeFromElement(style, element) { 1216 var def = style._.definition, 1217 overrides = getOverrides(style), 1218 attributes = S.merge(def["attributes"], 1219 (overrides[ element.nodeName()] || overrides["*"] || {})["attributes"]), 1220 styles = S.merge(def["styles"], 1221 (overrides[ element.nodeName()] || overrides["*"] || {})["styles"]), 1222 // If the style is only about the element itself, we have to remove the element. 1223 removeEmpty = S.isEmptyObject(attributes) && 1224 S.isEmptyObject(styles); 1225 1226 // Remove definition attributes/style from the element. 1227 for (var attName in attributes) { 1228 if (attributes.hasOwnProperty(attName)) { 1229 // The 'class' element value must match (#1318). 1230 if (( attName == 'class' || style._.definition["fullMatch"] ) 1231 && element.attr(attName) != normalizeProperty(attName, 1232 attributes[ attName ])) 1233 continue; 1234 removeEmpty = removeEmpty || !!element.hasAttr(attName); 1235 element.removeAttr(attName); 1236 } 1237 } 1238 1239 for (var styleName in styles) { 1240 if (styles.hasOwnProperty(styleName)) { 1241 // Full match style insist on having fully equivalence. (#5018) 1242 if (style._.definition["fullMatch"] 1243 && element.style(styleName) 1244 != normalizeProperty(styleName, styles[ styleName ], TRUE)) 1245 continue; 1246 1247 removeEmpty = removeEmpty || !!element.style(styleName); 1248 //设置空即为:清除样式 1249 element.style(styleName, ""); 1250 } 1251 } 1252 1253 //removeEmpty && 1254 //始终检查 1255 removeNoAttribsElement(element); 1256 } 1257 1258 /** 1259 * 1260 * @param {string} name 1261 * @param {string} value 1262 * @param {Boolean=} isStyle 1263 */ 1264 function normalizeProperty(name, value, isStyle) { 1265 var temp = new Node('<span>'); 1266 temp [ isStyle ? 'style' : 'attr' ](name, value); 1267 return temp[ isStyle ? 'style' : 'attr' ](name); 1268 } 1269 1270 1271 // Removes a style from inside an element. 1272 function removeFromInsideElement(style, element) { 1273 var //def = style._.definition, 1274 //attribs = def.attributes, 1275 //styles = def.styles, 1276 overrides = getOverrides(style), 1277 innerElements = element.all(style["element"]); 1278 1279 for (var i = innerElements.length; --i >= 0;) 1280 removeFromElement(style, new Node(innerElements[i])); 1281 1282 // Now remove any other element with different name that is 1283 // defined to be overriden. 1284 for (var overrideElement in overrides) { 1285 if (overrides.hasOwnProperty(overrideElement)) { 1286 if (overrideElement != style["element"]) { 1287 innerElements = element.all(overrideElement); 1288 for (i = innerElements.length - 1; i >= 0; i--) { 1289 var innerElement = new Node(innerElements[i]); 1290 removeOverrides(innerElement, 1291 overrides[ overrideElement ]); 1292 } 1293 } 1294 } 1295 } 1296 1297 } 1298 1299 /** 1300 * Remove overriding styles/attributes from the specific element. 1301 * Note: Remove the element if no attributes remain. 1302 * @param {Object} element 1303 * @param {Object} overrides 1304 */ 1305 function removeOverrides(element, overrides) { 1306 var i, attributes = overrides && overrides["attributes"]; 1307 1308 if (attributes) { 1309 for (i = 0; i < attributes.length; i++) { 1310 var attName = attributes[i][0], actualAttrValue; 1311 1312 if (( actualAttrValue = element.attr(attName) )) { 1313 var attValue = attributes[i][1]; 1314 1315 // Remove the attribute if: 1316 // - The override definition value is NULL ; 1317 // - The override definition valie is a string that 1318 // matches the attribute value exactly. 1319 // - The override definition value is a regex that 1320 // has matches in the attribute value. 1321 if (attValue === NULL || 1322 ( attValue.test && attValue.test(actualAttrValue) ) || 1323 ( typeof attValue == 'string' && actualAttrValue == attValue )) 1324 element[0].removeAttribute(attName); 1325 } 1326 } 1327 } 1328 1329 1330 var styles = overrides && overrides["styles"]; 1331 1332 if (styles) { 1333 for (i = 0; i < styles.length; i++) { 1334 var styleName = styles[i][0], actualStyleValue; 1335 1336 if (( actualStyleValue = element.css(styleName) )) { 1337 var styleValue = styles[i][1]; 1338 if (styleValue === NULL || 1339 //styleValue === "inherit" || 1340 ( styleValue.test && styleValue.test(actualAttrValue) ) || 1341 ( typeof styleValue == 'string' && actualStyleValue == styleValue )) 1342 element.css(styleName, ""); 1343 } 1344 } 1345 } 1346 1347 removeNoAttribsElement(element); 1348 } 1349 1350 // If the element has no more attributes, remove it. 1351 function removeNoAttribsElement(element) { 1352 // If no more attributes remained in the element, remove it, 1353 // leaving its children. 1354 if (!element._4e_hasAttributes()) { 1355 // Removing elements may open points where merging is possible, 1356 // so let's cache the first and last nodes for later checking. 1357 var firstChild = element[0].firstChild, 1358 lastChild = element[0].lastChild; 1359 1360 element._4e_remove(TRUE); 1361 1362 if (firstChild) { 1363 // Check the cached nodes for merging. 1364 firstChild.nodeType == DOM.ELEMENT_NODE && 1365 DOM._4e_mergeSiblings(firstChild); 1366 1367 if (lastChild && firstChild != lastChild 1368 && lastChild.nodeType == DOM.ELEMENT_NODE) 1369 DOM._4e_mergeSiblings(lastChild); 1370 } 1371 } 1372 } 1373 1374 Editor.Style = KEStyle; 1375 1376 return KEStyle; 1377 }, { 1378 requires:['./base', './range', './selection', './domIterator', './elementPath'] 1379 }); 1380 /** 1381 * TODO yiminghe@gmail.com : 重构 Refer 1382 * - http://dvcs.w3.org/hg/editing/raw-file/tip/editing.html 1383 */