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

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