rewrote quickstart, build-process
[AGL/documentation.git] / theme / mkdocs_windmill / js / elasticlunr.js
1 /**
2  * elasticlunr - http://weixsong.github.io
3  * Lightweight full-text search engine in Javascript for browser search and offline search. - 0.9.5
4  *
5  * Copyright (C) 2016 Oliver Nightingale
6  * Copyright (C) 2016 Wei Song
7  * MIT Licensed
8  * @license
9  */
10
11 (function(){
12
13 /*!
14  * elasticlunr.js
15  * Copyright (C) 2016 Oliver Nightingale
16  * Copyright (C) 2016 Wei Song
17  */
18
19 /**
20  * Convenience function for instantiating a new elasticlunr index and configuring it
21  * with the default pipeline functions and the passed config function.
22  *
23  * When using this convenience function a new index will be created with the
24  * following functions already in the pipeline:
25  *
26  * 1. elasticlunr.trimmer - trim non-word character
27  * 2. elasticlunr.StopWordFilter - filters out any stop words before they enter the
28  * index
29  * 3. elasticlunr.stemmer - stems the tokens before entering the index.
30  *
31  *
32  * Example:
33  *
34  *     var idx = elasticlunr(function () {
35  *       this.addField('id');
36  *       this.addField('title');
37  *       this.addField('body');
38  *
39  *       //this.setRef('id'); // default ref is 'id'
40  *
41  *       this.pipeline.add(function () {
42  *         // some custom pipeline function
43  *       });
44  *     });
45  *
46  *    idx.addDoc({
47  *      id: 1,
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.'
50  *    });
51  *
52  *    idx.addDoc({
53  *      id: 2,
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.'
56  *    });
57  *
58  *    # simple search
59  *    idx.search('oracle database');
60  *
61  *    # search with query-time boosting
62  *    idx.search('oracle database', {fields: {title: {boost: 2}, body: {boost: 1}}});
63  *
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.
67  * @namespace
68  * @module
69  * @return {elasticlunr.Index}
70  *
71  */
72 var elasticlunr = function (config) {
73   var idx = new elasticlunr.Index;
74
75   idx.pipeline.add(
76     elasticlunr.trimmer,
77     elasticlunr.stopWordFilter,
78     elasticlunr.stemmer
79   );
80
81   if (config) config.call(idx, idx);
82
83   return idx;
84 };
85
86 elasticlunr.version = "0.9.5";
87
88 // only used this to make elasticlunr.js compatible with lunr-languages
89 // this is a trick to define a global alias of elasticlunr
90 lunr = elasticlunr;
91
92 /*!
93  * elasticlunr.utils
94  * Copyright (C) 2016 Oliver Nightingale
95  * Copyright (C) 2016 Wei Song
96  */
97
98 /**
99  * A namespace containing utils for the rest of the elasticlunr library
100  */
101 elasticlunr.utils = {};
102
103 /**
104  * Print a warning message to the console.
105  *
106  * @param {String} message The message to be printed.
107  * @memberOf Utils
108  */
109 elasticlunr.utils.warn = (function (global) {
110   return function (message) {
111     if (global.console && console.warn) {
112       console.warn(message);
113     }
114   };
115 })(this);
116
117 /**
118  * Convert an object to string.
119  *
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.
123  *
124  * @param {object} obj The object to convert to a string.
125  * @return {String} string representation of the passed object.
126  * @memberOf Utils
127  */
128 elasticlunr.utils.toString = function (obj) {
129   if (obj === void 0 || obj === null) {
130     return "";
131   }
132
133   return obj.toString();
134 };
135 /*!
136  * elasticlunr.EventEmitter
137  * Copyright (C) 2016 Oliver Nightingale
138  * Copyright (C) 2016 Wei Song
139  */
140
141 /**
142  * elasticlunr.EventEmitter is an event emitter for elasticlunr.
143  * It manages adding and removing event handlers and triggering events and their handlers.
144  *
145  * Each event could has multiple corresponding functions,
146  * these functions will be called as the sequence that they are added into the event.
147  *
148  * @constructor
149  */
150 elasticlunr.EventEmitter = function () {
151   this.events = {};
152 };
153
154 /**
155  * Binds a handler function to a specific event(s).
156  *
157  * Can bind a single function to many different events in one call.
158  *
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
162  */
163 elasticlunr.EventEmitter.prototype.addListener = function () {
164   var args = Array.prototype.slice.call(arguments),
165       fn = args.pop(),
166       names = args;
167
168   if (typeof fn !== "function") throw new TypeError ("last argument must be a function");
169
170   names.forEach(function (name) {
171     if (!this.hasHandler(name)) this.events[name] = [];
172     this.events[name].push(fn);
173   }, this);
174 };
175
176 /**
177  * Removes a handler function from a specific event.
178  *
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
182  */
183 elasticlunr.EventEmitter.prototype.removeListener = function (name, fn) {
184   if (!this.hasHandler(name)) return;
185
186   var fnIndex = this.events[name].indexOf(fn);
187   if (fnIndex === -1) return;
188
189   this.events[name].splice(fnIndex, 1);
190
191   if (this.events[name].length == 0) delete this.events[name];
192 };
193
194 /**
195  * Call all functions that bounded to the given event.
196  *
197  * Additional data can be passed to the event handler as arguments to `emit`
198  * after the event name.
199  *
200  * @param {String} eventName The name of the event to emit.
201  * @memberOf EventEmitter
202  */
203 elasticlunr.EventEmitter.prototype.emit = function (name) {
204   if (!this.hasHandler(name)) return;
205
206   var args = Array.prototype.slice.call(arguments, 1);
207
208   this.events[name].forEach(function (fn) {
209     fn.apply(undefined, args);
210   }, this);
211 };
212
213 /**
214  * Checks whether a handler has ever been stored against an event.
215  *
216  * @param {String} eventName The name of the event to check.
217  * @private
218  * @memberOf EventEmitter
219  */
220 elasticlunr.EventEmitter.prototype.hasHandler = function (name) {
221   return name in this.events;
222 };
223 /*!
224  * elasticlunr.tokenizer
225  * Copyright (C) 2016 Oliver Nightingale
226  * Copyright (C) 2016 Wei Song
227  */
228
229 /**
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.
236  *
237  * @module
238  * @param {String} str The string that you want to tokenize.
239  * @see elasticlunr.tokenizer.seperator
240  * @return {Array}
241  */
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) {
247         return false;
248       }
249
250       return true;
251     });
252
253     arr = arr.map(function (t) {
254       return elasticlunr.utils.toString(t).toLowerCase();
255     });
256
257     var out = [];
258     arr.forEach(function(item) {
259       var tokens = item.split(elasticlunr.tokenizer.seperator);
260       out = out.concat(tokens);
261     }, this);
262
263     return out;
264   }
265
266   return str.toString().trim().toLowerCase().split(elasticlunr.tokenizer.seperator);
267 };
268
269 /**
270  * Default string seperator.
271  */
272 elasticlunr.tokenizer.defaultSeperator = /[\s\-]+/;
273
274 /**
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.
277  *
278  * @static
279  * @see elasticlunr.tokenizer
280  */
281 elasticlunr.tokenizer.seperator = elasticlunr.tokenizer.defaultSeperator;
282
283 /**
284  * Set up customized string seperator
285  *
286  * @param {Object} sep The customized seperator that you want to use to tokenize a string.
287  */
288 elasticlunr.tokenizer.setSeperator = function(sep) {
289     if (sep !== null && sep !== undefined && typeof(sep) === 'object') {
290         elasticlunr.tokenizer.seperator = sep;
291     }
292 }
293
294 /**
295  * Reset string seperator
296  *
297  */
298 elasticlunr.tokenizer.resetSeperator = function() {
299     elasticlunr.tokenizer.seperator = elasticlunr.tokenizer.defaultSeperator;
300 }
301
302 /**
303  * Get string seperator
304  *
305  */
306 elasticlunr.tokenizer.getSeperator = function() {
307     return elasticlunr.tokenizer.seperator;
308 }
309 /*!
310  * elasticlunr.Pipeline
311  * Copyright (C) 2016 Oliver Nightingale
312  * Copyright (C) 2016 Wei Song
313  */
314
315 /**
316  * elasticlunr.Pipelines maintain an ordered list of functions to be applied to
317  * both documents tokens and query tokens.
318  *
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.
323  *
324  * When run the pipeline, it will call each function in turn.
325  *
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
329  * this token.
330  *
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.
335  *
336  * If not planning on serialising the pipeline then registering pipeline functions
337  * is not necessary.
338  *
339  * @constructor
340  */
341 elasticlunr.Pipeline = function () {
342   this._queue = [];
343 };
344
345 elasticlunr.Pipeline.registeredFunctions = {};
346
347 /**
348  * Register a function in the pipeline.
349  *
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.
352  *
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.
355  *
356  * @param {Function} fn The function to register.
357  * @param {String} label The label to register this function with
358  * @memberOf Pipeline
359  */
360 elasticlunr.Pipeline.registerFunction = function (fn, label) {
361   if (label in elasticlunr.Pipeline.registeredFunctions) {
362     elasticlunr.utils.warn('Overwriting existing registered function: ' + label);
363   }
364
365   fn.label = label;
366   elasticlunr.Pipeline.registeredFunctions[label] = fn;
367 };
368
369 /**
370  * Get a registered function in the pipeline.
371  *
372  * @param {String} label The label of registered function.
373  * @return {Function}
374  * @memberOf Pipeline
375  */
376 elasticlunr.Pipeline.getRegisteredFunction = function (label) {
377   if ((label in elasticlunr.Pipeline.registeredFunctions) !== true) {
378     return null;
379   }
380
381   return elasticlunr.Pipeline.registeredFunctions[label];
382 };
383
384 /**
385  * Warns if the function is not registered as a Pipeline function.
386  *
387  * @param {Function} fn The function to check for.
388  * @private
389  * @memberOf Pipeline
390  */
391 elasticlunr.Pipeline.warnIfFunctionNotRegistered = function (fn) {
392   var isRegistered = fn.label && (fn.label in this.registeredFunctions);
393
394   if (!isRegistered) {
395     elasticlunr.utils.warn('Function is not registered with pipeline. This may cause problems when serialising the index.\n', fn);
396   }
397 };
398
399 /**
400  * Loads a previously serialised pipeline.
401  *
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.
405  *
406  * @param {Object} serialised The serialised pipeline to load.
407  * @return {elasticlunr.Pipeline}
408  * @memberOf Pipeline
409  */
410 elasticlunr.Pipeline.load = function (serialised) {
411   var pipeline = new elasticlunr.Pipeline;
412
413   serialised.forEach(function (fnName) {
414     var fn = elasticlunr.Pipeline.getRegisteredFunction(fnName);
415
416     if (fn) {
417       pipeline.add(fn);
418     } else {
419       throw new Error('Cannot load un-registered function: ' + fnName);
420     }
421   });
422
423   return pipeline;
424 };
425
426 /**
427  * Adds new functions to the end of the pipeline.
428  *
429  * Logs a warning if the function has not been registered.
430  *
431  * @param {Function} functions Any number of functions to add to the pipeline.
432  * @memberOf Pipeline
433  */
434 elasticlunr.Pipeline.prototype.add = function () {
435   var fns = Array.prototype.slice.call(arguments);
436
437   fns.forEach(function (fn) {
438     elasticlunr.Pipeline.warnIfFunctionNotRegistered(fn);
439     this._queue.push(fn);
440   }, this);
441 };
442
443 /**
444  * Adds a single function after a function that already exists in the
445  * pipeline.
446  *
447  * Logs a warning if the function has not been registered.
448  * If existingFn is not found, throw an Exception.
449  *
450  * @param {Function} existingFn A function that already exists in the pipeline.
451  * @param {Function} newFn The new function to add to the pipeline.
452  * @memberOf Pipeline
453  */
454 elasticlunr.Pipeline.prototype.after = function (existingFn, newFn) {
455   elasticlunr.Pipeline.warnIfFunctionNotRegistered(newFn);
456
457   var pos = this._queue.indexOf(existingFn);
458   if (pos === -1) {
459     throw new Error('Cannot find existingFn');
460   }
461
462   this._queue.splice(pos + 1, 0, newFn);
463 };
464
465 /**
466  * Adds a single function before a function that already exists in the
467  * pipeline.
468  *
469  * Logs a warning if the function has not been registered.
470  * If existingFn is not found, throw an Exception.
471  *
472  * @param {Function} existingFn A function that already exists in the pipeline.
473  * @param {Function} newFn The new function to add to the pipeline.
474  * @memberOf Pipeline
475  */
476 elasticlunr.Pipeline.prototype.before = function (existingFn, newFn) {
477   elasticlunr.Pipeline.warnIfFunctionNotRegistered(newFn);
478
479   var pos = this._queue.indexOf(existingFn);
480   if (pos === -1) {
481     throw new Error('Cannot find existingFn');
482   }
483
484   this._queue.splice(pos, 0, newFn);
485 };
486
487 /**
488  * Removes a function from the pipeline.
489  *
490  * @param {Function} fn The function to remove from the pipeline.
491  * @memberOf Pipeline
492  */
493 elasticlunr.Pipeline.prototype.remove = function (fn) {
494   var pos = this._queue.indexOf(fn);
495   if (pos === -1) {
496     return;
497   }
498
499   this._queue.splice(pos, 1);
500 };
501
502 /**
503  * Runs the current list of functions that registered in the pipeline against the
504  * input tokens.
505  *
506  * @param {Array} tokens The tokens to run through the pipeline.
507  * @return {Array}
508  * @memberOf Pipeline
509  */
510 elasticlunr.Pipeline.prototype.run = function (tokens) {
511   var out = [],
512       tokenLength = tokens.length,
513       pipelineLength = this._queue.length;
514
515   for (var i = 0; i < tokenLength; i++) {
516     var token = tokens[i];
517
518     for (var j = 0; j < pipelineLength; j++) {
519       token = this._queue[j](token, i, tokens);
520       if (token === void 0 || token === null) break;
521     };
522
523     if (token !== void 0 && token !== null) out.push(token);
524   };
525
526   return out;
527 };
528
529 /**
530  * Resets the pipeline by removing any existing processors.
531  *
532  * @memberOf Pipeline
533  */
534 elasticlunr.Pipeline.prototype.reset = function () {
535   this._queue = [];
536 };
537
538  /**
539   * Get the pipeline if user want to check the pipeline.
540   *
541   * @memberOf Pipeline
542   */
543  elasticlunr.Pipeline.prototype.get = function () {
544    return this._queue;
545  };
546
547 /**
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
552  *
553  * Logs a warning if the function has not been registered.
554  *
555  * @return {Array}
556  * @memberOf Pipeline
557  */
558 elasticlunr.Pipeline.prototype.toJSON = function () {
559   return this._queue.map(function (fn) {
560     elasticlunr.Pipeline.warnIfFunctionNotRegistered(fn);
561     return fn.label;
562   });
563 };
564 /*!
565  * elasticlunr.Index
566  * Copyright (C) 2016 Oliver Nightingale
567  * Copyright (C) 2016 Wei Song
568  */
569
570 /**
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.
574  *
575  * @constructor
576  */
577 elasticlunr.Index = function () {
578   this._fields = [];
579   this._ref = 'id';
580   this.pipeline = new elasticlunr.Pipeline;
581   this.documentStore = new elasticlunr.DocumentStore;
582   this.index = {};
583   this.eventEmitter = new elasticlunr.EventEmitter;
584   this._idfCache = {};
585
586   this.on('add', 'remove', 'update', (function () {
587     this._idfCache = {};
588   }).bind(this));
589 };
590
591 /**
592  * Bind a handler to events being emitted by the index.
593  *
594  * The handler can be bound to many events at the same time.
595  *
596  * @param {String} [eventName] The name(s) of events to bind the function to.
597  * @param {Function} fn The serialised set to load.
598  * @memberOf Index
599  */
600 elasticlunr.Index.prototype.on = function () {
601   var args = Array.prototype.slice.call(arguments);
602   return this.eventEmitter.addListener.apply(this.eventEmitter, args);
603 };
604
605 /**
606  * Removes a handler from an event being emitted by the index.
607  *
608  * @param {String} eventName The name of events to remove the function from.
609  * @param {Function} fn The serialised set to load.
610  * @memberOf Index
611  */
612 elasticlunr.Index.prototype.off = function (name, fn) {
613   return this.eventEmitter.removeListener(name, fn);
614 };
615
616 /**
617  * Loads a previously serialised index.
618  *
619  * Issues a warning if the index being imported was serialised
620  * by a different version of elasticlunr.
621  *
622  * @param {Object} serialisedData The serialised set to load.
623  * @return {elasticlunr.Index}
624  * @memberOf Index
625  */
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);
630   }
631
632   var idx = new this;
633
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);
638   idx.index = {};
639   for (var field in serialisedData.index) {
640     idx.index[field] = elasticlunr.InvertedIndex.load(serialisedData.index[field]);
641   }
642
643   return idx;
644 };
645
646 /**
647  * Adds a field to the list of fields that will be searchable within documents in the index.
648  *
649  * Remember that inner index is build based on field, which means each field has one inverted index.
650  *
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.
654  *
655  * @param {String} fieldName The name of the field within the document that should be indexed
656  * @return {elasticlunr.Index}
657  * @memberOf Index
658  */
659 elasticlunr.Index.prototype.addField = function (fieldName) {
660   this._fields.push(fieldName);
661   this.index[fieldName] = new elasticlunr.InvertedIndex;
662   return this;
663 };
664
665 /**
666  * Sets the property used to uniquely identify documents added to the index,
667  * by default this property is 'id'.
668  *
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.
671  *
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}
676  * @memberOf Index
677  */
678 elasticlunr.Index.prototype.setRef = function (refName) {
679   this._ref = refName;
680   return this;
681 };
682
683 /**
684  *
685  * Set if the JSON format original documents are save into elasticlunr.DocumentStore
686  *
687  * Defaultly save all the original JSON documents.
688  *
689  * @param {Boolean} save Whether to save the original JSON documents.
690  * @return {elasticlunr.Index}
691  * @memberOf Index
692  */
693 elasticlunr.Index.prototype.saveDocument = function (save) {
694   this.documentStore = new elasticlunr.DocumentStore(save);
695   return this;
696 };
697
698 /**
699  * Add a JSON format document to the index.
700  *
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.
704  *
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.
708  *
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.
711  * @memberOf Index
712  */
713 elasticlunr.Index.prototype.addDoc = function (doc, emitEvent) {
714   if (!doc) return;
715   var emitEvent = emitEvent === undefined ? true : emitEvent;
716
717   var docRef = doc[this._ref];
718
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);
723
724     var tokenCount = {};
725     fieldTokens.forEach(function (token) {
726       if (token in tokenCount) tokenCount[token] += 1;
727       else tokenCount[token] = 1;
728     }, this);
729
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 });
734     }
735   }, this);
736
737   if (emitEvent) this.eventEmitter.emit('add', doc, this);
738 };
739
740 /**
741  * Removes a document from the index by doc ref.
742  *
743  * To make sure documents no longer show up in search results they can be
744  * removed from the index using this method.
745  *
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.
749  *
750  * If user setting DocumentStore not storing the documents, then remove doc by docRef is not allowed.
751  *
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
754  * @memberOf Index
755  */
756 elasticlunr.Index.prototype.removeDocByRef = function (docRef, emitEvent) {
757   if (!docRef) return;
758   if (this.documentStore.isDocStored() === false) {
759     return;
760   }
761
762   if (!this.documentStore.hasDoc(docRef)) return;
763   var doc = this.documentStore.getDoc(docRef);
764   this.removeDoc(doc, false);
765 };
766
767 /**
768  * Removes a document from the index.
769  * This remove operation could work even the original doc is not store in the DocumentStore.
770  *
771  * To make sure documents no longer show up in search results they can be
772  * removed from the index using this method.
773  *
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.
777  *
778  *
779  * @param {Object} doc The document ref to remove from the index.
780  * @param {Boolean} emitEvent Whether to emit remove events, defaults to true
781  * @memberOf Index
782  */
783 elasticlunr.Index.prototype.removeDoc = function (doc, emitEvent) {
784   if (!doc) return;
785
786   var emitEvent = emitEvent === undefined ? true : emitEvent;
787
788   var docRef = doc[this._ref];
789   if (!this.documentStore.hasDoc(docRef)) return;
790
791   this.documentStore.removeDoc(docRef);
792
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);
797     }, this);
798   }, this);
799
800   if (emitEvent) this.eventEmitter.emit('remove', doc, this);
801 };
802
803 /**
804  * Updates a document in the index.
805  *
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.
809  *
810  * This method is just a wrapper around `remove` and `add`
811  *
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
815  * are silenced.
816  *
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
821  * @memberOf Index
822  */
823 elasticlunr.Index.prototype.updateDoc = function (doc, emitEvent) {
824   var emitEvent = emitEvent === undefined ? true : emitEvent;
825
826   this.removeDocByRef(doc[this._ref], false);
827   this.addDoc(doc, false);
828
829   if (emitEvent) this.eventEmitter.emit('update', doc, this);
830 };
831
832 /**
833  * Calculates the inverse document frequency for a token within the index of a field.
834  *
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
838  * @private
839  * @memberOf Index
840  */
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];
844
845   var df = this.index[field].getDocFreq(term);
846   var idf = 1 + Math.log(this.documentStore.length / (df + 1));
847   this._idfCache[cacheKey] = idf;
848
849   return idf;
850 };
851
852 /**
853  * get fields of current index instance
854  *
855  * @return {Array}
856  */
857 elasticlunr.Index.prototype.getFields = function () {
858   return this._fields.slice();
859 };
860
861 /**
862  * Searches the index using the passed query.
863  * Queries should be a string, multiple words are allowed.
864  *
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.
867  *
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
870  * query term.
871  *
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.
874  *
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.
878  *
879  * @param {String} query The query to search the index with.
880  * @param {JSON} userConfig The user query config, JSON format.
881  * @return {Object}
882  * @see Index.prototype.idf
883  * @see Index.prototype.documentVector
884  * @memberOf Index
885  */
886 elasticlunr.Index.prototype.search = function (query, userConfig) {
887   if (!query) return [];
888
889   var configStr = null;
890   if (userConfig != null) {
891     configStr = JSON.stringify(userConfig);
892   }
893
894   var config = new elasticlunr.Configuration(configStr, this.getFields()).get();
895
896   var queryTokens = this.pipeline.run(elasticlunr.tokenizer(query));
897
898   var queryResults = {};
899
900   for (var field in config) {
901     var fieldSearchResults = this.fieldSearch(queryTokens, field, config);
902     var fieldBoost = config[field].boost;
903
904     for (var docRef in fieldSearchResults) {
905       fieldSearchResults[docRef] = fieldSearchResults[docRef] * fieldBoost;
906     }
907
908     for (var docRef in fieldSearchResults) {
909       if (docRef in queryResults) {
910         queryResults[docRef] += fieldSearchResults[docRef];
911       } else {
912         queryResults[docRef] = fieldSearchResults[docRef];
913       }
914     }
915   }
916
917   var results = [];
918   for (var docRef in queryResults) {
919     results.push({ref: docRef, score: queryResults[docRef]});
920   }
921
922   results.sort(function (a, b) { return b.score - a.score; });
923   return results;
924 };
925
926 /**
927  * search queryTokens in specified field.
928  *
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.
932  * @return {Object}
933  */
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;
938   var scores = null;
939   var docTokens = {};
940
941   // Do nothing if the boost is 0
942   if (boost === 0) {
943     return;
944   }
945
946   queryTokens.forEach(function (token) {
947     var tokens = [token];
948     if (expand == true) {
949       tokens = this.index[fieldName].expandToken(token);
950     }
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,
956     // keyed by docRef.
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
963     // operation0.
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).
968
969     var queryTokenScores = {};
970     tokens.forEach(function (key) {
971       var docs = this.index[fieldName].getDocs(key);
972       var idf = this.idf(key, fieldName);
973
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];
982               }
983           }
984           docs = filteredDocs;
985       }
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
991       // coordNorm
992       if (key == token) {
993         this.fieldSearchStats(docTokens, key, docs);
994       }
995
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);
1002         }
1003
1004         var penality = 1;
1005         if (key != token) {
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;
1009         }
1010
1011         var score = tf * idf * fieldLengthNorm * penality;
1012
1013         if (docRef in queryTokenScores) {
1014           queryTokenScores[docRef] += score;
1015         } else {
1016           queryTokenScores[docRef] = score;
1017         }
1018       }
1019     }, this);
1020
1021     scores = this.mergeScores(scores, queryTokenScores, booleanType);
1022   }, this);
1023
1024   scores = this.coordNorm(scores, docTokens, queryTokens.length);
1025   return scores;
1026 };
1027
1028 /**
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.
1033  *
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').
1037  *
1038  */
1039
1040 elasticlunr.Index.prototype.mergeScores = function (accumScores, scores, op) {
1041     if (!accumScores) {
1042         return scores;
1043     }
1044     if (op == 'AND') {
1045         var intersection = {};
1046         for (var docRef in scores) {
1047             if (docRef in accumScores) {
1048                 intersection[docRef] = accumScores[docRef] + scores[docRef];
1049             }
1050         }
1051         return intersection;
1052     } else {
1053         for (var docRef in scores) {
1054             if (docRef in accumScores) {
1055                 accumScores[docRef] += scores[docRef];
1056             } else {
1057                 accumScores[docRef] = scores[docRef];
1058             }
1059         }
1060         return accumScores;
1061     }
1062 };
1063
1064
1065 /**
1066  * Record the occuring query token of retrieved doc specified by doc field.
1067  * Only for inner user.
1068  *
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
1072  *
1073  */
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);
1078     } else {
1079       docTokens[doc] = [token];
1080     }
1081   }
1082 };
1083
1084 /**
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.
1088  *
1089  * only for inner use.
1090  *
1091  * @param {Object} results first results
1092  * @param {Object} docs field search results of a token
1093  * @param {Integer} n query token number
1094  * @return {Object}
1095  */
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;
1101   }
1102
1103   return scores;
1104 };
1105
1106 /**
1107  * Returns a representation of the index ready for serialisation.
1108  *
1109  * @return {Object}
1110  * @memberOf Index
1111  */
1112 elasticlunr.Index.prototype.toJSON = function () {
1113   var indexJson = {};
1114   this._fields.forEach(function (field) {
1115     indexJson[field] = this.index[field].toJSON();
1116   }, this);
1117
1118   return {
1119     version: elasticlunr.version,
1120     fields: this._fields,
1121     ref: this._ref,
1122     documentStore: this.documentStore.toJSON(),
1123     index: indexJson,
1124     pipeline: this.pipeline.toJSON()
1125   };
1126 };
1127
1128 /**
1129  * Applies a plugin to the current index.
1130  *
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.
1135  *
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.
1139  *
1140  * Example:
1141  *
1142  *     var myPlugin = function (idx, arg1, arg2) {
1143  *       // `this` is the index to be extended
1144  *       // apply any extensions etc here.
1145  *     }
1146  *
1147  *     var idx = elasticlunr(function () {
1148  *       this.use(myPlugin, 'arg1', 'arg2')
1149  *     })
1150  *
1151  * @param {Function} plugin The plugin to apply.
1152  * @memberOf Index
1153  */
1154 elasticlunr.Index.prototype.use = function (plugin) {
1155   var args = Array.prototype.slice.call(arguments, 1);
1156   args.unshift(this);
1157   plugin.apply(this, args);
1158 };
1159 /*!
1160  * elasticlunr.DocumentStore
1161  * Copyright (C) 2016 Wei Song
1162  */
1163
1164 /**
1165  * elasticlunr.DocumentStore is a simple key-value document store used for storing sets of tokens for
1166  * documents stored in index.
1167  *
1168  * elasticlunr.DocumentStore store original JSON format documents that you could build search snippet by this original JSON document.
1169  *
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.
1173  *
1174  * @param {Boolean} save If the original JSON document should be stored.
1175  * @constructor
1176  * @module
1177  */
1178 elasticlunr.DocumentStore = function (save) {
1179   if (save === null || save === undefined) {
1180     this._save = true;
1181   } else {
1182     this._save = save;
1183   }
1184
1185   this.docs = {};
1186   this.docInfo = {};
1187   this.length = 0;
1188 };
1189
1190 /**
1191  * Loads a previously serialised document store
1192  *
1193  * @param {Object} serialisedData The serialised document store to load.
1194  * @return {elasticlunr.DocumentStore}
1195  */
1196 elasticlunr.DocumentStore.load = function (serialisedData) {
1197   var store = new this;
1198
1199   store.length = serialisedData.length;
1200   store.docs = serialisedData.docs;
1201   store.docInfo = serialisedData.docInfo;
1202   store._save = serialisedData.save;
1203
1204   return store;
1205 };
1206
1207 /**
1208  * check if current instance store the original doc
1209  *
1210  * @return {Boolean}
1211  */
1212 elasticlunr.DocumentStore.prototype.isDocStored = function () {
1213   return this._save;
1214 };
1215
1216 /**
1217  * Stores the given doc in the document store against the given id.
1218  * If docRef already exist, then update doc.
1219  *
1220  * Document is store by original JSON format, then you could use original document to generate search snippets.
1221  *
1222  * @param {Integer|String} docRef The key used to store the JSON format doc.
1223  * @param {Object} doc The JSON format doc.
1224  */
1225 elasticlunr.DocumentStore.prototype.addDoc = function (docRef, doc) {
1226   if (!this.hasDoc(docRef)) this.length++;
1227
1228   if (this._save === true) {
1229     this.docs[docRef] = clone(doc);
1230   } else {
1231     this.docs[docRef] = null;
1232   }
1233 };
1234
1235 /**
1236  * Retrieves the JSON doc from the document store for a given key.
1237  *
1238  * If docRef not found, return null.
1239  * If user set not storing the documents, return null.
1240  *
1241  * @param {Integer|String} docRef The key to lookup and retrieve from the document store.
1242  * @return {Object}
1243  * @memberOf DocumentStore
1244  */
1245 elasticlunr.DocumentStore.prototype.getDoc = function (docRef) {
1246   if (this.hasDoc(docRef) === false) return null;
1247   return this.docs[docRef];
1248 };
1249
1250 /**
1251  * Checks whether the document store contains a key (docRef).
1252  *
1253  * @param {Integer|String} docRef The id to look up in the document store.
1254  * @return {Boolean}
1255  * @memberOf DocumentStore
1256  */
1257 elasticlunr.DocumentStore.prototype.hasDoc = function (docRef) {
1258   return docRef in this.docs;
1259 };
1260
1261 /**
1262  * Removes the value for a key in the document store.
1263  *
1264  * @param {Integer|String} docRef The id to remove from the document store.
1265  * @memberOf DocumentStore
1266  */
1267 elasticlunr.DocumentStore.prototype.removeDoc = function (docRef) {
1268   if (!this.hasDoc(docRef)) return;
1269
1270   delete this.docs[docRef];
1271   delete this.docInfo[docRef];
1272   this.length--;
1273 };
1274
1275 /**
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.
1278  *
1279  * @param {Integer|String} docRef document's id or reference
1280  * @param {String} fieldName field name
1281  * @param {Integer} length field length
1282  */
1283 elasticlunr.DocumentStore.prototype.addFieldLength = function (docRef, fieldName, length) {
1284   if (docRef === null || docRef === undefined) return;
1285   if (this.hasDoc(docRef) == false) return;
1286
1287   if (!this.docInfo[docRef]) this.docInfo[docRef] = {};
1288   this.docInfo[docRef][fieldName] = length;
1289 };
1290
1291 /**
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.
1294  *
1295  * @param {Integer|String} docRef document's id or reference
1296  * @param {String} fieldName field name
1297  * @param {Integer} length field length
1298  */
1299 elasticlunr.DocumentStore.prototype.updateFieldLength = function (docRef, fieldName, length) {
1300   if (docRef === null || docRef === undefined) return;
1301   if (this.hasDoc(docRef) == false) return;
1302
1303   this.addFieldLength(docRef, fieldName, length);
1304 };
1305
1306 /**
1307  * get field length of a document by docRef
1308  *
1309  * @param {Integer|String} docRef document id or reference
1310  * @param {String} fieldName field name
1311  * @return {Integer} field length
1312  */
1313 elasticlunr.DocumentStore.prototype.getFieldLength = function (docRef, fieldName) {
1314   if (docRef === null || docRef === undefined) return 0;
1315
1316   if (!(docRef in this.docs)) return 0;
1317   if (!(fieldName in this.docInfo[docRef])) return 0;
1318   return this.docInfo[docRef][fieldName];
1319 };
1320
1321 /**
1322  * Returns a JSON representation of the document store used for serialisation.
1323  *
1324  * @return {Object} JSON format
1325  * @memberOf DocumentStore
1326  */
1327 elasticlunr.DocumentStore.prototype.toJSON = function () {
1328   return {
1329     docs: this.docs,
1330     docInfo: this.docInfo,
1331     length: this.length,
1332     save: this._save
1333   };
1334 };
1335
1336 /**
1337  * Cloning object
1338  *
1339  * @param {Object} object in JSON format
1340  * @return {Object} copied object
1341  */
1342 function clone(obj) {
1343   if (null === obj || "object" !== typeof obj) return obj;
1344
1345   var copy = obj.constructor();
1346
1347   for (var attr in obj) {
1348     if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
1349   }
1350
1351   return copy;
1352 }
1353 /*!
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
1358  */
1359
1360 /**
1361  * elasticlunr.stemmer is an english language stemmer, this is a JavaScript
1362  * implementation of the PorterStemmer taken from http://tartarus.org/~martin
1363  *
1364  * @module
1365  * @param {String} str The string to stem
1366  * @return {String}
1367  * @see elasticlunr.Pipeline
1368  */
1369 elasticlunr.stemmer = (function(){
1370   var step2list = {
1371       "ational" : "ate",
1372       "tional" : "tion",
1373       "enci" : "ence",
1374       "anci" : "ance",
1375       "izer" : "ize",
1376       "bli" : "ble",
1377       "alli" : "al",
1378       "entli" : "ent",
1379       "eli" : "e",
1380       "ousli" : "ous",
1381       "ization" : "ize",
1382       "ation" : "ate",
1383       "ator" : "ate",
1384       "alism" : "al",
1385       "iveness" : "ive",
1386       "fulness" : "ful",
1387       "ousness" : "ous",
1388       "aliti" : "al",
1389       "iviti" : "ive",
1390       "biliti" : "ble",
1391       "logi" : "log"
1392     },
1393
1394     step3list = {
1395       "icate" : "ic",
1396       "ative" : "",
1397       "alize" : "al",
1398       "iciti" : "ic",
1399       "ical" : "ic",
1400       "ful" : "",
1401       "ness" : ""
1402     },
1403
1404     c = "[^aeiou]",          // consonant
1405     v = "[aeiouy]",          // vowel
1406     C = c + "[^aeiouy]*",    // consonant sequence
1407     V = v + "[aeiou]*",      // vowel sequence
1408
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
1413
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);
1418
1419   var re_1a = /^(.+?)(ss|i)es$/;
1420   var re2_1a = /^(.+?)([^s])s$/;
1421   var re_1b = /^(.+?)eed$/;
1422   var re2_1b = /^(.+?)(ed|ing)$/;
1423   var re_1b_2 = /.$/;
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]$");
1427
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)$/;
1430
1431   var re_3 = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;
1432
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)$/;
1435
1436   var re_5 = /^(.+?)e$/;
1437   var re_5_1 = /ll$/;
1438   var re3_5 = new RegExp("^" + C + v + "[^aeiouwxy]$");
1439
1440   var porterStemmer = function porterStemmer(w) {
1441     var   stem,
1442       suffix,
1443       firstch,
1444       re,
1445       re2,
1446       re3,
1447       re4;
1448
1449     if (w.length < 3) { return w; }
1450
1451     firstch = w.substr(0,1);
1452     if (firstch == "y") {
1453       w = firstch.toUpperCase() + w.substr(1);
1454     }
1455
1456     // Step 1a
1457     re = re_1a
1458     re2 = re2_1a;
1459
1460     if (re.test(w)) { w = w.replace(re,"$1$2"); }
1461     else if (re2.test(w)) { w = w.replace(re2,"$1$2"); }
1462
1463     // Step 1b
1464     re = re_1b;
1465     re2 = re2_1b;
1466     if (re.test(w)) {
1467       var fp = re.exec(w);
1468       re = re_mgr0;
1469       if (re.test(fp[1])) {
1470         re = re_1b_2;
1471         w = w.replace(re,"");
1472       }
1473     } else if (re2.test(w)) {
1474       var fp = re2.exec(w);
1475       stem = fp[1];
1476       re2 = re_s_v;
1477       if (re2.test(stem)) {
1478         w = stem;
1479         re2 = re2_1b_2;
1480         re3 = re3_1b_2;
1481         re4 = re4_1b_2;
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"; }
1485       }
1486     }
1487
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)
1489     re = re_1c;
1490     if (re.test(w)) {
1491       var fp = re.exec(w);
1492       stem = fp[1];
1493       w = stem + "i";
1494     }
1495
1496     // Step 2
1497     re = re_2;
1498     if (re.test(w)) {
1499       var fp = re.exec(w);
1500       stem = fp[1];
1501       suffix = fp[2];
1502       re = re_mgr0;
1503       if (re.test(stem)) {
1504         w = stem + step2list[suffix];
1505       }
1506     }
1507
1508     // Step 3
1509     re = re_3;
1510     if (re.test(w)) {
1511       var fp = re.exec(w);
1512       stem = fp[1];
1513       suffix = fp[2];
1514       re = re_mgr0;
1515       if (re.test(stem)) {
1516         w = stem + step3list[suffix];
1517       }
1518     }
1519
1520     // Step 4
1521     re = re_4;
1522     re2 = re2_4;
1523     if (re.test(w)) {
1524       var fp = re.exec(w);
1525       stem = fp[1];
1526       re = re_mgr1;
1527       if (re.test(stem)) {
1528         w = stem;
1529       }
1530     } else if (re2.test(w)) {
1531       var fp = re2.exec(w);
1532       stem = fp[1] + fp[2];
1533       re2 = re_mgr1;
1534       if (re2.test(stem)) {
1535         w = stem;
1536       }
1537     }
1538
1539     // Step 5
1540     re = re_5;
1541     if (re.test(w)) {
1542       var fp = re.exec(w);
1543       stem = fp[1];
1544       re = re_mgr1;
1545       re2 = re_meq1;
1546       re3 = re3_5;
1547       if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) {
1548         w = stem;
1549       }
1550     }
1551
1552     re = re_5_1;
1553     re2 = re_mgr1;
1554     if (re.test(w) && re2.test(w)) {
1555       re = re_1b_2;
1556       w = w.replace(re,"");
1557     }
1558
1559     // and turn initial Y back to y
1560
1561     if (firstch == "y") {
1562       w = firstch.toLowerCase() + w.substr(1);
1563     }
1564
1565     return w;
1566   };
1567
1568   return porterStemmer;
1569 })();
1570
1571 elasticlunr.Pipeline.registerFunction(elasticlunr.stemmer, 'stemmer');
1572 /*!
1573  * elasticlunr.stopWordFilter
1574  * Copyright (C) 2016 Oliver Nightingale
1575  * Copyright (C) 2016 Wei Song
1576  */
1577
1578 /**
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.
1581  *
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.
1585  *
1586  * @module
1587  * @param {String} token The token to pass through the filter
1588  * @return {String}
1589  * @see elasticlunr.Pipeline
1590  */
1591 elasticlunr.stopWordFilter = function (token) {
1592   if (token && elasticlunr.stopWordFilter.stopWords[token] !== true) {
1593     return token;
1594   }
1595 };
1596
1597 /**
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.
1601  *
1602  * @return {null}
1603  */
1604 elasticlunr.clearStopWords = function () {
1605   elasticlunr.stopWordFilter.stopWords = {};
1606 };
1607
1608 /**
1609  * Add customized stop words
1610  * user could use this function to add customized stop words
1611  *
1612  * @params {Array} words customized stop words
1613  * @return {null}
1614  */
1615 elasticlunr.addStopWords = function (words) {
1616   if (words == null || Array.isArray(words) === false) return;
1617
1618   words.forEach(function (word) {
1619     elasticlunr.stopWordFilter.stopWords[word] = true;
1620   }, this);
1621 };
1622
1623 /**
1624  * Reset to default stop words
1625  * user could use this function to restore default stop words
1626  *
1627  * @return {null}
1628  */
1629 elasticlunr.resetStopWords = function () {
1630   elasticlunr.stopWordFilter.stopWords = elasticlunr.defaultStopWords;
1631 };
1632
1633 elasticlunr.defaultStopWords = {
1634   "": true,
1635   "a": true,
1636   "able": true,
1637   "about": true,
1638   "across": true,
1639   "after": true,
1640   "all": true,
1641   "almost": true,
1642   "also": true,
1643   "am": true,
1644   "among": true,
1645   "an": true,
1646   "and": true,
1647   "any": true,
1648   "are": true,
1649   "as": true,
1650   "at": true,
1651   "be": true,
1652   "because": true,
1653   "been": true,
1654   "but": true,
1655   "by": true,
1656   "can": true,
1657   "cannot": true,
1658   "could": true,
1659   "dear": true,
1660   "did": true,
1661   "do": true,
1662   "does": true,
1663   "either": true,
1664   "else": true,
1665   "ever": true,
1666   "every": true,
1667   "for": true,
1668   "from": true,
1669   "get": true,
1670   "got": true,
1671   "had": true,
1672   "has": true,
1673   "have": true,
1674   "he": true,
1675   "her": true,
1676   "hers": true,
1677   "him": true,
1678   "his": true,
1679   "how": true,
1680   "however": true,
1681   "i": true,
1682   "if": true,
1683   "in": true,
1684   "into": true,
1685   "is": true,
1686   "it": true,
1687   "its": true,
1688   "just": true,
1689   "least": true,
1690   "let": true,
1691   "like": true,
1692   "likely": true,
1693   "may": true,
1694   "me": true,
1695   "might": true,
1696   "most": true,
1697   "must": true,
1698   "my": true,
1699   "neither": true,
1700   "no": true,
1701   "nor": true,
1702   "not": true,
1703   "of": true,
1704   "off": true,
1705   "often": true,
1706   "on": true,
1707   "only": true,
1708   "or": true,
1709   "other": true,
1710   "our": true,
1711   "own": true,
1712   "rather": true,
1713   "said": true,
1714   "say": true,
1715   "says": true,
1716   "she": true,
1717   "should": true,
1718   "since": true,
1719   "so": true,
1720   "some": true,
1721   "than": true,
1722   "that": true,
1723   "the": true,
1724   "their": true,
1725   "them": true,
1726   "then": true,
1727   "there": true,
1728   "these": true,
1729   "they": true,
1730   "this": true,
1731   "tis": true,
1732   "to": true,
1733   "too": true,
1734   "twas": true,
1735   "us": true,
1736   "wants": true,
1737   "was": true,
1738   "we": true,
1739   "were": true,
1740   "what": true,
1741   "when": true,
1742   "where": true,
1743   "which": true,
1744   "while": true,
1745   "who": true,
1746   "whom": true,
1747   "why": true,
1748   "will": true,
1749   "with": true,
1750   "would": true,
1751   "yet": true,
1752   "you": true,
1753   "your": true
1754 };
1755
1756 elasticlunr.stopWordFilter.stopWords = elasticlunr.defaultStopWords;
1757
1758 elasticlunr.Pipeline.registerFunction(elasticlunr.stopWordFilter, 'stopWordFilter');
1759 /*!
1760  * elasticlunr.trimmer
1761  * Copyright (C) 2016 Oliver Nightingale
1762  * Copyright (C) 2016 Wei Song
1763  */
1764
1765 /**
1766  * elasticlunr.trimmer is a pipeline function for trimming non word
1767  * characters from the begining and end of tokens before they
1768  * enter the index.
1769  *
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.
1773  *
1774  * @module
1775  * @param {String} token The token to pass through the filter
1776  * @return {String}
1777  * @see elasticlunr.Pipeline
1778  */
1779 elasticlunr.trimmer = function (token) {
1780   if (token === null || token === undefined) {
1781     throw new Error('token should not be undefined');
1782   }
1783
1784   return token
1785     .replace(/^\W+/, '')
1786     .replace(/\W+$/, '');
1787 };
1788
1789 elasticlunr.Pipeline.registerFunction(elasticlunr.trimmer, 'trimmer');
1790 /*!
1791  * elasticlunr.InvertedIndex
1792  * Copyright (C) 2016 Wei Song
1793  * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt
1794  */
1795
1796 /**
1797  * elasticlunr.InvertedIndex is used for efficiently storing and
1798  * lookup of documents that contain a given token.
1799  *
1800  * @constructor
1801  */
1802 elasticlunr.InvertedIndex = function () {
1803   this.root = { docs: {}, df: 0 };
1804 };
1805
1806 /**
1807  * Loads a previously serialised inverted index.
1808  *
1809  * @param {Object} serialisedData The serialised inverted index to load.
1810  * @return {elasticlunr.InvertedIndex}
1811  */
1812 elasticlunr.InvertedIndex.load = function (serialisedData) {
1813   var idx = new this;
1814   idx.root = serialisedData.root;
1815
1816   return idx;
1817 };
1818
1819 /**
1820  * Adds a {token: tokenInfo} pair to the inverted index.
1821  * If the token already exist, then update the tokenInfo.
1822  *
1823  * tokenInfo format: { ref: 1, tf: 2}
1824  * tokenInfor should contains the document's ref and the tf(token frequency) of that token in
1825  * the document.
1826  *
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.
1829  *
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
1834  * is used.
1835  * @memberOf InvertedIndex
1836  */
1837 elasticlunr.InvertedIndex.prototype.addToken = function (token, tokenInfo, root) {
1838   var root = root || this.root,
1839       idx = 0;
1840
1841   while (idx <= token.length - 1) {
1842     var key = token[idx];
1843
1844     if (!(key in root)) root[key] = {docs: {}, df: 0};
1845     idx += 1;
1846     root = root[key];
1847   }
1848
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};
1853     root.df += 1;
1854   } else {
1855     // if this doc already exist, then update tokenInfo
1856     root.docs[docRef] = {tf: tokenInfo.tf};
1857   }
1858 };
1859
1860 /**
1861  * Checks whether a token is in this elasticlunr.InvertedIndex.
1862  *
1863  *
1864  * @param {String} token The token to be checked
1865  * @return {Boolean}
1866  * @memberOf InvertedIndex
1867  */
1868 elasticlunr.InvertedIndex.prototype.hasToken = function (token) {
1869   if (!token) return false;
1870
1871   var node = this.root;
1872
1873   for (var i = 0; i < token.length; i++) {
1874     if (!node[token[i]]) return false;
1875     node = node[token[i]];
1876   }
1877
1878   return true;
1879 };
1880
1881 /**
1882  * Retrieve a node from the inverted index for a given token.
1883  * If token not found in this InvertedIndex, return null.
1884  *
1885  *
1886  * @param {String} token The token to get the node for.
1887  * @return {Object}
1888  * @see InvertedIndex.prototype.get
1889  * @memberOf InvertedIndex
1890  */
1891 elasticlunr.InvertedIndex.prototype.getNode = function (token) {
1892   if (!token) return null;
1893
1894   var node = this.root;
1895
1896   for (var i = 0; i < token.length; i++) {
1897     if (!node[token[i]]) return null;
1898     node = node[token[i]];
1899   }
1900
1901   return node;
1902 };
1903
1904 /**
1905  * Retrieve the documents of a given token.
1906  * If token not found, return {}.
1907  *
1908  *
1909  * @param {String} token The token to get the documents for.
1910  * @return {Object}
1911  * @memberOf InvertedIndex
1912  */
1913 elasticlunr.InvertedIndex.prototype.getDocs = function (token) {
1914   var node = this.getNode(token);
1915   if (node == null) {
1916     return {};
1917   }
1918
1919   return node.docs;
1920 };
1921
1922 /**
1923  * Retrieve term frequency of given token in given docRef.
1924  * If token or docRef not found, return 0.
1925  *
1926  *
1927  * @param {String} token The token to get the documents for.
1928  * @param {String|Integer} docRef
1929  * @return {Integer}
1930  * @memberOf InvertedIndex
1931  */
1932 elasticlunr.InvertedIndex.prototype.getTermFrequency = function (token, docRef) {
1933   var node = this.getNode(token);
1934
1935   if (node == null) {
1936     return 0;
1937   }
1938
1939   if (!(docRef in node.docs)) {
1940     return 0;
1941   }
1942
1943   return node.docs[docRef].tf;
1944 };
1945
1946 /**
1947  * Retrieve the document frequency of given token.
1948  * If token not found, return 0.
1949  *
1950  *
1951  * @param {String} token The token to get the documents for.
1952  * @return {Object}
1953  * @memberOf InvertedIndex
1954  */
1955 elasticlunr.InvertedIndex.prototype.getDocFreq = function (token) {
1956   var node = this.getNode(token);
1957
1958   if (node == null) {
1959     return 0;
1960   }
1961
1962   return node.df;
1963 };
1964
1965 /**
1966  * Remove the document identified by document's ref from the token in the inverted index.
1967  *
1968  *
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
1972  */
1973 elasticlunr.InvertedIndex.prototype.removeToken = function (token, ref) {
1974   if (!token) return;
1975   var node = this.getNode(token);
1976
1977   if (node == null) return;
1978
1979   if (ref in node.docs) {
1980     delete node.docs[ref];
1981     node.df -= 1;
1982   }
1983 };
1984
1985 /**
1986  * Find all the possible suffixes of given token using tokens currently in the inverted index.
1987  * If token not found, return empty Array.
1988  *
1989  * @param {String} token The token to expand.
1990  * @return {Array}
1991  * @memberOf InvertedIndex
1992  */
1993 elasticlunr.InvertedIndex.prototype.expandToken = function (token, memo, root) {
1994   if (token == null || token == '') return [];
1995   var memo = memo || [];
1996
1997   if (root == void 0) {
1998     root = this.getNode(token);
1999     if (root == null) return memo;
2000   }
2001
2002   if (root.df > 0) memo.push(token);
2003
2004   for (var key in root) {
2005     if (key === 'docs') continue;
2006     if (key === 'df') continue;
2007     this.expandToken(token + key, memo, root[key]);
2008   }
2009
2010   return memo;
2011 };
2012
2013 /**
2014  * Returns a representation of the inverted index ready for serialisation.
2015  *
2016  * @return {Object}
2017  * @memberOf InvertedIndex
2018  */
2019 elasticlunr.InvertedIndex.prototype.toJSON = function () {
2020   return {
2021     root: this.root
2022   };
2023 };
2024
2025 /*!
2026  * elasticlunr.Configuration
2027  * Copyright (C) 2016 Wei Song
2028  */
2029
2030  /**
2031   * elasticlunr.Configuration is used to analyze the user search configuration.
2032   *
2033   * By elasticlunr.Configuration user could set query-time boosting, boolean model in each field.
2034   *
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.
2039   *
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.
2045   *
2046   * configuration example:
2047   * {
2048   *   fields:{
2049   *     title: {boost: 2},
2050   *     body: {boost: 1}
2051   *   },
2052   *   bool: "OR"
2053   * }
2054   *
2055   * "bool" field configuation overwrite global configuation example:
2056   * {
2057   *   fields:{
2058   *     title: {boost: 2, bool: "AND"},
2059   *     body: {boost: 1}
2060   *   },
2061   *   bool: "OR"
2062   * }
2063   *
2064   * "expand" example:
2065   * {
2066   *   fields:{
2067   *     title: {boost: 2, bool: "AND"},
2068   *     body: {boost: 1}
2069   *   },
2070   *   bool: "OR",
2071   *   expand: true
2072   * }
2073   *
2074   * "expand" example for field category:
2075   * {
2076   *   fields:{
2077   *     title: {boost: 2, bool: "AND", expand: true},
2078   *     body: {boost: 1}
2079   *   },
2080   *   bool: "OR"
2081   * }
2082   *
2083   * setting the boost to 0 ignores the field (this will only search the title):
2084   * {
2085   *   fields:{
2086   *     title: {boost: 1},
2087   *     body: {boost: 0}
2088   *   }
2089   * }
2090   *
2091   * then, user could search with configuration to do query-time boosting.
2092   * idx.search('oracle database', {fields: {title: {boost: 2}, body: {boost: 1}}});
2093   *
2094   *
2095   * @constructor
2096   *
2097   * @param {String} config user configuration
2098   * @param {Array} fields fields of index instance
2099   * @module
2100   */
2101 elasticlunr.Configuration = function (config, fields) {
2102   var config = config || '';
2103
2104   if (fields == undefined || fields == null) {
2105     throw new Error('fields should not be null');
2106   }
2107
2108   this.config = {};
2109
2110   var userConfig;
2111   try {
2112     userConfig = JSON.parse(config);
2113     this.buildUserConfig(userConfig, fields);
2114   } catch (error) {
2115     elasticlunr.utils.warn('user configuration parse failed, will use default configuration');
2116     this.buildDefaultConfig(fields);
2117   }
2118 };
2119
2120 /**
2121  * Build default search configuration.
2122  *
2123  * @param {Array} fields fields of index instance
2124  */
2125 elasticlunr.Configuration.prototype.buildDefaultConfig = function (fields) {
2126   this.reset();
2127   fields.forEach(function (field) {
2128     this.config[field] = {
2129       boost: 1,
2130       bool: "OR",
2131       expand: false
2132     };
2133   }, this);
2134 };
2135
2136 /**
2137  * Build user configuration.
2138  *
2139  * @param {JSON} config User JSON configuratoin
2140  * @param {Array} fields fields of index instance
2141  */
2142 elasticlunr.Configuration.prototype.buildUserConfig = function (config, fields) {
2143   var global_bool = "OR";
2144   var global_expand = false;
2145
2146   this.reset();
2147   if ('bool' in config) {
2148     global_bool = config['bool'] || global_bool;
2149   }
2150
2151   if ('expand' in config) {
2152     global_expand = config['expand'] || global_expand;
2153   }
2154
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;
2162         }
2163
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
2168         };
2169       } else {
2170         elasticlunr.utils.warn('field name in user configuration not found in index instance fields');
2171       }
2172     }
2173   } else {
2174     this.addAllFields2UserConfig(global_bool, global_expand, fields);
2175   }
2176 };
2177
2178 /**
2179  * Add all fields to user search configuration.
2180  *
2181  * @param {String} bool Boolean model
2182  * @param {String} expand Expand model
2183  * @param {Array} fields fields of index instance
2184  */
2185 elasticlunr.Configuration.prototype.addAllFields2UserConfig = function (bool, expand, fields) {
2186   fields.forEach(function (field) {
2187     this.config[field] = {
2188       boost: 1,
2189       bool: bool,
2190       expand: expand
2191     };
2192   }, this);
2193 };
2194
2195 /**
2196  * get current user configuration
2197  */
2198 elasticlunr.Configuration.prototype.get = function () {
2199   return this.config;
2200 };
2201
2202 /**
2203  * reset user search configuration.
2204  */
2205 elasticlunr.Configuration.prototype.reset = function () {
2206   this.config = {};
2207 };
2208 /**
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.
2212  *
2213  */
2214
2215
2216 /*!
2217  * lunr.SortedSet
2218  * Copyright (C) 2016 Oliver Nightingale
2219  */
2220
2221 /**
2222  * lunr.SortedSets are used to maintain an array of uniq values in a sorted
2223  * order.
2224  *
2225  * @constructor
2226  */
2227 lunr.SortedSet = function () {
2228   this.length = 0
2229   this.elements = []
2230 }
2231
2232 /**
2233  * Loads a previously serialised sorted set.
2234  *
2235  * @param {Array} serialisedData The serialised set to load.
2236  * @returns {lunr.SortedSet}
2237  * @memberOf SortedSet
2238  */
2239 lunr.SortedSet.load = function (serialisedData) {
2240   var set = new this
2241
2242   set.elements = serialisedData
2243   set.length = serialisedData.length
2244
2245   return set
2246 }
2247
2248 /**
2249  * Inserts new items into the set in the correct position to maintain the
2250  * order.
2251  *
2252  * @param {Object} The objects to add to this set.
2253  * @memberOf SortedSet
2254  */
2255 lunr.SortedSet.prototype.add = function () {
2256   var i, element
2257
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)
2262   }
2263
2264   this.length = this.elements.length
2265 }
2266
2267 /**
2268  * Converts this sorted set into an array.
2269  *
2270  * @returns {Array}
2271  * @memberOf SortedSet
2272  */
2273 lunr.SortedSet.prototype.toArray = function () {
2274   return this.elements.slice()
2275 }
2276
2277 /**
2278  * Creates a new array with the results of calling a provided function on every
2279  * element in this sorted set.
2280  *
2281  * Delegates to Array.prototype.map and has the same signature.
2282  *
2283  * @param {Function} fn The function that is called on each element of the
2284  * set.
2285  * @param {Object} ctx An optional object that can be used as the context
2286  * for the function fn.
2287  * @returns {Array}
2288  * @memberOf SortedSet
2289  */
2290 lunr.SortedSet.prototype.map = function (fn, ctx) {
2291   return this.elements.map(fn, ctx)
2292 }
2293
2294 /**
2295  * Executes a provided function once per sorted set element.
2296  *
2297  * Delegates to Array.prototype.forEach and has the same signature.
2298  *
2299  * @param {Function} fn The function that is called on each element of the
2300  * set.
2301  * @param {Object} ctx An optional object that can be used as the context
2302  * @memberOf SortedSet
2303  * for the function fn.
2304  */
2305 lunr.SortedSet.prototype.forEach = function (fn, ctx) {
2306   return this.elements.forEach(fn, ctx)
2307 }
2308
2309 /**
2310  * Returns the index at which a given element can be found in the
2311  * sorted set, or -1 if it is not present.
2312  *
2313  * @param {Object} elem The object to locate in the sorted set.
2314  * @returns {Number}
2315  * @memberOf SortedSet
2316  */
2317 lunr.SortedSet.prototype.indexOf = function (elem) {
2318   var start = 0,
2319       end = this.elements.length,
2320       sectionLength = end - start,
2321       pivot = start + Math.floor(sectionLength / 2),
2322       pivotElem = this.elements[pivot]
2323
2324   while (sectionLength > 1) {
2325     if (pivotElem === elem) return pivot
2326
2327     if (pivotElem < elem) start = pivot
2328     if (pivotElem > elem) end = pivot
2329
2330     sectionLength = end - start
2331     pivot = start + Math.floor(sectionLength / 2)
2332     pivotElem = this.elements[pivot]
2333   }
2334
2335   if (pivotElem === elem) return pivot
2336
2337   return -1
2338 }
2339
2340 /**
2341  * Returns the position within the sorted set that an element should be
2342  * inserted at to maintain the current order of the set.
2343  *
2344  * This function assumes that the element to search for does not already exist
2345  * in the sorted set.
2346  *
2347  * @param {Object} elem The elem to find the position for in the set
2348  * @returns {Number}
2349  * @memberOf SortedSet
2350  */
2351 lunr.SortedSet.prototype.locationFor = function (elem) {
2352   var start = 0,
2353       end = this.elements.length,
2354       sectionLength = end - start,
2355       pivot = start + Math.floor(sectionLength / 2),
2356       pivotElem = this.elements[pivot]
2357
2358   while (sectionLength > 1) {
2359     if (pivotElem < elem) start = pivot
2360     if (pivotElem > elem) end = pivot
2361
2362     sectionLength = end - start
2363     pivot = start + Math.floor(sectionLength / 2)
2364     pivotElem = this.elements[pivot]
2365   }
2366
2367   if (pivotElem > elem) return pivot
2368   if (pivotElem < elem) return pivot + 1
2369 }
2370
2371 /**
2372  * Creates a new lunr.SortedSet that contains the elements in the intersection
2373  * of this set and the passed set.
2374  *
2375  * @param {lunr.SortedSet} otherSet The set to intersect with this set.
2376  * @returns {lunr.SortedSet}
2377  * @memberOf SortedSet
2378  */
2379 lunr.SortedSet.prototype.intersect = function (otherSet) {
2380   var intersectSet = new lunr.SortedSet,
2381       i = 0, j = 0,
2382       a_len = this.length, b_len = otherSet.length,
2383       a = this.elements, b = otherSet.elements
2384
2385   while (true) {
2386     if (i > a_len - 1 || j > b_len - 1) break
2387
2388     if (a[i] === b[j]) {
2389       intersectSet.add(a[i])
2390       i++, j++
2391       continue
2392     }
2393
2394     if (a[i] < b[j]) {
2395       i++
2396       continue
2397     }
2398
2399     if (a[i] > b[j]) {
2400       j++
2401       continue
2402     }
2403   };
2404
2405   return intersectSet
2406 }
2407
2408 /**
2409  * Makes a copy of this set
2410  *
2411  * @returns {lunr.SortedSet}
2412  * @memberOf SortedSet
2413  */
2414 lunr.SortedSet.prototype.clone = function () {
2415   var clone = new lunr.SortedSet
2416
2417   clone.elements = this.toArray()
2418   clone.length = clone.elements.length
2419
2420   return clone
2421 }
2422
2423 /**
2424  * Creates a new lunr.SortedSet that contains the elements in the union
2425  * of this set and the passed set.
2426  *
2427  * @param {lunr.SortedSet} otherSet The set to union with this set.
2428  * @returns {lunr.SortedSet}
2429  * @memberOf SortedSet
2430  */
2431 lunr.SortedSet.prototype.union = function (otherSet) {
2432   var longSet, shortSet, unionSet
2433
2434   if (this.length >= otherSet.length) {
2435     longSet = this, shortSet = otherSet
2436   } else {
2437     longSet = otherSet, shortSet = this
2438   }
2439
2440   unionSet = longSet.clone()
2441
2442   for(var i = 0, shortSetElements = shortSet.toArray(); i < shortSetElements.length; i++){
2443     unionSet.add(shortSetElements[i])
2444   }
2445
2446   return unionSet
2447 }
2448
2449 /**
2450  * Returns a representation of the sorted set ready for serialisation.
2451  *
2452  * @returns {Array}
2453  * @memberOf SortedSet
2454  */
2455 lunr.SortedSet.prototype.toJSON = function () {
2456   return this.toArray()
2457 }
2458   /**
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
2461    */
2462   ;(function (root, factory) {
2463     if (typeof define === 'function' && define.amd) {
2464       // AMD. Register as an anonymous module.
2465       define(factory)
2466     } else if (typeof exports === 'object') {
2467       /**
2468        * Node. Does not work with strict CommonJS, but
2469        * only CommonJS-like enviroments that support module.exports,
2470        * like Node.
2471        */
2472       module.exports = factory()
2473     } else {
2474       // Browser globals (root is window)
2475       root.elasticlunr = factory()
2476     }
2477   }(this, function () {
2478     /**
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.
2482      */
2483     return elasticlunr
2484   }))
2485 })();