/*! * lunr.Pipeline * Copyright (C) @YEAR Oliver Nightingale */ /** * 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 = Object.create(null) /** * A pipeline function maps lunr.Token to lunr.Token. A lunr.Token contains the token * string as well as all known metadata. A pipeline function can mutate the token string * or mutate (or add) metadata for a given token. * * A pipeline function can indicate that the passed token should be discarded by returning * null, undefined or an empty string. This token will not be passed to any downstream pipeline * functions and will not be added to the index. * * Multiple tokens can be returned by returning an array of tokens. Each token will be passed * to any downstream pipeline functions and all will returned tokens will be added to the index. * * Any number of pipeline functions may be chained together using a lunr.Pipeline. * * @interface lunr.PipelineFunction * @param {lunr.Token} token - A token from the document being processed. * @param {number} i - The index of this token in the complete list of tokens for this document/field. * @param {lunr.Token[]} tokens - All tokens for this document/field. * @returns {(?lunr.Token|lunr.Token[])} */ /** * 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 {lunr.PipelineFunction} fn - The function to check for. * @param {String} label - The label to register this function with */ 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 {lunr.PipelineFunction} fn - The function to check for. * @private */ lunr.Pipeline.warnIfFunctionNotRegistered = function (fn) { var isRegistered = fn.label && (fn.label in this.registeredFunctions) if (!isRegistered) { lunr.utils.warn('Function is not registered with pipeline. This may cause problems when serialising the index.\n', fn) } } /** * Loads a previously serialised 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 * error will be thrown. * * @param {Object} serialised - The serialised pipeline to load. * @returns {lunr.Pipeline} */ lunr.Pipeline.load = function (serialised) { var pipeline = new lunr.Pipeline serialised.forEach(function (fnName) { var fn = lunr.Pipeline.registeredFunctions[fnName] if (fn) { pipeline.add(fn) } else { throw new Error('Cannot load unregistered function: ' + fnName) } }) return pipeline } /** * Adds new functions to the end of the pipeline. * * Logs a warning if the function has not been registered. * * @param {lunr.PipelineFunction[]} functions - Any number of functions to add to the pipeline. */ lunr.Pipeline.prototype.add = function () { var fns = Array.prototype.slice.call(arguments) fns.forEach(function (fn) { lunr.Pipeline.warnIfFunctionNotRegistered(fn) this._stack.push(fn) }, this) } /** * Adds a single function after a function that already exists in the * pipeline. * * Logs a warning if the function has not been registered. * * @param {lunr.PipelineFunction} existingFn - A function that already exists in the pipeline. * @param {lunr.PipelineFunction} newFn - The new function to add to the pipeline. */ lunr.Pipeline.prototype.after = function (existingFn, newFn) { lunr.Pipeline.warnIfFunctionNotRegistered(newFn) var pos = this._stack.indexOf(existingFn) if (pos == -1) { throw new Error('Cannot find existingFn') } pos = pos + 1 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 {lunr.PipelineFunction} existingFn - A function that already exists in the pipeline. * @param {lunr.PipelineFunction} newFn - The new function to add to the pipeline. */ lunr.Pipeline.prototype.before = function (existingFn, newFn) { lunr.Pipeline.warnIfFunctionNotRegistered(newFn) var pos = this._stack.indexOf(existingFn) if (pos == -1) { throw new Error('Cannot find existingFn') } this._stack.splice(pos, 0, newFn) } /** * Removes a function from the pipeline. * * @param {lunr.PipelineFunction} fn The function to remove from the pipeline. */ lunr.Pipeline.prototype.remove = function (fn) { var pos = this._stack.indexOf(fn) if (pos == -1) { return } this._stack.splice(pos, 1) } /** * 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} */ lunr.Pipeline.prototype.run = function (tokens) { var stackLength = this._stack.length for (var i = 0; i < stackLength; i++) { var fn = this._stack[i] var memo = [] for (var j = 0; j < tokens.length; j++) { var result = fn(tokens[j], j, tokens) if (result === null || result === void 0 || result === '') continue if (Array.isArray(result)) { for (var k = 0; k < result.length; k++) { memo.push(result[k]) } } else { memo.push(result) } } tokens = memo } return tokens } /** * Convenience method for passing a string through a pipeline and getting * strings out. This method takes care of wrapping the passed string in a * token and mapping the resulting tokens back to strings. * * @param {string} str - The string to pass through the pipeline. * @param {?object} metadata - Optional metadata to associate with the token * passed to the pipeline. * @returns {string[]} */ lunr.Pipeline.prototype.runString = function (str, metadata) { var token = new lunr.Token (str, metadata) return this.run([token]).map(function (t) { return t.toString() }) } /** * Resets the pipeline by removing any existing processors. * */ lunr.Pipeline.prototype.reset = function () { this._stack = [] } /** * Returns a representation of the pipeline ready for serialisation. * * Logs a warning if the function has not been registered. * * @returns {Array} */ lunr.Pipeline.prototype.toJSON = function () { return this._stack.map(function (fn) { lunr.Pipeline.warnIfFunctionNotRegistered(fn) return fn.label }) }