// 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; };