rangy-rails 1.3alpha.772.0 → 1.3alpha.780.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -26,1871 +26,7 @@
26
26
  *
27
27
  * Copyright 2013, Tim Down
28
28
  * Licensed under the MIT license.
29
- * Version: 1.3alpha.772
30
- * Build date: 26 February 2013
29
+ * Version: 1.3alpha.780M
30
+ * Build date: 17 May 2013
31
31
  */
32
-
33
- /**
34
- * Problem: handling of trailing spaces before line breaks is handled inconsistently between browsers.
35
- *
36
- * First, a <br>: this is relatively simple. For the following HTML:
37
- *
38
- * 1 <br>2
39
- *
40
- * - IE and WebKit render the space, include it in the selection (i.e. when the content is selected and pasted into a
41
- * textarea, the space is present) and allow the caret to be placed after it.
42
- * - Firefox does not acknowledge the space in the selection but it is possible to place the caret after it.
43
- * - Opera does not render the space but has two separate caret positions on either side of the space (left and right
44
- * arrow keys show this) and includes the space in the selection.
45
- *
46
- * The other case is the line break or breaks implied by block elements. For the following HTML:
47
- *
48
- * <p>1 </p><p>2<p>
49
- *
50
- * - WebKit does not acknowledge the space in any way
51
- * - Firefox, IE and Opera as per <br>
52
- *
53
- * One more case is trailing spaces before line breaks in elements with white-space: pre-line. For the following HTML:
54
- *
55
- * <p style="white-space: pre-line">1
56
- * 2</p>
57
- *
58
- * - Firefox and WebKit include the space in caret positions
59
- * - IE does not support pre-line up to and including version 9
60
- * - Opera ignores the space
61
- * - Trailing space only renders if there is a non-collapsed character in the line
62
- *
63
- * Problem is whether Rangy should ever acknowledge the space and if so, when. Another problem is whether this can be
64
- * feature-tested
65
- */
66
- rangy.createModule("TextRange", function(api, module) {
67
- api.requireModules( ["WrappedSelection"] );
68
-
69
- var UNDEF = "undefined";
70
- var CHARACTER = "character", WORD = "word";
71
- var dom = api.dom, util = api.util;
72
- var extend = util.extend;
73
- var getBody = dom.getBody;
74
-
75
-
76
- var spacesRegex = /^[ \t\f\r\n]+$/;
77
- var spacesMinusLineBreaksRegex = /^[ \t\f\r]+$/;
78
- var allWhiteSpaceRegex = /^[\t-\r \u0085\u00A0\u1680\u180E\u2000-\u200B\u2028\u2029\u202F\u205F\u3000]+$/;
79
- var nonLineBreakWhiteSpaceRegex = /^[\t \u00A0\u1680\u180E\u2000-\u200B\u202F\u205F\u3000]+$/;
80
- var lineBreakRegex = /^[\n-\r\u0085\u2028\u2029]$/;
81
-
82
- var defaultLanguage = "en";
83
-
84
- var isDirectionBackward = api.Selection.isDirectionBackward;
85
-
86
- // Properties representing whether trailing spaces inside blocks are completely collapsed (as they are in WebKit,
87
- // but not other browsers). Also test whether trailing spaces before <br> elements are collapsed.
88
- var trailingSpaceInBlockCollapses = false;
89
- var trailingSpaceBeforeBrCollapses = false;
90
- var trailingSpaceBeforeLineBreakInPreLineCollapses = true;
91
-
92
- (function() {
93
- var el = document.createElement("div");
94
- el.contentEditable = "true";
95
- el.innerHTML = "<p>1 </p><p></p>";
96
- var body = getBody(document);
97
- var p = el.firstChild;
98
- var sel = api.getSelection();
99
-
100
- body.appendChild(el);
101
- sel.collapse(p.lastChild, 2);
102
- sel.setStart(p.firstChild, 0);
103
- trailingSpaceInBlockCollapses = ("" + sel).length == 1;
104
-
105
- el.innerHTML = "1 <br>";
106
- sel.collapse(el, 2);
107
- sel.setStart(el.firstChild, 0);
108
- trailingSpaceBeforeBrCollapses = ("" + sel).length == 1;
109
- body.removeChild(el);
110
-
111
- sel.removeAllRanges();
112
- })();
113
-
114
- /*----------------------------------------------------------------------------------------------------------------*/
115
-
116
- // This function must create word and non-word tokens for the whole of the text supplied to it
117
- function defaultTokenizer(chars, wordOptions) {
118
- var word = chars.join(""), result, tokens = [];
119
-
120
- function createTokenFromRange(start, end, isWord) {
121
- var tokenChars = chars.slice(start, end);
122
- var token = {
123
- isWord: isWord,
124
- chars: tokenChars,
125
- toString: function() {
126
- return tokenChars.join("");
127
- }
128
- };
129
- for (var i = 0, len = tokenChars.length; i < len; ++i) {
130
- tokenChars[i].token = token;
131
- }
132
- tokens.push(token);
133
- }
134
-
135
- // Match words and mark characters
136
- var lastWordEnd = 0, wordStart, wordEnd;
137
- while ( (result = wordOptions.wordRegex.exec(word)) ) {
138
- wordStart = result.index;
139
- wordEnd = wordStart + result[0].length;
140
-
141
- // Create token for non-word characters preceding this word
142
- if (wordStart > lastWordEnd) {
143
- createTokenFromRange(lastWordEnd, wordStart, false);
144
- }
145
-
146
- // Get trailing space characters for word
147
- if (wordOptions.includeTrailingSpace) {
148
- while (nonLineBreakWhiteSpaceRegex.test(chars[wordEnd])) {
149
- ++wordEnd;
150
- }
151
- }
152
- createTokenFromRange(wordStart, wordEnd, true);
153
- lastWordEnd = wordEnd;
154
- }
155
-
156
- // Create token for trailing non-word characters, if any exist
157
- if (lastWordEnd < chars.length) {
158
- createTokenFromRange(lastWordEnd, chars.length, false);
159
- }
160
-
161
- return tokens;
162
- }
163
-
164
- var defaultCharacterOptions = {
165
- includeBlockContentTrailingSpace: true,
166
- includeSpaceBeforeBr: true,
167
- includePreLineTrailingSpace: true
168
- };
169
-
170
- var defaultCaretCharacterOptions = {
171
- includeBlockContentTrailingSpace: !trailingSpaceBeforeLineBreakInPreLineCollapses,
172
- includeSpaceBeforeBr: !trailingSpaceBeforeBrCollapses,
173
- includePreLineTrailingSpace: true
174
- };
175
-
176
- var defaultWordOptions = {
177
- "en": {
178
- wordRegex: /[a-z0-9]+('[a-z0-9]+)*/gi,
179
- includeTrailingSpace: false,
180
- tokenizer: defaultTokenizer
181
- }
182
- };
183
-
184
- function createOptions(optionsParam, defaults) {
185
- if (!optionsParam) {
186
- return defaults;
187
- } else {
188
- var options = {};
189
- extend(options, defaults);
190
- extend(options, optionsParam);
191
- return options;
192
- }
193
- }
194
-
195
- function createWordOptions(options) {
196
- var lang, defaults;
197
- if (!options) {
198
- return defaultWordOptions[defaultLanguage];
199
- } else {
200
- lang = options.language || defaultLanguage;
201
- defaults = {};
202
- extend(defaults, defaultWordOptions[lang] || defaultWordOptions[defaultLanguage]);
203
- extend(defaults, options);
204
- return defaults;
205
- }
206
- }
207
-
208
- function createCharacterOptions(options) {
209
- return createOptions(options, defaultCharacterOptions);
210
- }
211
-
212
- function createCaretCharacterOptions(options) {
213
- return createOptions(options, defaultCaretCharacterOptions);
214
- }
215
-
216
- var defaultFindOptions = {
217
- caseSensitive: false,
218
- withinRange: null,
219
- wholeWordsOnly: false,
220
- wrap: false,
221
- direction: "forward",
222
- wordOptions: null,
223
- characterOptions: null
224
- };
225
-
226
- var defaultMoveOptions = {
227
- wordOptions: null,
228
- characterOptions: null
229
- };
230
-
231
- var defaultExpandOptions = {
232
- wordOptions: null,
233
- characterOptions: null,
234
- trim: false,
235
- trimStart: true,
236
- trimEnd: true
237
- };
238
-
239
- var defaultWordIteratorOptions = {
240
- wordOptions: null,
241
- characterOptions: null,
242
- direction: "forward"
243
- };
244
-
245
- /*----------------------------------------------------------------------------------------------------------------*/
246
-
247
- /* DOM utility functions */
248
- var getComputedStyleProperty = dom.getComputedStyleProperty;
249
-
250
- // Create cachable versions of DOM functions
251
-
252
- // Test for old IE's incorrect display properties
253
- var tableCssDisplayBlock;
254
- (function() {
255
- var table = document.createElement("table");
256
- var body = getBody(document);
257
- body.appendChild(table);
258
- tableCssDisplayBlock = (getComputedStyleProperty(table, "display") == "block");
259
- body.removeChild(table);
260
- })();
261
-
262
- api.features.tableCssDisplayBlock = tableCssDisplayBlock;
263
-
264
- var defaultDisplayValueForTag = {
265
- table: "table",
266
- caption: "table-caption",
267
- colgroup: "table-column-group",
268
- col: "table-column",
269
- thead: "table-header-group",
270
- tbody: "table-row-group",
271
- tfoot: "table-footer-group",
272
- tr: "table-row",
273
- td: "table-cell",
274
- th: "table-cell"
275
- };
276
-
277
- // Corrects IE's "block" value for table-related elements
278
- function getComputedDisplay(el, win) {
279
- var display = getComputedStyleProperty(el, "display", win);
280
- var tagName = el.tagName.toLowerCase();
281
- return (display == "block"
282
- && tableCssDisplayBlock
283
- && defaultDisplayValueForTag.hasOwnProperty(tagName))
284
- ? defaultDisplayValueForTag[tagName] : display;
285
- }
286
-
287
- function isHidden(node) {
288
- var ancestors = getAncestorsAndSelf(node);
289
- for (var i = 0, len = ancestors.length; i < len; ++i) {
290
- if (ancestors[i].nodeType == 1 && getComputedDisplay(ancestors[i]) == "none") {
291
- return true;
292
- }
293
- }
294
-
295
- return false;
296
- }
297
-
298
- function isVisibilityHiddenTextNode(textNode) {
299
- var el;
300
- return textNode.nodeType == 3
301
- && (el = textNode.parentNode)
302
- && getComputedStyleProperty(el, "visibility") == "hidden";
303
- }
304
-
305
- /*----------------------------------------------------------------------------------------------------------------*/
306
-
307
-
308
- // "A block node is either an Element whose "display" property does not have
309
- // resolved value "inline" or "inline-block" or "inline-table" or "none", or a
310
- // Document, or a DocumentFragment."
311
- function isBlockNode(node) {
312
- return node
313
- && ((node.nodeType == 1 && !/^(inline(-block|-table)?|none)$/.test(getComputedDisplay(node)))
314
- || node.nodeType == 9 || node.nodeType == 11);
315
- }
316
-
317
- function getLastDescendantOrSelf(node) {
318
- var lastChild = node.lastChild;
319
- return lastChild ? getLastDescendantOrSelf(lastChild) : node;
320
- }
321
-
322
- function containsPositions(node) {
323
- return dom.isCharacterDataNode(node)
324
- || !/^(area|base|basefont|br|col|frame|hr|img|input|isindex|link|meta|param)$/i.test(node.nodeName);
325
- }
326
-
327
- function getAncestors(node) {
328
- var ancestors = [];
329
- while (node.parentNode) {
330
- ancestors.unshift(node.parentNode);
331
- node = node.parentNode;
332
- }
333
- return ancestors;
334
- }
335
-
336
- function getAncestorsAndSelf(node) {
337
- return getAncestors(node).concat([node]);
338
- }
339
-
340
- // Opera 11 puts HTML elements in the null namespace, it seems, and IE 7 has undefined namespaceURI
341
- function isHtmlNode(node) {
342
- var ns;
343
- return typeof (ns = node.namespaceURI) == UNDEF || (ns === null || ns == "http://www.w3.org/1999/xhtml");
344
- }
345
-
346
- function isHtmlElement(node, tagNames) {
347
- if (!node || node.nodeType != 1 || !isHtmlNode(node)) {
348
- return false;
349
- }
350
- switch (typeof tagNames) {
351
- case "string":
352
- return node.tagName.toLowerCase() == tagNames.toLowerCase();
353
- case "object":
354
- return new RegExp("^(" + tagNames.join("|S") + ")$", "i").test(node.tagName);
355
- default:
356
- return true;
357
- }
358
- }
359
-
360
- function nextNodeDescendants(node) {
361
- while (node && !node.nextSibling) {
362
- node = node.parentNode;
363
- }
364
- if (!node) {
365
- return null;
366
- }
367
- return node.nextSibling;
368
- }
369
-
370
- function nextNode(node, excludeChildren) {
371
- if (!excludeChildren && node.hasChildNodes()) {
372
- return node.firstChild;
373
- }
374
- return nextNodeDescendants(node);
375
- }
376
-
377
- function previousNode(node) {
378
- var previous = node.previousSibling;
379
- if (previous) {
380
- node = previous;
381
- while (node.hasChildNodes()) {
382
- node = node.lastChild;
383
- }
384
- return node;
385
- }
386
- var parent = node.parentNode;
387
- if (parent && parent.nodeType == 1) {
388
- return parent;
389
- }
390
- return null;
391
- }
392
-
393
-
394
-
395
- // Adpated from Aryeh's code.
396
- // "A whitespace node is either a Text node whose data is the empty string; or
397
- // a Text node whose data consists only of one or more tabs (0x0009), line
398
- // feeds (0x000A), carriage returns (0x000D), and/or spaces (0x0020), and whose
399
- // parent is an Element whose resolved value for "white-space" is "normal" or
400
- // "nowrap"; or a Text node whose data consists only of one or more tabs
401
- // (0x0009), carriage returns (0x000D), and/or spaces (0x0020), and whose
402
- // parent is an Element whose resolved value for "white-space" is "pre-line"."
403
- function isWhitespaceNode(node) {
404
- if (!node || node.nodeType != 3) {
405
- return false;
406
- }
407
- var text = node.data;
408
- if (text === "") {
409
- return true;
410
- }
411
- var parent = node.parentNode;
412
- if (!parent || parent.nodeType != 1) {
413
- return false;
414
- }
415
- var computedWhiteSpace = getComputedStyleProperty(node.parentNode, "whiteSpace");
416
-
417
- return (/^[\t\n\r ]+$/.test(text) && /^(normal|nowrap)$/.test(computedWhiteSpace))
418
- || (/^[\t\r ]+$/.test(text) && computedWhiteSpace == "pre-line");
419
- }
420
-
421
- // Adpated from Aryeh's code.
422
- // "node is a collapsed whitespace node if the following algorithm returns
423
- // true:"
424
- function isCollapsedWhitespaceNode(node) {
425
- // "If node's data is the empty string, return true."
426
- if (node.data === "") {
427
- return true;
428
- }
429
-
430
- // "If node is not a whitespace node, return false."
431
- if (!isWhitespaceNode(node)) {
432
- return false;
433
- }
434
-
435
- // "Let ancestor be node's parent."
436
- var ancestor = node.parentNode;
437
-
438
- // "If ancestor is null, return true."
439
- if (!ancestor) {
440
- return true;
441
- }
442
-
443
- // "If the "display" property of some ancestor of node has resolved value "none", return true."
444
- if (isHidden(node)) {
445
- return true;
446
- }
447
-
448
- return false;
449
- }
450
-
451
- function isCollapsedNode(node) {
452
- var type = node.nodeType;
453
- return type == 7 /* PROCESSING_INSTRUCTION */
454
- || type == 8 /* COMMENT */
455
- || isHidden(node)
456
- || /^(script|style)$/i.test(node.nodeName)
457
- || isVisibilityHiddenTextNode(node)
458
- || isCollapsedWhitespaceNode(node);
459
- }
460
-
461
- function isIgnoredNode(node, win) {
462
- var type = node.nodeType;
463
- return type == 7 /* PROCESSING_INSTRUCTION */
464
- || type == 8 /* COMMENT */
465
- || (type == 1 && getComputedDisplay(node, win) == "none");
466
- }
467
-
468
- /*----------------------------------------------------------------------------------------------------------------*/
469
-
470
- // Possibly overengineered caching system to prevent repeated DOM calls slowing everything down
471
-
472
- function Cache() {
473
- this.store = {};
474
- }
475
-
476
- Cache.prototype = {
477
- get: function(key) {
478
- return this.store.hasOwnProperty(key) ? this.store[key] : null;
479
- },
480
-
481
- set: function(key, value) {
482
- return this.store[key] = value;
483
- }
484
- };
485
-
486
- var cachedCount = 0, uncachedCount = 0;
487
-
488
- function createCachingGetter(methodName, func, objProperty) {
489
- return function(args) {
490
- var cache = this.cache;
491
- if (cache.hasOwnProperty(methodName)) {
492
- cachedCount++;
493
- return cache[methodName];
494
- } else {
495
- uncachedCount++;
496
- var value = func.call(this, objProperty ? this[objProperty] : this, args);
497
- cache[methodName] = value;
498
- return value;
499
- }
500
- };
501
- }
502
-
503
- api.report = function() {
504
- console.log("Cached: " + cachedCount + ", uncached: " + uncachedCount);
505
- };
506
-
507
- /*----------------------------------------------------------------------------------------------------------------*/
508
-
509
- function NodeWrapper(node, session) {
510
- this.node = node;
511
- this.session = session;
512
- this.cache = new Cache();
513
- this.positions = new Cache();
514
- }
515
-
516
- var nodeProto = {
517
- getPosition: function(offset) {
518
- var positions = this.positions;
519
- return positions.get(offset) || positions.set(offset, new Position(this, offset));
520
- },
521
-
522
- toString: function() {
523
- return "[NodeWrapper(" + dom.inspectNode(this.node) + ")]";
524
- }
525
- };
526
-
527
- NodeWrapper.prototype = nodeProto;
528
-
529
- var EMPTY = "EMPTY",
530
- NON_SPACE = "NON_SPACE",
531
- UNCOLLAPSIBLE_SPACE = "UNCOLLAPSIBLE_SPACE",
532
- COLLAPSIBLE_SPACE = "COLLAPSIBLE_SPACE",
533
- TRAILING_SPACE_IN_BLOCK = "TRAILING_SPACE_IN_BLOCK",
534
- TRAILING_SPACE_BEFORE_BR = "TRAILING_SPACE_BEFORE_BR",
535
- PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK = "PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK";
536
-
537
-
538
- extend(nodeProto, {
539
- isCharacterDataNode: createCachingGetter("isCharacterDataNode", dom.isCharacterDataNode, "node"),
540
- getNodeIndex: createCachingGetter("nodeIndex", dom.getNodeIndex, "node"),
541
- getLength: createCachingGetter("nodeLength", dom.getNodeLength, "node"),
542
- containsPositions: createCachingGetter("containsPositions", containsPositions, "node"),
543
- isWhitespace: createCachingGetter("isWhitespace", isWhitespaceNode, "node"),
544
- isCollapsedWhitespace: createCachingGetter("isCollapsedWhitespace", isCollapsedWhitespaceNode, "node"),
545
- getComputedDisplay: createCachingGetter("computedDisplay", getComputedDisplay, "node"),
546
- isCollapsed: createCachingGetter("collapsed", isCollapsedNode, "node"),
547
- isIgnored: createCachingGetter("ignored", isIgnoredNode, "node"),
548
- next: createCachingGetter("nextPos", nextNode, "node"),
549
- previous: createCachingGetter("previous", previousNode, "node"),
550
-
551
- getTextNodeInfo: createCachingGetter("textNodeInfo", function(textNode) {
552
- var spaceRegex = null, collapseSpaces = false;
553
- var cssWhitespace = getComputedStyleProperty(textNode.parentNode, "whiteSpace");
554
- var preLine = (cssWhitespace == "pre-line");
555
- if (preLine) {
556
- spaceRegex = spacesMinusLineBreaksRegex;
557
- collapseSpaces = true;
558
- } else if (cssWhitespace == "normal" || cssWhitespace == "nowrap") {
559
- spaceRegex = spacesRegex;
560
- collapseSpaces = true;
561
- }
562
-
563
- return {
564
- node: textNode,
565
- text: textNode.data,
566
- spaceRegex: spaceRegex,
567
- collapseSpaces: collapseSpaces,
568
- preLine: preLine
569
- };
570
- }, "node"),
571
-
572
- hasInnerText: createCachingGetter("hasInnerText", function(el, backward) {
573
- var session = this.session;
574
- var posAfterEl = session.getPosition(el.parentNode, this.getNodeIndex() + 1);
575
- var firstPosInEl = session.getPosition(el, 0);
576
-
577
- var pos = backward ? posAfterEl : firstPosInEl;
578
- var endPos = backward ? firstPosInEl : posAfterEl;
579
-
580
- /*
581
- <body><p>X </p><p>Y</p></body>
582
-
583
- Positions:
584
-
585
- body:0:""
586
- p:0:""
587
- text:0:""
588
- text:1:"X"
589
- text:2:TRAILING_SPACE_IN_BLOCK
590
- text:3:COLLAPSED_SPACE
591
- p:1:""
592
- body:1:"\n"
593
- p:0:""
594
- text:0:""
595
- text:1:"Y"
596
-
597
- A character is a TRAILING_SPACE_IN_BLOCK iff:
598
-
599
- - There is no uncollapsed character after it within the visible containing block element
600
-
601
- A character is a TRAILING_SPACE_BEFORE_BR iff:
602
-
603
- - There is no uncollapsed character after it preceding a <br> element
604
-
605
- An element has inner text iff
606
-
607
- - It is not hidden
608
- - It contains an uncollapsed character
609
-
610
- All trailing spaces (pre-line, before <br>, end of block) require definite non-empty characters to render.
611
- */
612
-
613
- while (pos !== endPos) {
614
- pos.prepopulateChar();
615
- if (pos.isDefinitelyNonEmpty()) {
616
- return true;
617
- }
618
- pos = backward ? pos.previousVisible() : pos.nextVisible();
619
- }
620
-
621
- return false;
622
- }, "node"),
623
-
624
- getTrailingSpace: createCachingGetter("trailingSpace", function(el) {
625
- if (el.tagName.toLowerCase() == "br") {
626
- return "";
627
- } else {
628
- switch (this.getComputedDisplay()) {
629
- case "inline":
630
- var child = el.lastChild;
631
- while (child) {
632
- if (!isIgnoredNode(child)) {
633
- return (child.nodeType == 1) ? this.session.getNodeWrapper(child).getTrailingSpace() : "";
634
- }
635
- child = child.previousSibling;
636
- }
637
- break;
638
- case "inline-block":
639
- case "inline-table":
640
- case "none":
641
- case "table-column":
642
- case "table-column-group":
643
- break;
644
- case "table-cell":
645
- return "\t";
646
- default:
647
- return this.hasInnerText(true) ? "\n" : "";
648
- }
649
- }
650
- return "";
651
- }, "node"),
652
-
653
- getLeadingSpace: createCachingGetter("leadingSpace", function(el) {
654
- switch (this.getComputedDisplay()) {
655
- case "inline":
656
- case "inline-block":
657
- case "inline-table":
658
- case "none":
659
- case "table-column":
660
- case "table-column-group":
661
- case "table-cell":
662
- break;
663
- default:
664
- return this.hasInnerText(false) ? "\n" : "";
665
- }
666
- return "";
667
- }, "node")
668
- });
669
-
670
- /*----------------------------------------------------------------------------------------------------------------*/
671
-
672
-
673
- function Position(nodeWrapper, offset) {
674
- this.offset = offset;
675
- this.nodeWrapper = nodeWrapper;
676
- this.node = nodeWrapper.node;
677
- this.session = nodeWrapper.session;
678
- this.cache = new Cache();
679
- }
680
-
681
- function inspectPosition() {
682
- return "[Position(" + dom.inspectNode(this.node) + ":" + this.offset + ")]";
683
- }
684
-
685
- var positionProto = {
686
- character: "",
687
- characterType: EMPTY,
688
- isBr: false,
689
-
690
- /*
691
- This method:
692
- - Fully populates positions that have characters that can be determined independently of any other characters.
693
- - Populates most types of space positions with a provisional character. The character is finalized later.
694
- */
695
- prepopulateChar: function() {
696
- var pos = this;
697
- if (!pos.prepopulatedChar) {
698
- var node = pos.node, offset = pos.offset;
699
- var visibleChar = "", charType = EMPTY;
700
- var finalizedChar = false;
701
- if (offset > 0) {
702
- if (node.nodeType == 3) {
703
- var text = node.data;
704
- var textChar = text.charAt(offset - 1);
705
-
706
- var nodeInfo = pos.nodeWrapper.getTextNodeInfo();
707
- var spaceRegex = nodeInfo.spaceRegex;
708
- if (nodeInfo.collapseSpaces) {
709
- if (spaceRegex.test(textChar)) {
710
- // "If the character at position is from set, append a single space (U+0020) to newdata and advance
711
- // position until the character at position is not from set."
712
-
713
- // We also need to check for the case where we're in a pre-line and we have a space preceding a
714
- // line break, because such spaces are collapsed in some browsers
715
- if (offset > 1 && spaceRegex.test(text.charAt(offset - 2))) {
716
- } else if (nodeInfo.preLine && text.charAt(offset) === "\n") {
717
- visibleChar = " ";
718
- charType = PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK;
719
- } else {
720
- visibleChar = " ";
721
- //pos.checkForFollowingLineBreak = true;
722
- charType = COLLAPSIBLE_SPACE;
723
- }
724
- } else {
725
- visibleChar = textChar;
726
- charType = NON_SPACE;
727
- finalizedChar = true;
728
- }
729
- } else {
730
- visibleChar = textChar;
731
- charType = UNCOLLAPSIBLE_SPACE;
732
- finalizedChar = true;
733
- }
734
- } else {
735
- var nodePassed = node.childNodes[offset - 1];
736
- if (nodePassed && nodePassed.nodeType == 1 && !isCollapsedNode(nodePassed)) {
737
- if (nodePassed.tagName.toLowerCase() == "br") {
738
- visibleChar = "\n";
739
- pos.isBr = true;
740
- charType = COLLAPSIBLE_SPACE;
741
- finalizedChar = false;
742
- } else {
743
- pos.checkForTrailingSpace = true;
744
- }
745
- }
746
-
747
- // Check the leading space of the next node for the case when a block element follows an inline
748
- // element or text node. In that case, there is an implied line break between the two nodes.
749
- if (!visibleChar) {
750
- var nextNode = node.childNodes[offset];
751
- if (nextNode && nextNode.nodeType == 1 && !isCollapsedNode(nextNode)) {
752
- pos.checkForLeadingSpace = true;
753
- }
754
- }
755
- }
756
- }
757
-
758
- pos.prepopulatedChar = true;
759
- pos.character = visibleChar;
760
- pos.characterType = charType;
761
- pos.isCharInvariant = finalizedChar;
762
- }
763
- },
764
-
765
- isDefinitelyNonEmpty: function() {
766
- var charType = this.characterType;
767
- return charType == NON_SPACE || charType == UNCOLLAPSIBLE_SPACE;
768
- },
769
-
770
- // Resolve leading and trailing spaces, which may involve prepopulating other positions
771
- resolveLeadingAndTrailingSpaces: function() {
772
- if (!this.prepopulatedChar) {
773
- this.prepopulateChar();
774
- }
775
- if (this.checkForTrailingSpace) {
776
- var trailingSpace = this.session.getNodeWrapper(this.node.childNodes[this.offset - 1]).getTrailingSpace();
777
- if (trailingSpace) {
778
- this.isTrailingSpace = true;
779
- this.character = trailingSpace;
780
- this.characterType = COLLAPSIBLE_SPACE;
781
- }
782
- this.checkForTrailingSpace = false;
783
- }
784
- if (this.checkForLeadingSpace) {
785
- var leadingSpace = this.session.getNodeWrapper(this.node.childNodes[this.offset]).getLeadingSpace();
786
- if (leadingSpace) {
787
- this.isLeadingSpace = true;
788
- this.character = leadingSpace;
789
- this.characterType = COLLAPSIBLE_SPACE;
790
- }
791
- this.checkForLeadingSpace = false;
792
- }
793
- },
794
-
795
- getPrecedingUncollapsedPosition: function(characterOptions) {
796
- var pos = this, character;
797
- while ( (pos = pos.previousVisible()) ) {
798
- character = pos.getCharacter(characterOptions);
799
- if (character !== "") {
800
- return pos;
801
- }
802
- }
803
-
804
- return null;
805
- },
806
-
807
- getCharacter: function(characterOptions) {
808
- this.resolveLeadingAndTrailingSpaces();
809
-
810
- // Check if this position's character is invariant (i.e. not dependent on character options) and return it
811
- // if so
812
- if (this.isCharInvariant) {
813
- return this.character;
814
- }
815
-
816
- var cacheKey = ["character", characterOptions.includeSpaceBeforeBr, characterOptions.includeBlockContentTrailingSpace, characterOptions.includePreLineTrailingSpace].join("_");
817
- var cachedChar = this.cache.get(cacheKey);
818
- if (cachedChar !== null) {
819
- return cachedChar;
820
- }
821
-
822
- // We need to actually get the character
823
- var character = "";
824
- var collapsible = (this.characterType == COLLAPSIBLE_SPACE);
825
-
826
- var nextPos, previousPos/* = this.getPrecedingUncollapsedPosition(characterOptions)*/;
827
- var gotPreviousPos = false;
828
- var pos = this;
829
-
830
- function getPreviousPos() {
831
- if (!gotPreviousPos) {
832
- previousPos = pos.getPrecedingUncollapsedPosition(characterOptions);
833
- gotPreviousPos = true;
834
- }
835
- return previousPos;
836
- }
837
-
838
- // Disallow a collapsible space that is followed by a line break or is the last character
839
- if (collapsible) {
840
- // Disallow a collapsible space that follows a trailing space or line break, or is the first character
841
- if (this.character == " " &&
842
- (!getPreviousPos() || previousPos.isTrailingSpace || previousPos.character == "\n")) {
843
- }
844
- // Allow a leading line break unless it follows a line break
845
- else if (this.character == "\n" && this.isLeadingSpace) {
846
- if (getPreviousPos() && previousPos.character != "\n") {
847
- character = "\n";
848
- } else {
849
- }
850
- } else {
851
- nextPos = this.nextUncollapsed();
852
- if (nextPos) {
853
- if (nextPos.isBr) {
854
- this.type = TRAILING_SPACE_BEFORE_BR;
855
- } else if (nextPos.isTrailingSpace && nextPos.character == "\n") {
856
- this.type = TRAILING_SPACE_IN_BLOCK;
857
- }
858
- if (nextPos.character === "\n") {
859
- if (this.type == TRAILING_SPACE_BEFORE_BR && !characterOptions.includeSpaceBeforeBr) {
860
- } else if (this.type == TRAILING_SPACE_IN_BLOCK && nextPos.isTrailingSpace && !characterOptions.includeBlockContentTrailingSpace) {
861
- } else if (this.type == PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK && nextPos.type == NON_SPACE && !characterOptions.includePreLineTrailingSpace) {
862
- } else if (this.character === "\n") {
863
- if (nextPos.isTrailingSpace) {
864
- if (this.isTrailingSpace) {
865
- } else if (this.isBr) {
866
- }
867
- } else {
868
- character = "\n";
869
- }
870
- } else if (this.character === " ") {
871
- character = " ";
872
- } else {
873
- }
874
- } else {
875
- character = this.character;
876
- }
877
- } else {
878
- }
879
- }
880
- }
881
-
882
- // Collapse a br element that is followed by a trailing space
883
- else if (this.character === "\n" &&
884
- (!(nextPos = this.nextUncollapsed()) || nextPos.isTrailingSpace)) {
885
- }
886
-
887
-
888
- this.cache.set(cacheKey, character);
889
-
890
- return character;
891
- },
892
-
893
- equals: function(pos) {
894
- return !!pos && this.node === pos.node && this.offset === pos.offset;
895
- },
896
-
897
- inspect: inspectPosition,
898
-
899
- toString: function() {
900
- return this.character;
901
- }
902
- };
903
-
904
- Position.prototype = positionProto;
905
-
906
- extend(positionProto, {
907
- next: createCachingGetter("nextPos", function(pos) {
908
- var nodeWrapper = pos.nodeWrapper, node = pos.node, offset = pos.offset, session = nodeWrapper.session;
909
- if (!node) {
910
- return null;
911
- }
912
- var nextNode, nextOffset, child;
913
- if (offset == nodeWrapper.getLength()) {
914
- // Move onto the next node
915
- nextNode = node.parentNode;
916
- nextOffset = nextNode ? nodeWrapper.getNodeIndex() + 1 : 0;
917
- } else {
918
- if (nodeWrapper.isCharacterDataNode()) {
919
- nextNode = node;
920
- nextOffset = offset + 1;
921
- } else {
922
- child = node.childNodes[offset];
923
- // Go into the children next, if children there are
924
- if (session.getNodeWrapper(child).containsPositions()) {
925
- nextNode = child;
926
- nextOffset = 0;
927
- } else {
928
- nextNode = node;
929
- nextOffset = offset + 1;
930
- }
931
- }
932
- }
933
-
934
- return nextNode ? session.getPosition(nextNode, nextOffset) : null;
935
- }),
936
-
937
- previous: createCachingGetter("previous", function(pos) {
938
- var nodeWrapper = pos.nodeWrapper, node = pos.node, offset = pos.offset, session = nodeWrapper.session;
939
- var previousNode, previousOffset, child;
940
- if (offset == 0) {
941
- previousNode = node.parentNode;
942
- previousOffset = previousNode ? nodeWrapper.getNodeIndex() : 0;
943
- } else {
944
- if (nodeWrapper.isCharacterDataNode()) {
945
- previousNode = node;
946
- previousOffset = offset - 1;
947
- } else {
948
- child = node.childNodes[offset - 1];
949
- // Go into the children next, if children there are
950
- if (session.getNodeWrapper(child).containsPositions()) {
951
- previousNode = child;
952
- previousOffset = dom.getNodeLength(child);
953
- } else {
954
- previousNode = node;
955
- previousOffset = offset - 1;
956
- }
957
- }
958
- }
959
- return previousNode ? session.getPosition(previousNode, previousOffset) : null;
960
- }),
961
-
962
- /*
963
- Next and previous position moving functions that filter out
964
-
965
- - Hidden (CSS visibility/display) elements
966
- - Script and style elements
967
- */
968
- nextVisible: createCachingGetter("nextVisible", function(pos) {
969
- var next = pos.next();
970
- if (!next) {
971
- return null;
972
- }
973
- var nodeWrapper = next.nodeWrapper, node = next.node;
974
- var newPos = next;
975
- if (nodeWrapper.isCollapsed()) {
976
- // We're skipping this node and all its descendants
977
- newPos = nodeWrapper.session.getPosition(node.parentNode, nodeWrapper.getNodeIndex() + 1);
978
- }
979
- return newPos;
980
- }),
981
-
982
- nextUncollapsed: createCachingGetter("nextUncollapsed", function(pos) {
983
- var nextPos = pos;
984
- while ( (nextPos = nextPos.nextVisible()) ) {
985
- nextPos.resolveLeadingAndTrailingSpaces();
986
- if (nextPos.character !== "") {
987
- return nextPos;
988
- }
989
- }
990
- return null;
991
- }),
992
-
993
- previousVisible: createCachingGetter("previousVisible", function(pos) {
994
- var previous = pos.previous();
995
- if (!previous) {
996
- return null;
997
- }
998
- var nodeWrapper = previous.nodeWrapper, node = previous.node;
999
- var newPos = previous;
1000
- if (nodeWrapper.isCollapsed()) {
1001
- // We're skipping this node and all its descendants
1002
- newPos = nodeWrapper.session.getPosition(node.parentNode, nodeWrapper.getNodeIndex());
1003
- }
1004
- return newPos;
1005
- })
1006
- });
1007
-
1008
- /*----------------------------------------------------------------------------------------------------------------*/
1009
-
1010
- var currentSession = null;
1011
-
1012
- var Session = (function() {
1013
- function createWrapperCache(nodeProperty) {
1014
- var cache = new Cache();
1015
-
1016
- return {
1017
- get: function(node) {
1018
- var wrappersByProperty = cache.get(node[nodeProperty]);
1019
- if (wrappersByProperty) {
1020
- for (var i = 0, wrapper; wrapper = wrappersByProperty[i++]; ) {
1021
- if (wrapper.node === node) {
1022
- return wrapper;
1023
- }
1024
- }
1025
- }
1026
- return null;
1027
- },
1028
-
1029
- set: function(nodeWrapper) {
1030
- var property = nodeWrapper.node[nodeProperty];
1031
- var wrappersByProperty = cache.get(property) || cache.set(property, []);
1032
- wrappersByProperty.push(nodeWrapper);
1033
- }
1034
- };
1035
- }
1036
-
1037
- var uniqueIDSupported = util.isHostProperty(document.documentElement, "uniqueID");
1038
-
1039
- function Session() {
1040
- this.initCaches();
1041
- }
1042
-
1043
- Session.prototype = {
1044
- initCaches: function() {
1045
- this.elementCache = uniqueIDSupported ? (function() {
1046
- var elementsCache = new Cache();
1047
-
1048
- return {
1049
- get: function(el) {
1050
- return elementsCache.get(el.uniqueID);
1051
- },
1052
-
1053
- set: function(elWrapper) {
1054
- elementsCache.set(elWrapper.node.uniqueID, elWrapper);
1055
- }
1056
- };
1057
- })() : createWrapperCache("tagName");
1058
-
1059
- // Store text nodes keyed by data, although we may need to truncate this
1060
- this.textNodeCache = createWrapperCache("data");
1061
- this.otherNodeCache = createWrapperCache("nodeName");
1062
- },
1063
-
1064
- getNodeWrapper: function(node) {
1065
- var wrapperCache;
1066
- switch (node.nodeType) {
1067
- case 1:
1068
- wrapperCache = this.elementCache;
1069
- break;
1070
- case 3:
1071
- wrapperCache = this.textNodeCache;
1072
- break;
1073
- default:
1074
- wrapperCache = this.otherNodeCache;
1075
- break;
1076
- }
1077
-
1078
- var wrapper = wrapperCache.get(node);
1079
- if (!wrapper) {
1080
- wrapper = new NodeWrapper(node, this);
1081
- wrapperCache.set(wrapper);
1082
- }
1083
- return wrapper;
1084
- },
1085
-
1086
- getPosition: function(node, offset) {
1087
- return this.getNodeWrapper(node).getPosition(offset);
1088
- },
1089
-
1090
- getRangeBoundaryPosition: function(range, isStart) {
1091
- var prefix = isStart ? "start" : "end";
1092
- return this.getPosition(range[prefix + "Container"], range[prefix + "Offset"]);
1093
- },
1094
-
1095
- detach: function() {
1096
- this.elementCache = this.textNodeCache = this.otherNodeCache = null;
1097
- }
1098
- };
1099
-
1100
- return Session;
1101
- })();
1102
-
1103
- /*----------------------------------------------------------------------------------------------------------------*/
1104
-
1105
- function startSession() {
1106
- endSession();
1107
- return (currentSession = new Session());
1108
- }
1109
-
1110
- function getSession() {
1111
- return currentSession || startSession();
1112
- }
1113
-
1114
- function endSession() {
1115
- if (currentSession) {
1116
- currentSession.detach();
1117
- }
1118
- currentSession = null;
1119
- }
1120
-
1121
- /*----------------------------------------------------------------------------------------------------------------*/
1122
-
1123
- // Extensions to the rangy.dom utility object
1124
-
1125
- extend(dom, {
1126
- nextNode: nextNode,
1127
- previousNode: previousNode
1128
- });
1129
-
1130
- /*----------------------------------------------------------------------------------------------------------------*/
1131
-
1132
- function createCharacterIterator(startPos, backward, endPos, characterOptions) {
1133
-
1134
- // Adjust the end position to ensure that it is actually reached
1135
- if (endPos) {
1136
- if (backward) {
1137
- if (isCollapsedNode(endPos.node)) {
1138
- endPos = startPos.previousVisible();
1139
- }
1140
- } else {
1141
- if (isCollapsedNode(endPos.node)) {
1142
- endPos = endPos.nextVisible();
1143
- }
1144
- }
1145
- }
1146
-
1147
- var pos = startPos, finished = false;
1148
-
1149
- function next() {
1150
- var newPos = null, charPos = null;
1151
- if (backward) {
1152
- charPos = pos;
1153
- if (!finished) {
1154
- pos = pos.previousVisible();
1155
- finished = !pos || (endPos && pos.equals(endPos));
1156
- }
1157
- } else {
1158
- if (!finished) {
1159
- charPos = pos = pos.nextVisible();
1160
- finished = !pos || (endPos && pos.equals(endPos));
1161
- }
1162
- }
1163
- if (finished) {
1164
- pos = null;
1165
- }
1166
- return charPos;
1167
- }
1168
-
1169
- var previousTextPos, returnPreviousTextPos = false;
1170
-
1171
- return {
1172
- next: function() {
1173
- if (returnPreviousTextPos) {
1174
- returnPreviousTextPos = false;
1175
- return previousTextPos;
1176
- } else {
1177
- var pos, character;
1178
- while ( (pos = next()) ) {
1179
- character = pos.getCharacter(characterOptions);
1180
- if (character) {
1181
- previousTextPos = pos;
1182
- return pos;
1183
- }
1184
- }
1185
- return null;
1186
- }
1187
- },
1188
-
1189
- rewind: function() {
1190
- if (previousTextPos) {
1191
- returnPreviousTextPos = true;
1192
- } else {
1193
- throw module.createError("createCharacterIterator: cannot rewind. Only one position can be rewound.");
1194
- }
1195
- },
1196
-
1197
- dispose: function() {
1198
- startPos = endPos = null;
1199
- }
1200
- };
1201
- }
1202
-
1203
- var arrayIndexOf = Array.prototype.indexOf ?
1204
- function(arr, val) {
1205
- return arr.indexOf(val);
1206
- } :
1207
- function(arr, val) {
1208
- for (var i = 0, len = arr.length; i < len; ++i) {
1209
- if (arr[i] === val) {
1210
- return i;
1211
- }
1212
- }
1213
- return -1;
1214
- };
1215
-
1216
- // Provides a pair of iterators over text positions, tokenized. Transparently requests more text when next()
1217
- // is called and there is no more tokenized text
1218
- function createTokenizedTextProvider(pos, characterOptions, wordOptions) {
1219
- var forwardIterator = createCharacterIterator(pos, false, null, characterOptions);
1220
- var backwardIterator = createCharacterIterator(pos, true, null, characterOptions);
1221
- var tokenizer = wordOptions.tokenizer;
1222
-
1223
- // Consumes a word and the whitespace beyond it
1224
- function consumeWord(forward) {
1225
- var pos, textChar;
1226
- var newChars = [], it = forward ? forwardIterator : backwardIterator;
1227
-
1228
- var passedWordBoundary = false, insideWord = false;
1229
-
1230
- while ( (pos = it.next()) ) {
1231
- textChar = pos.character;
1232
-
1233
-
1234
- if (allWhiteSpaceRegex.test(textChar)) {
1235
- if (insideWord) {
1236
- insideWord = false;
1237
- passedWordBoundary = true;
1238
- }
1239
- } else {
1240
- if (passedWordBoundary) {
1241
- it.rewind();
1242
- break;
1243
- } else {
1244
- insideWord = true;
1245
- }
1246
- }
1247
- newChars.push(pos);
1248
- }
1249
-
1250
-
1251
- return newChars;
1252
- }
1253
-
1254
- // Get initial word surrounding initial position and tokenize it
1255
- var forwardChars = consumeWord(true);
1256
- var backwardChars = consumeWord(false).reverse();
1257
- var tokens = tokenizer(backwardChars.concat(forwardChars), wordOptions);
1258
-
1259
- // Create initial token buffers
1260
- var forwardTokensBuffer = forwardChars.length ?
1261
- tokens.slice(arrayIndexOf(tokens, forwardChars[0].token)) : [];
1262
-
1263
- var backwardTokensBuffer = backwardChars.length ?
1264
- tokens.slice(0, arrayIndexOf(tokens, backwardChars.pop().token) + 1) : [];
1265
-
1266
- function inspectBuffer(buffer) {
1267
- var textPositions = ["[" + buffer.length + "]"];
1268
- for (var i = 0; i < buffer.length; ++i) {
1269
- textPositions.push("(word: " + buffer[i] + ", is word: " + buffer[i].isWord + ")");
1270
- }
1271
- return textPositions;
1272
- }
1273
-
1274
-
1275
- return {
1276
- nextEndToken: function() {
1277
- var lastToken, forwardChars;
1278
-
1279
- // If we're down to the last token, consume character chunks until we have a word or run out of
1280
- // characters to consume
1281
- while ( forwardTokensBuffer.length == 1 &&
1282
- !(lastToken = forwardTokensBuffer[0]).isWord &&
1283
- (forwardChars = consumeWord(true)).length > 0) {
1284
-
1285
- // Merge trailing non-word into next word and tokenize
1286
- forwardTokensBuffer = tokenizer(lastToken.chars.concat(forwardChars), wordOptions);
1287
- }
1288
-
1289
- return forwardTokensBuffer.shift();
1290
- },
1291
-
1292
- previousStartToken: function() {
1293
- var lastToken, backwardChars;
1294
-
1295
- // If we're down to the last token, consume character chunks until we have a word or run out of
1296
- // characters to consume
1297
- while ( backwardTokensBuffer.length == 1 &&
1298
- !(lastToken = backwardTokensBuffer[0]).isWord &&
1299
- (backwardChars = consumeWord(false)).length > 0) {
1300
-
1301
- // Merge leading non-word into next word and tokenize
1302
- backwardTokensBuffer = tokenizer(backwardChars.reverse().concat(lastToken.chars), wordOptions);
1303
- }
1304
-
1305
- return backwardTokensBuffer.pop();
1306
- },
1307
-
1308
- dispose: function() {
1309
- forwardIterator.dispose();
1310
- backwardIterator.dispose();
1311
- forwardTokensBuffer = backwardTokensBuffer = null;
1312
- }
1313
- };
1314
- }
1315
-
1316
- function movePositionBy(pos, unit, count, characterOptions, wordOptions) {
1317
- var unitsMoved = 0, currentPos, newPos = pos, charIterator, nextPos, absCount = Math.abs(count), token;
1318
- if (count !== 0) {
1319
- var backward = (count < 0);
1320
-
1321
- switch (unit) {
1322
- case CHARACTER:
1323
- charIterator = createCharacterIterator(pos, backward, null, characterOptions);
1324
- while ( (currentPos = charIterator.next()) && unitsMoved < absCount ) {
1325
- ++unitsMoved;
1326
- newPos = currentPos;
1327
- }
1328
- nextPos = currentPos;
1329
- charIterator.dispose();
1330
- break;
1331
- case WORD:
1332
- var tokenizedTextProvider = createTokenizedTextProvider(pos, characterOptions, wordOptions);
1333
- var next = backward ? tokenizedTextProvider.previousStartToken : tokenizedTextProvider.nextEndToken;
1334
-
1335
- while ( (token = next()) && unitsMoved < absCount ) {
1336
- if (token.isWord) {
1337
- ++unitsMoved;
1338
- newPos = backward ? token.chars[0] : token.chars[token.chars.length - 1];
1339
- }
1340
- }
1341
- break;
1342
- default:
1343
- throw new Error("movePositionBy: unit '" + unit + "' not implemented");
1344
- }
1345
-
1346
- // Perform any necessary position tweaks
1347
- if (backward) {
1348
- newPos = newPos.previousVisible();
1349
- unitsMoved = -unitsMoved;
1350
- } else if (newPos && newPos.isLeadingSpace) {
1351
- // Tweak the position for the case of a leading space. The problem is that an uncollapsed leading space
1352
- // before a block element (for example, the line break between "1" and "2" in the following HTML:
1353
- // "1<p>2</p>") is considered to be attached to the position immediately before the block element, which
1354
- // corresponds with a different selection position in most browsers from the one we want (i.e. at the
1355
- // start of the contents of the block element). We get round this by advancing the position returned to
1356
- // the last possible equivalent visible position.
1357
- if (unit == WORD) {
1358
- charIterator = createCharacterIterator(pos, false, null, characterOptions);
1359
- nextPos = charIterator.next();
1360
- charIterator.dispose();
1361
- }
1362
- if (nextPos) {
1363
- newPos = nextPos.previousVisible();
1364
- }
1365
- }
1366
- }
1367
-
1368
-
1369
- return {
1370
- position: newPos,
1371
- unitsMoved: unitsMoved
1372
- };
1373
- }
1374
-
1375
- function createRangeCharacterIterator(session, range, characterOptions, backward) {
1376
- var rangeStart = session.getRangeBoundaryPosition(range, true);
1377
- var rangeEnd = session.getRangeBoundaryPosition(range, false);
1378
- var itStart = backward ? rangeEnd : rangeStart;
1379
- var itEnd = backward ? rangeStart : rangeEnd;
1380
-
1381
- return createCharacterIterator(itStart, !!backward, itEnd, characterOptions);
1382
- }
1383
-
1384
- function getRangeCharacters(session, range, characterOptions) {
1385
-
1386
- var chars = [], it = createRangeCharacterIterator(session, range, characterOptions), pos;
1387
- while ( (pos = it.next()) ) {
1388
- chars.push(pos);
1389
- }
1390
-
1391
- it.dispose();
1392
- return chars;
1393
- }
1394
-
1395
- function isWholeWord(startPos, endPos, wordOptions) {
1396
- var range = api.createRange(startPos.node);
1397
- range.setStartAndEnd(startPos.node, startPos.offset, endPos.node, endPos.offset);
1398
- var returnVal = !range.expand("word", wordOptions);
1399
- range.detach();
1400
- return returnVal;
1401
- }
1402
-
1403
- function findTextFromPosition(initialPos, searchTerm, isRegex, searchScopeRange, findOptions) {
1404
- var backward = isDirectionBackward(findOptions.direction);
1405
- var it = createCharacterIterator(
1406
- initialPos,
1407
- backward,
1408
- initialPos.session.getRangeBoundaryPosition(searchScopeRange, backward),
1409
- findOptions
1410
- );
1411
- var text = "", chars = [], pos, currentChar, matchStartIndex, matchEndIndex;
1412
- var result, insideRegexMatch;
1413
- var returnValue = null;
1414
-
1415
- function handleMatch(startIndex, endIndex) {
1416
- var startPos = chars[startIndex].previousVisible();
1417
- var endPos = chars[endIndex - 1];
1418
- var valid = (!findOptions.wholeWordsOnly || isWholeWord(startPos, endPos, findOptions.wordOptions));
1419
-
1420
- return {
1421
- startPos: startPos,
1422
- endPos: endPos,
1423
- valid: valid
1424
- };
1425
- }
1426
-
1427
- while ( (pos = it.next()) ) {
1428
- currentChar = pos.character;
1429
- if (!isRegex && !findOptions.caseSensitive) {
1430
- currentChar = currentChar.toLowerCase();
1431
- }
1432
-
1433
- if (backward) {
1434
- chars.unshift(pos);
1435
- text = currentChar + text;
1436
- } else {
1437
- chars.push(pos);
1438
- text += currentChar;
1439
- }
1440
-
1441
- if (isRegex) {
1442
- result = searchTerm.exec(text);
1443
- if (result) {
1444
- if (insideRegexMatch) {
1445
- // Check whether the match is now over
1446
- matchStartIndex = result.index;
1447
- matchEndIndex = matchStartIndex + result[0].length;
1448
- if ((!backward && matchEndIndex < text.length) || (backward && matchStartIndex > 0)) {
1449
- returnValue = handleMatch(matchStartIndex, matchEndIndex);
1450
- break;
1451
- }
1452
- } else {
1453
- insideRegexMatch = true;
1454
- }
1455
- }
1456
- } else if ( (matchStartIndex = text.indexOf(searchTerm)) != -1 ) {
1457
- returnValue = handleMatch(matchStartIndex, matchStartIndex + searchTerm.length);
1458
- break;
1459
- }
1460
- }
1461
-
1462
- // Check whether regex match extends to the end of the range
1463
- if (insideRegexMatch) {
1464
- returnValue = handleMatch(matchStartIndex, matchEndIndex);
1465
- }
1466
- it.dispose();
1467
-
1468
- return returnValue;
1469
- }
1470
-
1471
- function createEntryPointFunction(func) {
1472
- return function() {
1473
- var sessionRunning = !!currentSession;
1474
- var session = getSession();
1475
- var args = [session].concat( util.toArray(arguments) );
1476
- var returnValue = func.apply(this, args);
1477
- if (!sessionRunning) {
1478
- endSession();
1479
- }
1480
- return returnValue;
1481
- };
1482
- }
1483
-
1484
- /*----------------------------------------------------------------------------------------------------------------*/
1485
-
1486
- // Extensions to the Rangy Range object
1487
-
1488
- function createRangeBoundaryMover(isStart, collapse) {
1489
- /*
1490
- Unit can be "character" or "word"
1491
- Options:
1492
-
1493
- - includeTrailingSpace
1494
- - wordRegex
1495
- - tokenizer
1496
- - collapseSpaceBeforeLineBreak
1497
- */
1498
- return createEntryPointFunction(
1499
- function(session, unit, count, moveOptions) {
1500
- if (typeof count == "undefined") {
1501
- count = unit;
1502
- unit = CHARACTER;
1503
- }
1504
- moveOptions = createOptions(moveOptions, defaultMoveOptions);
1505
- var characterOptions = createCharacterOptions(moveOptions.characterOptions);
1506
- var wordOptions = createWordOptions(moveOptions.wordOptions);
1507
-
1508
- var boundaryIsStart = isStart;
1509
- if (collapse) {
1510
- boundaryIsStart = (count >= 0);
1511
- this.collapse(!boundaryIsStart);
1512
- }
1513
- var moveResult = movePositionBy(session.getRangeBoundaryPosition(this, boundaryIsStart), unit, count, characterOptions, wordOptions);
1514
- var newPos = moveResult.position;
1515
- this[boundaryIsStart ? "setStart" : "setEnd"](newPos.node, newPos.offset);
1516
- return moveResult.unitsMoved;
1517
- }
1518
- );
1519
- }
1520
-
1521
- function createRangeTrimmer(isStart) {
1522
- return createEntryPointFunction(
1523
- function(session, characterOptions) {
1524
- characterOptions = createCharacterOptions(characterOptions);
1525
- var pos;
1526
- var it = createRangeCharacterIterator(session, this, characterOptions, !isStart);
1527
- var trimCharCount = 0;
1528
- while ( (pos = it.next()) && allWhiteSpaceRegex.test(pos.character) ) {
1529
- ++trimCharCount;
1530
- }
1531
- it.dispose();
1532
- var trimmed = (trimCharCount > 0);
1533
- if (trimmed) {
1534
- this[isStart ? "moveStart" : "moveEnd"](
1535
- "character",
1536
- isStart ? trimCharCount : -trimCharCount,
1537
- { characterOptions: characterOptions }
1538
- );
1539
- }
1540
- return trimmed;
1541
- }
1542
- );
1543
- }
1544
-
1545
- extend(api.rangePrototype, {
1546
- moveStart: createRangeBoundaryMover(true, false),
1547
-
1548
- moveEnd: createRangeBoundaryMover(false, false),
1549
-
1550
- move: createRangeBoundaryMover(true, true),
1551
-
1552
- trimStart: createRangeTrimmer(true),
1553
-
1554
- trimEnd: createRangeTrimmer(false),
1555
-
1556
- trim: createEntryPointFunction(
1557
- function(session, characterOptions) {
1558
- var startTrimmed = this.trimStart(characterOptions), endTrimmed = this.trimEnd(characterOptions);
1559
- return startTrimmed || endTrimmed;
1560
- }
1561
- ),
1562
-
1563
- expand: createEntryPointFunction(
1564
- function(session, unit, expandOptions) {
1565
- var moved = false;
1566
- expandOptions = createOptions(expandOptions, defaultExpandOptions);
1567
- var characterOptions = createCharacterOptions(expandOptions.characterOptions);
1568
- if (!unit) {
1569
- unit = CHARACTER;
1570
- }
1571
- if (unit == WORD) {
1572
- var wordOptions = createWordOptions(expandOptions.wordOptions);
1573
- var startPos = session.getRangeBoundaryPosition(this, true);
1574
- var endPos = session.getRangeBoundaryPosition(this, false);
1575
-
1576
- var startTokenizedTextProvider = createTokenizedTextProvider(startPos, characterOptions, wordOptions);
1577
- var startToken = startTokenizedTextProvider.nextEndToken();
1578
- var newStartPos = startToken.chars[0].previousVisible();
1579
- var endToken, newEndPos;
1580
-
1581
- if (this.collapsed) {
1582
- endToken = startToken;
1583
- } else {
1584
- var endTokenizedTextProvider = createTokenizedTextProvider(endPos, characterOptions, wordOptions);
1585
- endToken = endTokenizedTextProvider.previousStartToken();
1586
- }
1587
- newEndPos = endToken.chars[endToken.chars.length - 1];
1588
-
1589
- if (!newStartPos.equals(startPos)) {
1590
- this.setStart(newStartPos.node, newStartPos.offset);
1591
- moved = true;
1592
- }
1593
- if (newEndPos && !newEndPos.equals(endPos)) {
1594
- this.setEnd(newEndPos.node, newEndPos.offset);
1595
- moved = true;
1596
- }
1597
-
1598
- if (expandOptions.trim) {
1599
- if (expandOptions.trimStart) {
1600
- moved = this.trimStart(characterOptions) || moved;
1601
- }
1602
- if (expandOptions.trimEnd) {
1603
- moved = this.trimEnd(characterOptions) || moved;
1604
- }
1605
- }
1606
-
1607
- return moved;
1608
- } else {
1609
- return this.moveEnd(CHARACTER, 1, expandOptions);
1610
- }
1611
- }
1612
- ),
1613
-
1614
- text: createEntryPointFunction(
1615
- function(session, characterOptions) {
1616
- return this.collapsed ?
1617
- "" : getRangeCharacters(session, this, createCharacterOptions(characterOptions)).join("");
1618
- }
1619
- ),
1620
-
1621
- selectCharacters: createEntryPointFunction(
1622
- function(session, containerNode, startIndex, endIndex, characterOptions) {
1623
- var moveOptions = { characterOptions: characterOptions };
1624
- if (!containerNode) {
1625
- containerNode = getBody( this.getDocument() );
1626
- }
1627
- this.selectNodeContents(containerNode);
1628
- this.collapse(true);
1629
- this.moveStart("character", startIndex, moveOptions);
1630
- this.collapse(true);
1631
- this.moveEnd("character", endIndex - startIndex, moveOptions);
1632
- }
1633
- ),
1634
-
1635
- // Character indexes are relative to the start of node
1636
- toCharacterRange: createEntryPointFunction(
1637
- function(session, containerNode, characterOptions) {
1638
- if (!containerNode) {
1639
- containerNode = getBody( this.getDocument() );
1640
- }
1641
- var parent = containerNode.parentNode, nodeIndex = dom.getNodeIndex(containerNode);
1642
- var rangeStartsBeforeNode = (dom.comparePoints(this.startContainer, this.endContainer, parent, nodeIndex) == -1);
1643
- var rangeBetween = this.cloneRange();
1644
- var startIndex, endIndex;
1645
- if (rangeStartsBeforeNode) {
1646
- rangeBetween.setStartAndEnd(this.startContainer, this.startOffset, parent, nodeIndex);
1647
- startIndex = -rangeBetween.text(characterOptions).length;
1648
- } else {
1649
- rangeBetween.setStartAndEnd(parent, nodeIndex, this.startContainer, this.startOffset);
1650
- startIndex = rangeBetween.text(characterOptions).length;
1651
- }
1652
- endIndex = startIndex + this.text(characterOptions).length;
1653
-
1654
- return {
1655
- start: startIndex,
1656
- end: endIndex
1657
- };
1658
- }
1659
- ),
1660
-
1661
- findText: createEntryPointFunction(
1662
- function(session, searchTermParam, findOptions) {
1663
- // Set up options
1664
- findOptions = createOptions(findOptions, defaultFindOptions);
1665
-
1666
- // Create word options if we're matching whole words only
1667
- if (findOptions.wholeWordsOnly) {
1668
- findOptions.wordOptions = createWordOptions(findOptions.wordOptions);
1669
-
1670
- // We don't ever want trailing spaces for search results
1671
- findOptions.wordOptions.includeTrailingSpace = false;
1672
- }
1673
-
1674
- var backward = isDirectionBackward(findOptions.direction);
1675
-
1676
- // Create a range representing the search scope if none was provided
1677
- var searchScopeRange = findOptions.withinRange;
1678
- if (!searchScopeRange) {
1679
- searchScopeRange = api.createRange();
1680
- searchScopeRange.selectNodeContents(this.getDocument());
1681
- }
1682
-
1683
- // Examine and prepare the search term
1684
- var searchTerm = searchTermParam, isRegex = false;
1685
- if (typeof searchTerm == "string") {
1686
- if (!findOptions.caseSensitive) {
1687
- searchTerm = searchTerm.toLowerCase();
1688
- }
1689
- } else {
1690
- isRegex = true;
1691
- }
1692
-
1693
- var initialPos = session.getRangeBoundaryPosition(this, !backward);
1694
-
1695
- // Adjust initial position if it lies outside the search scope
1696
- var comparison = searchScopeRange.comparePoint(initialPos.node, initialPos.offset);
1697
-
1698
- if (comparison === -1) {
1699
- initialPos = session.getRangeBoundaryPosition(searchScopeRange, true);
1700
- } else if (comparison === 1) {
1701
- initialPos = session.getRangeBoundaryPosition(searchScopeRange, false);
1702
- }
1703
-
1704
- var pos = initialPos;
1705
- var wrappedAround = false;
1706
-
1707
- // Try to find a match and ignore invalid ones
1708
- var findResult;
1709
- while (true) {
1710
- findResult = findTextFromPosition(pos, searchTerm, isRegex, searchScopeRange, findOptions);
1711
-
1712
- if (findResult) {
1713
- if (findResult.valid) {
1714
- this.setStartAndEnd(findResult.startPos.node, findResult.startPos.offset, findResult.endPos.node, findResult.endPos.offset);
1715
- return true;
1716
- } else {
1717
- // We've found a match that is not a whole word, so we carry on searching from the point immediately
1718
- // after the match
1719
- pos = backward ? findResult.startPos : findResult.endPos;
1720
- }
1721
- } else if (findOptions.wrap && !wrappedAround) {
1722
- // No result found but we're wrapping around and limiting the scope to the unsearched part of the range
1723
- searchScopeRange = searchScopeRange.cloneRange();
1724
- pos = session.getRangeBoundaryPosition(searchScopeRange, !backward);
1725
- searchScopeRange.setBoundary(initialPos.node, initialPos.offset, backward);
1726
- wrappedAround = true;
1727
- } else {
1728
- // Nothing found and we can't wrap around, so we're done
1729
- return false;
1730
- }
1731
- }
1732
- }
1733
- ),
1734
-
1735
- pasteHtml: function(html) {
1736
- this.deleteContents();
1737
- if (html) {
1738
- var frag = this.createContextualFragment(html);
1739
- var lastChild = frag.lastChild;
1740
- this.insertNode(frag);
1741
- this.collapseAfter(lastChild);
1742
- }
1743
- }
1744
- });
1745
-
1746
- /*----------------------------------------------------------------------------------------------------------------*/
1747
-
1748
- // Extensions to the Rangy Selection object
1749
-
1750
- function createSelectionTrimmer(methodName) {
1751
- return createEntryPointFunction(
1752
- function(session, characterOptions) {
1753
- var trimmed = false;
1754
- this.changeEachRange(function(range) {
1755
- trimmed = range[methodName](characterOptions) || trimmed;
1756
- });
1757
- return trimmed;
1758
- }
1759
- );
1760
- }
1761
-
1762
- extend(api.selectionPrototype, {
1763
- expand: createEntryPointFunction(
1764
- function(session, unit, expandOptions) {
1765
- this.changeEachRange(function(range) {
1766
- range.expand(unit, expandOptions);
1767
- });
1768
- }
1769
- ),
1770
-
1771
- move: createEntryPointFunction(
1772
- function(session, unit, count, options) {
1773
- var unitsMoved = 0;
1774
- if (this.focusNode) {
1775
- this.collapse(this.focusNode, this.focusOffset);
1776
- var range = this.getRangeAt(0);
1777
- if (!options) {
1778
- options = {};
1779
- }
1780
- options.characterOptions = createCaretCharacterOptions(options.characterOptions);
1781
- unitsMoved = range.move(unit, count, options);
1782
- this.setSingleRange(range);
1783
- }
1784
- return unitsMoved;
1785
- }
1786
- ),
1787
-
1788
- trimStart: createSelectionTrimmer("trimStart"),
1789
- trimEnd: createSelectionTrimmer("trimEnd"),
1790
- trim: createSelectionTrimmer("trim"),
1791
-
1792
- selectCharacters: createEntryPointFunction(
1793
- function(session, containerNode, startIndex, endIndex, direction, characterOptions) {
1794
- var range = api.createRange(containerNode);
1795
- range.selectCharacters(containerNode, startIndex, endIndex, characterOptions);
1796
- this.setSingleRange(range, direction);
1797
- }
1798
- ),
1799
-
1800
- saveCharacterRanges: createEntryPointFunction(
1801
- function(session, containerNode, characterOptions) {
1802
- var ranges = this.getAllRanges(), rangeCount = ranges.length;
1803
- var rangeInfos = [];
1804
-
1805
- var backward = rangeCount == 1 && this.isBackward();
1806
-
1807
- for (var i = 0, len = ranges.length; i < len; ++i) {
1808
- rangeInfos[i] = {
1809
- characterRange: ranges[i].toCharacterRange(containerNode, characterOptions),
1810
- backward: backward,
1811
- characterOptions: characterOptions
1812
- };
1813
- }
1814
-
1815
- return rangeInfos;
1816
- }
1817
- ),
1818
-
1819
- restoreCharacterRanges: createEntryPointFunction(
1820
- function(session, containerNode, saved) {
1821
- this.removeAllRanges();
1822
- for (var i = 0, len = saved.length, range, rangeInfo, characterRange; i < len; ++i) {
1823
- rangeInfo = saved[i];
1824
- characterRange = rangeInfo.characterRange;
1825
- range = api.createRange(containerNode);
1826
- range.selectCharacters(containerNode, characterRange.start, characterRange.end, rangeInfo.characterOptions);
1827
- this.addRange(range, rangeInfo.backward);
1828
- }
1829
- }
1830
- ),
1831
-
1832
- text: createEntryPointFunction(
1833
- function(session, characterOptions) {
1834
- var rangeTexts = [];
1835
- for (var i = 0, len = this.rangeCount; i < len; ++i) {
1836
- rangeTexts[i] = this.getRangeAt(i).text(characterOptions);
1837
- }
1838
- return rangeTexts.join("");
1839
- }
1840
- )
1841
- });
1842
-
1843
- /*----------------------------------------------------------------------------------------------------------------*/
1844
-
1845
- // Extensions to the core rangy object
1846
-
1847
- api.innerText = function(el, characterOptions) {
1848
- var range = api.createRange(el);
1849
- range.selectNodeContents(el);
1850
- var text = range.text(characterOptions);
1851
- range.detach();
1852
- return text;
1853
- };
1854
-
1855
- api.createWordIterator = function(startNode, startOffset, iteratorOptions) {
1856
- var session = getSession();
1857
- iteratorOptions = createOptions(iteratorOptions, defaultWordIteratorOptions);
1858
- var characterOptions = createCharacterOptions(iteratorOptions.characterOptions);
1859
- var wordOptions = createWordOptions(iteratorOptions.wordOptions);
1860
- var startPos = session.getPosition(startNode, startOffset);
1861
- var tokenizedTextProvider = createTokenizedTextProvider(startPos, characterOptions, wordOptions);
1862
- var backward = isDirectionBackward(iteratorOptions.direction);
1863
-
1864
- return {
1865
- next: function() {
1866
- return backward ? tokenizedTextProvider.previousStartToken() : tokenizedTextProvider.nextEndToken();
1867
- },
1868
-
1869
- dispose: function() {
1870
- tokenizedTextProvider.dispose();
1871
- this.next = function() {};
1872
- }
1873
- };
1874
- };
1875
-
1876
- /*----------------------------------------------------------------------------------------------------------------*/
1877
-
1878
- api.noMutation = function(func) {
1879
- var session = getSession();
1880
- func(session);
1881
- endSession();
1882
- };
1883
-
1884
- api.noMutation.createEntryPointFunction = createEntryPointFunction;
1885
-
1886
- api.textRange = {
1887
- isBlockNode: isBlockNode,
1888
- isCollapsedWhitespaceNode: isCollapsedWhitespaceNode,
1889
-
1890
- createPosition: createEntryPointFunction(
1891
- function(session, node, offset) {
1892
- return session.getPosition(node, offset);
1893
- }
1894
- )
1895
- };
1896
- });
32
+ rangy.createModule("TextRange",["WrappedSelection"],function(a,b){function t(a,b){function f(b,c,d){var f=a.slice(b,c),g={isWord:d,chars:f,toString:function(){return f.join("")}};for(var h=0,i=f.length;h<i;++h)f[h].token=g;e.push(g)}var c=a.join(""),d,e=[],g=0,h,i;while(d=b.wordRegex.exec(c)){h=d.index,i=h+d[0].length,h>g&&f(g,h,!1);if(b.includeTrailingSpace)while(m.test(a[i]))++i;f(h,i,!0),g=i}return g<a.length&&f(g,a.length,!1),e}function x(a,b){if(!a)return b;var c={};return h(c,b),h(c,a),c}function y(a){var b,c;return a?(b=a.language||o,c={},h(c,w[b]||w[o]),h(c,a),c):w[o]}function z(a){return x(a,u)}function A(a){return x(a,v)}function I(a,b){var c=F(a,"display",b),d=a.tagName.toLowerCase();return c=="block"&&G&&H.hasOwnProperty(d)?H[d]:c}function J(a){var b=P(a);for(var c=0,d=b.length;c<d;++c)if(b[c].nodeType==1&&I(b[c])=="none")return!0;return!1}function K(a){var b;return a.nodeType==3&&(b=a.parentNode)&&F(b,"visibility")=="hidden"}function L(a){return a&&(a.nodeType==1&&!/^(inline(-block|-table)?|none)$/.test(I(a))||a.nodeType==9||a.nodeType==11)}function M(a){var b=a.lastChild;return b?M(b):a}function N(a){return f.isCharacterDataNode(a)||!/^(area|base|basefont|br|col|frame|hr|img|input|isindex|link|meta|param)$/i.test(a.nodeName)}function O(a){var b=[];while(a.parentNode)b.unshift(a.parentNode),a=a.parentNode;return b}function P(a){return O(a).concat([a])}function Q(a){var b;return typeof (b=a.namespaceURI)==c||b===null||b=="http://www.w3.org/1999/xhtml"}function R(a,b){if(!a||a.nodeType!=1||!Q(a))return!1;switch(typeof b){case"string":return a.tagName.toLowerCase()==b.toLowerCase();case"object":return(new RegExp("^("+b.join("|S")+")$","i")).test(a.tagName);default:return!0}}function S(a){while(a&&!a.nextSibling)a=a.parentNode;return a?a.nextSibling:null}function T(a,b){return!b&&a.hasChildNodes()?a.firstChild:S(a)}function U(a){var b=a.previousSibling;if(b){a=b;while(a.hasChildNodes())a=a.lastChild;return a}var c=a.parentNode;return c&&c.nodeType==1?c:null}function V(a){if(!a||a.nodeType!=3)return!1;var b=a.data;if(b==="")return!0;var c=a.parentNode;if(!c||c.nodeType!=1)return!1;var d=F(a.parentNode,"whiteSpace");return/^[\t\n\r ]+$/.test(b)&&/^(normal|nowrap)$/.test(d)||/^[\t\r ]+$/.test(b)&&d=="pre-line"}function W(a){if(a.data==="")return!0;if(!V(a))return!1;var b=a.parentNode;return b?J(a)?!0:!1:!0}function X(a){var b=a.nodeType;return b==7||b==8||J(a)||/^(script|style)$/i.test(a.nodeName)||K(a)||W(a)}function Y(a,b){var c=a.nodeType;return c==7||c==8||c==1&&I(a,b)=="none"}function Z(){this.store={}}function ab(a,b,c){return function(d){var e=this.cache;if(e.hasOwnProperty(a))return $++,e[a];_++;var f=b.call(this,c?this[c]:this,d);return e[a]=f,f}}function bb(a,b){this.node=a,this.session=b,this.cache=new Z,this.positions=new Z}function kb(a,b){this.offset=b,this.nodeWrapper=a,this.node=a.node,this.session=a.session,this.cache=new Z}function lb(){return"[Position("+f.inspectNode(this.node)+":"+this.offset+")]"}function pb(){return rb(),nb=new ob}function qb(){return nb||pb()}function rb(){nb&&nb.detach(),nb=null}function sb(a,c,d,e){function h(){var a=null,b=null;return c?(b=f,g||(f=f.previousVisible(),g=!f||d&&f.equals(d))):g||(b=f=f.nextVisible(),g=!f||d&&f.equals(d)),g&&(f=null),b}d&&(c?X(d.node)&&(d=a.previousVisible()):X(d.node)&&(d=d.nextVisible()));var f=a,g=!1,i,j=!1;return{next:function(){if(j)return j=!1,i;var a,b;while(a=h()){b=a.getCharacter(e);if(b)return i=a,a}return null},rewind:function(){if(!i)throw b.createError("createCharacterIterator: cannot rewind. Only one position can be rewound.");j=!0},dispose:function(){a=d=null}}}function ub(a,b,c){function g(a){var b,c,f=[],g=a?d:e,h=!1,i=!1;while(b=g.next()){c=b.character;if(l.test(c))i&&(i=!1,h=!0);else{if(h){g.rewind();break}i=!0}f.push(b)}return f}function n(a){var b=["["+a.length+"]"];for(var c=0;c<a.length;++c)b.push("(word: "+a[c]+", is word: "+a[c].isWord+")");return b}var d=sb(a,!1,null,b),e=sb(a,!0,null,b),f=c.tokenizer,h=g(!0),i=g(!1).reverse(),j=f(i.concat(h),c),k=h.length?j.slice(tb(j,h[0].token)):[],m=i.length?j.slice(0,tb(j,i.pop().token)+1):[];return{nextEndToken:function(){var a,b;while(k.length==1&&!(a=k[0]).isWord&&(b=g(!0)).length>0)k=f(a.chars.concat(b),c);return k.shift()},previousStartToken:function(){var a,b;while(m.length==1&&!(a=m[0]).isWord&&(b=g(!1)).length>0)m=f(b.reverse().concat(a.chars),c);return m.pop()},dispose:function(){d.dispose(),e.dispose(),k=m=null}}}function vb(a,b,c,f,g){var h=0,i,j=a,k,l,m=Math.abs(c),n;if(c!==0){var o=c<0;switch(b){case d:k=sb(a,o,null,f);while((i=k.next())&&h<m)++h,j=i;l=i,k.dispose();break;case e:var p=ub(a,f,g),q=o?p.previousStartToken:p.nextEndToken;while((n=q())&&h<m)n.isWord&&(++h,j=o?n.chars[0]:n.chars[n.chars.length-1]);break;default:throw new Error("movePositionBy: unit '"+b+"' not implemented")}o?(j=j.previousVisible(),h=-h):j&&j.isLeadingSpace&&(b==e&&(k=sb(a,!1,null,f),l=k.next(),k.dispose()),l&&(j=l.previousVisible()))}return{position:j,unitsMoved:h}}function wb(a,b,c,d){var e=a.getRangeBoundaryPosition(b,!0),f=a.getRangeBoundaryPosition(b,!1),g=d?f:e,h=d?e:f;return sb(g,!!d,h,c)}function xb(a,b,c){var d=[],e=wb(a,b,c),f;while(f=e.next())d.push(f);return e.dispose(),d}function yb(b,c,d){var e=a.createRange(b.node);e.setStartAndEnd(b.node,b.offset,c.node,c.offset);var f=!e.expand("word",d);return e.detach(),f}function zb(a,b,c,d,e){function r(a,b){var c=i[a].previousVisible(),d=i[b-1],f=!e.wholeWordsOnly||yb(c,d,e.wordOptions);return{startPos:c,endPos:d,valid:f}}var f=p(e.direction),g=sb(a,f,a.session.getRangeBoundaryPosition(d,f),e),h="",i=[],j,k,l,m,n,o,q=null;while(j=g.next()){k=j.character,!c&&!e.caseSensitive&&(k=k.toLowerCase()),f?(i.unshift(j),h=k+h):(i.push(j),h+=k);if(c){n=b.exec(h);if(n)if(o){l=n.index,m=l+n[0].length;if(!f&&m<h.length||f&&l>0){q=r(l,m);break}}else o=!0}else if((l=h.indexOf(b))!=-1){q=r(l,l+b.length);break}}return o&&(q=r(l,m)),g.dispose(),q}function Ab(a){return function(){var b=!!nb,c=qb(),d=[c].concat(g.toArray(arguments)),e=a.apply(this,d);return b||rb(),e}}function Bb(a,b){return Ab(function(c,e,f,g){typeof f=="undefined"&&(f=e,e=d),g=x(g,C);var h=z(g.characterOptions),i=y(g.wordOptions),j=a;b&&(j=f>=0,this.collapse(!j));var k=vb(c.getRangeBoundaryPosition(this,j),e,f,h,i),l=k.position;return this[j?"setStart":"setEnd"](l.node,l.offset),k.unitsMoved})}function Cb(a){return Ab(function(b,c){c=z(c);var d,e=wb(b,this,c,!a),f=0;while((d=e.next())&&l.test(d.character))++f;e.dispose();var g=f>0;return g&&this[a?"moveStart":"moveEnd"]("character",a?f:-f,{characterOptions:c}),g})}function Db(a){return Ab(function(b,c){var d=!1;return this.changeEachRange(function(b){d=b[a](c)||d}),d})}var c="undefined",d="character",e="word",f=a.dom,g=a.util,h=g.extend,i=f.getBody,j=/^[ \t\f\r\n]+$/,k=/^[ \t\f\r]+$/,l=/^[\t-\r \u0085\u00A0\u1680\u180E\u2000-\u200B\u2028\u2029\u202F\u205F\u3000]+$/,m=/^[\t \u00A0\u1680\u180E\u2000-\u200B\u202F\u205F\u3000]+$/,n=/^[\n-\r\u0085\u2028\u2029]$/,o="en",p=a.Selection.isDirectionBackward,q=!1,r=!1,s=!0;(function(){var b=document.createElement("div");b.contentEditable="true",b.innerHTML="<p>1 </p><p></p>";var c=i(document),d=b.firstChild,e=a.getSelection();c.appendChild(b),e.collapse(d.lastChild,2),e.setStart(d.firstChild,0),q=(""+e).length==1,b.innerHTML="1 <br>",e.collapse(b,2),e.setStart(b.firstChild,0),r=(""+e).length==1,c.removeChild(b),e.removeAllRanges()})();var u={includeBlockContentTrailingSpace:!0,includeSpaceBeforeBr:!0,includePreLineTrailingSpace:!0},v={includeBlockContentTrailingSpace:!s,includeSpaceBeforeBr:!r,includePreLineTrailingSpace:!0},w={en:{wordRegex:/[a-z0-9]+('[a-z0-9]+)*/gi,includeTrailingSpace:!1,tokenizer:t}},B={caseSensitive:!1,withinRange:null,wholeWordsOnly:!1,wrap:!1,direction:"forward",wordOptions:null,characterOptions:null},C={wordOptions:null,characterOptions:null},D={wordOptions:null,characterOptions:null,trim:!1,trimStart:!0,trimEnd:!0},E={wordOptions:null,characterOptions:null,direction:"forward"},F=f.getComputedStyleProperty,G;(function(){var a=document.createElement("table"),b=i(document);b.appendChild(a),G=F(a,"display")=="block",b.removeChild(a)})(),a.features.tableCssDisplayBlock=G;var H={table:"table",caption:"table-caption",colgroup:"table-column-group",col:"table-column",thead:"table-header-group",tbody:"table-row-group",tfoot:"table-footer-group",tr:"table-row",td:"table-cell",th:"table-cell"};Z.prototype={get:function(a){return this.store.hasOwnProperty(a)?this.store[a]:null},set:function(a,b){return this.store[a]=b}};var $=0,_=0;a.report=function(){console.log("Cached: "+$+", uncached: "+_)};var cb={getPosition:function(a){var b=this.positions;return b.get(a)||b.set(a,new kb(this,a))},toString:function(){return"[NodeWrapper("+f.inspectNode(this.node)+")]"}};bb.prototype=cb;var db="EMPTY",eb="NON_SPACE",fb="UNCOLLAPSIBLE_SPACE",gb="COLLAPSIBLE_SPACE",hb="TRAILING_SPACE_IN_BLOCK",ib="TRAILING_SPACE_BEFORE_BR",jb="PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK";h(cb,{isCharacterDataNode:ab("isCharacterDataNode",f.isCharacterDataNode,"node"),getNodeIndex:ab("nodeIndex",f.getNodeIndex,"node"),getLength:ab("nodeLength",f.getNodeLength,"node"),containsPositions:ab("containsPositions",N,"node"),isWhitespace:ab("isWhitespace",V,"node"),isCollapsedWhitespace:ab("isCollapsedWhitespace",W,"node"),getComputedDisplay:ab("computedDisplay",I,"node"),isCollapsed:ab("collapsed",X,"node"),isIgnored:ab("ignored",Y,"node"),next:ab("nextPos",T,"node"),previous:ab("previous",U,"node"),getTextNodeInfo:ab("textNodeInfo",function(a){var b=null,c=!1,d=F(a.parentNode,"whiteSpace"),e=d=="pre-line";if(e)b=k,c=!0;else if(d=="normal"||d=="nowrap")b=j,c=!0;return{node:a,text:a.data,spaceRegex:b,collapseSpaces:c,preLine:e}},"node"),hasInnerText:ab("hasInnerText",function(a,b){var c=this.session,d=c.getPosition(a.parentNode,this.getNodeIndex()+1),e=c.getPosition(a,0),f=b?d:e,g=b?e:d;while(f!==g){f.prepopulateChar();if(f.isDefinitelyNonEmpty())return!0;f=b?f.previousVisible():f.nextVisible()}return!1},"node"),getTrailingSpace:ab("trailingSpace",function(a){if(a.tagName.toLowerCase()=="br")return"";switch(this.getComputedDisplay()){case"inline":var b=a.lastChild;while(b){if(!Y(b))return b.nodeType==1?this.session.getNodeWrapper(b).getTrailingSpace():"";b=b.previousSibling}break;case"inline-block":case"inline-table":case"none":case"table-column":case"table-column-group":break;case"table-cell":return" ";default:return this.hasInnerText(!0)?"\n":""}return""},"node"),getLeadingSpace:ab("leadingSpace",function(a){switch(this.getComputedDisplay()){case"inline":case"inline-block":case"inline-table":case"none":case"table-column":case"table-column-group":case"table-cell":break;default:return this.hasInnerText(!1)?"\n":""}return""},"node")});var mb={character:"",characterType:db,isBr:!1,prepopulateChar:function(){var a=this;if(!a.prepopulatedChar){var b=a.node,c=a.offset,d="",e=db,f=!1;if(c>0)if(b.nodeType==3){var g=b.data,h=g.charAt(c-1),i=a.nodeWrapper.getTextNodeInfo(),j=i.spaceRegex;i.collapseSpaces?j.test(h)?c>1&&j.test(g.charAt(c-2))||(i.preLine&&g.charAt(c)==="\n"?(d=" ",e=jb):(d=" ",e=gb)):(d=h,e=eb,f=!0):(d=h,e=fb,f=!0)}else{var k=b.childNodes[c-1];k&&k.nodeType==1&&!X(k)&&(k.tagName.toLowerCase()=="br"?(d="\n",a.isBr=!0,e=gb,f=!1):a.checkForTrailingSpace=!0);if(!d){var l=b.childNodes[c];l&&l.nodeType==1&&!X(l)&&(a.checkForLeadingSpace=!0)}}a.prepopulatedChar=!0,a.character=d,a.characterType=e,a.isCharInvariant=f}},isDefinitelyNonEmpty:function(){var a=this.characterType;return a==eb||a==fb},resolveLeadingAndTrailingSpaces:function(){this.prepopulatedChar||this.prepopulateChar();if(this.checkForTrailingSpace){var a=this.session.getNodeWrapper(this.node.childNodes[this.offset-1]).getTrailingSpace();a&&(this.isTrailingSpace=!0,this.character=a,this.characterType=gb),this.checkForTrailingSpace=!1}if(this.checkForLeadingSpace){var b=this.session.getNodeWrapper(this.node.childNodes[this.offset]).getLeadingSpace();b&&(this.isLeadingSpace=!0,this.character=b,this.characterType=gb),this.checkForLeadingSpace=!1}},getPrecedingUncollapsedPosition:function(a){var b=this,c;while(b=b.previousVisible()){c=b.getCharacter(a);if(c!=="")return b}return null},getCharacter:function(a){function j(){return h||(g=i.getPrecedingUncollapsedPosition(a),h=!0),g}this.resolveLeadingAndTrailingSpaces();if(this.isCharInvariant)return this.character;var b=["character",a.includeSpaceBeforeBr,a.includeBlockContentTrailingSpace,a.includePreLineTrailingSpace].join("_"),c=this.cache.get(b);if(c!==null)return c;var d="",e=this.characterType==gb,f,g,h=!1,i=this;if(e){if(this.character!=" "||!!j()&&!g.isTrailingSpace&&g.character!="\n")if(this.character=="\n"&&this.isLeadingSpace)j()&&g.character!="\n"&&(d="\n");else{f=this.nextUncollapsed();if(f){f.isBr?this.type=ib:f.isTrailingSpace&&f.character=="\n"&&(this.type=hb);if(f.character==="\n"){if(this.type!=ib||!!a.includeSpaceBeforeBr)if(this.type!=hb||!f.isTrailingSpace||!!a.includeBlockContentTrailingSpace)if(this.type!=jb||f.type!=eb||!!a.includePreLineTrailingSpace)this.character==="\n"?f.isTrailingSpace?this.isTrailingSpace||!this.isBr:d="\n":this.character===" "&&(d=" ")}else d=this.character}}}else this.character!=="\n"||!!(f=this.nextUncollapsed())&&!f.isTrailingSpace;return this.cache.set(b,d),d},equals:function(a){return!!a&&this.node===a.node&&this.offset===a.offset},inspect:lb,toString:function(){return this.character}};kb.prototype=mb,h(mb,{next:ab("nextPos",function(a){var b=a.nodeWrapper,c=a.node,d=a.offset,e=b.session;if(!c)return null;var f,g,h;return d==b.getLength()?(f=c.parentNode,g=f?b.getNodeIndex()+1:0):b.isCharacterDataNode()?(f=c,g=d+1):(h=c.childNodes[d],e.getNodeWrapper(h).containsPositions()?(f=h,g=0):(f=c,g=d+1)),f?e.getPosition(f,g):null}),previous:ab("previous",function(a){var b=a.nodeWrapper,c=a.node,d=a.offset,e=b.session,g,h,i;return d==0?(g=c.parentNode,h=g?b.getNodeIndex():0):b.isCharacterDataNode()?(g=c,h=d-1):(i=c.childNodes[d-1],e.getNodeWrapper(i).containsPositions()?(g=i,h=f.getNodeLength(i)):(g=c,h=d-1)),g?e.getPosition(g,h):null}),nextVisible:ab("nextVisible",function(a){var b=a.next();if(!b)return null;var c=b.nodeWrapper,d=b.node,e=b;return c.isCollapsed()&&(e=c.session.getPosition(d.parentNode,c.getNodeIndex()+1)),e}),nextUncollapsed:ab("nextUncollapsed",function(a){var b=a;while(b=b.nextVisible()){b.resolveLeadingAndTrailingSpaces();if(b.character!=="")return b}return null}),previousVisible:ab("previousVisible",function(a){var b=a.previous();if(!b)return null;var c=b.nodeWrapper,d=b.node,e=b;return c.isCollapsed()&&(e=c.session.getPosition(d.parentNode,c.getNodeIndex())),e})});var nb=null,ob=function(){function a(a){var b=new Z;return{get:function(c){var d=b.get(c[a]);if(d)for(var e=0,f;f=d[e++];)if(f.node===c)return f;return null},set:function(c){var d=c.node[a],e=b.get(d)||b.set(d,[]);e.push(c)}}}function c(){this.initCaches()}var b=g.isHostProperty(document.documentElement,"uniqueID");return c.prototype={initCaches:function(){this.elementCache=b?function(){var a=new Z;return{get:function(b){return a.get(b.uniqueID)},set:function(b){a.set(b.node.uniqueID,b)}}}():a("tagName"),this.textNodeCache=a("data"),this.otherNodeCache=a("nodeName")},getNodeWrapper:function(a){var b;switch(a.nodeType){case 1:b=this.elementCache;break;case 3:b=this.textNodeCache;break;default:b=this.otherNodeCache}var c=b.get(a);return c||(c=new bb(a,this),b.set(c)),c},getPosition:function(a,b){return this.getNodeWrapper(a).getPosition(b)},getRangeBoundaryPosition:function(a,b){var c=b?"start":"end";return this.getPosition(a[c+"Container"],a[c+"Offset"])},detach:function(){this.elementCache=this.textNodeCache=this.otherNodeCache=null}},c}();h(f,{nextNode:T,previousNode:U});var tb=Array.prototype.indexOf?function(a,b){return a.indexOf(b)}:function(a,b){for(var c=0,d=a.length;c<d;++c)if(a[c]===b)return c;return-1};h(a.rangePrototype,{moveStart:Bb(!0,!1),moveEnd:Bb(!1,!1),move:Bb(!0,!0),trimStart:Cb(!0),trimEnd:Cb(!1),trim:Ab(function(a,b){var c=this.trimStart(b),d=this.trimEnd(b);return c||d}),expand:Ab(function(a,b,c){var f=!1;c=x(c,D);var g=z(c.characterOptions);b||(b=d);if(b==e){var h=y(c.wordOptions),i=a.getRangeBoundaryPosition(this,!0),j=a.getRangeBoundaryPosition(this,!1),k=ub(i,g,h),l=k.nextEndToken(),m=l.chars[0].previousVisible(),n,o;if(this.collapsed)n=l;else{var p=ub(j,g,h);n=p.previousStartToken()}return o=n.chars[n.chars.length-1],m.equals(i)||(this.setStart(m.node,m.offset),f=!0),o&&!o.equals(j)&&(this.setEnd(o.node,o.offset),f=!0),c.trim&&(c.trimStart&&(f=this.trimStart(g)||f),c.trimEnd&&(f=this.trimEnd(g)||f)),f}return this.moveEnd(d,1,c)}),text:Ab(function(a,b){return this.collapsed?"":xb(a,this,z(b)).join("")}),selectCharacters:Ab(function(a,b,c,d,e){var f={characterOptions:e};b||(b=i(this.getDocument())),this.selectNodeContents(b),this.collapse(!0),this.moveStart("character",c,f),this.collapse(!0),this.moveEnd("character",d-c,f)}),toCharacterRange:Ab(function(a,b,c){b||(b=i(this.getDocument()));var d=b.parentNode,e=f.getNodeIndex(b),g=f.comparePoints(this.startContainer,this.endContainer,d,e)==-1,h=this.cloneRange(),j,k;return g?(h.setStartAndEnd(this.startContainer,this.startOffset,d,e),j=-h.text(c).length):(h.setStartAndEnd(d,e,this.startContainer,this.startOffset),j=h.text(c).length),k=j+this.text(c).length,{start:j,end:k}}),findText:Ab(function(b,c,d){d=x(d,B),d.wholeWordsOnly&&(d.wordOptions=y(d.wordOptions),d.wordOptions.includeTrailingSpace=!1);var e=p(d.direction),f=d.withinRange;f||(f=a.createRange(),f.selectNodeContents(this.getDocument()));var g=c,h=!1;typeof g=="string"?d.caseSensitive||(g=g.toLowerCase()):h=!0;var i=b.getRangeBoundaryPosition(this,!e),j=f.comparePoint(i.node,i.offset);j===-1?i=b.getRangeBoundaryPosition(f,!0):j===1&&(i=b.getRangeBoundaryPosition(f,!1));var k=i,l=!1,m;for(;;){m=zb(k,g,h,f,d);if(m){if(m.valid)return this.setStartAndEnd(m.startPos.node,m.startPos.offset,m.endPos.node,m.endPos.offset),!0;k=e?m.startPos:m.endPos}else{if(!d.wrap||!!l)return!1;f=f.cloneRange(),k=b.getRangeBoundaryPosition(f,!e),f.setBoundary(i.node,i.offset,e),l=!0}}}),pasteHtml:function(a){this.deleteContents();if(a){var b=this.createContextualFragment(a),c=b.lastChild;this.insertNode(b),this.collapseAfter(c)}}}),h(a.selectionPrototype,{expand:Ab(function(a,b,c){this.changeEachRange(function(a){a.expand(b,c)})}),move:Ab(function(a,b,c,d){var e=0;if(this.focusNode){this.collapse(this.focusNode,this.focusOffset);var f=this.getRangeAt(0);d||(d={}),d.characterOptions=A(d.characterOptions),e=f.move(b,c,d),this.setSingleRange(f)}return e}),trimStart:Db("trimStart"),trimEnd:Db("trimEnd"),trim:Db("trim"),selectCharacters:Ab(function(b,c,d,e,f,g){var h=a.createRange(c);h.selectCharacters(c,d,e,g),this.setSingleRange(h,f)}),saveCharacterRanges:Ab(function(a,b,c){var d=this.getAllRanges(),e=d.length,f=[],g=e==1&&this.isBackward();for(var h=0,i=d.length;h<i;++h)f[h]={characterRange:d[h].toCharacterRange(b,c),backward:g,characterOptions:c};return f}),restoreCharacterRanges:Ab(function(b,c,d){this.removeAllRanges();for(var e=0,f=d.length,g,h,i;e<f;++e)h=d[e],i=h.characterRange,g=a.createRange(c),g.selectCharacters(c,i.start,i.end,h.characterOptions),this.addRange(g,h.backward)}),text:Ab(function(a,b){var c=[];for(var d=0,e=this.rangeCount;d<e;++d)c[d]=this.getRangeAt(d).text(b);return c.join("")})}),a.innerText=function(b,c){var d=a.createRange(b);d.selectNodeContents(b);var e=d.text(c);return d.detach(),e},a.createWordIterator=function(a,b,c){var d=qb();c=x(c,E);var e=z(c.characterOptions),f=y(c.wordOptions),g=d.getPosition(a,b),h=ub(g,e,f),i=p(c.direction);return{next:function(){return i?h.previousStartToken():h.nextEndToken()},dispose:function(){h.dispose(),this.next=function(){}}}},a.noMutation=function(a){var b=qb();a(b),rb()},a.noMutation.createEntryPointFunction=Ab,a.textRange={isBlockNode:L,isCollapsedWhitespaceNode:W,createPosition:Ab(function(a,b,c){return a.getPosition(b,c)})}})