Ubiquity  2.0.3
php rapid development framework
searchtools.js
Go to the documentation of this file.
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  */
17 var 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  */
201 var 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 
232 var 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 
307 function 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  */
332 var 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 });