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  */