4 * Copyright 2009, Moxiecode Systems AB
5 * Released under LGPL License.
7 * License: http://tinymce.moxiecode.com/license
8 * Contributing: http://tinymce.moxiecode.com/contributing
12 tinymce.dom.RangeUtils = function(dom) {
13 var INVISIBLE_CHAR = '\uFEFF';
16 * Walks the specified range like object and executes the callback for each sibling collection it finds.
18 * @param {Object} rng Range like object.
19 * @param {function} callback Callback function to execute for each sibling collection.
21 this.walk = function(rng, callback) {
22 var startContainer = rng.startContainer,
23 startOffset = rng.startOffset,
24 endContainer = rng.endContainer,
25 endOffset = rng.endOffset,
27 endPoint, node, parent, siblings, nodes;
29 // Handle table cell selection the table plugin enables
30 // you to fake select table cells and perform formatting actions on them
31 nodes = dom.select('td.mceSelected,th.mceSelected');
32 if (nodes.length > 0) {
33 tinymce.each(nodes, function(node) {
44 * @param {Node} node Node to collect siblings from.
45 * @param {String} name Name of the sibling to check for.
46 * @return {Array} Array of collected siblings.
48 function collectSiblings(node, name, end_node) {
51 for (; node && node != end_node; node = node[name])
58 * Find an end point this is the node just before the common ancestor root.
61 * @param {Node} node Node to start at.
62 * @param {Node} root Root/ancestor element to stop just before.
63 * @return {Node} Node just before the root element.
65 function findEndPoint(node, root) {
67 if (node.parentNode == root)
70 node = node.parentNode;
74 function walkBoundary(start_node, end_node, next) {
75 var siblingName = next ? 'nextSibling' : 'previousSibling';
77 for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
78 parent = node.parentNode;
79 siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
81 if (siblings.length) {
90 // If index based start position then resolve it
91 if (startContainer.nodeType == 1 && startContainer.hasChildNodes())
92 startContainer = startContainer.childNodes[startOffset];
94 // If index based end position then resolve it
95 if (endContainer.nodeType == 1 && endContainer.hasChildNodes())
96 endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];
98 // Find common ancestor and end points
99 ancestor = dom.findCommonAncestor(startContainer, endContainer);
102 if (startContainer == endContainer)
103 return callback([startContainer]);
106 for (node = startContainer; node; node = node.parentNode) {
107 if (node == endContainer)
108 return walkBoundary(startContainer, ancestor, true);
110 if (node == ancestor)
114 // Process right side
115 for (node = endContainer; node; node = node.parentNode) {
116 if (node == startContainer)
117 return walkBoundary(endContainer, ancestor);
119 if (node == ancestor)
123 // Find start/end point
124 startPoint = findEndPoint(startContainer, ancestor) || startContainer;
125 endPoint = findEndPoint(endContainer, ancestor) || endContainer;
128 walkBoundary(startContainer, startPoint, true);
130 // Walk the middle from start to end point
131 siblings = collectSiblings(
132 startPoint == startContainer ? startPoint : startPoint.nextSibling,
134 endPoint == endContainer ? endPoint.nextSibling : endPoint
141 walkBoundary(endContainer, endPoint);
145 * Splits the specified range at it's start/end points.
147 * @param {Range/RangeObject} rng Range to split.
148 * @return {Object} Range position object.
150 /* this.split = function(rng) {
151 var startContainer = rng.startContainer,
152 startOffset = rng.startOffset,
153 endContainer = rng.endContainer,
154 endOffset = rng.endOffset;
156 function splitText(node, offset) {
157 if (offset == node.nodeValue.length)
158 node.appendData(INVISIBLE_CHAR);
160 node = node.splitText(offset);
162 if (node.nodeValue === INVISIBLE_CHAR)
168 // Handle single text node
169 if (startContainer == endContainer) {
170 if (startContainer.nodeType == 3) {
171 if (startOffset != 0)
172 startContainer = endContainer = splitText(startContainer, startOffset);
174 if (endOffset - startOffset != startContainer.nodeValue.length)
175 splitText(startContainer, endOffset - startOffset);
178 // Split startContainer text node if needed
179 if (startContainer.nodeType == 3 && startOffset != 0) {
180 startContainer = splitText(startContainer, startOffset);
184 // Split endContainer text node if needed
185 if (endContainer.nodeType == 3 && endOffset != endContainer.nodeValue.length) {
186 endContainer = splitText(endContainer, endOffset).previousSibling;
187 endOffset = endContainer.nodeValue.length;
192 startContainer : startContainer,
193 startOffset : startOffset,
194 endContainer : endContainer,
195 endOffset : endOffset
202 * Compares two ranges and checks if they are equal.
205 * @param {DOMRange} rng1 First range to compare.
206 * @param {DOMRange} rng2 First range to compare.
207 * @return {Boolean} true/false if the ranges are equal.
209 tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) {
211 // Compare native IE ranges
212 if (rng1.item || rng1.duplicate) {
213 // Both are control ranges and the selected element matches
214 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
217 // Both are text ranges and the range matches
218 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))
221 // Compare w3c ranges
222 return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;