| // Copyright 2008 The Closure Library Authors. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS-IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| /** |
| * @fileoverview Iterator subclass for DOM tree traversal. |
| * |
| * @author robbyw@google.com (Robby Walker) |
| */ |
| |
| goog.provide('goog.dom.TagIterator'); |
| goog.provide('goog.dom.TagWalkType'); |
| |
| goog.require('goog.dom'); |
| goog.require('goog.dom.NodeType'); |
| goog.require('goog.iter.Iterator'); |
| goog.require('goog.iter.StopIteration'); |
| |
| |
| /** |
| * There are three types of token: |
| * <ol> |
| * <li>{@code START_TAG} - The beginning of a tag. |
| * <li>{@code OTHER} - Any non-element node position. |
| * <li>{@code END_TAG} - The end of a tag. |
| * </ol> |
| * Users of this enumeration can rely on {@code START_TAG + END_TAG = 0} and |
| * that {@code OTHER = 0}. |
| * |
| * @enum {number} |
| */ |
| goog.dom.TagWalkType = { |
| START_TAG: 1, |
| OTHER: 0, |
| END_TAG: -1 |
| }; |
| |
| |
| |
| /** |
| * A DOM tree traversal iterator. |
| * |
| * Starting with the given node, the iterator walks the DOM in order, reporting |
| * events for the start and end of Elements, and the presence of text nodes. For |
| * example: |
| * |
| * <pre> |
| * <div>1<span>2</span>3</div> |
| * </pre> |
| * |
| * Will return the following nodes: |
| * |
| * <code>[div, 1, span, 2, span, 3, div]</code> |
| * |
| * With the following states: |
| * |
| * <code>[START, OTHER, START, OTHER, END, OTHER, END]</code> |
| * |
| * And the following depths |
| * |
| * <code>[1, 1, 2, 2, 1, 1, 0]</code> |
| * |
| * Imagining <code>|</code> represents iterator position, the traversal stops at |
| * each of the following locations: |
| * |
| * <pre> |
| * <div>|1|<span>|2|</span>|3|</div>| |
| * </pre> |
| * |
| * The iterator can also be used in reverse mode, which will return the nodes |
| * and states in the opposite order. The depths will be slightly different |
| * since, like in normal mode, the depth is computed *after* the given node. |
| * |
| * Lastly, it is possible to create an iterator that is unconstrained, meaning |
| * that it will continue iterating until the end of the document instead of |
| * until exiting the start node. |
| * |
| * @param {Node=} opt_node The start node. If unspecified or null, defaults to |
| * an empty iterator. |
| * @param {boolean=} opt_reversed Whether to traverse the tree in reverse. |
| * @param {boolean=} opt_unconstrained Whether the iterator is not constrained |
| * to the starting node and its children. |
| * @param {goog.dom.TagWalkType?=} opt_tagType The type of the position. |
| * Defaults to the start of the given node for forward iterators, and |
| * the end of the node for reverse iterators. |
| * @param {number=} opt_depth The starting tree depth. |
| * @constructor |
| * @extends {goog.iter.Iterator<Node>} |
| */ |
| goog.dom.TagIterator = function(opt_node, opt_reversed, |
| opt_unconstrained, opt_tagType, opt_depth) { |
| this.reversed = !!opt_reversed; |
| if (opt_node) { |
| this.setPosition(opt_node, opt_tagType); |
| } |
| this.depth = opt_depth != undefined ? opt_depth : this.tagType || 0; |
| if (this.reversed) { |
| this.depth *= -1; |
| } |
| this.constrained = !opt_unconstrained; |
| }; |
| goog.inherits(goog.dom.TagIterator, goog.iter.Iterator); |
| |
| |
| /** |
| * The node this position is located on. |
| * @type {Node} |
| */ |
| goog.dom.TagIterator.prototype.node = null; |
| |
| |
| /** |
| * The type of this position. |
| * @type {goog.dom.TagWalkType} |
| */ |
| goog.dom.TagIterator.prototype.tagType = goog.dom.TagWalkType.OTHER; |
| |
| |
| /** |
| * The tree depth of this position relative to where the iterator started. The |
| * depth is considered to be the tree depth just past the current node, so if an |
| * iterator is at position <pre> |
| * <div>|</div> |
| * </pre> |
| * (i.e. the node is the div and the type is START_TAG) its depth will be 1. |
| * @type {number} |
| */ |
| goog.dom.TagIterator.prototype.depth; |
| |
| |
| /** |
| * Whether the node iterator is moving in reverse. |
| * @type {boolean} |
| */ |
| goog.dom.TagIterator.prototype.reversed; |
| |
| |
| /** |
| * Whether the iterator is constrained to the starting node and its children. |
| * @type {boolean} |
| */ |
| goog.dom.TagIterator.prototype.constrained; |
| |
| |
| /** |
| * Whether iteration has started. |
| * @type {boolean} |
| * @private |
| */ |
| goog.dom.TagIterator.prototype.started_ = false; |
| |
| |
| /** |
| * Set the position of the iterator. Overwrite the tree node and the position |
| * type which can be one of the {@link goog.dom.TagWalkType} token types. |
| * Only overwrites the tree depth when the parameter is specified. |
| * @param {Node} node The node to set the position to. |
| * @param {goog.dom.TagWalkType?=} opt_tagType The type of the position |
| * Defaults to the start of the given node. |
| * @param {number=} opt_depth The tree depth. |
| */ |
| goog.dom.TagIterator.prototype.setPosition = function(node, |
| opt_tagType, opt_depth) { |
| this.node = node; |
| |
| if (node) { |
| if (goog.isNumber(opt_tagType)) { |
| this.tagType = opt_tagType; |
| } else { |
| // Auto-determine the proper type |
| this.tagType = this.node.nodeType != goog.dom.NodeType.ELEMENT ? |
| goog.dom.TagWalkType.OTHER : |
| this.reversed ? goog.dom.TagWalkType.END_TAG : |
| goog.dom.TagWalkType.START_TAG; |
| } |
| } |
| |
| if (goog.isNumber(opt_depth)) { |
| this.depth = opt_depth; |
| } |
| }; |
| |
| |
| /** |
| * Replace this iterator's values with values from another. The two iterators |
| * must be of the same type. |
| * @param {goog.dom.TagIterator} other The iterator to copy. |
| * @protected |
| */ |
| goog.dom.TagIterator.prototype.copyFrom = function(other) { |
| this.node = other.node; |
| this.tagType = other.tagType; |
| this.depth = other.depth; |
| this.reversed = other.reversed; |
| this.constrained = other.constrained; |
| }; |
| |
| |
| /** |
| * @return {!goog.dom.TagIterator} A copy of this iterator. |
| */ |
| goog.dom.TagIterator.prototype.clone = function() { |
| return new goog.dom.TagIterator(this.node, this.reversed, |
| !this.constrained, this.tagType, this.depth); |
| }; |
| |
| |
| /** |
| * Skip the current tag. |
| */ |
| goog.dom.TagIterator.prototype.skipTag = function() { |
| var check = this.reversed ? goog.dom.TagWalkType.END_TAG : |
| goog.dom.TagWalkType.START_TAG; |
| if (this.tagType == check) { |
| this.tagType = /** @type {goog.dom.TagWalkType} */ (check * -1); |
| this.depth += this.tagType * (this.reversed ? -1 : 1); |
| } |
| }; |
| |
| |
| /** |
| * Restart the current tag. |
| */ |
| goog.dom.TagIterator.prototype.restartTag = function() { |
| var check = this.reversed ? goog.dom.TagWalkType.START_TAG : |
| goog.dom.TagWalkType.END_TAG; |
| if (this.tagType == check) { |
| this.tagType = /** @type {goog.dom.TagWalkType} */ (check * -1); |
| this.depth += this.tagType * (this.reversed ? -1 : 1); |
| } |
| }; |
| |
| |
| /** |
| * Move to the next position in the DOM tree. |
| * @return {Node} Returns the next node, or throws a goog.iter.StopIteration |
| * exception if the end of the iterator's range has been reached. |
| * @override |
| */ |
| goog.dom.TagIterator.prototype.next = function() { |
| var node; |
| |
| if (this.started_) { |
| if (!this.node || this.constrained && this.depth == 0) { |
| throw goog.iter.StopIteration; |
| } |
| node = this.node; |
| |
| var startType = this.reversed ? goog.dom.TagWalkType.END_TAG : |
| goog.dom.TagWalkType.START_TAG; |
| |
| if (this.tagType == startType) { |
| // If we have entered the tag, test if there are any children to move to. |
| var child = this.reversed ? node.lastChild : node.firstChild; |
| if (child) { |
| this.setPosition(child); |
| } else { |
| // If not, move on to exiting this tag. |
| this.setPosition(node, |
| /** @type {goog.dom.TagWalkType} */ (startType * -1)); |
| } |
| } else { |
| var sibling = this.reversed ? node.previousSibling : node.nextSibling; |
| if (sibling) { |
| // Try to move to the next node. |
| this.setPosition(sibling); |
| } else { |
| // If no such node exists, exit our parent. |
| this.setPosition(node.parentNode, |
| /** @type {goog.dom.TagWalkType} */ (startType * -1)); |
| } |
| } |
| |
| this.depth += this.tagType * (this.reversed ? -1 : 1); |
| } else { |
| this.started_ = true; |
| } |
| |
| // Check the new position for being last, and return it if it's not. |
| node = this.node; |
| if (!this.node) { |
| throw goog.iter.StopIteration; |
| } |
| return node; |
| }; |
| |
| |
| /** |
| * @return {boolean} Whether next has ever been called on this iterator. |
| * @protected |
| */ |
| goog.dom.TagIterator.prototype.isStarted = function() { |
| return this.started_; |
| }; |
| |
| |
| /** |
| * @return {boolean} Whether this iterator's position is a start tag position. |
| */ |
| goog.dom.TagIterator.prototype.isStartTag = function() { |
| return this.tagType == goog.dom.TagWalkType.START_TAG; |
| }; |
| |
| |
| /** |
| * @return {boolean} Whether this iterator's position is an end tag position. |
| */ |
| goog.dom.TagIterator.prototype.isEndTag = function() { |
| return this.tagType == goog.dom.TagWalkType.END_TAG; |
| }; |
| |
| |
| /** |
| * @return {boolean} Whether this iterator's position is not at an element node. |
| */ |
| goog.dom.TagIterator.prototype.isNonElement = function() { |
| return this.tagType == goog.dom.TagWalkType.OTHER; |
| }; |
| |
| |
| /** |
| * Test if two iterators are at the same position - i.e. if the node and tagType |
| * is the same. This will still return true if the two iterators are moving in |
| * opposite directions or have different constraints. |
| * @param {goog.dom.TagIterator} other The iterator to compare to. |
| * @return {boolean} Whether the two iterators are at the same position. |
| */ |
| goog.dom.TagIterator.prototype.equals = function(other) { |
| // Nodes must be equal, and we must either have reached the end of our tree |
| // or be at the same position. |
| return other.node == this.node && (!this.node || |
| other.tagType == this.tagType); |
| }; |
| |
| |
| /** |
| * Replace the current node with the list of nodes. Reset the iterator so that |
| * it visits the first of the nodes next. |
| * @param {...Object} var_args A list of nodes to replace the current node with. |
| * If the first argument is array-like, it will be used, otherwise all the |
| * arguments are assumed to be nodes. |
| */ |
| goog.dom.TagIterator.prototype.splice = function(var_args) { |
| // Reset the iterator so that it iterates over the first replacement node in |
| // the arguments on the next iteration. |
| var node = this.node; |
| this.restartTag(); |
| this.reversed = !this.reversed; |
| goog.dom.TagIterator.prototype.next.call(this); |
| this.reversed = !this.reversed; |
| |
| // Replace the node with the arguments. |
| var arr = goog.isArrayLike(arguments[0]) ? arguments[0] : arguments; |
| for (var i = arr.length - 1; i >= 0; i--) { |
| goog.dom.insertSiblingAfter(arr[i], node); |
| } |
| goog.dom.removeNode(node); |
| }; |