1 | var Range; |
---|
2 | |
---|
3 | Range = {}; |
---|
4 | |
---|
5 | Range.sniff = function(r) { |
---|
6 | if (r.commonAncestorContainer != null) { |
---|
7 | return new Range.BrowserRange(r); |
---|
8 | } else if (typeof r.start === "string") { |
---|
9 | return new Range.SerializedRange(r); |
---|
10 | } else if (r.start && typeof r.start === "object") { |
---|
11 | return new Range.NormalizedRange(r); |
---|
12 | } else { |
---|
13 | console.error(_t("Could not sniff range type")); |
---|
14 | return false; |
---|
15 | } |
---|
16 | }; |
---|
17 | |
---|
18 | Range.BrowserRange = (function() { |
---|
19 | |
---|
20 | function BrowserRange(obj) { |
---|
21 | this.commonAncestorContainer = obj.commonAncestorContainer; |
---|
22 | this.startContainer = obj.startContainer; |
---|
23 | this.startOffset = obj.startOffset; |
---|
24 | this.endContainer = obj.endContainer; |
---|
25 | this.endOffset = obj.endOffset; |
---|
26 | } |
---|
27 | |
---|
28 | BrowserRange.prototype.normalize = function(root) { |
---|
29 | var it, node, nr, offset, p, r, _i, _len, _ref; |
---|
30 | if (this.tainted) { |
---|
31 | console.error(_t("You may only call normalize() once on a BrowserRange!")); |
---|
32 | return false; |
---|
33 | } else { |
---|
34 | this.tainted = true; |
---|
35 | } |
---|
36 | r = {}; |
---|
37 | nr = {}; |
---|
38 | _ref = ['start', 'end']; |
---|
39 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { |
---|
40 | p = _ref[_i]; |
---|
41 | node = this[p + 'Container']; |
---|
42 | offset = this[p + 'Offset']; |
---|
43 | if (!((node != null) && (offset != null))) return false; |
---|
44 | if (node.nodeType === 1) { |
---|
45 | it = node.childNodes[offset]; |
---|
46 | node = it || node.childNodes[offset - 1]; |
---|
47 | if (node.nodeType === 1 && !node.firstChild) { |
---|
48 | it = null; |
---|
49 | node = node.previousSibling; |
---|
50 | } |
---|
51 | while (node.nodeType !== 3) { |
---|
52 | node = node.firstChild; |
---|
53 | } |
---|
54 | offset = it ? 0 : node.nodeValue.length; |
---|
55 | } |
---|
56 | r[p] = node; |
---|
57 | r[p + 'Offset'] = offset; |
---|
58 | } |
---|
59 | nr.start = r.startOffset > 0 ? r.start.splitText(r.startOffset) : r.start; |
---|
60 | if (r.start === r.end) { |
---|
61 | if ((r.endOffset - r.startOffset) < nr.start.nodeValue.length) { |
---|
62 | nr.start.splitText(r.endOffset - r.startOffset); |
---|
63 | } |
---|
64 | nr.end = nr.start; |
---|
65 | } else { |
---|
66 | if (r.endOffset < r.end.nodeValue.length) r.end.splitText(r.endOffset); |
---|
67 | nr.end = r.end; |
---|
68 | } |
---|
69 | nr.commonAncestor = this.commonAncestorContainer; |
---|
70 | while (nr.commonAncestor.nodeType !== 1) { |
---|
71 | nr.commonAncestor = nr.commonAncestor.parentNode; |
---|
72 | } |
---|
73 | return new Range.NormalizedRange(nr); |
---|
74 | }; |
---|
75 | |
---|
76 | BrowserRange.prototype.serialize = function(root, ignoreSelector) { |
---|
77 | return this.normalize(root).serialize(root, ignoreSelector); |
---|
78 | }; |
---|
79 | |
---|
80 | return BrowserRange; |
---|
81 | |
---|
82 | })(); |
---|
83 | |
---|
84 | Range.NormalizedRange = (function() { |
---|
85 | |
---|
86 | function NormalizedRange(obj) { |
---|
87 | this.commonAncestor = obj.commonAncestor; |
---|
88 | this.start = obj.start; |
---|
89 | this.end = obj.end; |
---|
90 | } |
---|
91 | |
---|
92 | NormalizedRange.prototype.normalize = function(root) { |
---|
93 | return this; |
---|
94 | }; |
---|
95 | |
---|
96 | NormalizedRange.prototype.limit = function(bounds) { |
---|
97 | var nodes, parent, startParents, _i, _len, _ref; |
---|
98 | nodes = $.grep(this.textNodes(), function(node) { |
---|
99 | return node.parentNode === bounds || $.contains(bounds, node.parentNode); |
---|
100 | }); |
---|
101 | if (!nodes.length) return null; |
---|
102 | this.start = nodes[0]; |
---|
103 | this.end = nodes[nodes.length - 1]; |
---|
104 | startParents = $(this.start).parents(); |
---|
105 | _ref = $(this.end).parents(); |
---|
106 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { |
---|
107 | parent = _ref[_i]; |
---|
108 | if (startParents.index(parent) !== -1) { |
---|
109 | this.commonAncestor = parent; |
---|
110 | break; |
---|
111 | } |
---|
112 | } |
---|
113 | return this; |
---|
114 | }; |
---|
115 | |
---|
116 | NormalizedRange.prototype.serialize = function(root, ignoreSelector) { |
---|
117 | var end, serialization, start; |
---|
118 | serialization = function(node, isEnd) { |
---|
119 | var n, nodes, offset, origParent, textNodes, xpath, _i, _len; |
---|
120 | if (ignoreSelector) { |
---|
121 | origParent = $(node).parents(":not(" + ignoreSelector + ")").eq(0); |
---|
122 | } else { |
---|
123 | origParent = $(node).parent(); |
---|
124 | } |
---|
125 | xpath = origParent.xpath(root)[0]; |
---|
126 | textNodes = origParent.textNodes(); |
---|
127 | nodes = textNodes.slice(0, textNodes.index(node)); |
---|
128 | offset = 0; |
---|
129 | for (_i = 0, _len = nodes.length; _i < _len; _i++) { |
---|
130 | n = nodes[_i]; |
---|
131 | offset += n.nodeValue.length; |
---|
132 | } |
---|
133 | if (isEnd) { |
---|
134 | return [xpath, offset + node.nodeValue.length]; |
---|
135 | } else { |
---|
136 | return [xpath, offset]; |
---|
137 | } |
---|
138 | }; |
---|
139 | start = serialization(this.start); |
---|
140 | end = serialization(this.end, true); |
---|
141 | return new Range.SerializedRange({ |
---|
142 | start: start[0], |
---|
143 | end: end[0], |
---|
144 | startOffset: start[1], |
---|
145 | endOffset: end[1] |
---|
146 | }); |
---|
147 | }; |
---|
148 | |
---|
149 | NormalizedRange.prototype.text = function() { |
---|
150 | var node; |
---|
151 | return ((function() { |
---|
152 | var _i, _len, _ref, _results; |
---|
153 | _ref = this.textNodes(); |
---|
154 | _results = []; |
---|
155 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { |
---|
156 | node = _ref[_i]; |
---|
157 | _results.push(node.nodeValue); |
---|
158 | } |
---|
159 | return _results; |
---|
160 | }).call(this)).join(''); |
---|
161 | }; |
---|
162 | |
---|
163 | NormalizedRange.prototype.textNodes = function() { |
---|
164 | var end, start, textNodes, _ref; |
---|
165 | textNodes = $(this.commonAncestor).textNodes(); |
---|
166 | _ref = [textNodes.index(this.start), textNodes.index(this.end)], start = _ref[0], end = _ref[1]; |
---|
167 | return $.makeArray(textNodes.slice(start, end + 1 || 9e9)); |
---|
168 | }; |
---|
169 | |
---|
170 | NormalizedRange.prototype.toRange = function() { |
---|
171 | var range; |
---|
172 | range = document.createRange(); |
---|
173 | range.setStartBefore(this.start); |
---|
174 | range.setEndAfter(this.end); |
---|
175 | return range; |
---|
176 | }; |
---|
177 | |
---|
178 | return NormalizedRange; |
---|
179 | |
---|
180 | })(); |
---|
181 | |
---|
182 | Range.SerializedRange = (function() { |
---|
183 | |
---|
184 | function SerializedRange(obj) { |
---|
185 | this.start = obj.start; |
---|
186 | this.startOffset = obj.startOffset; |
---|
187 | this.end = obj.end; |
---|
188 | this.endOffset = obj.endOffset; |
---|
189 | } |
---|
190 | |
---|
191 | SerializedRange.prototype._nodeFromXPath = function(xpath) { |
---|
192 | var customResolver, evaluateXPath, namespace, node, segment; |
---|
193 | evaluateXPath = function(xp, nsResolver) { |
---|
194 | if (nsResolver == null) nsResolver = null; |
---|
195 | return document.evaluate(xp, document, nsResolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; |
---|
196 | }; |
---|
197 | if (!$.isXMLDoc(document.documentElement)) { |
---|
198 | return evaluateXPath(xpath); |
---|
199 | } else { |
---|
200 | customResolver = document.createNSResolver(document.ownerDocument === null ? document.documentElement : document.ownerDocument.documentElement); |
---|
201 | node = evaluateXPath(xpath, customResolver); |
---|
202 | if (!node) { |
---|
203 | xpath = ((function() { |
---|
204 | var _i, _len, _ref, _results; |
---|
205 | _ref = xpath.split('/'); |
---|
206 | _results = []; |
---|
207 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { |
---|
208 | segment = _ref[_i]; |
---|
209 | if (segment && segment.indexOf(':') === -1) { |
---|
210 | _results.push(segment.replace(/^([a-z]+)/, 'xhtml:$1')); |
---|
211 | } else { |
---|
212 | _results.push(segment); |
---|
213 | } |
---|
214 | } |
---|
215 | return _results; |
---|
216 | })()).join('/'); |
---|
217 | namespace = document.lookupNamespaceURI(null); |
---|
218 | customResolver = function(ns) { |
---|
219 | if (ns === 'xhtml') { |
---|
220 | return namespace; |
---|
221 | } else { |
---|
222 | return document.documentElement.getAttribute('xmlns:' + ns); |
---|
223 | } |
---|
224 | }; |
---|
225 | node = evaluateXPath(xpath, customResolver); |
---|
226 | } |
---|
227 | return node; |
---|
228 | } |
---|
229 | }; |
---|
230 | |
---|
231 | SerializedRange.prototype.normalize = function(root) { |
---|
232 | var cacXPath, common, endAncestry, i, length, p, parentXPath, range, startAncestry, tn, _i, _j, _len, _len2, _ref, _ref2, _ref3; |
---|
233 | parentXPath = $(root).xpath()[0]; |
---|
234 | startAncestry = this.start.split("/"); |
---|
235 | endAncestry = this.end.split("/"); |
---|
236 | common = []; |
---|
237 | range = {}; |
---|
238 | for (i = 0, _ref = startAncestry.length; 0 <= _ref ? i < _ref : i > _ref; 0 <= _ref ? i++ : i--) { |
---|
239 | if (startAncestry[i] === endAncestry[i]) { |
---|
240 | common.push(startAncestry[i]); |
---|
241 | } else { |
---|
242 | break; |
---|
243 | } |
---|
244 | } |
---|
245 | cacXPath = parentXPath + common.join("/"); |
---|
246 | range.commonAncestorContainer = this._nodeFromXPath(cacXPath); |
---|
247 | if (!range.commonAncestorContainer) { |
---|
248 | console.error(_t("Error deserializing range: can't find XPath '") + cacXPath + _t("'. Is this the right document?")); |
---|
249 | return null; |
---|
250 | } |
---|
251 | _ref2 = ['start', 'end']; |
---|
252 | for (_i = 0, _len = _ref2.length; _i < _len; _i++) { |
---|
253 | p = _ref2[_i]; |
---|
254 | length = 0; |
---|
255 | _ref3 = $(this._nodeFromXPath(parentXPath + this[p])).textNodes(); |
---|
256 | for (_j = 0, _len2 = _ref3.length; _j < _len2; _j++) { |
---|
257 | tn = _ref3[_j]; |
---|
258 | if (length + tn.nodeValue.length >= this[p + 'Offset']) { |
---|
259 | range[p + 'Container'] = tn; |
---|
260 | range[p + 'Offset'] = this[p + 'Offset'] - length; |
---|
261 | break; |
---|
262 | } else { |
---|
263 | length += tn.nodeValue.length; |
---|
264 | } |
---|
265 | } |
---|
266 | } |
---|
267 | return new Range.BrowserRange(range).normalize(root); |
---|
268 | }; |
---|
269 | |
---|
270 | SerializedRange.prototype.serialize = function(root, ignoreSelector) { |
---|
271 | return this.normalize(root).serialize(root, ignoreSelector); |
---|
272 | }; |
---|
273 | |
---|
274 | SerializedRange.prototype.toObject = function() { |
---|
275 | return { |
---|
276 | start: this.start, |
---|
277 | startOffset: this.startOffset, |
---|
278 | end: this.end, |
---|
279 | endOffset: this.endOffset |
---|
280 | }; |
---|
281 | }; |
---|
282 | |
---|
283 | return SerializedRange; |
---|
284 | |
---|
285 | })(); |
---|