blob: c82157380b008f641a14e50619a23cc5c0b4b04d [file] [log] [blame]
Andrey Andreevc5769952019-01-16 17:49:35 +02001/*
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});