textrangeiterator.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. // Copyright 2007 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview Iterator between two DOM text range positions.
  16. *
  17. * @author robbyw@google.com (Robby Walker)
  18. */
  19. goog.provide('goog.dom.TextRangeIterator');
  20. goog.require('goog.array');
  21. goog.require('goog.dom');
  22. goog.require('goog.dom.NodeType');
  23. goog.require('goog.dom.RangeIterator');
  24. goog.require('goog.dom.TagName');
  25. goog.require('goog.iter.StopIteration');
  26. /**
  27. * Subclass of goog.dom.TagIterator that iterates over a DOM range. It
  28. * adds functions to determine the portion of each text node that is selected.
  29. *
  30. * @param {Node} startNode The starting node position.
  31. * @param {number} startOffset The offset in to startNode. If startNode is
  32. * an element, indicates an offset in to childNodes. If startNode is a
  33. * text node, indicates an offset in to nodeValue.
  34. * @param {Node} endNode The ending node position.
  35. * @param {number} endOffset The offset in to endNode. If endNode is
  36. * an element, indicates an offset in to childNodes. If endNode is a
  37. * text node, indicates an offset in to nodeValue.
  38. * @param {boolean=} opt_reverse Whether to traverse nodes in reverse.
  39. * @constructor
  40. * @extends {goog.dom.RangeIterator}
  41. * @final
  42. */
  43. goog.dom.TextRangeIterator = function(
  44. startNode, startOffset, endNode, endOffset, opt_reverse) {
  45. /**
  46. * The first node in the selection.
  47. * @private {Node}
  48. */
  49. this.startNode_ = null;
  50. /**
  51. * The last node in the selection.
  52. * @private {Node}
  53. */
  54. this.endNode_ = null;
  55. /**
  56. * The offset within the first node in the selection.
  57. * @private {number}
  58. */
  59. this.startOffset_ = 0;
  60. /**
  61. * The offset within the last node in the selection.
  62. * @private {number}
  63. */
  64. this.endOffset_ = 0;
  65. var goNext;
  66. if (startNode) {
  67. this.startNode_ = startNode;
  68. this.startOffset_ = startOffset;
  69. this.endNode_ = endNode;
  70. this.endOffset_ = endOffset;
  71. // Skip to the offset nodes - being careful to special case BRs since these
  72. // have no children but still can appear as the startContainer of a range.
  73. if (startNode.nodeType == goog.dom.NodeType.ELEMENT &&
  74. /** @type {!Element} */ (startNode).tagName != goog.dom.TagName.BR) {
  75. var startChildren = startNode.childNodes;
  76. var candidate = startChildren[startOffset];
  77. if (candidate) {
  78. this.startNode_ = candidate;
  79. this.startOffset_ = 0;
  80. } else {
  81. if (startChildren.length) {
  82. this.startNode_ =
  83. /** @type {Node} */ (goog.array.peek(startChildren));
  84. }
  85. goNext = true;
  86. }
  87. }
  88. if (endNode.nodeType == goog.dom.NodeType.ELEMENT) {
  89. this.endNode_ = endNode.childNodes[endOffset];
  90. if (this.endNode_) {
  91. this.endOffset_ = 0;
  92. } else {
  93. // The offset was past the last element.
  94. this.endNode_ = endNode;
  95. }
  96. }
  97. }
  98. goog.dom.TextRangeIterator.base(
  99. this, 'constructor', opt_reverse ? this.endNode_ : this.startNode_,
  100. opt_reverse);
  101. if (goNext) {
  102. try {
  103. this.next();
  104. } catch (e) {
  105. if (e != goog.iter.StopIteration) {
  106. throw e;
  107. }
  108. }
  109. }
  110. };
  111. goog.inherits(goog.dom.TextRangeIterator, goog.dom.RangeIterator);
  112. /** @override */
  113. goog.dom.TextRangeIterator.prototype.getStartTextOffset = function() {
  114. // Offsets only apply to text nodes. If our current node is the start node,
  115. // return the saved offset. Otherwise, return 0.
  116. return this.node.nodeType != goog.dom.NodeType.TEXT ?
  117. -1 :
  118. this.node == this.startNode_ ? this.startOffset_ : 0;
  119. };
  120. /** @override */
  121. goog.dom.TextRangeIterator.prototype.getEndTextOffset = function() {
  122. // Offsets only apply to text nodes. If our current node is the end node,
  123. // return the saved offset. Otherwise, return the length of the node.
  124. return this.node.nodeType != goog.dom.NodeType.TEXT ?
  125. -1 :
  126. this.node == this.endNode_ ? this.endOffset_ : this.node.nodeValue.length;
  127. };
  128. /** @override */
  129. goog.dom.TextRangeIterator.prototype.getStartNode = function() {
  130. return this.startNode_;
  131. };
  132. /**
  133. * Change the start node of the iterator.
  134. * @param {Node} node The new start node.
  135. */
  136. goog.dom.TextRangeIterator.prototype.setStartNode = function(node) {
  137. if (!this.isStarted()) {
  138. this.setPosition(node);
  139. }
  140. this.startNode_ = node;
  141. this.startOffset_ = 0;
  142. };
  143. /** @override */
  144. goog.dom.TextRangeIterator.prototype.getEndNode = function() {
  145. return this.endNode_;
  146. };
  147. /**
  148. * Change the end node of the iterator.
  149. * @param {Node} node The new end node.
  150. */
  151. goog.dom.TextRangeIterator.prototype.setEndNode = function(node) {
  152. this.endNode_ = node;
  153. this.endOffset_ = 0;
  154. };
  155. /** @override */
  156. goog.dom.TextRangeIterator.prototype.isLast = function() {
  157. return this.isStarted() && this.node == this.endNode_ &&
  158. (!this.endOffset_ || !this.isStartTag());
  159. };
  160. /**
  161. * Move to the next position in the selection.
  162. * Throws {@code goog.iter.StopIteration} when it passes the end of the range.
  163. * @return {Node} The node at the next position.
  164. * @override
  165. */
  166. goog.dom.TextRangeIterator.prototype.next = function() {
  167. if (this.isLast()) {
  168. throw goog.iter.StopIteration;
  169. }
  170. // Call the super function.
  171. return goog.dom.TextRangeIterator.superClass_.next.call(this);
  172. };
  173. /** @override */
  174. goog.dom.TextRangeIterator.prototype.skipTag = function() {
  175. goog.dom.TextRangeIterator.superClass_.skipTag.apply(this);
  176. // If the node we are skipping contains the end node, we just skipped past
  177. // the end, so we stop the iteration.
  178. if (goog.dom.contains(this.node, this.endNode_)) {
  179. throw goog.iter.StopIteration;
  180. }
  181. };
  182. /** @override */
  183. goog.dom.TextRangeIterator.prototype.copyFrom = function(other) {
  184. this.startNode_ = other.startNode_;
  185. this.endNode_ = other.endNode_;
  186. this.startOffset_ = other.startOffset_;
  187. this.endOffset_ = other.endOffset_;
  188. this.isReversed_ = other.isReversed_;
  189. goog.dom.TextRangeIterator.superClass_.copyFrom.call(this, other);
  190. };
  191. /**
  192. * @return {!goog.dom.TextRangeIterator} An identical iterator.
  193. * @override
  194. */
  195. goog.dom.TextRangeIterator.prototype.clone = function() {
  196. var copy = new goog.dom.TextRangeIterator(
  197. this.startNode_, this.startOffset_, this.endNode_, this.endOffset_,
  198. this.isReversed_);
  199. copy.copyFrom(this);
  200. return copy;
  201. };