github.com/shyftnetwork/go-empyrean@v1.8.3-0.20191127201940-fbfca9338f04/shyft_documentation/source/javascripts/lib/_lunr.js (about) 1 /** 2 * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 0.5.7 3 * Copyright (C) 2014 Oliver Nightingale 4 * MIT Licensed 5 * @license 6 */ 7 8 (function(){ 9 10 /** 11 * Convenience function for instantiating a new lunr index and configuring it 12 * with the default pipeline functions and the passed config function. 13 * 14 * When using this convenience function a new index will be created with the 15 * following functions already in the pipeline: 16 * 17 * lunr.StopWordFilter - filters out any stop words before they enter the 18 * index 19 * 20 * lunr.stemmer - stems the tokens before entering the index. 21 * 22 * Example: 23 * 24 * var idx = lunr(function () { 25 * this.field('title', 10) 26 * this.field('tags', 100) 27 * this.field('body') 28 * 29 * this.ref('cid') 30 * 31 * this.pipeline.add(function () { 32 * // some custom pipeline function 33 * }) 34 * 35 * }) 36 * 37 * @param {Function} config A function that will be called with the new instance 38 * of the lunr.Index as both its context and first parameter. It can be used to 39 * customize the instance of new lunr.Index. 40 * @namespace 41 * @module 42 * @returns {lunr.Index} 43 * 44 */ 45 var lunr = function (config) { 46 var idx = new lunr.Index 47 48 idx.pipeline.add( 49 lunr.trimmer, 50 lunr.stopWordFilter, 51 lunr.stemmer 52 ) 53 54 if (config) config.call(idx, idx) 55 56 return idx 57 } 58 59 lunr.version = "0.5.7" 60 /*! 61 * lunr.utils 62 * Copyright (C) 2014 Oliver Nightingale 63 */ 64 65 /** 66 * A namespace containing utils for the rest of the lunr library 67 */ 68 lunr.utils = {} 69 70 /** 71 * Print a warning message to the console. 72 * 73 * @param {String} message The message to be printed. 74 * @memberOf Utils 75 */ 76 lunr.utils.warn = (function (global) { 77 return function (message) { 78 if (global.console && console.warn) { 79 console.warn(message) 80 } 81 } 82 })(this) 83 84 /*! 85 * lunr.EventEmitter 86 * Copyright (C) 2014 Oliver Nightingale 87 */ 88 89 /** 90 * lunr.EventEmitter is an event emitter for lunr. It manages adding and removing event handlers and triggering events and their handlers. 91 * 92 * @constructor 93 */ 94 lunr.EventEmitter = function () { 95 this.events = {} 96 } 97 98 /** 99 * Binds a handler function to a specific event(s). 100 * 101 * Can bind a single function to many different events in one call. 102 * 103 * @param {String} [eventName] The name(s) of events to bind this function to. 104 * @param {Function} handler The function to call when an event is fired. 105 * @memberOf EventEmitter 106 */ 107 lunr.EventEmitter.prototype.addListener = function () { 108 var args = Array.prototype.slice.call(arguments), 109 fn = args.pop(), 110 names = args 111 112 if (typeof fn !== "function") throw new TypeError ("last argument must be a function") 113 114 names.forEach(function (name) { 115 if (!this.hasHandler(name)) this.events[name] = [] 116 this.events[name].push(fn) 117 }, this) 118 } 119 120 /** 121 * Removes a handler function from a specific event. 122 * 123 * @param {String} eventName The name of the event to remove this function from. 124 * @param {Function} handler The function to remove from an event. 125 * @memberOf EventEmitter 126 */ 127 lunr.EventEmitter.prototype.removeListener = function (name, fn) { 128 if (!this.hasHandler(name)) return 129 130 var fnIndex = this.events[name].indexOf(fn) 131 this.events[name].splice(fnIndex, 1) 132 133 if (!this.events[name].length) delete this.events[name] 134 } 135 136 /** 137 * Calls all functions bound to the given event. 138 * 139 * Additional data can be passed to the event handler as arguments to `emit` 140 * after the event name. 141 * 142 * @param {String} eventName The name of the event to emit. 143 * @memberOf EventEmitter 144 */ 145 lunr.EventEmitter.prototype.emit = function (name) { 146 if (!this.hasHandler(name)) return 147 148 var args = Array.prototype.slice.call(arguments, 1) 149 150 this.events[name].forEach(function (fn) { 151 fn.apply(undefined, args) 152 }) 153 } 154 155 /** 156 * Checks whether a handler has ever been stored against an event. 157 * 158 * @param {String} eventName The name of the event to check. 159 * @private 160 * @memberOf EventEmitter 161 */ 162 lunr.EventEmitter.prototype.hasHandler = function (name) { 163 return name in this.events 164 } 165 166 /*! 167 * lunr.tokenizer 168 * Copyright (C) 2014 Oliver Nightingale 169 */ 170 171 /** 172 * A function for splitting a string into tokens ready to be inserted into 173 * the search index. 174 * 175 * @module 176 * @param {String} obj The string to convert into tokens 177 * @returns {Array} 178 */ 179 lunr.tokenizer = function (obj) { 180 if (!arguments.length || obj == null || obj == undefined) return [] 181 if (Array.isArray(obj)) return obj.map(function (t) { return t.toLowerCase() }) 182 183 var str = obj.toString().replace(/^\s+/, '') 184 185 for (var i = str.length - 1; i >= 0; i--) { 186 if (/\S/.test(str.charAt(i))) { 187 str = str.substring(0, i + 1) 188 break 189 } 190 } 191 192 return str 193 .split(/(?:\s+|\-)/) 194 .filter(function (token) { 195 return !!token 196 }) 197 .map(function (token) { 198 return token.toLowerCase() 199 }) 200 } 201 /*! 202 * lunr.Pipeline 203 * Copyright (C) 2014 Oliver Nightingale 204 */ 205 206 /** 207 * lunr.Pipelines maintain an ordered list of functions to be applied to all 208 * tokens in documents entering the search index and queries being ran against 209 * the index. 210 * 211 * An instance of lunr.Index created with the lunr shortcut will contain a 212 * pipeline with a stop word filter and an English language stemmer. Extra 213 * functions can be added before or after either of these functions or these 214 * default functions can be removed. 215 * 216 * When run the pipeline will call each function in turn, passing a token, the 217 * index of that token in the original list of all tokens and finally a list of 218 * all the original tokens. 219 * 220 * The output of functions in the pipeline will be passed to the next function 221 * in the pipeline. To exclude a token from entering the index the function 222 * should return undefined, the rest of the pipeline will not be called with 223 * this token. 224 * 225 * For serialisation of pipelines to work, all functions used in an instance of 226 * a pipeline should be registered with lunr.Pipeline. Registered functions can 227 * then be loaded. If trying to load a serialised pipeline that uses functions 228 * that are not registered an error will be thrown. 229 * 230 * If not planning on serialising the pipeline then registering pipeline functions 231 * is not necessary. 232 * 233 * @constructor 234 */ 235 lunr.Pipeline = function () { 236 this._stack = [] 237 } 238 239 lunr.Pipeline.registeredFunctions = {} 240 241 /** 242 * Register a function with the pipeline. 243 * 244 * Functions that are used in the pipeline should be registered if the pipeline 245 * needs to be serialised, or a serialised pipeline needs to be loaded. 246 * 247 * Registering a function does not add it to a pipeline, functions must still be 248 * added to instances of the pipeline for them to be used when running a pipeline. 249 * 250 * @param {Function} fn The function to check for. 251 * @param {String} label The label to register this function with 252 * @memberOf Pipeline 253 */ 254 lunr.Pipeline.registerFunction = function (fn, label) { 255 if (label in this.registeredFunctions) { 256 lunr.utils.warn('Overwriting existing registered function: ' + label) 257 } 258 259 fn.label = label 260 lunr.Pipeline.registeredFunctions[fn.label] = fn 261 } 262 263 /** 264 * Warns if the function is not registered as a Pipeline function. 265 * 266 * @param {Function} fn The function to check for. 267 * @private 268 * @memberOf Pipeline 269 */ 270 lunr.Pipeline.warnIfFunctionNotRegistered = function (fn) { 271 var isRegistered = fn.label && (fn.label in this.registeredFunctions) 272 273 if (!isRegistered) { 274 lunr.utils.warn('Function is not registered with pipeline. This may cause problems when serialising the index.\n', fn) 275 } 276 } 277 278 /** 279 * Loads a previously serialised pipeline. 280 * 281 * All functions to be loaded must already be registered with lunr.Pipeline. 282 * If any function from the serialised data has not been registered then an 283 * error will be thrown. 284 * 285 * @param {Object} serialised The serialised pipeline to load. 286 * @returns {lunr.Pipeline} 287 * @memberOf Pipeline 288 */ 289 lunr.Pipeline.load = function (serialised) { 290 var pipeline = new lunr.Pipeline 291 292 serialised.forEach(function (fnName) { 293 var fn = lunr.Pipeline.registeredFunctions[fnName] 294 295 if (fn) { 296 pipeline.add(fn) 297 } else { 298 throw new Error ('Cannot load un-registered function: ' + fnName) 299 } 300 }) 301 302 return pipeline 303 } 304 305 /** 306 * Adds new functions to the end of the pipeline. 307 * 308 * Logs a warning if the function has not been registered. 309 * 310 * @param {Function} functions Any number of functions to add to the pipeline. 311 * @memberOf Pipeline 312 */ 313 lunr.Pipeline.prototype.add = function () { 314 var fns = Array.prototype.slice.call(arguments) 315 316 fns.forEach(function (fn) { 317 lunr.Pipeline.warnIfFunctionNotRegistered(fn) 318 this._stack.push(fn) 319 }, this) 320 } 321 322 /** 323 * Adds a single function after a function that already exists in the 324 * pipeline. 325 * 326 * Logs a warning if the function has not been registered. 327 * 328 * @param {Function} existingFn A function that already exists in the pipeline. 329 * @param {Function} newFn The new function to add to the pipeline. 330 * @memberOf Pipeline 331 */ 332 lunr.Pipeline.prototype.after = function (existingFn, newFn) { 333 lunr.Pipeline.warnIfFunctionNotRegistered(newFn) 334 335 var pos = this._stack.indexOf(existingFn) + 1 336 this._stack.splice(pos, 0, newFn) 337 } 338 339 /** 340 * Adds a single function before a function that already exists in the 341 * pipeline. 342 * 343 * Logs a warning if the function has not been registered. 344 * 345 * @param {Function} existingFn A function that already exists in the pipeline. 346 * @param {Function} newFn The new function to add to the pipeline. 347 * @memberOf Pipeline 348 */ 349 lunr.Pipeline.prototype.before = function (existingFn, newFn) { 350 lunr.Pipeline.warnIfFunctionNotRegistered(newFn) 351 352 var pos = this._stack.indexOf(existingFn) 353 this._stack.splice(pos, 0, newFn) 354 } 355 356 /** 357 * Removes a function from the pipeline. 358 * 359 * @param {Function} fn The function to remove from the pipeline. 360 * @memberOf Pipeline 361 */ 362 lunr.Pipeline.prototype.remove = function (fn) { 363 var pos = this._stack.indexOf(fn) 364 this._stack.splice(pos, 1) 365 } 366 367 /** 368 * Runs the current list of functions that make up the pipeline against the 369 * passed tokens. 370 * 371 * @param {Array} tokens The tokens to run through the pipeline. 372 * @returns {Array} 373 * @memberOf Pipeline 374 */ 375 lunr.Pipeline.prototype.run = function (tokens) { 376 var out = [], 377 tokenLength = tokens.length, 378 stackLength = this._stack.length 379 380 for (var i = 0; i < tokenLength; i++) { 381 var token = tokens[i] 382 383 for (var j = 0; j < stackLength; j++) { 384 token = this._stack[j](token, i, tokens) 385 if (token === void 0) break 386 }; 387 388 if (token !== void 0) out.push(token) 389 }; 390 391 return out 392 } 393 394 /** 395 * Resets the pipeline by removing any existing processors. 396 * 397 * @memberOf Pipeline 398 */ 399 lunr.Pipeline.prototype.reset = function () { 400 this._stack = [] 401 } 402 403 /** 404 * Returns a representation of the pipeline ready for serialisation. 405 * 406 * Logs a warning if the function has not been registered. 407 * 408 * @returns {Array} 409 * @memberOf Pipeline 410 */ 411 lunr.Pipeline.prototype.toJSON = function () { 412 return this._stack.map(function (fn) { 413 lunr.Pipeline.warnIfFunctionNotRegistered(fn) 414 415 return fn.label 416 }) 417 } 418 /*! 419 * lunr.Vector 420 * Copyright (C) 2014 Oliver Nightingale 421 */ 422 423 /** 424 * lunr.Vectors implement vector related operations for 425 * a series of elements. 426 * 427 * @constructor 428 */ 429 lunr.Vector = function () { 430 this._magnitude = null 431 this.list = undefined 432 this.length = 0 433 } 434 435 /** 436 * lunr.Vector.Node is a simple struct for each node 437 * in a lunr.Vector. 438 * 439 * @private 440 * @param {Number} The index of the node in the vector. 441 * @param {Object} The data at this node in the vector. 442 * @param {lunr.Vector.Node} The node directly after this node in the vector. 443 * @constructor 444 * @memberOf Vector 445 */ 446 lunr.Vector.Node = function (idx, val, next) { 447 this.idx = idx 448 this.val = val 449 this.next = next 450 } 451 452 /** 453 * Inserts a new value at a position in a vector. 454 * 455 * @param {Number} The index at which to insert a value. 456 * @param {Object} The object to insert in the vector. 457 * @memberOf Vector. 458 */ 459 lunr.Vector.prototype.insert = function (idx, val) { 460 var list = this.list 461 462 if (!list) { 463 this.list = new lunr.Vector.Node (idx, val, list) 464 return this.length++ 465 } 466 467 var prev = list, 468 next = list.next 469 470 while (next != undefined) { 471 if (idx < next.idx) { 472 prev.next = new lunr.Vector.Node (idx, val, next) 473 return this.length++ 474 } 475 476 prev = next, next = next.next 477 } 478 479 prev.next = new lunr.Vector.Node (idx, val, next) 480 return this.length++ 481 } 482 483 /** 484 * Calculates the magnitude of this vector. 485 * 486 * @returns {Number} 487 * @memberOf Vector 488 */ 489 lunr.Vector.prototype.magnitude = function () { 490 if (this._magniture) return this._magnitude 491 var node = this.list, 492 sumOfSquares = 0, 493 val 494 495 while (node) { 496 val = node.val 497 sumOfSquares += val * val 498 node = node.next 499 } 500 501 return this._magnitude = Math.sqrt(sumOfSquares) 502 } 503 504 /** 505 * Calculates the dot product of this vector and another vector. 506 * 507 * @param {lunr.Vector} otherVector The vector to compute the dot product with. 508 * @returns {Number} 509 * @memberOf Vector 510 */ 511 lunr.Vector.prototype.dot = function (otherVector) { 512 var node = this.list, 513 otherNode = otherVector.list, 514 dotProduct = 0 515 516 while (node && otherNode) { 517 if (node.idx < otherNode.idx) { 518 node = node.next 519 } else if (node.idx > otherNode.idx) { 520 otherNode = otherNode.next 521 } else { 522 dotProduct += node.val * otherNode.val 523 node = node.next 524 otherNode = otherNode.next 525 } 526 } 527 528 return dotProduct 529 } 530 531 /** 532 * Calculates the cosine similarity between this vector and another 533 * vector. 534 * 535 * @param {lunr.Vector} otherVector The other vector to calculate the 536 * similarity with. 537 * @returns {Number} 538 * @memberOf Vector 539 */ 540 lunr.Vector.prototype.similarity = function (otherVector) { 541 return this.dot(otherVector) / (this.magnitude() * otherVector.magnitude()) 542 } 543 /*! 544 * lunr.SortedSet 545 * Copyright (C) 2014 Oliver Nightingale 546 */ 547 548 /** 549 * lunr.SortedSets are used to maintain an array of uniq values in a sorted 550 * order. 551 * 552 * @constructor 553 */ 554 lunr.SortedSet = function () { 555 this.length = 0 556 this.elements = [] 557 } 558 559 /** 560 * Loads a previously serialised sorted set. 561 * 562 * @param {Array} serialisedData The serialised set to load. 563 * @returns {lunr.SortedSet} 564 * @memberOf SortedSet 565 */ 566 lunr.SortedSet.load = function (serialisedData) { 567 var set = new this 568 569 set.elements = serialisedData 570 set.length = serialisedData.length 571 572 return set 573 } 574 575 /** 576 * Inserts new items into the set in the correct position to maintain the 577 * order. 578 * 579 * @param {Object} The objects to add to this set. 580 * @memberOf SortedSet 581 */ 582 lunr.SortedSet.prototype.add = function () { 583 Array.prototype.slice.call(arguments).forEach(function (element) { 584 if (~this.indexOf(element)) return 585 this.elements.splice(this.locationFor(element), 0, element) 586 }, this) 587 588 this.length = this.elements.length 589 } 590 591 /** 592 * Converts this sorted set into an array. 593 * 594 * @returns {Array} 595 * @memberOf SortedSet 596 */ 597 lunr.SortedSet.prototype.toArray = function () { 598 return this.elements.slice() 599 } 600 601 /** 602 * Creates a new array with the results of calling a provided function on every 603 * element in this sorted set. 604 * 605 * Delegates to Array.prototype.map and has the same signature. 606 * 607 * @param {Function} fn The function that is called on each element of the 608 * set. 609 * @param {Object} ctx An optional object that can be used as the context 610 * for the function fn. 611 * @returns {Array} 612 * @memberOf SortedSet 613 */ 614 lunr.SortedSet.prototype.map = function (fn, ctx) { 615 return this.elements.map(fn, ctx) 616 } 617 618 /** 619 * Executes a provided function once per sorted set element. 620 * 621 * Delegates to Array.prototype.forEach and has the same signature. 622 * 623 * @param {Function} fn The function that is called on each element of the 624 * set. 625 * @param {Object} ctx An optional object that can be used as the context 626 * @memberOf SortedSet 627 * for the function fn. 628 */ 629 lunr.SortedSet.prototype.forEach = function (fn, ctx) { 630 return this.elements.forEach(fn, ctx) 631 } 632 633 /** 634 * Returns the index at which a given element can be found in the 635 * sorted set, or -1 if it is not present. 636 * 637 * @param {Object} elem The object to locate in the sorted set. 638 * @param {Number} start An optional index at which to start searching from 639 * within the set. 640 * @param {Number} end An optional index at which to stop search from within 641 * the set. 642 * @returns {Number} 643 * @memberOf SortedSet 644 */ 645 lunr.SortedSet.prototype.indexOf = function (elem, start, end) { 646 var start = start || 0, 647 end = end || this.elements.length, 648 sectionLength = end - start, 649 pivot = start + Math.floor(sectionLength / 2), 650 pivotElem = this.elements[pivot] 651 652 if (sectionLength <= 1) { 653 if (pivotElem === elem) { 654 return pivot 655 } else { 656 return -1 657 } 658 } 659 660 if (pivotElem < elem) return this.indexOf(elem, pivot, end) 661 if (pivotElem > elem) return this.indexOf(elem, start, pivot) 662 if (pivotElem === elem) return pivot 663 } 664 665 /** 666 * Returns the position within the sorted set that an element should be 667 * inserted at to maintain the current order of the set. 668 * 669 * This function assumes that the element to search for does not already exist 670 * in the sorted set. 671 * 672 * @param {Object} elem The elem to find the position for in the set 673 * @param {Number} start An optional index at which to start searching from 674 * within the set. 675 * @param {Number} end An optional index at which to stop search from within 676 * the set. 677 * @returns {Number} 678 * @memberOf SortedSet 679 */ 680 lunr.SortedSet.prototype.locationFor = function (elem, start, end) { 681 var start = start || 0, 682 end = end || this.elements.length, 683 sectionLength = end - start, 684 pivot = start + Math.floor(sectionLength / 2), 685 pivotElem = this.elements[pivot] 686 687 if (sectionLength <= 1) { 688 if (pivotElem > elem) return pivot 689 if (pivotElem < elem) return pivot + 1 690 } 691 692 if (pivotElem < elem) return this.locationFor(elem, pivot, end) 693 if (pivotElem > elem) return this.locationFor(elem, start, pivot) 694 } 695 696 /** 697 * Creates a new lunr.SortedSet that contains the elements in the intersection 698 * of this set and the passed set. 699 * 700 * @param {lunr.SortedSet} otherSet The set to intersect with this set. 701 * @returns {lunr.SortedSet} 702 * @memberOf SortedSet 703 */ 704 lunr.SortedSet.prototype.intersect = function (otherSet) { 705 var intersectSet = new lunr.SortedSet, 706 i = 0, j = 0, 707 a_len = this.length, b_len = otherSet.length, 708 a = this.elements, b = otherSet.elements 709 710 while (true) { 711 if (i > a_len - 1 || j > b_len - 1) break 712 713 if (a[i] === b[j]) { 714 intersectSet.add(a[i]) 715 i++, j++ 716 continue 717 } 718 719 if (a[i] < b[j]) { 720 i++ 721 continue 722 } 723 724 if (a[i] > b[j]) { 725 j++ 726 continue 727 } 728 }; 729 730 return intersectSet 731 } 732 733 /** 734 * Makes a copy of this set 735 * 736 * @returns {lunr.SortedSet} 737 * @memberOf SortedSet 738 */ 739 lunr.SortedSet.prototype.clone = function () { 740 var clone = new lunr.SortedSet 741 742 clone.elements = this.toArray() 743 clone.length = clone.elements.length 744 745 return clone 746 } 747 748 /** 749 * Creates a new lunr.SortedSet that contains the elements in the union 750 * of this set and the passed set. 751 * 752 * @param {lunr.SortedSet} otherSet The set to union with this set. 753 * @returns {lunr.SortedSet} 754 * @memberOf SortedSet 755 */ 756 lunr.SortedSet.prototype.union = function (otherSet) { 757 var longSet, shortSet, unionSet 758 759 if (this.length >= otherSet.length) { 760 longSet = this, shortSet = otherSet 761 } else { 762 longSet = otherSet, shortSet = this 763 } 764 765 unionSet = longSet.clone() 766 767 unionSet.add.apply(unionSet, shortSet.toArray()) 768 769 return unionSet 770 } 771 772 /** 773 * Returns a representation of the sorted set ready for serialisation. 774 * 775 * @returns {Array} 776 * @memberOf SortedSet 777 */ 778 lunr.SortedSet.prototype.toJSON = function () { 779 return this.toArray() 780 } 781 /*! 782 * lunr.Index 783 * Copyright (C) 2014 Oliver Nightingale 784 */ 785 786 /** 787 * lunr.Index is object that manages a search index. It contains the indexes 788 * and stores all the tokens and document lookups. It also provides the main 789 * user facing API for the library. 790 * 791 * @constructor 792 */ 793 lunr.Index = function () { 794 this._fields = [] 795 this._ref = 'id' 796 this.pipeline = new lunr.Pipeline 797 this.documentStore = new lunr.Store 798 this.tokenStore = new lunr.TokenStore 799 this.corpusTokens = new lunr.SortedSet 800 this.eventEmitter = new lunr.EventEmitter 801 802 this._idfCache = {} 803 804 this.on('add', 'remove', 'update', (function () { 805 this._idfCache = {} 806 }).bind(this)) 807 } 808 809 /** 810 * Bind a handler to events being emitted by the index. 811 * 812 * The handler can be bound to many events at the same time. 813 * 814 * @param {String} [eventName] The name(s) of events to bind the function to. 815 * @param {Function} handler The serialised set to load. 816 * @memberOf Index 817 */ 818 lunr.Index.prototype.on = function () { 819 var args = Array.prototype.slice.call(arguments) 820 return this.eventEmitter.addListener.apply(this.eventEmitter, args) 821 } 822 823 /** 824 * Removes a handler from an event being emitted by the index. 825 * 826 * @param {String} eventName The name of events to remove the function from. 827 * @param {Function} handler The serialised set to load. 828 * @memberOf Index 829 */ 830 lunr.Index.prototype.off = function (name, fn) { 831 return this.eventEmitter.removeListener(name, fn) 832 } 833 834 /** 835 * Loads a previously serialised index. 836 * 837 * Issues a warning if the index being imported was serialised 838 * by a different version of lunr. 839 * 840 * @param {Object} serialisedData The serialised set to load. 841 * @returns {lunr.Index} 842 * @memberOf Index 843 */ 844 lunr.Index.load = function (serialisedData) { 845 if (serialisedData.version !== lunr.version) { 846 lunr.utils.warn('version mismatch: current ' + lunr.version + ' importing ' + serialisedData.version) 847 } 848 849 var idx = new this 850 851 idx._fields = serialisedData.fields 852 idx._ref = serialisedData.ref 853 854 idx.documentStore = lunr.Store.load(serialisedData.documentStore) 855 idx.tokenStore = lunr.TokenStore.load(serialisedData.tokenStore) 856 idx.corpusTokens = lunr.SortedSet.load(serialisedData.corpusTokens) 857 idx.pipeline = lunr.Pipeline.load(serialisedData.pipeline) 858 859 return idx 860 } 861 862 /** 863 * Adds a field to the list of fields that will be searchable within documents 864 * in the index. 865 * 866 * An optional boost param can be passed to affect how much tokens in this field 867 * rank in search results, by default the boost value is 1. 868 * 869 * Fields should be added before any documents are added to the index, fields 870 * that are added after documents are added to the index will only apply to new 871 * documents added to the index. 872 * 873 * @param {String} fieldName The name of the field within the document that 874 * should be indexed 875 * @param {Number} boost An optional boost that can be applied to terms in this 876 * field. 877 * @returns {lunr.Index} 878 * @memberOf Index 879 */ 880 lunr.Index.prototype.field = function (fieldName, opts) { 881 var opts = opts || {}, 882 field = { name: fieldName, boost: opts.boost || 1 } 883 884 this._fields.push(field) 885 return this 886 } 887 888 /** 889 * Sets the property used to uniquely identify documents added to the index, 890 * by default this property is 'id'. 891 * 892 * This should only be changed before adding documents to the index, changing 893 * the ref property without resetting the index can lead to unexpected results. 894 * 895 * @param {String} refName The property to use to uniquely identify the 896 * documents in the index. 897 * @param {Boolean} emitEvent Whether to emit add events, defaults to true 898 * @returns {lunr.Index} 899 * @memberOf Index 900 */ 901 lunr.Index.prototype.ref = function (refName) { 902 this._ref = refName 903 return this 904 } 905 906 /** 907 * Add a document to the index. 908 * 909 * This is the way new documents enter the index, this function will run the 910 * fields from the document through the index's pipeline and then add it to 911 * the index, it will then show up in search results. 912 * 913 * An 'add' event is emitted with the document that has been added and the index 914 * the document has been added to. This event can be silenced by passing false 915 * as the second argument to add. 916 * 917 * @param {Object} doc The document to add to the index. 918 * @param {Boolean} emitEvent Whether or not to emit events, default true. 919 * @memberOf Index 920 */ 921 lunr.Index.prototype.add = function (doc, emitEvent) { 922 var docTokens = {}, 923 allDocumentTokens = new lunr.SortedSet, 924 docRef = doc[this._ref], 925 emitEvent = emitEvent === undefined ? true : emitEvent 926 927 this._fields.forEach(function (field) { 928 var fieldTokens = this.pipeline.run(lunr.tokenizer(doc[field.name])) 929 930 docTokens[field.name] = fieldTokens 931 lunr.SortedSet.prototype.add.apply(allDocumentTokens, fieldTokens) 932 }, this) 933 934 this.documentStore.set(docRef, allDocumentTokens) 935 lunr.SortedSet.prototype.add.apply(this.corpusTokens, allDocumentTokens.toArray()) 936 937 for (var i = 0; i < allDocumentTokens.length; i++) { 938 var token = allDocumentTokens.elements[i] 939 var tf = this._fields.reduce(function (memo, field) { 940 var fieldLength = docTokens[field.name].length 941 942 if (!fieldLength) return memo 943 944 var tokenCount = docTokens[field.name].filter(function (t) { return t === token }).length 945 946 return memo + (tokenCount / fieldLength * field.boost) 947 }, 0) 948 949 this.tokenStore.add(token, { ref: docRef, tf: tf }) 950 }; 951 952 if (emitEvent) this.eventEmitter.emit('add', doc, this) 953 } 954 955 /** 956 * Removes a document from the index. 957 * 958 * To make sure documents no longer show up in search results they can be 959 * removed from the index using this method. 960 * 961 * The document passed only needs to have the same ref property value as the 962 * document that was added to the index, they could be completely different 963 * objects. 964 * 965 * A 'remove' event is emitted with the document that has been removed and the index 966 * the document has been removed from. This event can be silenced by passing false 967 * as the second argument to remove. 968 * 969 * @param {Object} doc The document to remove from the index. 970 * @param {Boolean} emitEvent Whether to emit remove events, defaults to true 971 * @memberOf Index 972 */ 973 lunr.Index.prototype.remove = function (doc, emitEvent) { 974 var docRef = doc[this._ref], 975 emitEvent = emitEvent === undefined ? true : emitEvent 976 977 if (!this.documentStore.has(docRef)) return 978 979 var docTokens = this.documentStore.get(docRef) 980 981 this.documentStore.remove(docRef) 982 983 docTokens.forEach(function (token) { 984 this.tokenStore.remove(token, docRef) 985 }, this) 986 987 if (emitEvent) this.eventEmitter.emit('remove', doc, this) 988 } 989 990 /** 991 * Updates a document in the index. 992 * 993 * When a document contained within the index gets updated, fields changed, 994 * added or removed, to make sure it correctly matched against search queries, 995 * it should be updated in the index. 996 * 997 * This method is just a wrapper around `remove` and `add` 998 * 999 * An 'update' event is emitted with the document that has been updated and the index. 1000 * This event can be silenced by passing false as the second argument to update. Only 1001 * an update event will be fired, the 'add' and 'remove' events of the underlying calls 1002 * are silenced. 1003 * 1004 * @param {Object} doc The document to update in the index. 1005 * @param {Boolean} emitEvent Whether to emit update events, defaults to true 1006 * @see Index.prototype.remove 1007 * @see Index.prototype.add 1008 * @memberOf Index 1009 */ 1010 lunr.Index.prototype.update = function (doc, emitEvent) { 1011 var emitEvent = emitEvent === undefined ? true : emitEvent 1012 1013 this.remove(doc, false) 1014 this.add(doc, false) 1015 1016 if (emitEvent) this.eventEmitter.emit('update', doc, this) 1017 } 1018 1019 /** 1020 * Calculates the inverse document frequency for a token within the index. 1021 * 1022 * @param {String} token The token to calculate the idf of. 1023 * @see Index.prototype.idf 1024 * @private 1025 * @memberOf Index 1026 */ 1027 lunr.Index.prototype.idf = function (term) { 1028 var cacheKey = "@" + term 1029 if (Object.prototype.hasOwnProperty.call(this._idfCache, cacheKey)) return this._idfCache[cacheKey] 1030 1031 var documentFrequency = this.tokenStore.count(term), 1032 idf = 1 1033 1034 if (documentFrequency > 0) { 1035 idf = 1 + Math.log(this.tokenStore.length / documentFrequency) 1036 } 1037 1038 return this._idfCache[cacheKey] = idf 1039 } 1040 1041 /** 1042 * Searches the index using the passed query. 1043 * 1044 * Queries should be a string, multiple words are allowed and will lead to an 1045 * AND based query, e.g. `idx.search('foo bar')` will run a search for 1046 * documents containing both 'foo' and 'bar'. 1047 * 1048 * All query tokens are passed through the same pipeline that document tokens 1049 * are passed through, so any language processing involved will be run on every 1050 * query term. 1051 * 1052 * Each query term is expanded, so that the term 'he' might be expanded to 1053 * 'hello' and 'help' if those terms were already included in the index. 1054 * 1055 * Matching documents are returned as an array of objects, each object contains 1056 * the matching document ref, as set for this index, and the similarity score 1057 * for this document against the query. 1058 * 1059 * @param {String} query The query to search the index with. 1060 * @returns {Object} 1061 * @see Index.prototype.idf 1062 * @see Index.prototype.documentVector 1063 * @memberOf Index 1064 */ 1065 lunr.Index.prototype.search = function (query) { 1066 var queryTokens = this.pipeline.run(lunr.tokenizer(query)), 1067 queryVector = new lunr.Vector, 1068 documentSets = [], 1069 fieldBoosts = this._fields.reduce(function (memo, f) { return memo + f.boost }, 0) 1070 1071 var hasSomeToken = queryTokens.some(function (token) { 1072 return this.tokenStore.has(token) 1073 }, this) 1074 1075 if (!hasSomeToken) return [] 1076 1077 queryTokens 1078 .forEach(function (token, i, tokens) { 1079 var tf = 1 / tokens.length * this._fields.length * fieldBoosts, 1080 self = this 1081 1082 var set = this.tokenStore.expand(token).reduce(function (memo, key) { 1083 var pos = self.corpusTokens.indexOf(key), 1084 idf = self.idf(key), 1085 similarityBoost = 1, 1086 set = new lunr.SortedSet 1087 1088 // if the expanded key is not an exact match to the token then 1089 // penalise the score for this key by how different the key is 1090 // to the token. 1091 if (key !== token) { 1092 var diff = Math.max(3, key.length - token.length) 1093 similarityBoost = 1 / Math.log(diff) 1094 } 1095 1096 // calculate the query tf-idf score for this token 1097 // applying an similarityBoost to ensure exact matches 1098 // these rank higher than expanded terms 1099 if (pos > -1) queryVector.insert(pos, tf * idf * similarityBoost) 1100 1101 // add all the documents that have this key into a set 1102 Object.keys(self.tokenStore.get(key)).forEach(function (ref) { set.add(ref) }) 1103 1104 return memo.union(set) 1105 }, new lunr.SortedSet) 1106 1107 documentSets.push(set) 1108 }, this) 1109 1110 var documentSet = documentSets.reduce(function (memo, set) { 1111 return memo.intersect(set) 1112 }) 1113 1114 return documentSet 1115 .map(function (ref) { 1116 return { ref: ref, score: queryVector.similarity(this.documentVector(ref)) } 1117 }, this) 1118 .sort(function (a, b) { 1119 return b.score - a.score 1120 }) 1121 } 1122 1123 /** 1124 * Generates a vector containing all the tokens in the document matching the 1125 * passed documentRef. 1126 * 1127 * The vector contains the tf-idf score for each token contained in the 1128 * document with the passed documentRef. The vector will contain an element 1129 * for every token in the indexes corpus, if the document does not contain that 1130 * token the element will be 0. 1131 * 1132 * @param {Object} documentRef The ref to find the document with. 1133 * @returns {lunr.Vector} 1134 * @private 1135 * @memberOf Index 1136 */ 1137 lunr.Index.prototype.documentVector = function (documentRef) { 1138 var documentTokens = this.documentStore.get(documentRef), 1139 documentTokensLength = documentTokens.length, 1140 documentVector = new lunr.Vector 1141 1142 for (var i = 0; i < documentTokensLength; i++) { 1143 var token = documentTokens.elements[i], 1144 tf = this.tokenStore.get(token)[documentRef].tf, 1145 idf = this.idf(token) 1146 1147 documentVector.insert(this.corpusTokens.indexOf(token), tf * idf) 1148 }; 1149 1150 return documentVector 1151 } 1152 1153 /** 1154 * Returns a representation of the index ready for serialisation. 1155 * 1156 * @returns {Object} 1157 * @memberOf Index 1158 */ 1159 lunr.Index.prototype.toJSON = function () { 1160 return { 1161 version: lunr.version, 1162 fields: this._fields, 1163 ref: this._ref, 1164 documentStore: this.documentStore.toJSON(), 1165 tokenStore: this.tokenStore.toJSON(), 1166 corpusTokens: this.corpusTokens.toJSON(), 1167 pipeline: this.pipeline.toJSON() 1168 } 1169 } 1170 1171 /** 1172 * Applies a plugin to the current index. 1173 * 1174 * A plugin is a function that is called with the index as its context. 1175 * Plugins can be used to customise or extend the behaviour the index 1176 * in some way. A plugin is just a function, that encapsulated the custom 1177 * behaviour that should be applied to the index. 1178 * 1179 * The plugin function will be called with the index as its argument, additional 1180 * arguments can also be passed when calling use. The function will be called 1181 * with the index as its context. 1182 * 1183 * Example: 1184 * 1185 * var myPlugin = function (idx, arg1, arg2) { 1186 * // `this` is the index to be extended 1187 * // apply any extensions etc here. 1188 * } 1189 * 1190 * var idx = lunr(function () { 1191 * this.use(myPlugin, 'arg1', 'arg2') 1192 * }) 1193 * 1194 * @param {Function} plugin The plugin to apply. 1195 * @memberOf Index 1196 */ 1197 lunr.Index.prototype.use = function (plugin) { 1198 var args = Array.prototype.slice.call(arguments, 1) 1199 args.unshift(this) 1200 plugin.apply(this, args) 1201 } 1202 /*! 1203 * lunr.Store 1204 * Copyright (C) 2014 Oliver Nightingale 1205 */ 1206 1207 /** 1208 * lunr.Store is a simple key-value store used for storing sets of tokens for 1209 * documents stored in index. 1210 * 1211 * @constructor 1212 * @module 1213 */ 1214 lunr.Store = function () { 1215 this.store = {} 1216 this.length = 0 1217 } 1218 1219 /** 1220 * Loads a previously serialised store 1221 * 1222 * @param {Object} serialisedData The serialised store to load. 1223 * @returns {lunr.Store} 1224 * @memberOf Store 1225 */ 1226 lunr.Store.load = function (serialisedData) { 1227 var store = new this 1228 1229 store.length = serialisedData.length 1230 store.store = Object.keys(serialisedData.store).reduce(function (memo, key) { 1231 memo[key] = lunr.SortedSet.load(serialisedData.store[key]) 1232 return memo 1233 }, {}) 1234 1235 return store 1236 } 1237 1238 /** 1239 * Stores the given tokens in the store against the given id. 1240 * 1241 * @param {Object} id The key used to store the tokens against. 1242 * @param {Object} tokens The tokens to store against the key. 1243 * @memberOf Store 1244 */ 1245 lunr.Store.prototype.set = function (id, tokens) { 1246 if (!this.has(id)) this.length++ 1247 this.store[id] = tokens 1248 } 1249 1250 /** 1251 * Retrieves the tokens from the store for a given key. 1252 * 1253 * @param {Object} id The key to lookup and retrieve from the store. 1254 * @returns {Object} 1255 * @memberOf Store 1256 */ 1257 lunr.Store.prototype.get = function (id) { 1258 return this.store[id] 1259 } 1260 1261 /** 1262 * Checks whether the store contains a key. 1263 * 1264 * @param {Object} id The id to look up in the store. 1265 * @returns {Boolean} 1266 * @memberOf Store 1267 */ 1268 lunr.Store.prototype.has = function (id) { 1269 return id in this.store 1270 } 1271 1272 /** 1273 * Removes the value for a key in the store. 1274 * 1275 * @param {Object} id The id to remove from the store. 1276 * @memberOf Store 1277 */ 1278 lunr.Store.prototype.remove = function (id) { 1279 if (!this.has(id)) return 1280 1281 delete this.store[id] 1282 this.length-- 1283 } 1284 1285 /** 1286 * Returns a representation of the store ready for serialisation. 1287 * 1288 * @returns {Object} 1289 * @memberOf Store 1290 */ 1291 lunr.Store.prototype.toJSON = function () { 1292 return { 1293 store: this.store, 1294 length: this.length 1295 } 1296 } 1297 1298 /*! 1299 * lunr.stemmer 1300 * Copyright (C) 2014 Oliver Nightingale 1301 * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt 1302 */ 1303 1304 /** 1305 * lunr.stemmer is an english language stemmer, this is a JavaScript 1306 * implementation of the PorterStemmer taken from http://tartaurs.org/~martin 1307 * 1308 * @module 1309 * @param {String} str The string to stem 1310 * @returns {String} 1311 * @see lunr.Pipeline 1312 */ 1313 lunr.stemmer = (function(){ 1314 var step2list = { 1315 "ational" : "ate", 1316 "tional" : "tion", 1317 "enci" : "ence", 1318 "anci" : "ance", 1319 "izer" : "ize", 1320 "bli" : "ble", 1321 "alli" : "al", 1322 "entli" : "ent", 1323 "eli" : "e", 1324 "ousli" : "ous", 1325 "ization" : "ize", 1326 "ation" : "ate", 1327 "ator" : "ate", 1328 "alism" : "al", 1329 "iveness" : "ive", 1330 "fulness" : "ful", 1331 "ousness" : "ous", 1332 "aliti" : "al", 1333 "iviti" : "ive", 1334 "biliti" : "ble", 1335 "logi" : "log" 1336 }, 1337 1338 step3list = { 1339 "icate" : "ic", 1340 "ative" : "", 1341 "alize" : "al", 1342 "iciti" : "ic", 1343 "ical" : "ic", 1344 "ful" : "", 1345 "ness" : "" 1346 }, 1347 1348 c = "[^aeiou]", // consonant 1349 v = "[aeiouy]", // vowel 1350 C = c + "[^aeiouy]*", // consonant sequence 1351 V = v + "[aeiou]*", // vowel sequence 1352 1353 mgr0 = "^(" + C + ")?" + V + C, // [C]VC... is m>0 1354 meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$", // [C]VC[V] is m=1 1355 mgr1 = "^(" + C + ")?" + V + C + V + C, // [C]VCVC... is m>1 1356 s_v = "^(" + C + ")?" + v; // vowel in stem 1357 1358 var re_mgr0 = new RegExp(mgr0); 1359 var re_mgr1 = new RegExp(mgr1); 1360 var re_meq1 = new RegExp(meq1); 1361 var re_s_v = new RegExp(s_v); 1362 1363 var re_1a = /^(.+?)(ss|i)es$/; 1364 var re2_1a = /^(.+?)([^s])s$/; 1365 var re_1b = /^(.+?)eed$/; 1366 var re2_1b = /^(.+?)(ed|ing)$/; 1367 var re_1b_2 = /.$/; 1368 var re2_1b_2 = /(at|bl|iz)$/; 1369 var re3_1b_2 = new RegExp("([^aeiouylsz])\\1$"); 1370 var re4_1b_2 = new RegExp("^" + C + v + "[^aeiouwxy]$"); 1371 1372 var re_1c = /^(.+?[^aeiou])y$/; 1373 var re_2 = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; 1374 1375 var re_3 = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; 1376 1377 var re_4 = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; 1378 var re2_4 = /^(.+?)(s|t)(ion)$/; 1379 1380 var re_5 = /^(.+?)e$/; 1381 var re_5_1 = /ll$/; 1382 var re3_5 = new RegExp("^" + C + v + "[^aeiouwxy]$"); 1383 1384 var porterStemmer = function porterStemmer(w) { 1385 var stem, 1386 suffix, 1387 firstch, 1388 re, 1389 re2, 1390 re3, 1391 re4; 1392 1393 if (w.length < 3) { return w; } 1394 1395 firstch = w.substr(0,1); 1396 if (firstch == "y") { 1397 w = firstch.toUpperCase() + w.substr(1); 1398 } 1399 1400 // Step 1a 1401 re = re_1a 1402 re2 = re2_1a; 1403 1404 if (re.test(w)) { w = w.replace(re,"$1$2"); } 1405 else if (re2.test(w)) { w = w.replace(re2,"$1$2"); } 1406 1407 // Step 1b 1408 re = re_1b; 1409 re2 = re2_1b; 1410 if (re.test(w)) { 1411 var fp = re.exec(w); 1412 re = re_mgr0; 1413 if (re.test(fp[1])) { 1414 re = re_1b_2; 1415 w = w.replace(re,""); 1416 } 1417 } else if (re2.test(w)) { 1418 var fp = re2.exec(w); 1419 stem = fp[1]; 1420 re2 = re_s_v; 1421 if (re2.test(stem)) { 1422 w = stem; 1423 re2 = re2_1b_2; 1424 re3 = re3_1b_2; 1425 re4 = re4_1b_2; 1426 if (re2.test(w)) { w = w + "e"; } 1427 else if (re3.test(w)) { re = re_1b_2; w = w.replace(re,""); } 1428 else if (re4.test(w)) { w = w + "e"; } 1429 } 1430 } 1431 1432 // 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) 1433 re = re_1c; 1434 if (re.test(w)) { 1435 var fp = re.exec(w); 1436 stem = fp[1]; 1437 w = stem + "i"; 1438 } 1439 1440 // Step 2 1441 re = re_2; 1442 if (re.test(w)) { 1443 var fp = re.exec(w); 1444 stem = fp[1]; 1445 suffix = fp[2]; 1446 re = re_mgr0; 1447 if (re.test(stem)) { 1448 w = stem + step2list[suffix]; 1449 } 1450 } 1451 1452 // Step 3 1453 re = re_3; 1454 if (re.test(w)) { 1455 var fp = re.exec(w); 1456 stem = fp[1]; 1457 suffix = fp[2]; 1458 re = re_mgr0; 1459 if (re.test(stem)) { 1460 w = stem + step3list[suffix]; 1461 } 1462 } 1463 1464 // Step 4 1465 re = re_4; 1466 re2 = re2_4; 1467 if (re.test(w)) { 1468 var fp = re.exec(w); 1469 stem = fp[1]; 1470 re = re_mgr1; 1471 if (re.test(stem)) { 1472 w = stem; 1473 } 1474 } else if (re2.test(w)) { 1475 var fp = re2.exec(w); 1476 stem = fp[1] + fp[2]; 1477 re2 = re_mgr1; 1478 if (re2.test(stem)) { 1479 w = stem; 1480 } 1481 } 1482 1483 // Step 5 1484 re = re_5; 1485 if (re.test(w)) { 1486 var fp = re.exec(w); 1487 stem = fp[1]; 1488 re = re_mgr1; 1489 re2 = re_meq1; 1490 re3 = re3_5; 1491 if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) { 1492 w = stem; 1493 } 1494 } 1495 1496 re = re_5_1; 1497 re2 = re_mgr1; 1498 if (re.test(w) && re2.test(w)) { 1499 re = re_1b_2; 1500 w = w.replace(re,""); 1501 } 1502 1503 // and turn initial Y back to y 1504 1505 if (firstch == "y") { 1506 w = firstch.toLowerCase() + w.substr(1); 1507 } 1508 1509 return w; 1510 }; 1511 1512 return porterStemmer; 1513 })(); 1514 1515 lunr.Pipeline.registerFunction(lunr.stemmer, 'stemmer') 1516 /*! 1517 * lunr.stopWordFilter 1518 * Copyright (C) 2014 Oliver Nightingale 1519 */ 1520 1521 /** 1522 * lunr.stopWordFilter is an English language stop word list filter, any words 1523 * contained in the list will not be passed through the filter. 1524 * 1525 * This is intended to be used in the Pipeline. If the token does not pass the 1526 * filter then undefined will be returned. 1527 * 1528 * @module 1529 * @param {String} token The token to pass through the filter 1530 * @returns {String} 1531 * @see lunr.Pipeline 1532 */ 1533 lunr.stopWordFilter = function (token) { 1534 if (lunr.stopWordFilter.stopWords.indexOf(token) === -1) return token 1535 } 1536 1537 lunr.stopWordFilter.stopWords = new lunr.SortedSet 1538 lunr.stopWordFilter.stopWords.length = 119 1539 lunr.stopWordFilter.stopWords.elements = [ 1540 "", 1541 "a", 1542 "able", 1543 "about", 1544 "across", 1545 "after", 1546 "all", 1547 "almost", 1548 "also", 1549 "am", 1550 "among", 1551 "an", 1552 "and", 1553 "any", 1554 "are", 1555 "as", 1556 "at", 1557 "be", 1558 "because", 1559 "been", 1560 "but", 1561 "by", 1562 "can", 1563 "cannot", 1564 "could", 1565 "dear", 1566 "did", 1567 "do", 1568 "does", 1569 "either", 1570 "else", 1571 "ever", 1572 "every", 1573 "for", 1574 "from", 1575 "get", 1576 "got", 1577 "had", 1578 "has", 1579 "have", 1580 "he", 1581 "her", 1582 "hers", 1583 "him", 1584 "his", 1585 "how", 1586 "however", 1587 "i", 1588 "if", 1589 "in", 1590 "into", 1591 "is", 1592 "it", 1593 "its", 1594 "just", 1595 "least", 1596 "let", 1597 "like", 1598 "likely", 1599 "may", 1600 "me", 1601 "might", 1602 "most", 1603 "must", 1604 "my", 1605 "neither", 1606 "no", 1607 "nor", 1608 "not", 1609 "of", 1610 "off", 1611 "often", 1612 "on", 1613 "only", 1614 "or", 1615 "other", 1616 "our", 1617 "own", 1618 "rather", 1619 "said", 1620 "say", 1621 "says", 1622 "she", 1623 "should", 1624 "since", 1625 "so", 1626 "some", 1627 "than", 1628 "that", 1629 "the", 1630 "their", 1631 "them", 1632 "then", 1633 "there", 1634 "these", 1635 "they", 1636 "this", 1637 "tis", 1638 "to", 1639 "too", 1640 "twas", 1641 "us", 1642 "wants", 1643 "was", 1644 "we", 1645 "were", 1646 "what", 1647 "when", 1648 "where", 1649 "which", 1650 "while", 1651 "who", 1652 "whom", 1653 "why", 1654 "will", 1655 "with", 1656 "would", 1657 "yet", 1658 "you", 1659 "your" 1660 ] 1661 1662 lunr.Pipeline.registerFunction(lunr.stopWordFilter, 'stopWordFilter') 1663 /*! 1664 * lunr.trimmer 1665 * Copyright (C) 2014 Oliver Nightingale 1666 */ 1667 1668 /** 1669 * lunr.trimmer is a pipeline function for trimming non word 1670 * characters from the begining and end of tokens before they 1671 * enter the index. 1672 * 1673 * This implementation may not work correctly for non latin 1674 * characters and should either be removed or adapted for use 1675 * with languages with non-latin characters. 1676 * 1677 * @module 1678 * @param {String} token The token to pass through the filter 1679 * @returns {String} 1680 * @see lunr.Pipeline 1681 */ 1682 lunr.trimmer = function (token) { 1683 return token 1684 .replace(/^\W+/, '') 1685 .replace(/\W+$/, '') 1686 } 1687 1688 lunr.Pipeline.registerFunction(lunr.trimmer, 'trimmer') 1689 /*! 1690 * lunr.stemmer 1691 * Copyright (C) 2014 Oliver Nightingale 1692 * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt 1693 */ 1694 1695 /** 1696 * lunr.TokenStore is used for efficient storing and lookup of the reverse 1697 * index of token to document ref. 1698 * 1699 * @constructor 1700 */ 1701 lunr.TokenStore = function () { 1702 this.root = { docs: {} } 1703 this.length = 0 1704 } 1705 1706 /** 1707 * Loads a previously serialised token store 1708 * 1709 * @param {Object} serialisedData The serialised token store to load. 1710 * @returns {lunr.TokenStore} 1711 * @memberOf TokenStore 1712 */ 1713 lunr.TokenStore.load = function (serialisedData) { 1714 var store = new this 1715 1716 store.root = serialisedData.root 1717 store.length = serialisedData.length 1718 1719 return store 1720 } 1721 1722 /** 1723 * Adds a new token doc pair to the store. 1724 * 1725 * By default this function starts at the root of the current store, however 1726 * it can start at any node of any token store if required. 1727 * 1728 * @param {String} token The token to store the doc under 1729 * @param {Object} doc The doc to store against the token 1730 * @param {Object} root An optional node at which to start looking for the 1731 * correct place to enter the doc, by default the root of this lunr.TokenStore 1732 * is used. 1733 * @memberOf TokenStore 1734 */ 1735 lunr.TokenStore.prototype.add = function (token, doc, root) { 1736 var root = root || this.root, 1737 key = token[0], 1738 rest = token.slice(1) 1739 1740 if (!(key in root)) root[key] = {docs: {}} 1741 1742 if (rest.length === 0) { 1743 root[key].docs[doc.ref] = doc 1744 this.length += 1 1745 return 1746 } else { 1747 return this.add(rest, doc, root[key]) 1748 } 1749 } 1750 1751 /** 1752 * Checks whether this key is contained within this lunr.TokenStore. 1753 * 1754 * By default this function starts at the root of the current store, however 1755 * it can start at any node of any token store if required. 1756 * 1757 * @param {String} token The token to check for 1758 * @param {Object} root An optional node at which to start 1759 * @memberOf TokenStore 1760 */ 1761 lunr.TokenStore.prototype.has = function (token) { 1762 if (!token) return false 1763 1764 var node = this.root 1765 1766 for (var i = 0; i < token.length; i++) { 1767 if (!node[token[i]]) return false 1768 1769 node = node[token[i]] 1770 } 1771 1772 return true 1773 } 1774 1775 /** 1776 * Retrieve a node from the token store for a given token. 1777 * 1778 * By default this function starts at the root of the current store, however 1779 * it can start at any node of any token store if required. 1780 * 1781 * @param {String} token The token to get the node for. 1782 * @param {Object} root An optional node at which to start. 1783 * @returns {Object} 1784 * @see TokenStore.prototype.get 1785 * @memberOf TokenStore 1786 */ 1787 lunr.TokenStore.prototype.getNode = function (token) { 1788 if (!token) return {} 1789 1790 var node = this.root 1791 1792 for (var i = 0; i < token.length; i++) { 1793 if (!node[token[i]]) return {} 1794 1795 node = node[token[i]] 1796 } 1797 1798 return node 1799 } 1800 1801 /** 1802 * Retrieve the documents for a node for the given token. 1803 * 1804 * By default this function starts at the root of the current store, however 1805 * it can start at any node of any token store if required. 1806 * 1807 * @param {String} token The token to get the documents for. 1808 * @param {Object} root An optional node at which to start. 1809 * @returns {Object} 1810 * @memberOf TokenStore 1811 */ 1812 lunr.TokenStore.prototype.get = function (token, root) { 1813 return this.getNode(token, root).docs || {} 1814 } 1815 1816 lunr.TokenStore.prototype.count = function (token, root) { 1817 return Object.keys(this.get(token, root)).length 1818 } 1819 1820 /** 1821 * Remove the document identified by ref from the token in the store. 1822 * 1823 * By default this function starts at the root of the current store, however 1824 * it can start at any node of any token store if required. 1825 * 1826 * @param {String} token The token to get the documents for. 1827 * @param {String} ref The ref of the document to remove from this token. 1828 * @param {Object} root An optional node at which to start. 1829 * @returns {Object} 1830 * @memberOf TokenStore 1831 */ 1832 lunr.TokenStore.prototype.remove = function (token, ref) { 1833 if (!token) return 1834 var node = this.root 1835 1836 for (var i = 0; i < token.length; i++) { 1837 if (!(token[i] in node)) return 1838 node = node[token[i]] 1839 } 1840 1841 delete node.docs[ref] 1842 } 1843 1844 /** 1845 * Find all the possible suffixes of the passed token using tokens 1846 * currently in the store. 1847 * 1848 * @param {String} token The token to expand. 1849 * @returns {Array} 1850 * @memberOf TokenStore 1851 */ 1852 lunr.TokenStore.prototype.expand = function (token, memo) { 1853 var root = this.getNode(token), 1854 docs = root.docs || {}, 1855 memo = memo || [] 1856 1857 if (Object.keys(docs).length) memo.push(token) 1858 1859 Object.keys(root) 1860 .forEach(function (key) { 1861 if (key === 'docs') return 1862 1863 memo.concat(this.expand(token + key, memo)) 1864 }, this) 1865 1866 return memo 1867 } 1868 1869 /** 1870 * Returns a representation of the token store ready for serialisation. 1871 * 1872 * @returns {Object} 1873 * @memberOf TokenStore 1874 */ 1875 lunr.TokenStore.prototype.toJSON = function () { 1876 return { 1877 root: this.root, 1878 length: this.length 1879 } 1880 } 1881 1882 1883 /** 1884 * export the module via AMD, CommonJS or as a browser global 1885 * Export code from https://github.com/umdjs/umd/blob/master/returnExports.js 1886 */ 1887 ;(function (root, factory) { 1888 if (typeof define === 'function' && define.amd) { 1889 // AMD. Register as an anonymous module. 1890 define(factory) 1891 } else if (typeof exports === 'object') { 1892 /** 1893 * Node. Does not work with strict CommonJS, but 1894 * only CommonJS-like enviroments that support module.exports, 1895 * like Node. 1896 */ 1897 module.exports = factory() 1898 } else { 1899 // Browser globals (root is window) 1900 root.lunr = factory() 1901 } 1902 }(this, function () { 1903 /** 1904 * Just return a value to define the module export. 1905 * This example returns an object, but the module 1906 * can return a function as the exported value. 1907 */ 1908 return lunr 1909 })) 1910 })()