1/**
2 * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.8
3 * Copyright (C) 2019 Oliver Nightingale
4 * @license MIT
5 */
6
7;(function(){
8
9/**
10 * A convenience function for configuring and constructing
11 * a new lunr Index.
12 *
13 * A lunr.Builder instance is created and the pipeline setup
14 * with a trimmer, stop word filter and stemmer.
15 *
16 * This builder object is yielded to the configuration function
17 * that is passed as a parameter, allowing the list of fields
18 * and other builder parameters to be customised.
19 *
20 * All documents _must_ be added within the passed config function.
21 *
22 * @example
23 * var idx = lunr(function () {
24 *   this.field('title')
25 *   this.field('body')
26 *   this.ref('id')
27 *
28 *   documents.forEach(function (doc) {
29 *     this.add(doc)
30 *   }, this)
31 * })
32 *
33 * @see {@link lunr.Builder}
34 * @see {@link lunr.Pipeline}
35 * @see {@link lunr.trimmer}
36 * @see {@link lunr.stopWordFilter}
37 * @see {@link lunr.stemmer}
38 * @namespace {function} lunr
39 */
40var lunr = function (config) {
41  var builder = new lunr.Builder
42
43  builder.pipeline.add(
44    lunr.trimmer,
45    lunr.stopWordFilter,
46    lunr.stemmer
47  )
48
49  builder.searchPipeline.add(
50    lunr.stemmer
51  )
52
53  config.call(builder, builder)
54  return builder.build()
55}
56
57lunr.version = "2.3.8"
58/*!
59 * lunr.utils
60 * Copyright (C) 2019 Oliver Nightingale
61 */
62
63/**
64 * A namespace containing utils for the rest of the lunr library
65 * @namespace lunr.utils
66 */
67lunr.utils = {}
68
69/**
70 * Print a warning message to the console.
71 *
72 * @param {String} message The message to be printed.
73 * @memberOf lunr.utils
74 * @function
75 */
76lunr.utils.warn = (function (global) {
77  /* eslint-disable no-console */
78  return function (message) {
79    if (global.console && console.warn) {
80      console.warn(message)
81    }
82  }
83  /* eslint-enable no-console */
84})(this)
85
86/**
87 * Convert an object to a string.
88 *
89 * In the case of `null` and `undefined` the function returns
90 * the empty string, in all other cases the result of calling
91 * `toString` on the passed object is returned.
92 *
93 * @param {Any} obj The object to convert to a string.
94 * @return {String} string representation of the passed object.
95 * @memberOf lunr.utils
96 */
97lunr.utils.asString = function (obj) {
98  if (obj === void 0 || obj === null) {
99    return ""
100  } else {
101    return obj.toString()
102  }
103}
104
105/**
106 * Clones an object.
107 *
108 * Will create a copy of an existing object such that any mutations
109 * on the copy cannot affect the original.
110 *
111 * Only shallow objects are supported, passing a nested object to this
112 * function will cause a TypeError.
113 *
114 * Objects with primitives, and arrays of primitives are supported.
115 *
116 * @param {Object} obj The object to clone.
117 * @return {Object} a clone of the passed object.
118 * @throws {TypeError} when a nested object is passed.
119 * @memberOf Utils
120 */
121lunr.utils.clone = function (obj) {
122  if (obj === null || obj === undefined) {
123    return obj
124  }
125
126  var clone = Object.create(null),
127      keys = Object.keys(obj)
128
129  for (var i = 0; i < keys.length; i++) {
130    var key = keys[i],
131        val = obj[key]
132
133    if (Array.isArray(val)) {
134      clone[key] = val.slice()
135      continue
136    }
137
138    if (typeof val === 'string' ||
139        typeof val === 'number' ||
140        typeof val === 'boolean') {
141      clone[key] = val
142      continue
143    }
144
145    throw new TypeError("clone is not deep and does not support nested objects")
146  }
147
148  return clone
149}
150lunr.FieldRef = function (docRef, fieldName, stringValue) {
151  this.docRef = docRef
152  this.fieldName = fieldName
153  this._stringValue = stringValue
154}
155
156lunr.FieldRef.joiner = "/"
157
158lunr.FieldRef.fromString = function (s) {
159  var n = s.indexOf(lunr.FieldRef.joiner)
160
161  if (n === -1) {
162    throw "malformed field ref string"
163  }
164
165  var fieldRef = s.slice(0, n),
166      docRef = s.slice(n + 1)
167
168  return new lunr.FieldRef (docRef, fieldRef, s)
169}
170
171lunr.FieldRef.prototype.toString = function () {
172  if (this._stringValue == undefined) {
173    this._stringValue = this.fieldName + lunr.FieldRef.joiner + this.docRef
174  }
175
176  return this._stringValue
177}
178/*!
179 * lunr.Set
180 * Copyright (C) 2019 Oliver Nightingale
181 */
182
183/**
184 * A lunr set.
185 *
186 * @constructor
187 */
188lunr.Set = function (elements) {
189  this.elements = Object.create(null)
190
191  if (elements) {
192    this.length = elements.length
193
194    for (var i = 0; i < this.length; i++) {
195      this.elements[elements[i]] = true
196    }
197  } else {
198    this.length = 0
199  }
200}
201
202/**
203 * A complete set that contains all elements.
204 *
205 * @static
206 * @readonly
207 * @type {lunr.Set}
208 */
209lunr.Set.complete = {
210  intersect: function (other) {
211    return other
212  },
213
214  union: function (other) {
215    return other
216  },
217
218  contains: function () {
219    return true
220  }
221}
222
223/**
224 * An empty set that contains no elements.
225 *
226 * @static
227 * @readonly
228 * @type {lunr.Set}
229 */
230lunr.Set.empty = {
231  intersect: function () {
232    return this
233  },
234
235  union: function (other) {
236    return other
237  },
238
239  contains: function () {
240    return false
241  }
242}
243
244/**
245 * Returns true if this set contains the specified object.
246 *
247 * @param {object} object - Object whose presence in this set is to be tested.
248 * @returns {boolean} - True if this set contains the specified object.
249 */
250lunr.Set.prototype.contains = function (object) {
251  return !!this.elements[object]
252}
253
254/**
255 * Returns a new set containing only the elements that are present in both
256 * this set and the specified set.
257 *
258 * @param {lunr.Set} other - set to intersect with this set.
259 * @returns {lunr.Set} a new set that is the intersection of this and the specified set.
260 */
261
262lunr.Set.prototype.intersect = function (other) {
263  var a, b, elements, intersection = []
264
265  if (other === lunr.Set.complete) {
266    return this
267  }
268
269  if (other === lunr.Set.empty) {
270    return other
271  }
272
273  if (this.length < other.length) {
274    a = this
275    b = other
276  } else {
277    a = other
278    b = this
279  }
280
281  elements = Object.keys(a.elements)
282
283  for (var i = 0; i < elements.length; i++) {
284    var element = elements[i]
285    if (element in b.elements) {
286      intersection.push(element)
287    }
288  }
289
290  return new lunr.Set (intersection)
291}
292
293/**
294 * Returns a new set combining the elements of this and the specified set.
295 *
296 * @param {lunr.Set} other - set to union with this set.
297 * @return {lunr.Set} a new set that is the union of this and the specified set.
298 */
299
300lunr.Set.prototype.union = function (other) {
301  if (other === lunr.Set.complete) {
302    return lunr.Set.complete
303  }
304
305  if (other === lunr.Set.empty) {
306    return this
307  }
308
309  return new lunr.Set(Object.keys(this.elements).concat(Object.keys(other.elements)))
310}
311/**
312 * A function to calculate the inverse document frequency for
313 * a posting. This is shared between the builder and the index
314 *
315 * @private
316 * @param {object} posting - The posting for a given term
317 * @param {number} documentCount - The total number of documents.
318 */
319lunr.idf = function (posting, documentCount) {
320  var documentsWithTerm = 0
321
322  for (var fieldName in posting) {
323    if (fieldName == '_index') continue // Ignore the term index, its not a field
324    documentsWithTerm += Object.keys(posting[fieldName]).length
325  }
326
327  var x = (documentCount - documentsWithTerm + 0.5) / (documentsWithTerm + 0.5)
328
329  return Math.log(1 + Math.abs(x))
330}
331
332/**
333 * A token wraps a string representation of a token
334 * as it is passed through the text processing pipeline.
335 *
336 * @constructor
337 * @param {string} [str=''] - The string token being wrapped.
338 * @param {object} [metadata={}] - Metadata associated with this token.
339 */
340lunr.Token = function (str, metadata) {
341  this.str = str || ""
342  this.metadata = metadata || {}
343}
344
345/**
346 * Returns the token string that is being wrapped by this object.
347 *
348 * @returns {string}
349 */
350lunr.Token.prototype.toString = function () {
351  return this.str
352}
353
354/**
355 * A token update function is used when updating or optionally
356 * when cloning a token.
357 *
358 * @callback lunr.Token~updateFunction
359 * @param {string} str - The string representation of the token.
360 * @param {Object} metadata - All metadata associated with this token.
361 */
362
363/**
364 * Applies the given function to the wrapped string token.
365 *
366 * @example
367 * token.update(function (str, metadata) {
368 *   return str.toUpperCase()
369 * })
370 *
371 * @param {lunr.Token~updateFunction} fn - A function to apply to the token string.
372 * @returns {lunr.Token}
373 */
374lunr.Token.prototype.update = function (fn) {
375  this.str = fn(this.str, this.metadata)
376  return this
377}
378
379/**
380 * Creates a clone of this token. Optionally a function can be
381 * applied to the cloned token.
382 *
383 * @param {lunr.Token~updateFunction} [fn] - An optional function to apply to the cloned token.
384 * @returns {lunr.Token}
385 */
386lunr.Token.prototype.clone = function (fn) {
387  fn = fn || function (s) { return s }
388  return new lunr.Token (fn(this.str, this.metadata), this.metadata)
389}
390/*!
391 * lunr.tokenizer
392 * Copyright (C) 2019 Oliver Nightingale
393 */
394
395/**
396 * A function for splitting a string into tokens ready to be inserted into
397 * the search index. Uses `lunr.tokenizer.separator` to split strings, change
398 * the value of this property to change how strings are split into tokens.
399 *
400 * This tokenizer will convert its parameter to a string by calling `toString` and
401 * then will split this string on the character in `lunr.tokenizer.separator`.
402 * Arrays will have their elements converted to strings and wrapped in a lunr.Token.
403 *
404 * Optional metadata can be passed to the tokenizer, this metadata will be cloned and
405 * added as metadata to every token that is created from the object to be tokenized.
406 *
407 * @static
408 * @param {?(string|object|object[])} obj - The object to convert into tokens
409 * @param {?object} metadata - Optional metadata to associate with every token
410 * @returns {lunr.Token[]}
411 * @see {@link lunr.Pipeline}
412 */
413lunr.tokenizer = function (obj, metadata) {
414  if (obj == null || obj == undefined) {
415    return []
416  }
417
418  if (Array.isArray(obj)) {
419    return obj.map(function (t) {
420      return new lunr.Token(
421        lunr.utils.asString(t).toLowerCase(),
422        lunr.utils.clone(metadata)
423      )
424    })
425  }
426
427  var str = obj.toString().toLowerCase(),
428      len = str.length,
429      tokens = []
430
431  for (var sliceEnd = 0, sliceStart = 0; sliceEnd <= len; sliceEnd++) {
432    var char = str.charAt(sliceEnd),
433        sliceLength = sliceEnd - sliceStart
434
435    if ((char.match(lunr.tokenizer.separator) || sliceEnd == len)) {
436
437      if (sliceLength > 0) {
438        var tokenMetadata = lunr.utils.clone(metadata) || {}
439        tokenMetadata["position"] = [sliceStart, sliceLength]
440        tokenMetadata["index"] = tokens.length
441
442        tokens.push(
443          new lunr.Token (
444            str.slice(sliceStart, sliceEnd),
445            tokenMetadata
446          )
447        )
448      }
449
450      sliceStart = sliceEnd + 1
451    }
452
453  }
454
455  return tokens
456}
457
458/**
459 * The separator used to split a string into tokens. Override this property to change the behaviour of
460 * `lunr.tokenizer` behaviour when tokenizing strings. By default this splits on whitespace and hyphens.
461 *
462 * @static
463 * @see lunr.tokenizer
464 */
465lunr.tokenizer.separator = /[\s\-]+/
466/*!
467 * lunr.Pipeline
468 * Copyright (C) 2019 Oliver Nightingale
469 */
470
471/**
472 * lunr.Pipelines maintain an ordered list of functions to be applied to all
473 * tokens in documents entering the search index and queries being ran against
474 * the index.
475 *
476 * An instance of lunr.Index created with the lunr shortcut will contain a
477 * pipeline with a stop word filter and an English language stemmer. Extra
478 * functions can be added before or after either of these functions or these
479 * default functions can be removed.
480 *
481 * When run the pipeline will call each function in turn, passing a token, the
482 * index of that token in the original list of all tokens and finally a list of
483 * all the original tokens.
484 *
485 * The output of functions in the pipeline will be passed to the next function
486 * in the pipeline. To exclude a token from entering the index the function
487 * should return undefined, the rest of the pipeline will not be called with
488 * this token.
489 *
490 * For serialisation of pipelines to work, all functions used in an instance of
491 * a pipeline should be registered with lunr.Pipeline. Registered functions can
492 * then be loaded. If trying to load a serialised pipeline that uses functions
493 * that are not registered an error will be thrown.
494 *
495 * If not planning on serialising the pipeline then registering pipeline functions
496 * is not necessary.
497 *
498 * @constructor
499 */
500lunr.Pipeline = function () {
501  this._stack = []
502}
503
504lunr.Pipeline.registeredFunctions = Object.create(null)
505
506/**
507 * A pipeline function maps lunr.Token to lunr.Token. A lunr.Token contains the token
508 * string as well as all known metadata. A pipeline function can mutate the token string
509 * or mutate (or add) metadata for a given token.
510 *
511 * A pipeline function can indicate that the passed token should be discarded by returning
512 * null, undefined or an empty string. This token will not be passed to any downstream pipeline
513 * functions and will not be added to the index.
514 *
515 * Multiple tokens can be returned by returning an array of tokens. Each token will be passed
516 * to any downstream pipeline functions and all will returned tokens will be added to the index.
517 *
518 * Any number of pipeline functions may be chained together using a lunr.Pipeline.
519 *
520 * @interface lunr.PipelineFunction
521 * @param {lunr.Token} token - A token from the document being processed.
522 * @param {number} i - The index of this token in the complete list of tokens for this document/field.
523 * @param {lunr.Token[]} tokens - All tokens for this document/field.
524 * @returns {(?lunr.Token|lunr.Token[])}
525 */
526
527/**
528 * Register a function with the pipeline.
529 *
530 * Functions that are used in the pipeline should be registered if the pipeline
531 * needs to be serialised, or a serialised pipeline needs to be loaded.
532 *
533 * Registering a function does not add it to a pipeline, functions must still be
534 * added to instances of the pipeline for them to be used when running a pipeline.
535 *
536 * @param {lunr.PipelineFunction} fn - The function to check for.
537 * @param {String} label - The label to register this function with
538 */
539lunr.Pipeline.registerFunction = function (fn, label) {
540  if (label in this.registeredFunctions) {
541    lunr.utils.warn('Overwriting existing registered function: ' + label)
542  }
543
544  fn.label = label
545  lunr.Pipeline.registeredFunctions[fn.label] = fn
546}
547
548/**
549 * Warns if the function is not registered as a Pipeline function.
550 *
551 * @param {lunr.PipelineFunction} fn - The function to check for.
552 * @private
553 */
554lunr.Pipeline.warnIfFunctionNotRegistered = function (fn) {
555  var isRegistered = fn.label && (fn.label in this.registeredFunctions)
556
557  if (!isRegistered) {
558    lunr.utils.warn('Function is not registered with pipeline. This may cause problems when serialising the index.\n', fn)
559  }
560}
561
562/**
563 * Loads a previously serialised pipeline.
564 *
565 * All functions to be loaded must already be registered with lunr.Pipeline.
566 * If any function from the serialised data has not been registered then an
567 * error will be thrown.
568 *
569 * @param {Object} serialised - The serialised pipeline to load.
570 * @returns {lunr.Pipeline}
571 */
572lunr.Pipeline.load = function (serialised) {
573  var pipeline = new lunr.Pipeline
574
575  serialised.forEach(function (fnName) {
576    var fn = lunr.Pipeline.registeredFunctions[fnName]
577
578    if (fn) {
579      pipeline.add(fn)
580    } else {
581      throw new Error('Cannot load unregistered function: ' + fnName)
582    }
583  })
584
585  return pipeline
586}
587
588/**
589 * Adds new functions to the end of the pipeline.
590 *
591 * Logs a warning if the function has not been registered.
592 *
593 * @param {lunr.PipelineFunction[]} functions - Any number of functions to add to the pipeline.
594 */
595lunr.Pipeline.prototype.add = function () {
596  var fns = Array.prototype.slice.call(arguments)
597
598  fns.forEach(function (fn) {
599    lunr.Pipeline.warnIfFunctionNotRegistered(fn)
600    this._stack.push(fn)
601  }, this)
602}
603
604/**
605 * Adds a single function after a function that already exists in the
606 * pipeline.
607 *
608 * Logs a warning if the function has not been registered.
609 *
610 * @param {lunr.PipelineFunction} existingFn - A function that already exists in the pipeline.
611 * @param {lunr.PipelineFunction} newFn - The new function to add to the pipeline.
612 */
613lunr.Pipeline.prototype.after = function (existingFn, newFn) {
614  lunr.Pipeline.warnIfFunctionNotRegistered(newFn)
615
616  var pos = this._stack.indexOf(existingFn)
617  if (pos == -1) {
618    throw new Error('Cannot find existingFn')
619  }
620
621  pos = pos + 1
622  this._stack.splice(pos, 0, newFn)
623}
624
625/**
626 * Adds a single function before a function that already exists in the
627 * pipeline.
628 *
629 * Logs a warning if the function has not been registered.
630 *
631 * @param {lunr.PipelineFunction} existingFn - A function that already exists in the pipeline.
632 * @param {lunr.PipelineFunction} newFn - The new function to add to the pipeline.
633 */
634lunr.Pipeline.prototype.before = function (existingFn, newFn) {
635  lunr.Pipeline.warnIfFunctionNotRegistered(newFn)
636
637  var pos = this._stack.indexOf(existingFn)
638  if (pos == -1) {
639    throw new Error('Cannot find existingFn')
640  }
641
642  this._stack.splice(pos, 0, newFn)
643}
644
645/**
646 * Removes a function from the pipeline.
647 *
648 * @param {lunr.PipelineFunction} fn The function to remove from the pipeline.
649 */
650lunr.Pipeline.prototype.remove = function (fn) {
651  var pos = this._stack.indexOf(fn)
652  if (pos == -1) {
653    return
654  }
655
656  this._stack.splice(pos, 1)
657}
658
659/**
660 * Runs the current list of functions that make up the pipeline against the
661 * passed tokens.
662 *
663 * @param {Array} tokens The tokens to run through the pipeline.
664 * @returns {Array}
665 */
666lunr.Pipeline.prototype.run = function (tokens) {
667  var stackLength = this._stack.length
668
669  for (var i = 0; i < stackLength; i++) {
670    var fn = this._stack[i]
671    var memo = []
672
673    for (var j = 0; j < tokens.length; j++) {
674      var result = fn(tokens[j], j, tokens)
675
676      if (result === null || result === void 0 || result === '') continue
677
678      if (Array.isArray(result)) {
679        for (var k = 0; k < result.length; k++) {
680          memo.push(result[k])
681        }
682      } else {
683        memo.push(result)
684      }
685    }
686
687    tokens = memo
688  }
689
690  return tokens
691}
692
693/**
694 * Convenience method for passing a string through a pipeline and getting
695 * strings out. This method takes care of wrapping the passed string in a
696 * token and mapping the resulting tokens back to strings.
697 *
698 * @param {string} str - The string to pass through the pipeline.
699 * @param {?object} metadata - Optional metadata to associate with the token
700 * passed to the pipeline.
701 * @returns {string[]}
702 */
703lunr.Pipeline.prototype.runString = function (str, metadata) {
704  var token = new lunr.Token (str, metadata)
705
706  return this.run([token]).map(function (t) {
707    return t.toString()
708  })
709}
710
711/**
712 * Resets the pipeline by removing any existing processors.
713 *
714 */
715lunr.Pipeline.prototype.reset = function () {
716  this._stack = []
717}
718
719/**
720 * Returns a representation of the pipeline ready for serialisation.
721 *
722 * Logs a warning if the function has not been registered.
723 *
724 * @returns {Array}
725 */
726lunr.Pipeline.prototype.toJSON = function () {
727  return this._stack.map(function (fn) {
728    lunr.Pipeline.warnIfFunctionNotRegistered(fn)
729
730    return fn.label
731  })
732}
733/*!
734 * lunr.Vector
735 * Copyright (C) 2019 Oliver Nightingale
736 */
737
738/**
739 * A vector is used to construct the vector space of documents and queries. These
740 * vectors support operations to determine the similarity between two documents or
741 * a document and a query.
742 *
743 * Normally no parameters are required for initializing a vector, but in the case of
744 * loading a previously dumped vector the raw elements can be provided to the constructor.
745 *
746 * For performance reasons vectors are implemented with a flat array, where an elements
747 * index is immediately followed by its value. E.g. [index, value, index, value]. This
748 * allows the underlying array to be as sparse as possible and still offer decent
749 * performance when being used for vector calculations.
750 *
751 * @constructor
752 * @param {Number[]} [elements] - The flat list of element index and element value pairs.
753 */
754lunr.Vector = function (elements) {
755  this._magnitude = 0
756  this.elements = elements || []
757}
758
759
760/**
761 * Calculates the position within the vector to insert a given index.
762 *
763 * This is used internally by insert and upsert. If there are duplicate indexes then
764 * the position is returned as if the value for that index were to be updated, but it
765 * is the callers responsibility to check whether there is a duplicate at that index
766 *
767 * @param {Number} insertIdx - The index at which the element should be inserted.
768 * @returns {Number}
769 */
770lunr.Vector.prototype.positionForIndex = function (index) {
771  // For an empty vector the tuple can be inserted at the beginning
772  if (this.elements.length == 0) {
773    return 0
774  }
775
776  var start = 0,
777      end = this.elements.length / 2,
778      sliceLength = end - start,
779      pivotPoint = Math.floor(sliceLength / 2),
780      pivotIndex = this.elements[pivotPoint * 2]
781
782  while (sliceLength > 1) {
783    if (pivotIndex < index) {
784      start = pivotPoint
785    }
786
787    if (pivotIndex > index) {
788      end = pivotPoint
789    }
790
791    if (pivotIndex == index) {
792      break
793    }
794
795    sliceLength = end - start
796    pivotPoint = start + Math.floor(sliceLength / 2)
797    pivotIndex = this.elements[pivotPoint * 2]
798  }
799
800  if (pivotIndex == index) {
801    return pivotPoint * 2
802  }
803
804  if (pivotIndex > index) {
805    return pivotPoint * 2
806  }
807
808  if (pivotIndex < index) {
809    return (pivotPoint + 1) * 2
810  }
811}
812
813/**
814 * Inserts an element at an index within the vector.
815 *
816 * Does not allow duplicates, will throw an error if there is already an entry
817 * for this index.
818 *
819 * @param {Number} insertIdx - The index at which the element should be inserted.
820 * @param {Number} val - The value to be inserted into the vector.
821 */
822lunr.Vector.prototype.insert = function (insertIdx, val) {
823  this.upsert(insertIdx, val, function () {
824    throw "duplicate index"
825  })
826}
827
828/**
829 * Inserts or updates an existing index within the vector.
830 *
831 * @param {Number} insertIdx - The index at which the element should be inserted.
832 * @param {Number} val - The value to be inserted into the vector.
833 * @param {function} fn - A function that is called for updates, the existing value and the
834 * requested value are passed as arguments
835 */
836lunr.Vector.prototype.upsert = function (insertIdx, val, fn) {
837  this._magnitude = 0
838  var position = this.positionForIndex(insertIdx)
839
840  if (this.elements[position] == insertIdx) {
841    this.elements[position + 1] = fn(this.elements[position + 1], val)
842  } else {
843    this.elements.splice(position, 0, insertIdx, val)
844  }
845}
846
847/**
848 * Calculates the magnitude of this vector.
849 *
850 * @returns {Number}
851 */
852lunr.Vector.prototype.magnitude = function () {
853  if (this._magnitude) return this._magnitude
854
855  var sumOfSquares = 0,
856      elementsLength = this.elements.length
857
858  for (var i = 1; i < elementsLength; i += 2) {
859    var val = this.elements[i]
860    sumOfSquares += val * val
861  }
862
863  return this._magnitude = Math.sqrt(sumOfSquares)
864}
865
866/**
867 * Calculates the dot product of this vector and another vector.
868 *
869 * @param {lunr.Vector} otherVector - The vector to compute the dot product with.
870 * @returns {Number}
871 */
872lunr.Vector.prototype.dot = function (otherVector) {
873  var dotProduct = 0,
874      a = this.elements, b = otherVector.elements,
875      aLen = a.length, bLen = b.length,
876      aVal = 0, bVal = 0,
877      i = 0, j = 0
878
879  while (i < aLen && j < bLen) {
880    aVal = a[i], bVal = b[j]
881    if (aVal < bVal) {
882      i += 2
883    } else if (aVal > bVal) {
884      j += 2
885    } else if (aVal == bVal) {
886      dotProduct += a[i + 1] * b[j + 1]
887      i += 2
888      j += 2
889    }
890  }
891
892  return dotProduct
893}
894
895/**
896 * Calculates the similarity between this vector and another vector.
897 *
898 * @param {lunr.Vector} otherVector - The other vector to calculate the
899 * similarity with.
900 * @returns {Number}
901 */
902lunr.Vector.prototype.similarity = function (otherVector) {
903  return this.dot(otherVector) / this.magnitude() || 0
904}
905
906/**
907 * Converts the vector to an array of the elements within the vector.
908 *
909 * @returns {Number[]}
910 */
911lunr.Vector.prototype.toArray = function () {
912  var output = new Array (this.elements.length / 2)
913
914  for (var i = 1, j = 0; i < this.elements.length; i += 2, j++) {
915    output[j] = this.elements[i]
916  }
917
918  return output
919}
920
921/**
922 * A JSON serializable representation of the vector.
923 *
924 * @returns {Number[]}
925 */
926lunr.Vector.prototype.toJSON = function () {
927  return this.elements
928}
929/* eslint-disable */
930/*!
931 * lunr.stemmer
932 * Copyright (C) 2019 Oliver Nightingale
933 * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt
934 */
935
936/**
937 * lunr.stemmer is an english language stemmer, this is a JavaScript
938 * implementation of the PorterStemmer taken from http://tartarus.org/~martin
939 *
940 * @static
941 * @implements {lunr.PipelineFunction}
942 * @param {lunr.Token} token - The string to stem
943 * @returns {lunr.Token}
944 * @see {@link lunr.Pipeline}
945 * @function
946 */
947lunr.stemmer = (function(){
948  var step2list = {
949      "ational" : "ate",
950      "tional" : "tion",
951      "enci" : "ence",
952      "anci" : "ance",
953      "izer" : "ize",
954      "bli" : "ble",
955      "alli" : "al",
956      "entli" : "ent",
957      "eli" : "e",
958      "ousli" : "ous",
959      "ization" : "ize",
960      "ation" : "ate",
961      "ator" : "ate",
962      "alism" : "al",
963      "iveness" : "ive",
964      "fulness" : "ful",
965      "ousness" : "ous",
966      "aliti" : "al",
967      "iviti" : "ive",
968      "biliti" : "ble",
969      "logi" : "log"
970    },
971
972    step3list = {
973      "icate" : "ic",
974      "ative" : "",
975      "alize" : "al",
976      "iciti" : "ic",
977      "ical" : "ic",
978      "ful" : "",
979      "ness" : ""
980    },
981
982    c = "[^aeiou]",          // consonant
983    v = "[aeiouy]",          // vowel
984    C = c + "[^aeiouy]*",    // consonant sequence
985    V = v + "[aeiou]*",      // vowel sequence
986
987    mgr0 = "^(" + C + ")?" + V + C,               // [C]VC... is m>0
988    meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$",  // [C]VC[V] is m=1
989    mgr1 = "^(" + C + ")?" + V + C + V + C,       // [C]VCVC... is m>1
990    s_v = "^(" + C + ")?" + v;                   // vowel in stem
991
992  var re_mgr0 = new RegExp(mgr0);
993  var re_mgr1 = new RegExp(mgr1);
994  var re_meq1 = new RegExp(meq1);
995  var re_s_v = new RegExp(s_v);
996
997  var re_1a = /^(.+?)(ss|i)es$/;
998  var re2_1a = /^(.+?)([^s])s$/;
999  var re_1b = /^(.+?)eed$/;
1000  var re2_1b = /^(.+?)(ed|ing)$/;
1001  var re_1b_2 = /.$/;
1002  var re2_1b_2 = /(at|bl|iz)$/;
1003  var re3_1b_2 = new RegExp("([^aeiouylsz])\\1$");
1004  var re4_1b_2 = new RegExp("^" + C + v + "[^aeiouwxy]$");
1005
1006  var re_1c = /^(.+?[^aeiou])y$/;
1007  var re_2 = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;
1008
1009  var re_3 = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;
1010
1011  var re_4 = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;
1012  var re2_4 = /^(.+?)(s|t)(ion)$/;
1013
1014  var re_5 = /^(.+?)e$/;
1015  var re_5_1 = /ll$/;
1016  var re3_5 = new RegExp("^" + C + v + "[^aeiouwxy]$");
1017
1018  var porterStemmer = function porterStemmer(w) {
1019    var stem,
1020      suffix,
1021      firstch,
1022      re,
1023      re2,
1024      re3,
1025      re4;
1026
1027    if (w.length < 3) { return w; }
1028
1029    firstch = w.substr(0,1);
1030    if (firstch == "y") {
1031      w = firstch.toUpperCase() + w.substr(1);
1032    }
1033
1034    // Step 1a
1035    re = re_1a
1036    re2 = re2_1a;
1037
1038    if (re.test(w)) { w = w.replace(re,"$1$2"); }
1039    else if (re2.test(w)) { w = w.replace(re2,"$1$2"); }
1040
1041    // Step 1b
1042    re = re_1b;
1043    re2 = re2_1b;
1044    if (re.test(w)) {
1045      var fp = re.exec(w);
1046      re = re_mgr0;
1047      if (re.test(fp[1])) {
1048        re = re_1b_2;
1049        w = w.replace(re,"");
1050      }
1051    } else if (re2.test(w)) {
1052      var fp = re2.exec(w);
1053      stem = fp[1];
1054      re2 = re_s_v;
1055      if (re2.test(stem)) {
1056        w = stem;
1057        re2 = re2_1b_2;
1058        re3 = re3_1b_2;
1059        re4 = re4_1b_2;
1060        if (re2.test(w)) { w = w + "e"; }
1061        else if (re3.test(w)) { re = re_1b_2; w = w.replace(re,""); }
1062        else if (re4.test(w)) { w = w + "e"; }
1063      }
1064    }
1065
1066    // 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)
1067    re = re_1c;
1068    if (re.test(w)) {
1069      var fp = re.exec(w);
1070      stem = fp[1];
1071      w = stem + "i";
1072    }
1073
1074    // Step 2
1075    re = re_2;
1076    if (re.test(w)) {
1077      var fp = re.exec(w);
1078      stem = fp[1];
1079      suffix = fp[2];
1080      re = re_mgr0;
1081      if (re.test(stem)) {
1082        w = stem + step2list[suffix];
1083      }
1084    }
1085
1086    // Step 3
1087    re = re_3;
1088    if (re.test(w)) {
1089      var fp = re.exec(w);
1090      stem = fp[1];
1091      suffix = fp[2];
1092      re = re_mgr0;
1093      if (re.test(stem)) {
1094        w = stem + step3list[suffix];
1095      }
1096    }
1097
1098    // Step 4
1099    re = re_4;
1100    re2 = re2_4;
1101    if (re.test(w)) {
1102      var fp = re.exec(w);
1103      stem = fp[1];
1104      re = re_mgr1;
1105      if (re.test(stem)) {
1106        w = stem;
1107      }
1108    } else if (re2.test(w)) {
1109      var fp = re2.exec(w);
1110      stem = fp[1] + fp[2];
1111      re2 = re_mgr1;
1112      if (re2.test(stem)) {
1113        w = stem;
1114      }
1115    }
1116
1117    // Step 5
1118    re = re_5;
1119    if (re.test(w)) {
1120      var fp = re.exec(w);
1121      stem = fp[1];
1122      re = re_mgr1;
1123      re2 = re_meq1;
1124      re3 = re3_5;
1125      if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) {
1126        w = stem;
1127      }
1128    }
1129
1130    re = re_5_1;
1131    re2 = re_mgr1;
1132    if (re.test(w) && re2.test(w)) {
1133      re = re_1b_2;
1134      w = w.replace(re,"");
1135    }
1136
1137    // and turn initial Y back to y
1138
1139    if (firstch == "y") {
1140      w = firstch.toLowerCase() + w.substr(1);
1141    }
1142
1143    return w;
1144  };
1145
1146  return function (token) {
1147    return token.update(porterStemmer);
1148  }
1149})();
1150
1151lunr.Pipeline.registerFunction(lunr.stemmer, 'stemmer')
1152/*!
1153 * lunr.stopWordFilter
1154 * Copyright (C) 2019 Oliver Nightingale
1155 */
1156
1157/**
1158 * lunr.generateStopWordFilter builds a stopWordFilter function from the provided
1159 * list of stop words.
1160 *
1161 * The built in lunr.stopWordFilter is built using this generator and can be used
1162 * to generate custom stopWordFilters for applications or non English languages.
1163 *
1164 * @function
1165 * @param {Array} token The token to pass through the filter
1166 * @returns {lunr.PipelineFunction}
1167 * @see lunr.Pipeline
1168 * @see lunr.stopWordFilter
1169 */
1170lunr.generateStopWordFilter = function (stopWords) {
1171  var words = stopWords.reduce(function (memo, stopWord) {
1172    memo[stopWord] = stopWord
1173    return memo
1174  }, {})
1175
1176  return function (token) {
1177    if (token && words[token.toString()] !== token.toString()) return token
1178  }
1179}
1180
1181/**
1182 * lunr.stopWordFilter is an English language stop word list filter, any words
1183 * contained in the list will not be passed through the filter.
1184 *
1185 * This is intended to be used in the Pipeline. If the token does not pass the
1186 * filter then undefined will be returned.
1187 *
1188 * @function
1189 * @implements {lunr.PipelineFunction}
1190 * @params {lunr.Token} token - A token to check for being a stop word.
1191 * @returns {lunr.Token}
1192 * @see {@link lunr.Pipeline}
1193 */
1194lunr.stopWordFilter = lunr.generateStopWordFilter([
1195  'a',
1196  'able',
1197  'about',
1198  'across',
1199  'after',
1200  'all',
1201  'almost',
1202  'also',
1203  'am',
1204  'among',
1205  'an',
1206  'and',
1207  'any',
1208  'are',
1209  'as',
1210  'at',
1211  'be',
1212  'because',
1213  'been',
1214  'but',
1215  'by',
1216  'can',
1217  'cannot',
1218  'could',
1219  'dear',
1220  'did',
1221  'do',
1222  'does',
1223  'either',
1224  'else',
1225  'ever',
1226  'every',
1227  'for',
1228  'from',
1229  'get',
1230  'got',
1231  'had',
1232  'has',
1233  'have',
1234  'he',
1235  'her',
1236  'hers',
1237  'him',
1238  'his',
1239  'how',
1240  'however',
1241  'i',
1242  'if',
1243  'in',
1244  'into',
1245  'is',
1246  'it',
1247  'its',
1248  'just',
1249  'least',
1250  'let',
1251  'like',
1252  'likely',
1253  'may',
1254  'me',
1255  'might',
1256  'most',
1257  'must',
1258  'my',
1259  'neither',
1260  'no',
1261  'nor',
1262  'not',
1263  'of',
1264  'off',
1265  'often',
1266  'on',
1267  'only',
1268  'or',
1269  'other',
1270  'our',
1271  'own',
1272  'rather',
1273  'said',
1274  'say',
1275  'says',
1276  'she',
1277  'should',
1278  'since',
1279  'so',
1280  'some',
1281  'than',
1282  'that',
1283  'the',
1284  'their',
1285  'them',
1286  'then',
1287  'there',
1288  'these',
1289  'they',
1290  'this',
1291  'tis',
1292  'to',
1293  'too',
1294  'twas',
1295  'us',
1296  'wants',
1297  'was',
1298  'we',
1299  'were',
1300  'what',
1301  'when',
1302  'where',
1303  'which',
1304  'while',
1305  'who',
1306  'whom',
1307  'why',
1308  'will',
1309  'with',
1310  'would',
1311  'yet',
1312  'you',
1313  'your'
1314])
1315
1316lunr.Pipeline.registerFunction(lunr.stopWordFilter, 'stopWordFilter')
1317/*!
1318 * lunr.trimmer
1319 * Copyright (C) 2019 Oliver Nightingale
1320 */
1321
1322/**
1323 * lunr.trimmer is a pipeline function for trimming non word
1324 * characters from the beginning and end of tokens before they
1325 * enter the index.
1326 *
1327 * This implementation may not work correctly for non latin
1328 * characters and should either be removed or adapted for use
1329 * with languages with non-latin characters.
1330 *
1331 * @static
1332 * @implements {lunr.PipelineFunction}
1333 * @param {lunr.Token} token The token to pass through the filter
1334 * @returns {lunr.Token}
1335 * @see lunr.Pipeline
1336 */
1337lunr.trimmer = function (token) {
1338  return token.update(function (s) {
1339    return s.replace(/^\W+/, '').replace(/\W+$/, '')
1340  })
1341}
1342
1343lunr.Pipeline.registerFunction(lunr.trimmer, 'trimmer')
1344/*!
1345 * lunr.TokenSet
1346 * Copyright (C) 2019 Oliver Nightingale
1347 */
1348
1349/**
1350 * A token set is used to store the unique list of all tokens
1351 * within an index. Token sets are also used to represent an
1352 * incoming query to the index, this query token set and index
1353 * token set are then intersected to find which tokens to look
1354 * up in the inverted index.
1355 *
1356 * A token set can hold multiple tokens, as in the case of the
1357 * index token set, or it can hold a single token as in the
1358 * case of a simple query token set.
1359 *
1360 * Additionally token sets are used to perform wildcard matching.
1361 * Leading, contained and trailing wildcards are supported, and
1362 * from this edit distance matching can also be provided.
1363 *
1364 * Token sets are implemented as a minimal finite state automata,
1365 * where both common prefixes and suffixes are shared between tokens.
1366 * This helps to reduce the space used for storing the token set.
1367 *
1368 * @constructor
1369 */
1370lunr.TokenSet = function () {
1371  this.final = false
1372  this.edges = {}
1373  this.id = lunr.TokenSet._nextId
1374  lunr.TokenSet._nextId += 1
1375}
1376
1377/**
1378 * Keeps track of the next, auto increment, identifier to assign
1379 * to a new tokenSet.
1380 *
1381 * TokenSets require a unique identifier to be correctly minimised.
1382 *
1383 * @private
1384 */
1385lunr.TokenSet._nextId = 1
1386
1387/**
1388 * Creates a TokenSet instance from the given sorted array of words.
1389 *
1390 * @param {String[]} arr - A sorted array of strings to create the set from.
1391 * @returns {lunr.TokenSet}
1392 * @throws Will throw an error if the input array is not sorted.
1393 */
1394lunr.TokenSet.fromArray = function (arr) {
1395  var builder = new lunr.TokenSet.Builder
1396
1397  for (var i = 0, len = arr.length; i < len; i++) {
1398    builder.insert(arr[i])
1399  }
1400
1401  builder.finish()
1402  return builder.root
1403}
1404
1405/**
1406 * Creates a token set from a query clause.
1407 *
1408 * @private
1409 * @param {Object} clause - A single clause from lunr.Query.
1410 * @param {string} clause.term - The query clause term.
1411 * @param {number} [clause.editDistance] - The optional edit distance for the term.
1412 * @returns {lunr.TokenSet}
1413 */
1414lunr.TokenSet.fromClause = function (clause) {
1415  if ('editDistance' in clause) {
1416    return lunr.TokenSet.fromFuzzyString(clause.term, clause.editDistance)
1417  } else {
1418    return lunr.TokenSet.fromString(clause.term)
1419  }
1420}
1421
1422/**
1423 * Creates a token set representing a single string with a specified
1424 * edit distance.
1425 *
1426 * Insertions, deletions, substitutions and transpositions are each
1427 * treated as an edit distance of 1.
1428 *
1429 * Increasing the allowed edit distance will have a dramatic impact
1430 * on the performance of both creating and intersecting these TokenSets.
1431 * It is advised to keep the edit distance less than 3.
1432 *
1433 * @param {string} str - The string to create the token set from.
1434 * @param {number} editDistance - The allowed edit distance to match.
1435 * @returns {lunr.Vector}
1436 */
1437lunr.TokenSet.fromFuzzyString = function (str, editDistance) {
1438  var root = new lunr.TokenSet
1439
1440  var stack = [{
1441    node: root,
1442    editsRemaining: editDistance,
1443    str: str
1444  }]
1445
1446  while (stack.length) {
1447    var frame = stack.pop()
1448
1449    // no edit
1450    if (frame.str.length > 0) {
1451      var char = frame.str.charAt(0),
1452          noEditNode
1453
1454      if (char in frame.node.edges) {
1455        noEditNode = frame.node.edges[char]
1456      } else {
1457        noEditNode = new lunr.TokenSet
1458        frame.node.edges[char] = noEditNode
1459      }
1460
1461      if (frame.str.length == 1) {
1462        noEditNode.final = true
1463      }
1464
1465      stack.push({
1466        node: noEditNode,
1467        editsRemaining: frame.editsRemaining,
1468        str: frame.str.slice(1)
1469      })
1470    }
1471
1472    if (frame.editsRemaining == 0) {
1473      continue
1474    }
1475
1476    // insertion
1477    if ("*" in frame.node.edges) {
1478      var insertionNode = frame.node.edges["*"]
1479    } else {
1480      var insertionNode = new lunr.TokenSet
1481      frame.node.edges["*"] = insertionNode
1482    }
1483
1484    if (frame.str.length == 0) {
1485      insertionNode.final = true
1486    }
1487
1488    stack.push({
1489      node: insertionNode,
1490      editsRemaining: frame.editsRemaining - 1,
1491      str: frame.str
1492    })
1493
1494    // deletion
1495    // can only do a deletion if we have enough edits remaining
1496    // and if there are characters left to delete in the string
1497    if (frame.str.length > 1) {
1498      stack.push({
1499        node: frame.node,
1500        editsRemaining: frame.editsRemaining - 1,
1501        str: frame.str.slice(1)
1502      })
1503    }
1504
1505    // deletion
1506    // just removing the last character from the str
1507    if (frame.str.length == 1) {
1508      frame.node.final = true
1509    }
1510
1511    // substitution
1512    // can only do a substitution if we have enough edits remaining
1513    // and if there are characters left to substitute
1514    if (frame.str.length >= 1) {
1515      if ("*" in frame.node.edges) {
1516        var substitutionNode = frame.node.edges["*"]
1517      } else {
1518        var substitutionNode = new lunr.TokenSet
1519        frame.node.edges["*"] = substitutionNode
1520      }
1521
1522      if (frame.str.length == 1) {
1523        substitutionNode.final = true
1524      }
1525
1526      stack.push({
1527        node: substitutionNode,
1528        editsRemaining: frame.editsRemaining - 1,
1529        str: frame.str.slice(1)
1530      })
1531    }
1532
1533    // transposition
1534    // can only do a transposition if there are edits remaining
1535    // and there are enough characters to transpose
1536    if (frame.str.length > 1) {
1537      var charA = frame.str.charAt(0),
1538          charB = frame.str.charAt(1),
1539          transposeNode
1540
1541      if (charB in frame.node.edges) {
1542        transposeNode = frame.node.edges[charB]
1543      } else {
1544        transposeNode = new lunr.TokenSet
1545        frame.node.edges[charB] = transposeNode
1546      }
1547
1548      if (frame.str.length == 1) {
1549        transposeNode.final = true
1550      }
1551
1552      stack.push({
1553        node: transposeNode,
1554        editsRemaining: frame.editsRemaining - 1,
1555        str: charA + frame.str.slice(2)
1556      })
1557    }
1558  }
1559
1560  return root
1561}
1562
1563/**
1564 * Creates a TokenSet from a string.
1565 *
1566 * The string may contain one or more wildcard characters (*)
1567 * that will allow wildcard matching when intersecting with
1568 * another TokenSet.
1569 *
1570 * @param {string} str - The string to create a TokenSet from.
1571 * @returns {lunr.TokenSet}
1572 */
1573lunr.TokenSet.fromString = function (str) {
1574  var node = new lunr.TokenSet,
1575      root = node
1576
1577  /*
1578   * Iterates through all characters within the passed string
1579   * appending a node for each character.
1580   *
1581   * When a wildcard character is found then a self
1582   * referencing edge is introduced to continually match
1583   * any number of any characters.
1584   */
1585  for (var i = 0, len = str.length; i < len; i++) {
1586    var char = str[i],
1587        final = (i == len - 1)
1588
1589    if (char == "*") {
1590      node.edges[char] = node
1591      node.final = final
1592
1593    } else {
1594      var next = new lunr.TokenSet
1595      next.final = final
1596
1597      node.edges[char] = next
1598      node = next
1599    }
1600  }
1601
1602  return root
1603}
1604
1605/**
1606 * Converts this TokenSet into an array of strings
1607 * contained within the TokenSet.
1608 *
1609 * This is not intended to be used on a TokenSet that
1610 * contains wildcards, in these cases the results are
1611 * undefined and are likely to cause an infinite loop.
1612 *
1613 * @returns {string[]}
1614 */
1615lunr.TokenSet.prototype.toArray = function () {
1616  var words = []
1617
1618  var stack = [{
1619    prefix: "",
1620    node: this
1621  }]
1622
1623  while (stack.length) {
1624    var frame = stack.pop(),
1625        edges = Object.keys(frame.node.edges),
1626        len = edges.length
1627
1628    if (frame.node.final) {
1629      /* In Safari, at this point the prefix is sometimes corrupted, see:
1630       * https://github.com/olivernn/lunr.js/issues/279 Calling any
1631       * String.prototype method forces Safari to "cast" this string to what
1632       * it's supposed to be, fixing the bug. */
1633      frame.prefix.charAt(0)
1634      words.push(frame.prefix)
1635    }
1636
1637    for (var i = 0; i < len; i++) {
1638      var edge = edges[i]
1639
1640      stack.push({
1641        prefix: frame.prefix.concat(edge),
1642        node: frame.node.edges[edge]
1643      })
1644    }
1645  }
1646
1647  return words
1648}
1649
1650/**
1651 * Generates a string representation of a TokenSet.
1652 *
1653 * This is intended to allow TokenSets to be used as keys
1654 * in objects, largely to aid the construction and minimisation
1655 * of a TokenSet. As such it is not designed to be a human
1656 * friendly representation of the TokenSet.
1657 *
1658 * @returns {string}
1659 */
1660lunr.TokenSet.prototype.toString = function () {
1661  // NOTE: Using Object.keys here as this.edges is very likely
1662  // to enter 'hash-mode' with many keys being added
1663  //
1664  // avoiding a for-in loop here as it leads to the function
1665  // being de-optimised (at least in V8). From some simple
1666  // benchmarks the performance is comparable, but allowing
1667  // V8 to optimize may mean easy performance wins in the future.
1668
1669  if (this._str) {
1670    return this._str
1671  }
1672
1673  var str = this.final ? '1' : '0',
1674      labels = Object.keys(this.edges).sort(),
1675      len = labels.length
1676
1677  for (var i = 0; i < len; i++) {
1678    var label = labels[i],
1679        node = this.edges[label]
1680
1681    str = str + label + node.id
1682  }
1683
1684  return str
1685}
1686
1687/**
1688 * Returns a new TokenSet that is the intersection of
1689 * this TokenSet and the passed TokenSet.
1690 *
1691 * This intersection will take into account any wildcards
1692 * contained within the TokenSet.
1693 *
1694 * @param {lunr.TokenSet} b - An other TokenSet to intersect with.
1695 * @returns {lunr.TokenSet}
1696 */
1697lunr.TokenSet.prototype.intersect = function (b) {
1698  var output = new lunr.TokenSet,
1699      frame = undefined
1700
1701  var stack = [{
1702    qNode: b,
1703    output: output,
1704    node: this
1705  }]
1706
1707  while (stack.length) {
1708    frame = stack.pop()
1709
1710    // NOTE: As with the #toString method, we are using
1711    // Object.keys and a for loop instead of a for-in loop
1712    // as both of these objects enter 'hash' mode, causing
1713    // the function to be de-optimised in V8
1714    var qEdges = Object.keys(frame.qNode.edges),
1715        qLen = qEdges.length,
1716        nEdges = Object.keys(frame.node.edges),
1717        nLen = nEdges.length
1718
1719    for (var q = 0; q < qLen; q++) {
1720      var qEdge = qEdges[q]
1721
1722      for (var n = 0; n < nLen; n++) {
1723        var nEdge = nEdges[n]
1724
1725        if (nEdge == qEdge || qEdge == '*') {
1726          var node = frame.node.edges[nEdge],
1727              qNode = frame.qNode.edges[qEdge],
1728              final = node.final && qNode.final,
1729              next = undefined
1730
1731          if (nEdge in frame.output.edges) {
1732            // an edge already exists for this character
1733            // no need to create a new node, just set the finality
1734            // bit unless this node is already final
1735            next = frame.output.edges[nEdge]
1736            next.final = next.final || final
1737
1738          } else {
1739            // no edge exists yet, must create one
1740            // set the finality bit and insert it
1741            // into the output
1742            next = new lunr.TokenSet
1743            next.final = final
1744            frame.output.edges[nEdge] = next
1745          }
1746
1747          stack.push({
1748            qNode: qNode,
1749            output: next,
1750            node: node
1751          })
1752        }
1753      }
1754    }
1755  }
1756
1757  return output
1758}
1759lunr.TokenSet.Builder = function () {
1760  this.previousWord = ""
1761  this.root = new lunr.TokenSet
1762  this.uncheckedNodes = []
1763  this.minimizedNodes = {}
1764}
1765
1766lunr.TokenSet.Builder.prototype.insert = function (word) {
1767  var node,
1768      commonPrefix = 0
1769
1770  if (word < this.previousWord) {
1771    throw new Error ("Out of order word insertion")
1772  }
1773
1774  for (var i = 0; i < word.length && i < this.previousWord.length; i++) {
1775    if (word[i] != this.previousWord[i]) break
1776    commonPrefix++
1777  }
1778
1779  this.minimize(commonPrefix)
1780
1781  if (this.uncheckedNodes.length == 0) {
1782    node = this.root
1783  } else {
1784    node = this.uncheckedNodes[this.uncheckedNodes.length - 1].child
1785  }
1786
1787  for (var i = commonPrefix; i < word.length; i++) {
1788    var nextNode = new lunr.TokenSet,
1789        char = word[i]
1790
1791    node.edges[char] = nextNode
1792
1793    this.uncheckedNodes.push({
1794      parent: node,
1795      char: char,
1796      child: nextNode
1797    })
1798
1799    node = nextNode
1800  }
1801
1802  node.final = true
1803  this.previousWord = word
1804}
1805
1806lunr.TokenSet.Builder.prototype.finish = function () {
1807  this.minimize(0)
1808}
1809
1810lunr.TokenSet.Builder.prototype.minimize = function (downTo) {
1811  for (var i = this.uncheckedNodes.length - 1; i >= downTo; i--) {
1812    var node = this.uncheckedNodes[i],
1813        childKey = node.child.toString()
1814
1815    if (childKey in this.minimizedNodes) {
1816      node.parent.edges[node.char] = this.minimizedNodes[childKey]
1817    } else {
1818      // Cache the key for this node since
1819      // we know it can't change anymore
1820      node.child._str = childKey
1821
1822      this.minimizedNodes[childKey] = node.child
1823    }
1824
1825    this.uncheckedNodes.pop()
1826  }
1827}
1828/*!
1829 * lunr.Index
1830 * Copyright (C) 2019 Oliver Nightingale
1831 */
1832
1833/**
1834 * An index contains the built index of all documents and provides a query interface
1835 * to the index.
1836 *
1837 * Usually instances of lunr.Index will not be created using this constructor, instead
1838 * lunr.Builder should be used to construct new indexes, or lunr.Index.load should be
1839 * used to load previously built and serialized indexes.
1840 *
1841 * @constructor
1842 * @param {Object} attrs - The attributes of the built search index.
1843 * @param {Object} attrs.invertedIndex - An index of term/field to document reference.
1844 * @param {Object<string, lunr.Vector>} attrs.fieldVectors - Field vectors
1845 * @param {lunr.TokenSet} attrs.tokenSet - An set of all corpus tokens.
1846 * @param {string[]} attrs.fields - The names of indexed document fields.
1847 * @param {lunr.Pipeline} attrs.pipeline - The pipeline to use for search terms.
1848 */
1849lunr.Index = function (attrs) {
1850  this.invertedIndex = attrs.invertedIndex
1851  this.fieldVectors = attrs.fieldVectors
1852  this.tokenSet = attrs.tokenSet
1853  this.fields = attrs.fields
1854  this.pipeline = attrs.pipeline
1855}
1856
1857/**
1858 * A result contains details of a document matching a search query.
1859 * @typedef {Object} lunr.Index~Result
1860 * @property {string} ref - The reference of the document this result represents.
1861 * @property {number} score - A number between 0 and 1 representing how similar this document is to the query.
1862 * @property {lunr.MatchData} matchData - Contains metadata about this match including which term(s) caused the match.
1863 */
1864
1865/**
1866 * Although lunr provides the ability to create queries using lunr.Query, it also provides a simple
1867 * query language which itself is parsed into an instance of lunr.Query.
1868 *
1869 * For programmatically building queries it is advised to directly use lunr.Query, the query language
1870 * is best used for human entered text rather than program generated text.
1871 *
1872 * At its simplest queries can just be a single term, e.g. `hello`, multiple terms are also supported
1873 * and will be combined with OR, e.g `hello world` will match documents that contain either 'hello'
1874 * or 'world', though those that contain both will rank higher in the results.
1875 *
1876 * Wildcards can be included in terms to match one or more unspecified characters, these wildcards can
1877 * be inserted anywhere within the term, and more than one wildcard can exist in a single term. Adding
1878 * wildcards will increase the number of documents that will be found but can also have a negative
1879 * impact on query performance, especially with wildcards at the beginning of a term.
1880 *
1881 * Terms can be restricted to specific fields, e.g. `title:hello`, only documents with the term
1882 * hello in the title field will match this query. Using a field not present in the index will lead
1883 * to an error being thrown.
1884 *
1885 * Modifiers can also be added to terms, lunr supports edit distance and boost modifiers on terms. A term
1886 * boost will make documents matching that term score higher, e.g. `foo^5`. Edit distance is also supported
1887 * to provide fuzzy matching, e.g. 'hello~2' will match documents with hello with an edit distance of 2.
1888 * Avoid large values for edit distance to improve query performance.
1889 *
1890 * Each term also supports a presence modifier. By default a term's presence in document is optional, however
1891 * this can be changed to either required or prohibited. For a term's presence to be required in a document the
1892 * term should be prefixed with a '+', e.g. `+foo bar` is a search for documents that must contain 'foo' and
1893 * optionally contain 'bar'. Conversely a leading '-' sets the terms presence to prohibited, i.e. it must not
1894 * appear in a document, e.g. `-foo bar` is a search for documents that do not contain 'foo' but may contain 'bar'.
1895 *
1896 * To escape special characters the backslash character '\' can be used, this allows searches to include
1897 * characters that would normally be considered modifiers, e.g. `foo\~2` will search for a term "foo~2" instead
1898 * of attempting to apply a boost of 2 to the search term "foo".
1899 *
1900 * @typedef {string} lunr.Index~QueryString
1901 * @example <caption>Simple single term query</caption>
1902 * hello
1903 * @example <caption>Multiple term query</caption>
1904 * hello world
1905 * @example <caption>term scoped to a field</caption>
1906 * title:hello
1907 * @example <caption>term with a boost of 10</caption>
1908 * hello^10
1909 * @example <caption>term with an edit distance of 2</caption>
1910 * hello~2
1911 * @example <caption>terms with presence modifiers</caption>
1912 * -foo +bar baz
1913 */
1914
1915/**
1916 * Performs a search against the index using lunr query syntax.
1917 *
1918 * Results will be returned sorted by their score, the most relevant results
1919 * will be returned first.  For details on how the score is calculated, please see
1920 * the {@link https://lunrjs.com/guides/searching.html#scoring|guide}.
1921 *
1922 * For more programmatic querying use lunr.Index#query.
1923 *
1924 * @param {lunr.Index~QueryString} queryString - A string containing a lunr query.
1925 * @throws {lunr.QueryParseError} If the passed query string cannot be parsed.
1926 * @returns {lunr.Index~Result[]}
1927 */
1928lunr.Index.prototype.search = function (queryString) {
1929  return this.query(function (query) {
1930    var parser = new lunr.QueryParser(queryString, query)
1931    parser.parse()
1932  })
1933}
1934
1935/**
1936 * A query builder callback provides a query object to be used to express
1937 * the query to perform on the index.
1938 *
1939 * @callback lunr.Index~queryBuilder
1940 * @param {lunr.Query} query - The query object to build up.
1941 * @this lunr.Query
1942 */
1943
1944/**
1945 * Performs a query against the index using the yielded lunr.Query object.
1946 *
1947 * If performing programmatic queries against the index, this method is preferred
1948 * over lunr.Index#search so as to avoid the additional query parsing overhead.
1949 *
1950 * A query object is yielded to the supplied function which should be used to
1951 * express the query to be run against the index.
1952 *
1953 * Note that although this function takes a callback parameter it is _not_ an
1954 * asynchronous operation, the callback is just yielded a query object to be
1955 * customized.
1956 *
1957 * @param {lunr.Index~queryBuilder} fn - A function that is used to build the query.
1958 * @returns {lunr.Index~Result[]}
1959 */
1960lunr.Index.prototype.query = function (fn) {
1961  // for each query clause
1962  // * process terms
1963  // * expand terms from token set
1964  // * find matching documents and metadata
1965  // * get document vectors
1966  // * score documents
1967
1968  var query = new lunr.Query(this.fields),
1969      matchingFields = Object.create(null),
1970      queryVectors = Object.create(null),
1971      termFieldCache = Object.create(null),
1972      requiredMatches = Object.create(null),
1973      prohibitedMatches = Object.create(null)
1974
1975  /*
1976   * To support field level boosts a query vector is created per
1977   * field. An empty vector is eagerly created to support negated
1978   * queries.
1979   */
1980  for (var i = 0; i < this.fields.length; i++) {
1981    queryVectors[this.fields[i]] = new lunr.Vector
1982  }
1983
1984  fn.call(query, query)
1985
1986  for (var i = 0; i < query.clauses.length; i++) {
1987    /*
1988     * Unless the pipeline has been disabled for this term, which is
1989     * the case for terms with wildcards, we need to pass the clause
1990     * term through the search pipeline. A pipeline returns an array
1991     * of processed terms. Pipeline functions may expand the passed
1992     * term, which means we may end up performing multiple index lookups
1993     * for a single query term.
1994     */
1995    var clause = query.clauses[i],
1996        terms = null,
1997        clauseMatches = lunr.Set.complete
1998
1999    if (clause.usePipeline) {
2000      terms = this.pipeline.runString(clause.term, {
2001        fields: clause.fields
2002      })
2003    } else {
2004      terms = [clause.term]
2005    }
2006
2007    for (var m = 0; m < terms.length; m++) {
2008      var term = terms[m]
2009
2010      /*
2011       * Each term returned from the pipeline needs to use the same query
2012       * clause object, e.g. the same boost and or edit distance. The
2013       * simplest way to do this is to re-use the clause object but mutate
2014       * its term property.
2015       */
2016      clause.term = term
2017
2018      /*
2019       * From the term in the clause we create a token set which will then
2020       * be used to intersect the indexes token set to get a list of terms
2021       * to lookup in the inverted index
2022       */
2023      var termTokenSet = lunr.TokenSet.fromClause(clause),
2024          expandedTerms = this.tokenSet.intersect(termTokenSet).toArray()
2025
2026      /*
2027       * If a term marked as required does not exist in the tokenSet it is
2028       * impossible for the search to return any matches. We set all the field
2029       * scoped required matches set to empty and stop examining any further
2030       * clauses.
2031       */
2032      if (expandedTerms.length === 0 && clause.presence === lunr.Query.presence.REQUIRED) {
2033        for (var k = 0; k < clause.fields.length; k++) {
2034          var field = clause.fields[k]
2035          requiredMatches[field] = lunr.Set.empty
2036        }
2037
2038        break
2039      }
2040
2041      for (var j = 0; j < expandedTerms.length; j++) {
2042        /*
2043         * For each term get the posting and termIndex, this is required for
2044         * building the query vector.
2045         */
2046        var expandedTerm = expandedTerms[j],
2047            posting = this.invertedIndex[expandedTerm],
2048            termIndex = posting._index
2049
2050        for (var k = 0; k < clause.fields.length; k++) {
2051          /*
2052           * For each field that this query term is scoped by (by default
2053           * all fields are in scope) we need to get all the document refs
2054           * that have this term in that field.
2055           *
2056           * The posting is the entry in the invertedIndex for the matching
2057           * term from above.
2058           */
2059          var field = clause.fields[k],
2060              fieldPosting = posting[field],
2061              matchingDocumentRefs = Object.keys(fieldPosting),
2062              termField = expandedTerm + "/" + field,
2063              matchingDocumentsSet = new lunr.Set(matchingDocumentRefs)
2064
2065          /*
2066           * if the presence of this term is required ensure that the matching
2067           * documents are added to the set of required matches for this clause.
2068           *
2069           */
2070          if (clause.presence == lunr.Query.presence.REQUIRED) {
2071            clauseMatches = clauseMatches.union(matchingDocumentsSet)
2072
2073            if (requiredMatches[field] === undefined) {
2074              requiredMatches[field] = lunr.Set.complete
2075            }
2076          }
2077
2078          /*
2079           * if the presence of this term is prohibited ensure that the matching
2080           * documents are added to the set of prohibited matches for this field,
2081           * creating that set if it does not yet exist.
2082           */
2083          if (clause.presence == lunr.Query.presence.PROHIBITED) {
2084            if (prohibitedMatches[field] === undefined) {
2085              prohibitedMatches[field] = lunr.Set.empty
2086            }
2087
2088            prohibitedMatches[field] = prohibitedMatches[field].union(matchingDocumentsSet)
2089
2090            /*
2091             * Prohibited matches should not be part of the query vector used for
2092             * similarity scoring and no metadata should be extracted so we continue
2093             * to the next field
2094             */
2095            continue
2096          }
2097
2098          /*
2099           * The query field vector is populated using the termIndex found for
2100           * the term and a unit value with the appropriate boost applied.
2101           * Using upsert because there could already be an entry in the vector
2102           * for the term we are working with. In that case we just add the scores
2103           * together.
2104           */
2105          queryVectors[field].upsert(termIndex, clause.boost, function (a, b) { return a + b })
2106
2107          /**
2108           * If we've already seen this term, field combo then we've already collected
2109           * the matching documents and metadata, no need to go through all that again
2110           */
2111          if (termFieldCache[termField]) {
2112            continue
2113          }
2114
2115          for (var l = 0; l < matchingDocumentRefs.length; l++) {
2116            /*
2117             * All metadata for this term/field/document triple
2118             * are then extracted and collected into an instance
2119             * of lunr.MatchData ready to be returned in the query
2120             * results
2121             */
2122            var matchingDocumentRef = matchingDocumentRefs[l],
2123                matchingFieldRef = new lunr.FieldRef (matchingDocumentRef, field),
2124                metadata = fieldPosting[matchingDocumentRef],
2125                fieldMatch
2126
2127            if ((fieldMatch = matchingFields[matchingFieldRef]) === undefined) {
2128              matchingFields[matchingFieldRef] = new lunr.MatchData (expandedTerm, field, metadata)
2129            } else {
2130              fieldMatch.add(expandedTerm, field, metadata)
2131            }
2132
2133          }
2134
2135          termFieldCache[termField] = true
2136        }
2137      }
2138    }
2139
2140    /**
2141     * If the presence was required we need to update the requiredMatches field sets.
2142     * We do this after all fields for the term have collected their matches because
2143     * the clause terms presence is required in _any_ of the fields not _all_ of the
2144     * fields.
2145     */
2146    if (clause.presence === lunr.Query.presence.REQUIRED) {
2147      for (var k = 0; k < clause.fields.length; k++) {
2148        var field = clause.fields[k]
2149        requiredMatches[field] = requiredMatches[field].intersect(clauseMatches)
2150      }
2151    }
2152  }
2153
2154  /**
2155   * Need to combine the field scoped required and prohibited
2156   * matching documents into a global set of required and prohibited
2157   * matches
2158   */
2159  var allRequiredMatches = lunr.Set.complete,
2160      allProhibitedMatches = lunr.Set.empty
2161
2162  for (var i = 0; i < this.fields.length; i++) {
2163    var field = this.fields[i]
2164
2165    if (requiredMatches[field]) {
2166      allRequiredMatches = allRequiredMatches.intersect(requiredMatches[field])
2167    }
2168
2169    if (prohibitedMatches[field]) {
2170      allProhibitedMatches = allProhibitedMatches.union(prohibitedMatches[field])
2171    }
2172  }
2173
2174  var matchingFieldRefs = Object.keys(matchingFields),
2175      results = [],
2176      matches = Object.create(null)
2177
2178  /*
2179   * If the query is negated (contains only prohibited terms)
2180   * we need to get _all_ fieldRefs currently existing in the
2181   * index. This is only done when we know that the query is
2182   * entirely prohibited terms to avoid any cost of getting all
2183   * fieldRefs unnecessarily.
2184   *
2185   * Additionally, blank MatchData must be created to correctly
2186   * populate the results.
2187   */
2188  if (query.isNegated()) {
2189    matchingFieldRefs = Object.keys(this.fieldVectors)
2190
2191    for (var i = 0; i < matchingFieldRefs.length; i++) {
2192      var matchingFieldRef = matchingFieldRefs[i]
2193      var fieldRef = lunr.FieldRef.fromString(matchingFieldRef)
2194      matchingFields[matchingFieldRef] = new lunr.MatchData
2195    }
2196  }
2197
2198  for (var i = 0; i < matchingFieldRefs.length; i++) {
2199    /*
2200     * Currently we have document fields that match the query, but we
2201     * need to return documents. The matchData and scores are combined
2202     * from multiple fields belonging to the same document.
2203     *
2204     * Scores are calculated by field, using the query vectors created
2205     * above, and combined into a final document score using addition.
2206     */
2207    var fieldRef = lunr.FieldRef.fromString(matchingFieldRefs[i]),
2208        docRef = fieldRef.docRef
2209
2210    if (!allRequiredMatches.contains(docRef)) {
2211      continue
2212    }
2213
2214    if (allProhibitedMatches.contains(docRef)) {
2215      continue
2216    }
2217
2218    var fieldVector = this.fieldVectors[fieldRef],
2219        score = queryVectors[fieldRef.fieldName].similarity(fieldVector),
2220        docMatch
2221
2222    if ((docMatch = matches[docRef]) !== undefined) {
2223      docMatch.score += score
2224      docMatch.matchData.combine(matchingFields[fieldRef])
2225    } else {
2226      var match = {
2227        ref: docRef,
2228        score: score,
2229        matchData: matchingFields[fieldRef]
2230      }
2231      matches[docRef] = match
2232      results.push(match)
2233    }
2234  }
2235
2236  /*
2237   * Sort the results objects by score, highest first.
2238   */
2239  return results.sort(function (a, b) {
2240    return b.score - a.score
2241  })
2242}
2243
2244/**
2245 * Prepares the index for JSON serialization.
2246 *
2247 * The schema for this JSON blob will be described in a
2248 * separate JSON schema file.
2249 *
2250 * @returns {Object}
2251 */
2252lunr.Index.prototype.toJSON = function () {
2253  var invertedIndex = Object.keys(this.invertedIndex)
2254    .sort()
2255    .map(function (term) {
2256      return [term, this.invertedIndex[term]]
2257    }, this)
2258
2259  var fieldVectors = Object.keys(this.fieldVectors)
2260    .map(function (ref) {
2261      return [ref, this.fieldVectors[ref].toJSON()]
2262    }, this)
2263
2264  return {
2265    version: lunr.version,
2266    fields: this.fields,
2267    fieldVectors: fieldVectors,
2268    invertedIndex: invertedIndex,
2269    pipeline: this.pipeline.toJSON()
2270  }
2271}
2272
2273/**
2274 * Loads a previously serialized lunr.Index
2275 *
2276 * @param {Object} serializedIndex - A previously serialized lunr.Index
2277 * @returns {lunr.Index}
2278 */
2279lunr.Index.load = function (serializedIndex) {
2280  var attrs = {},
2281      fieldVectors = {},
2282      serializedVectors = serializedIndex.fieldVectors,
2283      invertedIndex = Object.create(null),
2284      serializedInvertedIndex = serializedIndex.invertedIndex,
2285      tokenSetBuilder = new lunr.TokenSet.Builder,
2286      pipeline = lunr.Pipeline.load(serializedIndex.pipeline)
2287
2288  if (serializedIndex.version != lunr.version) {
2289    lunr.utils.warn("Version mismatch when loading serialised index. Current version of lunr '" + lunr.version + "' does not match serialized index '" + serializedIndex.version + "'")
2290  }
2291
2292  for (var i = 0; i < serializedVectors.length; i++) {
2293    var tuple = serializedVectors[i],
2294        ref = tuple[0],
2295        elements = tuple[1]
2296
2297    fieldVectors[ref] = new lunr.Vector(elements)
2298  }
2299
2300  for (var i = 0; i < serializedInvertedIndex.length; i++) {
2301    var tuple = serializedInvertedIndex[i],
2302        term = tuple[0],
2303        posting = tuple[1]
2304
2305    tokenSetBuilder.insert(term)
2306    invertedIndex[term] = posting
2307  }
2308
2309  tokenSetBuilder.finish()
2310
2311  attrs.fields = serializedIndex.fields
2312
2313  attrs.fieldVectors = fieldVectors
2314  attrs.invertedIndex = invertedIndex
2315  attrs.tokenSet = tokenSetBuilder.root
2316  attrs.pipeline = pipeline
2317
2318  return new lunr.Index(attrs)
2319}
2320/*!
2321 * lunr.Builder
2322 * Copyright (C) 2019 Oliver Nightingale
2323 */
2324
2325/**
2326 * lunr.Builder performs indexing on a set of documents and
2327 * returns instances of lunr.Index ready for querying.
2328 *
2329 * All configuration of the index is done via the builder, the
2330 * fields to index, the document reference, the text processing
2331 * pipeline and document scoring parameters are all set on the
2332 * builder before indexing.
2333 *
2334 * @constructor
2335 * @property {string} _ref - Internal reference to the document reference field.
2336 * @property {string[]} _fields - Internal reference to the document fields to index.
2337 * @property {object} invertedIndex - The inverted index maps terms to document fields.
2338 * @property {object} documentTermFrequencies - Keeps track of document term frequencies.
2339 * @property {object} documentLengths - Keeps track of the length of documents added to the index.
2340 * @property {lunr.tokenizer} tokenizer - Function for splitting strings into tokens for indexing.
2341 * @property {lunr.Pipeline} pipeline - The pipeline performs text processing on tokens before indexing.
2342 * @property {lunr.Pipeline} searchPipeline - A pipeline for processing search terms before querying the index.
2343 * @property {number} documentCount - Keeps track of the total number of documents indexed.
2344 * @property {number} _b - A parameter to control field length normalization, setting this to 0 disabled normalization, 1 fully normalizes field lengths, the default value is 0.75.
2345 * @property {number} _k1 - A parameter to control how quickly an increase in term frequency results in term frequency saturation, the default value is 1.2.
2346 * @property {number} termIndex - A counter incremented for each unique term, used to identify a terms position in the vector space.
2347 * @property {array} metadataWhitelist - A list of metadata keys that have been whitelisted for entry in the index.
2348 */
2349lunr.Builder = function () {
2350  this._ref = "id"
2351  this._fields = Object.create(null)
2352  this._documents = Object.create(null)
2353  this.invertedIndex = Object.create(null)
2354  this.fieldTermFrequencies = {}
2355  this.fieldLengths = {}
2356  this.tokenizer = lunr.tokenizer
2357  this.pipeline = new lunr.Pipeline
2358  this.searchPipeline = new lunr.Pipeline
2359  this.documentCount = 0
2360  this._b = 0.75
2361  this._k1 = 1.2
2362  this.termIndex = 0
2363  this.metadataWhitelist = []
2364}
2365
2366/**
2367 * Sets the document field used as the document reference. Every document must have this field.
2368 * The type of this field in the document should be a string, if it is not a string it will be
2369 * coerced into a string by calling toString.
2370 *
2371 * The default ref is 'id'.
2372 *
2373 * The ref should _not_ be changed during indexing, it should be set before any documents are
2374 * added to the index. Changing it during indexing can lead to inconsistent results.
2375 *
2376 * @param {string} ref - The name of the reference field in the document.
2377 */
2378lunr.Builder.prototype.ref = function (ref) {
2379  this._ref = ref
2380}
2381
2382/**
2383 * A function that is used to extract a field from a document.
2384 *
2385 * Lunr expects a field to be at the top level of a document, if however the field
2386 * is deeply nested within a document an extractor function can be used to extract
2387 * the right field for indexing.
2388 *
2389 * @callback fieldExtractor
2390 * @param {object} doc - The document being added to the index.
2391 * @returns {?(string|object|object[])} obj - The object that will be indexed for this field.
2392 * @example <caption>Extracting a nested field</caption>
2393 * function (doc) { return doc.nested.field }
2394 */
2395
2396/**
2397 * Adds a field to the list of document fields that will be indexed. Every document being
2398 * indexed should have this field. Null values for this field in indexed documents will
2399 * not cause errors but will limit the chance of that document being retrieved by searches.
2400 *
2401 * All fields should be added before adding documents to the index. Adding fields after
2402 * a document has been indexed will have no effect on already indexed documents.
2403 *
2404 * Fields can be boosted at build time. This allows terms within that field to have more
2405 * importance when ranking search results. Use a field boost to specify that matches within
2406 * one field are more important than other fields.
2407 *
2408 * @param {string} fieldName - The name of a field to index in all documents.
2409 * @param {object} attributes - Optional attributes associated with this field.
2410 * @param {number} [attributes.boost=1] - Boost applied to all terms within this field.
2411 * @param {fieldExtractor} [attributes.extractor] - Function to extract a field from a document.
2412 * @throws {RangeError} fieldName cannot contain unsupported characters '/'
2413 */
2414lunr.Builder.prototype.field = function (fieldName, attributes) {
2415  if (/\//.test(fieldName)) {
2416    throw new RangeError ("Field '" + fieldName + "' contains illegal character '/'")
2417  }
2418
2419  this._fields[fieldName] = attributes || {}
2420}
2421
2422/**
2423 * A parameter to tune the amount of field length normalisation that is applied when
2424 * calculating relevance scores. A value of 0 will completely disable any normalisation
2425 * and a value of 1 will fully normalise field lengths. The default is 0.75. Values of b
2426 * will be clamped to the range 0 - 1.
2427 *
2428 * @param {number} number - The value to set for this tuning parameter.
2429 */
2430lunr.Builder.prototype.b = function (number) {
2431  if (number < 0) {
2432    this._b = 0
2433  } else if (number > 1) {
2434    this._b = 1
2435  } else {
2436    this._b = number
2437  }
2438}
2439
2440/**
2441 * A parameter that controls the speed at which a rise in term frequency results in term
2442 * frequency saturation. The default value is 1.2. Setting this to a higher value will give
2443 * slower saturation levels, a lower value will result in quicker saturation.
2444 *
2445 * @param {number} number - The value to set for this tuning parameter.
2446 */
2447lunr.Builder.prototype.k1 = function (number) {
2448  this._k1 = number
2449}
2450
2451/**
2452 * Adds a document to the index.
2453 *
2454 * Before adding fields to the index the index should have been fully setup, with the document
2455 * ref and all fields to index already having been specified.
2456 *
2457 * The document must have a field name as specified by the ref (by default this is 'id') and
2458 * it should have all fields defined for indexing, though null or undefined values will not
2459 * cause errors.
2460 *
2461 * Entire documents can be boosted at build time. Applying a boost to a document indicates that
2462 * this document should rank higher in search results than other documents.
2463 *
2464 * @param {object} doc - The document to add to the index.
2465 * @param {object} attributes - Optional attributes associated with this document.
2466 * @param {number} [attributes.boost=1] - Boost applied to all terms within this document.
2467 */
2468lunr.Builder.prototype.add = function (doc, attributes) {
2469  var docRef = doc[this._ref],
2470      fields = Object.keys(this._fields)
2471
2472  this._documents[docRef] = attributes || {}
2473  this.documentCount += 1
2474
2475  for (var i = 0; i < fields.length; i++) {
2476    var fieldName = fields[i],
2477        extractor = this._fields[fieldName].extractor,
2478        field = extractor ? extractor(doc) : doc[fieldName],
2479        tokens = this.tokenizer(field, {
2480          fields: [fieldName]
2481        }),
2482        terms = this.pipeline.run(tokens),
2483        fieldRef = new lunr.FieldRef (docRef, fieldName),
2484        fieldTerms = Object.create(null)
2485
2486    this.fieldTermFrequencies[fieldRef] = fieldTerms
2487    this.fieldLengths[fieldRef] = 0
2488
2489    // store the length of this field for this document
2490    this.fieldLengths[fieldRef] += terms.length
2491
2492    // calculate term frequencies for this field
2493    for (var j = 0; j < terms.length; j++) {
2494      var term = terms[j]
2495
2496      if (fieldTerms[term] == undefined) {
2497        fieldTerms[term] = 0
2498      }
2499
2500      fieldTerms[term] += 1
2501
2502      // add to inverted index
2503      // create an initial posting if one doesn't exist
2504      if (this.invertedIndex[term] == undefined) {
2505        var posting = Object.create(null)
2506        posting["_index"] = this.termIndex
2507        this.termIndex += 1
2508
2509        for (var k = 0; k < fields.length; k++) {
2510          posting[fields[k]] = Object.create(null)
2511        }
2512
2513        this.invertedIndex[term] = posting
2514      }
2515
2516      // add an entry for this term/fieldName/docRef to the invertedIndex
2517      if (this.invertedIndex[term][fieldName][docRef] == undefined) {
2518        this.invertedIndex[term][fieldName][docRef] = Object.create(null)
2519      }
2520
2521      // store all whitelisted metadata about this token in the
2522      // inverted index
2523      for (var l = 0; l < this.metadataWhitelist.length; l++) {
2524        var metadataKey = this.metadataWhitelist[l],
2525            metadata = term.metadata[metadataKey]
2526
2527        if (this.invertedIndex[term][fieldName][docRef][metadataKey] == undefined) {
2528          this.invertedIndex[term][fieldName][docRef][metadataKey] = []
2529        }
2530
2531        this.invertedIndex[term][fieldName][docRef][metadataKey].push(metadata)
2532      }
2533    }
2534
2535  }
2536}
2537
2538/**
2539 * Calculates the average document length for this index
2540 *
2541 * @private
2542 */
2543lunr.Builder.prototype.calculateAverageFieldLengths = function () {
2544
2545  var fieldRefs = Object.keys(this.fieldLengths),
2546      numberOfFields = fieldRefs.length,
2547      accumulator = {},
2548      documentsWithField = {}
2549
2550  for (var i = 0; i < numberOfFields; i++) {
2551    var fieldRef = lunr.FieldRef.fromString(fieldRefs[i]),
2552        field = fieldRef.fieldName
2553
2554    documentsWithField[field] || (documentsWithField[field] = 0)
2555    documentsWithField[field] += 1
2556
2557    accumulator[field] || (accumulator[field] = 0)
2558    accumulator[field] += this.fieldLengths[fieldRef]
2559  }
2560
2561  var fields = Object.keys(this._fields)
2562
2563  for (var i = 0; i < fields.length; i++) {
2564    var fieldName = fields[i]
2565    accumulator[fieldName] = accumulator[fieldName] / documentsWithField[fieldName]
2566  }
2567
2568  this.averageFieldLength = accumulator
2569}
2570
2571/**
2572 * Builds a vector space model of every document using lunr.Vector
2573 *
2574 * @private
2575 */
2576lunr.Builder.prototype.createFieldVectors = function () {
2577  var fieldVectors = {},
2578      fieldRefs = Object.keys(this.fieldTermFrequencies),
2579      fieldRefsLength = fieldRefs.length,
2580      termIdfCache = Object.create(null)
2581
2582  for (var i = 0; i < fieldRefsLength; i++) {
2583    var fieldRef = lunr.FieldRef.fromString(fieldRefs[i]),
2584        fieldName = fieldRef.fieldName,
2585        fieldLength = this.fieldLengths[fieldRef],
2586        fieldVector = new lunr.Vector,
2587        termFrequencies = this.fieldTermFrequencies[fieldRef],
2588        terms = Object.keys(termFrequencies),
2589        termsLength = terms.length
2590
2591
2592    var fieldBoost = this._fields[fieldName].boost || 1,
2593        docBoost = this._documents[fieldRef.docRef].boost || 1
2594
2595    for (var j = 0; j < termsLength; j++) {
2596      var term = terms[j],
2597          tf = termFrequencies[term],
2598          termIndex = this.invertedIndex[term]._index,
2599          idf, score, scoreWithPrecision
2600
2601      if (termIdfCache[term] === undefined) {
2602        idf = lunr.idf(this.invertedIndex[term], this.documentCount)
2603        termIdfCache[term] = idf
2604      } else {
2605        idf = termIdfCache[term]
2606      }
2607
2608      score = idf * ((this._k1 + 1) * tf) / (this._k1 * (1 - this._b + this._b * (fieldLength / this.averageFieldLength[fieldName])) + tf)
2609      score *= fieldBoost
2610      score *= docBoost
2611      scoreWithPrecision = Math.round(score * 1000) / 1000
2612      // Converts 1.23456789 to 1.234.
2613      // Reducing the precision so that the vectors take up less
2614      // space when serialised. Doing it now so that they behave
2615      // the same before and after serialisation. Also, this is
2616      // the fastest approach to reducing a number's precision in
2617      // JavaScript.
2618
2619      fieldVector.insert(termIndex, scoreWithPrecision)
2620    }
2621
2622    fieldVectors[fieldRef] = fieldVector
2623  }
2624
2625  this.fieldVectors = fieldVectors
2626}
2627
2628/**
2629 * Creates a token set of all tokens in the index using lunr.TokenSet
2630 *
2631 * @private
2632 */
2633lunr.Builder.prototype.createTokenSet = function () {
2634  this.tokenSet = lunr.TokenSet.fromArray(
2635    Object.keys(this.invertedIndex).sort()
2636  )
2637}
2638
2639/**
2640 * Builds the index, creating an instance of lunr.Index.
2641 *
2642 * This completes the indexing process and should only be called
2643 * once all documents have been added to the index.
2644 *
2645 * @returns {lunr.Index}
2646 */
2647lunr.Builder.prototype.build = function () {
2648  this.calculateAverageFieldLengths()
2649  this.createFieldVectors()
2650  this.createTokenSet()
2651
2652  return new lunr.Index({
2653    invertedIndex: this.invertedIndex,
2654    fieldVectors: this.fieldVectors,
2655    tokenSet: this.tokenSet,
2656    fields: Object.keys(this._fields),
2657    pipeline: this.searchPipeline
2658  })
2659}
2660
2661/**
2662 * Applies a plugin to the index builder.
2663 *
2664 * A plugin is a function that is called with the index builder as its context.
2665 * Plugins can be used to customise or extend the behaviour of the index
2666 * in some way. A plugin is just a function, that encapsulated the custom
2667 * behaviour that should be applied when building the index.
2668 *
2669 * The plugin function will be called with the index builder as its argument, additional
2670 * arguments can also be passed when calling use. The function will be called
2671 * with the index builder as its context.
2672 *
2673 * @param {Function} plugin The plugin to apply.
2674 */
2675lunr.Builder.prototype.use = function (fn) {
2676  var args = Array.prototype.slice.call(arguments, 1)
2677  args.unshift(this)
2678  fn.apply(this, args)
2679}
2680/**
2681 * Contains and collects metadata about a matching document.
2682 * A single instance of lunr.MatchData is returned as part of every
2683 * lunr.Index~Result.
2684 *
2685 * @constructor
2686 * @param {string} term - The term this match data is associated with
2687 * @param {string} field - The field in which the term was found
2688 * @param {object} metadata - The metadata recorded about this term in this field
2689 * @property {object} metadata - A cloned collection of metadata associated with this document.
2690 * @see {@link lunr.Index~Result}
2691 */
2692lunr.MatchData = function (term, field, metadata) {
2693  var clonedMetadata = Object.create(null),
2694      metadataKeys = Object.keys(metadata || {})
2695
2696  // Cloning the metadata to prevent the original
2697  // being mutated during match data combination.
2698  // Metadata is kept in an array within the inverted
2699  // index so cloning the data can be done with
2700  // Array#slice
2701  for (var i = 0; i < metadataKeys.length; i++) {
2702    var key = metadataKeys[i]
2703    clonedMetadata[key] = metadata[key].slice()
2704  }
2705
2706  this.metadata = Object.create(null)
2707
2708  if (term !== undefined) {
2709    this.metadata[term] = Object.create(null)
2710    this.metadata[term][field] = clonedMetadata
2711  }
2712}
2713
2714/**
2715 * An instance of lunr.MatchData will be created for every term that matches a
2716 * document. However only one instance is required in a lunr.Index~Result. This
2717 * method combines metadata from another instance of lunr.MatchData with this
2718 * objects metadata.
2719 *
2720 * @param {lunr.MatchData} otherMatchData - Another instance of match data to merge with this one.
2721 * @see {@link lunr.Index~Result}
2722 */
2723lunr.MatchData.prototype.combine = function (otherMatchData) {
2724  var terms = Object.keys(otherMatchData.metadata)
2725
2726  for (var i = 0; i < terms.length; i++) {
2727    var term = terms[i],
2728        fields = Object.keys(otherMatchData.metadata[term])
2729
2730    if (this.metadata[term] == undefined) {
2731      this.metadata[term] = Object.create(null)
2732    }
2733
2734    for (var j = 0; j < fields.length; j++) {
2735      var field = fields[j],
2736          keys = Object.keys(otherMatchData.metadata[term][field])
2737
2738      if (this.metadata[term][field] == undefined) {
2739        this.metadata[term][field] = Object.create(null)
2740      }
2741
2742      for (var k = 0; k < keys.length; k++) {
2743        var key = keys[k]
2744
2745        if (this.metadata[term][field][key] == undefined) {
2746          this.metadata[term][field][key] = otherMatchData.metadata[term][field][key]
2747        } else {
2748          this.metadata[term][field][key] = this.metadata[term][field][key].concat(otherMatchData.metadata[term][field][key])
2749        }
2750
2751      }
2752    }
2753  }
2754}
2755
2756/**
2757 * Add metadata for a term/field pair to this instance of match data.
2758 *
2759 * @param {string} term - The term this match data is associated with
2760 * @param {string} field - The field in which the term was found
2761 * @param {object} metadata - The metadata recorded about this term in this field
2762 */
2763lunr.MatchData.prototype.add = function (term, field, metadata) {
2764  if (!(term in this.metadata)) {
2765    this.metadata[term] = Object.create(null)
2766    this.metadata[term][field] = metadata
2767    return
2768  }
2769
2770  if (!(field in this.metadata[term])) {
2771    this.metadata[term][field] = metadata
2772    return
2773  }
2774
2775  var metadataKeys = Object.keys(metadata)
2776
2777  for (var i = 0; i < metadataKeys.length; i++) {
2778    var key = metadataKeys[i]
2779
2780    if (key in this.metadata[term][field]) {
2781      this.metadata[term][field][key] = this.metadata[term][field][key].concat(metadata[key])
2782    } else {
2783      this.metadata[term][field][key] = metadata[key]
2784    }
2785  }
2786}
2787/**
2788 * A lunr.Query provides a programmatic way of defining queries to be performed
2789 * against a {@link lunr.Index}.
2790 *
2791 * Prefer constructing a lunr.Query using the {@link lunr.Index#query} method
2792 * so the query object is pre-initialized with the right index fields.
2793 *
2794 * @constructor
2795 * @property {lunr.Query~Clause[]} clauses - An array of query clauses.
2796 * @property {string[]} allFields - An array of all available fields in a lunr.Index.
2797 */
2798lunr.Query = function (allFields) {
2799  this.clauses = []
2800  this.allFields = allFields
2801}
2802
2803/**
2804 * Constants for indicating what kind of automatic wildcard insertion will be used when constructing a query clause.
2805 *
2806 * This allows wildcards to be added to the beginning and end of a term without having to manually do any string
2807 * concatenation.
2808 *
2809 * The wildcard constants can be bitwise combined to select both leading and trailing wildcards.
2810 *
2811 * @constant
2812 * @default
2813 * @property {number} wildcard.NONE - The term will have no wildcards inserted, this is the default behaviour
2814 * @property {number} wildcard.LEADING - Prepend the term with a wildcard, unless a leading wildcard already exists
2815 * @property {number} wildcard.TRAILING - Append a wildcard to the term, unless a trailing wildcard already exists
2816 * @see lunr.Query~Clause
2817 * @see lunr.Query#clause
2818 * @see lunr.Query#term
2819 * @example <caption>query term with trailing wildcard</caption>
2820 * query.term('foo', { wildcard: lunr.Query.wildcard.TRAILING })
2821 * @example <caption>query term with leading and trailing wildcard</caption>
2822 * query.term('foo', {
2823 *   wildcard: lunr.Query.wildcard.LEADING | lunr.Query.wildcard.TRAILING
2824 * })
2825 */
2826
2827lunr.Query.wildcard = new String ("*")
2828lunr.Query.wildcard.NONE = 0
2829lunr.Query.wildcard.LEADING = 1
2830lunr.Query.wildcard.TRAILING = 2
2831
2832/**
2833 * Constants for indicating what kind of presence a term must have in matching documents.
2834 *
2835 * @constant
2836 * @enum {number}
2837 * @see lunr.Query~Clause
2838 * @see lunr.Query#clause
2839 * @see lunr.Query#term
2840 * @example <caption>query term with required presence</caption>
2841 * query.term('foo', { presence: lunr.Query.presence.REQUIRED })
2842 */
2843lunr.Query.presence = {
2844  /**
2845   * Term's presence in a document is optional, this is the default value.
2846   */
2847  OPTIONAL: 1,
2848
2849  /**
2850   * Term's presence in a document is required, documents that do not contain
2851   * this term will not be returned.
2852   */
2853  REQUIRED: 2,
2854
2855  /**
2856   * Term's presence in a document is prohibited, documents that do contain
2857   * this term will not be returned.
2858   */
2859  PROHIBITED: 3
2860}
2861
2862/**
2863 * A single clause in a {@link lunr.Query} contains a term and details on how to
2864 * match that term against a {@link lunr.Index}.
2865 *
2866 * @typedef {Object} lunr.Query~Clause
2867 * @property {string[]} fields - The fields in an index this clause should be matched against.
2868 * @property {number} [boost=1] - Any boost that should be applied when matching this clause.
2869 * @property {number} [editDistance] - Whether the term should have fuzzy matching applied, and how fuzzy the match should be.
2870 * @property {boolean} [usePipeline] - Whether the term should be passed through the search pipeline.
2871 * @property {number} [wildcard=lunr.Query.wildcard.NONE] - Whether the term should have wildcards appended or prepended.
2872 * @property {number} [presence=lunr.Query.presence.OPTIONAL] - The terms presence in any matching documents.
2873 */
2874
2875/**
2876 * Adds a {@link lunr.Query~Clause} to this query.
2877 *
2878 * Unless the clause contains the fields to be matched all fields will be matched. In addition
2879 * a default boost of 1 is applied to the clause.
2880 *
2881 * @param {lunr.Query~Clause} clause - The clause to add to this query.
2882 * @see lunr.Query~Clause
2883 * @returns {lunr.Query}
2884 */
2885lunr.Query.prototype.clause = function (clause) {
2886  if (!('fields' in clause)) {
2887    clause.fields = this.allFields
2888  }
2889
2890  if (!('boost' in clause)) {
2891    clause.boost = 1
2892  }
2893
2894  if (!('usePipeline' in clause)) {
2895    clause.usePipeline = true
2896  }
2897
2898  if (!('wildcard' in clause)) {
2899    clause.wildcard = lunr.Query.wildcard.NONE
2900  }
2901
2902  if ((clause.wildcard & lunr.Query.wildcard.LEADING) && (clause.term.charAt(0) != lunr.Query.wildcard)) {
2903    clause.term = "*" + clause.term
2904  }
2905
2906  if ((clause.wildcard & lunr.Query.wildcard.TRAILING) && (clause.term.slice(-1) != lunr.Query.wildcard)) {
2907    clause.term = "" + clause.term + "*"
2908  }
2909
2910  if (!('presence' in clause)) {
2911    clause.presence = lunr.Query.presence.OPTIONAL
2912  }
2913
2914  this.clauses.push(clause)
2915
2916  return this
2917}
2918
2919/**
2920 * A negated query is one in which every clause has a presence of
2921 * prohibited. These queries require some special processing to return
2922 * the expected results.
2923 *
2924 * @returns boolean
2925 */
2926lunr.Query.prototype.isNegated = function () {
2927  for (var i = 0; i < this.clauses.length; i++) {
2928    if (this.clauses[i].presence != lunr.Query.presence.PROHIBITED) {
2929      return false
2930    }
2931  }
2932
2933  return true
2934}
2935
2936/**
2937 * Adds a term to the current query, under the covers this will create a {@link lunr.Query~Clause}
2938 * to the list of clauses that make up this query.
2939 *
2940 * The term is used as is, i.e. no tokenization will be performed by this method. Instead conversion
2941 * to a token or token-like string should be done before calling this method.
2942 *
2943 * The term will be converted to a string by calling `toString`. Multiple terms can be passed as an
2944 * array, each term in the array will share the same options.
2945 *
2946 * @param {object|object[]} term - The term(s) to add to the query.
2947 * @param {object} [options] - Any additional properties to add to the query clause.
2948 * @returns {lunr.Query}
2949 * @see lunr.Query#clause
2950 * @see lunr.Query~Clause
2951 * @example <caption>adding a single term to a query</caption>
2952 * query.term("foo")
2953 * @example <caption>adding a single term to a query and specifying search fields, term boost and automatic trailing wildcard</caption>
2954 * query.term("foo", {
2955 *   fields: ["title"],
2956 *   boost: 10,
2957 *   wildcard: lunr.Query.wildcard.TRAILING
2958 * })
2959 * @example <caption>using lunr.tokenizer to convert a string to tokens before using them as terms</caption>
2960 * query.term(lunr.tokenizer("foo bar"))
2961 */
2962lunr.Query.prototype.term = function (term, options) {
2963  if (Array.isArray(term)) {
2964    term.forEach(function (t) { this.term(t, lunr.utils.clone(options)) }, this)
2965    return this
2966  }
2967
2968  var clause = options || {}
2969  clause.term = term.toString()
2970
2971  this.clause(clause)
2972
2973  return this
2974}
2975lunr.QueryParseError = function (message, start, end) {
2976  this.name = "QueryParseError"
2977  this.message = message
2978  this.start = start
2979  this.end = end
2980}
2981
2982lunr.QueryParseError.prototype = new Error
2983lunr.QueryLexer = function (str) {
2984  this.lexemes = []
2985  this.str = str
2986  this.length = str.length
2987  this.pos = 0
2988  this.start = 0
2989  this.escapeCharPositions = []
2990}
2991
2992lunr.QueryLexer.prototype.run = function () {
2993  var state = lunr.QueryLexer.lexText
2994
2995  while (state) {
2996    state = state(this)
2997  }
2998}
2999
3000lunr.QueryLexer.prototype.sliceString = function () {
3001  var subSlices = [],
3002      sliceStart = this.start,
3003      sliceEnd = this.pos
3004
3005  for (var i = 0; i < this.escapeCharPositions.length; i++) {
3006    sliceEnd = this.escapeCharPositions[i]
3007    subSlices.push(this.str.slice(sliceStart, sliceEnd))
3008    sliceStart = sliceEnd + 1
3009  }
3010
3011  subSlices.push(this.str.slice(sliceStart, this.pos))
3012  this.escapeCharPositions.length = 0
3013
3014  return subSlices.join('')
3015}
3016
3017lunr.QueryLexer.prototype.emit = function (type) {
3018  this.lexemes.push({
3019    type: type,
3020    str: this.sliceString(),
3021    start: this.start,
3022    end: this.pos
3023  })
3024
3025  this.start = this.pos
3026}
3027
3028lunr.QueryLexer.prototype.escapeCharacter = function () {
3029  this.escapeCharPositions.push(this.pos - 1)
3030  this.pos += 1
3031}
3032
3033lunr.QueryLexer.prototype.next = function () {
3034  if (this.pos >= this.length) {
3035    return lunr.QueryLexer.EOS
3036  }
3037
3038  var char = this.str.charAt(this.pos)
3039  this.pos += 1
3040  return char
3041}
3042
3043lunr.QueryLexer.prototype.width = function () {
3044  return this.pos - this.start
3045}
3046
3047lunr.QueryLexer.prototype.ignore = function () {
3048  if (this.start == this.pos) {
3049    this.pos += 1
3050  }
3051
3052  this.start = this.pos
3053}
3054
3055lunr.QueryLexer.prototype.backup = function () {
3056  this.pos -= 1
3057}
3058
3059lunr.QueryLexer.prototype.acceptDigitRun = function () {
3060  var char, charCode
3061
3062  do {
3063    char = this.next()
3064    charCode = char.charCodeAt(0)
3065  } while (charCode > 47 && charCode < 58)
3066
3067  if (char != lunr.QueryLexer.EOS) {
3068    this.backup()
3069  }
3070}
3071
3072lunr.QueryLexer.prototype.more = function () {
3073  return this.pos < this.length
3074}
3075
3076lunr.QueryLexer.EOS = 'EOS'
3077lunr.QueryLexer.FIELD = 'FIELD'
3078lunr.QueryLexer.TERM = 'TERM'
3079lunr.QueryLexer.EDIT_DISTANCE = 'EDIT_DISTANCE'
3080lunr.QueryLexer.BOOST = 'BOOST'
3081lunr.QueryLexer.PRESENCE = 'PRESENCE'
3082
3083lunr.QueryLexer.lexField = function (lexer) {
3084  lexer.backup()
3085  lexer.emit(lunr.QueryLexer.FIELD)
3086  lexer.ignore()
3087  return lunr.QueryLexer.lexText
3088}
3089
3090lunr.QueryLexer.lexTerm = function (lexer) {
3091  if (lexer.width() > 1) {
3092    lexer.backup()
3093    lexer.emit(lunr.QueryLexer.TERM)
3094  }
3095
3096  lexer.ignore()
3097
3098  if (lexer.more()) {
3099    return lunr.QueryLexer.lexText
3100  }
3101}
3102
3103lunr.QueryLexer.lexEditDistance = function (lexer) {
3104  lexer.ignore()
3105  lexer.acceptDigitRun()
3106  lexer.emit(lunr.QueryLexer.EDIT_DISTANCE)
3107  return lunr.QueryLexer.lexText
3108}
3109
3110lunr.QueryLexer.lexBoost = function (lexer) {
3111  lexer.ignore()
3112  lexer.acceptDigitRun()
3113  lexer.emit(lunr.QueryLexer.BOOST)
3114  return lunr.QueryLexer.lexText
3115}
3116
3117lunr.QueryLexer.lexEOS = function (lexer) {
3118  if (lexer.width() > 0) {
3119    lexer.emit(lunr.QueryLexer.TERM)
3120  }
3121}
3122
3123// This matches the separator used when tokenising fields
3124// within a document. These should match otherwise it is
3125// not possible to search for some tokens within a document.
3126//
3127// It is possible for the user to change the separator on the
3128// tokenizer so it _might_ clash with any other of the special
3129// characters already used within the search string, e.g. :.
3130//
3131// This means that it is possible to change the separator in
3132// such a way that makes some words unsearchable using a search
3133// string.
3134lunr.QueryLexer.termSeparator = lunr.tokenizer.separator
3135
3136lunr.QueryLexer.lexText = function (lexer) {
3137  while (true) {
3138    var char = lexer.next()
3139
3140    if (char == lunr.QueryLexer.EOS) {
3141      return lunr.QueryLexer.lexEOS
3142    }
3143
3144    // Escape character is '\'
3145    if (char.charCodeAt(0) == 92) {
3146      lexer.escapeCharacter()
3147      continue
3148    }
3149
3150    if (char == ":") {
3151      return lunr.QueryLexer.lexField
3152    }
3153
3154    if (char == "~") {
3155      lexer.backup()
3156      if (lexer.width() > 0) {
3157        lexer.emit(lunr.QueryLexer.TERM)
3158      }
3159      return lunr.QueryLexer.lexEditDistance
3160    }
3161
3162    if (char == "^") {
3163      lexer.backup()
3164      if (lexer.width() > 0) {
3165        lexer.emit(lunr.QueryLexer.TERM)
3166      }
3167      return lunr.QueryLexer.lexBoost
3168    }
3169
3170    // "+" indicates term presence is required
3171    // checking for length to ensure that only
3172    // leading "+" are considered
3173    if (char == "+" && lexer.width() === 1) {
3174      lexer.emit(lunr.QueryLexer.PRESENCE)
3175      return lunr.QueryLexer.lexText
3176    }
3177
3178    // "-" indicates term presence is prohibited
3179    // checking for length to ensure that only
3180    // leading "-" are considered
3181    if (char == "-" && lexer.width() === 1) {
3182      lexer.emit(lunr.QueryLexer.PRESENCE)
3183      return lunr.QueryLexer.lexText
3184    }
3185
3186    if (char.match(lunr.QueryLexer.termSeparator)) {
3187      return lunr.QueryLexer.lexTerm
3188    }
3189  }
3190}
3191
3192lunr.QueryParser = function (str, query) {
3193  this.lexer = new lunr.QueryLexer (str)
3194  this.query = query
3195  this.currentClause = {}
3196  this.lexemeIdx = 0
3197}
3198
3199lunr.QueryParser.prototype.parse = function () {
3200  this.lexer.run()
3201  this.lexemes = this.lexer.lexemes
3202
3203  var state = lunr.QueryParser.parseClause
3204
3205  while (state) {
3206    state = state(this)
3207  }
3208
3209  return this.query
3210}
3211
3212lunr.QueryParser.prototype.peekLexeme = function () {
3213  return this.lexemes[this.lexemeIdx]
3214}
3215
3216lunr.QueryParser.prototype.consumeLexeme = function () {
3217  var lexeme = this.peekLexeme()
3218  this.lexemeIdx += 1
3219  return lexeme
3220}
3221
3222lunr.QueryParser.prototype.nextClause = function () {
3223  var completedClause = this.currentClause
3224  this.query.clause(completedClause)
3225  this.currentClause = {}
3226}
3227
3228lunr.QueryParser.parseClause = function (parser) {
3229  var lexeme = parser.peekLexeme()
3230
3231  if (lexeme == undefined) {
3232    return
3233  }
3234
3235  switch (lexeme.type) {
3236    case lunr.QueryLexer.PRESENCE:
3237      return lunr.QueryParser.parsePresence
3238    case lunr.QueryLexer.FIELD:
3239      return lunr.QueryParser.parseField
3240    case lunr.QueryLexer.TERM:
3241      return lunr.QueryParser.parseTerm
3242    default:
3243      var errorMessage = "expected either a field or a term, found " + lexeme.type
3244
3245      if (lexeme.str.length >= 1) {
3246        errorMessage += " with value '" + lexeme.str + "'"
3247      }
3248
3249      throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)
3250  }
3251}
3252
3253lunr.QueryParser.parsePresence = function (parser) {
3254  var lexeme = parser.consumeLexeme()
3255
3256  if (lexeme == undefined) {
3257    return
3258  }
3259
3260  switch (lexeme.str) {
3261    case "-":
3262      parser.currentClause.presence = lunr.Query.presence.PROHIBITED
3263      break
3264    case "+":
3265      parser.currentClause.presence = lunr.Query.presence.REQUIRED
3266      break
3267    default:
3268      var errorMessage = "unrecognised presence operator'" + lexeme.str + "'"
3269      throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)
3270  }
3271
3272  var nextLexeme = parser.peekLexeme()
3273
3274  if (nextLexeme == undefined) {
3275    var errorMessage = "expecting term or field, found nothing"
3276    throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)
3277  }
3278
3279  switch (nextLexeme.type) {
3280    case lunr.QueryLexer.FIELD:
3281      return lunr.QueryParser.parseField
3282    case lunr.QueryLexer.TERM:
3283      return lunr.QueryParser.parseTerm
3284    default:
3285      var errorMessage = "expecting term or field, found '" + nextLexeme.type + "'"
3286      throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)
3287  }
3288}
3289
3290lunr.QueryParser.parseField = function (parser) {
3291  var lexeme = parser.consumeLexeme()
3292
3293  if (lexeme == undefined) {
3294    return
3295  }
3296
3297  if (parser.query.allFields.indexOf(lexeme.str) == -1) {
3298    var possibleFields = parser.query.allFields.map(function (f) { return "'" + f + "'" }).join(', '),
3299        errorMessage = "unrecognised field '" + lexeme.str + "', possible fields: " + possibleFields
3300
3301    throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)
3302  }
3303
3304  parser.currentClause.fields = [lexeme.str]
3305
3306  var nextLexeme = parser.peekLexeme()
3307
3308  if (nextLexeme == undefined) {
3309    var errorMessage = "expecting term, found nothing"
3310    throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)
3311  }
3312
3313  switch (nextLexeme.type) {
3314    case lunr.QueryLexer.TERM:
3315      return lunr.QueryParser.parseTerm
3316    default:
3317      var errorMessage = "expecting term, found '" + nextLexeme.type + "'"
3318      throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)
3319  }
3320}
3321
3322lunr.QueryParser.parseTerm = function (parser) {
3323  var lexeme = parser.consumeLexeme()
3324
3325  if (lexeme == undefined) {
3326    return
3327  }
3328
3329  parser.currentClause.term = lexeme.str.toLowerCase()
3330
3331  if (lexeme.str.indexOf("*") != -1) {
3332    parser.currentClause.usePipeline = false
3333  }
3334
3335  var nextLexeme = parser.peekLexeme()
3336
3337  if (nextLexeme == undefined) {
3338    parser.nextClause()
3339    return
3340  }
3341
3342  switch (nextLexeme.type) {
3343    case lunr.QueryLexer.TERM:
3344      parser.nextClause()
3345      return lunr.QueryParser.parseTerm
3346    case lunr.QueryLexer.FIELD:
3347      parser.nextClause()
3348      return lunr.QueryParser.parseField
3349    case lunr.QueryLexer.EDIT_DISTANCE:
3350      return lunr.QueryParser.parseEditDistance
3351    case lunr.QueryLexer.BOOST:
3352      return lunr.QueryParser.parseBoost
3353    case lunr.QueryLexer.PRESENCE:
3354      parser.nextClause()
3355      return lunr.QueryParser.parsePresence
3356    default:
3357      var errorMessage = "Unexpected lexeme type '" + nextLexeme.type + "'"
3358      throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)
3359  }
3360}
3361
3362lunr.QueryParser.parseEditDistance = function (parser) {
3363  var lexeme = parser.consumeLexeme()
3364
3365  if (lexeme == undefined) {
3366    return
3367  }
3368
3369  var editDistance = parseInt(lexeme.str, 10)
3370
3371  if (isNaN(editDistance)) {
3372    var errorMessage = "edit distance must be numeric"
3373    throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)
3374  }
3375
3376  parser.currentClause.editDistance = editDistance
3377
3378  var nextLexeme = parser.peekLexeme()
3379
3380  if (nextLexeme == undefined) {
3381    parser.nextClause()
3382    return
3383  }
3384
3385  switch (nextLexeme.type) {
3386    case lunr.QueryLexer.TERM:
3387      parser.nextClause()
3388      return lunr.QueryParser.parseTerm
3389    case lunr.QueryLexer.FIELD:
3390      parser.nextClause()
3391      return lunr.QueryParser.parseField
3392    case lunr.QueryLexer.EDIT_DISTANCE:
3393      return lunr.QueryParser.parseEditDistance
3394    case lunr.QueryLexer.BOOST:
3395      return lunr.QueryParser.parseBoost
3396    case lunr.QueryLexer.PRESENCE:
3397      parser.nextClause()
3398      return lunr.QueryParser.parsePresence
3399    default:
3400      var errorMessage = "Unexpected lexeme type '" + nextLexeme.type + "'"
3401      throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)
3402  }
3403}
3404
3405lunr.QueryParser.parseBoost = function (parser) {
3406  var lexeme = parser.consumeLexeme()
3407
3408  if (lexeme == undefined) {
3409    return
3410  }
3411
3412  var boost = parseInt(lexeme.str, 10)
3413
3414  if (isNaN(boost)) {
3415    var errorMessage = "boost must be numeric"
3416    throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)
3417  }
3418
3419  parser.currentClause.boost = boost
3420
3421  var nextLexeme = parser.peekLexeme()
3422
3423  if (nextLexeme == undefined) {
3424    parser.nextClause()
3425    return
3426  }
3427
3428  switch (nextLexeme.type) {
3429    case lunr.QueryLexer.TERM:
3430      parser.nextClause()
3431      return lunr.QueryParser.parseTerm
3432    case lunr.QueryLexer.FIELD:
3433      parser.nextClause()
3434      return lunr.QueryParser.parseField
3435    case lunr.QueryLexer.EDIT_DISTANCE:
3436      return lunr.QueryParser.parseEditDistance
3437    case lunr.QueryLexer.BOOST:
3438      return lunr.QueryParser.parseBoost
3439    case lunr.QueryLexer.PRESENCE:
3440      parser.nextClause()
3441      return lunr.QueryParser.parsePresence
3442    default:
3443      var errorMessage = "Unexpected lexeme type '" + nextLexeme.type + "'"
3444      throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)
3445  }
3446}
3447
3448  /**
3449   * export the module via AMD, CommonJS or as a browser global
3450   * Export code from https://github.com/umdjs/umd/blob/master/returnExports.js
3451   */
3452  ;(function (root, factory) {
3453    if (typeof define === 'function' && define.amd) {
3454      // AMD. Register as an anonymous module.
3455      define(factory)
3456    } else if (typeof exports === 'object') {
3457      /**
3458       * Node. Does not work with strict CommonJS, but
3459       * only CommonJS-like enviroments that support module.exports,
3460       * like Node.
3461       */
3462      module.exports = factory()
3463    } else {
3464      // Browser globals (root is window)
3465      root.lunr = factory()
3466    }
3467  }(this, function () {
3468    /**
3469     * Just return a value to define the module export.
3470     * This example returns an object, but the module
3471     * can return a function as the exported value.
3472     */
3473    return lunr
3474  }))
3475})();
3476