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