• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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});