datafire
Version:
[![Travis][travis-image]][travis-link] [![Downloads][downloads-image]][npm-link] [![NPM version][npm-image]][npm-link] [](https://www.npmjs.com/package/datafire) <!--[![Dependency status][deps-i
1,703 lines (1,497 loc) • 61.6 kB
JavaScript
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = "./src/app/lunr_worker.js");
/******/ })
/************************************************************************/
/******/ ({
/***/ "./node_modules/lunr/lunr.js":
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
/**
* lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 0.7.2
* Copyright (C) 2016 Oliver Nightingale
* @license MIT
*/
;(function () {
/**
* Convenience function for instantiating a new lunr index and configuring it
* with the default pipeline functions and the passed config function.
*
* When using this convenience function a new index will be created with the
* following functions already in the pipeline:
*
* lunr.StopWordFilter - filters out any stop words before they enter the
* index
*
* lunr.stemmer - stems the tokens before entering the index.
*
* Example:
*
* var idx = lunr(function () {
* this.field('title', 10)
* this.field('tags', 100)
* this.field('body')
*
* this.ref('cid')
*
* this.pipeline.add(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 lunr(config) {
var idx = new lunr.Index();
idx.pipeline.add(lunr.trimmer, lunr.stopWordFilter, lunr.stemmer);
if (config) config.call(idx, idx);
return idx;
};
lunr.version = "0.7.2";
/*!
* lunr.utils
* Copyright (C) 2016 Oliver Nightingale
*/
/**
* A namespace containing utils for the rest of the lunr library
*/
lunr.utils = {};
/**
* Print a warning message to the console.
*
* @param {String} message The message to be printed.
* @memberOf Utils
*/
lunr.utils.warn = function (global) {
return function (message) {
if (global.console && console.warn) {
console.warn(message);
}
};
}(this);
/**
* Convert an object to a string.
*
* In the case of `null` and `undefined` the function returns
* the empty string, in all other cases the result of calling
* `toString` on the passed object is returned.
*
* @param {Any} obj The object to convert to a string.
* @return {String} string representation of the passed object.
* @memberOf Utils
*/
lunr.utils.asString = function (obj) {
if (obj === void 0 || obj === null) {
return "";
} else {
return obj.toString();
}
};
/*!
* lunr.EventEmitter
* Copyright (C) 2016 Oliver Nightingale
*/
/**
* lunr.EventEmitter is an event emitter for lunr. It manages adding and removing event handlers and triggering events and their handlers.
*
* @constructor
*/
lunr.EventEmitter = function () {
this.events = {};
};
/**
* Binds a handler function to a specific event(s).
*
* 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 {Function} fn The function to call when an event is fired.
* @memberOf EventEmitter
*/
lunr.EventEmitter.prototype.addListener = function () {
var args = Array.prototype.slice.call(arguments),
fn = args.pop(),
names = args;
if (typeof fn !== "function") throw new TypeError("last argument must be a function");
names.forEach(function (name) {
if (!this.hasHandler(name)) this.events[name] = [];
this.events[name].push(fn);
}, this);
};
/**
* Removes a handler function from a specific event.
*
* @param {String} eventName The name of the event to remove this function from.
* @param {Function} fn The function to remove from an event.
* @memberOf EventEmitter
*/
lunr.EventEmitter.prototype.removeListener = function (name, fn) {
if (!this.hasHandler(name)) return;
var fnIndex = this.events[name].indexOf(fn);
this.events[name].splice(fnIndex, 1);
if (!this.events[name].length) delete this.events[name];
};
/**
* Calls all functions bound to the given event.
*
* Additional data can be passed to the event handler as arguments to `emit`
* after the event name.
*
* @param {String} eventName The name of the event to emit.
* @memberOf EventEmitter
*/
lunr.EventEmitter.prototype.emit = function (name) {
if (!this.hasHandler(name)) return;
var args = Array.prototype.slice.call(arguments, 1);
this.events[name].forEach(function (fn) {
fn.apply(undefined, args);
});
};
/**
* Checks whether a handler has ever been stored against an event.
*
* @param {String} eventName The name of the event to check.
* @private
* @memberOf EventEmitter
*/
lunr.EventEmitter.prototype.hasHandler = function (name) {
return name in this.events;
};
/*!
* lunr.tokenizer
* Copyright (C) 2016 Oliver Nightingale
*/
/**
* A function for splitting a string into tokens ready to be inserted into
* the search index. Uses `lunr.tokenizer.separator` to split strings, change
* the value of this property to change how strings are split into tokens.
*
* @module
* @param {String} obj The string to convert into tokens
* @see lunr.tokenizer.separator
* @returns {Array}
*/
lunr.tokenizer = function (obj) {
if (!arguments.length || obj == null || obj == undefined) return [];
if (Array.isArray(obj)) return obj.map(function (t) {
return lunr.utils.asString(t).toLowerCase();
});
// TODO: This exists so that the deprecated property lunr.tokenizer.seperator can still be used. By
// default it is set to false and so the correctly spelt lunr.tokenizer.separator is used unless
// the user is using the old property to customise the tokenizer.
//
// This should be removed when version 1.0.0 is released.
var separator = lunr.tokenizer.seperator || lunr.tokenizer.separator;
return obj.toString().trim().toLowerCase().split(separator);
};
/**
* This property is legacy alias for lunr.tokenizer.separator to maintain backwards compatability.
* When introduced the token was spelt incorrectly. It will remain until 1.0.0 when it will be removed,
* all code should use the correctly spelt lunr.tokenizer.separator property instead.
*
* @static
* @see lunr.tokenizer.separator
* @deprecated since 0.7.2 will be removed in 1.0.0
* @private
* @see lunr.tokenizer
*/
lunr.tokenizer.seperator = false;
/**
* The sperator used to split a string into tokens. Override this property to change the behaviour of
* `lunr.tokenizer` behaviour when tokenizing strings. By default this splits on whitespace and hyphens.
*
* @static
* @see lunr.tokenizer
*/
lunr.tokenizer.separator = /[\s\-]+/;
/**
* Loads a previously serialised tokenizer.
*
* A tokenizer function to be loaded must already be registered with lunr.tokenizer.
* If the serialised tokenizer has not been registered then an error will be thrown.
*
* @param {String} label The label of the serialised tokenizer.
* @returns {Function}
* @memberOf tokenizer
*/
lunr.tokenizer.load = function (label) {
var fn = this.registeredFunctions[label];
if (!fn) {
throw new Error('Cannot load un-registered function: ' + label);
}
return fn;
};
lunr.tokenizer.label = 'default';
lunr.tokenizer.registeredFunctions = {
'default': lunr.tokenizer
/**
* Register a tokenizer function.
*
* Functions that are used as tokenizers should be registered if they are to be used with a serialised index.
*
* Registering a function does not add it to an index, functions must still be associated with a specific index for them to be used when indexing and searching documents.
*
* @param {Function} fn The function to register.
* @param {String} label The label to register this function with
* @memberOf tokenizer
*/
};lunr.tokenizer.registerFunction = function (fn, label) {
if (label in this.registeredFunctions) {
lunr.utils.warn('Overwriting existing tokenizer: ' + label);
}
fn.label = label;
this.registeredFunctions[label] = fn;
};
/*!
* lunr.Pipeline
* Copyright (C) 2016 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 = {};
/**
* 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;
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
* @memberOf Pipeline
*/
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}
* @memberOf 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 un-registered function: ' + fnName);
}
});
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);
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 {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);
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 {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);
if (pos == -1) {
throw new Error('Cannot find existingFn');
}
this._stack.splice(pos, 0, newFn);
};
/**
* Removes a function from the pipeline.
*
* @param {Function} fn The function to remove from the pipeline.
* @memberOf 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}
* @memberOf Pipeline
*/
lunr.Pipeline.prototype.run = function (tokens) {
var out = [],
tokenLength = tokens.length,
stackLength = this._stack.length;
for (var i = 0; i < tokenLength; i++) {
var token = tokens[i];
for (var j = 0; j < stackLength; j++) {
token = this._stack[j](token, i, tokens);
if (token === void 0 || token === '') break;
};
if (token !== void 0 && token !== '') 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.
*
* Logs a warning if the function has not been registered.
*
* @returns {Array}
* @memberOf Pipeline
*/
lunr.Pipeline.prototype.toJSON = function () {
return this._stack.map(function (fn) {
lunr.Pipeline.warnIfFunctionNotRegistered(fn);
return fn.label;
});
};
/*!
* lunr.Vector
* Copyright (C) 2016 Oliver Nightingale
*/
/**
* lunr.Vectors implement vector related operations for
* a series of elements.
*
* @constructor
*/
lunr.Vector = function () {
this._magnitude = null;
this.list = undefined;
this.length = 0;
};
/**
* lunr.Vector.Node is a simple struct for each node
* 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;
};
/**
* 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) {
this._magnitude = undefined;
var list = this.list;
if (!list) {
this.list = new lunr.Vector.Node(idx, val, list);
return this.length++;
}
if (idx < list.idx) {
this.list = new lunr.Vector.Node(idx, val, list);
return this.length++;
}
var prev = list,
next = list.next;
while (next != undefined) {
if (idx < next.idx) {
prev.next = new lunr.Vector.Node(idx, val, next);
return this.length++;
}
prev = next, next = next.next;
}
prev.next = new lunr.Vector.Node(idx, val, next);
return this.length++;
};
/**
* Calculates the magnitude of this vector.
*
* @returns {Number}
* @memberOf Vector
*/
lunr.Vector.prototype.magnitude = function () {
if (this._magnitude) 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) {
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;
};
/**
* 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) 2016 Oliver Nightingale
*/
/**
* lunr.SortedSets are used to maintain an array of uniq values in a sorted
* order.
*
* @constructor
*/
lunr.SortedSet = function () {
this.length = 0;
this.elements = [];
};
/**
* Loads a previously serialised sorted set.
*
* @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;
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 () {
var i, element;
for (i = 0; i < arguments.length; i++) {
element = arguments[i];
if (~this.indexOf(element)) continue;
this.elements.splice(this.locationFor(element), 0, element);
}
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();
};
/**
* Creates a new array with the results of calling a provided function on every
* element in this sorted set.
*
* Delegates to Array.prototype.map and has the same signature.
*
* @param {Function} fn The function that is called on each element of the
* 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);
};
/**
* Executes a provided function once per sorted set element.
*
* Delegates to Array.prototype.forEach and has the same signature.
*
* @param {Function} fn The function that is called on each element of the
* set.
* @param {Object} ctx An optional object that can be used as the context
* @memberOf SortedSet
* for the function fn.
*/
lunr.SortedSet.prototype.forEach = function (fn, ctx) {
return this.elements.forEach(fn, ctx);
};
/**
* Returns the index at which a given element can be found in the
* sorted set, or -1 if it is not present.
*
* @param {Object} elem The object to locate in the sorted set.
* @returns {Number}
* @memberOf SortedSet
*/
lunr.SortedSet.prototype.indexOf = function (elem) {
var start = 0,
end = this.elements.length,
sectionLength = end - start,
pivot = start + Math.floor(sectionLength / 2),
pivotElem = this.elements[pivot];
while (sectionLength > 1) {
if (pivotElem === elem) return pivot;
if (pivotElem < elem) start = pivot;
if (pivotElem > elem) end = pivot;
sectionLength = end - start;
pivot = start + Math.floor(sectionLength / 2);
pivotElem = this.elements[pivot];
}
if (pivotElem === elem) return pivot;
return -1;
};
/**
* Returns the position within the sorted set that an element should be
* inserted at to maintain the current order of the set.
*
* 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
* @returns {Number}
* @memberOf SortedSet
*/
lunr.SortedSet.prototype.locationFor = function (elem) {
var start = 0,
end = this.elements.length,
sectionLength = end - start,
pivot = start + Math.floor(sectionLength / 2),
pivotElem = this.elements[pivot];
while (sectionLength > 1) {
if (pivotElem < elem) start = pivot;
if (pivotElem > elem) end = pivot;
sectionLength = end - start;
pivot = start + Math.floor(sectionLength / 2);
pivotElem = this.elements[pivot];
}
if (pivotElem > elem) return pivot;
if (pivotElem < elem) return pivot + 1;
};
/**
* Creates a new lunr.SortedSet that contains the elements in the intersection
* 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]) {
i++;
continue;
}
if (a[i] > b[j]) {
j++;
continue;
}
};
return intersectSet;
};
/**
* 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();
clone.length = clone.elements.length;
return clone;
};
/**
* Creates a new lunr.SortedSet that contains the elements in the union
* 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;
}
unionSet = longSet.clone();
for (var i = 0, shortSetElements = shortSet.toArray(); i < shortSetElements.length; i++) {
unionSet.add(shortSetElements[i]);
}
return unionSet;
};
/**
* 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) 2016 Oliver Nightingale
*/
/**
* 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.tokenizerFn = lunr.tokenizer;
this._idfCache = {};
this.on('add', 'remove', 'update', function () {
this._idfCache = {};
}.bind(this));
};
/**
* 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} fn 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);
};
/**
* 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} fn The serialised set to load.
* @memberOf Index
*/
lunr.Index.prototype.off = function (name, fn) {
return this.eventEmitter.removeListener(name, fn);
};
/**
* 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();
idx._fields = serialisedData.fields;
idx._ref = serialisedData.ref;
idx.tokenizer(lunr.tokenizer.load(serialisedData.tokenizer));
idx.documentStore = lunr.Store.load(serialisedData.documentStore);
idx.tokenStore = lunr.TokenStore.load(serialisedData.tokenStore);
idx.corpusTokens = lunr.SortedSet.load(serialisedData.corpusTokens);
idx.pipeline = lunr.Pipeline.load(serialisedData.pipeline);
return idx;
};
/**
* Adds a field to the list of fields that will be searchable within documents
* in the index.
*
* 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 };
this._fields.push(field);
return this;
};
/**
* 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.
*
* The value of ref can be of any type but it _must_ be stably comparable and
* orderable.
*
* @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;
};
/**
* Sets the tokenizer used for this index.
*
* By default the index will use the default tokenizer, lunr.tokenizer. The tokenizer
* should only be changed before adding documents to the index. Changing the tokenizer
* without re-building the index can lead to unexpected results.
*
* @param {Function} fn The function to use as a tokenizer.
* @returns {lunr.Index}
* @memberOf Index
*/
lunr.Index.prototype.tokenizer = function (fn) {
var isRegistered = fn.label && fn.label in lunr.tokenizer.registeredFunctions;
if (!isRegistered) {
lunr.utils.warn('Function is not a registered tokenizer. This may cause problems when serialising the index');
}
this.tokenizerFn = fn;
return this;
};
/**
* Add a document to the index.
*
* 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) {
var fieldTokens = this.pipeline.run(this.tokenizerFn(doc[field.name]));
docTokens[field.name] = fieldTokens;
for (var i = 0; i < fieldTokens.length; i++) {
var token = fieldTokens[i];
allDocumentTokens.add(token);
this.corpusTokens.add(token);
}
}, this);
this.documentStore.set(docRef, allDocumentTokens);
for (var i = 0; i < allDocumentTokens.length; i++) {
var token = allDocumentTokens.elements[i];
var tf = 0;
for (var j = 0; j < this._fields.length; j++) {
var field = this._fields[j];
var fieldTokens = docTokens[field.name];
var fieldLength = fieldTokens.length;
if (!fieldLength) continue;
var tokenCount = 0;
for (var k = 0; k < fieldLength; k++) {
if (fieldTokens[k] === token) {
tokenCount++;
}
}
tf += tokenCount / fieldLength * field.boost;
}
this.tokenStore.add(token, { ref: docRef, tf: tf });
};
if (emitEvent) this.eventEmitter.emit('add', doc, this);
};
/**
* 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;
var docTokens = this.documentStore.get(docRef);
this.documentStore.remove(docRef);
docTokens.forEach(function (token) {
this.tokenStore.remove(token, docRef);
}, this);
if (emitEvent) this.eventEmitter.emit('remove', doc, this);
};
/**
* 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;
this.remove(doc, false);
this.add(doc, false);
if (emitEvent) this.eventEmitter.emit('update', doc, this);
};
/**
* 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;
if (documentFrequency > 0) {
idf = 1 + Math.log(this.documentStore.length / documentFrequency);
}
return this._idfCache[cacheKey] = idf;
};
/**
* 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(this.tokenizerFn(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
// applying an similarityBoost to ensure exact matches
// these rank higher than expanded terms
if (pos > -1) queryVector.insert(pos, tf * idf * similarityBoost);
// add all the documents that have this key into a set
// ensuring that the type of key is preserved
var matchingDocuments = self.tokenStore.get(key),
refs = Object.keys(matchingDocuments),
refsLen = refs.length;
for (var i = 0; i < refsLen; i++) {
set.add(matchingDocuments[refs[i]].ref);
}
return memo.union(set);
}, new lunr.SortedSet());
documentSets.push(set);
}, this);
var documentSet = documentSets.reduce(function (memo, set) {
return memo.intersect(set);
});
return documentSet.map(function (ref) {
return { ref: ref, score: queryVector.similarity(this.documentVector(ref)) };
}, this).sort(function (a, b) {
return b.score - a.score;
});
};
/**
* Generates a vector containing all the tokens in the document matching the
* passed documentRef.
*
* 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();
for (var i = 0; i < documentTokensLength; i++) {
var token = documentTokens.elements[i],
tf = this.tokenStore.get(token)[documentRef].tf,
idf = this.idf(token);
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,
tokenizer: this.tokenizerFn.label,
documentStore: this.documentStore.toJSON(),
tokenStore: this.tokenStore.toJSON(),
corpusTokens: this.corpusTokens.toJSON(),
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
* // apply any extensions etc here.
* }
*
* var idx = lunr(function () {
* this.use(myPlugin, 'arg1', 'arg2')
* })
*
* @param {Function} plugin The plugin to apply.
* @memberOf Index
*/
lunr.Index.prototype.use = function (plugin) {
var args = Array.prototype.slice.call(arguments, 1);
args.unshift(this);
plugin.apply(this, args);
};
/*!
* lunr.Store
* Copyright (C) 2016 Oliver Nightingale
*/
/**
* lunr.Store is a simple key-value store used for storing sets of tokens for
* documents stored in index.
*
* @constructor
* @module
*/
lunr.Store = function () {
this.store = {};
this.length = 0;
};
/**
* Loads a previously serialised store
*
* @param {Object} serialisedData The serialised store to load.
* @returns {lunr.Store}
* @memberOf Store
*/
lunr.Store.load = function (serialisedData) {
var store = new this();
store.length = serialisedData.length;
store.store = Object.keys(serialisedData.store).reduce(function (memo, key) {
memo[key] = lunr.SortedSet.load(serialisedData.store[key]);
return memo;
}, {});
return store;
};
/**
* Stores the given tokens in the store against the given id.
*
* @param {Object} id The key used to store the tokens against.
* @param {Object} tokens The tokens to store against the key.
* @memberOf Store
*/
lunr.Store.prototype.set = function (id, tokens) {
if (!this.has(id)) this.length++;
this.store[id] = tokens;
};
/**
* Retrieves the tokens from the store for a given key.
*
* @param {Object} id The key to lookup and retrieve from the store.
* @returns {Object}
* @memberOf Store
*/
lunr.Store.prototype.get = function (id) {
return this.store[id];
};
/**
* Checks whether the store contains a key.
*
* @param {Object} id The id to look up in the store.
* @returns {Boolean}
* @memberOf Store
*/
lunr.Store.prototype.has = function (id) {
return id in this.store;
};
/**
* Removes the value for a key in the store.
*
* @param {Object} id The id to remove from the store.
* @memberOf Store
*/
lunr.Store.prototype.remove = function (id) {
if (!this.has(id)) return;
delete this.store[id];
this.length--;
};
/**
* Returns a representation of the store ready for serialisation.
*
* @returns {Object}
* @memberOf Store
*/
lunr.Store.prototype.toJSON = function () {
return {
store: this.store,
length: this.length
};
};
/*!
* lunr.stemmer
* Copyright (C) 2016 Oliver Nightingale
* Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt
*/
/**
* lunr.stemmer is an english language stemmer, this is a JavaScript
* implementation of the PorterStemmer taken from http://tartarus.org/~martin
*
* @module
* @param {String} str The string to stem
* @returns {String}
* @see lunr.Pipeline
*/
lunr.stemmer = function () {
var step2list = {
"ational": "ate",
"tional": "tion",
"enci": "ence",
"anci": "ance",
"izer": "ize",
"bli": "ble",
"alli": "al",
"entli": "ent",
"eli": "e",
"ousli": "ous",
"ization": "ize",
"ation": "ate",
"ator": "ate",
"alism": "al",
"iveness": "ive",
"fulness": "ful",
"ousness": "ous",
"aliti": "al",
"iviti": "ive",
"biliti": "ble",
"logi": "log"
},
step3list = {
"icate": "ic",
"ative": "",
"alize": "al",
"iciti": "ic",
"ical": "ic",
"ful": "",
"ness": ""
},
c = "[^aeiou]",
// consonant
v = "[aeiouy]",
// vowel
C = c + "[^aeiouy]*",
// consonant sequence
V = v + "[aeiou]*",
// vowel sequence
mgr0 = "^(" + C + ")?" + V + C,
// [C]VC... is m>0
meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$",
// [C]VC[V] is m=1
mgr1 = "^(" + C + ")?" + V + C + V + C,
// [C]VCVC... is m>1
s_v = "^(" + C + ")?" + v; // vowel in stem
var re_mgr0 = new RegExp(mgr0);
var re_mgr1 = new RegExp(mgr1);
var re_meq1 = new RegExp(meq1);
var re_s_v = new RegExp(s_v);
var re_1a = /^(.+?)(ss|i)es$/;
var re2_1a = /^(.+?)([^s])s$/;
var re_1b = /^(.+?)eed$/;
var re2_1b = /^(.+?)(ed|ing)$/;
var re_1b_2 = /.$/;
var re2_1b_2 = /(at|bl|iz)$/;
var re3_1b_2 = new RegExp("([^aeiouylsz])\\1$");
var re4_1b_2 = new RegExp("^" + C + v + "[^aeiouwxy]$");
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
re = re_1a;
re2 = re2_1a;
if (re.test(w)) {
w = w.replace(re, "$1$2");
} else if (re2.test(w)) {
w = w.replace(re2, "$1$2");
}
// Step 1b
re = re_1b;
re2 = re2_1b;
if (re.test(w)) {
var fp = re.exec(w);
re = re_mgr0;
if (re.test(fp[1])) {
re = re_1b_2;
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";
}
}
}
// 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)
re = re_1c;
if (re.test(w)) {
var fp = re.exec(w);
stem = fp[1];
w = stem + "i";
}
// Step 2
re = re_2;
if (re.test(w)) {
var fp = re.exec(w);
stem = fp[1];
suffix = fp[2];
re = re_mgr0;
if (re.test(stem)) {
w = stem + step2list[suffix];
}
}
// Step 3
re = re_3;
if (re.test(w)) {
var fp = re.exec(w);
stem = fp[1];
suffix = fp[2];
re = re_mgr0;
if (re.test(stem)) {
w = stem + step3list[suffix];
}
}
// Step 4
re = re_4;
re2 = re2_4;
if (re.test(w)) {
var fp = re.exec(w);
stem = fp[1];
re = re_mgr1;
if (re.test(stem)) {