Commit b1e0ff5d authored by Rouven Weßling's avatar Rouven Weßling Committed by Robert Lord

Update lunr.js to version 0.5.7

parent 3a236aa8
/** /**
* lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 0.5.2 * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 0.5.7
* Copyright (C) 2014 Oliver Nightingale * Copyright (C) 2014 Oliver Nightingale
* MIT Licensed * MIT Licensed
* @license * @license
...@@ -7,1851 +7,1881 @@ ...@@ -7,1851 +7,1881 @@
(function(){ (function(){
/** /**
* Convenience function for instantiating a new lunr index and configuring it * Convenience function for instantiating a new lunr index and configuring it
* with the default pipeline functions and the passed config function. * with the default pipeline functions and the passed config function.
* *
* When using this convenience function a new index will be created with the * When using this convenience function a new index will be created with the
* following functions already in the pipeline: * following functions already in the pipeline:
* *
* lunr.StopWordFilter - filters out any stop words before they enter the * lunr.StopWordFilter - filters out any stop words before they enter the
* index * index
* *
* lunr.stemmer - stems the tokens before entering the index. * lunr.stemmer - stems the tokens before entering the index.
* *
* Example: * Example:
* *
* var idx = lunr(function () { * var idx = lunr(function () {
* this.field('title', 10) * this.field('title', 10)
* this.field('tags', 100) * this.field('tags', 100)
* this.field('body') * this.field('body')
* *
* this.ref('cid') * this.ref('cid')
* *
* this.pipeline.add(function () { * this.pipeline.add(function () {
* // some custom pipeline function * // some custom pipeline function
* }) * })
*
* })
*
* @param {Function} config A function that will be called with the new instance
* of the lunr.Index as both its context and first parameter. It can be used to
* customize the instance of new lunr.Index.
* @namespace
* @module
* @returns {lunr.Index}
* *
*/ * })
var lunr = function (config) { *
var idx = new lunr.Index * @param {Function} config A function that will be called with the new instance
* of the lunr.Index as both its context and first parameter. It can be used to
* customize the instance of new lunr.Index.
* @namespace
* @module
* @returns {lunr.Index}
*
*/
var lunr = function (config) {
var idx = new lunr.Index
idx.pipeline.add( idx.pipeline.add(
lunr.trimmer, lunr.trimmer,
lunr.stopWordFilter, lunr.stopWordFilter,
lunr.stemmer lunr.stemmer
) )
if (config) config.call(idx, idx) if (config) config.call(idx, idx)
return idx return idx
} }
lunr.version = "0.5.2" lunr.version = "0.5.7"
/*! /*!
* lunr.utils * lunr.utils
* Copyright (C) 2014 Oliver Nightingale * Copyright (C) 2014 Oliver Nightingale
*/ */
/** /**
* A namespace containing utils for the rest of the lunr library * A namespace containing utils for the rest of the lunr library
*/ */
lunr.utils = {} lunr.utils = {}
/** /**
* Print a warning message to the console. * Print a warning message to the console.
* *
* @param {String} message The message to be printed. * @param {String} message The message to be printed.
* @memberOf Utils * @memberOf Utils
*/ */
lunr.utils.warn = (function (global) { lunr.utils.warn = (function (global) {
return function (message) { return function (message) {
if (global.console && console.warn) { if (global.console && console.warn) {
console.warn(message) console.warn(message)
}
} }
} })(this)
})(this)
/*! /*!
* lunr.EventEmitter * lunr.EventEmitter
* Copyright (C) 2014 Oliver Nightingale * Copyright (C) 2014 Oliver Nightingale
*/ */
/** /**
* lunr.EventEmitter is an event emitter for lunr. It manages adding and removing event handlers and triggering events and their handlers. * lunr.EventEmitter is an event emitter for lunr. It manages adding and removing event handlers and triggering events and their handlers.
* *
* @constructor * @constructor
*/ */
lunr.EventEmitter = function () { lunr.EventEmitter = function () {
this.events = {} this.events = {}
} }
/** /**
* Binds a handler function to a specific event(s). * Binds a handler function to a specific event(s).
* *
* Can bind a single function to many different events in one call. * Can bind a single function to many different events in one call.
* *
* @param {String} [eventName] The name(s) of events to bind this function to. * @param {String} [eventName] The name(s) of events to bind this function to.
* @param {Function} handler The function to call when an event is fired. * @param {Function} handler The function to call when an event is fired.
* @memberOf EventEmitter * @memberOf EventEmitter
*/ */
lunr.EventEmitter.prototype.addListener = function () { lunr.EventEmitter.prototype.addListener = function () {
var args = Array.prototype.slice.call(arguments), var args = Array.prototype.slice.call(arguments),
fn = args.pop(), fn = args.pop(),
names = args names = args
if (typeof fn !== "function") throw new TypeError ("last argument must be a function") if (typeof fn !== "function") throw new TypeError ("last argument must be a function")
names.forEach(function (name) { names.forEach(function (name) {
if (!this.hasHandler(name)) this.events[name] = [] if (!this.hasHandler(name)) this.events[name] = []
this.events[name].push(fn) this.events[name].push(fn)
}, this) }, this)
} }
/** /**
* Removes a handler function from a specific event. * Removes a handler function from a specific event.
* *
* @param {String} eventName The name of the event to remove this function from. * @param {String} eventName The name of the event to remove this function from.
* @param {Function} handler The function to remove from an event. * @param {Function} handler The function to remove from an event.
* @memberOf EventEmitter * @memberOf EventEmitter
*/ */
lunr.EventEmitter.prototype.removeListener = function (name, fn) { lunr.EventEmitter.prototype.removeListener = function (name, fn) {
if (!this.hasHandler(name)) return if (!this.hasHandler(name)) return
var fnIndex = this.events[name].indexOf(fn) var fnIndex = this.events[name].indexOf(fn)
this.events[name].splice(fnIndex, 1) this.events[name].splice(fnIndex, 1)
if (!this.events[name].length) delete this.events[name] if (!this.events[name].length) delete this.events[name]
} }
/** /**
* Calls all functions bound to the given event. * Calls all functions bound to the given event.
* *
* Additional data can be passed to the event handler as arguments to `emit` * Additional data can be passed to the event handler as arguments to `emit`
* after the event name. * after the event name.
* *
* @param {String} eventName The name of the event to emit. * @param {String} eventName The name of the event to emit.
* @memberOf EventEmitter * @memberOf EventEmitter
*/ */
lunr.EventEmitter.prototype.emit = function (name) { lunr.EventEmitter.prototype.emit = function (name) {
if (!this.hasHandler(name)) return if (!this.hasHandler(name)) return
var args = Array.prototype.slice.call(arguments, 1) var args = Array.prototype.slice.call(arguments, 1)
this.events[name].forEach(function (fn) { this.events[name].forEach(function (fn) {
fn.apply(undefined, args) fn.apply(undefined, args)
}) })
} }
/** /**
* Checks whether a handler has ever been stored against an event. * Checks whether a handler has ever been stored against an event.
* *
* @param {String} eventName The name of the event to check. * @param {String} eventName The name of the event to check.
* @private * @private
* @memberOf EventEmitter * @memberOf EventEmitter
*/ */
lunr.EventEmitter.prototype.hasHandler = function (name) { lunr.EventEmitter.prototype.hasHandler = function (name) {
return name in this.events return name in this.events
} }
/*! /*!
* lunr.tokenizer * lunr.tokenizer
* Copyright (C) 2014 Oliver Nightingale * Copyright (C) 2014 Oliver Nightingale
*/ */
/** /**
* A function for splitting a string into tokens ready to be inserted into * A function for splitting a string into tokens ready to be inserted into
* the search index. * the search index.
* *
* @module * @module
* @param {String} obj The string to convert into tokens * @param {String} obj The string to convert into tokens
* @returns {Array} * @returns {Array}
*/ */
lunr.tokenizer = function (obj) { lunr.tokenizer = function (obj) {
if (!arguments.length || obj == null || obj == undefined) return [] if (!arguments.length || obj == null || obj == undefined) return []
if (Array.isArray(obj)) return obj.map(function (t) { return t.toLowerCase() }) if (Array.isArray(obj)) return obj.map(function (t) { return t.toLowerCase() })
var str = obj.toString().replace(/^\s+/, '') var str = obj.toString().replace(/^\s+/, '')
for (var i = str.length - 1; i >= 0; i--) { for (var i = str.length - 1; i >= 0; i--) {
if (/\S/.test(str.charAt(i))) { if (/\S/.test(str.charAt(i))) {
str = str.substring(0, i + 1) str = str.substring(0, i + 1)
break break
}
} }
return str
.split(/(?:\s+|\-)/)
.filter(function (token) {
return !!token
})
.map(function (token) {
return token.toLowerCase()
})
} }
/*!
* lunr.Pipeline
* Copyright (C) 2014 Oliver Nightingale
*/
return str /**
.split(/\s+/) * lunr.Pipelines maintain an ordered list of functions to be applied to all
.map(function (token) { * tokens in documents entering the search index and queries being ran against
return token.toLowerCase() * the index.
}) *
} * An instance of lunr.Index created with the lunr shortcut will contain a
/*! * pipeline with a stop word filter and an English language stemmer. Extra
* lunr.Pipeline * functions can be added before or after either of these functions or these
* Copyright (C) 2014 Oliver Nightingale * default functions can be removed.
*/ *
* When run the pipeline will call each function in turn, passing a token, the
* index of that token in the original list of all tokens and finally a list of
* all the original tokens.
*
* The output of functions in the pipeline will be passed to the next function
* in the pipeline. To exclude a token from entering the index the function
* should return undefined, the rest of the pipeline will not be called with
* this token.
*
* For serialisation of pipelines to work, all functions used in an instance of
* a pipeline should be registered with lunr.Pipeline. Registered functions can
* then be loaded. If trying to load a serialised pipeline that uses functions
* that are not registered an error will be thrown.
*
* If not planning on serialising the pipeline then registering pipeline functions
* is not necessary.
*
* @constructor
*/
lunr.Pipeline = function () {
this._stack = []
}
/** lunr.Pipeline.registeredFunctions = {}
* lunr.Pipelines maintain an ordered list of functions to be applied to all
* tokens in documents entering the search index and queries being ran against
* the index.
*
* An instance of lunr.Index created with the lunr shortcut will contain a
* pipeline with a stop word filter and an English language stemmer. Extra
* functions can be added before or after either of these functions or these
* default functions can be removed.
*
* When run the pipeline will call each function in turn, passing a token, the
* index of that token in the original list of all tokens and finally a list of
* all the original tokens.
*
* The output of functions in the pipeline will be passed to the next function
* in the pipeline. To exclude a token from entering the index the function
* should return undefined, the rest of the pipeline will not be called with
* this token.
*
* For serialisation of pipelines to work, all functions used in an instance of
* a pipeline should be registered with lunr.Pipeline. Registered functions can
* then be loaded. If trying to load a serialised pipeline that uses functions
* that are not registered an error will be thrown.
*
* If not planning on serialising the pipeline then registering pipeline functions
* is not necessary.
*
* @constructor
*/
lunr.Pipeline = function () {
this._stack = []
}
lunr.Pipeline.registeredFunctions = {} /**
* Register a function with the pipeline.
*
* Functions that are used in the pipeline should be registered if the pipeline
* needs to be serialised, or a serialised pipeline needs to be loaded.
*
* Registering a function does not add it to a pipeline, functions must still be
* added to instances of the pipeline for them to be used when running a pipeline.
*
* @param {Function} fn The function to check for.
* @param {String} label The label to register this function with
* @memberOf Pipeline
*/
lunr.Pipeline.registerFunction = function (fn, label) {
if (label in this.registeredFunctions) {
lunr.utils.warn('Overwriting existing registered function: ' + label)
}
/** fn.label = label
* Register a function with the pipeline. lunr.Pipeline.registeredFunctions[fn.label] = fn
*
* Functions that are used in the pipeline should be registered if the pipeline
* needs to be serialised, or a serialised pipeline needs to be loaded.
*
* Registering a function does not add it to a pipeline, functions must still be
* added to instances of the pipeline for them to be used when running a pipeline.
*
* @param {Function} fn The function to check for.
* @param {String} label The label to register this function with
* @memberOf Pipeline
*/
lunr.Pipeline.registerFunction = function (fn, label) {
if (label in this.registeredFunctions) {
lunr.utils.warn('Overwriting existing registered function: ' + label)
} }
fn.label = label /**
lunr.Pipeline.registeredFunctions[fn.label] = fn * Warns if the function is not registered as a Pipeline function.
} *
* @param {Function} fn The function to check for.
/** * @private
* Warns if the function is not registered as a Pipeline function. * @memberOf Pipeline
* */
* @param {Function} fn The function to check for. lunr.Pipeline.warnIfFunctionNotRegistered = function (fn) {
* @private var isRegistered = fn.label && (fn.label in this.registeredFunctions)
* @memberOf Pipeline
*/
lunr.Pipeline.warnIfFunctionNotRegistered = function (fn) {
var isRegistered = fn.label && (fn.label in this.registeredFunctions)
if (!isRegistered) { if (!isRegistered) {
lunr.utils.warn('Function is not registered with pipeline. This may cause problems when serialising the index.\n', fn) lunr.utils.warn('Function is not registered with pipeline. This may cause problems when serialising the index.\n', fn)
}
} }
}
/** /**
* Loads a previously serialised pipeline. * Loads a previously serialised pipeline.
* *
* All functions to be loaded must already be registered with lunr.Pipeline. * All functions to be loaded must already be registered with lunr.Pipeline.
* If any function from the serialised data has not been registered then an * If any function from the serialised data has not been registered then an
* error will be thrown. * error will be thrown.
* *
* @param {Object} serialised The serialised pipeline to load. * @param {Object} serialised The serialised pipeline to load.
* @returns {lunr.Pipeline} * @returns {lunr.Pipeline}
* @memberOf Pipeline * @memberOf Pipeline
*/ */
lunr.Pipeline.load = function (serialised) { lunr.Pipeline.load = function (serialised) {
var pipeline = new lunr.Pipeline var pipeline = new lunr.Pipeline
serialised.forEach(function (fnName) { serialised.forEach(function (fnName) {
var fn = lunr.Pipeline.registeredFunctions[fnName] var fn = lunr.Pipeline.registeredFunctions[fnName]
if (fn) { if (fn) {
pipeline.add(fn) pipeline.add(fn)
} else { } else {
throw new Error ('Cannot load un-registered function: ' + fnName) throw new Error ('Cannot load un-registered function: ' + fnName)
} }
}) })
return pipeline
}
/** return pipeline
* Adds new functions to the end of the pipeline. }
*
* Logs a warning if the function has not been registered.
*
* @param {Function} functions Any number of functions to add to the pipeline.
* @memberOf Pipeline
*/
lunr.Pipeline.prototype.add = function () {
var fns = Array.prototype.slice.call(arguments)
fns.forEach(function (fn) { /**
lunr.Pipeline.warnIfFunctionNotRegistered(fn) * Adds new functions to the end of the pipeline.
this._stack.push(fn) *
}, this) * Logs a warning if the function has not been registered.
} *
* @param {Function} functions Any number of functions to add to the pipeline.
* @memberOf Pipeline
*/
lunr.Pipeline.prototype.add = function () {
var fns = Array.prototype.slice.call(arguments)
/** fns.forEach(function (fn) {
* Adds a single function after a function that already exists in the lunr.Pipeline.warnIfFunctionNotRegistered(fn)
* pipeline. this._stack.push(fn)
* }, this)
* Logs a warning if the function has not been registered. }
*
* @param {Function} existingFn A function that already exists in the pipeline.
* @param {Function} newFn The new function to add to the pipeline.
* @memberOf Pipeline
*/
lunr.Pipeline.prototype.after = function (existingFn, newFn) {
lunr.Pipeline.warnIfFunctionNotRegistered(newFn)
var pos = this._stack.indexOf(existingFn) + 1 /**
this._stack.splice(pos, 0, newFn) * Adds a single function after a function that already exists in the
} * pipeline.
*
* Logs a warning if the function has not been registered.
*
* @param {Function} existingFn A function that already exists in the pipeline.
* @param {Function} newFn The new function to add to the pipeline.
* @memberOf Pipeline
*/
lunr.Pipeline.prototype.after = function (existingFn, newFn) {
lunr.Pipeline.warnIfFunctionNotRegistered(newFn)
/** var pos = this._stack.indexOf(existingFn) + 1
* Adds a single function before a function that already exists in the this._stack.splice(pos, 0, newFn)
* pipeline. }
*
* Logs a warning if the function has not been registered.
*
* @param {Function} existingFn A function that already exists in the pipeline.
* @param {Function} newFn The new function to add to the pipeline.
* @memberOf Pipeline
*/
lunr.Pipeline.prototype.before = function (existingFn, newFn) {
lunr.Pipeline.warnIfFunctionNotRegistered(newFn)
var pos = this._stack.indexOf(existingFn) /**
this._stack.splice(pos, 0, newFn) * Adds a single function before a function that already exists in the
} * pipeline.
*
* Logs a warning if the function has not been registered.
*
* @param {Function} existingFn A function that already exists in the pipeline.
* @param {Function} newFn The new function to add to the pipeline.
* @memberOf Pipeline
*/
lunr.Pipeline.prototype.before = function (existingFn, newFn) {
lunr.Pipeline.warnIfFunctionNotRegistered(newFn)
/** var pos = this._stack.indexOf(existingFn)
* Removes a function from the pipeline. this._stack.splice(pos, 0, newFn)
* }
* @param {Function} fn The function to remove from the pipeline.
* @memberOf Pipeline
*/
lunr.Pipeline.prototype.remove = function (fn) {
var pos = this._stack.indexOf(fn)
this._stack.splice(pos, 1)
}
/** /**
* Runs the current list of functions that make up the pipeline against the * Removes a function from the pipeline.
* passed tokens. *
* * @param {Function} fn The function to remove from the pipeline.
* @param {Array} tokens The tokens to run through the pipeline. * @memberOf Pipeline
* @returns {Array} */
* @memberOf Pipeline lunr.Pipeline.prototype.remove = function (fn) {
*/ var pos = this._stack.indexOf(fn)
lunr.Pipeline.prototype.run = function (tokens) { this._stack.splice(pos, 1)
var out = [], }
tokenLength = tokens.length,
stackLength = this._stack.length
for (var i = 0; i < tokenLength; i++) { /**
var token = tokens[i] * Runs the current list of functions that make up the pipeline against the
* passed tokens.
*
* @param {Array} tokens The tokens to run through the pipeline.
* @returns {Array}
* @memberOf Pipeline
*/
lunr.Pipeline.prototype.run = function (tokens) {
var out = [],
tokenLength = tokens.length,
stackLength = this._stack.length
for (var j = 0; j < stackLength; j++) { for (var i = 0; i < tokenLength; i++) {
token = this._stack[j](token, i, tokens) var token = tokens[i]
if (token === void 0) break
};
if (token !== void 0) out.push(token) for (var j = 0; j < stackLength; j++) {
}; token = this._stack[j](token, i, tokens)
if (token === void 0) break
};
return out if (token !== void 0) out.push(token)
} };
/** return out
* Resets the pipeline by removing any existing processors. }
*
* @memberOf Pipeline
*/
lunr.Pipeline.prototype.reset = function () {
this._stack = []
}
/** /**
* Returns a representation of the pipeline ready for serialisation. * Resets the pipeline by removing any existing processors.
* *
* Logs a warning if the function has not been registered. * @memberOf Pipeline
* */
* @returns {Array} lunr.Pipeline.prototype.reset = function () {
* @memberOf Pipeline this._stack = []
*/ }
lunr.Pipeline.prototype.toJSON = function () {
return this._stack.map(function (fn) {
lunr.Pipeline.warnIfFunctionNotRegistered(fn)
return fn.label
})
}
/*!
* lunr.Vector
* Copyright (C) 2014 Oliver Nightingale
*/
/** /**
* lunr.Vectors implement vector related operations for * Returns a representation of the pipeline ready for serialisation.
* a series of elements. *
* * Logs a warning if the function has not been registered.
* @constructor *
*/ * @returns {Array}
lunr.Vector = function () { * @memberOf Pipeline
this._magnitude = null */
this.list = undefined lunr.Pipeline.prototype.toJSON = function () {
this.length = 0 return this._stack.map(function (fn) {
} lunr.Pipeline.warnIfFunctionNotRegistered(fn)
/** return fn.label
* lunr.Vector.Node is a simple struct for each node })
* in a lunr.Vector. }
* /*!
* @private * lunr.Vector
* @param {Number} The index of the node in the vector. * Copyright (C) 2014 Oliver Nightingale
* @param {Object} The data at this node in the vector. */
* @param {lunr.Vector.Node} The node directly after this node in the vector.
* @constructor
* @memberOf Vector
*/
lunr.Vector.Node = function (idx, val, next) {
this.idx = idx
this.val = val
this.next = next
}
/** /**
* Inserts a new value at a position in a vector. * lunr.Vectors implement vector related operations for
* * a series of elements.
* @param {Number} The index at which to insert a value. *
* @param {Object} The object to insert in the vector. * @constructor
* @memberOf Vector. */
*/ lunr.Vector = function () {
lunr.Vector.prototype.insert = function (idx, val) { this._magnitude = null
var list = this.list this.list = undefined
this.length = 0
}
if (!list) { /**
this.list = new lunr.Vector.Node (idx, val, list) * lunr.Vector.Node is a simple struct for each node
return this.length++ * in a lunr.Vector.
*
* @private
* @param {Number} The index of the node in the vector.
* @param {Object} The data at this node in the vector.
* @param {lunr.Vector.Node} The node directly after this node in the vector.
* @constructor
* @memberOf Vector
*/
lunr.Vector.Node = function (idx, val, next) {
this.idx = idx
this.val = val
this.next = next
} }
var prev = list, /**
next = list.next * Inserts a new value at a position in a vector.
*
* @param {Number} The index at which to insert a value.
* @param {Object} The object to insert in the vector.
* @memberOf Vector.
*/
lunr.Vector.prototype.insert = function (idx, val) {
var list = this.list
while (next != undefined) { if (!list) {
if (idx < next.idx) { this.list = new lunr.Vector.Node (idx, val, list)
prev.next = new lunr.Vector.Node (idx, val, next)
return this.length++ return this.length++
} }
prev = next, next = next.next var prev = list,
} next = list.next
prev.next = new lunr.Vector.Node (idx, val, next) while (next != undefined) {
return this.length++ if (idx < next.idx) {
} prev.next = new lunr.Vector.Node (idx, val, next)
return this.length++
}
/** prev = next, next = next.next
* Calculates the magnitude of this vector. }
*
* @returns {Number}
* @memberOf Vector
*/
lunr.Vector.prototype.magnitude = function () {
if (this._magniture) return this._magnitude
var node = this.list,
sumOfSquares = 0,
val
while (node) { prev.next = new lunr.Vector.Node (idx, val, next)
val = node.val return this.length++
sumOfSquares += val * val
node = node.next
} }
return this._magnitude = Math.sqrt(sumOfSquares) /**
} * Calculates the magnitude of this vector.
*
* @returns {Number}
* @memberOf Vector
*/
lunr.Vector.prototype.magnitude = function () {
if (this._magniture) return this._magnitude
var node = this.list,
sumOfSquares = 0,
val
while (node) {
val = node.val
sumOfSquares += val * val
node = node.next
}
/** return this._magnitude = Math.sqrt(sumOfSquares)
* Calculates the dot product of this vector and another vector. }
*
* @param {lunr.Vector} otherVector The vector to compute the dot product with.
* @returns {Number}
* @memberOf Vector
*/
lunr.Vector.prototype.dot = function (otherVector) {
var node = this.list,
otherNode = otherVector.list,
dotProduct = 0
while (node && otherNode) { /**
if (node.idx < otherNode.idx) { * Calculates the dot product of this vector and another vector.
node = node.next *
} else if (node.idx > otherNode.idx) { * @param {lunr.Vector} otherVector The vector to compute the dot product with.
otherNode = otherNode.next * @returns {Number}
} else { * @memberOf Vector
dotProduct += node.val * otherNode.val */
node = node.next lunr.Vector.prototype.dot = function (otherVector) {
otherNode = otherNode.next var node = this.list,
otherNode = otherVector.list,
dotProduct = 0
while (node && otherNode) {
if (node.idx < otherNode.idx) {
node = node.next
} else if (node.idx > otherNode.idx) {
otherNode = otherNode.next
} else {
dotProduct += node.val * otherNode.val
node = node.next
otherNode = otherNode.next
}
} }
return dotProduct
} }
return dotProduct /**
} * Calculates the cosine similarity between this vector and another
* vector.
*
* @param {lunr.Vector} otherVector The other vector to calculate the
* similarity with.
* @returns {Number}
* @memberOf Vector
*/
lunr.Vector.prototype.similarity = function (otherVector) {
return this.dot(otherVector) / (this.magnitude() * otherVector.magnitude())
}
/*!
* lunr.SortedSet
* Copyright (C) 2014 Oliver Nightingale
*/
/** /**
* Calculates the cosine similarity between this vector and another * lunr.SortedSets are used to maintain an array of uniq values in a sorted
* vector. * order.
* *
* @param {lunr.Vector} otherVector The other vector to calculate the * @constructor
* similarity with. */
* @returns {Number} lunr.SortedSet = function () {
* @memberOf Vector this.length = 0
*/ this.elements = []
lunr.Vector.prototype.similarity = function (otherVector) { }
return this.dot(otherVector) / (this.magnitude() * otherVector.magnitude())
}
/*!
* lunr.SortedSet
* Copyright (C) 2014 Oliver Nightingale
*/
/** /**
* lunr.SortedSets are used to maintain an array of uniq values in a sorted * Loads a previously serialised sorted set.
* order. *
* * @param {Array} serialisedData The serialised set to load.
* @constructor * @returns {lunr.SortedSet}
*/ * @memberOf SortedSet
lunr.SortedSet = function () { */
this.length = 0 lunr.SortedSet.load = function (serialisedData) {
this.elements = [] var set = new this
}
/** set.elements = serialisedData
* Loads a previously serialised sorted set. set.length = serialisedData.length
*
* @param {Array} serialisedData The serialised set to load.
* @returns {lunr.SortedSet}
* @memberOf SortedSet
*/
lunr.SortedSet.load = function (serialisedData) {
var set = new this
set.elements = serialisedData return set
set.length = serialisedData.length }
return set /**
} * Inserts new items into the set in the correct position to maintain the
* order.
*
* @param {Object} The objects to add to this set.
* @memberOf SortedSet
*/
lunr.SortedSet.prototype.add = function () {
Array.prototype.slice.call(arguments).forEach(function (element) {
if (~this.indexOf(element)) return
this.elements.splice(this.locationFor(element), 0, element)
}, this)
/** this.length = this.elements.length
* Inserts new items into the set in the correct position to maintain the }
* order.
*
* @param {Object} The objects to add to this set.
* @memberOf SortedSet
*/
lunr.SortedSet.prototype.add = function () {
Array.prototype.slice.call(arguments).forEach(function (element) {
if (~this.indexOf(element)) return
this.elements.splice(this.locationFor(element), 0, element)
}, this)
this.length = this.elements.length /**
} * Converts this sorted set into an array.
*
* @returns {Array}
* @memberOf SortedSet
*/
lunr.SortedSet.prototype.toArray = function () {
return this.elements.slice()
}
/** /**
* Converts this sorted set into an array. * Creates a new array with the results of calling a provided function on every
* * element in this sorted set.
* @returns {Array} *
* @memberOf SortedSet * Delegates to Array.prototype.map and has the same signature.
*/ *
lunr.SortedSet.prototype.toArray = function () { * @param {Function} fn The function that is called on each element of the
return this.elements.slice() * set.
} * @param {Object} ctx An optional object that can be used as the context
* for the function fn.
* @returns {Array}
* @memberOf SortedSet
*/
lunr.SortedSet.prototype.map = function (fn, ctx) {
return this.elements.map(fn, ctx)
}
/** /**
* Creates a new array with the results of calling a provided function on every * Executes a provided function once per sorted set element.
* element in this sorted set. *
* * Delegates to Array.prototype.forEach and has the same signature.
* Delegates to Array.prototype.map and has the same signature. *
* * @param {Function} fn The function that is called on each element of the
* @param {Function} fn The function that is called on each element of the * set.
* set. * @param {Object} ctx An optional object that can be used as the context
* @param {Object} ctx An optional object that can be used as the context * @memberOf SortedSet
* for the function fn. * for the function fn.
* @returns {Array} */
* @memberOf SortedSet lunr.SortedSet.prototype.forEach = function (fn, ctx) {
*/ return this.elements.forEach(fn, ctx)
lunr.SortedSet.prototype.map = function (fn, ctx) { }
return this.elements.map(fn, ctx)
}
/** /**
* Executes a provided function once per sorted set element. * Returns the index at which a given element can be found in the
* * sorted set, or -1 if it is not present.
* Delegates to Array.prototype.forEach and has the same signature. *
* * @param {Object} elem The object to locate in the sorted set.
* @param {Function} fn The function that is called on each element of the * @param {Number} start An optional index at which to start searching from
* set. * within the set.
* @param {Object} ctx An optional object that can be used as the context * @param {Number} end An optional index at which to stop search from within
* @memberOf SortedSet * the set.
* for the function fn. * @returns {Number}
*/ * @memberOf SortedSet
lunr.SortedSet.prototype.forEach = function (fn, ctx) { */
return this.elements.forEach(fn, ctx) lunr.SortedSet.prototype.indexOf = function (elem, start, end) {
} var start = start || 0,
end = end || this.elements.length,
sectionLength = end - start,
pivot = start + Math.floor(sectionLength / 2),
pivotElem = this.elements[pivot]
if (sectionLength <= 1) {
if (pivotElem === elem) {
return pivot
} else {
return -1
}
}
/** if (pivotElem < elem) return this.indexOf(elem, pivot, end)
* Returns the index at which a given element can be found in the if (pivotElem > elem) return this.indexOf(elem, start, pivot)
* sorted set, or -1 if it is not present. if (pivotElem === elem) return pivot
* }
* @param {Object} elem The object to locate in the sorted set.
* @param {Number} start An optional index at which to start searching from /**
* within the set. * Returns the position within the sorted set that an element should be
* @param {Number} end An optional index at which to stop search from within * inserted at to maintain the current order of the set.
* the set. *
* @returns {Number} * This function assumes that the element to search for does not already exist
* @memberOf SortedSet * in the sorted set.
*/ *
lunr.SortedSet.prototype.indexOf = function (elem, start, end) { * @param {Object} elem The elem to find the position for in the set
var start = start || 0, * @param {Number} start An optional index at which to start searching from
end = end || this.elements.length, * within the set.
sectionLength = end - start, * @param {Number} end An optional index at which to stop search from within
pivot = start + Math.floor(sectionLength / 2), * the set.
pivotElem = this.elements[pivot] * @returns {Number}
* @memberOf SortedSet
if (sectionLength <= 1) { */
if (pivotElem === elem) { lunr.SortedSet.prototype.locationFor = function (elem, start, end) {
return pivot var start = start || 0,
} else { end = end || this.elements.length,
return -1 sectionLength = end - start,
pivot = start + Math.floor(sectionLength / 2),
pivotElem = this.elements[pivot]
if (sectionLength <= 1) {
if (pivotElem > elem) return pivot
if (pivotElem < elem) return pivot + 1
} }
if (pivotElem < elem) return this.locationFor(elem, pivot, end)
if (pivotElem > elem) return this.locationFor(elem, start, pivot)
} }
if (pivotElem < elem) return this.indexOf(elem, pivot, end) /**
if (pivotElem > elem) return this.indexOf(elem, start, pivot) * Creates a new lunr.SortedSet that contains the elements in the intersection
if (pivotElem === elem) return pivot * of this set and the passed set.
} *
* @param {lunr.SortedSet} otherSet The set to intersect with this set.
* @returns {lunr.SortedSet}
* @memberOf SortedSet
*/
lunr.SortedSet.prototype.intersect = function (otherSet) {
var intersectSet = new lunr.SortedSet,
i = 0, j = 0,
a_len = this.length, b_len = otherSet.length,
a = this.elements, b = otherSet.elements
while (true) {
if (i > a_len - 1 || j > b_len - 1) break
if (a[i] === b[j]) {
intersectSet.add(a[i])
i++, j++
continue
}
/** if (a[i] < b[j]) {
* Returns the position within the sorted set that an element should be i++
* inserted at to maintain the current order of the set. continue
* }
* This function assumes that the element to search for does not already exist
* in the sorted set.
*
* @param {Object} elem The elem to find the position for in the set
* @param {Number} start An optional index at which to start searching from
* within the set.
* @param {Number} end An optional index at which to stop search from within
* the set.
* @returns {Number}
* @memberOf SortedSet
*/
lunr.SortedSet.prototype.locationFor = function (elem, start, end) {
var start = start || 0,
end = end || this.elements.length,
sectionLength = end - start,
pivot = start + Math.floor(sectionLength / 2),
pivotElem = this.elements[pivot]
if (sectionLength <= 1) { if (a[i] > b[j]) {
if (pivotElem > elem) return pivot j++
if (pivotElem < elem) return pivot + 1 continue
}
};
return intersectSet
} }
if (pivotElem < elem) return this.locationFor(elem, pivot, end) /**
if (pivotElem > elem) return this.locationFor(elem, start, pivot) * Makes a copy of this set
} *
* @returns {lunr.SortedSet}
* @memberOf SortedSet
*/
lunr.SortedSet.prototype.clone = function () {
var clone = new lunr.SortedSet
/** clone.elements = this.toArray()
* Creates a new lunr.SortedSet that contains the elements in the intersection clone.length = clone.elements.length
* of this set and the passed set.
*
* @param {lunr.SortedSet} otherSet The set to intersect with this set.
* @returns {lunr.SortedSet}
* @memberOf SortedSet
*/
lunr.SortedSet.prototype.intersect = function (otherSet) {
var intersectSet = new lunr.SortedSet,
i = 0, j = 0,
a_len = this.length, b_len = otherSet.length,
a = this.elements, b = otherSet.elements
while (true) {
if (i > a_len - 1 || j > b_len - 1) break
if (a[i] === b[j]) {
intersectSet.add(a[i])
i++, j++
continue
}
if (a[i] < b[j]) { return clone
i++ }
continue
}
if (a[i] > b[j]) { /**
j++ * Creates a new lunr.SortedSet that contains the elements in the union
continue * of this set and the passed set.
*
* @param {lunr.SortedSet} otherSet The set to union with this set.
* @returns {lunr.SortedSet}
* @memberOf SortedSet
*/
lunr.SortedSet.prototype.union = function (otherSet) {
var longSet, shortSet, unionSet
if (this.length >= otherSet.length) {
longSet = this, shortSet = otherSet
} else {
longSet = otherSet, shortSet = this
} }
};
return intersectSet unionSet = longSet.clone()
}
/** unionSet.add.apply(unionSet, shortSet.toArray())
* Makes a copy of this set
*
* @returns {lunr.SortedSet}
* @memberOf SortedSet
*/
lunr.SortedSet.prototype.clone = function () {
var clone = new lunr.SortedSet
clone.elements = this.toArray() return unionSet
clone.length = clone.elements.length }
return clone /**
} * Returns a representation of the sorted set ready for serialisation.
*
* @returns {Array}
* @memberOf SortedSet
*/
lunr.SortedSet.prototype.toJSON = function () {
return this.toArray()
}
/*!
* lunr.Index
* Copyright (C) 2014 Oliver Nightingale
*/
/** /**
* Creates a new lunr.SortedSet that contains the elements in the union * lunr.Index is object that manages a search index. It contains the indexes
* of this set and the passed set. * and stores all the tokens and document lookups. It also provides the main
* * user facing API for the library.
* @param {lunr.SortedSet} otherSet The set to union with this set. *
* @returns {lunr.SortedSet} * @constructor
* @memberOf SortedSet */
*/ lunr.Index = function () {
lunr.SortedSet.prototype.union = function (otherSet) { this._fields = []
var longSet, shortSet, unionSet this._ref = 'id'
this.pipeline = new lunr.Pipeline
this.documentStore = new lunr.Store
this.tokenStore = new lunr.TokenStore
this.corpusTokens = new lunr.SortedSet
this.eventEmitter = new lunr.EventEmitter
if (this.length >= otherSet.length) { this._idfCache = {}
longSet = this, shortSet = otherSet
} else {
longSet = otherSet, shortSet = this
}
unionSet = longSet.clone() this.on('add', 'remove', 'update', (function () {
this._idfCache = {}
}).bind(this))
}
unionSet.add.apply(unionSet, shortSet.toArray()) /**
* Bind a handler to events being emitted by the index.
*
* The handler can be bound to many events at the same time.
*
* @param {String} [eventName] The name(s) of events to bind the function to.
* @param {Function} handler The serialised set to load.
* @memberOf Index
*/
lunr.Index.prototype.on = function () {
var args = Array.prototype.slice.call(arguments)
return this.eventEmitter.addListener.apply(this.eventEmitter, args)
}
return unionSet /**
} * Removes a handler from an event being emitted by the index.
*
* @param {String} eventName The name of events to remove the function from.
* @param {Function} handler The serialised set to load.
* @memberOf Index
*/
lunr.Index.prototype.off = function (name, fn) {
return this.eventEmitter.removeListener(name, fn)
}
/** /**
* Returns a representation of the sorted set ready for serialisation. * Loads a previously serialised index.
* *
* @returns {Array} * Issues a warning if the index being imported was serialised
* @memberOf SortedSet * by a different version of lunr.
*/ *
lunr.SortedSet.prototype.toJSON = function () { * @param {Object} serialisedData The serialised set to load.
return this.toArray() * @returns {lunr.Index}
} * @memberOf Index
/*! */
* lunr.Index lunr.Index.load = function (serialisedData) {
* Copyright (C) 2014 Oliver Nightingale if (serialisedData.version !== lunr.version) {
*/ lunr.utils.warn('version mismatch: current ' + lunr.version + ' importing ' + serialisedData.version)
}
/** var idx = new this
* lunr.Index is object that manages a search index. It contains the indexes
* and stores all the tokens and document lookups. It also provides the main
* user facing API for the library.
*
* @constructor
*/
lunr.Index = function () {
this._fields = []
this._ref = 'id'
this.pipeline = new lunr.Pipeline
this.documentStore = new lunr.Store
this.tokenStore = new lunr.TokenStore
this.corpusTokens = new lunr.SortedSet
this.eventEmitter = new lunr.EventEmitter
this._idfCache = {}
this.on('add', 'remove', 'update', (function () {
this._idfCache = {}
}).bind(this))
}
/** idx._fields = serialisedData.fields
* Bind a handler to events being emitted by the index. idx._ref = serialisedData.ref
*
* The handler can be bound to many events at the same time.
*
* @param {String} [eventName] The name(s) of events to bind the function to.
* @param {Function} handler The serialised set to load.
* @memberOf Index
*/
lunr.Index.prototype.on = function () {
var args = Array.prototype.slice.call(arguments)
return this.eventEmitter.addListener.apply(this.eventEmitter, args)
}
/** idx.documentStore = lunr.Store.load(serialisedData.documentStore)
* Removes a handler from an event being emitted by the index. idx.tokenStore = lunr.TokenStore.load(serialisedData.tokenStore)
* idx.corpusTokens = lunr.SortedSet.load(serialisedData.corpusTokens)
* @param {String} eventName The name of events to remove the function from. idx.pipeline = lunr.Pipeline.load(serialisedData.pipeline)
* @param {Function} handler The serialised set to load.
* @memberOf Index
*/
lunr.Index.prototype.off = function (name, fn) {
return this.eventEmitter.removeListener(name, fn)
}
/** return idx
* Loads a previously serialised index.
*
* Issues a warning if the index being imported was serialised
* by a different version of lunr.
*
* @param {Object} serialisedData The serialised set to load.
* @returns {lunr.Index}
* @memberOf Index
*/
lunr.Index.load = function (serialisedData) {
if (serialisedData.version !== lunr.version) {
lunr.utils.warn('version mismatch: current ' + lunr.version + ' importing ' + serialisedData.version)
} }
var idx = new this /**
* Adds a field to the list of fields that will be searchable within documents
idx._fields = serialisedData.fields * in the index.
idx._ref = serialisedData.ref *
* An optional boost param can be passed to affect how much tokens in this field
* rank in search results, by default the boost value is 1.
*
* Fields should be added before any documents are added to the index, fields
* that are added after documents are added to the index will only apply to new
* documents added to the index.
*
* @param {String} fieldName The name of the field within the document that
* should be indexed
* @param {Number} boost An optional boost that can be applied to terms in this
* field.
* @returns {lunr.Index}
* @memberOf Index
*/
lunr.Index.prototype.field = function (fieldName, opts) {
var opts = opts || {},
field = { name: fieldName, boost: opts.boost || 1 }
idx.documentStore = lunr.Store.load(serialisedData.documentStore) this._fields.push(field)
idx.tokenStore = lunr.TokenStore.load(serialisedData.tokenStore) return this
idx.corpusTokens = lunr.SortedSet.load(serialisedData.corpusTokens) }
idx.pipeline = lunr.Pipeline.load(serialisedData.pipeline)
return idx /**
} * Sets the property used to uniquely identify documents added to the index,
* by default this property is 'id'.
*
* This should only be changed before adding documents to the index, changing
* the ref property without resetting the index can lead to unexpected results.
*
* @param {String} refName The property to use to uniquely identify the
* documents in the index.
* @param {Boolean} emitEvent Whether to emit add events, defaults to true
* @returns {lunr.Index}
* @memberOf Index
*/
lunr.Index.prototype.ref = function (refName) {
this._ref = refName
return this
}
/** /**
* Adds a field to the list of fields that will be searchable within documents * Add a document to the index.
* in the index. *
* * This is the way new documents enter the index, this function will run the
* An optional boost param can be passed to affect how much tokens in this field * fields from the document through the index's pipeline and then add it to
* rank in search results, by default the boost value is 1. * the index, it will then show up in search results.
* *
* Fields should be added before any documents are added to the index, fields * An 'add' event is emitted with the document that has been added and the index
* that are added after documents are added to the index will only apply to new * the document has been added to. This event can be silenced by passing false
* documents added to the index. * as the second argument to add.
* *
* @param {String} fieldName The name of the field within the document that * @param {Object} doc The document to add to the index.
* should be indexed * @param {Boolean} emitEvent Whether or not to emit events, default true.
* @param {Number} boost An optional boost that can be applied to terms in this * @memberOf Index
* field. */
* @returns {lunr.Index} lunr.Index.prototype.add = function (doc, emitEvent) {
* @memberOf Index var docTokens = {},
*/ allDocumentTokens = new lunr.SortedSet,
lunr.Index.prototype.field = function (fieldName, opts) { docRef = doc[this._ref],
var opts = opts || {}, emitEvent = emitEvent === undefined ? true : emitEvent
field = { name: fieldName, boost: opts.boost || 1 }
this._fields.push(field) this._fields.forEach(function (field) {
return this var fieldTokens = this.pipeline.run(lunr.tokenizer(doc[field.name]))
}
/** docTokens[field.name] = fieldTokens
* Sets the property used to uniquely identify documents added to the index, lunr.SortedSet.prototype.add.apply(allDocumentTokens, fieldTokens)
* by default this property is 'id'. }, this)
*
* This should only be changed before adding documents to the index, changing
* the ref property without resetting the index can lead to unexpected results.
*
* @param {String} refName The property to use to uniquely identify the
* documents in the index.
* @param {Boolean} emitEvent Whether to emit add events, defaults to true
* @returns {lunr.Index}
* @memberOf Index
*/
lunr.Index.prototype.ref = function (refName) {
this._ref = refName
return this
}
/** this.documentStore.set(docRef, allDocumentTokens)
* Add a document to the index. lunr.SortedSet.prototype.add.apply(this.corpusTokens, allDocumentTokens.toArray())
*
* This is the way new documents enter the index, this function will run the
* fields from the document through the index's pipeline and then add it to
* the index, it will then show up in search results.
*
* An 'add' event is emitted with the document that has been added and the index
* the document has been added to. This event can be silenced by passing false
* as the second argument to add.
*
* @param {Object} doc The document to add to the index.
* @param {Boolean} emitEvent Whether or not to emit events, default true.
* @memberOf Index
*/
lunr.Index.prototype.add = function (doc, emitEvent) {
var docTokens = {},
allDocumentTokens = new lunr.SortedSet,
docRef = doc[this._ref],
emitEvent = emitEvent === undefined ? true : emitEvent
this._fields.forEach(function (field) { for (var i = 0; i < allDocumentTokens.length; i++) {
var fieldTokens = this.pipeline.run(lunr.tokenizer(doc[field.name])) var token = allDocumentTokens.elements[i]
var tf = this._fields.reduce(function (memo, field) {
var fieldLength = docTokens[field.name].length
docTokens[field.name] = fieldTokens if (!fieldLength) return memo
lunr.SortedSet.prototype.add.apply(allDocumentTokens, fieldTokens)
}, this)
this.documentStore.set(docRef, allDocumentTokens) var tokenCount = docTokens[field.name].filter(function (t) { return t === token }).length
lunr.SortedSet.prototype.add.apply(this.corpusTokens, allDocumentTokens.toArray())
for (var i = 0; i < allDocumentTokens.length; i++) { return memo + (tokenCount / fieldLength * field.boost)
var token = allDocumentTokens.elements[i] }, 0)
var tf = this._fields.reduce(function (memo, field) {
var fieldLength = docTokens[field.name].length
if (!fieldLength) return memo this.tokenStore.add(token, { ref: docRef, tf: tf })
};
var tokenCount = docTokens[field.name].filter(function (t) { return t === token }).length if (emitEvent) this.eventEmitter.emit('add', doc, this)
}
return memo + (tokenCount / fieldLength * field.boost) /**
}, 0) * Removes a document from the index.
*
* To make sure documents no longer show up in search results they can be
* removed from the index using this method.
*
* The document passed only needs to have the same ref property value as the
* document that was added to the index, they could be completely different
* objects.
*
* A 'remove' event is emitted with the document that has been removed and the index
* the document has been removed from. This event can be silenced by passing false
* as the second argument to remove.
*
* @param {Object} doc The document to remove from the index.
* @param {Boolean} emitEvent Whether to emit remove events, defaults to true
* @memberOf Index
*/
lunr.Index.prototype.remove = function (doc, emitEvent) {
var docRef = doc[this._ref],
emitEvent = emitEvent === undefined ? true : emitEvent
this.tokenStore.add(token, { ref: docRef, tf: tf }) if (!this.documentStore.has(docRef)) return
};
if (emitEvent) this.eventEmitter.emit('add', doc, this) var docTokens = this.documentStore.get(docRef)
}
/** this.documentStore.remove(docRef)
* Removes a document from the index.
*
* To make sure documents no longer show up in search results they can be
* removed from the index using this method.
*
* The document passed only needs to have the same ref property value as the
* document that was added to the index, they could be completely different
* objects.
*
* A 'remove' event is emitted with the document that has been removed and the index
* the document has been removed from. This event can be silenced by passing false
* as the second argument to remove.
*
* @param {Object} doc The document to remove from the index.
* @param {Boolean} emitEvent Whether to emit remove events, defaults to true
* @memberOf Index
*/
lunr.Index.prototype.remove = function (doc, emitEvent) {
var docRef = doc[this._ref],
emitEvent = emitEvent === undefined ? true : emitEvent
if (!this.documentStore.has(docRef)) return docTokens.forEach(function (token) {
this.tokenStore.remove(token, docRef)
}, this)
var docTokens = this.documentStore.get(docRef) if (emitEvent) this.eventEmitter.emit('remove', doc, this)
}
this.documentStore.remove(docRef) /**
* Updates a document in the index.
*
* When a document contained within the index gets updated, fields changed,
* added or removed, to make sure it correctly matched against search queries,
* it should be updated in the index.
*
* This method is just a wrapper around `remove` and `add`
*
* An 'update' event is emitted with the document that has been updated and the index.
* This event can be silenced by passing false as the second argument to update. Only
* an update event will be fired, the 'add' and 'remove' events of the underlying calls
* are silenced.
*
* @param {Object} doc The document to update in the index.
* @param {Boolean} emitEvent Whether to emit update events, defaults to true
* @see Index.prototype.remove
* @see Index.prototype.add
* @memberOf Index
*/
lunr.Index.prototype.update = function (doc, emitEvent) {
var emitEvent = emitEvent === undefined ? true : emitEvent
docTokens.forEach(function (token) { this.remove(doc, false)
this.tokenStore.remove(token, docRef) this.add(doc, false)
}, this)
if (emitEvent) this.eventEmitter.emit('remove', doc, this) if (emitEvent) this.eventEmitter.emit('update', doc, this)
} }
/** /**
* Updates a document in the index. * Calculates the inverse document frequency for a token within the index.
* *
* When a document contained within the index gets updated, fields changed, * @param {String} token The token to calculate the idf of.
* added or removed, to make sure it correctly matched against search queries, * @see Index.prototype.idf
* it should be updated in the index. * @private
* * @memberOf Index
* This method is just a wrapper around `remove` and `add` */
* lunr.Index.prototype.idf = function (term) {
* An 'update' event is emitted with the document that has been updated and the index. var cacheKey = "@" + term
* This event can be silenced by passing false as the second argument to update. Only if (Object.prototype.hasOwnProperty.call(this._idfCache, cacheKey)) return this._idfCache[cacheKey]
* an update event will be fired, the 'add' and 'remove' events of the underlying calls
* are silenced.
*
* @param {Object} doc The document to update in the index.
* @param {Boolean} emitEvent Whether to emit update events, defaults to true
* @see Index.prototype.remove
* @see Index.prototype.add
* @memberOf Index
*/
lunr.Index.prototype.update = function (doc, emitEvent) {
var emitEvent = emitEvent === undefined ? true : emitEvent
this.remove(doc, false) var documentFrequency = this.tokenStore.count(term),
this.add(doc, false) idf = 1
if (emitEvent) this.eventEmitter.emit('update', doc, this) if (documentFrequency > 0) {
} idf = 1 + Math.log(this.tokenStore.length / documentFrequency)
}
/** return this._idfCache[cacheKey] = idf
* Calculates the inverse document frequency for a token within the index. }
*
* @param {String} token The token to calculate the idf of.
* @see Index.prototype.idf
* @private
* @memberOf Index
*/
lunr.Index.prototype.idf = function (term) {
var cacheKey = "@" + term
if (Object.prototype.hasOwnProperty.call(this._idfCache, cacheKey)) return this._idfCache[cacheKey]
var documentFrequency = this.tokenStore.count(term), /**
idf = 1 * Searches the index using the passed query.
*
* Queries should be a string, multiple words are allowed and will lead to an
* AND based query, e.g. `idx.search('foo bar')` will run a search for
* documents containing both 'foo' and 'bar'.
*
* All query tokens are passed through the same pipeline that document tokens
* are passed through, so any language processing involved will be run on every
* query term.
*
* Each query term is expanded, so that the term 'he' might be expanded to
* 'hello' and 'help' if those terms were already included in the index.
*
* Matching documents are returned as an array of objects, each object contains
* the matching document ref, as set for this index, and the similarity score
* for this document against the query.
*
* @param {String} query The query to search the index with.
* @returns {Object}
* @see Index.prototype.idf
* @see Index.prototype.documentVector
* @memberOf Index
*/
lunr.Index.prototype.search = function (query) {
var queryTokens = this.pipeline.run(lunr.tokenizer(query)),
queryVector = new lunr.Vector,
documentSets = [],
fieldBoosts = this._fields.reduce(function (memo, f) { return memo + f.boost }, 0)
var hasSomeToken = queryTokens.some(function (token) {
return this.tokenStore.has(token)
}, this)
if (documentFrequency > 0) { if (!hasSomeToken) return []
idf = 1 + Math.log(this.tokenStore.length / documentFrequency)
}
return this._idfCache[cacheKey] = idf queryTokens
} .forEach(function (token, i, tokens) {
var tf = 1 / tokens.length * this._fields.length * fieldBoosts,
self = this
/** var set = this.tokenStore.expand(token).reduce(function (memo, key) {
* Searches the index using the passed query. var pos = self.corpusTokens.indexOf(key),
* idf = self.idf(key),
* Queries should be a string, multiple words are allowed and will lead to an similarityBoost = 1,
* AND based query, e.g. `idx.search('foo bar')` will run a search for set = new lunr.SortedSet
* documents containing both 'foo' and 'bar'.
*
* All query tokens are passed through the same pipeline that document tokens
* are passed through, so any language processing involved will be run on every
* query term.
*
* Each query term is expanded, so that the term 'he' might be expanded to
* 'hello' and 'help' if those terms were already included in the index.
*
* Matching documents are returned as an array of objects, each object contains
* the matching document ref, as set for this index, and the similarity score
* for this document against the query.
*
* @param {String} query The query to search the index with.
* @returns {Object}
* @see Index.prototype.idf
* @see Index.prototype.documentVector
* @memberOf Index
*/
lunr.Index.prototype.search = function (query) {
var queryTokens = this.pipeline.run(lunr.tokenizer(query)),
queryVector = new lunr.Vector,
documentSets = [],
fieldBoosts = this._fields.reduce(function (memo, f) { return memo + f.boost }, 0)
var hasSomeToken = queryTokens.some(function (token) {
return this.tokenStore.has(token)
}, this)
if (!hasSomeToken) return []
queryTokens
.forEach(function (token, i, tokens) {
var tf = 1 / tokens.length * this._fields.length * fieldBoosts,
self = this
var set = this.tokenStore.expand(token).reduce(function (memo, key) {
var pos = self.corpusTokens.indexOf(key),
idf = self.idf(key),
similarityBoost = 1,
set = new lunr.SortedSet
// if the expanded key is not an exact match to the token then
// penalise the score for this key by how different the key is
// to the token.
if (key !== token) {
var diff = Math.max(3, key.length - token.length)
similarityBoost = 1 / Math.log(diff)
}
// calculate the query tf-idf score for this token // if the expanded key is not an exact match to the token then
// applying an similarityBoost to ensure exact matches // penalise the score for this key by how different the key is
// these rank higher than expanded terms // to the token.
if (pos > -1) queryVector.insert(pos, tf * idf * similarityBoost) if (key !== token) {
var diff = Math.max(3, key.length - token.length)
similarityBoost = 1 / Math.log(diff)
}
// add all the documents that have this key into a set // calculate the query tf-idf score for this token
Object.keys(self.tokenStore.get(key)).forEach(function (ref) { set.add(ref) }) // applying an similarityBoost to ensure exact matches
// these rank higher than expanded terms
if (pos > -1) queryVector.insert(pos, tf * idf * similarityBoost)
return memo.union(set) // add all the documents that have this key into a set
}, new lunr.SortedSet) Object.keys(self.tokenStore.get(key)).forEach(function (ref) { set.add(ref) })
documentSets.push(set) return memo.union(set)
}, this) }, new lunr.SortedSet)
var documentSet = documentSets.reduce(function (memo, set) { documentSets.push(set)
return memo.intersect(set) }, this)
})
return documentSet var documentSet = documentSets.reduce(function (memo, set) {
.map(function (ref) { return memo.intersect(set)
return { ref: ref, score: queryVector.similarity(this.documentVector(ref)) }
}, this)
.sort(function (a, b) {
return b.score - a.score
}) })
}
/** return documentSet
* Generates a vector containing all the tokens in the document matching the .map(function (ref) {
* passed documentRef. return { ref: ref, score: queryVector.similarity(this.documentVector(ref)) }
* }, this)
* The vector contains the tf-idf score for each token contained in the .sort(function (a, b) {
* document with the passed documentRef. The vector will contain an element return b.score - a.score
* for every token in the indexes corpus, if the document does not contain that })
* token the element will be 0. }
*
* @param {Object} documentRef The ref to find the document with.
* @returns {lunr.Vector}
* @private
* @memberOf Index
*/
lunr.Index.prototype.documentVector = function (documentRef) {
var documentTokens = this.documentStore.get(documentRef),
documentTokensLength = documentTokens.length,
documentVector = new lunr.Vector
for (var i = 0; i < documentTokensLength; i++) { /**
var token = documentTokens.elements[i], * Generates a vector containing all the tokens in the document matching the
tf = this.tokenStore.get(token)[documentRef].tf, * passed documentRef.
idf = this.idf(token) *
* The vector contains the tf-idf score for each token contained in the
* document with the passed documentRef. The vector will contain an element
* for every token in the indexes corpus, if the document does not contain that
* token the element will be 0.
*
* @param {Object} documentRef The ref to find the document with.
* @returns {lunr.Vector}
* @private
* @memberOf Index
*/
lunr.Index.prototype.documentVector = function (documentRef) {
var documentTokens = this.documentStore.get(documentRef),
documentTokensLength = documentTokens.length,
documentVector = new lunr.Vector
documentVector.insert(this.corpusTokens.indexOf(token), tf * idf) for (var i = 0; i < documentTokensLength; i++) {
}; var token = documentTokens.elements[i],
tf = this.tokenStore.get(token)[documentRef].tf,
idf = this.idf(token)
return documentVector documentVector.insert(this.corpusTokens.indexOf(token), tf * idf)
} };
/** return documentVector
* Returns a representation of the index ready for serialisation. }
*
* @returns {Object}
* @memberOf Index
*/
lunr.Index.prototype.toJSON = function () {
return {
version: lunr.version,
fields: this._fields,
ref: this._ref,
documentStore: this.documentStore.toJSON(),
tokenStore: this.tokenStore.toJSON(),
corpusTokens: this.corpusTokens.toJSON(),
pipeline: this.pipeline.toJSON()
}
}
/** /**
* Applies a plugin to the current index. * Returns a representation of the index ready for serialisation.
* *
* A plugin is a function that is called with the index as its context. * @returns {Object}
* Plugins can be used to customise or extend the behaviour the index * @memberOf Index
* in some way. A plugin is just a function, that encapsulated the custom */
* behaviour that should be applied to the index. lunr.Index.prototype.toJSON = function () {
* return {
* The plugin function will be called with the index as its argument, additional version: lunr.version,
* arguments can also be passed when calling use. The function will be called fields: this._fields,
* with the index as its context. ref: this._ref,
* documentStore: this.documentStore.toJSON(),
* Example: tokenStore: this.tokenStore.toJSON(),
* corpusTokens: this.corpusTokens.toJSON(),
* var myPlugin = function (idx, arg1, arg2) { pipeline: this.pipeline.toJSON()
}
}
/**
* Applies a plugin to the current index.
*
* A plugin is a function that is called with the index as its context.
* Plugins can be used to customise or extend the behaviour the index
* in some way. A plugin is just a function, that encapsulated the custom
* behaviour that should be applied to the index.
*
* The plugin function will be called with the index as its argument, additional
* arguments can also be passed when calling use. The function will be called
* with the index as its context.
*
* Example:
*
* var myPlugin = function (idx, arg1, arg2) {
* // `this` is the index to be extended * // `this` is the index to be extended
* // apply any extensions etc here. * // apply any extensions etc here.
* } * }
* *
* var idx = lunr(function () { * var idx = lunr(function () {
* this.use(myPlugin, 'arg1', 'arg2') * this.use(myPlugin, 'arg1', 'arg2')
* }) * })
* *
* @param {Function} plugin The plugin to apply. * @param {Function} plugin The plugin to apply.
* @memberOf Index * @memberOf Index
*/ */
lunr.Index.prototype.use = function (plugin) { lunr.Index.prototype.use = function (plugin) {
var args = Array.prototype.slice.call(arguments, 1) var args = Array.prototype.slice.call(arguments, 1)
args.unshift(this) args.unshift(this)
plugin.apply(this, args) plugin.apply(this, args)
} }
/*! /*!
* lunr.Store * lunr.Store
* Copyright (C) 2014 Oliver Nightingale * Copyright (C) 2014 Oliver Nightingale
*/ */
/** /**
* lunr.Store is a simple key-value store used for storing sets of tokens for * lunr.Store is a simple key-value store used for storing sets of tokens for
* documents stored in index. * documents stored in index.
* *
* @constructor * @constructor
* @module * @module
*/ */
lunr.Store = function () { lunr.Store = function () {
this.store = {} this.store = {}
this.length = 0 this.length = 0
} }
/** /**
* Loads a previously serialised store * Loads a previously serialised store
* *
* @param {Object} serialisedData The serialised store to load. * @param {Object} serialisedData The serialised store to load.
* @returns {lunr.Store} * @returns {lunr.Store}
* @memberOf Store * @memberOf Store
*/ */
lunr.Store.load = function (serialisedData) { lunr.Store.load = function (serialisedData) {
var store = new this var store = new this
store.length = serialisedData.length store.length = serialisedData.length
store.store = Object.keys(serialisedData.store).reduce(function (memo, key) { store.store = Object.keys(serialisedData.store).reduce(function (memo, key) {
memo[key] = lunr.SortedSet.load(serialisedData.store[key]) memo[key] = lunr.SortedSet.load(serialisedData.store[key])
return memo return memo
}, {}) }, {})
return store return store
} }
/** /**
* Stores the given tokens in the store against the given id. * Stores the given tokens in the store against the given id.
* *
* @param {Object} id The key used to store the tokens against. * @param {Object} id The key used to store the tokens against.
* @param {Object} tokens The tokens to store against the key. * @param {Object} tokens The tokens to store against the key.
* @memberOf Store * @memberOf Store
*/ */
lunr.Store.prototype.set = function (id, tokens) { lunr.Store.prototype.set = function (id, tokens) {
this.store[id] = tokens if (!this.has(id)) this.length++
this.length = Object.keys(this.store).length this.store[id] = tokens
} }
/** /**
* Retrieves the tokens from the store for a given key. * Retrieves the tokens from the store for a given key.
* *
* @param {Object} id The key to lookup and retrieve from the store. * @param {Object} id The key to lookup and retrieve from the store.
* @returns {Object} * @returns {Object}
* @memberOf Store * @memberOf Store
*/ */
lunr.Store.prototype.get = function (id) { lunr.Store.prototype.get = function (id) {
return this.store[id] return this.store[id]
} }
/** /**
* Checks whether the store contains a key. * Checks whether the store contains a key.
* *
* @param {Object} id The id to look up in the store. * @param {Object} id The id to look up in the store.
* @returns {Boolean} * @returns {Boolean}
* @memberOf Store * @memberOf Store
*/ */
lunr.Store.prototype.has = function (id) { lunr.Store.prototype.has = function (id) {
return id in this.store return id in this.store
} }
/** /**
* Removes the value for a key in the store. * Removes the value for a key in the store.
* *
* @param {Object} id The id to remove from the store. * @param {Object} id The id to remove from the store.
* @memberOf Store * @memberOf Store
*/ */
lunr.Store.prototype.remove = function (id) { lunr.Store.prototype.remove = function (id) {
if (!this.has(id)) return if (!this.has(id)) return
delete this.store[id] delete this.store[id]
this.length-- this.length--
} }
/** /**
* Returns a representation of the store ready for serialisation. * Returns a representation of the store ready for serialisation.
* *
* @returns {Object} * @returns {Object}
* @memberOf Store * @memberOf Store
*/ */
lunr.Store.prototype.toJSON = function () { lunr.Store.prototype.toJSON = function () {
return { return {
store: this.store, store: this.store,
length: this.length length: this.length
}
} }
}
/*! /*!
* lunr.stemmer * lunr.stemmer
* Copyright (C) 2014 Oliver Nightingale * Copyright (C) 2014 Oliver Nightingale
* Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt
*/ */
/** /**
* lunr.stemmer is an english language stemmer, this is a JavaScript * lunr.stemmer is an english language stemmer, this is a JavaScript
* implementation of the PorterStemmer taken from http://tartaurs.org/~martin * implementation of the PorterStemmer taken from http://tartaurs.org/~martin
* *
* @module * @module
* @param {String} str The string to stem * @param {String} str The string to stem
* @returns {String} * @returns {String}
* @see lunr.Pipeline * @see lunr.Pipeline
*/ */
lunr.stemmer = (function(){ lunr.stemmer = (function(){
var step2list = { var step2list = {
"ational" : "ate", "ational" : "ate",
"tional" : "tion", "tional" : "tion",
"enci" : "ence", "enci" : "ence",
"anci" : "ance", "anci" : "ance",
"izer" : "ize", "izer" : "ize",
"bli" : "ble", "bli" : "ble",
"alli" : "al", "alli" : "al",
"entli" : "ent", "entli" : "ent",
"eli" : "e", "eli" : "e",
"ousli" : "ous", "ousli" : "ous",
"ization" : "ize", "ization" : "ize",
"ation" : "ate", "ation" : "ate",
"ator" : "ate", "ator" : "ate",
"alism" : "al", "alism" : "al",
"iveness" : "ive", "iveness" : "ive",
"fulness" : "ful", "fulness" : "ful",
"ousness" : "ous", "ousness" : "ous",
"aliti" : "al", "aliti" : "al",
"iviti" : "ive", "iviti" : "ive",
"biliti" : "ble", "biliti" : "ble",
"logi" : "log" "logi" : "log"
}, },
step3list = { step3list = {
"icate" : "ic", "icate" : "ic",
"ative" : "", "ative" : "",
"alize" : "al", "alize" : "al",
"iciti" : "ic", "iciti" : "ic",
"ical" : "ic", "ical" : "ic",
"ful" : "", "ful" : "",
"ness" : "" "ness" : ""
}, },
c = "[^aeiou]", // consonant c = "[^aeiou]", // consonant
v = "[aeiouy]", // vowel v = "[aeiouy]", // vowel
C = c + "[^aeiouy]*", // consonant sequence C = c + "[^aeiouy]*", // consonant sequence
V = v + "[aeiou]*", // vowel sequence V = v + "[aeiou]*", // vowel sequence
mgr0 = "^(" + C + ")?" + V + C, // [C]VC... is m>0 mgr0 = "^(" + C + ")?" + V + C, // [C]VC... is m>0
meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$", // [C]VC[V] is m=1 meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$", // [C]VC[V] is m=1
mgr1 = "^(" + C + ")?" + V + C + V + C, // [C]VCVC... is m>1 mgr1 = "^(" + C + ")?" + V + C + V + C, // [C]VCVC... is m>1
s_v = "^(" + C + ")?" + v; // vowel in stem s_v = "^(" + C + ")?" + v; // vowel in stem
return function (w) { var re_mgr0 = new RegExp(mgr0);
var stem, var re_mgr1 = new RegExp(mgr1);
suffix, var re_meq1 = new RegExp(meq1);
firstch, var re_s_v = new RegExp(s_v);
re,
re2, var re_1a = /^(.+?)(ss|i)es$/;
re3, var re2_1a = /^(.+?)([^s])s$/;
re4; var re_1b = /^(.+?)eed$/;
var re2_1b = /^(.+?)(ed|ing)$/;
if (w.length < 3) { return w; } var re_1b_2 = /.$/;
var re2_1b_2 = /(at|bl|iz)$/;
firstch = w.substr(0,1); var re3_1b_2 = new RegExp("([^aeiouylsz])\\1$");
if (firstch == "y") { var re4_1b_2 = new RegExp("^" + C + v + "[^aeiouwxy]$");
w = firstch.toUpperCase() + w.substr(1);
} var re_1c = /^(.+?[^aeiou])y$/;
var re_2 = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;
var re_3 = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;
var re_4 = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;
var re2_4 = /^(.+?)(s|t)(ion)$/;
var re_5 = /^(.+?)e$/;
var re_5_1 = /ll$/;
var re3_5 = new RegExp("^" + C + v + "[^aeiouwxy]$");
var porterStemmer = function porterStemmer(w) {
var stem,
suffix,
firstch,
re,
re2,
re3,
re4;
if (w.length < 3) { return w; }
firstch = w.substr(0,1);
if (firstch == "y") {
w = firstch.toUpperCase() + w.substr(1);
}
// Step 1a // Step 1a
re = /^(.+?)(ss|i)es$/; re = re_1a
re2 = /^(.+?)([^s])s$/; re2 = re2_1a;
if (re.test(w)) { w = w.replace(re,"$1$2"); } if (re.test(w)) { w = w.replace(re,"$1$2"); }
else if (re2.test(w)) { w = w.replace(re2,"$1$2"); } else if (re2.test(w)) { w = w.replace(re2,"$1$2"); }
// Step 1b // Step 1b
re = /^(.+?)eed$/; re = re_1b;
re2 = /^(.+?)(ed|ing)$/; re2 = re2_1b;
if (re.test(w)) { if (re.test(w)) {
var fp = re.exec(w); var fp = re.exec(w);
re = new RegExp(mgr0); re = re_mgr0;
if (re.test(fp[1])) { if (re.test(fp[1])) {
re = /.$/; re = re_1b_2;
w = w.replace(re,""); w = w.replace(re,"");
}
} else if (re2.test(w)) {
var fp = re2.exec(w);
stem = fp[1];
re2 = re_s_v;
if (re2.test(stem)) {
w = stem;
re2 = re2_1b_2;
re3 = re3_1b_2;
re4 = re4_1b_2;
if (re2.test(w)) { w = w + "e"; }
else if (re3.test(w)) { re = re_1b_2; w = w.replace(re,""); }
else if (re4.test(w)) { w = w + "e"; }
}
} }
} else if (re2.test(w)) {
var fp = re2.exec(w); // Step 1c - replace suffix y or Y by i if preceded by a non-vowel which is not the first letter of the word (so cry -> cri, by -> by, say -> say)
stem = fp[1]; re = re_1c;
re2 = new RegExp(s_v); if (re.test(w)) {
if (re2.test(stem)) { var fp = re.exec(w);
w = stem; stem = fp[1];
re2 = /(at|bl|iz)$/; w = stem + "i";
re3 = new RegExp("([^aeiouylsz])\\1$");
re4 = new RegExp("^" + C + v + "[^aeiouwxy]$");
if (re2.test(w)) { w = w + "e"; }
else if (re3.test(w)) { re = /.$/; w = w.replace(re,""); }
else if (re4.test(w)) { w = w + "e"; }
} }
}
// Step 1c // Step 2
re = /^(.+?)y$/; re = re_2;
if (re.test(w)) { if (re.test(w)) {
var fp = re.exec(w); var fp = re.exec(w);
stem = fp[1]; stem = fp[1];
re = new RegExp(s_v); suffix = fp[2];
if (re.test(stem)) { w = stem + "i"; } re = re_mgr0;
} if (re.test(stem)) {
w = stem + step2list[suffix];
}
}
// Step 2 // Step 3
re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; re = re_3;
if (re.test(w)) { if (re.test(w)) {
var fp = re.exec(w); var fp = re.exec(w);
stem = fp[1]; stem = fp[1];
suffix = fp[2]; suffix = fp[2];
re = new RegExp(mgr0); re = re_mgr0;
if (re.test(stem)) { if (re.test(stem)) {
w = stem + step2list[suffix]; w = stem + step3list[suffix];
}
} }
}
// Step 3 // Step 4
re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; re = re_4;
if (re.test(w)) { re2 = re2_4;
var fp = re.exec(w); if (re.test(w)) {
stem = fp[1]; var fp = re.exec(w);
suffix = fp[2]; stem = fp[1];
re = new RegExp(mgr0); re = re_mgr1;
if (re.test(stem)) { if (re.test(stem)) {
w = stem + step3list[suffix]; w = stem;
}
} else if (re2.test(w)) {
var fp = re2.exec(w);
stem = fp[1] + fp[2];
re2 = re_mgr1;
if (re2.test(stem)) {
w = stem;
}
} }
}
// Step 4 // Step 5
re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; re = re_5;
re2 = /^(.+?)(s|t)(ion)$/; if (re.test(w)) {
if (re.test(w)) { var fp = re.exec(w);
var fp = re.exec(w); stem = fp[1];
stem = fp[1]; re = re_mgr1;
re = new RegExp(mgr1); re2 = re_meq1;
if (re.test(stem)) { re3 = re3_5;
w = stem; if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) {
w = stem;
}
} }
} else if (re2.test(w)) {
var fp = re2.exec(w); re = re_5_1;
stem = fp[1] + fp[2]; re2 = re_mgr1;
re2 = new RegExp(mgr1); if (re.test(w) && re2.test(w)) {
if (re2.test(stem)) { re = re_1b_2;
w = stem; w = w.replace(re,"");
} }
}
// Step 5 // and turn initial Y back to y
re = /^(.+?)e$/;
if (re.test(w)) { if (firstch == "y") {
var fp = re.exec(w); w = firstch.toLowerCase() + w.substr(1);
stem = fp[1];
re = new RegExp(mgr1);
re2 = new RegExp(meq1);
re3 = new RegExp("^" + C + v + "[^aeiouwxy]$");
if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) {
w = stem;
} }
}
re = /ll$/; return w;
re2 = new RegExp(mgr1); };
if (re.test(w) && re2.test(w)) {
re = /.$/;
w = w.replace(re,"");
}
// and turn initial Y back to y return porterStemmer;
})();
if (firstch == "y") { lunr.Pipeline.registerFunction(lunr.stemmer, 'stemmer')
w = firstch.toLowerCase() + w.substr(1); /*!
} * lunr.stopWordFilter
* Copyright (C) 2014 Oliver Nightingale
*/
return w; /**
* lunr.stopWordFilter is an English language stop word list filter, any words
* contained in the list will not be passed through the filter.
*
* This is intended to be used in the Pipeline. If the token does not pass the
* filter then undefined will be returned.
*
* @module
* @param {String} token The token to pass through the filter
* @returns {String}
* @see lunr.Pipeline
*/
lunr.stopWordFilter = function (token) {
if (lunr.stopWordFilter.stopWords.indexOf(token) === -1) return token
} }
})();
lunr.Pipeline.registerFunction(lunr.stemmer, 'stemmer') lunr.stopWordFilter.stopWords = new lunr.SortedSet
/*! lunr.stopWordFilter.stopWords.length = 119
* lunr.stopWordFilter lunr.stopWordFilter.stopWords.elements = [
* Copyright (C) 2014 Oliver Nightingale "",
*/ "a",
"able",
"about",
"across",
"after",
"all",
"almost",
"also",
"am",
"among",
"an",
"and",
"any",
"are",
"as",
"at",
"be",
"because",
"been",
"but",
"by",
"can",
"cannot",
"could",
"dear",
"did",
"do",
"does",
"either",
"else",
"ever",
"every",
"for",
"from",
"get",
"got",
"had",
"has",
"have",
"he",
"her",
"hers",
"him",
"his",
"how",
"however",
"i",
"if",
"in",
"into",
"is",
"it",
"its",
"just",
"least",
"let",
"like",
"likely",
"may",
"me",
"might",
"most",
"must",
"my",
"neither",
"no",
"nor",
"not",
"of",
"off",
"often",
"on",
"only",
"or",
"other",
"our",
"own",
"rather",
"said",
"say",
"says",
"she",
"should",
"since",
"so",
"some",
"than",
"that",
"the",
"their",
"them",
"then",
"there",
"these",
"they",
"this",
"tis",
"to",
"too",
"twas",
"us",
"wants",
"was",
"we",
"were",
"what",
"when",
"where",
"which",
"while",
"who",
"whom",
"why",
"will",
"with",
"would",
"yet",
"you",
"your"
]
lunr.Pipeline.registerFunction(lunr.stopWordFilter, 'stopWordFilter')
/*!
* lunr.trimmer
* Copyright (C) 2014 Oliver Nightingale
*/
/** /**
* lunr.stopWordFilter is an English language stop word list filter, any words * lunr.trimmer is a pipeline function for trimming non word
* contained in the list will not be passed through the filter. * characters from the begining and end of tokens before they
* * enter the index.
* This is intended to be used in the Pipeline. If the token does not pass the *
* filter then undefined will be returned. * This implementation may not work correctly for non latin
* * characters and should either be removed or adapted for use
* @module * with languages with non-latin characters.
* @param {String} token The token to pass through the filter *
* @returns {String} * @module
* @see lunr.Pipeline * @param {String} token The token to pass through the filter
*/ * @returns {String}
lunr.stopWordFilter = function (token) { * @see lunr.Pipeline
if (lunr.stopWordFilter.stopWords.indexOf(token) === -1) return token */
} lunr.trimmer = function (token) {
return token
lunr.stopWordFilter.stopWords = new lunr.SortedSet .replace(/^\W+/, '')
lunr.stopWordFilter.stopWords.length = 119 .replace(/\W+$/, '')
lunr.stopWordFilter.stopWords.elements = [ }
"",
"a",
"able",
"about",
"across",
"after",
"all",
"almost",
"also",
"am",
"among",
"an",
"and",
"any",
"are",
"as",
"at",
"be",
"because",
"been",
"but",
"by",
"can",
"cannot",
"could",
"dear",
"did",
"do",
"does",
"either",
"else",
"ever",
"every",
"for",
"from",
"get",
"got",
"had",
"has",
"have",
"he",
"her",
"hers",
"him",
"his",
"how",
"however",
"i",
"if",
"in",
"into",
"is",
"it",
"its",
"just",
"least",
"let",
"like",
"likely",
"may",
"me",
"might",
"most",
"must",
"my",
"neither",
"no",
"nor",
"not",
"of",
"off",
"often",
"on",
"only",
"or",
"other",
"our",
"own",
"rather",
"said",
"say",
"says",
"she",
"should",
"since",
"so",
"some",
"than",
"that",
"the",
"their",
"them",
"then",
"there",
"these",
"they",
"this",
"tis",
"to",
"too",
"twas",
"us",
"wants",
"was",
"we",
"were",
"what",
"when",
"where",
"which",
"while",
"who",
"whom",
"why",
"will",
"with",
"would",
"yet",
"you",
"your"
]
lunr.Pipeline.registerFunction(lunr.stopWordFilter, 'stopWordFilter')
/*!
* lunr.trimmer
* Copyright (C) 2014 Oliver Nightingale
*/
/** lunr.Pipeline.registerFunction(lunr.trimmer, 'trimmer')
* lunr.trimmer is a pipeline function for trimming non word /*!
* characters from the begining and end of tokens before they * lunr.stemmer
* enter the index. * Copyright (C) 2014 Oliver Nightingale
* * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt
* This implementation may not work correctly for non latin */
* characters and should either be removed or adapted for use
* with languages with non-latin characters.
*
* @module
* @param {String} token The token to pass through the filter
* @returns {String}
* @see lunr.Pipeline
*/
lunr.trimmer = function (token) {
return token
.replace(/^\W+/, '')
.replace(/\W+$/, '')
}
lunr.Pipeline.registerFunction(lunr.trimmer, 'trimmer')
/*!
* lunr.stemmer
* Copyright (C) 2014 Oliver Nightingale
* Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt
*/
/** /**
* lunr.TokenStore is used for efficient storing and lookup of the reverse * lunr.TokenStore is used for efficient storing and lookup of the reverse
* index of token to document ref. * index of token to document ref.
* *
* @constructor * @constructor
*/ */
lunr.TokenStore = function () { lunr.TokenStore = function () {
this.root = { docs: {} } this.root = { docs: {} }
this.length = 0 this.length = 0
} }
/** /**
* Loads a previously serialised token store * Loads a previously serialised token store
* *
* @param {Object} serialisedData The serialised token store to load. * @param {Object} serialisedData The serialised token store to load.
* @returns {lunr.TokenStore} * @returns {lunr.TokenStore}
* @memberOf TokenStore * @memberOf TokenStore
*/ */
lunr.TokenStore.load = function (serialisedData) { lunr.TokenStore.load = function (serialisedData) {
var store = new this var store = new this
store.root = serialisedData.root store.root = serialisedData.root
store.length = serialisedData.length store.length = serialisedData.length
return store return store
} }
/** /**
* Adds a new token doc pair to the store. * Adds a new token doc pair to the store.
* *
* By default this function starts at the root of the current store, however * By default this function starts at the root of the current store, however
* it can start at any node of any token store if required. * it can start at any node of any token store if required.
* *
* @param {String} token The token to store the doc under * @param {String} token The token to store the doc under
* @param {Object} doc The doc to store against the token * @param {Object} doc The doc to store against the token
* @param {Object} root An optional node at which to start looking for the * @param {Object} root An optional node at which to start looking for the
* correct place to enter the doc, by default the root of this lunr.TokenStore * correct place to enter the doc, by default the root of this lunr.TokenStore
* is used. * is used.
* @memberOf TokenStore * @memberOf TokenStore
*/ */
lunr.TokenStore.prototype.add = function (token, doc, root) { lunr.TokenStore.prototype.add = function (token, doc, root) {
var root = root || this.root, var root = root || this.root,
key = token[0], key = token[0],
rest = token.slice(1) rest = token.slice(1)
if (!(key in root)) root[key] = {docs: {}} if (!(key in root)) root[key] = {docs: {}}
if (rest.length === 0) { if (rest.length === 0) {
root[key].docs[doc.ref] = doc root[key].docs[doc.ref] = doc
this.length += 1 this.length += 1
return return
} else { } else {
return this.add(rest, doc, root[key]) return this.add(rest, doc, root[key])
}
} }
}
/** /**
* Checks whether this key is contained within this lunr.TokenStore. * Checks whether this key is contained within this lunr.TokenStore.
* *
* By default this function starts at the root of the current store, however * By default this function starts at the root of the current store, however
* it can start at any node of any token store if required. * it can start at any node of any token store if required.
* *
* @param {String} token The token to check for * @param {String} token The token to check for
* @param {Object} root An optional node at which to start * @param {Object} root An optional node at which to start
* @memberOf TokenStore * @memberOf TokenStore
*/ */
lunr.TokenStore.prototype.has = function (token) { lunr.TokenStore.prototype.has = function (token) {
if (!token) return false if (!token) return false
var node = this.root
var node = this.root for (var i = 0; i < token.length; i++) {
if (!node[token[i]]) return false
for (var i = 0; i < token.length; i++) { node = node[token[i]]
if (!node[token[i]]) return false }
node = node[token[i]] return true
} }
return true /**
} * Retrieve a node from the token store for a given token.
*
* By default this function starts at the root of the current store, however
* it can start at any node of any token store if required.
*
* @param {String} token The token to get the node for.
* @param {Object} root An optional node at which to start.
* @returns {Object}
* @see TokenStore.prototype.get
* @memberOf TokenStore
*/
lunr.TokenStore.prototype.getNode = function (token) {
if (!token) return {}
/** var node = this.root
* Retrieve a node from the token store for a given token.
*
* By default this function starts at the root of the current store, however
* it can start at any node of any token store if required.
*
* @param {String} token The token to get the node for.
* @param {Object} root An optional node at which to start.
* @returns {Object}
* @see TokenStore.prototype.get
* @memberOf TokenStore
*/
lunr.TokenStore.prototype.getNode = function (token) {
if (!token) return {}
var node = this.root for (var i = 0; i < token.length; i++) {
if (!node[token[i]]) return {}
for (var i = 0; i < token.length; i++) { node = node[token[i]]
if (!node[token[i]]) return {} }
node = node[token[i]] return node
} }
return node /**
} * Retrieve the documents for a node for the given token.
*
* By default this function starts at the root of the current store, however
* it can start at any node of any token store if required.
*
* @param {String} token The token to get the documents for.
* @param {Object} root An optional node at which to start.
* @returns {Object}
* @memberOf TokenStore
*/
lunr.TokenStore.prototype.get = function (token, root) {
return this.getNode(token, root).docs || {}
}
/** lunr.TokenStore.prototype.count = function (token, root) {
* Retrieve the documents for a node for the given token. return Object.keys(this.get(token, root)).length
* }
* By default this function starts at the root of the current store, however
* it can start at any node of any token store if required.
*
* @param {String} token The token to get the documents for.
* @param {Object} root An optional node at which to start.
* @returns {Object}
* @memberOf TokenStore
*/
lunr.TokenStore.prototype.get = function (token, root) {
return this.getNode(token, root).docs || {}
}
lunr.TokenStore.prototype.count = function (token, root) { /**
return Object.keys(this.get(token, root)).length * Remove the document identified by ref from the token in the store.
} *
* By default this function starts at the root of the current store, however
* it can start at any node of any token store if required.
*
* @param {String} token The token to get the documents for.
* @param {String} ref The ref of the document to remove from this token.
* @param {Object} root An optional node at which to start.
* @returns {Object}
* @memberOf TokenStore
*/
lunr.TokenStore.prototype.remove = function (token, ref) {
if (!token) return
var node = this.root
/** for (var i = 0; i < token.length; i++) {
* Remove the document identified by ref from the token in the store. if (!(token[i] in node)) return
* node = node[token[i]]
* By default this function starts at the root of the current store, however }
* it can start at any node of any token store if required.
*
* @param {String} token The token to get the documents for.
* @param {String} ref The ref of the document to remove from this token.
* @param {Object} root An optional node at which to start.
* @returns {Object}
* @memberOf TokenStore
*/
lunr.TokenStore.prototype.remove = function (token, ref) {
if (!token) return
var node = this.root
for (var i = 0; i < token.length; i++) { delete node.docs[ref]
if (!(token[i] in node)) return
node = node[token[i]]
} }
delete node.docs[ref] /**
} * Find all the possible suffixes of the passed token using tokens
* currently in the store.
/** *
* Find all the possible suffixes of the passed token using tokens * @param {String} token The token to expand.
* currently in the store. * @returns {Array}
* * @memberOf TokenStore
* @param {String} token The token to expand. */
* @returns {Array} lunr.TokenStore.prototype.expand = function (token, memo) {
* @memberOf TokenStore var root = this.getNode(token),
*/ docs = root.docs || {},
lunr.TokenStore.prototype.expand = function (token, memo) { memo = memo || []
var root = this.getNode(token),
docs = root.docs || {},
memo = memo || []
if (Object.keys(docs).length) memo.push(token) if (Object.keys(docs).length) memo.push(token)
Object.keys(root) Object.keys(root)
.forEach(function (key) { .forEach(function (key) {
if (key === 'docs') return if (key === 'docs') return
memo.concat(this.expand(token + key, memo)) memo.concat(this.expand(token + key, memo))
}, this) }, this)
return memo return memo
} }
/** /**
* Returns a representation of the token store ready for serialisation. * Returns a representation of the token store ready for serialisation.
* *
* @returns {Object} * @returns {Object}
* @memberOf TokenStore * @memberOf TokenStore
*/ */
lunr.TokenStore.prototype.toJSON = function () { lunr.TokenStore.prototype.toJSON = function () {
return { return {
root: this.root, root: this.root,
length: this.length length: this.length
}
} }
}
/** /**
* export the module via AMD, CommonnJS or as a browser global * export the module via AMD, CommonJS or as a browser global
* Export code from https://github.com/umdjs/umd/blob/master/returnExports.js * Export code from https://github.com/umdjs/umd/blob/master/returnExports.js
*/ */
;(function (root, factory) { ;(function (root, factory) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment