5
* Sphinx JavaScript utilties for the full-text search.
7
* :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
8
* :license: BSD, see LICENSE for details.
13
* helper function to return a node containing the
14
* search summary for a given text. keywords is a list
15
* of stemmed words, hlwords is the list of normal, unstemmed
16
* words. the first one is used to find the occurance, the
17
* latter for highlighting it.
20
jQuery.makeSearchSummary = function(text, keywords, hlwords) {
21
var textLower = text.toLowerCase();
23
$.each(keywords, function() {
24
var i = textLower.indexOf(this.toLowerCase());
28
start = Math.max(start - 120, 0);
29
var excerpt = ((start > 0) ? '...' : '') +
30
$.trim(text.substr(start, 240)) +
31
((start + 240 - text.length) ? '...' : '');
32
var rv = $('<div class="context"></div>').text(excerpt);
33
$.each(hlwords, function() {
34
rv = rv.highlightText(this, 'highlighted');
43
var Stemmer = function() {
79
var c = "[^aeiou]"; // consonant
80
var v = "[aeiouy]"; // vowel
81
var C = c + "[^aeiouy]*"; // consonant sequence
82
var V = v + "[aeiou]*"; // vowel sequence
84
var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0
85
var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1
86
var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1
87
var s_v = "^(" + C + ")?" + v; // vowel in stem
89
this.stemWord = function (w) {
103
firstch = w.substr(0,1);
105
w = firstch.toUpperCase() + w.substr(1);
108
re = /^(.+?)(ss|i)es$/;
109
re2 = /^(.+?)([^s])s$/;
112
w = w.replace(re,"$1$2");
113
else if (re2.test(w))
114
w = w.replace(re2,"$1$2");
118
re2 = /^(.+?)(ed|ing)$/;
121
re = new RegExp(mgr0);
122
if (re.test(fp[1])) {
124
w = w.replace(re,"");
127
else if (re2.test(w)) {
128
var fp = re2.exec(w);
130
re2 = new RegExp(s_v);
131
if (re2.test(stem)) {
134
re3 = new RegExp("([^aeiouylsz])\\1$");
135
re4 = new RegExp("^" + C + v + "[^aeiouwxy]$");
138
else if (re3.test(w)) {
140
w = w.replace(re,"");
142
else if (re4.test(w))
152
re = new RegExp(s_v);
158
re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;
163
re = new RegExp(mgr0);
165
w = stem + step2list[suffix];
169
re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;
174
re = new RegExp(mgr0);
176
w = stem + step3list[suffix];
180
re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;
181
re2 = /^(.+?)(s|t)(ion)$/;
185
re = new RegExp(mgr1);
189
else if (re2.test(w)) {
190
var fp = re2.exec(w);
191
stem = fp[1] + fp[2];
192
re2 = new RegExp(mgr1);
202
re = new RegExp(mgr1);
203
re2 = new RegExp(meq1);
204
re3 = new RegExp("^" + C + v + "[^aeiouwxy]$");
205
if (re.test(stem) || (re2.test(stem) && !(re3.test(stem))))
209
re2 = new RegExp(mgr1);
210
if (re.test(w) && re2.test(w)) {
212
w = w.replace(re,"");
215
// and turn initial Y back to y
217
w = firstch.toLowerCase() + w.substr(1);
229
_queued_query : null,
233
var params = $.getQueryParameters();
235
var query = params.q[0];
236
$('input[name="q"]')[0].value = query;
237
this.performSearch(query);
241
loadIndex : function(url) {
242
$.ajax({type: "GET", url: url, data: null, success: null,
243
dataType: "script", cache: true});
246
setIndex : function(index) {
249
if ((q = this._queued_query) !== null) {
250
this._queued_query = null;
255
hasIndex : function() {
256
return this._index !== null;
259
deferQuery : function(query) {
260
this._queued_query = query;
263
stopPulse : function() {
264
this._pulse_status = 0;
267
startPulse : function() {
268
if (this._pulse_status >= 0)
271
Search._pulse_status = (Search._pulse_status + 1) % 4;
273
for (var i = 0; i < Search._pulse_status; i++)
275
Search.dots.text(dotString);
276
if (Search._pulse_status > -1)
277
window.setTimeout(pulse, 500);
283
* perform a search for something
285
performSearch : function(query) {
286
// create the required interface elements
287
this.out = $('#search-results');
288
this.title = $('<h2>' + _('Searching') + '</h2>').appendTo(this.out);
289
this.dots = $('<span></span>').appendTo(this.title);
290
this.status = $('<p style="display: none"></p>').appendTo(this.out);
291
this.output = $('<ul class="search"/>').appendTo(this.out);
293
$('#search-progress').text(_('Preparing search...'));
296
// index already loaded, the browser was quick!
300
this.deferQuery(query);
303
query : function(query) {
304
var stopwords = ["and","then","into","it","as","are","in","if","for","no","there","their","was","is","be","to","that","but","they","not","such","with","by","a","on","these","of","will","this","near","the","or","at"];
306
// Stem the searchterms and add them to the correct list
307
var stemmer = new Stemmer();
308
var searchterms = [];
311
var tmp = query.split(/\s+/);
312
var objectterms = [];
313
for (var i = 0; i < tmp.length; i++) {
315
objectterms.push(tmp[i].toLowerCase());
318
if ($u.indexOf(stopwords, tmp[i]) != -1 || tmp[i].match(/^\d+$/) ||
324
var word = stemmer.stemWord(tmp[i]).toLowerCase();
325
// select the correct list
326
if (word[0] == '-') {
327
var toAppend = excluded;
328
word = word.substr(1);
331
var toAppend = searchterms;
332
hlterms.push(tmp[i].toLowerCase());
334
// only add if not already in the list
335
if (!$.contains(toAppend, word))
338
var highlightstring = '?highlight=' + $.urlencode(hlterms.join(" "));
340
// console.debug('SEARCH: searching for:');
341
// console.info('required: ', searchterms);
342
// console.info('excluded: ', excluded);
345
var filenames = this._index.filenames;
346
var titles = this._index.titles;
347
var terms = this._index.terms;
350
// different result priorities
351
var importantResults = [];
352
var objectResults = [];
353
var regularResults = [];
354
var unimportantResults = [];
355
$('#search-progress').empty();
358
for (var i = 0; i < objectterms.length; i++) {
359
var others = [].concat(objectterms.slice(0,i),
360
objectterms.slice(i+1, objectterms.length))
361
var results = this.performObjectSearch(objectterms[i], others);
362
// Assume first word is most likely to be the object,
363
// other words more likely to be in description.
364
// Therefore put matches for earlier words first.
365
// (Results are eventually used in reverse order).
366
objectResults = results[0].concat(objectResults);
367
importantResults = results[1].concat(importantResults);
368
unimportantResults = results[2].concat(unimportantResults);
371
// perform the search on the required terms
372
for (var i = 0; i < searchterms.length; i++) {
373
var word = searchterms[i];
374
// no match but word was a required one
375
if ((files = terms[word]) == null)
377
if (files.length == undefined) {
380
// create the mapping
381
for (var j = 0; j < files.length; j++) {
384
fileMap[file].push(word);
386
fileMap[file] = [word];
390
// now check if the files don't contain excluded terms
391
for (var file in fileMap) {
394
// check if all requirements are matched
395
if (fileMap[file].length != searchterms.length)
398
// ensure that none of the excluded terms is in the
400
for (var i = 0; i < excluded.length; i++) {
401
if (terms[excluded[i]] == file ||
402
$.contains(terms[excluded[i]] || [], file)) {
408
// if we have still a valid result we can add it
409
// to the result list
411
regularResults.push([filenames[file], titles[file], '', null]);
414
// delete unused variables in order to not waste
415
// memory until list is retrieved completely
416
delete filenames, titles, terms;
418
// now sort the regular results descending by title
419
regularResults.sort(function(a, b) {
420
var left = a[1].toLowerCase();
421
var right = b[1].toLowerCase();
422
return (left > right) ? -1 : ((left < right) ? 1 : 0);
425
// combine all results
426
var results = unimportantResults.concat(regularResults)
427
.concat(objectResults).concat(importantResults);
430
var resultCount = results.length;
431
function displayNextItem() {
432
// results left, load the summary and display it
433
if (results.length) {
434
var item = results.pop();
435
var listItem = $('<li style="display:none"></li>');
436
if (DOCUMENTATION_OPTIONS.FILE_SUFFIX == '') {
438
var dirname = item[0] + '/';
439
if (dirname.match(/\/index\/$/)) {
440
dirname = dirname.substring(0, dirname.length-6);
441
} else if (dirname == 'index/') {
444
listItem.append($('<a/>').attr('href',
445
DOCUMENTATION_OPTIONS.URL_ROOT + dirname +
446
highlightstring + item[2]).html(item[1]));
448
// normal html builders
449
listItem.append($('<a/>').attr('href',
450
item[0] + DOCUMENTATION_OPTIONS.FILE_SUFFIX +
451
highlightstring + item[2]).html(item[1]));
454
listItem.append($('<span> (' + item[3] + ')</span>'));
455
Search.output.append(listItem);
456
listItem.slideDown(5, function() {
459
} else if (DOCUMENTATION_OPTIONS.HAS_SOURCE) {
460
$.get(DOCUMENTATION_OPTIONS.URL_ROOT + '_sources/' +
461
item[0] + '.txt', function(data) {
463
listItem.append($.makeSearchSummary(data, searchterms, hlterms));
464
Search.output.append(listItem);
466
listItem.slideDown(5, function() {
471
// no source available, just display title
472
Search.output.append(listItem);
473
listItem.slideDown(5, function() {
478
// search finished, update title and status message
481
Search.title.text(_('Search Results'));
483
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.'));
485
Search.status.text(_('Search finished, found %s page(s) matching the search query.').replace('%s', resultCount));
486
Search.status.fadeIn(500);
492
performObjectSearch : function(object, otherterms) {
493
var filenames = this._index.filenames;
494
var objects = this._index.objects;
495
var objnames = this._index.objnames;
496
var titles = this._index.titles;
498
var importantResults = [];
499
var objectResults = [];
500
var unimportantResults = [];
502
for (var prefix in objects) {
503
for (var name in objects[prefix]) {
504
var fullname = (prefix ? prefix + '.' : '') + name;
505
if (fullname.toLowerCase().indexOf(object) > -1) {
506
var match = objects[prefix][name];
507
var objname = objnames[match[1]][2];
508
var title = titles[match[0]];
509
// If more than one term searched for, we require other words to be
510
// found in the name/title/description
511
if (otherterms.length > 0) {
512
var haystack = (prefix + ' ' + name + ' ' +
513
objname + ' ' + title).toLowerCase();
515
for (var i = 0; i < otherterms.length; i++) {
516
if (haystack.indexOf(otherterms[i]) == -1) {
525
var descr = objname + _(', in ') + title;
529
else if (anchor == '-')
530
anchor = objnames[match[1]][1] + '-' + fullname;
531
result = [filenames[match[0]], fullname, '#'+anchor, descr];
533
case 1: objectResults.push(result); break;
534
case 0: importantResults.push(result); break;
535
case 2: unimportantResults.push(result); break;
541
// sort results descending
542
objectResults.sort(function(a, b) {
543
return (a[1] > b[1]) ? -1 : ((a[1] < b[1]) ? 1 : 0);
546
importantResults.sort(function(a, b) {
547
return (a[1] > b[1]) ? -1 : ((a[1] < b[1]) ? 1 : 0);
550
unimportantResults.sort(function(a, b) {
551
return (a[1] > b[1]) ? -1 : ((a[1] < b[1]) ? 1 : 0);
554
return [importantResults, objectResults, unimportantResults]
558
$(document).ready(function() {
b'\\ No newline at end of file'