blob: fb3cc7d23e91af3d20a9da18336140873689a0ed [file] [log] [blame]
// 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 matcher. Allows for simple searching of DOM
* using patterns descended from {@link goog.dom.pattern.AbstractPattern}.
*
* @author robbyw@google.com (Robby Walker)
*/
goog.provide('goog.dom.pattern.Matcher');
goog.require('goog.dom.TagIterator');
goog.require('goog.dom.pattern.MatchType');
goog.require('goog.iter');
// TODO(robbyw): Allow for backtracks of size > 1.
/**
* Given a set of patterns and a root node, this class tests the patterns in
* parallel.
*
* It is not (yet) a smart matcher - it doesn't do any advanced backtracking.
* Given the pattern <code>DIV, SPAN</code> the matcher will not match
* <code>DIV, DIV, SPAN</code> because it starts matching at the first
* <code>DIV</code>, fails to match <code>SPAN</code> at the second, and never
* backtracks to try again.
*
* It is also possible to have a set of complex patterns that when matched in
* parallel will miss some possible matches. Running multiple times will catch
* all matches eventually.
*
* @constructor
* @final
*/
goog.dom.pattern.Matcher = function() {
this.patterns_ = [];
this.callbacks_ = [];
};
/**
* Array of patterns to attempt to match in parallel.
*
* @type {Array<goog.dom.pattern.AbstractPattern>}
* @private
*/
goog.dom.pattern.Matcher.prototype.patterns_;
/**
* Array of callbacks to call when a pattern is matched. The indexing is the
* same as the {@link #patterns_} array.
*
* @type {Array<Function>}
* @private
*/
goog.dom.pattern.Matcher.prototype.callbacks_;
/**
* Adds a pattern to be matched. The callback can return an object whose keys
* are processing instructions.
*
* @param {goog.dom.pattern.AbstractPattern} pattern The pattern to add.
* @param {Function} callback Function to call when a match is found. Uses
* the above semantics.
*/
goog.dom.pattern.Matcher.prototype.addPattern = function(pattern, callback) {
this.patterns_.push(pattern);
this.callbacks_.push(callback);
};
/**
* Resets all the patterns.
*
* @private
*/
goog.dom.pattern.Matcher.prototype.reset_ = function() {
for (var i = 0, len = this.patterns_.length; i < len; i++) {
this.patterns_[i].reset();
}
};
/**
* Test the given node against all patterns.
*
* @param {goog.dom.TagIterator} position A position in a node walk that is
* located at the token to process.
* @return {boolean} Whether a pattern modified the position or tree
* and its callback resulted in DOM structure or position modification.
* @private
*/
goog.dom.pattern.Matcher.prototype.matchToken_ = function(position) {
for (var i = 0, len = this.patterns_.length; i < len; i++) {
var pattern = this.patterns_[i];
switch (pattern.matchToken(position.node, position.tagType)) {
case goog.dom.pattern.MatchType.MATCH:
case goog.dom.pattern.MatchType.BACKTRACK_MATCH:
var callback = this.callbacks_[i];
// Callbacks are allowed to modify the current position, but must
// return true if the do.
if (callback(pattern.matchedNode, position, pattern)) {
return true;
}
default:
// Do nothing.
break;
}
}
return false;
};
/**
* Match the set of patterns against a match tree.
*
* @param {Node} node The root node of the tree to match.
*/
goog.dom.pattern.Matcher.prototype.match = function(node) {
var position = new goog.dom.TagIterator(node);
this.reset_();
goog.iter.forEach(position, function() {
while (this.matchToken_(position)) {
// Since we've moved, our old pattern statuses don't make sense any more.
// Reset them.
this.reset_();
}
}, this);
};