Path: blob/trunk/third_party/closure/goog/spell/spellcheck.js
2868 views
// Copyright 2007 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview Support class for spell checker components.16*17* @author [email protected] (Emil A Eklund)18*/1920goog.provide('goog.spell.SpellCheck');21goog.provide('goog.spell.SpellCheck.WordChangedEvent');2223goog.require('goog.Timer');24goog.require('goog.events.Event');25goog.require('goog.events.EventTarget');26goog.require('goog.structs.Set');27282930/**31* Support class for spell checker components. Provides basic functionality32* such as word lookup and caching.33*34* @param {Function=} opt_lookupFunction Function to use for word lookup. Must35* accept an array of words, an object reference and a callback function as36* parameters. It must also call the callback function (as a method on the37* object), once ready, with an array containing the original words, their38* spelling status and optionally an array of suggestions.39* @param {string=} opt_language Content language.40* @constructor41* @extends {goog.events.EventTarget}42* @final43*/44goog.spell.SpellCheck = function(opt_lookupFunction, opt_language) {45goog.events.EventTarget.call(this);4647/**48* Function used to lookup spelling of words.49* @type {Function}50* @private51*/52this.lookupFunction_ = opt_lookupFunction || null;5354/**55* Cache for words not yet checked with lookup function.56* @type {goog.structs.Set}57* @private58*/59this.unknownWords_ = new goog.structs.Set();6061this.setLanguage(opt_language);62};63goog.inherits(goog.spell.SpellCheck, goog.events.EventTarget);646566/**67* Delay, in ms, to wait for additional words to be entered before a lookup68* operation is triggered.69*70* @type {number}71* @private72*/73goog.spell.SpellCheck.LOOKUP_DELAY_ = 100;747576/**77* Constants for event names78*79* @enum {string}80*/81goog.spell.SpellCheck.EventType = {82/**83* Fired when all pending words have been processed.84*/85READY: 'ready',8687/**88* Fired when all lookup function failed.89*/90ERROR: 'error',9192/**93* Fired when a word's status is changed.94*/95WORD_CHANGED: 'wordchanged'96};979899/**100* Cache. Shared across all spell checker instances. Map with langauge as the101* key and a cache for that language as the value.102*103* @type {Object}104* @private105*/106goog.spell.SpellCheck.cache_ = {};107108109/**110* Content Language.111* @type {string}112* @private113*/114goog.spell.SpellCheck.prototype.language_ = '';115116117/**118* Cache for set language. Reference to the element corresponding to the set119* language in the static goog.spell.SpellCheck.cache_.120*121* @type {Object|undefined}122* @private123*/124goog.spell.SpellCheck.prototype.cache_;125126127/**128* Id for timer processing the pending queue.129*130* @type {number}131* @private132*/133goog.spell.SpellCheck.prototype.queueTimer_ = 0;134135136/**137* Whether a lookup operation is in progress.138*139* @type {boolean}140* @private141*/142goog.spell.SpellCheck.prototype.lookupInProgress_ = false;143144145/**146* Codes representing the status of an individual word.147*148* @enum {number}149*/150goog.spell.SpellCheck.WordStatus = {151UNKNOWN: 0,152VALID: 1,153INVALID: 2,154IGNORED: 3,155CORRECTED: 4 // Temporary status, not stored in cache156};157158159/**160* Fields for word array in cache.161*162* @enum {number}163*/164goog.spell.SpellCheck.CacheIndex = {165STATUS: 0,166SUGGESTIONS: 1167};168169170/**171* Regular expression for identifying word boundaries.172*173* @type {string}174*/175goog.spell.SpellCheck.WORD_BOUNDARY_CHARS =176'\t\r\n\u00A0 !\"#$%&()*+,\-.\/:;<=>?@\[\\\]^_`{|}~';177178179/**180* Regular expression for identifying word boundaries.181*182* @type {RegExp}183*/184goog.spell.SpellCheck.WORD_BOUNDARY_REGEX =185new RegExp('[' + goog.spell.SpellCheck.WORD_BOUNDARY_CHARS + ']');186187188/**189* Regular expression for splitting a string into individual words and blocks of190* separators. Matches zero or one word followed by zero or more separators.191*192* @type {RegExp}193*/194goog.spell.SpellCheck.SPLIT_REGEX = new RegExp(195'([^' + goog.spell.SpellCheck.WORD_BOUNDARY_CHARS + ']*)' +196'([' + goog.spell.SpellCheck.WORD_BOUNDARY_CHARS + ']*)');197198199/**200* Sets the lookup function.201*202* @param {Function} f Function to use for word lookup. Must accept an array of203* words, an object reference and a callback function as parameters.204* It must also call the callback function (as a method on the object),205* once ready, with an array containing the original words, their206* spelling status and optionally an array of suggestions.207*/208goog.spell.SpellCheck.prototype.setLookupFunction = function(f) {209this.lookupFunction_ = f;210};211212213/**214* Sets language.215*216* @param {string=} opt_language Content language.217*/218goog.spell.SpellCheck.prototype.setLanguage = function(opt_language) {219this.language_ = opt_language || '';220221if (!goog.spell.SpellCheck.cache_[this.language_]) {222goog.spell.SpellCheck.cache_[this.language_] = {};223}224this.cache_ = goog.spell.SpellCheck.cache_[this.language_];225};226227228/**229* Returns language.230*231* @return {string} Content language.232*/233goog.spell.SpellCheck.prototype.getLanguage = function() {234return this.language_;235};236237238/**239* Checks spelling for a block of text.240*241* @param {string} text Block of text to spell check.242*/243goog.spell.SpellCheck.prototype.checkBlock = function(text) {244var words = text.split(goog.spell.SpellCheck.WORD_BOUNDARY_REGEX);245246var len = words.length;247for (var word, i = 0; i < len; i++) {248word = words[i];249this.checkWord_(word);250}251252if (!this.queueTimer_ && !this.lookupInProgress_ &&253this.unknownWords_.getCount()) {254this.processPending_();255} else if (this.unknownWords_.getCount() == 0) {256this.dispatchEvent(goog.spell.SpellCheck.EventType.READY);257}258};259260261/**262* Checks spelling for a single word. Returns the status of the supplied word,263* or UNKNOWN if it's not cached. If it's not cached the word is added to a264* queue and checked with the verification implementation with a short delay.265*266* @param {string} word Word to check spelling of.267* @return {goog.spell.SpellCheck.WordStatus} The status of the supplied word,268* or UNKNOWN if it's not cached.269*/270goog.spell.SpellCheck.prototype.checkWord = function(word) {271var status = this.checkWord_(word);272273if (status == goog.spell.SpellCheck.WordStatus.UNKNOWN && !this.queueTimer_ &&274!this.lookupInProgress_) {275this.queueTimer_ = goog.Timer.callOnce(276this.processPending_, goog.spell.SpellCheck.LOOKUP_DELAY_, this);277}278279return status;280};281282283/**284* Checks spelling for a single word. Returns the status of the supplied word,285* or UNKNOWN if it's not cached.286*287* @param {string} word Word to check spelling of.288* @return {goog.spell.SpellCheck.WordStatus} The status of the supplied word,289* or UNKNOWN if it's not cached.290* @private291*/292goog.spell.SpellCheck.prototype.checkWord_ = function(word) {293if (!word) {294return goog.spell.SpellCheck.WordStatus.INVALID;295}296297var cacheEntry = this.cache_[word];298if (!cacheEntry) {299this.unknownWords_.add(word);300return goog.spell.SpellCheck.WordStatus.UNKNOWN;301}302303return cacheEntry[goog.spell.SpellCheck.CacheIndex.STATUS];304};305306307/**308* Processes pending words unless a lookup operation has already been queued or309* is in progress.310*311* @throws {Error}312*/313goog.spell.SpellCheck.prototype.processPending = function() {314if (this.unknownWords_.getCount()) {315if (!this.queueTimer_ && !this.lookupInProgress_) {316this.processPending_();317}318} else {319this.dispatchEvent(goog.spell.SpellCheck.EventType.READY);320}321};322323324/**325* Processes pending words using the verification callback.326*327* @throws {Error}328* @private329*/330goog.spell.SpellCheck.prototype.processPending_ = function() {331if (!this.lookupFunction_) {332throw Error('No lookup function provided for spell checker.');333}334335if (this.unknownWords_.getCount()) {336this.lookupInProgress_ = true;337var func = this.lookupFunction_;338func(this.unknownWords_.getValues(), this, this.lookupCallback_);339} else {340this.dispatchEvent(goog.spell.SpellCheck.EventType.READY);341}342343this.queueTimer_ = 0;344};345346347/**348* Callback for lookup function.349*350* @param {Array<Array<?>>} data Data array. Each word is represented by an351* array containing the word, the status and optionally an array of352* suggestions. Passing null indicates that the operation failed.353* @private354*355* Example:356* obj.lookupCallback_([357* ['word', VALID],358* ['wrod', INVALID, ['word', 'wood', 'rod']]359* ]);360*/361goog.spell.SpellCheck.prototype.lookupCallback_ = function(data) {362363// Lookup function failed; abort then dispatch error event.364if (data == null) {365if (this.queueTimer_) {366goog.Timer.clear(this.queueTimer_);367this.queueTimer_ = 0;368}369this.lookupInProgress_ = false;370371this.dispatchEvent(goog.spell.SpellCheck.EventType.ERROR);372return;373}374375for (var a, i = 0; a = data[i]; i++) {376this.setWordStatus_(a[0], a[1], a[2]);377}378this.lookupInProgress_ = false;379380// Fire ready event if all pending words have been processed.381if (this.unknownWords_.getCount() == 0) {382this.dispatchEvent(goog.spell.SpellCheck.EventType.READY);383384// Process pending385} else if (!this.queueTimer_) {386this.queueTimer_ = goog.Timer.callOnce(387this.processPending_, goog.spell.SpellCheck.LOOKUP_DELAY_, this);388}389};390391392/**393* Sets a words spelling status.394*395* @param {string} word Word to set status for.396* @param {goog.spell.SpellCheck.WordStatus} status Status of word.397* @param {Array<string>=} opt_suggestions Suggestions.398*399* Example:400* obj.setWordStatus('word', VALID);401* obj.setWordStatus('wrod', INVALID, ['word', 'wood', 'rod']);.402*/403goog.spell.SpellCheck.prototype.setWordStatus = function(404word, status, opt_suggestions) {405this.setWordStatus_(word, status, opt_suggestions);406};407408409/**410* Sets a words spelling status.411*412* @param {string} word Word to set status for.413* @param {goog.spell.SpellCheck.WordStatus} status Status of word.414* @param {Array<string>=} opt_suggestions Suggestions.415* @private416*/417goog.spell.SpellCheck.prototype.setWordStatus_ = function(418word, status, opt_suggestions) {419var suggestions = opt_suggestions || [];420this.cache_[word] = [status, suggestions];421this.unknownWords_.remove(word);422423this.dispatchEvent(424new goog.spell.SpellCheck.WordChangedEvent(this, word, status));425};426427428/**429* Returns suggestions for the given word.430*431* @param {string} word Word to get suggestions for.432* @return {Array<string>} An array of suggestions for the given word.433*/434goog.spell.SpellCheck.prototype.getSuggestions = function(word) {435var cacheEntry = this.cache_[word];436437if (!cacheEntry) {438this.checkWord(word);439return [];440}441442return cacheEntry[goog.spell.SpellCheck.CacheIndex.STATUS] ==443goog.spell.SpellCheck.WordStatus.INVALID ?444cacheEntry[goog.spell.SpellCheck.CacheIndex.SUGGESTIONS] :445[];446};447448449450/**451* Object representing a word changed event. Fired when the status of a word452* changes.453*454* @param {goog.spell.SpellCheck} target Spellcheck object initiating event.455* @param {string} word Word to set status for.456* @param {goog.spell.SpellCheck.WordStatus} status Status of word.457* @extends {goog.events.Event}458* @constructor459* @final460*/461goog.spell.SpellCheck.WordChangedEvent = function(target, word, status) {462goog.events.Event.call(463this, goog.spell.SpellCheck.EventType.WORD_CHANGED, target);464465/**466* Word the status has changed for.467* @type {string}468*/469this.word = word;470471/**472* New status473* @type {goog.spell.SpellCheck.WordStatus}474*/475this.status = status;476};477goog.inherits(goog.spell.SpellCheck.WordChangedEvent, goog.events.Event);478479480