1/* 2 * searchtools.js_t 3 * ~~~~~~~~~~~~~~~~ 4 * 5 * Sphinx JavaScript utilities for the full-text search. 6 * 7 * :copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS. 8 * :license: BSD, see LICENSE for details. 9 * 10 */ 11 12 13/* Non-minified version JS is _stemmer.js if file is provided */ 14/** 15 * Porter Stemmer 16 */ 17var Stemmer = function() { 18 19 var step2list = { 20 ational: 'ate', 21 tional: 'tion', 22 enci: 'ence', 23 anci: 'ance', 24 izer: 'ize', 25 bli: 'ble', 26 alli: 'al', 27 entli: 'ent', 28 eli: 'e', 29 ousli: 'ous', 30 ization: 'ize', 31 ation: 'ate', 32 ator: 'ate', 33 alism: 'al', 34 iveness: 'ive', 35 fulness: 'ful', 36 ousness: 'ous', 37 aliti: 'al', 38 iviti: 'ive', 39 biliti: 'ble', 40 logi: 'log' 41 }; 42 43 var step3list = { 44 icate: 'ic', 45 ative: '', 46 alize: 'al', 47 iciti: 'ic', 48 ical: 'ic', 49 ful: '', 50 ness: '' 51 }; 52 53 var c = "[^aeiou]"; // consonant 54 var v = "[aeiouy]"; // vowel 55 var C = c + "[^aeiouy]*"; // consonant sequence 56 var V = v + "[aeiou]*"; // vowel sequence 57 58 var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 59 var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 60 var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 61 var s_v = "^(" + C + ")?" + v; // vowel in stem 62 63 this.stemWord = function (w) { 64 var stem; 65 var suffix; 66 var firstch; 67 var origword = w; 68 69 if (w.length < 3) 70 return w; 71 72 var re; 73 var re2; 74 var re3; 75 var re4; 76 77 firstch = w.substr(0,1); 78 if (firstch == "y") 79 w = firstch.toUpperCase() + w.substr(1); 80 81 // Step 1a 82 re = /^(.+?)(ss|i)es$/; 83 re2 = /^(.+?)([^s])s$/; 84 85 if (re.test(w)) 86 w = w.replace(re,"$1$2"); 87 else if (re2.test(w)) 88 w = w.replace(re2,"$1$2"); 89 90 // Step 1b 91 re = /^(.+?)eed$/; 92 re2 = /^(.+?)(ed|ing)$/; 93 if (re.test(w)) { 94 var fp = re.exec(w); 95 re = new RegExp(mgr0); 96 if (re.test(fp[1])) { 97 re = /.$/; 98 w = w.replace(re,""); 99 } 100 } 101 else if (re2.test(w)) { 102 var fp = re2.exec(w); 103 stem = fp[1]; 104 re2 = new RegExp(s_v); 105 if (re2.test(stem)) { 106 w = stem; 107 re2 = /(at|bl|iz)$/; 108 re3 = new RegExp("([^aeiouylsz])\\1$"); 109 re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); 110 if (re2.test(w)) 111 w = w + "e"; 112 else if (re3.test(w)) { 113 re = /.$/; 114 w = w.replace(re,""); 115 } 116 else if (re4.test(w)) 117 w = w + "e"; 118 } 119 } 120 121 // Step 1c 122 re = /^(.+?)y$/; 123 if (re.test(w)) { 124 var fp = re.exec(w); 125 stem = fp[1]; 126 re = new RegExp(s_v); 127 if (re.test(stem)) 128 w = stem + "i"; 129 } 130 131 // Step 2 132 re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; 133 if (re.test(w)) { 134 var fp = re.exec(w); 135 stem = fp[1]; 136 suffix = fp[2]; 137 re = new RegExp(mgr0); 138 if (re.test(stem)) 139 w = stem + step2list[suffix]; 140 } 141 142 // Step 3 143 re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; 144 if (re.test(w)) { 145 var fp = re.exec(w); 146 stem = fp[1]; 147 suffix = fp[2]; 148 re = new RegExp(mgr0); 149 if (re.test(stem)) 150 w = stem + step3list[suffix]; 151 } 152 153 // Step 4 154 re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; 155 re2 = /^(.+?)(s|t)(ion)$/; 156 if (re.test(w)) { 157 var fp = re.exec(w); 158 stem = fp[1]; 159 re = new RegExp(mgr1); 160 if (re.test(stem)) 161 w = stem; 162 } 163 else if (re2.test(w)) { 164 var fp = re2.exec(w); 165 stem = fp[1] + fp[2]; 166 re2 = new RegExp(mgr1); 167 if (re2.test(stem)) 168 w = stem; 169 } 170 171 // Step 5 172 re = /^(.+?)e$/; 173 if (re.test(w)) { 174 var fp = re.exec(w); 175 stem = fp[1]; 176 re = new RegExp(mgr1); 177 re2 = new RegExp(meq1); 178 re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); 179 if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) 180 w = stem; 181 } 182 re = /ll$/; 183 re2 = new RegExp(mgr1); 184 if (re.test(w) && re2.test(w)) { 185 re = /.$/; 186 w = w.replace(re,""); 187 } 188 189 // and turn initial Y back to y 190 if (firstch == "y") 191 w = firstch.toLowerCase() + w.substr(1); 192 return w; 193 } 194} 195 196 197 198/** 199 * Simple result scoring code. 200 */ 201var Scorer = { 202 // Implement the following function to further tweak the score for each result 203 // The function takes a result array [filename, title, anchor, descr, score] 204 // and returns the new score. 205 /* 206 score: function(result) { 207 return result[4]; 208 }, 209 */ 210 211 // query matches the full name of an object 212 objNameMatch: 11, 213 // or matches in the last dotted part of the object name 214 objPartialMatch: 6, 215 // Additive scores depending on the priority of the object 216 objPrio: {0: 15, // used to be importantResults 217 1: 5, // used to be objectResults 218 2: -5}, // used to be unimportantResults 219 // Used when the priority is not in the mapping. 220 objPrioDefault: 0, 221 222 // query found in title 223 title: 15, 224 // query found in terms 225 term: 5 226}; 227 228 229 230 231 232var splitChars = (function() { 233 var result = {}; 234 var singles = [96, 180, 187, 191, 215, 247, 749, 885, 903, 907, 909, 930, 1014, 1648, 235 1748, 1809, 2416, 2473, 2481, 2526, 2601, 2609, 2612, 2615, 2653, 2702, 236 2706, 2729, 2737, 2740, 2857, 2865, 2868, 2910, 2928, 2948, 2961, 2971, 237 2973, 3085, 3089, 3113, 3124, 3213, 3217, 3241, 3252, 3295, 3341, 3345, 238 3369, 3506, 3516, 3633, 3715, 3721, 3736, 3744, 3748, 3750, 3756, 3761, 239 3781, 3912, 4239, 4347, 4681, 4695, 4697, 4745, 4785, 4799, 4801, 4823, 240 4881, 5760, 5901, 5997, 6313, 7405, 8024, 8026, 8028, 8030, 8117, 8125, 241 8133, 8181, 8468, 8485, 8487, 8489, 8494, 8527, 11311, 11359, 11687, 11695, 242 11703, 11711, 11719, 11727, 11735, 12448, 12539, 43010, 43014, 43019, 43587, 243 43696, 43713, 64286, 64297, 64311, 64317, 64319, 64322, 64325, 65141]; 244 var i, j, start, end; 245 for (i = 0; i < singles.length; i++) { 246 result[singles[i]] = true; 247 } 248 var ranges = [[0, 47], [58, 64], [91, 94], [123, 169], [171, 177], [182, 184], [706, 709], 249 [722, 735], [741, 747], [751, 879], [888, 889], [894, 901], [1154, 1161], 250 [1318, 1328], [1367, 1368], [1370, 1376], [1416, 1487], [1515, 1519], [1523, 1568], 251 [1611, 1631], [1642, 1645], [1750, 1764], [1767, 1773], [1789, 1790], [1792, 1807], 252 [1840, 1868], [1958, 1968], [1970, 1983], [2027, 2035], [2038, 2041], [2043, 2047], 253 [2070, 2073], [2075, 2083], [2085, 2087], [2089, 2307], [2362, 2364], [2366, 2383], 254 [2385, 2391], [2402, 2405], [2419, 2424], [2432, 2436], [2445, 2446], [2449, 2450], 255 [2483, 2485], [2490, 2492], [2494, 2509], [2511, 2523], [2530, 2533], [2546, 2547], 256 [2554, 2564], [2571, 2574], [2577, 2578], [2618, 2648], [2655, 2661], [2672, 2673], 257 [2677, 2692], [2746, 2748], [2750, 2767], [2769, 2783], [2786, 2789], [2800, 2820], 258 [2829, 2830], [2833, 2834], [2874, 2876], [2878, 2907], [2914, 2917], [2930, 2946], 259 [2955, 2957], [2966, 2968], [2976, 2978], [2981, 2983], [2987, 2989], [3002, 3023], 260 [3025, 3045], [3059, 3076], [3130, 3132], [3134, 3159], [3162, 3167], [3170, 3173], 261 [3184, 3191], [3199, 3204], [3258, 3260], [3262, 3293], [3298, 3301], [3312, 3332], 262 [3386, 3388], [3390, 3423], [3426, 3429], [3446, 3449], [3456, 3460], [3479, 3481], 263 [3518, 3519], [3527, 3584], [3636, 3647], [3655, 3663], [3674, 3712], [3717, 3718], 264 [3723, 3724], [3726, 3731], [3752, 3753], [3764, 3772], [3774, 3775], [3783, 3791], 265 [3802, 3803], [3806, 3839], [3841, 3871], [3892, 3903], [3949, 3975], [3980, 4095], 266 [4139, 4158], [4170, 4175], [4182, 4185], [4190, 4192], [4194, 4196], [4199, 4205], 267 [4209, 4212], [4226, 4237], [4250, 4255], [4294, 4303], [4349, 4351], [4686, 4687], 268 [4702, 4703], [4750, 4751], [4790, 4791], [4806, 4807], [4886, 4887], [4955, 4968], 269 [4989, 4991], [5008, 5023], [5109, 5120], [5741, 5742], [5787, 5791], [5867, 5869], 270 [5873, 5887], [5906, 5919], [5938, 5951], [5970, 5983], [6001, 6015], [6068, 6102], 271 [6104, 6107], [6109, 6111], [6122, 6127], [6138, 6159], [6170, 6175], [6264, 6271], 272 [6315, 6319], [6390, 6399], [6429, 6469], [6510, 6511], [6517, 6527], [6572, 6592], 273 [6600, 6607], [6619, 6655], [6679, 6687], [6741, 6783], [6794, 6799], [6810, 6822], 274 [6824, 6916], [6964, 6980], [6988, 6991], [7002, 7042], [7073, 7085], [7098, 7167], 275 [7204, 7231], [7242, 7244], [7294, 7400], [7410, 7423], [7616, 7679], [7958, 7959], 276 [7966, 7967], [8006, 8007], [8014, 8015], [8062, 8063], [8127, 8129], [8141, 8143], 277 [8148, 8149], [8156, 8159], [8173, 8177], [8189, 8303], [8306, 8307], [8314, 8318], 278 [8330, 8335], [8341, 8449], [8451, 8454], [8456, 8457], [8470, 8472], [8478, 8483], 279 [8506, 8507], [8512, 8516], [8522, 8525], [8586, 9311], [9372, 9449], [9472, 10101], 280 [10132, 11263], [11493, 11498], [11503, 11516], [11518, 11519], [11558, 11567], 281 [11622, 11630], [11632, 11647], [11671, 11679], [11743, 11822], [11824, 12292], 282 [12296, 12320], [12330, 12336], [12342, 12343], [12349, 12352], [12439, 12444], 283 [12544, 12548], [12590, 12592], [12687, 12689], [12694, 12703], [12728, 12783], 284 [12800, 12831], [12842, 12880], [12896, 12927], [12938, 12976], [12992, 13311], 285 [19894, 19967], [40908, 40959], [42125, 42191], [42238, 42239], [42509, 42511], 286 [42540, 42559], [42592, 42593], [42607, 42622], [42648, 42655], [42736, 42774], 287 [42784, 42785], [42889, 42890], [42893, 43002], [43043, 43055], [43062, 43071], 288 [43124, 43137], [43188, 43215], [43226, 43249], [43256, 43258], [43260, 43263], 289 [43302, 43311], [43335, 43359], [43389, 43395], [43443, 43470], [43482, 43519], 290 [43561, 43583], [43596, 43599], [43610, 43615], [43639, 43641], [43643, 43647], 291 [43698, 43700], [43703, 43704], [43710, 43711], [43715, 43738], [43742, 43967], 292 [44003, 44015], [44026, 44031], [55204, 55215], [55239, 55242], [55292, 55295], 293 [57344, 63743], [64046, 64047], [64110, 64111], [64218, 64255], [64263, 64274], 294 [64280, 64284], [64434, 64466], [64830, 64847], [64912, 64913], [64968, 65007], 295 [65020, 65135], [65277, 65295], [65306, 65312], [65339, 65344], [65371, 65381], 296 [65471, 65473], [65480, 65481], [65488, 65489], [65496, 65497]]; 297 for (i = 0; i < ranges.length; i++) { 298 start = ranges[i][0]; 299 end = ranges[i][1]; 300 for (j = start; j <= end; j++) { 301 result[j] = true; 302 } 303 } 304 return result; 305})(); 306 307function splitQuery(query) { 308 var result = []; 309 var start = -1; 310 for (var i = 0; i < query.length; i++) { 311 if (splitChars[query.charCodeAt(i)]) { 312 if (start !== -1) { 313 result.push(query.slice(start, i)); 314 start = -1; 315 } 316 } else if (start === -1) { 317 start = i; 318 } 319 } 320 if (start !== -1) { 321 result.push(query.slice(start)); 322 } 323 return result; 324} 325 326 327 328 329/** 330 * Search Module 331 */ 332var Search = { 333 334 _index : null, 335 _queued_query : null, 336 _pulse_status : -1, 337 338 init : function() { 339 var params = $.getQueryParameters(); 340 if (params.q) { 341 var query = params.q[0]; 342 $('input[name="q"]')[0].value = query; 343 this.performSearch(query); 344 } 345 }, 346 347 loadIndex : function(url) { 348 $.ajax({type: "GET", url: url, data: null, 349 dataType: "script", cache: true, 350 complete: function(jqxhr, textstatus) { 351 if (textstatus != "success") { 352 document.getElementById("searchindexloader").src = url; 353 } 354 }}); 355 }, 356 357 setIndex : function(index) { 358 var q; 359 this._index = index; 360 if ((q = this._queued_query) !== null) { 361 this._queued_query = null; 362 Search.query(q); 363 } 364 }, 365 366 hasIndex : function() { 367 return this._index !== null; 368 }, 369 370 deferQuery : function(query) { 371 this._queued_query = query; 372 }, 373 374 stopPulse : function() { 375 this._pulse_status = 0; 376 }, 377 378 startPulse : function() { 379 if (this._pulse_status >= 0) 380 return; 381 function pulse() { 382 var i; 383 Search._pulse_status = (Search._pulse_status + 1) % 4; 384 var dotString = ''; 385 for (i = 0; i < Search._pulse_status; i++) 386 dotString += '.'; 387 Search.dots.text(dotString); 388 if (Search._pulse_status > -1) 389 window.setTimeout(pulse, 500); 390 } 391 pulse(); 392 }, 393 394 /** 395 * perform a search for something (or wait until index is loaded) 396 */ 397 performSearch : function(query) { 398 // create the required interface elements 399 this.out = $('#search-results'); 400 this.title = $('<h2>' + _('Searching') + '</h2>').appendTo(this.out); 401 this.dots = $('<span></span>').appendTo(this.title); 402 this.status = $('<p style="display: none"></p>').appendTo(this.out); 403 this.output = $('<ul class="search"/>').appendTo(this.out); 404 405 $('#search-progress').text(_('Preparing search...')); 406 this.startPulse(); 407 408 // index already loaded, the browser was quick! 409 if (this.hasIndex()) 410 this.query(query); 411 else 412 this.deferQuery(query); 413 }, 414 415 /** 416 * execute search (requires search index to be loaded) 417 */ 418 query : function(query) { 419 var i; 420 var stopwords = ["a","and","are","as","at","be","but","by","for","if","in","into","is","it","near","no","not","of","on","or","such","that","the","their","then","there","these","they","this","to","was","will","with"]; 421 422 // stem the searchterms and add them to the correct list 423 var stemmer = new Stemmer(); 424 var searchterms = []; 425 var excluded = []; 426 var hlterms = []; 427 var tmp = splitQuery(query); 428 var objectterms = []; 429 for (i = 0; i < tmp.length; i++) { 430 if (tmp[i] !== "") { 431 objectterms.push(tmp[i].toLowerCase()); 432 } 433 434 if ($u.indexOf(stopwords, tmp[i].toLowerCase()) != -1 || tmp[i].match(/^\d+$/) || 435 tmp[i] === "") { 436 // skip this "word" 437 continue; 438 } 439 // stem the word 440 var word = stemmer.stemWord(tmp[i].toLowerCase()); 441 // prevent stemmer from cutting word smaller than two chars 442 if(word.length < 3 && tmp[i].length >= 3) { 443 word = tmp[i]; 444 } 445 var toAppend; 446 // select the correct list 447 if (word[0] == '-') { 448 toAppend = excluded; 449 word = word.substr(1); 450 } 451 else { 452 toAppend = searchterms; 453 hlterms.push(tmp[i].toLowerCase()); 454 } 455 // only add if not already in the list 456 if (!$u.contains(toAppend, word)) 457 toAppend.push(word); 458 } 459 var highlightstring = '?highlight=' + $.urlencode(hlterms.join(" ")); 460 461 // console.debug('SEARCH: searching for:'); 462 // console.info('required: ', searchterms); 463 // console.info('excluded: ', excluded); 464 465 // prepare search 466 var terms = this._index.terms; 467 var titleterms = this._index.titleterms; 468 469 // array of [filename, title, anchor, descr, score] 470 var results = []; 471 $('#search-progress').empty(); 472 473 // lookup as object 474 for (i = 0; i < objectterms.length; i++) { 475 var others = [].concat(objectterms.slice(0, i), 476 objectterms.slice(i+1, objectterms.length)); 477 results = results.concat(this.performObjectSearch(objectterms[i], others)); 478 } 479 480 // lookup as search terms in fulltext 481 results = results.concat(this.performTermsSearch(searchterms, excluded, terms, titleterms)); 482 483 // let the scorer override scores with a custom scoring function 484 if (Scorer.score) { 485 for (i = 0; i < results.length; i++) 486 results[i][4] = Scorer.score(results[i]); 487 } 488 489 // now sort the results by score (in opposite order of appearance, since the 490 // display function below uses pop() to retrieve items) and then 491 // alphabetically 492 results.sort(function(a, b) { 493 var left = a[4]; 494 var right = b[4]; 495 if (left > right) { 496 return 1; 497 } else if (left < right) { 498 return -1; 499 } else { 500 // same score: sort alphabetically 501 left = a[1].toLowerCase(); 502 right = b[1].toLowerCase(); 503 return (left > right) ? -1 : ((left < right) ? 1 : 0); 504 } 505 }); 506 507 // for debugging 508 //Search.lastresults = results.slice(); // a copy 509 //console.info('search results:', Search.lastresults); 510 511 // print the results 512 var resultCount = results.length; 513 function displayNextItem() { 514 // results left, load the summary and display it 515 if (results.length) { 516 var item = results.pop(); 517 var listItem = $('<li style="display:none"></li>'); 518 if (DOCUMENTATION_OPTIONS.FILE_SUFFIX === '') { 519 // dirhtml builder 520 var dirname = item[0] + '/'; 521 if (dirname.match(/\/index\/$/)) { 522 dirname = dirname.substring(0, dirname.length-6); 523 } else if (dirname == 'index/') { 524 dirname = ''; 525 } 526 listItem.append($('<a/>').attr('href', 527 DOCUMENTATION_OPTIONS.URL_ROOT + dirname + 528 highlightstring + item[2]).html(item[1])); 529 } else { 530 // normal html builders 531 listItem.append($('<a/>').attr('href', 532 item[0] + DOCUMENTATION_OPTIONS.FILE_SUFFIX + 533 highlightstring + item[2]).html(item[1])); 534 } 535 if (item[3]) { 536 listItem.append($('<span> (' + item[3] + ')</span>')); 537 Search.output.append(listItem); 538 listItem.slideDown(5, function() { 539 displayNextItem(); 540 }); 541 } else if (DOCUMENTATION_OPTIONS.HAS_SOURCE) { 542 var suffix = DOCUMENTATION_OPTIONS.SOURCELINK_SUFFIX; 543 $.ajax({url: DOCUMENTATION_OPTIONS.URL_ROOT + '_sources/' + item[5] + (item[5].slice(-suffix.length) === suffix ? '' : suffix), 544 dataType: "text", 545 complete: function(jqxhr, textstatus) { 546 var data = jqxhr.responseText; 547 if (data !== '' && data !== undefined) { 548 listItem.append(Search.makeSearchSummary(data, searchterms, hlterms)); 549 } 550 Search.output.append(listItem); 551 listItem.slideDown(5, function() { 552 displayNextItem(); 553 }); 554 }}); 555 } else { 556 // no source available, just display title 557 Search.output.append(listItem); 558 listItem.slideDown(5, function() { 559 displayNextItem(); 560 }); 561 } 562 } 563 // search finished, update title and status message 564 else { 565 Search.stopPulse(); 566 Search.title.text(_('Search Results')); 567 if (!resultCount) 568 Search.status.text(_('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve selected enough categories.')); 569 else 570 Search.status.text(_('Search finished, found %s page(s) matching the search query.').replace('%s', resultCount)); 571 Search.status.fadeIn(500); 572 } 573 } 574 displayNextItem(); 575 }, 576 577 /** 578 * search for object names 579 */ 580 performObjectSearch : function(object, otherterms) { 581 var filenames = this._index.filenames; 582 var docnames = this._index.docnames; 583 var objects = this._index.objects; 584 var objnames = this._index.objnames; 585 var titles = this._index.titles; 586 587 var i; 588 var results = []; 589 590 for (var prefix in objects) { 591 for (var name in objects[prefix]) { 592 var fullname = (prefix ? prefix + '.' : '') + name; 593 if (fullname.toLowerCase().indexOf(object) > -1) { 594 var score = 0; 595 var parts = fullname.split('.'); 596 // check for different match types: exact matches of full name or 597 // "last name" (i.e. last dotted part) 598 if (fullname == object || parts[parts.length - 1] == object) { 599 score += Scorer.objNameMatch; 600 // matches in last name 601 } else if (parts[parts.length - 1].indexOf(object) > -1) { 602 score += Scorer.objPartialMatch; 603 } 604 var match = objects[prefix][name]; 605 var objname = objnames[match[1]][2]; 606 var title = titles[match[0]]; 607 // If more than one term searched for, we require other words to be 608 // found in the name/title/description 609 if (otherterms.length > 0) { 610 var haystack = (prefix + ' ' + name + ' ' + 611 objname + ' ' + title).toLowerCase(); 612 var allfound = true; 613 for (i = 0; i < otherterms.length; i++) { 614 if (haystack.indexOf(otherterms[i]) == -1) { 615 allfound = false; 616 break; 617 } 618 } 619 if (!allfound) { 620 continue; 621 } 622 } 623 var descr = objname + _(', in ') + title; 624 625 var anchor = match[3]; 626 if (anchor === '') 627 anchor = fullname; 628 else if (anchor == '-') 629 anchor = objnames[match[1]][1] + '-' + fullname; 630 // add custom score for some objects according to scorer 631 if (Scorer.objPrio.hasOwnProperty(match[2])) { 632 score += Scorer.objPrio[match[2]]; 633 } else { 634 score += Scorer.objPrioDefault; 635 } 636 results.push([docnames[match[0]], fullname, '#'+anchor, descr, score, filenames[match[0]]]); 637 } 638 } 639 } 640 641 return results; 642 }, 643 644 /** 645 * search for full-text terms in the index 646 */ 647 performTermsSearch : function(searchterms, excluded, terms, titleterms) { 648 var docnames = this._index.docnames; 649 var filenames = this._index.filenames; 650 var titles = this._index.titles; 651 652 var i, j, file; 653 var fileMap = {}; 654 var scoreMap = {}; 655 var results = []; 656 657 // perform the search on the required terms 658 for (i = 0; i < searchterms.length; i++) { 659 var word = searchterms[i]; 660 var files = []; 661 var _o = [ 662 {files: terms[word], score: Scorer.term}, 663 {files: titleterms[word], score: Scorer.title} 664 ]; 665 666 // no match but word was a required one 667 if ($u.every(_o, function(o){return o.files === undefined;})) { 668 break; 669 } 670 // found search word in contents 671 $u.each(_o, function(o) { 672 var _files = o.files; 673 if (_files === undefined) 674 return 675 676 if (_files.length === undefined) 677 _files = [_files]; 678 files = files.concat(_files); 679 680 // set score for the word in each file to Scorer.term 681 for (j = 0; j < _files.length; j++) { 682 file = _files[j]; 683 if (!(file in scoreMap)) 684 scoreMap[file] = {} 685 scoreMap[file][word] = o.score; 686 } 687 }); 688 689 // create the mapping 690 for (j = 0; j < files.length; j++) { 691 file = files[j]; 692 if (file in fileMap) 693 fileMap[file].push(word); 694 else 695 fileMap[file] = [word]; 696 } 697 } 698 699 // now check if the files don't contain excluded terms 700 for (file in fileMap) { 701 var valid = true; 702 703 // check if all requirements are matched 704 if (fileMap[file].length != searchterms.length) 705 continue; 706 707 // ensure that none of the excluded terms is in the search result 708 for (i = 0; i < excluded.length; i++) { 709 if (terms[excluded[i]] == file || 710 titleterms[excluded[i]] == file || 711 $u.contains(terms[excluded[i]] || [], file) || 712 $u.contains(titleterms[excluded[i]] || [], file)) { 713 valid = false; 714 break; 715 } 716 } 717 718 // if we have still a valid result we can add it to the result list 719 if (valid) { 720 // select one (max) score for the file. 721 // for better ranking, we should calculate ranking by using words statistics like basic tf-idf... 722 var score = $u.max($u.map(fileMap[file], function(w){return scoreMap[file][w]})); 723 results.push([docnames[file], titles[file], '', null, score, filenames[file]]); 724 } 725 } 726 return results; 727 }, 728 729 /** 730 * helper function to return a node containing the 731 * search summary for a given text. keywords is a list 732 * of stemmed words, hlwords is the list of normal, unstemmed 733 * words. the first one is used to find the occurrence, the 734 * latter for highlighting it. 735 */ 736 makeSearchSummary : function(text, keywords, hlwords) { 737 var textLower = text.toLowerCase(); 738 var start = 0; 739 $.each(keywords, function() { 740 var i = textLower.indexOf(this.toLowerCase()); 741 if (i > -1) 742 start = i; 743 }); 744 start = Math.max(start - 120, 0); 745 var excerpt = ((start > 0) ? '...' : '') + 746 $.trim(text.substr(start, 240)) + 747 ((start + 240 - text.length) ? '...' : ''); 748 var rv = $('<div class="context"></div>').text(excerpt); 749 $.each(hlwords, function() { 750 rv = rv.highlightText(this, 'highlighted'); 751 }); 752 return rv; 753 } 754}; 755 756$(document).ready(function() { 757 Search.init(); 758});