1 /**
  2  * dom utils for kissy editor,mainly 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/dom", function (S, Editor, Utils) {
 10 
 11     var TRUE = true,
 12         undefined = undefined,
 13         FALSE = false,
 14         NULL = null,
 15         xhtml_dtd = Editor.XHTML_DTD,
 16         DOM = S.DOM,
 17         UA = S.UA,
 18         Node = S.Node,
 19         REMOVE_EMPTY = {
 20             a:1,
 21             abbr:1,
 22             acronym:1,
 23             address:1,
 24             b:1,
 25             bdo:1,
 26             big:1,
 27             cite:1,
 28             code:1,
 29             del:1,
 30             dfn:1,
 31             em:1,
 32             font:1,
 33             i:1,
 34             ins:1,
 35             label:1,
 36             kbd:1,
 37             q:1,
 38             s:1,
 39             samp:1,
 40             small:1,
 41             span:1,
 42             strike:1,
 43             strong:1,
 44             sub:1,
 45             sup:1,
 46             tt:1,
 47             u:1,
 48             'var':1
 49         };
 50     /**
 51      * Enum for node position
 52      * @enum {number}
 53      */
 54     Editor.POSITION = {
 55         POSITION_IDENTICAL:0,
 56         POSITION_DISCONNECTED:1,
 57         POSITION_FOLLOWING:2,
 58         POSITION_PRECEDING:4,
 59         POSITION_IS_CONTAINED:8,
 60         POSITION_CONTAINS:16
 61     };
 62     var KEP = Editor.POSITION;
 63 
 64     /*
 65      * Anything whose display computed style is block, list-item, table,
 66      * table-row-group, table-header-group, table-footer-group, table-row,
 67      * table-column-group, table-column, table-cell, table-caption, or whose node
 68      * name is hr, br (when enterMode is br only) is a block boundary.
 69      */
 70     var blockBoundaryDisplayMatch = {
 71             block:1,
 72             'list-item':1,
 73             table:1,
 74             'table-row-group':1,
 75             'table-header-group':1,
 76             'table-footer-group':1,
 77             'table-row':1,
 78             'table-column-group':1,
 79             'table-column':1,
 80             'table-cell':1,
 81             'table-caption':1
 82         },
 83         blockBoundaryNodeNameMatch = { hr:1 },
 84         /**
 85          * @param el {(Node)}
 86          */
 87             normalElDom = function (el) {
 88             return el && (el[0] || el);
 89         },
 90         /**
 91          * @param el {(Node)}
 92          */
 93             normalEl = function (el) {
 94             return new Node(el);
 95         },
 96         editorDom = {
 97 
 98             /**
 99              * Whether two nodes are on the same level.
100              * @param el1
101              * @param [el2]
102              * @return {Boolean}
103              * @private
104              */
105             _4e_sameLevel:function (el1, el2) {
106                 el2 = normalElDom(el2);
107                 var e1p = el1.parentNode;
108                 return e1p && e1p == el2.parentNode;
109             },
110 
111             /**
112              * 是否是块状元素或块状元素边界
113              * @param el
114              * @param [customNodeNames]
115              */
116             _4e_isBlockBoundary:function (el, customNodeNames) {
117                 var nodeNameMatches = S.merge(blockBoundaryNodeNameMatch, customNodeNames);
118                 return !!(blockBoundaryDisplayMatch[ DOM.css(el, 'display') ] || nodeNameMatches[ DOM.nodeName(el) ]);
119             },
120 
121             /**
122              * 返回当前元素在父元素中所有儿子节点中的序号
123              * @param [el]
124              * @param [normalized]
125              */
126             _4e_index:function (el, normalized) {
127                 var siblings = el.parentNode.childNodes,
128                     candidate,
129                     currentIndex = -1;
130 
131                 for (var i = 0; i < siblings.length; i++) {
132                     candidate = siblings[i];
133 
134                     // 连续的字符串节点合并
135                     if (normalized &&
136                         candidate.nodeType == 3 &&
137                         candidate.previousSibling &&
138                         candidate.previousSibling.nodeType == 3) {
139                         continue;
140                     }
141 
142                     currentIndex++;
143 
144                     if (candidate === el) {
145                         return currentIndex;
146                     }
147                 }
148                 return -1;
149             },
150 
151             /**
152              * 把 thisElement 移到 target 的前面或后面
153              * @param thisElement
154              * @param target
155              * @param toStart
156              */
157             _4e_move:function (thisElement, target, toStart) {
158                 target = normalElDom(target);
159                 if (toStart) {
160                     target.insertBefore(thisElement, target.firstChild);
161                 } else {
162                     target.appendChild(thisElement);
163                 }
164             },
165 
166             /**
167              * 两个元素是否名称和属性都相同
168              * @param thisElement
169              * @param otherElement
170              */
171             _4e_isIdentical:function (thisElement, otherElement) {
172                 if (!otherElement) {
173                     return FALSE;
174                 }
175 
176                 otherElement = normalElDom(otherElement);
177 
178                 if (DOM.nodeName(thisElement) != DOM.nodeName(otherElement)) {
179                     return FALSE;
180                 }
181 
182                 var thisAttributes = thisElement.attributes,
183                     otherAttributes = otherElement.attributes;
184 
185                 var thisLength = thisAttributes.length,
186                     otherLength = otherAttributes.length;
187 
188                 if (thisLength != otherLength) {
189                     return FALSE;
190                 }
191 
192                 for (var i = 0; i < thisLength; i++) {
193                     var attribute = thisAttributes[i],
194                         name = attribute.name;
195                     if (attribute.specified &&
196                         DOM.attr(thisElement, name) != DOM.attr(otherElement, name)) {
197                         return FALSE;
198                     }
199                 }
200 
201                 // For IE, we have to for both elements, because it's difficult to
202                 // know how the atttibutes collection is organized in its DOM.
203                 // ie 使用版本 < 8
204                 if (Utils.ieEngine < 8) {
205                     for (i = 0; i < otherLength; i++) {
206                         attribute = otherAttributes[ i ];
207                         name = attribute.name;
208                         if (attribute.specified &&
209                             DOM.attr(thisElement, name) != DOM.attr(otherElement, name)) {
210                             return FALSE;
211                         }
212                     }
213                 }
214 
215                 return TRUE;
216             },
217 
218             /**
219              * inline 元素是否没有包含有效文字内容
220              * @param thisElement
221              */
222             _4e_isEmptyInlineRemovable:function (thisElement) {
223                 if (!xhtml_dtd.$removeEmpty[DOM.nodeName(thisElement)]) {
224                     return false;
225                 }
226                 var children = thisElement.childNodes;
227                 for (var i = 0, count = children.length; i < count; i++) {
228                     var child = children[i],
229                         nodeType = child.nodeType;
230 
231                     if (nodeType == DOM.ELEMENT_NODE &&
232                         child.getAttribute('_ke_bookmark')) {
233                         continue;
234                     }
235 
236                     if (nodeType == DOM.ELEMENT_NODE && !DOM._4e_isEmptyInlineRemovable(child) ||
237                         nodeType == DOM.TEXT_NODE && S.trim(child.nodeValue)) {
238                         return FALSE;
239                     }
240                 }
241                 return TRUE;
242             },
243 
244             /**
245              * 把 thisElement 的所有儿子节点都插入到 target 节点的前面或后面
246              * @param thisElement
247              * @param target
248              * @param toStart
249              */
250             _4e_moveChildren:function (thisElement, target, toStart) {
251                 target = normalElDom(target);
252 
253                 if (thisElement == target) {
254                     return;
255                 }
256 
257                 var child;
258 
259                 if (toStart) {
260                     while (child = thisElement.lastChild) {
261                         target.insertBefore(thisElement.removeChild(child), target.firstChild);
262                     }
263                 } else {
264                     while (child = thisElement.firstChild) {
265                         target.appendChild(thisElement.removeChild(child));
266                     }
267                 }
268             },
269 
270             /**
271              * 将当前元素和周围的元素合并
272              * @example
273              * <code>
274              * <b><i>1</i></b><b><i>3</i></b>
275              * =>
276              * <b><i>13</i></b>
277              * </code>
278              */
279             _4e_mergeSiblings:function (thisElement) {
280                 thisElement = normalEl(thisElement);
281                 // 只合并空元素不占用空间的标签
282                 if (REMOVE_EMPTY[thisElement.nodeName()]) {
283                     mergeElements(thisElement, TRUE);
284                     mergeElements(thisElement);
285                 }
286             },
287 
288             /**
289              * 将一个字符串节点拆散为两个字符串节点,并返回最后一个。
290              * 如果 offset 为 0,仍然拆成两个!第一个字符串为空文字节点。
291              * @param el
292              * @param offset
293              */
294             _4e_splitText:function (el, offset) {
295                 var doc = el.ownerDocument;
296 
297                 if (el.nodeType != DOM.TEXT_NODE) {
298                     return;
299                 }
300                 // If the offset is after the last char, IE creates the text node
301                 // on split, but don't include it into the DOM. So, we have to do
302                 // that manually here.
303                 if (UA['ie'] && offset == el.nodeValue.length) {
304                     var next = doc.createTextNode("");
305                     DOM.insertAfter(next, el);
306                     return next;
307                 }
308 
309                 var ret = el.splitText(offset);
310 
311                 // IE BUG: IE8 does not update the childNodes array in DOM after splitText(),
312                 // we need to make some DOM changes to make it update. (#3436)
313                 // UA['ie']==8 不对,
314                 // 判断不出来:UA['ie']==7 && doc.documentMode==7
315                 // 浏览器模式:当ie8处于兼容视图以及ie7时,UA['ie']==7
316                 // 文本模式: mode=5 ,mode=7, mode=8
317                 // ie8 浏览器有问题,而不在于是否哪个模式
318                 if (!!(doc.documentMode)) {
319                     var workaround = doc.createTextNode("");
320                     DOM.insertAfter(workaround, ret);
321                     DOM.remove(workaround);
322                 }
323 
324                 return ret;
325             },
326 
327             /**
328              * 得到该节点的所有附近节点集合(包括自身)
329              * @param node
330              * @param closerFirst
331              */
332             _4e_parents:function (node, closerFirst) {
333                 var parents = [];
334                 parents.__IS_NODELIST = 1;
335                 do {
336                     parents[  closerFirst ? 'push' : 'unshift' ](node);
337                 } while (node = node.parentNode);
338                 return parents;
339             },
340 
341             /**
342              * 得到该节点在前序遍历下的下一个节点
343              * @param el
344              * @param [startFromSibling]
345              * @param [nodeType]
346              * @param [guard]
347              */
348             _4e_nextSourceNode:function (el, startFromSibling, nodeType, guard) {
349                 // If "guard" is a node, transform it in a function.
350                 if (guard && !guard.call) {
351                     var guardNode = normalElDom(guard);
352                     guard = function (node) {
353                         return node !== guardNode;
354                     };
355                 }
356 
357                 var node = !startFromSibling && el.firstChild ,
358                     parent = el;
359 
360                 // Guarding when we're skipping the current element( no children or 'startFromSibling' ).
361                 // send the 'moving out' signal even we don't actually dive into.
362                 if (!node) {
363                     if (el.nodeType == DOM.ELEMENT_NODE &&
364                         guard && guard(el, TRUE) === FALSE) {
365                         return NULL;
366                     }
367                     node = el.nextSibling;
368                 }
369 
370                 while (!node && ( parent = parent.parentNode)) {
371                     // The guard check sends the "TRUE" paramenter to indicate that
372                     // we are moving "out" of the element.
373                     if (guard && guard(parent, TRUE) === FALSE) {
374                         return NULL;
375                     }
376                     node = parent.nextSibling;
377                 }
378 
379                 if (!node) {
380                     return NULL;
381                 }
382 
383                 if (guard && guard(node) === FALSE) {
384                     return NULL;
385                 }
386 
387                 if (nodeType && nodeType != node.nodeType) {
388                     return DOM._4e_nextSourceNode(node, FALSE, nodeType, guard);
389                 }
390 
391                 return node;
392             },
393 
394             /**
395              * 得到该节点在从右向左前序遍历下的下一个节点( rtl 情况)
396              * @param el
397              * @param startFromSibling
398              * @param nodeType
399              * @param guard
400              */
401             _4e_previousSourceNode:function (el, startFromSibling, nodeType, guard) {
402                 if (guard && !guard.call) {
403                     var guardNode = normalElDom(guard);
404                     guard = function (node) {
405                         return node !== guardNode;
406                     };
407                 }
408 
409                 var node = !startFromSibling && el.lastChild,
410                     parent = el;
411 
412                 // Guarding when we're skipping the current element( no children or 'startFromSibling' ).
413                 // send the 'moving out' signal even we don't actually dive into.
414                 if (!node) {
415                     if (el.nodeType == DOM.ELEMENT_NODE &&
416                         guard && guard(el, TRUE) === FALSE) {
417                         return NULL;
418                     }
419                     node = el.previousSibling;
420                 }
421 
422                 while (!node && ( parent = parent.parentNode )) {
423                     // The guard check sends the "TRUE" paramenter to indicate that
424                     // we are moving "out" of the element.
425                     if (guard && guard(parent, TRUE) === FALSE)
426                         return NULL;
427                     node = parent.previousSibling;
428                 }
429 
430                 if (!node) {
431                     return NULL;
432                 }
433 
434                 if (guard && guard(node) === FALSE) {
435                     return NULL;
436                 }
437 
438                 if (nodeType && node.nodeType != nodeType) {
439                     return DOM._4e_previousSourceNode(node, FALSE, nodeType, guard);
440                 }
441 
442                 return node;
443             },
444 
445             /**
446              * 得到两个节点的公共祖先节点
447              * @param el
448              * @param node
449              */
450             _4e_commonAncestor:function (el, node) {
451 
452                 node = normalElDom(node);
453 
454                 if (el === node) {
455                     return el;
456                 }
457 
458                 if (DOM.contains(node, el)) {
459                     return node;
460                 }
461 
462                 var start = el;
463 
464                 do {
465                     if (DOM.contains(start, node)) {
466                         return start;
467                     }
468                 } while (start = start.parentNode);
469 
470                 return NULL;
471             },
472 
473             /**
474              * 判断当前元素是否有设置过属性
475              */
476             _4e_hasAttributes:Utils.ieEngine < 9 ?
477                 function (el) {
478                     var attributes = el.attributes;
479                     for (var i = 0; i < attributes.length; i++) {
480                         var attribute = attributes[i];
481                         switch (attribute.name) {
482                             case 'class' :
483                                 // IE has a strange bug. If calling removeAttribute('className'),
484                                 // the attributes collection will still contain the "class"
485                                 // attribute, which will be marked as "specified", even if the
486                                 // outerHTML of the element is not displaying the class attribute.
487                                 if (el.getAttribute('class')) {
488                                     return TRUE;
489                                 }
490                                 break;
491                             default :
492                                 if (attribute.specified) {
493                                     return TRUE;
494                                 }
495                         }
496                     }
497                     return FALSE;
498                 } : function (el) {
499                 // 删除firefox自己添加的标志
500                 if (UA.gecko) {
501                     el.removeAttribute("_moz_dirty");
502                 }
503                 // 使用原生
504                 // ie8 莫名其妙多个shape??specified为false
505                 return el.hasAttributes();
506             },
507 
508             /**
509              * 得到两个元素的位置关系,参见
510              * <a href='https://developer.mozilla.org/en/DOM/Node.compareDocumentPosition'>
511              *     compareDocumentPosition
512              * </a>
513              * 注意:这里的 following 和 preceding 和 mdc 相反!
514              * @param el
515              * @param otherNode
516              */
517             _4e_position:function (el, otherNode) {
518                 var $other = normalElDom(otherNode);
519 
520                 if (el.compareDocumentPosition) {
521                     return el.compareDocumentPosition($other);
522                 }
523 
524                 // IE and Safari have no support for compareDocumentPosition.
525 
526                 if (el == $other) {
527                     return KEP.POSITION_IDENTICAL;
528                 }
529 
530                 // Only element nodes support contains and sourceIndex.
531                 if (el.nodeType == DOM.ELEMENT_NODE &&
532                     $other.nodeType == DOM.ELEMENT_NODE) {
533                     if (DOM.contains(el, $other)) {
534                         return KEP.POSITION_CONTAINS + KEP.POSITION_PRECEDING;
535                     }
536 
537                     if (DOM.contains($other, el)) {
538                         return KEP.POSITION_IS_CONTAINED + KEP.POSITION_FOLLOWING;
539                     }
540 
541                     if ('sourceIndex' in el) {
542                         return ( el.sourceIndex < 0 || $other.sourceIndex < 0 ) ?
543                             KEP.POSITION_DISCONNECTED :
544                             ( el.sourceIndex < $other.sourceIndex ) ?
545                                 KEP.POSITION_PRECEDING :
546                                 KEP.POSITION_FOLLOWING;
547                     }
548                 }
549 
550                 // For nodes that don't support compareDocumentPosition, contains
551                 // or sourceIndex, their "address" is compared.
552                 var addressOfThis = DOM._4e_address(el),
553                     addressOfOther = DOM._4e_address($other),
554                     minLevel = Math.min(addressOfThis.length, addressOfOther.length);
555 
556                 // Determinate preceed/follow relationship.
557                 for (var i = 0; i <= minLevel - 1; i++) {
558                     if (addressOfThis[ i ] != addressOfOther[ i ]) {
559                         return addressOfThis[ i ] < addressOfOther[ i ] ?
560                             KEP.POSITION_PRECEDING : KEP.POSITION_FOLLOWING;
561                     }
562                 }
563 
564                 // Determinate contains/contained relationship.
565                 return ( addressOfThis.length < addressOfOther.length ) ?
566                     KEP.POSITION_CONTAINS + KEP.POSITION_PRECEDING :
567                     KEP.POSITION_IS_CONTAINED + KEP.POSITION_FOLLOWING;
568             },
569 
570             /**
571              * 得到元素及其所有祖先元素在其兄弟节点中的序号。
572              * @param el
573              * @param [normalized]
574              */
575             _4e_address:function (el, normalized) {
576                 var address = [],
577                     $documentElement = el.ownerDocument.documentElement,
578                     node = el;
579 
580                 while (node && node != $documentElement) {
581                     address.unshift(DOM._4e_index(node, normalized));
582                     node = node.parentNode;
583                 }
584 
585                 return address;
586             },
587 
588             /**
589              * 删除一个元素
590              * @param el
591              * @param preserveChildren 是否保留其子元素(将子元素插入到当前元素之前)
592              */
593             _4e_remove:function (el, preserveChildren) {
594                 var parent = el.parentNode;
595                 if (parent) {
596                     if (preserveChildren) {
597                         // Move all children before the node.
598                         for (var child; child = el.firstChild;) {
599                             parent.insertBefore(el.removeChild(child), el);
600                         }
601                     }
602                     parent.removeChild(el);
603                 }
604                 return el;
605             },
606 
607             /**
608              * 清除左右空的字符串节点
609              * @param el
610              */
611             _4e_trim:function (el) {
612                 DOM._4e_ltrim(el);
613                 DOM._4e_rtrim(el);
614             },
615 
616             /**
617              * 清除左边空的字符串节点
618              * @param el
619              */
620             _4e_ltrim:function (el) {
621                 var child;
622                 while (child = el.firstChild) {
623                     if (child.nodeType == DOM.TEXT_NODE) {
624                         var trimmed = Utils.ltrim(child.nodeValue),
625                             originalLength = child.nodeValue.length;
626 
627                         if (!trimmed) {
628                             el.removeChild(child);
629                             continue;
630                         }
631                         else if (trimmed.length < originalLength) {
632                             DOM._4e_splitText(child, originalLength - trimmed.length);
633                             // IE BUG: child.remove() may raise JavaScript errors here. (#81)
634                             el.removeChild(el.firstChild);
635                         }
636                     }
637                     break;
638                 }
639             },
640 
641             /**
642              * 清除右边空的字符串节点
643              * @param el
644              */
645             _4e_rtrim:function (el) {
646                 var child;
647                 while (child = el.lastChild) {
648                     if (child.type == DOM.TEXT_NODE) {
649                         var trimmed = Utils.rtrim(child.nodeValue),
650                             originalLength = child.nodeValue.length;
651                         if (!trimmed) {
652                             el.removeChild(child);
653                             continue;
654                         } else if (trimmed.length < originalLength) {
655                             DOM._4e_splitText(child, trimmed.length);
656                             // IE BUG: child.getNext().remove() may raise JavaScript errors here.
657                             // (#81)
658                             el.removeChild(el.lastChild);
659                         }
660                     }
661                     break;
662                 }
663 
664                 if (!UA['ie'] && !UA.opera) {
665                     child = el.lastChild;
666                     if (child &&
667                         child.nodeType == 1 &&
668                         DOM.nodeName(child) == 'br') {
669                         el.removeChild(child);
670                     }
671                 }
672             },
673 
674             /**
675              * 将一个 bogus 元素添加到元素末尾
676              * @param el
677              */
678             _4e_appendBogus:function (el) {
679                 var lastChild = el.lastChild, bogus;
680 
681                 // Ignore empty/spaces text.
682                 while (lastChild &&
683                     lastChild.nodeType == DOM.TEXT_NODE &&
684                     !S.trim(lastChild.nodeValue)) {
685                     lastChild = lastChild.previousSibling;
686                 }
687 
688                 if (!lastChild ||
689                     lastChild.nodeType == DOM.TEXT_NODE ||
690                     DOM.nodeName(lastChild) !== 'br') {
691                     bogus = UA.opera ?
692                         el.ownerDocument.createTextNode('') :
693                         el.ownerDocument.createElement('br');
694                     if (UA.gecko) {
695                         bogus.setAttribute('type', '_moz');
696                     }
697                     el.appendChild(bogus);
698                 }
699             },
700 
701             /**
702              * 得到元素的 outerHTML
703              * @param el
704              */
705             _4e_outerHtml:function (el) {
706                 if (el.outerHTML) {
707                     // IE includes the <?xml:namespace> tag in the outerHTML of
708                     // namespaced element. So, we must strip it here. (#3341)
709                     return el.outerHTML.replace(/<\?[^>]*>/, '');
710                 }
711 
712                 var tmpDiv = el.ownerDocument.createElement('div');
713                 tmpDiv.appendChild(el.cloneNode(TRUE));
714                 return tmpDiv.innerHTML;
715             },
716 
717             /**
718              * 设置元素的自定义 data 值,并记录
719              * @param element
720              * @param database
721              * @param name
722              * @param value
723              */
724             _4e_setMarker:function (element, database, name, value) {
725                 element = normalEl(element);
726                 var id = element.data('list_marker_id') ||
727                         ( element.data('list_marker_id', S.guid()).data('list_marker_id')),
728                     markerNames = element.data('list_marker_names') ||
729                         ( element.data('list_marker_names', {}).data('list_marker_names'));
730                 database[id] = element;
731                 markerNames[name] = 1;
732                 return element.data(name, value);
733             },
734 
735             /**
736              * 清除元素设置的自定义 data 值。
737              * @param element
738              * @param database
739              * @param removeFromDatabase
740              */
741             _4e_clearMarkers:function (element, database, removeFromDatabase) {
742                 element = normalEl(element);
743                 var names = element.data('list_marker_names'),
744                     id = element.data('list_marker_id');
745                 for (var i in names) {
746                     if (names.hasOwnProperty(i)) {
747                         element.removeData(i);
748                     }
749                 }
750                 element.removeData('list_marker_names');
751                 if (removeFromDatabase) {
752                     element.removeData('list_marker_id');
753                     delete database[id];
754                 }
755             },
756 
757             /**
758              * 把属性从 target 复制到 el 上.
759              * @param el
760              * @param target
761              * @param skipAttributes
762              */
763             _4e_copyAttributes:function (el, target, skipAttributes) {
764                 target = normalEl(target);
765                 var attributes = el.attributes;
766                 skipAttributes = skipAttributes || {};
767 
768                 for (var n = 0; n < attributes.length; n++) {
769                     // Lowercase attribute name hard rule is broken for
770                     // some attribute on IE, e.g. CHECKED.
771                     var attribute = attributes[n],
772                         attrName = attribute.name.toLowerCase(),
773                         attrValue;
774 
775                     // We can set the type only once, so do it with the proper value, not copying it.
776                     if (attrName in skipAttributes) {
777                         continue;
778                     }
779 
780                     if (attrName == 'checked' && ( attrValue = DOM.attr(el, attrName) )) {
781                         target.attr(attrName, attrValue);
782                     }
783                     // IE BUG: value attribute is never specified even if it exists.
784                     else if (attribute.specified ||
785                         ( UA['ie'] && attribute.value && attrName == 'value' )) {
786                         attrValue = DOM.attr(el, attrName);
787                         if (attrValue === NULL) {
788                             attrValue = attribute.nodeValue;
789                         }
790                         target.attr(attrName, attrValue);
791                     }
792                 }
793 
794                 // The style:
795                 if (el.style.cssText !== '') {
796                     target[0].style.cssText = el.style.cssText;
797                 }
798             },
799 
800             /**
801              * 当前元素是否可以被编辑
802              * @param el
803              */
804             _4e_isEditable:function (el) {
805                 // Get the element DTD (defaults to span for unknown elements).
806                 var name = DOM.nodeName(el),
807                     dtd = !xhtml_dtd.$nonEditable[ name ] &&
808                         ( xhtml_dtd[ name ] || xhtml_dtd["span"] );
809                 // In the DTD # == text node.
810                 return dtd && dtd['#text'];
811             },
812 
813             /**
814              * 根据dom路径得到某个节点
815              * @param doc
816              * @param address
817              * @param [normalized]
818              * @return {NodeList}
819              */
820             _4e_getByAddress:function (doc, address, normalized) {
821                 var $ = doc.documentElement;
822 
823                 for (var i = 0; $ && i < address.length; i++) {
824                     var target = address[ i ];
825 
826                     if (!normalized) {
827                         $ = $.childNodes[ target ];
828                         continue;
829                     }
830 
831                     var currentIndex = -1;
832 
833                     for (var j = 0; j < $.childNodes.length; j++) {
834                         var candidate = $.childNodes[ j ];
835 
836                         if (normalized === TRUE &&
837                             candidate.nodeType == 3 &&
838                             candidate.previousSibling &&
839                             candidate.previousSibling.nodeType == 3) {
840                             continue;
841                         }
842 
843                         currentIndex++;
844 
845                         if (currentIndex == target) {
846                             $ = candidate;
847                             break;
848                         }
849                     }
850                 }
851 
852                 return $;
853             }
854         };
855 
856 
857     function mergeElements(element, isNext) {
858         var sibling = element[isNext ? "next" : "prev"](undefined, 1);
859 
860         if (sibling && sibling[0].nodeType == DOM.ELEMENT_NODE) {
861 
862             // Jumping over bookmark nodes and empty inline elements, e.g. <b><i></i></b>,
863             // queuing them to be moved later. (#5567)
864             var pendingNodes = [];
865 
866             while (sibling.attr('_ke_bookmark') || sibling._4e_isEmptyInlineRemovable(undefined)) {
867                 pendingNodes.push(sibling);
868                 sibling = isNext ? sibling.next(undefined, 1) : sibling.prev(undefined, 1);
869                 if (!sibling) {
870                     return;
871                 }
872             }
873 
874             if (element._4e_isIdentical(sibling, undefined)) {
875                 // Save the last child to be checked too, to merge things like
876                 // <b><i></i></b><b><i></i></b> => <b><i></i></b>
877                 var innerSibling = new Node(isNext ? element[0].lastChild : element[0].firstChild);
878 
879                 // Move pending nodes first into the target element.
880                 while (pendingNodes.length) {
881                     pendingNodes.shift()._4e_move(element, !isNext, undefined);
882                 }
883 
884                 sibling._4e_moveChildren(element, !isNext, undefined);
885                 sibling.remove();
886 
887                 // Now check the last inner child (see two comments above).
888                 if (innerSibling[0] && innerSibling[0].nodeType == DOM.ELEMENT_NODE) {
889                     innerSibling._4e_mergeSiblings();
890                 }
891             }
892         }
893     }
894 
895     Utils.injectDom(editorDom);
896 }, {
897     requires:['./base', './utils']
898 });
899