http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/dom/pattern/nodetype.js ---------------------------------------------------------------------- diff --git a/externs/GCL/externs/goog/dom/pattern/nodetype.js b/externs/GCL/externs/goog/dom/pattern/nodetype.js new file mode 100644 index 0000000..a12c9a1 --- /dev/null +++ b/externs/GCL/externs/goog/dom/pattern/nodetype.js @@ -0,0 +1,59 @@ +// 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 DOM pattern to match a node of the given type. + * + * @author [email protected] (Robby Walker) + */ + +goog.provide('goog.dom.pattern.NodeType'); + +goog.require('goog.dom.pattern.AbstractPattern'); +goog.require('goog.dom.pattern.MatchType'); + + + +/** + * Pattern object that matches any node of the given type. + * @param {goog.dom.NodeType} nodeType The node type to match. + * @constructor + * @extends {goog.dom.pattern.AbstractPattern} + * @final + */ +goog.dom.pattern.NodeType = function(nodeType) { + /** + * The node type to match. + * @type {goog.dom.NodeType} + * @private + */ + this.nodeType_ = nodeType; +}; +goog.inherits(goog.dom.pattern.NodeType, goog.dom.pattern.AbstractPattern); + + +/** + * Test whether the given token is a text token which matches the string or + * regular expression provided in the constructor. + * @param {Node} token Token to match against. + * @param {goog.dom.TagWalkType} type The type of token. + * @return {goog.dom.pattern.MatchType} <code>MATCH</code> if the pattern + * matches, <code>NO_MATCH</code> otherwise. + * @override + */ +goog.dom.pattern.NodeType.prototype.matchToken = function(token, type) { + return token.nodeType == this.nodeType_ ? + goog.dom.pattern.MatchType.MATCH : + goog.dom.pattern.MatchType.NO_MATCH; +};
http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/dom/pattern/pattern.js ---------------------------------------------------------------------- diff --git a/externs/GCL/externs/goog/dom/pattern/pattern.js b/externs/GCL/externs/goog/dom/pattern/pattern.js new file mode 100644 index 0000000..19f4d1b --- /dev/null +++ b/externs/GCL/externs/goog/dom/pattern/pattern.js @@ -0,0 +1,93 @@ +// Copyright 2007 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 DOM patterns. Allows for description of complex DOM patterns + * using regular expression like constructs. + * + * @author [email protected] (Robby Walker) + */ + +goog.provide('goog.dom.pattern'); +goog.provide('goog.dom.pattern.MatchType'); + + +/** + * Regular expression for breaking text nodes. + * @type {RegExp} + */ +goog.dom.pattern.BREAKING_TEXTNODE_RE = /^\s*$/; + + +/** + * Utility function to match a string against either a string or a regular + * expression. + * + * @param {string|RegExp} obj Either a string or a regular expression. + * @param {string} str The string to match. + * @return {boolean} Whether the strings are equal, or if the string matches + * the regular expression. + */ +goog.dom.pattern.matchStringOrRegex = function(obj, str) { + if (goog.isString(obj)) { + // Match a string + return str == obj; + } else { + // Match a regular expression + return !!(str && str.match(obj)); + } +}; + + +/** + * Utility function to match a DOM attribute against either a string or a + * regular expression. Conforms to the interface spec for + * {@link goog.object#every}. + * + * @param {string|RegExp} elem Either a string or a regular expression. + * @param {string} index The attribute name to match. + * @param {Object} orig The original map of matches to test. + * @return {boolean} Whether the strings are equal, or if the attribute matches + * the regular expression. + * @this {Element} Called using goog.object every on an Element. + */ +goog.dom.pattern.matchStringOrRegexMap = function(elem, index, orig) { + return goog.dom.pattern.matchStringOrRegex(elem, + index in this ? this[index] : + (this.getAttribute ? this.getAttribute(index) : null)); +}; + + +/** + * When matched to a token, a pattern may return any of the following statuses: + * <ol> + * <li><code>NO_MATCH</code> - The pattern does not match. This is the only + * value that evaluates to <code>false</code> in a boolean context. + * <li><code>MATCHING</code> - The token is part of an incomplete match. + * <li><code>MATCH</code> - The token completes a match. + * <li><code>BACKTRACK_MATCH</code> - The token does not match, but indicates + * the end of a repetitive match. For instance, in regular expressions, + * the pattern <code>/a+/</code> would match <code>'aaaaaaaab'</code>. + * Every <code>'a'</code> token would give a status of + * <code>MATCHING</code> while the <code>'b'</code> token would give a + * status of <code>BACKTRACK_MATCH</code>. + * </ol> + * @enum {number} + */ +goog.dom.pattern.MatchType = { + NO_MATCH: 0, + MATCHING: 1, + MATCH: 2, + BACKTRACK_MATCH: 3 +}; http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/dom/pattern/repeat.js ---------------------------------------------------------------------- diff --git a/externs/GCL/externs/goog/dom/pattern/repeat.js b/externs/GCL/externs/goog/dom/pattern/repeat.js new file mode 100644 index 0000000..392a6a6 --- /dev/null +++ b/externs/GCL/externs/goog/dom/pattern/repeat.js @@ -0,0 +1,177 @@ +// Copyright 2007 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 DOM pattern to match a tag and all of its children. + * + * @author [email protected] (Robby Walker) + */ + +goog.provide('goog.dom.pattern.Repeat'); + +goog.require('goog.dom.NodeType'); +goog.require('goog.dom.pattern.AbstractPattern'); +goog.require('goog.dom.pattern.MatchType'); + + + +/** + * Pattern object that matches a repetition of another pattern. + * @param {goog.dom.pattern.AbstractPattern} pattern The pattern to + * repetitively match. + * @param {number=} opt_minimum The minimum number of times to match. Defaults + * to 0. + * @param {number=} opt_maximum The maximum number of times to match. Defaults + * to unlimited. + * @constructor + * @extends {goog.dom.pattern.AbstractPattern} + * @final + */ +goog.dom.pattern.Repeat = function(pattern, + opt_minimum, + opt_maximum) { + /** + * Pattern to repetitively match. + * + * @private {goog.dom.pattern.AbstractPattern} + */ + this.pattern_ = pattern; + + /** + * Minimum number of times to match the pattern. + * + * @private {number} + */ + this.minimum_ = opt_minimum || 0; + + /** + * Optional maximum number of times to match the pattern. A {@code null} value + * will be treated as infinity. + * + * @private {?number} + */ + this.maximum_ = opt_maximum || null; + + /** + * The matched nodes. + * + * @type {Array<Node>} + */ + this.matches = []; + + /** + * Number of times the pattern has matched. + * + * @type {number} + */ + this.count = 0; + + /** + * Whether the pattern has recently matched or failed to match and will need + * to be reset when starting a new round of matches. + * + * @private {boolean} + */ + this.needsReset_ = false; +}; +goog.inherits(goog.dom.pattern.Repeat, goog.dom.pattern.AbstractPattern); + + +/** + * Test whether the given token continues a repeated series of matches of the + * pattern given in the constructor. + * + * @param {Node} token Token to match against. + * @param {goog.dom.TagWalkType} type The type of token. + * @return {goog.dom.pattern.MatchType} <code>MATCH</code> if the pattern + * matches, <code>BACKTRACK_MATCH</code> if the pattern does not match + * but already had accumulated matches, <code>MATCHING</code> if the pattern + * starts a match, and <code>NO_MATCH</code> if the pattern does not match. + * @suppress {missingProperties} See the broken line below. + * @override + */ +goog.dom.pattern.Repeat.prototype.matchToken = function(token, type) { + // Reset if we're starting a new match + if (this.needsReset_) { + this.reset(); + } + + // If the option is set, ignore any whitespace only text nodes + if (token.nodeType == goog.dom.NodeType.TEXT && + token.nodeValue.match(/^\s+$/)) { + return goog.dom.pattern.MatchType.MATCHING; + } + + switch (this.pattern_.matchToken(token, type)) { + case goog.dom.pattern.MatchType.MATCH: + // Record the first token we match. + if (this.count == 0) { + this.matchedNode = token; + } + + // Mark the match + this.count++; + + // Add to the list + this.matches.push(this.pattern_.matchedNode); + + // Check if this match hits our maximum + if (this.maximum_ !== null && this.count == this.maximum_) { + this.needsReset_ = true; + return goog.dom.pattern.MatchType.MATCH; + } else { + return goog.dom.pattern.MatchType.MATCHING; + } + + case goog.dom.pattern.MatchType.MATCHING: + // This can happen when our child pattern is a sequence or a repetition. + return goog.dom.pattern.MatchType.MATCHING; + + case goog.dom.pattern.MatchType.BACKTRACK_MATCH: + // This happens if our child pattern is repetitive too. + // TODO(robbyw): Backtrack further if necessary. + this.count++; + + // NOTE(nicksantos): This line of code is broken. this.patterns_ doesn't + // exist, and this.currentPosition_ doesn't exit. When this is fixed, + // remove the missingProperties suppression above. + if (this.currentPosition_ == this.patterns_.length) { + this.needsReset_ = true; + return goog.dom.pattern.MatchType.BACKTRACK_MATCH; + } else { + // Retry the same token on the next iteration of the child pattern. + return this.matchToken(token, type); + } + + default: + this.needsReset_ = true; + if (this.count >= this.minimum_) { + return goog.dom.pattern.MatchType.BACKTRACK_MATCH; + } else { + return goog.dom.pattern.MatchType.NO_MATCH; + } + } +}; + + +/** + * Reset any internal state this pattern keeps. + * @override + */ +goog.dom.pattern.Repeat.prototype.reset = function() { + this.pattern_.reset(); + this.count = 0; + this.needsReset_ = false; + this.matches.length = 0; +}; http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/dom/pattern/sequence.js ---------------------------------------------------------------------- diff --git a/externs/GCL/externs/goog/dom/pattern/sequence.js b/externs/GCL/externs/goog/dom/pattern/sequence.js new file mode 100644 index 0000000..2282361 --- /dev/null +++ b/externs/GCL/externs/goog/dom/pattern/sequence.js @@ -0,0 +1,135 @@ +// Copyright 2007 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 DOM pattern to match a sequence of other patterns. + * + * @author [email protected] (Robby Walker) + */ + +goog.provide('goog.dom.pattern.Sequence'); + +goog.require('goog.dom.NodeType'); +goog.require('goog.dom.pattern'); +goog.require('goog.dom.pattern.AbstractPattern'); +goog.require('goog.dom.pattern.MatchType'); + + + +/** + * Pattern object that matches a sequence of other patterns. + * + * @param {Array<goog.dom.pattern.AbstractPattern>} patterns Ordered array of + * patterns to match. + * @param {boolean=} opt_ignoreWhitespace Optional flag to ignore text nodes + * consisting entirely of whitespace. The default is to not ignore them. + * @constructor + * @extends {goog.dom.pattern.AbstractPattern} + * @final + */ +goog.dom.pattern.Sequence = function(patterns, opt_ignoreWhitespace) { + /** + * Ordered array of patterns to match. + * + * @type {Array<goog.dom.pattern.AbstractPattern>} + */ + this.patterns = patterns; + + /** + * Whether or not to ignore whitespace only Text nodes. + * + * @private {boolean} + */ + this.ignoreWhitespace_ = !!opt_ignoreWhitespace; + + /** + * Position in the patterns array we have reached by successful matches. + * + * @private {number} + */ + this.currentPosition_ = 0; +}; +goog.inherits(goog.dom.pattern.Sequence, goog.dom.pattern.AbstractPattern); + + +/** + * Test whether the given token starts, continues, or finishes the sequence + * of patterns given in the constructor. + * + * @param {Node} token Token to match against. + * @param {goog.dom.TagWalkType} type The type of token. + * @return {goog.dom.pattern.MatchType} <code>MATCH</code> if the pattern + * matches, <code>MATCHING</code> if the pattern starts a match, and + * <code>NO_MATCH</code> if the pattern does not match. + * @override + */ +goog.dom.pattern.Sequence.prototype.matchToken = function(token, type) { + // If the option is set, ignore any whitespace only text nodes + if (this.ignoreWhitespace_ && token.nodeType == goog.dom.NodeType.TEXT && + goog.dom.pattern.BREAKING_TEXTNODE_RE.test(token.nodeValue)) { + return goog.dom.pattern.MatchType.MATCHING; + } + + switch (this.patterns[this.currentPosition_].matchToken(token, type)) { + case goog.dom.pattern.MatchType.MATCH: + // Record the first token we match. + if (this.currentPosition_ == 0) { + this.matchedNode = token; + } + + // Move forward one position. + this.currentPosition_++; + + // Check if this is the last position. + if (this.currentPosition_ == this.patterns.length) { + this.reset(); + return goog.dom.pattern.MatchType.MATCH; + } else { + return goog.dom.pattern.MatchType.MATCHING; + } + + case goog.dom.pattern.MatchType.MATCHING: + // This can happen when our child pattern is a sequence or a repetition. + return goog.dom.pattern.MatchType.MATCHING; + + case goog.dom.pattern.MatchType.BACKTRACK_MATCH: + // This means a repetitive match succeeded 1 token ago. + // TODO(robbyw): Backtrack further if necessary. + this.currentPosition_++; + + if (this.currentPosition_ == this.patterns.length) { + this.reset(); + return goog.dom.pattern.MatchType.BACKTRACK_MATCH; + } else { + // Retry the same token on the next pattern. + return this.matchToken(token, type); + } + + default: + this.reset(); + return goog.dom.pattern.MatchType.NO_MATCH; + } +}; + + +/** + * Reset any internal state this pattern keeps. + * @override + */ +goog.dom.pattern.Sequence.prototype.reset = function() { + if (this.patterns[this.currentPosition_]) { + this.patterns[this.currentPosition_].reset(); + } + this.currentPosition_ = 0; +}; http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/dom/pattern/starttag.js ---------------------------------------------------------------------- diff --git a/externs/GCL/externs/goog/dom/pattern/starttag.js b/externs/GCL/externs/goog/dom/pattern/starttag.js new file mode 100644 index 0000000..4ce0113 --- /dev/null +++ b/externs/GCL/externs/goog/dom/pattern/starttag.js @@ -0,0 +1,53 @@ +// Copyright 2007 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 DOM pattern to match the start of a tag. + * + * @author [email protected] (Robby Walker) + */ + +goog.provide('goog.dom.pattern.StartTag'); + +goog.require('goog.dom.TagWalkType'); +goog.require('goog.dom.pattern.Tag'); + + + +/** + * Pattern object that matches an opening tag. + * + * @param {string|RegExp} tag Name of the tag. Also will accept a regular + * expression to match against the tag name. + * @param {Object=} opt_attrs Optional map of attribute names to desired values. + * This pattern will only match when all attributes are present and match + * the string or regular expression value provided here. + * @param {Object=} opt_styles Optional map of CSS style names to desired + * values. This pattern will only match when all styles are present and + * match the string or regular expression value provided here. + * @param {Function=} opt_test Optional function that takes the element as a + * parameter and returns true if this pattern should match it. + * @constructor + * @extends {goog.dom.pattern.Tag} + */ +goog.dom.pattern.StartTag = function(tag, opt_attrs, opt_styles, opt_test) { + goog.dom.pattern.Tag.call( + this, + tag, + goog.dom.TagWalkType.START_TAG, + opt_attrs, + opt_styles, + opt_test); +}; +goog.inherits(goog.dom.pattern.StartTag, goog.dom.pattern.Tag); http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/dom/pattern/tag.js ---------------------------------------------------------------------- diff --git a/externs/GCL/externs/goog/dom/pattern/tag.js b/externs/GCL/externs/goog/dom/pattern/tag.js new file mode 100644 index 0000000..ba95123 --- /dev/null +++ b/externs/GCL/externs/goog/dom/pattern/tag.js @@ -0,0 +1,128 @@ +// Copyright 2007 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 DOM pattern to match a tag. + * + * @author [email protected] (Robby Walker) + */ + +goog.provide('goog.dom.pattern.Tag'); + +goog.require('goog.dom.pattern'); +goog.require('goog.dom.pattern.AbstractPattern'); +goog.require('goog.dom.pattern.MatchType'); +goog.require('goog.object'); + + + +/** + * Pattern object that matches an tag. + * + * @param {string|RegExp} tag Name of the tag. Also will accept a regular + * expression to match against the tag name. + * @param {goog.dom.TagWalkType} type Type of token to match. + * @param {Object=} opt_attrs Optional map of attribute names to desired values. + * This pattern will only match when all attributes are present and match + * the string or regular expression value provided here. + * @param {Object=} opt_styles Optional map of CSS style names to desired + * values. This pattern will only match when all styles are present and + * match the string or regular expression value provided here. + * @param {Function=} opt_test Optional function that takes the element as a + * parameter and returns true if this pattern should match it. + * @constructor + * @extends {goog.dom.pattern.AbstractPattern} + */ +goog.dom.pattern.Tag = function(tag, type, opt_attrs, opt_styles, opt_test) { + /** + * The tag to match. + * + * @private {string|RegExp} + */ + this.tag_ = goog.isString(tag) ? tag.toUpperCase() : tag; + + /** + * The type of token to match. + * + * @private {goog.dom.TagWalkType} + */ + this.type_ = type; + + /** + * The attributes to test for. + * + * @private {Object} + */ + this.attrs_ = opt_attrs || null; + + /** + * The styles to test for. + * + * @private {Object} + */ + this.styles_ = opt_styles || null; + + /** + * Function that takes the element as a parameter and returns true if this + * pattern should match it. + * + * @private {Function} + */ + this.test_ = opt_test || null; +}; +goog.inherits(goog.dom.pattern.Tag, goog.dom.pattern.AbstractPattern); + + +/** + * Test whether the given token is a tag token which matches the tag name, + * style, and attributes provided in the constructor. + * + * @param {Node} token Token to match against. + * @param {goog.dom.TagWalkType} type The type of token. + * @return {goog.dom.pattern.MatchType} <code>MATCH</code> if the pattern + * matches, <code>NO_MATCH</code> otherwise. + * @override + */ +goog.dom.pattern.Tag.prototype.matchToken = function(token, type) { + // Check the direction and tag name. + if (type == this.type_ && + goog.dom.pattern.matchStringOrRegex(this.tag_, token.nodeName)) { + // Check the attributes. + if (this.attrs_ && + !goog.object.every( + this.attrs_, + goog.dom.pattern.matchStringOrRegexMap, + token)) { + return goog.dom.pattern.MatchType.NO_MATCH; + } + // Check the styles. + if (this.styles_ && + !goog.object.every( + this.styles_, + goog.dom.pattern.matchStringOrRegexMap, + token.style)) { + return goog.dom.pattern.MatchType.NO_MATCH; + } + + if (this.test_ && !this.test_(token)) { + return goog.dom.pattern.MatchType.NO_MATCH; + } + + // If we reach this point, we have a match and should save it. + this.matchedNode = token; + return goog.dom.pattern.MatchType.MATCH; + } + + return goog.dom.pattern.MatchType.NO_MATCH; +}; http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/dom/pattern/text.js ---------------------------------------------------------------------- diff --git a/externs/GCL/externs/goog/dom/pattern/text.js b/externs/GCL/externs/goog/dom/pattern/text.js new file mode 100644 index 0000000..cf920e1 --- /dev/null +++ b/externs/GCL/externs/goog/dom/pattern/text.js @@ -0,0 +1,67 @@ +// Copyright 2007 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 DOM pattern to match a text node. + * + * @author [email protected] (Robby Walker) + */ + +goog.provide('goog.dom.pattern.Text'); + +goog.require('goog.dom.NodeType'); +goog.require('goog.dom.pattern'); +goog.require('goog.dom.pattern.AbstractPattern'); +goog.require('goog.dom.pattern.MatchType'); + + + +/** + * Pattern object that matches text by exact matching or regular expressions. + * + * @param {string|RegExp} match String or regular expression to match against. + * @constructor + * @extends {goog.dom.pattern.AbstractPattern} + * @final + */ +goog.dom.pattern.Text = function(match) { + /** + * The text or regular expression to match. + * + * @private {string|RegExp} + */ + this.match_ = match; +}; +goog.inherits(goog.dom.pattern.Text, goog.dom.pattern.AbstractPattern); + + +/** + * Test whether the given token is a text token which matches the string or + * regular expression provided in the constructor. + * + * @param {Node} token Token to match against. + * @param {goog.dom.TagWalkType} type The type of token. + * @return {goog.dom.pattern.MatchType} <code>MATCH</code> if the pattern + * matches, <code>NO_MATCH</code> otherwise. + * @override + */ +goog.dom.pattern.Text.prototype.matchToken = function(token, type) { + if (token.nodeType == goog.dom.NodeType.TEXT && + goog.dom.pattern.matchStringOrRegex(this.match_, token.nodeValue)) { + this.matchedNode = token; + return goog.dom.pattern.MatchType.MATCH; + } + + return goog.dom.pattern.MatchType.NO_MATCH; +}; http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/dom/range.js ---------------------------------------------------------------------- diff --git a/externs/GCL/externs/goog/dom/range.js b/externs/GCL/externs/goog/dom/range.js new file mode 100644 index 0000000..eec784a --- /dev/null +++ b/externs/GCL/externs/goog/dom/range.js @@ -0,0 +1,226 @@ +// Copyright 2007 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 Utilities for working with ranges in HTML documents. + * + * @author [email protected] (Robby Walker) + */ + +goog.provide('goog.dom.Range'); + +goog.require('goog.dom'); +goog.require('goog.dom.AbstractRange'); +goog.require('goog.dom.BrowserFeature'); +goog.require('goog.dom.ControlRange'); +goog.require('goog.dom.MultiRange'); +goog.require('goog.dom.NodeType'); +goog.require('goog.dom.TextRange'); + + +/** + * Create a new selection from the given browser window's current selection. + * Note that this object does not auto-update if the user changes their + * selection and should be used as a snapshot. + * @param {Window=} opt_win The window to get the selection of. Defaults to the + * window this class was defined in. + * @return {goog.dom.AbstractRange?} A range wrapper object, or null if there + * was an error. + */ +goog.dom.Range.createFromWindow = function(opt_win) { + var sel = goog.dom.AbstractRange.getBrowserSelectionForWindow( + opt_win || window); + return sel && goog.dom.Range.createFromBrowserSelection(sel); +}; + + +/** + * Create a new range wrapper from the given browser selection object. Note + * that this object does not auto-update if the user changes their selection and + * should be used as a snapshot. + * @param {!Object} selection The browser selection object. + * @return {goog.dom.AbstractRange?} A range wrapper object or null if there + * was an error. + */ +goog.dom.Range.createFromBrowserSelection = function(selection) { + var range; + var isReversed = false; + if (selection.createRange) { + /** @preserveTry */ + try { + range = selection.createRange(); + } catch (e) { + // Access denied errors can be thrown here in IE if the selection was + // a flash obj or if there are cross domain issues + return null; + } + } else if (selection.rangeCount) { + if (selection.rangeCount > 1) { + return goog.dom.MultiRange.createFromBrowserSelection( + /** @type {!Selection} */ (selection)); + } else { + range = selection.getRangeAt(0); + isReversed = goog.dom.Range.isReversed(selection.anchorNode, + selection.anchorOffset, selection.focusNode, selection.focusOffset); + } + } else { + return null; + } + + return goog.dom.Range.createFromBrowserRange(range, isReversed); +}; + + +/** + * Create a new range wrapper from the given browser range object. + * @param {Range|TextRange} range The browser range object. + * @param {boolean=} opt_isReversed Whether the focus node is before the anchor + * node. + * @return {!goog.dom.AbstractRange} A range wrapper object. + */ +goog.dom.Range.createFromBrowserRange = function(range, opt_isReversed) { + // Create an IE control range when appropriate. + return goog.dom.AbstractRange.isNativeControlRange(range) ? + goog.dom.ControlRange.createFromBrowserRange(range) : + goog.dom.TextRange.createFromBrowserRange(range, opt_isReversed); +}; + + +/** + * Create a new range wrapper that selects the given node's text. + * @param {Node} node The node to select. + * @param {boolean=} opt_isReversed Whether the focus node is before the anchor + * node. + * @return {!goog.dom.AbstractRange} A range wrapper object. + */ +goog.dom.Range.createFromNodeContents = function(node, opt_isReversed) { + return goog.dom.TextRange.createFromNodeContents(node, opt_isReversed); +}; + + +/** + * Create a new range wrapper that represents a caret at the given node, + * accounting for the given offset. This always creates a TextRange, regardless + * of whether node is an image node or other control range type node. + * @param {Node} node The node to place a caret at. + * @param {number} offset The offset within the node to place the caret at. + * @return {!goog.dom.AbstractRange} A range wrapper object. + */ +goog.dom.Range.createCaret = function(node, offset) { + return goog.dom.TextRange.createFromNodes(node, offset, node, offset); +}; + + +/** + * Create a new range wrapper that selects the area between the given nodes, + * accounting for the given offsets. + * @param {Node} anchorNode The node to anchor on. + * @param {number} anchorOffset The offset within the node to anchor on. + * @param {Node} focusNode The node to focus on. + * @param {number} focusOffset The offset within the node to focus on. + * @return {!goog.dom.AbstractRange} A range wrapper object. + */ +goog.dom.Range.createFromNodes = function(anchorNode, anchorOffset, focusNode, + focusOffset) { + return goog.dom.TextRange.createFromNodes(anchorNode, anchorOffset, focusNode, + focusOffset); +}; + + +/** + * Clears the window's selection. + * @param {Window=} opt_win The window to get the selection of. Defaults to the + * window this class was defined in. + */ +goog.dom.Range.clearSelection = function(opt_win) { + var sel = goog.dom.AbstractRange.getBrowserSelectionForWindow( + opt_win || window); + if (!sel) { + return; + } + if (sel.empty) { + // We can't just check that the selection is empty, becuase IE + // sometimes gets confused. + try { + sel.empty(); + } catch (e) { + // Emptying an already empty selection throws an exception in IE + } + } else { + try { + sel.removeAllRanges(); + } catch (e) { + // This throws in IE9 if the range has been invalidated; for example, if + // the user clicked on an element which disappeared during the event + // handler. + } + } +}; + + +/** + * Tests if the window has a selection. + * @param {Window=} opt_win The window to check the selection of. Defaults to + * the window this class was defined in. + * @return {boolean} Whether the window has a selection. + */ +goog.dom.Range.hasSelection = function(opt_win) { + var sel = goog.dom.AbstractRange.getBrowserSelectionForWindow( + opt_win || window); + return !!sel && + (goog.dom.BrowserFeature.LEGACY_IE_RANGES ? + sel.type != 'None' : !!sel.rangeCount); +}; + + +/** + * Returns whether the focus position occurs before the anchor position. + * @param {Node} anchorNode The node to anchor on. + * @param {number} anchorOffset The offset within the node to anchor on. + * @param {Node} focusNode The node to focus on. + * @param {number} focusOffset The offset within the node to focus on. + * @return {boolean} Whether the focus position occurs before the anchor + * position. + */ +goog.dom.Range.isReversed = function(anchorNode, anchorOffset, focusNode, + focusOffset) { + if (anchorNode == focusNode) { + return focusOffset < anchorOffset; + } + var child; + if (anchorNode.nodeType == goog.dom.NodeType.ELEMENT && anchorOffset) { + child = anchorNode.childNodes[anchorOffset]; + if (child) { + anchorNode = child; + anchorOffset = 0; + } else if (goog.dom.contains(anchorNode, focusNode)) { + // If focus node is contained in anchorNode, it must be before the + // end of the node. Hence we are reversed. + return true; + } + } + if (focusNode.nodeType == goog.dom.NodeType.ELEMENT && focusOffset) { + child = focusNode.childNodes[focusOffset]; + if (child) { + focusNode = child; + focusOffset = 0; + } else if (goog.dom.contains(focusNode, anchorNode)) { + // If anchor node is contained in focusNode, it must be before the + // end of the node. Hence we are not reversed. + return false; + } + } + return (goog.dom.compareNodeOrder(anchorNode, focusNode) || + anchorOffset - focusOffset) > 0; +}; http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/dom/rangeendpoint.js ---------------------------------------------------------------------- diff --git a/externs/GCL/externs/goog/dom/rangeendpoint.js b/externs/GCL/externs/goog/dom/rangeendpoint.js new file mode 100644 index 0000000..f8d0fe4 --- /dev/null +++ b/externs/GCL/externs/goog/dom/rangeendpoint.js @@ -0,0 +1,32 @@ +// Copyright 2007 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 Simple struct for endpoints of a range. + * + * @author [email protected] (Robby Walker) + */ + + +goog.provide('goog.dom.RangeEndpoint'); + + +/** + * Constants for selection endpoints. + * @enum {number} + */ +goog.dom.RangeEndpoint = { + START: 1, + END: 0 +}; http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/dom/safe.js ---------------------------------------------------------------------- diff --git a/externs/GCL/externs/goog/dom/safe.js b/externs/GCL/externs/goog/dom/safe.js new file mode 100644 index 0000000..8aa9d9e --- /dev/null +++ b/externs/GCL/externs/goog/dom/safe.js @@ -0,0 +1,325 @@ +// Copyright 2013 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 Type-safe wrappers for unsafe DOM APIs. + * + * This file provides type-safe wrappers for DOM APIs that can result in + * cross-site scripting (XSS) vulnerabilities, if the API is supplied with + * untrusted (attacker-controlled) input. Instead of plain strings, the type + * safe wrappers consume values of types from the goog.html package whose + * contract promises that values are safe to use in the corresponding context. + * + * Hence, a program that exclusively uses the wrappers in this file (i.e., whose + * only reference to security-sensitive raw DOM APIs are in this file) is + * guaranteed to be free of XSS due to incorrect use of such DOM APIs (modulo + * correctness of code that produces values of the respective goog.html types, + * and absent code that violates type safety). + * + * For example, assigning to an element's .innerHTML property a string that is + * derived (even partially) from untrusted input typically results in an XSS + * vulnerability. The type-safe wrapper goog.html.setInnerHtml consumes a value + * of type goog.html.SafeHtml, whose contract states that using its values in a + * HTML context will not result in XSS. Hence a program that is free of direct + * assignments to any element's innerHTML property (with the exception of the + * assignment to .innerHTML in this file) is guaranteed to be free of XSS due to + * assignment of untrusted strings to the innerHTML property. + */ + +goog.provide('goog.dom.safe'); + +goog.require('goog.asserts'); +goog.require('goog.html.SafeHtml'); +goog.require('goog.html.SafeUrl'); +goog.require('goog.html.TrustedResourceUrl'); +goog.require('goog.string'); +goog.require('goog.string.Const'); + + +/** + * Assigns known-safe HTML to an element's innerHTML property. + * @param {!Element} elem The element whose innerHTML is to be assigned to. + * @param {!goog.html.SafeHtml} html The known-safe HTML to assign. + */ +goog.dom.safe.setInnerHtml = function(elem, html) { + elem.innerHTML = goog.html.SafeHtml.unwrap(html); +}; + + +/** + * Assigns known-safe HTML to an element's outerHTML property. + * @param {!Element} elem The element whose outerHTML is to be assigned to. + * @param {!goog.html.SafeHtml} html The known-safe HTML to assign. + */ +goog.dom.safe.setOuterHtml = function(elem, html) { + elem.outerHTML = goog.html.SafeHtml.unwrap(html); +}; + + +/** + * Writes known-safe HTML to a document. + * @param {!Document} doc The document to be written to. + * @param {!goog.html.SafeHtml} html The known-safe HTML to assign. + */ +goog.dom.safe.documentWrite = function(doc, html) { + doc.write(goog.html.SafeHtml.unwrap(html)); +}; + + +/** + * Safely assigns a URL to an anchor element's href property. + * + * If url is of type goog.html.SafeUrl, its value is unwrapped and assigned to + * anchor's href property. If url is of type string however, it is first + * sanitized using goog.html.SafeUrl.sanitize. + * + * Example usage: + * goog.dom.safe.setAnchorHref(anchorEl, url); + * which is a safe alternative to + * anchorEl.href = url; + * The latter can result in XSS vulnerabilities if url is a + * user-/attacker-controlled value. + * + * @param {!HTMLAnchorElement} anchor The anchor element whose href property + * is to be assigned to. + * @param {string|!goog.html.SafeUrl} url The URL to assign. + * @see goog.html.SafeUrl#sanitize + */ +goog.dom.safe.setAnchorHref = function(anchor, url) { + /** @type {!goog.html.SafeUrl} */ + var safeUrl; + if (url instanceof goog.html.SafeUrl) { + safeUrl = url; + } else { + safeUrl = goog.html.SafeUrl.sanitize(url); + } + anchor.href = goog.html.SafeUrl.unwrap(safeUrl); +}; + + +/** + * Safely assigns a URL to an embed element's src property. + * + * Example usage: + * goog.dom.safe.setEmbedSrc(embedEl, url); + * which is a safe alternative to + * embedEl.src = url; + * The latter can result in loading untrusted code unless it is ensured that + * the URL refers to a trustworthy resource. + * + * @param {!HTMLEmbedElement} embed The embed element whose src property + * is to be assigned to. + * @param {!goog.html.TrustedResourceUrl} url The URL to assign. + */ +goog.dom.safe.setEmbedSrc = function(embed, url) { + embed.src = goog.html.TrustedResourceUrl.unwrap(url); +}; + + +/** + * Safely assigns a URL to a frame element's src property. + * + * Example usage: + * goog.dom.safe.setFrameSrc(frameEl, url); + * which is a safe alternative to + * frameEl.src = url; + * The latter can result in loading untrusted code unless it is ensured that + * the URL refers to a trustworthy resource. + * + * @param {!HTMLFrameElement} frame The frame element whose src property + * is to be assigned to. + * @param {!goog.html.TrustedResourceUrl} url The URL to assign. + */ +goog.dom.safe.setFrameSrc = function(frame, url) { + frame.src = goog.html.TrustedResourceUrl.unwrap(url); +}; + + +/** + * Safely assigns a URL to an iframe element's src property. + * + * Example usage: + * goog.dom.safe.setIframeSrc(iframeEl, url); + * which is a safe alternative to + * iframeEl.src = url; + * The latter can result in loading untrusted code unless it is ensured that + * the URL refers to a trustworthy resource. + * + * @param {!HTMLIFrameElement} iframe The iframe element whose src property + * is to be assigned to. + * @param {!goog.html.TrustedResourceUrl} url The URL to assign. + */ +goog.dom.safe.setIframeSrc = function(iframe, url) { + iframe.src = goog.html.TrustedResourceUrl.unwrap(url); +}; + + +/** + * Safely sets a link element's href and rel properties. Whether or not + * the URL assigned to href has to be a goog.html.TrustedResourceUrl + * depends on the value of the rel property. If rel contains "stylesheet" + * then a TrustedResourceUrl is required. + * + * Example usage: + * goog.dom.safe.setLinkHrefAndRel(linkEl, url, 'stylesheet'); + * which is a safe alternative to + * linkEl.rel = 'stylesheet'; + * linkEl.href = url; + * The latter can result in loading untrusted code unless it is ensured that + * the URL refers to a trustworthy resource. + * + * @param {!HTMLLinkElement} link The link element whose href property + * is to be assigned to. + * @param {string|!goog.html.SafeUrl|!goog.html.TrustedResourceUrl} url The URL + * to assign to the href property. Must be a TrustedResourceUrl if the + * value assigned to rel contains "stylesheet". A string value is + * sanitized with goog.html.SafeUrl.sanitize. + * @param {string} rel The value to assign to the rel property. + * @throws {Error} if rel contains "stylesheet" and url is not a + * TrustedResourceUrl + * @see goog.html.SafeUrl#sanitize + */ +goog.dom.safe.setLinkHrefAndRel = function(link, url, rel) { + link.rel = rel; + if (goog.string.caseInsensitiveContains(rel, 'stylesheet')) { + goog.asserts.assert( + url instanceof goog.html.TrustedResourceUrl, + 'URL must be TrustedResourceUrl because "rel" contains "stylesheet"'); + link.href = goog.html.TrustedResourceUrl.unwrap(url); + } else if (url instanceof goog.html.TrustedResourceUrl) { + link.href = goog.html.TrustedResourceUrl.unwrap(url); + } else if (url instanceof goog.html.SafeUrl) { + link.href = goog.html.SafeUrl.unwrap(url); + } else { // string + // SafeUrl.sanitize must return legitimate SafeUrl when passed a string. + link.href = goog.html.SafeUrl.sanitize(url).getTypedStringValue(); + } +}; + + +/** + * Safely assigns a URL to an object element's data property. + * + * Example usage: + * goog.dom.safe.setObjectData(objectEl, url); + * which is a safe alternative to + * objectEl.data = url; + * The latter can result in loading untrusted code unless setit is ensured that + * the URL refers to a trustworthy resource. + * + * @param {!HTMLObjectElement} object The object element whose data property + * is to be assigned to. + * @param {!goog.html.TrustedResourceUrl} url The URL to assign. + */ +goog.dom.safe.setObjectData = function(object, url) { + object.data = goog.html.TrustedResourceUrl.unwrap(url); +}; + + +/** + * Safely assigns a URL to an iframe element's src property. + * + * Example usage: + * goog.dom.safe.setScriptSrc(scriptEl, url); + * which is a safe alternative to + * scriptEl.src = url; + * The latter can result in loading untrusted code unless it is ensured that + * the URL refers to a trustworthy resource. + * + * @param {!HTMLScriptElement} script The script element whose src property + * is to be assigned to. + * @param {!goog.html.TrustedResourceUrl} url The URL to assign. + */ +goog.dom.safe.setScriptSrc = function(script, url) { + script.src = goog.html.TrustedResourceUrl.unwrap(url); +}; + + +/** + * Safely assigns a URL to a Location object's href property. + * + * If url is of type goog.html.SafeUrl, its value is unwrapped and assigned to + * loc's href property. If url is of type string however, it is first sanitized + * using goog.html.SafeUrl.sanitize. + * + * Example usage: + * goog.dom.safe.setLocationHref(document.location, redirectUrl); + * which is a safe alternative to + * document.location.href = redirectUrl; + * The latter can result in XSS vulnerabilities if redirectUrl is a + * user-/attacker-controlled value. + * + * @param {!Location} loc The Location object whose href property is to be + * assigned to. + * @param {string|!goog.html.SafeUrl} url The URL to assign. + * @see goog.html.SafeUrl#sanitize + */ +goog.dom.safe.setLocationHref = function(loc, url) { + /** @type {!goog.html.SafeUrl} */ + var safeUrl; + if (url instanceof goog.html.SafeUrl) { + safeUrl = url; + } else { + safeUrl = goog.html.SafeUrl.sanitize(url); + } + loc.href = goog.html.SafeUrl.unwrap(safeUrl); +}; + + +/** + * Safely opens a URL in a new window (via window.open). + * + * If url is of type goog.html.SafeUrl, its value is unwrapped and passed in to + * window.open. If url is of type string however, it is first sanitized + * using goog.html.SafeUrl.sanitize. + * + * Note that this function does not prevent leakages via the referer that is + * sent by window.open. It is advised to only use this to open 1st party URLs. + * + * Example usage: + * goog.dom.safe.openInWindow(url); + * which is a safe alternative to + * window.open(url); + * The latter can result in XSS vulnerabilities if redirectUrl is a + * user-/attacker-controlled value. + * + * @param {string|!goog.html.SafeUrl} url The URL to open. + * @param {Window=} opt_openerWin Window of which to call the .open() method. + * Defaults to the global window. + * @param {!goog.string.Const=} opt_name Name of the window to open in. Can be + * _top, etc as allowed by window.open(). + * @param {string=} opt_specs Comma-separated list of specifications, same as + * in window.open(). + * @param {boolean=} opt_replace Whether to replace the current entry in browser + * history, same as in window.open(). + * @return {Window} Window the url was opened in. + */ +goog.dom.safe.openInWindow = function( + url, opt_openerWin, opt_name, opt_specs, opt_replace) { + /** @type {!goog.html.SafeUrl} */ + var safeUrl; + if (url instanceof goog.html.SafeUrl) { + safeUrl = url; + } else { + safeUrl = goog.html.SafeUrl.sanitize(url); + } + var win = opt_openerWin || window; + return win.open(goog.html.SafeUrl.unwrap(safeUrl), + // If opt_name is undefined, simply passing that in to open() causes IE to + // reuse the current window instead of opening a new one. Thus we pass '' + // in instead, which according to spec opens a new window. See + // https://html.spec.whatwg.org/multipage/browsers.html#dom-open . + opt_name ? goog.string.Const.unwrap(opt_name) : '', + opt_specs, opt_replace); +}; http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/dom/savedcaretrange.js ---------------------------------------------------------------------- diff --git a/externs/GCL/externs/goog/dom/savedcaretrange.js b/externs/GCL/externs/goog/dom/savedcaretrange.js new file mode 100644 index 0000000..ea61050 --- /dev/null +++ b/externs/GCL/externs/goog/dom/savedcaretrange.js @@ -0,0 +1,215 @@ +// 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 An API for saving and restoring ranges as HTML carets. + * + * @author [email protected] (Nick Santos) + */ + + +goog.provide('goog.dom.SavedCaretRange'); + +goog.require('goog.array'); +goog.require('goog.dom'); +goog.require('goog.dom.SavedRange'); +goog.require('goog.dom.TagName'); +goog.require('goog.string'); + + + +/** + * A struct for holding context about saved selections. + * This can be used to preserve the selection and restore while the DOM is + * manipulated, or through an asynchronous call. Use goog.dom.Range factory + * methods to obtain an {@see goog.dom.AbstractRange} instance, and use + * {@see goog.dom.AbstractRange#saveUsingCarets} to obtain a SavedCaretRange. + * For editor ranges under content-editable elements or design-mode iframes, + * prefer using {@see goog.editor.range.saveUsingNormalizedCarets}. + * @param {goog.dom.AbstractRange} range The range being saved. + * @constructor + * @extends {goog.dom.SavedRange} + */ +goog.dom.SavedCaretRange = function(range) { + goog.dom.SavedRange.call(this); + + /** + * The DOM id of the caret at the start of the range. + * @type {string} + * @private + */ + this.startCaretId_ = goog.string.createUniqueString(); + + /** + * The DOM id of the caret at the end of the range. + * @type {string} + * @private + */ + this.endCaretId_ = goog.string.createUniqueString(); + + /** + * Whether the range is reversed (anchor at the end). + * @private {boolean} + */ + this.reversed_ = range.isReversed(); + + /** + * A DOM helper for storing the current document context. + * @type {goog.dom.DomHelper} + * @private + */ + this.dom_ = goog.dom.getDomHelper(range.getDocument()); + + range.surroundWithNodes(this.createCaret_(true), this.createCaret_(false)); +}; +goog.inherits(goog.dom.SavedCaretRange, goog.dom.SavedRange); + + +/** + * Gets the range that this SavedCaretRage represents, without selecting it + * or removing the carets from the DOM. + * @return {goog.dom.AbstractRange?} An abstract range. + */ +goog.dom.SavedCaretRange.prototype.toAbstractRange = function() { + var range = null; + var startCaret = this.getCaret(true); + var endCaret = this.getCaret(false); + if (startCaret && endCaret) { + /** @suppress {missingRequire} circular dependency */ + range = goog.dom.Range.createFromNodes(startCaret, 0, endCaret, 0); + } + return range; +}; + + +/** + * Gets carets. + * @param {boolean} start If true, returns the start caret. Otherwise, get the + * end caret. + * @return {Element} The start or end caret in the given document. + */ +goog.dom.SavedCaretRange.prototype.getCaret = function(start) { + return this.dom_.getElement(start ? this.startCaretId_ : this.endCaretId_); +}; + + +/** + * Removes the carets from the current restoration document. + * @param {goog.dom.AbstractRange=} opt_range A range whose offsets have already + * been adjusted for caret removal; it will be adjusted if it is also + * affected by post-removal operations, such as text node normalization. + * @return {goog.dom.AbstractRange|undefined} The adjusted range, if opt_range + * was provided. + */ +goog.dom.SavedCaretRange.prototype.removeCarets = function(opt_range) { + goog.dom.removeNode(this.getCaret(true)); + goog.dom.removeNode(this.getCaret(false)); + return opt_range; +}; + + +/** + * Sets the document where the range will be restored. + * @param {!Document} doc An HTML document. + */ +goog.dom.SavedCaretRange.prototype.setRestorationDocument = function(doc) { + this.dom_.setDocument(doc); +}; + + +/** + * Reconstruct the selection from the given saved range. Removes carets after + * restoring the selection. If restore does not dispose this saved range, it may + * only be restored a second time if innerHTML or some other mechanism is used + * to restore the carets to the dom. + * @return {goog.dom.AbstractRange?} Restored selection. + * @override + * @protected + */ +goog.dom.SavedCaretRange.prototype.restoreInternal = function() { + var range = null; + var anchorCaret = this.getCaret(!this.reversed_); + var focusCaret = this.getCaret(this.reversed_); + if (anchorCaret && focusCaret) { + var anchorNode = anchorCaret.parentNode; + var anchorOffset = goog.array.indexOf(anchorNode.childNodes, anchorCaret); + var focusNode = focusCaret.parentNode; + var focusOffset = goog.array.indexOf(focusNode.childNodes, focusCaret); + if (focusNode == anchorNode) { + // Compensate for the start caret being removed. + if (this.reversed_) { + anchorOffset--; + } else { + focusOffset--; + } + } + /** @suppress {missingRequire} circular dependency */ + range = goog.dom.Range.createFromNodes(anchorNode, anchorOffset, + focusNode, focusOffset); + range = this.removeCarets(range); + range.select(); + } else { + // If only one caret was found, remove it. + this.removeCarets(); + } + return range; +}; + + +/** + * Dispose the saved range and remove the carets from the DOM. + * @override + * @protected + */ +goog.dom.SavedCaretRange.prototype.disposeInternal = function() { + this.removeCarets(); + this.dom_ = null; +}; + + +/** + * Creates a caret element. + * @param {boolean} start If true, creates the start caret. Otherwise, + * creates the end caret. + * @return {!Element} The new caret element. + * @private + */ +goog.dom.SavedCaretRange.prototype.createCaret_ = function(start) { + return this.dom_.createDom(goog.dom.TagName.SPAN, + {'id': start ? this.startCaretId_ : this.endCaretId_}); +}; + + +/** + * A regex that will match all saved range carets in a string. + * @type {RegExp} + */ +goog.dom.SavedCaretRange.CARET_REGEX = /<span\s+id="?goog_\d+"?><\/span>/ig; + + +/** + * Returns whether two strings of html are equal, ignoring any saved carets. + * Thus two strings of html whose only difference is the id of their saved + * carets will be considered equal, since they represent html with the + * same selection. + * @param {string} str1 The first string. + * @param {string} str2 The second string. + * @return {boolean} Whether two strings of html are equal, ignoring any + * saved carets. + */ +goog.dom.SavedCaretRange.htmlEqual = function(str1, str2) { + return str1 == str2 || + str1.replace(goog.dom.SavedCaretRange.CARET_REGEX, '') == + str2.replace(goog.dom.SavedCaretRange.CARET_REGEX, ''); +}; http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/dom/savedrange.js ---------------------------------------------------------------------- diff --git a/externs/GCL/externs/goog/dom/savedrange.js b/externs/GCL/externs/goog/dom/savedrange.js new file mode 100644 index 0000000..5a7e951 --- /dev/null +++ b/externs/GCL/externs/goog/dom/savedrange.js @@ -0,0 +1,74 @@ +// Copyright 2007 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 A generic interface for saving and restoring ranges. + * + * @author [email protected] (Robby Walker) + */ + + +goog.provide('goog.dom.SavedRange'); + +goog.require('goog.Disposable'); +goog.require('goog.log'); + + + +/** + * Abstract interface for a saved range. + * @constructor + * @extends {goog.Disposable} + */ +goog.dom.SavedRange = function() { + goog.Disposable.call(this); +}; +goog.inherits(goog.dom.SavedRange, goog.Disposable); + + +/** + * Logging object. + * @type {goog.log.Logger} + * @private + */ +goog.dom.SavedRange.logger_ = + goog.log.getLogger('goog.dom.SavedRange'); + + +/** + * Restores the range and by default disposes of the saved copy. Take note: + * this means the by default SavedRange objects are single use objects. + * @param {boolean=} opt_stayAlive Whether this SavedRange should stay alive + * (not be disposed) after restoring the range. Defaults to false (dispose). + * @return {goog.dom.AbstractRange} The restored range. + */ +goog.dom.SavedRange.prototype.restore = function(opt_stayAlive) { + if (this.isDisposed()) { + goog.log.error(goog.dom.SavedRange.logger_, + 'Disposed SavedRange objects cannot be restored.'); + } + + var range = this.restoreInternal(); + if (!opt_stayAlive) { + this.dispose(); + } + return range; +}; + + +/** + * Internal method to restore the saved range. + * @return {goog.dom.AbstractRange} The restored range. + */ +goog.dom.SavedRange.prototype.restoreInternal = goog.abstractMethod; http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/dom/selection.js ---------------------------------------------------------------------- diff --git a/externs/GCL/externs/goog/dom/selection.js b/externs/GCL/externs/goog/dom/selection.js new file mode 100644 index 0000000..4afb4f7 --- /dev/null +++ b/externs/GCL/externs/goog/dom/selection.js @@ -0,0 +1,472 @@ +// Copyright 2006 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 Utilities for working with selections in input boxes and text + * areas. + * + * @author [email protected] (Erik Arvidsson) + * @see ../demos/dom_selection.html + */ + + +goog.provide('goog.dom.selection'); + +goog.require('goog.dom.InputType'); +goog.require('goog.string'); +goog.require('goog.userAgent'); + + +/** + * Sets the place where the selection should start inside a textarea or a text + * input + * @param {Element} textfield A textarea or text input. + * @param {number} pos The position to set the start of the selection at. + */ +goog.dom.selection.setStart = function(textfield, pos) { + if (goog.dom.selection.useSelectionProperties_(textfield)) { + textfield.selectionStart = pos; + } else if (goog.userAgent.IE) { + // destructuring assignment would have been sweet + var tmp = goog.dom.selection.getRangeIe_(textfield); + var range = tmp[0]; + var selectionRange = tmp[1]; + + if (range.inRange(selectionRange)) { + pos = goog.dom.selection.canonicalizePositionIe_(textfield, pos); + + range.collapse(true); + range.move('character', pos); + range.select(); + } + } +}; + + +/** + * Return the place where the selection starts inside a textarea or a text + * input + * @param {Element} textfield A textarea or text input. + * @return {number} The position where the selection starts or 0 if it was + * unable to find the position or no selection exists. Note that we can't + * reliably tell the difference between an element that has no selection and + * one where it starts at 0. + */ +goog.dom.selection.getStart = function(textfield) { + return goog.dom.selection.getEndPoints_(textfield, true)[0]; +}; + + +/** + * Returns the start and end points of the selection within a textarea in IE. + * IE treats newline characters as \r\n characters, and we need to check for + * these characters at the edge of our selection, to ensure that we return the + * right cursor position. + * @param {TextRange} range Complete range object, e.g., "Hello\r\n". + * @param {TextRange} selRange Selected range object. + * @param {boolean} getOnlyStart Value indicating if only start + * cursor position is to be returned. In IE, obtaining the end position + * involves extra work, hence we have this parameter for calls which need + * only start position. + * @return {!Array<number>} An array with the start and end positions where the + * selection starts and ends or [0,0] if it was unable to find the + * positions or no selection exists. Note that we can't reliably tell the + * difference between an element that has no selection and one where + * it starts and ends at 0. If getOnlyStart was true, we return + * -1 as end offset. + * @private + */ +goog.dom.selection.getEndPointsTextareaIe_ = function( + range, selRange, getOnlyStart) { + // Create a duplicate of the selected range object to perform our actions + // against. Example of selectionRange = "" (assuming that the cursor is + // just after the \r\n combination) + var selectionRange = selRange.duplicate(); + + // Text before the selection start, e.g.,"Hello" (notice how range.text + // excludes the \r\n sequence) + var beforeSelectionText = range.text; + // Text before the selection start, e.g., "Hello" (this will later include + // the \r\n sequences also) + var untrimmedBeforeSelectionText = beforeSelectionText; + // Text within the selection , e.g. "" assuming that the cursor is just after + // the \r\n combination. + var selectionText = selectionRange.text; + // Text within the selection, e.g., "" (this will later include the \r\n + // sequences also) + var untrimmedSelectionText = selectionText; + + // Boolean indicating whether we are done dealing with the text before the + // selection's beginning. + var isRangeEndTrimmed = false; + // Go over the range until it becomes a 0-lengthed range or until the range + // text starts changing when we move the end back by one character. + // If after moving the end back by one character, the text remains the same, + // then we need to add a "\r\n" at the end to get the actual text. + while (!isRangeEndTrimmed) { + if (range.compareEndPoints('StartToEnd', range) == 0) { + isRangeEndTrimmed = true; + } else { + range.moveEnd('character', -1); + if (range.text == beforeSelectionText) { + // If the start position of the cursor was after a \r\n string, + // we would skip over it in one go with the moveEnd call, but + // range.text will still show "Hello" (because of the IE range.text + // bug) - this implies that we should add a \r\n to our + // untrimmedBeforeSelectionText string. + untrimmedBeforeSelectionText += '\r\n'; + } else { + isRangeEndTrimmed = true; + } + } + } + + if (getOnlyStart) { + // We return -1 as end, since the caller is only interested in the start + // value. + return [untrimmedBeforeSelectionText.length, -1]; + } + // Boolean indicating whether we are done dealing with the text inside the + // selection. + var isSelectionRangeEndTrimmed = false; + // Go over the selected range until it becomes a 0-lengthed range or until + // the range text starts changing when we move the end back by one character. + // If after moving the end back by one character, the text remains the same, + // then we need to add a "\r\n" at the end to get the actual text. + while (!isSelectionRangeEndTrimmed) { + if (selectionRange.compareEndPoints('StartToEnd', selectionRange) == 0) { + isSelectionRangeEndTrimmed = true; + } else { + selectionRange.moveEnd('character', -1); + if (selectionRange.text == selectionText) { + // If the selection was not empty, and the end point of the selection + // was just after a \r\n, we would have skipped it in one go with the + // moveEnd call, and this implies that we should add a \r\n to the + // untrimmedSelectionText string. + untrimmedSelectionText += '\r\n'; + } else { + isSelectionRangeEndTrimmed = true; + } + } + } + return [ + untrimmedBeforeSelectionText.length, + untrimmedBeforeSelectionText.length + untrimmedSelectionText.length]; +}; + + +/** + * Returns the start and end points of the selection inside a textarea or a + * text input. + * @param {Element} textfield A textarea or text input. + * @return {!Array<number>} An array with the start and end positions where the + * selection starts and ends or [0,0] if it was unable to find the + * positions or no selection exists. Note that we can't reliably tell the + * difference between an element that has no selection and one where + * it starts and ends at 0. + */ +goog.dom.selection.getEndPoints = function(textfield) { + return goog.dom.selection.getEndPoints_(textfield, false); +}; + + +/** + * Returns the start and end points of the selection inside a textarea or a + * text input. + * @param {Element} textfield A textarea or text input. + * @param {boolean} getOnlyStart Value indicating if only start + * cursor position is to be returned. In IE, obtaining the end position + * involves extra work, hence we have this parameter. In FF, there is not + * much extra effort involved. + * @return {!Array<number>} An array with the start and end positions where the + * selection starts and ends or [0,0] if it was unable to find the + * positions or no selection exists. Note that we can't reliably tell the + * difference between an element that has no selection and one where + * it starts and ends at 0. If getOnlyStart was true, we return + * -1 as end offset. + * @private + */ +goog.dom.selection.getEndPoints_ = function(textfield, getOnlyStart) { + var startPos = 0; + var endPos = 0; + if (goog.dom.selection.useSelectionProperties_(textfield)) { + startPos = textfield.selectionStart; + endPos = getOnlyStart ? -1 : textfield.selectionEnd; + } else if (goog.userAgent.IE) { + var tmp = goog.dom.selection.getRangeIe_(textfield); + var range = tmp[0]; + var selectionRange = tmp[1]; + + if (range.inRange(selectionRange)) { + range.setEndPoint('EndToStart', selectionRange); + if (textfield.type == goog.dom.InputType.TEXTAREA) { + return goog.dom.selection.getEndPointsTextareaIe_( + range, selectionRange, getOnlyStart); + } + startPos = range.text.length; + if (!getOnlyStart) { + endPos = range.text.length + selectionRange.text.length; + } else { + endPos = -1; // caller did not ask for end position + } + } + } + return [startPos, endPos]; +}; + + +/** + * Sets the place where the selection should end inside a text area or a text + * input + * @param {Element} textfield A textarea or text input. + * @param {number} pos The position to end the selection at. + */ +goog.dom.selection.setEnd = function(textfield, pos) { + if (goog.dom.selection.useSelectionProperties_(textfield)) { + textfield.selectionEnd = pos; + } else if (goog.userAgent.IE) { + var tmp = goog.dom.selection.getRangeIe_(textfield); + var range = tmp[0]; + var selectionRange = tmp[1]; + + if (range.inRange(selectionRange)) { + // Both the current position and the start cursor position need + // to be canonicalized to take care of possible \r\n miscounts. + pos = goog.dom.selection.canonicalizePositionIe_(textfield, pos); + var startCursorPos = goog.dom.selection.canonicalizePositionIe_( + textfield, goog.dom.selection.getStart(textfield)); + + selectionRange.collapse(true); + selectionRange.moveEnd('character', pos - startCursorPos); + selectionRange.select(); + } + } +}; + + +/** + * Returns the place where the selection ends inside a textarea or a text input + * @param {Element} textfield A textarea or text input. + * @return {number} The position where the selection ends or 0 if it was + * unable to find the position or no selection exists. + */ +goog.dom.selection.getEnd = function(textfield) { + return goog.dom.selection.getEndPoints_(textfield, false)[1]; +}; + + +/** + * Sets the cursor position within a textfield. + * @param {Element} textfield A textarea or text input. + * @param {number} pos The position within the text field. + */ +goog.dom.selection.setCursorPosition = function(textfield, pos) { + if (goog.dom.selection.useSelectionProperties_(textfield)) { + // Mozilla directly supports this + textfield.selectionStart = pos; + textfield.selectionEnd = pos; + + } else if (goog.userAgent.IE) { + pos = goog.dom.selection.canonicalizePositionIe_(textfield, pos); + + // IE has textranges. A textfield's textrange encompasses the + // entire textfield's text by default + var sel = textfield.createTextRange(); + + sel.collapse(true); + sel.move('character', pos); + sel.select(); + } +}; + + +/** + * Sets the selected text inside a textarea or a text input + * @param {Element} textfield A textarea or text input. + * @param {string} text The text to change the selection to. + */ +goog.dom.selection.setText = function(textfield, text) { + if (goog.dom.selection.useSelectionProperties_(textfield)) { + var value = textfield.value; + var oldSelectionStart = textfield.selectionStart; + var before = value.substr(0, oldSelectionStart); + var after = value.substr(textfield.selectionEnd); + textfield.value = before + text + after; + textfield.selectionStart = oldSelectionStart; + textfield.selectionEnd = oldSelectionStart + text.length; + } else if (goog.userAgent.IE) { + var tmp = goog.dom.selection.getRangeIe_(textfield); + var range = tmp[0]; + var selectionRange = tmp[1]; + + if (!range.inRange(selectionRange)) { + return; + } + // When we set the selection text the selection range is collapsed to the + // end. We therefore duplicate the current selection so we know where it + // started. Once we've set the selection text we move the start of the + // selection range to the old start + var range2 = selectionRange.duplicate(); + selectionRange.text = text; + selectionRange.setEndPoint('StartToStart', range2); + selectionRange.select(); + } else { + throw Error('Cannot set the selection end'); + } +}; + + +/** + * Returns the selected text inside a textarea or a text input + * @param {Element} textfield A textarea or text input. + * @return {string} The selected text. + */ +goog.dom.selection.getText = function(textfield) { + if (goog.dom.selection.useSelectionProperties_(textfield)) { + var s = textfield.value; + return s.substring(textfield.selectionStart, textfield.selectionEnd); + } + + if (goog.userAgent.IE) { + var tmp = goog.dom.selection.getRangeIe_(textfield); + var range = tmp[0]; + var selectionRange = tmp[1]; + + if (!range.inRange(selectionRange)) { + return ''; + } else if (textfield.type == goog.dom.InputType.TEXTAREA) { + return goog.dom.selection.getSelectionRangeText_(selectionRange); + } + return selectionRange.text; + } + + throw Error('Cannot get the selection text'); +}; + + +/** + * Returns the selected text within a textarea in IE. + * IE treats newline characters as \r\n characters, and we need to check for + * these characters at the edge of our selection, to ensure that we return the + * right string. + * @param {TextRange} selRange Selected range object. + * @return {string} Selected text in the textarea. + * @private + */ +goog.dom.selection.getSelectionRangeText_ = function(selRange) { + // Create a duplicate of the selected range object to perform our actions + // against. Suppose the text in the textarea is "Hello\r\nWorld" and the + // selection encompasses the "o\r\n" bit, initial selectionRange will be "o" + // (assuming that the cursor is just after the \r\n combination) + var selectionRange = selRange.duplicate(); + + // Text within the selection , e.g. "o" assuming that the cursor is just after + // the \r\n combination. + var selectionText = selectionRange.text; + // Text within the selection, e.g., "o" (this will later include the \r\n + // sequences also) + var untrimmedSelectionText = selectionText; + + // Boolean indicating whether we are done dealing with the text inside the + // selection. + var isSelectionRangeEndTrimmed = false; + // Go over the selected range until it becomes a 0-lengthed range or until + // the range text starts changing when we move the end back by one character. + // If after moving the end back by one character, the text remains the same, + // then we need to add a "\r\n" at the end to get the actual text. + while (!isSelectionRangeEndTrimmed) { + if (selectionRange.compareEndPoints('StartToEnd', selectionRange) == 0) { + isSelectionRangeEndTrimmed = true; + } else { + selectionRange.moveEnd('character', -1); + if (selectionRange.text == selectionText) { + // If the selection was not empty, and the end point of the selection + // was just after a \r\n, we would have skipped it in one go with the + // moveEnd call, and this implies that we should add a \r\n to the + // untrimmedSelectionText string. + untrimmedSelectionText += '\r\n'; + } else { + isSelectionRangeEndTrimmed = true; + } + } + } + return untrimmedSelectionText; +}; + + +/** + * Helper function for returning the range for an object as well as the + * selection range + * @private + * @param {Element} el The element to get the range for. + * @return {!Array<TextRange>} Range of object and selection range in two + * element array. + */ +goog.dom.selection.getRangeIe_ = function(el) { + var doc = el.ownerDocument || el.document; + + var selectionRange = doc.selection.createRange(); + // el.createTextRange() doesn't work on textareas + var range; + + if (el.type == goog.dom.InputType.TEXTAREA) { + range = doc.body.createTextRange(); + range.moveToElementText(el); + } else { + range = el.createTextRange(); + } + + return [range, selectionRange]; +}; + + +/** + * Helper function for canonicalizing a position inside a textfield in IE. + * Deals with the issue that \r\n counts as 2 characters, but + * move('character', n) passes over both characters in one move. + * @private + * @param {Element} textfield The text element. + * @param {number} pos The position desired in that element. + * @return {number} The canonicalized position that will work properly with + * move('character', pos). + */ +goog.dom.selection.canonicalizePositionIe_ = function(textfield, pos) { + if (textfield.type == goog.dom.InputType.TEXTAREA) { + // We do this only for textarea because it is the only one which can + // have a \r\n (input cannot have this). + var value = textfield.value.substring(0, pos); + pos = goog.string.canonicalizeNewlines(value).length; + } + return pos; +}; + + +/** + * Helper function to determine whether it's okay to use + * selectionStart/selectionEnd. + * + * @param {Element} el The element to check for. + * @return {boolean} Whether it's okay to use the selectionStart and + * selectionEnd properties on {@code el}. + * @private + */ +goog.dom.selection.useSelectionProperties_ = function(el) { + try { + return typeof el.selectionStart == 'number'; + } catch (e) { + // Firefox throws an exception if you try to access selectionStart + // on an element with display: none. + return false; + } +}; http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/dom/tagiterator.js ---------------------------------------------------------------------- diff --git a/externs/GCL/externs/goog/dom/tagiterator.js b/externs/GCL/externs/goog/dom/tagiterator.js new file mode 100644 index 0000000..4b6354c --- /dev/null +++ b/externs/GCL/externs/goog/dom/tagiterator.js @@ -0,0 +1,360 @@ +// 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 [email protected] (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) { + /** + * Whether the node iterator is moving in reverse. + * @type {boolean} + */ + this.reversed = !!opt_reversed; + + /** + * The node this position is located on. + * @type {Node} + */ + this.node = null; + + /** + * The type of this position. + * @type {goog.dom.TagWalkType} + */ + this.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} + */ + this.depth; + + /** + * Whether iteration has started. + * @private {boolean} + */ + this.started_ = false; + + /** + * Whether the iterator is constrained to the starting node and its children. + * @type {boolean} + */ + this.constrained = !opt_unconstrained; + + 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; + } +}; +goog.inherits(goog.dom.TagIterator, goog.iter.Iterator); + + +/** + * 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); +};
