]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/javascript/tiny_mce/classes/dom/Range.js
Release 6.5.0
[Github/sugarcrm.git] / include / javascript / tiny_mce / classes / dom / Range.js
1 /**
2  * Range.js
3  *
4  * Copyright 2009, Moxiecode Systems AB
5  * Released under LGPL License.
6  *
7  * License: http://tinymce.moxiecode.com/license
8  * Contributing: http://tinymce.moxiecode.com/contributing
9  */
10
11 (function(ns) {
12         // Range constructor
13         function Range(dom) {
14                 var t = this,
15                         doc = dom.doc,
16                         EXTRACT = 0,
17                         CLONE = 1,
18                         DELETE = 2,
19                         TRUE = true,
20                         FALSE = false,
21                         START_OFFSET = 'startOffset',
22                         START_CONTAINER = 'startContainer',
23                         END_CONTAINER = 'endContainer',
24                         END_OFFSET = 'endOffset',
25                         extend = tinymce.extend,
26                         nodeIndex = dom.nodeIndex;
27
28                 extend(t, {
29                         // Inital states
30                         startContainer : doc,
31                         startOffset : 0,
32                         endContainer : doc,
33                         endOffset : 0,
34                         collapsed : TRUE,
35                         commonAncestorContainer : doc,
36
37                         // Range constants
38                         START_TO_START : 0,
39                         START_TO_END : 1,
40                         END_TO_END : 2,
41                         END_TO_START : 3,
42
43                         // Public methods
44                         setStart : setStart,
45                         setEnd : setEnd,
46                         setStartBefore : setStartBefore,
47                         setStartAfter : setStartAfter,
48                         setEndBefore : setEndBefore,
49                         setEndAfter : setEndAfter,
50                         collapse : collapse,
51                         selectNode : selectNode,
52                         selectNodeContents : selectNodeContents,
53                         compareBoundaryPoints : compareBoundaryPoints,
54                         deleteContents : deleteContents,
55                         extractContents : extractContents,
56                         cloneContents : cloneContents,
57                         insertNode : insertNode,
58                         surroundContents : surroundContents,
59                         cloneRange : cloneRange
60                 });
61
62                 function setStart(n, o) {
63                         _setEndPoint(TRUE, n, o);
64                 };
65
66                 function setEnd(n, o) {
67                         _setEndPoint(FALSE, n, o);
68                 };
69
70                 function setStartBefore(n) {
71                         setStart(n.parentNode, nodeIndex(n));
72                 };
73
74                 function setStartAfter(n) {
75                         setStart(n.parentNode, nodeIndex(n) + 1);
76                 };
77
78                 function setEndBefore(n) {
79                         setEnd(n.parentNode, nodeIndex(n));
80                 };
81
82                 function setEndAfter(n) {
83                         setEnd(n.parentNode, nodeIndex(n) + 1);
84                 };
85
86                 function collapse(ts) {
87                         if (ts) {
88                                 t[END_CONTAINER] = t[START_CONTAINER];
89                                 t[END_OFFSET] = t[START_OFFSET];
90                         } else {
91                                 t[START_CONTAINER] = t[END_CONTAINER];
92                                 t[START_OFFSET] = t[END_OFFSET];
93                         }
94
95                         t.collapsed = TRUE;
96                 };
97
98                 function selectNode(n) {
99                         setStartBefore(n);
100                         setEndAfter(n);
101                 };
102
103                 function selectNodeContents(n) {
104                         setStart(n, 0);
105                         setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
106                 };
107
108                 function compareBoundaryPoints(h, r) {
109                         var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET],
110                         rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;
111
112                         // Check START_TO_START
113                         if (h === 0)
114                                 return _compareBoundaryPoints(sc, so, rsc, rso);
115         
116                         // Check START_TO_END
117                         if (h === 1)
118                                 return _compareBoundaryPoints(ec, eo, rsc, rso);
119         
120                         // Check END_TO_END
121                         if (h === 2)
122                                 return _compareBoundaryPoints(ec, eo, rec, reo);
123         
124                         // Check END_TO_START
125                         if (h === 3) 
126                                 return _compareBoundaryPoints(sc, so, rec, reo);
127                 };
128
129                 function deleteContents() {
130                         _traverse(DELETE);
131                 };
132
133                 function extractContents() {
134                         return _traverse(EXTRACT);
135                 };
136
137                 function cloneContents() {
138                         return _traverse(CLONE);
139                 };
140
141                 function insertNode(n) {
142                         var startContainer = this[START_CONTAINER],
143                                 startOffset = this[START_OFFSET], nn, o;
144
145                         // Node is TEXT_NODE or CDATA
146                         if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
147                                 if (!startOffset) {
148                                         // At the start of text
149                                         startContainer.parentNode.insertBefore(n, startContainer);
150                                 } else if (startOffset >= startContainer.nodeValue.length) {
151                                         // At the end of text
152                                         dom.insertAfter(n, startContainer);
153                                 } else {
154                                         // Middle, need to split
155                                         nn = startContainer.splitText(startOffset);
156                                         startContainer.parentNode.insertBefore(n, nn);
157                                 }
158                         } else {
159                                 // Insert element node
160                                 if (startContainer.childNodes.length > 0)
161                                         o = startContainer.childNodes[startOffset];
162
163                                 if (o)
164                                         startContainer.insertBefore(n, o);
165                                 else
166                                         startContainer.appendChild(n);
167                         }
168                 };
169
170                 function surroundContents(n) {
171                         var f = t.extractContents();
172
173                         t.insertNode(n);
174                         n.appendChild(f);
175                         t.selectNode(n);
176                 };
177
178                 function cloneRange() {
179                         return extend(new Range(dom), {
180                                 startContainer : t[START_CONTAINER],
181                                 startOffset : t[START_OFFSET],
182                                 endContainer : t[END_CONTAINER],
183                                 endOffset : t[END_OFFSET],
184                                 collapsed : t.collapsed,
185                                 commonAncestorContainer : t.commonAncestorContainer
186                         });
187                 };
188
189                 // Private methods
190
191                 function _getSelectedNode(container, offset) {
192                         var child;
193
194                         if (container.nodeType == 3 /* TEXT_NODE */)
195                                 return container;
196
197                         if (offset < 0)
198                                 return container;
199
200                         child = container.firstChild;
201                         while (child && offset > 0) {
202                                 --offset;
203                                 child = child.nextSibling;
204                         }
205
206                         if (child)
207                                 return child;
208
209                         return container;
210                 };
211
212                 function _isCollapsed() {
213                         return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
214                 };
215
216                 function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
217                         var c, offsetC, n, cmnRoot, childA, childB;
218                         
219                         // In the first case the boundary-points have the same container. A is before B
220                         // if its offset is less than the offset of B, A is equal to B if its offset is
221                         // equal to the offset of B, and A is after B if its offset is greater than the
222                         // offset of B.
223                         if (containerA == containerB) {
224                                 if (offsetA == offsetB)
225                                         return 0; // equal
226
227                                 if (offsetA < offsetB)
228                                         return -1; // before
229
230                                 return 1; // after
231                         }
232
233                         // In the second case a child node C of the container of A is an ancestor
234                         // container of B. In this case, A is before B if the offset of A is less than or
235                         // equal to the index of the child node C and A is after B otherwise.
236                         c = containerB;
237                         while (c && c.parentNode != containerA)
238                                 c = c.parentNode;
239
240                         if (c) {
241                                 offsetC = 0;
242                                 n = containerA.firstChild;
243
244                                 while (n != c && offsetC < offsetA) {
245                                         offsetC++;
246                                         n = n.nextSibling;
247                                 }
248
249                                 if (offsetA <= offsetC)
250                                         return -1; // before
251
252                                 return 1; // after
253                         }
254
255                         // In the third case a child node C of the container of B is an ancestor container
256                         // of A. In this case, A is before B if the index of the child node C is less than
257                         // the offset of B and A is after B otherwise.
258                         c = containerA;
259                         while (c && c.parentNode != containerB) {
260                                 c = c.parentNode;
261                         }
262
263                         if (c) {
264                                 offsetC = 0;
265                                 n = containerB.firstChild;
266
267                                 while (n != c && offsetC < offsetB) {
268                                         offsetC++;
269                                         n = n.nextSibling;
270                                 }
271
272                                 if (offsetC < offsetB)
273                                         return -1; // before
274
275                                 return 1; // after
276                         }
277
278                         // In the fourth case, none of three other cases hold: the containers of A and B
279                         // are siblings or descendants of sibling nodes. In this case, A is before B if
280                         // the container of A is before the container of B in a pre-order traversal of the
281                         // Ranges' context tree and A is after B otherwise.
282                         cmnRoot = dom.findCommonAncestor(containerA, containerB);
283                         childA = containerA;
284
285                         while (childA && childA.parentNode != cmnRoot)
286                                 childA = childA.parentNode;
287
288                         if (!childA)
289                                 childA = cmnRoot;
290
291                         childB = containerB;
292                         while (childB && childB.parentNode != cmnRoot)
293                                 childB = childB.parentNode;
294
295                         if (!childB)
296                                 childB = cmnRoot;
297
298                         if (childA == childB)
299                                 return 0; // equal
300
301                         n = cmnRoot.firstChild;
302                         while (n) {
303                                 if (n == childA)
304                                         return -1; // before
305
306                                 if (n == childB)
307                                         return 1; // after
308
309                                 n = n.nextSibling;
310                         }
311                 };
312
313                 function _setEndPoint(st, n, o) {
314                         var ec, sc;
315
316                         if (st) {
317                                 t[START_CONTAINER] = n;
318                                 t[START_OFFSET] = o;
319                         } else {
320                                 t[END_CONTAINER] = n;
321                                 t[END_OFFSET] = o;
322                         }
323
324                         // If one boundary-point of a Range is set to have a root container
325                         // other than the current one for the Range, the Range is collapsed to
326                         // the new position. This enforces the restriction that both boundary-
327                         // points of a Range must have the same root container.
328                         ec = t[END_CONTAINER];
329                         while (ec.parentNode)
330                                 ec = ec.parentNode;
331
332                         sc = t[START_CONTAINER];
333                         while (sc.parentNode)
334                                 sc = sc.parentNode;
335
336                         if (sc == ec) {
337                                 // The start position of a Range is guaranteed to never be after the
338                                 // end position. To enforce this restriction, if the start is set to
339                                 // be at a position after the end, the Range is collapsed to that
340                                 // position.
341                                 if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)
342                                         t.collapse(st);
343                         } else
344                                 t.collapse(st);
345
346                         t.collapsed = _isCollapsed();
347                         t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
348                 };
349
350                 function _traverse(how) {
351                         var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
352
353                         if (t[START_CONTAINER] == t[END_CONTAINER])
354                                 return _traverseSameContainer(how);
355
356                         for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
357                                 if (p == t[START_CONTAINER])
358                                         return _traverseCommonStartContainer(c, how);
359
360                                 ++endContainerDepth;
361                         }
362
363                         for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
364                                 if (p == t[END_CONTAINER])
365                                         return _traverseCommonEndContainer(c, how);
366
367                                 ++startContainerDepth;
368                         }
369
370                         depthDiff = startContainerDepth - endContainerDepth;
371
372                         startNode = t[START_CONTAINER];
373                         while (depthDiff > 0) {
374                                 startNode = startNode.parentNode;
375                                 depthDiff--;
376                         }
377
378                         endNode = t[END_CONTAINER];
379                         while (depthDiff < 0) {
380                                 endNode = endNode.parentNode;
381                                 depthDiff++;
382                         }
383
384                         // ascend the ancestor hierarchy until we have a common parent.
385                         for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
386                                 startNode = sp;
387                                 endNode = ep;
388                         }
389
390                         return _traverseCommonAncestors(startNode, endNode, how);
391                 };
392
393                  function _traverseSameContainer(how) {
394                         var frag, s, sub, n, cnt, sibling, xferNode;
395
396                         if (how != DELETE)
397                                 frag = doc.createDocumentFragment();
398
399                         // If selection is empty, just return the fragment
400                         if (t[START_OFFSET] == t[END_OFFSET])
401                                 return frag;
402
403                         // Text node needs special case handling
404                         if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
405                                 // get the substring
406                                 s = t[START_CONTAINER].nodeValue;
407                                 sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
408
409                                 // set the original text node to its new value
410                                 if (how != CLONE) {
411                                         t[START_CONTAINER].deleteData(t[START_OFFSET], t[END_OFFSET] - t[START_OFFSET]);
412
413                                         // Nothing is partially selected, so collapse to start point
414                                         t.collapse(TRUE);
415                                 }
416
417                                 if (how == DELETE)
418                                         return;
419
420                                 frag.appendChild(doc.createTextNode(sub));
421                                 return frag;
422                         }
423
424                         // Copy nodes between the start/end offsets.
425                         n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
426                         cnt = t[END_OFFSET] - t[START_OFFSET];
427
428                         while (cnt > 0) {
429                                 sibling = n.nextSibling;
430                                 xferNode = _traverseFullySelected(n, how);
431
432                                 if (frag)
433                                         frag.appendChild( xferNode );
434
435                                 --cnt;
436                                 n = sibling;
437                         }
438
439                         // Nothing is partially selected, so collapse to start point
440                         if (how != CLONE)
441                                 t.collapse(TRUE);
442
443                         return frag;
444                 };
445
446                 function _traverseCommonStartContainer(endAncestor, how) {
447                         var frag, n, endIdx, cnt, sibling, xferNode;
448
449                         if (how != DELETE)
450                                 frag = doc.createDocumentFragment();
451
452                         n = _traverseRightBoundary(endAncestor, how);
453
454                         if (frag)
455                                 frag.appendChild(n);
456
457                         endIdx = nodeIndex(endAncestor);
458                         cnt = endIdx - t[START_OFFSET];
459
460                         if (cnt <= 0) {
461                                 // Collapse to just before the endAncestor, which
462                                 // is partially selected.
463                                 if (how != CLONE) {
464                                         t.setEndBefore(endAncestor);
465                                         t.collapse(FALSE);
466                                 }
467
468                                 return frag;
469                         }
470
471                         n = endAncestor.previousSibling;
472                         while (cnt > 0) {
473                                 sibling = n.previousSibling;
474                                 xferNode = _traverseFullySelected(n, how);
475
476                                 if (frag)
477                                         frag.insertBefore(xferNode, frag.firstChild);
478
479                                 --cnt;
480                                 n = sibling;
481                         }
482
483                         // Collapse to just before the endAncestor, which
484                         // is partially selected.
485                         if (how != CLONE) {
486                                 t.setEndBefore(endAncestor);
487                                 t.collapse(FALSE);
488                         }
489
490                         return frag;
491                 };
492
493                 function _traverseCommonEndContainer(startAncestor, how) {
494                         var frag, startIdx, n, cnt, sibling, xferNode;
495
496                         if (how != DELETE)
497                                 frag = doc.createDocumentFragment();
498
499                         n = _traverseLeftBoundary(startAncestor, how);
500                         if (frag)
501                                 frag.appendChild(n);
502
503                         startIdx = nodeIndex(startAncestor);
504                         ++startIdx; // Because we already traversed it
505
506                         cnt = t[END_OFFSET] - startIdx;
507                         n = startAncestor.nextSibling;
508                         while (cnt > 0) {
509                                 sibling = n.nextSibling;
510                                 xferNode = _traverseFullySelected(n, how);
511
512                                 if (frag)
513                                         frag.appendChild(xferNode);
514
515                                 --cnt;
516                                 n = sibling;
517                         }
518
519                         if (how != CLONE) {
520                                 t.setStartAfter(startAncestor);
521                                 t.collapse(TRUE);
522                         }
523
524                         return frag;
525                 };
526
527                 function _traverseCommonAncestors(startAncestor, endAncestor, how) {
528                         var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
529
530                         if (how != DELETE)
531                                 frag = doc.createDocumentFragment();
532
533                         n = _traverseLeftBoundary(startAncestor, how);
534                         if (frag)
535                                 frag.appendChild(n);
536
537                         commonParent = startAncestor.parentNode;
538                         startOffset = nodeIndex(startAncestor);
539                         endOffset = nodeIndex(endAncestor);
540                         ++startOffset;
541
542                         cnt = endOffset - startOffset;
543                         sibling = startAncestor.nextSibling;
544
545                         while (cnt > 0) {
546                                 nextSibling = sibling.nextSibling;
547                                 n = _traverseFullySelected(sibling, how);
548
549                                 if (frag)
550                                         frag.appendChild(n);
551
552                                 sibling = nextSibling;
553                                 --cnt;
554                         }
555
556                         n = _traverseRightBoundary(endAncestor, how);
557
558                         if (frag)
559                                 frag.appendChild(n);
560
561                         if (how != CLONE) {
562                                 t.setStartAfter(startAncestor);
563                                 t.collapse(TRUE);
564                         }
565
566                         return frag;
567                 };
568
569                 function _traverseRightBoundary(root, how) {
570                         var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
571
572                         if (next == root)
573                                 return _traverseNode(next, isFullySelected, FALSE, how);
574
575                         parent = next.parentNode;
576                         clonedParent = _traverseNode(parent, FALSE, FALSE, how);
577
578                         while (parent) {
579                                 while (next) {
580                                         prevSibling = next.previousSibling;
581                                         clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
582
583                                         if (how != DELETE)
584                                                 clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
585
586                                         isFullySelected = TRUE;
587                                         next = prevSibling;
588                                 }
589
590                                 if (parent == root)
591                                         return clonedParent;
592
593                                 next = parent.previousSibling;
594                                 parent = parent.parentNode;
595
596                                 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
597
598                                 if (how != DELETE)
599                                         clonedGrandParent.appendChild(clonedParent);
600
601                                 clonedParent = clonedGrandParent;
602                         }
603                 };
604
605                 function _traverseLeftBoundary(root, how) {
606                         var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
607
608                         if (next == root)
609                                 return _traverseNode(next, isFullySelected, TRUE, how);
610
611                         parent = next.parentNode;
612                         clonedParent = _traverseNode(parent, FALSE, TRUE, how);
613
614                         while (parent) {
615                                 while (next) {
616                                         nextSibling = next.nextSibling;
617                                         clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
618
619                                         if (how != DELETE)
620                                                 clonedParent.appendChild(clonedChild);
621
622                                         isFullySelected = TRUE;
623                                         next = nextSibling;
624                                 }
625
626                                 if (parent == root)
627                                         return clonedParent;
628
629                                 next = parent.nextSibling;
630                                 parent = parent.parentNode;
631
632                                 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
633
634                                 if (how != DELETE)
635                                         clonedGrandParent.appendChild(clonedParent);
636
637                                 clonedParent = clonedGrandParent;
638                         }
639                 };
640
641                 function _traverseNode(n, isFullySelected, isLeft, how) {
642                         var txtValue, newNodeValue, oldNodeValue, offset, newNode;
643
644                         if (isFullySelected)
645                                 return _traverseFullySelected(n, how);
646
647                         if (n.nodeType == 3 /* TEXT_NODE */) {
648                                 txtValue = n.nodeValue;
649
650                                 if (isLeft) {
651                                         offset = t[START_OFFSET];
652                                         newNodeValue = txtValue.substring(offset);
653                                         oldNodeValue = txtValue.substring(0, offset);
654                                 } else {
655                                         offset = t[END_OFFSET];
656                                         newNodeValue = txtValue.substring(0, offset);
657                                         oldNodeValue = txtValue.substring(offset);
658                                 }
659
660                                 if (how != CLONE)
661                                         n.nodeValue = oldNodeValue;
662
663                                 if (how == DELETE)
664                                         return;
665
666                                 newNode = n.cloneNode(FALSE);
667                                 newNode.nodeValue = newNodeValue;
668
669                                 return newNode;
670                         }
671
672                         if (how == DELETE)
673                                 return;
674
675                         return n.cloneNode(FALSE);
676                 };
677
678                 function _traverseFullySelected(n, how) {
679                         if (how != DELETE)
680                                 return how == CLONE ? n.cloneNode(TRUE) : n;
681
682                         n.parentNode.removeChild(n);
683                 };
684         };
685
686         ns.Range = Range;
687 })(tinymce.dom);