2 * elasticlunr - http://weixsong.github.io
3 * Lightweight full-text search engine in Javascript for browser search and offline search. - 0.9.5
5 * Copyright (C) 2016 Oliver Nightingale
6 * Copyright (C) 2016 Wei Song
15 * Copyright (C) 2016 Oliver Nightingale
16 * Copyright (C) 2016 Wei Song
20 * Convenience function for instantiating a new elasticlunr index and configuring it
21 * with the default pipeline functions and the passed config function.
23 * When using this convenience function a new index will be created with the
24 * following functions already in the pipeline:
26 * 1. elasticlunr.trimmer - trim non-word character
27 * 2. elasticlunr.StopWordFilter - filters out any stop words before they enter the
29 * 3. elasticlunr.stemmer - stems the tokens before entering the index.
34 * var idx = elasticlunr(function () {
35 * this.addField('id');
36 * this.addField('title');
37 * this.addField('body');
39 * //this.setRef('id'); // default ref is 'id'
41 * this.pipeline.add(function () {
42 * // some custom pipeline function
48 * title: 'Oracle released database 12g',
49 * body: 'Yestaday, Oracle has released their latest database, named 12g, more robust. this product will increase Oracle profit.'
54 * title: 'Oracle released annual profit report',
55 * body: 'Yestaday, Oracle has released their annual profit report of 2015, total profit is 12.5 Billion.'
59 * idx.search('oracle database');
61 * # search with query-time boosting
62 * idx.search('oracle database', {fields: {title: {boost: 2}, body: {boost: 1}}});
64 * @param {Function} config A function that will be called with the new instance
65 * of the elasticlunr.Index as both its context and first parameter. It can be used to
66 * customize the instance of new elasticlunr.Index.
69 * @return {elasticlunr.Index}
72 var elasticlunr = function (config) {
73 var idx = new elasticlunr.Index;
77 elasticlunr.stopWordFilter,
81 if (config) config.call(idx, idx);
86 elasticlunr.version = "0.9.5";
88 // only used this to make elasticlunr.js compatible with lunr-languages
89 // this is a trick to define a global alias of elasticlunr
94 * Copyright (C) 2016 Oliver Nightingale
95 * Copyright (C) 2016 Wei Song
99 * A namespace containing utils for the rest of the elasticlunr library
101 elasticlunr.utils = {};
104 * Print a warning message to the console.
106 * @param {String} message The message to be printed.
109 elasticlunr.utils.warn = (function (global) {
110 return function (message) {
111 if (global.console && console.warn) {
112 console.warn(message);
118 * Convert an object to string.
120 * In the case of `null` and `undefined` the function returns
121 * an empty string, in all other cases the result of calling
122 * `toString` on the passed object is returned.
124 * @param {object} obj The object to convert to a string.
125 * @return {String} string representation of the passed object.
128 elasticlunr.utils.toString = function (obj) {
129 if (obj === void 0 || obj === null) {
133 return obj.toString();
136 * elasticlunr.EventEmitter
137 * Copyright (C) 2016 Oliver Nightingale
138 * Copyright (C) 2016 Wei Song
142 * elasticlunr.EventEmitter is an event emitter for elasticlunr.
143 * It manages adding and removing event handlers and triggering events and their handlers.
145 * Each event could has multiple corresponding functions,
146 * these functions will be called as the sequence that they are added into the event.
150 elasticlunr.EventEmitter = function () {
155 * Binds a handler function to a specific event(s).
157 * Can bind a single function to many different events in one call.
159 * @param {String} [eventName] The name(s) of events to bind this function to.
160 * @param {Function} fn The function to call when an event is fired.
161 * @memberOf EventEmitter
163 elasticlunr.EventEmitter.prototype.addListener = function () {
164 var args = Array.prototype.slice.call(arguments),
168 if (typeof fn !== "function") throw new TypeError ("last argument must be a function");
170 names.forEach(function (name) {
171 if (!this.hasHandler(name)) this.events[name] = [];
172 this.events[name].push(fn);
177 * Removes a handler function from a specific event.
179 * @param {String} eventName The name of the event to remove this function from.
180 * @param {Function} fn The function to remove from an event.
181 * @memberOf EventEmitter
183 elasticlunr.EventEmitter.prototype.removeListener = function (name, fn) {
184 if (!this.hasHandler(name)) return;
186 var fnIndex = this.events[name].indexOf(fn);
187 if (fnIndex === -1) return;
189 this.events[name].splice(fnIndex, 1);
191 if (this.events[name].length == 0) delete this.events[name];
195 * Call all functions that bounded to the given event.
197 * Additional data can be passed to the event handler as arguments to `emit`
198 * after the event name.
200 * @param {String} eventName The name of the event to emit.
201 * @memberOf EventEmitter
203 elasticlunr.EventEmitter.prototype.emit = function (name) {
204 if (!this.hasHandler(name)) return;
206 var args = Array.prototype.slice.call(arguments, 1);
208 this.events[name].forEach(function (fn) {
209 fn.apply(undefined, args);
214 * Checks whether a handler has ever been stored against an event.
216 * @param {String} eventName The name of the event to check.
218 * @memberOf EventEmitter
220 elasticlunr.EventEmitter.prototype.hasHandler = function (name) {
221 return name in this.events;
224 * elasticlunr.tokenizer
225 * Copyright (C) 2016 Oliver Nightingale
226 * Copyright (C) 2016 Wei Song
230 * A function for splitting a string into tokens.
231 * Currently English is supported as default.
232 * Uses `elasticlunr.tokenizer.seperator` to split strings, you could change
233 * the value of this property to set how you want strings are split into tokens.
234 * IMPORTANT: use elasticlunr.tokenizer.seperator carefully, if you are not familiar with
235 * text process, then you'd better not change it.
238 * @param {String} str The string that you want to tokenize.
239 * @see elasticlunr.tokenizer.seperator
242 elasticlunr.tokenizer = function (str) {
243 if (!arguments.length || str === null || str === undefined) return [];
244 if (Array.isArray(str)) {
245 var arr = str.filter(function(token) {
246 if (token === null || token === undefined) {
253 arr = arr.map(function (t) {
254 return elasticlunr.utils.toString(t).toLowerCase();
258 arr.forEach(function(item) {
259 var tokens = item.split(elasticlunr.tokenizer.seperator);
260 out = out.concat(tokens);
266 return str.toString().trim().toLowerCase().split(elasticlunr.tokenizer.seperator);
270 * Default string seperator.
272 elasticlunr.tokenizer.defaultSeperator = /[\s\-]+/;
275 * The sperator used to split a string into tokens. Override this property to change the behaviour of
276 * `elasticlunr.tokenizer` behaviour when tokenizing strings. By default this splits on whitespace and hyphens.
279 * @see elasticlunr.tokenizer
281 elasticlunr.tokenizer.seperator = elasticlunr.tokenizer.defaultSeperator;
284 * Set up customized string seperator
286 * @param {Object} sep The customized seperator that you want to use to tokenize a string.
288 elasticlunr.tokenizer.setSeperator = function(sep) {
289 if (sep !== null && sep !== undefined && typeof(sep) === 'object') {
290 elasticlunr.tokenizer.seperator = sep;
295 * Reset string seperator
298 elasticlunr.tokenizer.resetSeperator = function() {
299 elasticlunr.tokenizer.seperator = elasticlunr.tokenizer.defaultSeperator;
303 * Get string seperator
306 elasticlunr.tokenizer.getSeperator = function() {
307 return elasticlunr.tokenizer.seperator;
310 * elasticlunr.Pipeline
311 * Copyright (C) 2016 Oliver Nightingale
312 * Copyright (C) 2016 Wei Song
316 * elasticlunr.Pipelines maintain an ordered list of functions to be applied to
317 * both documents tokens and query tokens.
319 * An instance of elasticlunr.Index will contain a pipeline
320 * with a trimmer, a stop word filter, an English stemmer. Extra
321 * functions can be added before or after either of these functions or these
322 * default functions can be removed.
324 * When run the pipeline, it will call each function in turn.
326 * The output of the functions in the pipeline will be passed to the next function
327 * in the pipeline. To exclude a token from entering the index the function
328 * should return undefined, the rest of the pipeline will not be called with
331 * For serialisation of pipelines to work, all functions used in an instance of
332 * a pipeline should be registered with elasticlunr.Pipeline. Registered functions can
333 * then be loaded. If trying to load a serialised pipeline that uses functions
334 * that are not registered an error will be thrown.
336 * If not planning on serialising the pipeline then registering pipeline functions
341 elasticlunr.Pipeline = function () {
345 elasticlunr.Pipeline.registeredFunctions = {};
348 * Register a function in the pipeline.
350 * Functions that are used in the pipeline should be registered if the pipeline
351 * needs to be serialised, or a serialised pipeline needs to be loaded.
353 * Registering a function does not add it to a pipeline, functions must still be
354 * added to instances of the pipeline for them to be used when running a pipeline.
356 * @param {Function} fn The function to register.
357 * @param {String} label The label to register this function with
360 elasticlunr.Pipeline.registerFunction = function (fn, label) {
361 if (label in elasticlunr.Pipeline.registeredFunctions) {
362 elasticlunr.utils.warn('Overwriting existing registered function: ' + label);
366 elasticlunr.Pipeline.registeredFunctions[label] = fn;
370 * Get a registered function in the pipeline.
372 * @param {String} label The label of registered function.
376 elasticlunr.Pipeline.getRegisteredFunction = function (label) {
377 if ((label in elasticlunr.Pipeline.registeredFunctions) !== true) {
381 return elasticlunr.Pipeline.registeredFunctions[label];
385 * Warns if the function is not registered as a Pipeline function.
387 * @param {Function} fn The function to check for.
391 elasticlunr.Pipeline.warnIfFunctionNotRegistered = function (fn) {
392 var isRegistered = fn.label && (fn.label in this.registeredFunctions);
395 elasticlunr.utils.warn('Function is not registered with pipeline. This may cause problems when serialising the index.\n', fn);
400 * Loads a previously serialised pipeline.
402 * All functions to be loaded must already be registered with elasticlunr.Pipeline.
403 * If any function from the serialised data has not been registered then an
404 * error will be thrown.
406 * @param {Object} serialised The serialised pipeline to load.
407 * @return {elasticlunr.Pipeline}
410 elasticlunr.Pipeline.load = function (serialised) {
411 var pipeline = new elasticlunr.Pipeline;
413 serialised.forEach(function (fnName) {
414 var fn = elasticlunr.Pipeline.getRegisteredFunction(fnName);
419 throw new Error('Cannot load un-registered function: ' + fnName);
427 * Adds new functions to the end of the pipeline.
429 * Logs a warning if the function has not been registered.
431 * @param {Function} functions Any number of functions to add to the pipeline.
434 elasticlunr.Pipeline.prototype.add = function () {
435 var fns = Array.prototype.slice.call(arguments);
437 fns.forEach(function (fn) {
438 elasticlunr.Pipeline.warnIfFunctionNotRegistered(fn);
439 this._queue.push(fn);
444 * Adds a single function after a function that already exists in the
447 * Logs a warning if the function has not been registered.
448 * If existingFn is not found, throw an Exception.
450 * @param {Function} existingFn A function that already exists in the pipeline.
451 * @param {Function} newFn The new function to add to the pipeline.
454 elasticlunr.Pipeline.prototype.after = function (existingFn, newFn) {
455 elasticlunr.Pipeline.warnIfFunctionNotRegistered(newFn);
457 var pos = this._queue.indexOf(existingFn);
459 throw new Error('Cannot find existingFn');
462 this._queue.splice(pos + 1, 0, newFn);
466 * Adds a single function before a function that already exists in the
469 * Logs a warning if the function has not been registered.
470 * If existingFn is not found, throw an Exception.
472 * @param {Function} existingFn A function that already exists in the pipeline.
473 * @param {Function} newFn The new function to add to the pipeline.
476 elasticlunr.Pipeline.prototype.before = function (existingFn, newFn) {
477 elasticlunr.Pipeline.warnIfFunctionNotRegistered(newFn);
479 var pos = this._queue.indexOf(existingFn);
481 throw new Error('Cannot find existingFn');
484 this._queue.splice(pos, 0, newFn);
488 * Removes a function from the pipeline.
490 * @param {Function} fn The function to remove from the pipeline.
493 elasticlunr.Pipeline.prototype.remove = function (fn) {
494 var pos = this._queue.indexOf(fn);
499 this._queue.splice(pos, 1);
503 * Runs the current list of functions that registered in the pipeline against the
506 * @param {Array} tokens The tokens to run through the pipeline.
510 elasticlunr.Pipeline.prototype.run = function (tokens) {
512 tokenLength = tokens.length,
513 pipelineLength = this._queue.length;
515 for (var i = 0; i < tokenLength; i++) {
516 var token = tokens[i];
518 for (var j = 0; j < pipelineLength; j++) {
519 token = this._queue[j](token, i, tokens);
520 if (token === void 0 || token === null) break;
523 if (token !== void 0 && token !== null) out.push(token);
530 * Resets the pipeline by removing any existing processors.
534 elasticlunr.Pipeline.prototype.reset = function () {
539 * Get the pipeline if user want to check the pipeline.
543 elasticlunr.Pipeline.prototype.get = function () {
548 * Returns a representation of the pipeline ready for serialisation.
549 * Only serialize pipeline function's name. Not storing function, so when
550 * loading the archived JSON index file, corresponding pipeline function is
551 * added by registered function of elasticlunr.Pipeline.registeredFunctions
553 * Logs a warning if the function has not been registered.
558 elasticlunr.Pipeline.prototype.toJSON = function () {
559 return this._queue.map(function (fn) {
560 elasticlunr.Pipeline.warnIfFunctionNotRegistered(fn);
566 * Copyright (C) 2016 Oliver Nightingale
567 * Copyright (C) 2016 Wei Song
571 * elasticlunr.Index is object that manages a search index. It contains the indexes
572 * and stores all the tokens and document lookups. It also provides the main
573 * user facing API for the library.
577 elasticlunr.Index = function () {
580 this.pipeline = new elasticlunr.Pipeline;
581 this.documentStore = new elasticlunr.DocumentStore;
583 this.eventEmitter = new elasticlunr.EventEmitter;
586 this.on('add', 'remove', 'update', (function () {
592 * Bind a handler to events being emitted by the index.
594 * The handler can be bound to many events at the same time.
596 * @param {String} [eventName] The name(s) of events to bind the function to.
597 * @param {Function} fn The serialised set to load.
600 elasticlunr.Index.prototype.on = function () {
601 var args = Array.prototype.slice.call(arguments);
602 return this.eventEmitter.addListener.apply(this.eventEmitter, args);
606 * Removes a handler from an event being emitted by the index.
608 * @param {String} eventName The name of events to remove the function from.
609 * @param {Function} fn The serialised set to load.
612 elasticlunr.Index.prototype.off = function (name, fn) {
613 return this.eventEmitter.removeListener(name, fn);
617 * Loads a previously serialised index.
619 * Issues a warning if the index being imported was serialised
620 * by a different version of elasticlunr.
622 * @param {Object} serialisedData The serialised set to load.
623 * @return {elasticlunr.Index}
626 elasticlunr.Index.load = function (serialisedData) {
627 if (serialisedData.version !== elasticlunr.version) {
628 elasticlunr.utils.warn('version mismatch: current '
629 + elasticlunr.version + ' importing ' + serialisedData.version);
634 idx._fields = serialisedData.fields;
635 idx._ref = serialisedData.ref;
636 idx.documentStore = elasticlunr.DocumentStore.load(serialisedData.documentStore);
637 idx.pipeline = elasticlunr.Pipeline.load(serialisedData.pipeline);
639 for (var field in serialisedData.index) {
640 idx.index[field] = elasticlunr.InvertedIndex.load(serialisedData.index[field]);
647 * Adds a field to the list of fields that will be searchable within documents in the index.
649 * Remember that inner index is build based on field, which means each field has one inverted index.
651 * Fields should be added before any documents are added to the index, fields
652 * that are added after documents are added to the index will only apply to new
653 * documents added to the index.
655 * @param {String} fieldName The name of the field within the document that should be indexed
656 * @return {elasticlunr.Index}
659 elasticlunr.Index.prototype.addField = function (fieldName) {
660 this._fields.push(fieldName);
661 this.index[fieldName] = new elasticlunr.InvertedIndex;
666 * Sets the property used to uniquely identify documents added to the index,
667 * by default this property is 'id'.
669 * This should only be changed before adding documents to the index, changing
670 * the ref property without resetting the index can lead to unexpected results.
672 * @param {String} refName The property to use to uniquely identify the
673 * documents in the index.
674 * @param {Boolean} emitEvent Whether to emit add events, defaults to true
675 * @return {elasticlunr.Index}
678 elasticlunr.Index.prototype.setRef = function (refName) {
685 * Set if the JSON format original documents are save into elasticlunr.DocumentStore
687 * Defaultly save all the original JSON documents.
689 * @param {Boolean} save Whether to save the original JSON documents.
690 * @return {elasticlunr.Index}
693 elasticlunr.Index.prototype.saveDocument = function (save) {
694 this.documentStore = new elasticlunr.DocumentStore(save);
699 * Add a JSON format document to the index.
701 * This is the way new documents enter the index, this function will run the
702 * fields from the document through the index's pipeline and then add it to
703 * the index, it will then show up in search results.
705 * An 'add' event is emitted with the document that has been added and the index
706 * the document has been added to. This event can be silenced by passing false
707 * as the second argument to add.
709 * @param {Object} doc The JSON format document to add to the index.
710 * @param {Boolean} emitEvent Whether or not to emit events, default true.
713 elasticlunr.Index.prototype.addDoc = function (doc, emitEvent) {
715 var emitEvent = emitEvent === undefined ? true : emitEvent;
717 var docRef = doc[this._ref];
719 this.documentStore.addDoc(docRef, doc);
720 this._fields.forEach(function (field) {
721 var fieldTokens = this.pipeline.run(elasticlunr.tokenizer(doc[field]));
722 this.documentStore.addFieldLength(docRef, field, fieldTokens.length);
725 fieldTokens.forEach(function (token) {
726 if (token in tokenCount) tokenCount[token] += 1;
727 else tokenCount[token] = 1;
730 for (var token in tokenCount) {
731 var termFrequency = tokenCount[token];
732 termFrequency = Math.sqrt(termFrequency);
733 this.index[field].addToken(token, { ref: docRef, tf: termFrequency });
737 if (emitEvent) this.eventEmitter.emit('add', doc, this);
741 * Removes a document from the index by doc ref.
743 * To make sure documents no longer show up in search results they can be
744 * removed from the index using this method.
746 * A 'remove' event is emitted with the document that has been removed and the index
747 * the document has been removed from. This event can be silenced by passing false
748 * as the second argument to remove.
750 * If user setting DocumentStore not storing the documents, then remove doc by docRef is not allowed.
752 * @param {String|Integer} docRef The document ref to remove from the index.
753 * @param {Boolean} emitEvent Whether to emit remove events, defaults to true
756 elasticlunr.Index.prototype.removeDocByRef = function (docRef, emitEvent) {
758 if (this.documentStore.isDocStored() === false) {
762 if (!this.documentStore.hasDoc(docRef)) return;
763 var doc = this.documentStore.getDoc(docRef);
764 this.removeDoc(doc, false);
768 * Removes a document from the index.
769 * This remove operation could work even the original doc is not store in the DocumentStore.
771 * To make sure documents no longer show up in search results they can be
772 * removed from the index using this method.
774 * A 'remove' event is emitted with the document that has been removed and the index
775 * the document has been removed from. This event can be silenced by passing false
776 * as the second argument to remove.
779 * @param {Object} doc The document ref to remove from the index.
780 * @param {Boolean} emitEvent Whether to emit remove events, defaults to true
783 elasticlunr.Index.prototype.removeDoc = function (doc, emitEvent) {
786 var emitEvent = emitEvent === undefined ? true : emitEvent;
788 var docRef = doc[this._ref];
789 if (!this.documentStore.hasDoc(docRef)) return;
791 this.documentStore.removeDoc(docRef);
793 this._fields.forEach(function (field) {
794 var fieldTokens = this.pipeline.run(elasticlunr.tokenizer(doc[field]));
795 fieldTokens.forEach(function (token) {
796 this.index[field].removeToken(token, docRef);
800 if (emitEvent) this.eventEmitter.emit('remove', doc, this);
804 * Updates a document in the index.
806 * When a document contained within the index gets updated, fields changed,
807 * added or removed, to make sure it correctly matched against search queries,
808 * it should be updated in the index.
810 * This method is just a wrapper around `remove` and `add`
812 * An 'update' event is emitted with the document that has been updated and the index.
813 * This event can be silenced by passing false as the second argument to update. Only
814 * an update event will be fired, the 'add' and 'remove' events of the underlying calls
817 * @param {Object} doc The document to update in the index.
818 * @param {Boolean} emitEvent Whether to emit update events, defaults to true
819 * @see Index.prototype.remove
820 * @see Index.prototype.add
823 elasticlunr.Index.prototype.updateDoc = function (doc, emitEvent) {
824 var emitEvent = emitEvent === undefined ? true : emitEvent;
826 this.removeDocByRef(doc[this._ref], false);
827 this.addDoc(doc, false);
829 if (emitEvent) this.eventEmitter.emit('update', doc, this);
833 * Calculates the inverse document frequency for a token within the index of a field.
835 * @param {String} token The token to calculate the idf of.
836 * @param {String} field The field to compute idf.
837 * @see Index.prototype.idf
841 elasticlunr.Index.prototype.idf = function (term, field) {
842 var cacheKey = "@" + field + '/' + term;
843 if (Object.prototype.hasOwnProperty.call(this._idfCache, cacheKey)) return this._idfCache[cacheKey];
845 var df = this.index[field].getDocFreq(term);
846 var idf = 1 + Math.log(this.documentStore.length / (df + 1));
847 this._idfCache[cacheKey] = idf;
853 * get fields of current index instance
857 elasticlunr.Index.prototype.getFields = function () {
858 return this._fields.slice();
862 * Searches the index using the passed query.
863 * Queries should be a string, multiple words are allowed.
865 * If config is null, will search all fields defaultly, and lead to OR based query.
866 * If config is specified, will search specified with query time boosting.
868 * All query tokens are passed through the same pipeline that document tokens
869 * are passed through, so any language processing involved will be run on every
872 * Each query term is expanded, so that the term 'he' might be expanded to
873 * 'hello' and 'help' if those terms were already included in the index.
875 * Matching documents are returned as an array of objects, each object contains
876 * the matching document ref, as set for this index, and the similarity score
877 * for this document against the query.
879 * @param {String} query The query to search the index with.
880 * @param {JSON} userConfig The user query config, JSON format.
882 * @see Index.prototype.idf
883 * @see Index.prototype.documentVector
886 elasticlunr.Index.prototype.search = function (query, userConfig) {
887 if (!query) return [];
889 var configStr = null;
890 if (userConfig != null) {
891 configStr = JSON.stringify(userConfig);
894 var config = new elasticlunr.Configuration(configStr, this.getFields()).get();
896 var queryTokens = this.pipeline.run(elasticlunr.tokenizer(query));
898 var queryResults = {};
900 for (var field in config) {
901 var fieldSearchResults = this.fieldSearch(queryTokens, field, config);
902 var fieldBoost = config[field].boost;
904 for (var docRef in fieldSearchResults) {
905 fieldSearchResults[docRef] = fieldSearchResults[docRef] * fieldBoost;
908 for (var docRef in fieldSearchResults) {
909 if (docRef in queryResults) {
910 queryResults[docRef] += fieldSearchResults[docRef];
912 queryResults[docRef] = fieldSearchResults[docRef];
918 for (var docRef in queryResults) {
919 results.push({ref: docRef, score: queryResults[docRef]});
922 results.sort(function (a, b) { return b.score - a.score; });
927 * search queryTokens in specified field.
929 * @param {Array} queryTokens The query tokens to query in this field.
930 * @param {String} field Field to query in.
931 * @param {elasticlunr.Configuration} config The user query config, JSON format.
934 elasticlunr.Index.prototype.fieldSearch = function (queryTokens, fieldName, config) {
935 var booleanType = config[fieldName].bool;
936 var expand = config[fieldName].expand;
937 var boost = config[fieldName].boost;
941 // Do nothing if the boost is 0
946 queryTokens.forEach(function (token) {
947 var tokens = [token];
948 if (expand == true) {
949 tokens = this.index[fieldName].expandToken(token);
951 // Consider every query token in turn. If expanded, each query token
952 // corresponds to a set of tokens, which is all tokens in the
953 // index matching the pattern queryToken* .
954 // For the set of tokens corresponding to a query token, find and score
955 // all matching documents. Store those scores in queryTokenScores,
957 // Then, depending on the value of booleanType, combine the scores
958 // for this query token with previous scores. If booleanType is OR,
959 // then merge the scores by summing into the accumulated total, adding
960 // new document scores are required (effectively a union operator).
961 // If booleanType is AND, accumulate scores only if the document
962 // has previously been scored by another query token (an intersection
964 // Furthermore, since when booleanType is AND, additional
965 // query tokens can't add new documents to the result set, use the
966 // current document set to limit the processing of each new query
967 // token for efficiency (i.e., incremental intersection).
969 var queryTokenScores = {};
970 tokens.forEach(function (key) {
971 var docs = this.index[fieldName].getDocs(key);
972 var idf = this.idf(key, fieldName);
974 if (scores && booleanType == 'AND') {
975 // special case, we can rule out documents that have been
976 // already been filtered out because they weren't scored
977 // by previous query token passes.
978 var filteredDocs = {};
979 for (var docRef in scores) {
980 if (docRef in docs) {
981 filteredDocs[docRef] = docs[docRef];
986 // only record appeared token for retrieved documents for the
987 // original token, not for expaned token.
988 // beause for doing coordNorm for a retrieved document, coordNorm only care how many
989 // query token appear in that document.
990 // so expanded token should not be added into docTokens, if added, this will pollute the
993 this.fieldSearchStats(docTokens, key, docs);
996 for (var docRef in docs) {
997 var tf = this.index[fieldName].getTermFrequency(key, docRef);
998 var fieldLength = this.documentStore.getFieldLength(docRef, fieldName);
999 var fieldLengthNorm = 1;
1000 if (fieldLength != 0) {
1001 fieldLengthNorm = 1 / Math.sqrt(fieldLength);
1006 // currently I'm not sure if this penality is enough,
1007 // need to do verification
1008 penality = (1 - (key.length - token.length) / key.length) * 0.15;
1011 var score = tf * idf * fieldLengthNorm * penality;
1013 if (docRef in queryTokenScores) {
1014 queryTokenScores[docRef] += score;
1016 queryTokenScores[docRef] = score;
1021 scores = this.mergeScores(scores, queryTokenScores, booleanType);
1024 scores = this.coordNorm(scores, docTokens, queryTokens.length);
1029 * Merge the scores from one set of tokens into an accumulated score table.
1030 * Exact operation depends on the op parameter. If op is 'AND', then only the
1031 * intersection of the two score lists is retained. Otherwise, the union of
1032 * the two score lists is returned. For internal use only.
1034 * @param {Object} bool accumulated scores. Should be null on first call.
1035 * @param {String} scores new scores to merge into accumScores.
1036 * @param {Object} op merge operation (should be 'AND' or 'OR').
1040 elasticlunr.Index.prototype.mergeScores = function (accumScores, scores, op) {
1045 var intersection = {};
1046 for (var docRef in scores) {
1047 if (docRef in accumScores) {
1048 intersection[docRef] = accumScores[docRef] + scores[docRef];
1051 return intersection;
1053 for (var docRef in scores) {
1054 if (docRef in accumScores) {
1055 accumScores[docRef] += scores[docRef];
1057 accumScores[docRef] = scores[docRef];
1066 * Record the occuring query token of retrieved doc specified by doc field.
1067 * Only for inner user.
1069 * @param {Object} docTokens a data structure stores which token appears in the retrieved doc.
1070 * @param {String} token query token
1071 * @param {Object} docs the retrieved documents of the query token
1074 elasticlunr.Index.prototype.fieldSearchStats = function (docTokens, token, docs) {
1075 for (var doc in docs) {
1076 if (doc in docTokens) {
1077 docTokens[doc].push(token);
1079 docTokens[doc] = [token];
1085 * coord norm the score of a doc.
1086 * if a doc contain more query tokens, then the score will larger than the doc
1087 * contains less query tokens.
1089 * only for inner use.
1091 * @param {Object} results first results
1092 * @param {Object} docs field search results of a token
1093 * @param {Integer} n query token number
1096 elasticlunr.Index.prototype.coordNorm = function (scores, docTokens, n) {
1097 for (var doc in scores) {
1098 if (!(doc in docTokens)) continue;
1099 var tokens = docTokens[doc].length;
1100 scores[doc] = scores[doc] * tokens / n;
1107 * Returns a representation of the index ready for serialisation.
1112 elasticlunr.Index.prototype.toJSON = function () {
1114 this._fields.forEach(function (field) {
1115 indexJson[field] = this.index[field].toJSON();
1119 version: elasticlunr.version,
1120 fields: this._fields,
1122 documentStore: this.documentStore.toJSON(),
1124 pipeline: this.pipeline.toJSON()
1129 * Applies a plugin to the current index.
1131 * A plugin is a function that is called with the index as its context.
1132 * Plugins can be used to customise or extend the behaviour the index
1133 * in some way. A plugin is just a function, that encapsulated the custom
1134 * behaviour that should be applied to the index.
1136 * The plugin function will be called with the index as its argument, additional
1137 * arguments can also be passed when calling use. The function will be called
1138 * with the index as its context.
1142 * var myPlugin = function (idx, arg1, arg2) {
1143 * // `this` is the index to be extended
1144 * // apply any extensions etc here.
1147 * var idx = elasticlunr(function () {
1148 * this.use(myPlugin, 'arg1', 'arg2')
1151 * @param {Function} plugin The plugin to apply.
1154 elasticlunr.Index.prototype.use = function (plugin) {
1155 var args = Array.prototype.slice.call(arguments, 1);
1157 plugin.apply(this, args);
1160 * elasticlunr.DocumentStore
1161 * Copyright (C) 2016 Wei Song
1165 * elasticlunr.DocumentStore is a simple key-value document store used for storing sets of tokens for
1166 * documents stored in index.
1168 * elasticlunr.DocumentStore store original JSON format documents that you could build search snippet by this original JSON document.
1170 * user could choose whether original JSON format document should be store, if no configuration then document will be stored defaultly.
1171 * If user care more about the index size, user could select not store JSON documents, then this will has some defects, such as user
1172 * could not use JSON document to generate snippets of search results.
1174 * @param {Boolean} save If the original JSON document should be stored.
1178 elasticlunr.DocumentStore = function (save) {
1179 if (save === null || save === undefined) {
1191 * Loads a previously serialised document store
1193 * @param {Object} serialisedData The serialised document store to load.
1194 * @return {elasticlunr.DocumentStore}
1196 elasticlunr.DocumentStore.load = function (serialisedData) {
1197 var store = new this;
1199 store.length = serialisedData.length;
1200 store.docs = serialisedData.docs;
1201 store.docInfo = serialisedData.docInfo;
1202 store._save = serialisedData.save;
1208 * check if current instance store the original doc
1212 elasticlunr.DocumentStore.prototype.isDocStored = function () {
1217 * Stores the given doc in the document store against the given id.
1218 * If docRef already exist, then update doc.
1220 * Document is store by original JSON format, then you could use original document to generate search snippets.
1222 * @param {Integer|String} docRef The key used to store the JSON format doc.
1223 * @param {Object} doc The JSON format doc.
1225 elasticlunr.DocumentStore.prototype.addDoc = function (docRef, doc) {
1226 if (!this.hasDoc(docRef)) this.length++;
1228 if (this._save === true) {
1229 this.docs[docRef] = clone(doc);
1231 this.docs[docRef] = null;
1236 * Retrieves the JSON doc from the document store for a given key.
1238 * If docRef not found, return null.
1239 * If user set not storing the documents, return null.
1241 * @param {Integer|String} docRef The key to lookup and retrieve from the document store.
1243 * @memberOf DocumentStore
1245 elasticlunr.DocumentStore.prototype.getDoc = function (docRef) {
1246 if (this.hasDoc(docRef) === false) return null;
1247 return this.docs[docRef];
1251 * Checks whether the document store contains a key (docRef).
1253 * @param {Integer|String} docRef The id to look up in the document store.
1255 * @memberOf DocumentStore
1257 elasticlunr.DocumentStore.prototype.hasDoc = function (docRef) {
1258 return docRef in this.docs;
1262 * Removes the value for a key in the document store.
1264 * @param {Integer|String} docRef The id to remove from the document store.
1265 * @memberOf DocumentStore
1267 elasticlunr.DocumentStore.prototype.removeDoc = function (docRef) {
1268 if (!this.hasDoc(docRef)) return;
1270 delete this.docs[docRef];
1271 delete this.docInfo[docRef];
1276 * Add field length of a document's field tokens from pipeline results.
1277 * The field length of a document is used to do field length normalization even without the original JSON document stored.
1279 * @param {Integer|String} docRef document's id or reference
1280 * @param {String} fieldName field name
1281 * @param {Integer} length field length
1283 elasticlunr.DocumentStore.prototype.addFieldLength = function (docRef, fieldName, length) {
1284 if (docRef === null || docRef === undefined) return;
1285 if (this.hasDoc(docRef) == false) return;
1287 if (!this.docInfo[docRef]) this.docInfo[docRef] = {};
1288 this.docInfo[docRef][fieldName] = length;
1292 * Update field length of a document's field tokens from pipeline results.
1293 * The field length of a document is used to do field length normalization even without the original JSON document stored.
1295 * @param {Integer|String} docRef document's id or reference
1296 * @param {String} fieldName field name
1297 * @param {Integer} length field length
1299 elasticlunr.DocumentStore.prototype.updateFieldLength = function (docRef, fieldName, length) {
1300 if (docRef === null || docRef === undefined) return;
1301 if (this.hasDoc(docRef) == false) return;
1303 this.addFieldLength(docRef, fieldName, length);
1307 * get field length of a document by docRef
1309 * @param {Integer|String} docRef document id or reference
1310 * @param {String} fieldName field name
1311 * @return {Integer} field length
1313 elasticlunr.DocumentStore.prototype.getFieldLength = function (docRef, fieldName) {
1314 if (docRef === null || docRef === undefined) return 0;
1316 if (!(docRef in this.docs)) return 0;
1317 if (!(fieldName in this.docInfo[docRef])) return 0;
1318 return this.docInfo[docRef][fieldName];
1322 * Returns a JSON representation of the document store used for serialisation.
1324 * @return {Object} JSON format
1325 * @memberOf DocumentStore
1327 elasticlunr.DocumentStore.prototype.toJSON = function () {
1330 docInfo: this.docInfo,
1331 length: this.length,
1339 * @param {Object} object in JSON format
1340 * @return {Object} copied object
1342 function clone(obj) {
1343 if (null === obj || "object" !== typeof obj) return obj;
1345 var copy = obj.constructor();
1347 for (var attr in obj) {
1348 if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
1354 * elasticlunr.stemmer
1355 * Copyright (C) 2016 Oliver Nightingale
1356 * Copyright (C) 2016 Wei Song
1357 * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt
1361 * elasticlunr.stemmer is an english language stemmer, this is a JavaScript
1362 * implementation of the PorterStemmer taken from http://tartarus.org/~martin
1365 * @param {String} str The string to stem
1367 * @see elasticlunr.Pipeline
1369 elasticlunr.stemmer = (function(){
1404 c = "[^aeiou]", // consonant
1405 v = "[aeiouy]", // vowel
1406 C = c + "[^aeiouy]*", // consonant sequence
1407 V = v + "[aeiou]*", // vowel sequence
1409 mgr0 = "^(" + C + ")?" + V + C, // [C]VC... is m>0
1410 meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$", // [C]VC[V] is m=1
1411 mgr1 = "^(" + C + ")?" + V + C + V + C, // [C]VCVC... is m>1
1412 s_v = "^(" + C + ")?" + v; // vowel in stem
1414 var re_mgr0 = new RegExp(mgr0);
1415 var re_mgr1 = new RegExp(mgr1);
1416 var re_meq1 = new RegExp(meq1);
1417 var re_s_v = new RegExp(s_v);
1419 var re_1a = /^(.+?)(ss|i)es$/;
1420 var re2_1a = /^(.+?)([^s])s$/;
1421 var re_1b = /^(.+?)eed$/;
1422 var re2_1b = /^(.+?)(ed|ing)$/;
1424 var re2_1b_2 = /(at|bl|iz)$/;
1425 var re3_1b_2 = new RegExp("([^aeiouylsz])\\1$");
1426 var re4_1b_2 = new RegExp("^" + C + v + "[^aeiouwxy]$");
1428 var re_1c = /^(.+?[^aeiou])y$/;
1429 var re_2 = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;
1431 var re_3 = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;
1433 var re_4 = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;
1434 var re2_4 = /^(.+?)(s|t)(ion)$/;
1436 var re_5 = /^(.+?)e$/;
1438 var re3_5 = new RegExp("^" + C + v + "[^aeiouwxy]$");
1440 var porterStemmer = function porterStemmer(w) {
1449 if (w.length < 3) { return w; }
1451 firstch = w.substr(0,1);
1452 if (firstch == "y") {
1453 w = firstch.toUpperCase() + w.substr(1);
1460 if (re.test(w)) { w = w.replace(re,"$1$2"); }
1461 else if (re2.test(w)) { w = w.replace(re2,"$1$2"); }
1467 var fp = re.exec(w);
1469 if (re.test(fp[1])) {
1471 w = w.replace(re,"");
1473 } else if (re2.test(w)) {
1474 var fp = re2.exec(w);
1477 if (re2.test(stem)) {
1482 if (re2.test(w)) { w = w + "e"; }
1483 else if (re3.test(w)) { re = re_1b_2; w = w.replace(re,""); }
1484 else if (re4.test(w)) { w = w + "e"; }
1488 // 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)
1491 var fp = re.exec(w);
1499 var fp = re.exec(w);
1503 if (re.test(stem)) {
1504 w = stem + step2list[suffix];
1511 var fp = re.exec(w);
1515 if (re.test(stem)) {
1516 w = stem + step3list[suffix];
1524 var fp = re.exec(w);
1527 if (re.test(stem)) {
1530 } else if (re2.test(w)) {
1531 var fp = re2.exec(w);
1532 stem = fp[1] + fp[2];
1534 if (re2.test(stem)) {
1542 var fp = re.exec(w);
1547 if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) {
1554 if (re.test(w) && re2.test(w)) {
1556 w = w.replace(re,"");
1559 // and turn initial Y back to y
1561 if (firstch == "y") {
1562 w = firstch.toLowerCase() + w.substr(1);
1568 return porterStemmer;
1571 elasticlunr.Pipeline.registerFunction(elasticlunr.stemmer, 'stemmer');
1573 * elasticlunr.stopWordFilter
1574 * Copyright (C) 2016 Oliver Nightingale
1575 * Copyright (C) 2016 Wei Song
1579 * elasticlunr.stopWordFilter is an English language stop words filter, any words
1580 * contained in the stop word list will not be passed through the filter.
1582 * This is intended to be used in the Pipeline. If the token does not pass the
1583 * filter then undefined will be returned.
1584 * Currently this StopwordFilter using dictionary to do O(1) time complexity stop word filtering.
1587 * @param {String} token The token to pass through the filter
1589 * @see elasticlunr.Pipeline
1591 elasticlunr.stopWordFilter = function (token) {
1592 if (token && elasticlunr.stopWordFilter.stopWords[token] !== true) {
1598 * Remove predefined stop words
1599 * if user want to use customized stop words, user could use this function to delete
1600 * all predefined stopwords.
1604 elasticlunr.clearStopWords = function () {
1605 elasticlunr.stopWordFilter.stopWords = {};
1609 * Add customized stop words
1610 * user could use this function to add customized stop words
1612 * @params {Array} words customized stop words
1615 elasticlunr.addStopWords = function (words) {
1616 if (words == null || Array.isArray(words) === false) return;
1618 words.forEach(function (word) {
1619 elasticlunr.stopWordFilter.stopWords[word] = true;
1624 * Reset to default stop words
1625 * user could use this function to restore default stop words
1629 elasticlunr.resetStopWords = function () {
1630 elasticlunr.stopWordFilter.stopWords = elasticlunr.defaultStopWords;
1633 elasticlunr.defaultStopWords = {
1756 elasticlunr.stopWordFilter.stopWords = elasticlunr.defaultStopWords;
1758 elasticlunr.Pipeline.registerFunction(elasticlunr.stopWordFilter, 'stopWordFilter');
1760 * elasticlunr.trimmer
1761 * Copyright (C) 2016 Oliver Nightingale
1762 * Copyright (C) 2016 Wei Song
1766 * elasticlunr.trimmer is a pipeline function for trimming non word
1767 * characters from the begining and end of tokens before they
1770 * This implementation may not work correctly for non latin
1771 * characters and should either be removed or adapted for use
1772 * with languages with non-latin characters.
1775 * @param {String} token The token to pass through the filter
1777 * @see elasticlunr.Pipeline
1779 elasticlunr.trimmer = function (token) {
1780 if (token === null || token === undefined) {
1781 throw new Error('token should not be undefined');
1785 .replace(/^\W+/, '')
1786 .replace(/\W+$/, '');
1789 elasticlunr.Pipeline.registerFunction(elasticlunr.trimmer, 'trimmer');
1791 * elasticlunr.InvertedIndex
1792 * Copyright (C) 2016 Wei Song
1793 * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt
1797 * elasticlunr.InvertedIndex is used for efficiently storing and
1798 * lookup of documents that contain a given token.
1802 elasticlunr.InvertedIndex = function () {
1803 this.root = { docs: {}, df: 0 };
1807 * Loads a previously serialised inverted index.
1809 * @param {Object} serialisedData The serialised inverted index to load.
1810 * @return {elasticlunr.InvertedIndex}
1812 elasticlunr.InvertedIndex.load = function (serialisedData) {
1814 idx.root = serialisedData.root;
1820 * Adds a {token: tokenInfo} pair to the inverted index.
1821 * If the token already exist, then update the tokenInfo.
1823 * tokenInfo format: { ref: 1, tf: 2}
1824 * tokenInfor should contains the document's ref and the tf(token frequency) of that token in
1827 * By default this function starts at the root of the current inverted index, however
1828 * it can start at any node of the inverted index if required.
1830 * @param {String} token
1831 * @param {Object} tokenInfo format: { ref: 1, tf: 2}
1832 * @param {Object} root An optional node at which to start looking for the
1833 * correct place to enter the doc, by default the root of this elasticlunr.InvertedIndex
1835 * @memberOf InvertedIndex
1837 elasticlunr.InvertedIndex.prototype.addToken = function (token, tokenInfo, root) {
1838 var root = root || this.root,
1841 while (idx <= token.length - 1) {
1842 var key = token[idx];
1844 if (!(key in root)) root[key] = {docs: {}, df: 0};
1849 var docRef = tokenInfo.ref;
1850 if (!root.docs[docRef]) {
1851 // if this doc not exist, then add this doc
1852 root.docs[docRef] = {tf: tokenInfo.tf};
1855 // if this doc already exist, then update tokenInfo
1856 root.docs[docRef] = {tf: tokenInfo.tf};
1861 * Checks whether a token is in this elasticlunr.InvertedIndex.
1864 * @param {String} token The token to be checked
1866 * @memberOf InvertedIndex
1868 elasticlunr.InvertedIndex.prototype.hasToken = function (token) {
1869 if (!token) return false;
1871 var node = this.root;
1873 for (var i = 0; i < token.length; i++) {
1874 if (!node[token[i]]) return false;
1875 node = node[token[i]];
1882 * Retrieve a node from the inverted index for a given token.
1883 * If token not found in this InvertedIndex, return null.
1886 * @param {String} token The token to get the node for.
1888 * @see InvertedIndex.prototype.get
1889 * @memberOf InvertedIndex
1891 elasticlunr.InvertedIndex.prototype.getNode = function (token) {
1892 if (!token) return null;
1894 var node = this.root;
1896 for (var i = 0; i < token.length; i++) {
1897 if (!node[token[i]]) return null;
1898 node = node[token[i]];
1905 * Retrieve the documents of a given token.
1906 * If token not found, return {}.
1909 * @param {String} token The token to get the documents for.
1911 * @memberOf InvertedIndex
1913 elasticlunr.InvertedIndex.prototype.getDocs = function (token) {
1914 var node = this.getNode(token);
1923 * Retrieve term frequency of given token in given docRef.
1924 * If token or docRef not found, return 0.
1927 * @param {String} token The token to get the documents for.
1928 * @param {String|Integer} docRef
1930 * @memberOf InvertedIndex
1932 elasticlunr.InvertedIndex.prototype.getTermFrequency = function (token, docRef) {
1933 var node = this.getNode(token);
1939 if (!(docRef in node.docs)) {
1943 return node.docs[docRef].tf;
1947 * Retrieve the document frequency of given token.
1948 * If token not found, return 0.
1951 * @param {String} token The token to get the documents for.
1953 * @memberOf InvertedIndex
1955 elasticlunr.InvertedIndex.prototype.getDocFreq = function (token) {
1956 var node = this.getNode(token);
1966 * Remove the document identified by document's ref from the token in the inverted index.
1969 * @param {String} token Remove the document from which token.
1970 * @param {String} ref The ref of the document to remove from given token.
1971 * @memberOf InvertedIndex
1973 elasticlunr.InvertedIndex.prototype.removeToken = function (token, ref) {
1975 var node = this.getNode(token);
1977 if (node == null) return;
1979 if (ref in node.docs) {
1980 delete node.docs[ref];
1986 * Find all the possible suffixes of given token using tokens currently in the inverted index.
1987 * If token not found, return empty Array.
1989 * @param {String} token The token to expand.
1991 * @memberOf InvertedIndex
1993 elasticlunr.InvertedIndex.prototype.expandToken = function (token, memo, root) {
1994 if (token == null || token == '') return [];
1995 var memo = memo || [];
1997 if (root == void 0) {
1998 root = this.getNode(token);
1999 if (root == null) return memo;
2002 if (root.df > 0) memo.push(token);
2004 for (var key in root) {
2005 if (key === 'docs') continue;
2006 if (key === 'df') continue;
2007 this.expandToken(token + key, memo, root[key]);
2014 * Returns a representation of the inverted index ready for serialisation.
2017 * @memberOf InvertedIndex
2019 elasticlunr.InvertedIndex.prototype.toJSON = function () {
2026 * elasticlunr.Configuration
2027 * Copyright (C) 2016 Wei Song
2031 * elasticlunr.Configuration is used to analyze the user search configuration.
2033 * By elasticlunr.Configuration user could set query-time boosting, boolean model in each field.
2035 * Currently configuration supports:
2036 * 1. query-time boosting, user could set how to boost each field.
2037 * 2. boolean model chosing, user could choose which boolean model to use for each field.
2038 * 3. token expandation, user could set token expand to True to improve Recall. Default is False.
2040 * Query time boosting must be configured by field category, "boolean" model could be configured
2041 * by both field category or globally as the following example. Field configuration for "boolean"
2042 * will overwrite global configuration.
2043 * Token expand could be configured both by field category or golbally. Local field configuration will
2044 * overwrite global configuration.
2046 * configuration example:
2049 * title: {boost: 2},
2055 * "bool" field configuation overwrite global configuation example:
2058 * title: {boost: 2, bool: "AND"},
2067 * title: {boost: 2, bool: "AND"},
2074 * "expand" example for field category:
2077 * title: {boost: 2, bool: "AND", expand: true},
2083 * setting the boost to 0 ignores the field (this will only search the title):
2086 * title: {boost: 1},
2091 * then, user could search with configuration to do query-time boosting.
2092 * idx.search('oracle database', {fields: {title: {boost: 2}, body: {boost: 1}}});
2097 * @param {String} config user configuration
2098 * @param {Array} fields fields of index instance
2101 elasticlunr.Configuration = function (config, fields) {
2102 var config = config || '';
2104 if (fields == undefined || fields == null) {
2105 throw new Error('fields should not be null');
2112 userConfig = JSON.parse(config);
2113 this.buildUserConfig(userConfig, fields);
2115 elasticlunr.utils.warn('user configuration parse failed, will use default configuration');
2116 this.buildDefaultConfig(fields);
2121 * Build default search configuration.
2123 * @param {Array} fields fields of index instance
2125 elasticlunr.Configuration.prototype.buildDefaultConfig = function (fields) {
2127 fields.forEach(function (field) {
2128 this.config[field] = {
2137 * Build user configuration.
2139 * @param {JSON} config User JSON configuratoin
2140 * @param {Array} fields fields of index instance
2142 elasticlunr.Configuration.prototype.buildUserConfig = function (config, fields) {
2143 var global_bool = "OR";
2144 var global_expand = false;
2147 if ('bool' in config) {
2148 global_bool = config['bool'] || global_bool;
2151 if ('expand' in config) {
2152 global_expand = config['expand'] || global_expand;
2155 if ('fields' in config) {
2156 for (var field in config['fields']) {
2157 if (fields.indexOf(field) > -1) {
2158 var field_config = config['fields'][field];
2159 var field_expand = global_expand;
2160 if (field_config.expand != undefined) {
2161 field_expand = field_config.expand;
2164 this.config[field] = {
2165 boost: (field_config.boost || field_config.boost === 0) ? field_config.boost : 1,
2166 bool: field_config.bool || global_bool,
2167 expand: field_expand
2170 elasticlunr.utils.warn('field name in user configuration not found in index instance fields');
2174 this.addAllFields2UserConfig(global_bool, global_expand, fields);
2179 * Add all fields to user search configuration.
2181 * @param {String} bool Boolean model
2182 * @param {String} expand Expand model
2183 * @param {Array} fields fields of index instance
2185 elasticlunr.Configuration.prototype.addAllFields2UserConfig = function (bool, expand, fields) {
2186 fields.forEach(function (field) {
2187 this.config[field] = {
2196 * get current user configuration
2198 elasticlunr.Configuration.prototype.get = function () {
2203 * reset user search configuration.
2205 elasticlunr.Configuration.prototype.reset = function () {
2209 * sorted_set.js is added only to make elasticlunr.js compatible with lunr-languages.
2210 * if elasticlunr.js support different languages by default, this will make elasticlunr.js
2211 * much bigger that not good for browser usage.
2218 * Copyright (C) 2016 Oliver Nightingale
2222 * lunr.SortedSets are used to maintain an array of uniq values in a sorted
2227 lunr.SortedSet = function () {
2233 * Loads a previously serialised sorted set.
2235 * @param {Array} serialisedData The serialised set to load.
2236 * @returns {lunr.SortedSet}
2237 * @memberOf SortedSet
2239 lunr.SortedSet.load = function (serialisedData) {
2242 set.elements = serialisedData
2243 set.length = serialisedData.length
2249 * Inserts new items into the set in the correct position to maintain the
2252 * @param {Object} The objects to add to this set.
2253 * @memberOf SortedSet
2255 lunr.SortedSet.prototype.add = function () {
2258 for (i = 0; i < arguments.length; i++) {
2259 element = arguments[i]
2260 if (~this.indexOf(element)) continue
2261 this.elements.splice(this.locationFor(element), 0, element)
2264 this.length = this.elements.length
2268 * Converts this sorted set into an array.
2271 * @memberOf SortedSet
2273 lunr.SortedSet.prototype.toArray = function () {
2274 return this.elements.slice()
2278 * Creates a new array with the results of calling a provided function on every
2279 * element in this sorted set.
2281 * Delegates to Array.prototype.map and has the same signature.
2283 * @param {Function} fn The function that is called on each element of the
2285 * @param {Object} ctx An optional object that can be used as the context
2286 * for the function fn.
2288 * @memberOf SortedSet
2290 lunr.SortedSet.prototype.map = function (fn, ctx) {
2291 return this.elements.map(fn, ctx)
2295 * Executes a provided function once per sorted set element.
2297 * Delegates to Array.prototype.forEach and has the same signature.
2299 * @param {Function} fn The function that is called on each element of the
2301 * @param {Object} ctx An optional object that can be used as the context
2302 * @memberOf SortedSet
2303 * for the function fn.
2305 lunr.SortedSet.prototype.forEach = function (fn, ctx) {
2306 return this.elements.forEach(fn, ctx)
2310 * Returns the index at which a given element can be found in the
2311 * sorted set, or -1 if it is not present.
2313 * @param {Object} elem The object to locate in the sorted set.
2315 * @memberOf SortedSet
2317 lunr.SortedSet.prototype.indexOf = function (elem) {
2319 end = this.elements.length,
2320 sectionLength = end - start,
2321 pivot = start + Math.floor(sectionLength / 2),
2322 pivotElem = this.elements[pivot]
2324 while (sectionLength > 1) {
2325 if (pivotElem === elem) return pivot
2327 if (pivotElem < elem) start = pivot
2328 if (pivotElem > elem) end = pivot
2330 sectionLength = end - start
2331 pivot = start + Math.floor(sectionLength / 2)
2332 pivotElem = this.elements[pivot]
2335 if (pivotElem === elem) return pivot
2341 * Returns the position within the sorted set that an element should be
2342 * inserted at to maintain the current order of the set.
2344 * This function assumes that the element to search for does not already exist
2345 * in the sorted set.
2347 * @param {Object} elem The elem to find the position for in the set
2349 * @memberOf SortedSet
2351 lunr.SortedSet.prototype.locationFor = function (elem) {
2353 end = this.elements.length,
2354 sectionLength = end - start,
2355 pivot = start + Math.floor(sectionLength / 2),
2356 pivotElem = this.elements[pivot]
2358 while (sectionLength > 1) {
2359 if (pivotElem < elem) start = pivot
2360 if (pivotElem > elem) end = pivot
2362 sectionLength = end - start
2363 pivot = start + Math.floor(sectionLength / 2)
2364 pivotElem = this.elements[pivot]
2367 if (pivotElem > elem) return pivot
2368 if (pivotElem < elem) return pivot + 1
2372 * Creates a new lunr.SortedSet that contains the elements in the intersection
2373 * of this set and the passed set.
2375 * @param {lunr.SortedSet} otherSet The set to intersect with this set.
2376 * @returns {lunr.SortedSet}
2377 * @memberOf SortedSet
2379 lunr.SortedSet.prototype.intersect = function (otherSet) {
2380 var intersectSet = new lunr.SortedSet,
2382 a_len = this.length, b_len = otherSet.length,
2383 a = this.elements, b = otherSet.elements
2386 if (i > a_len - 1 || j > b_len - 1) break
2388 if (a[i] === b[j]) {
2389 intersectSet.add(a[i])
2409 * Makes a copy of this set
2411 * @returns {lunr.SortedSet}
2412 * @memberOf SortedSet
2414 lunr.SortedSet.prototype.clone = function () {
2415 var clone = new lunr.SortedSet
2417 clone.elements = this.toArray()
2418 clone.length = clone.elements.length
2424 * Creates a new lunr.SortedSet that contains the elements in the union
2425 * of this set and the passed set.
2427 * @param {lunr.SortedSet} otherSet The set to union with this set.
2428 * @returns {lunr.SortedSet}
2429 * @memberOf SortedSet
2431 lunr.SortedSet.prototype.union = function (otherSet) {
2432 var longSet, shortSet, unionSet
2434 if (this.length >= otherSet.length) {
2435 longSet = this, shortSet = otherSet
2437 longSet = otherSet, shortSet = this
2440 unionSet = longSet.clone()
2442 for(var i = 0, shortSetElements = shortSet.toArray(); i < shortSetElements.length; i++){
2443 unionSet.add(shortSetElements[i])
2450 * Returns a representation of the sorted set ready for serialisation.
2453 * @memberOf SortedSet
2455 lunr.SortedSet.prototype.toJSON = function () {
2456 return this.toArray()
2459 * export the module via AMD, CommonJS or as a browser global
2460 * Export code from https://github.com/umdjs/umd/blob/master/returnExports.js
2462 ;(function (root, factory) {
2463 if (typeof define === 'function' && define.amd) {
2464 // AMD. Register as an anonymous module.
2466 } else if (typeof exports === 'object') {
2468 * Node. Does not work with strict CommonJS, but
2469 * only CommonJS-like enviroments that support module.exports,
2472 module.exports = factory()
2474 // Browser globals (root is window)
2475 root.elasticlunr = factory()
2477 }(this, function () {
2479 * Just return a value to define the module export.
2480 * This example returns an object, but the module
2481 * can return a function as the exported value.