blob: 6d41584e4cb0eecc69b3cbd0c1f4714b40a7c2d0 [file] [log] [blame]
// 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 Basic class for matching words in an array.
*
*/
goog.provide('goog.ui.ac.ArrayMatcher');
goog.require('goog.string');
/**
* Basic class for matching words in an array
* @constructor
* @param {Array<?>} rows Dictionary of items to match. Can be objects if they
* have a toString method that returns the value to match against.
* @param {boolean=} opt_noSimilar if true, do not do similarity matches for the
* input token against the dictionary.
*/
goog.ui.ac.ArrayMatcher = function(rows, opt_noSimilar) {
this.rows_ = rows || [];
this.useSimilar_ = !opt_noSimilar;
};
/**
* Replaces the rows that this object searches over.
* @param {Array<?>} rows Dictionary of items to match.
*/
goog.ui.ac.ArrayMatcher.prototype.setRows = function(rows) {
this.rows_ = rows || [];
};
/**
* Function used to pass matches to the autocomplete
* @param {string} token Token to match.
* @param {number} maxMatches Max number of matches to return.
* @param {Function} matchHandler callback to execute after matching.
* @param {string=} opt_fullString The full string from the input box.
*/
goog.ui.ac.ArrayMatcher.prototype.requestMatchingRows =
function(token, maxMatches, matchHandler, opt_fullString) {
var matches = this.useSimilar_ ?
goog.ui.ac.ArrayMatcher.getMatchesForRows(token, maxMatches, this.rows_) :
this.getPrefixMatches(token, maxMatches);
matchHandler(token, matches);
};
/**
* Matches the token against the specified rows, first looking for prefix
* matches and if that fails, then looking for similar matches.
*
* @param {string} token Token to match.
* @param {number} maxMatches Max number of matches to return.
* @param {!Array<?>} rows Rows to search for matches. Can be objects if they
* have a toString method that returns the value to match against.
* @return {!Array<?>} Rows that match.
*/
goog.ui.ac.ArrayMatcher.getMatchesForRows =
function(token, maxMatches, rows) {
var matches =
goog.ui.ac.ArrayMatcher.getPrefixMatchesForRows(token, maxMatches, rows);
if (matches.length == 0) {
matches = goog.ui.ac.ArrayMatcher.getSimilarMatchesForRows(token,
maxMatches, rows);
}
return matches;
};
/**
* Matches the token against the start of words in the row.
* @param {string} token Token to match.
* @param {number} maxMatches Max number of matches to return.
* @return {!Array<?>} Rows that match.
*/
goog.ui.ac.ArrayMatcher.prototype.getPrefixMatches =
function(token, maxMatches) {
return goog.ui.ac.ArrayMatcher.getPrefixMatchesForRows(token, maxMatches,
this.rows_);
};
/**
* Matches the token against the start of words in the row.
* @param {string} token Token to match.
* @param {number} maxMatches Max number of matches to return.
* @param {!Array<?>} rows Rows to search for matches. Can be objects if they have
* a toString method that returns the value to match against.
* @return {!Array<?>} Rows that match.
*/
goog.ui.ac.ArrayMatcher.getPrefixMatchesForRows =
function(token, maxMatches, rows) {
var matches = [];
if (token != '') {
var escapedToken = goog.string.regExpEscape(token);
var matcher = new RegExp('(^|\\W+)' + escapedToken, 'i');
for (var i = 0; i < rows.length && matches.length < maxMatches; i++) {
var row = rows[i];
if (String(row).match(matcher)) {
matches.push(row);
}
}
}
return matches;
};
/**
* Matches the token against similar rows, by calculating "distance" between the
* terms.
* @param {string} token Token to match.
* @param {number} maxMatches Max number of matches to return.
* @return {!Array<?>} The best maxMatches rows.
*/
goog.ui.ac.ArrayMatcher.prototype.getSimilarRows = function(token, maxMatches) {
return goog.ui.ac.ArrayMatcher.getSimilarMatchesForRows(token, maxMatches,
this.rows_);
};
/**
* Matches the token against similar rows, by calculating "distance" between the
* terms.
* @param {string} token Token to match.
* @param {number} maxMatches Max number of matches to return.
* @param {!Array<?>} rows Rows to search for matches. Can be objects
* if they have a toString method that returns the value to
* match against.
* @return {!Array<?>} The best maxMatches rows.
*/
goog.ui.ac.ArrayMatcher.getSimilarMatchesForRows =
function(token, maxMatches, rows) {
var results = [];
for (var index = 0; index < rows.length; index++) {
var row = rows[index];
var str = token.toLowerCase();
var txt = String(row).toLowerCase();
var score = 0;
if (txt.indexOf(str) != -1) {
score = parseInt((txt.indexOf(str) / 4).toString(), 10);
} else {
var arr = str.split('');
var lastPos = -1;
var penalty = 10;
for (var i = 0, c; c = arr[i]; i++) {
var pos = txt.indexOf(c);
if (pos > lastPos) {
var diff = pos - lastPos - 1;
if (diff > penalty - 5) {
diff = penalty - 5;
}
score += diff;
lastPos = pos;
} else {
score += penalty;
penalty += 5;
}
}
}
if (score < str.length * 6) {
results.push({
str: row,
score: score,
index: index
});
}
}
results.sort(function(a, b) {
var diff = a.score - b.score;
if (diff != 0) {
return diff;
}
return a.index - b.index;
});
var matches = [];
for (var i = 0; i < maxMatches && i < results.length; i++) {
matches.push(results[i].str);
}
return matches;
};