source: flex_extract.git/documentation/Sphinx/build/html/_static/searchtools.js @ 2625ca8

ctbtodev
Last change on this file since 2625ca8 was 2625ca8, checked in by Anne Philipp <anne.philipp@…>, 5 years ago

new build of sphinx for updated api

  • Property mode set to 100644
File size: 14.8 KB
Line 
1/*
2 * searchtools.js
3 * ~~~~~~~~~~~~~~~~
4 *
5 * Sphinx JavaScript utilities for the full-text search.
6 *
7 * :copyright: Copyright 2007-2018 by the Sphinx team, see AUTHORS.
8 * :license: BSD, see LICENSE for details.
9 *
10 */
11
12if (!Scorer) {
13  /**
14   * Simple result scoring code.
15   */
16  var Scorer = {
17    // Implement the following function to further tweak the score for each result
18    // The function takes a result array [filename, title, anchor, descr, score]
19    // and returns the new score.
20    /*
21    score: function(result) {
22      return result[4];
23    },
24    */
25
26    // query matches the full name of an object
27    objNameMatch: 11,
28    // or matches in the last dotted part of the object name
29    objPartialMatch: 6,
30    // Additive scores depending on the priority of the object
31    objPrio: {0:  15,   // used to be importantResults
32              1:  5,   // used to be objectResults
33              2: -5},  // used to be unimportantResults
34    //  Used when the priority is not in the mapping.
35    objPrioDefault: 0,
36
37    // query found in title
38    title: 15,
39    // query found in terms
40    term: 5
41  };
42}
43
44if (!splitQuery) {
45  function splitQuery(query) {
46    return query.split(/\s+/);
47  }
48}
49
50/**
51 * Search Module
52 */
53var Search = {
54
55  _index : null,
56  _queued_query : null,
57  _pulse_status : -1,
58
59  init : function() {
60      var params = $.getQueryParameters();
61      if (params.q) {
62          var query = params.q[0];
63          $('input[name="q"]')[0].value = query;
64          this.performSearch(query);
65      }
66  },
67
68  loadIndex : function(url) {
69    $.ajax({type: "GET", url: url, data: null,
70            dataType: "script", cache: true,
71            complete: function(jqxhr, textstatus) {
72              if (textstatus != "success") {
73                document.getElementById("searchindexloader").src = url;
74              }
75            }});
76  },
77
78  setIndex : function(index) {
79    var q;
80    this._index = index;
81    if ((q = this._queued_query) !== null) {
82      this._queued_query = null;
83      Search.query(q);
84    }
85  },
86
87  hasIndex : function() {
88      return this._index !== null;
89  },
90
91  deferQuery : function(query) {
92      this._queued_query = query;
93  },
94
95  stopPulse : function() {
96      this._pulse_status = 0;
97  },
98
99  startPulse : function() {
100    if (this._pulse_status >= 0)
101        return;
102    function pulse() {
103      var i;
104      Search._pulse_status = (Search._pulse_status + 1) % 4;
105      var dotString = '';
106      for (i = 0; i < Search._pulse_status; i++)
107        dotString += '.';
108      Search.dots.text(dotString);
109      if (Search._pulse_status > -1)
110        window.setTimeout(pulse, 500);
111    }
112    pulse();
113  },
114
115  /**
116   * perform a search for something (or wait until index is loaded)
117   */
118  performSearch : function(query) {
119    // create the required interface elements
120    this.out = $('#search-results');
121    this.title = $('<h2>' + _('Searching') + '</h2>').appendTo(this.out);
122    this.dots = $('<span></span>').appendTo(this.title);
123    this.status = $('<p style="display: none"></p>').appendTo(this.out);
124    this.output = $('<ul class="search"/>').appendTo(this.out);
125
126    $('#search-progress').text(_('Preparing search...'));
127    this.startPulse();
128
129    // index already loaded, the browser was quick!
130    if (this.hasIndex())
131      this.query(query);
132    else
133      this.deferQuery(query);
134  },
135
136  /**
137   * execute search (requires search index to be loaded)
138   */
139  query : function(query) {
140    var i;
141    var stopwords = DOCUMENTATION_OPTIONS.SEARCH_LANGUAGE_STOP_WORDS;
142
143    // stem the searchterms and add them to the correct list
144    var stemmer = new Stemmer();
145    var searchterms = [];
146    var excluded = [];
147    var hlterms = [];
148    var tmp = splitQuery(query);
149    var objectterms = [];
150    for (i = 0; i < tmp.length; i++) {
151      if (tmp[i] !== "") {
152          objectterms.push(tmp[i].toLowerCase());
153      }
154
155      if ($u.indexOf(stopwords, tmp[i].toLowerCase()) != -1 || tmp[i].match(/^\d+$/) ||
156          tmp[i] === "") {
157        // skip this "word"
158        continue;
159      }
160      // stem the word
161      var word = stemmer.stemWord(tmp[i].toLowerCase());
162      // prevent stemmer from cutting word smaller than two chars
163      if(word.length < 3 && tmp[i].length >= 3) {
164        word = tmp[i];
165      }
166      var toAppend;
167      // select the correct list
168      if (word[0] == '-') {
169        toAppend = excluded;
170        word = word.substr(1);
171      }
172      else {
173        toAppend = searchterms;
174        hlterms.push(tmp[i].toLowerCase());
175      }
176      // only add if not already in the list
177      if (!$u.contains(toAppend, word))
178        toAppend.push(word);
179    }
180    var highlightstring = '?highlight=' + $.urlencode(hlterms.join(" "));
181
182    // console.debug('SEARCH: searching for:');
183    // console.info('required: ', searchterms);
184    // console.info('excluded: ', excluded);
185
186    // prepare search
187    var terms = this._index.terms;
188    var titleterms = this._index.titleterms;
189
190    // array of [filename, title, anchor, descr, score]
191    var results = [];
192    $('#search-progress').empty();
193
194    // lookup as object
195    for (i = 0; i < objectterms.length; i++) {
196      var others = [].concat(objectterms.slice(0, i),
197                             objectterms.slice(i+1, objectterms.length));
198      results = results.concat(this.performObjectSearch(objectterms[i], others));
199    }
200
201    // lookup as search terms in fulltext
202    results = results.concat(this.performTermsSearch(searchterms, excluded, terms, titleterms));
203
204    // let the scorer override scores with a custom scoring function
205    if (Scorer.score) {
206      for (i = 0; i < results.length; i++)
207        results[i][4] = Scorer.score(results[i]);
208    }
209
210    // now sort the results by score (in opposite order of appearance, since the
211    // display function below uses pop() to retrieve items) and then
212    // alphabetically
213    results.sort(function(a, b) {
214      var left = a[4];
215      var right = b[4];
216      if (left > right) {
217        return 1;
218      } else if (left < right) {
219        return -1;
220      } else {
221        // same score: sort alphabetically
222        left = a[1].toLowerCase();
223        right = b[1].toLowerCase();
224        return (left > right) ? -1 : ((left < right) ? 1 : 0);
225      }
226    });
227
228    // for debugging
229    //Search.lastresults = results.slice();  // a copy
230    //console.info('search results:', Search.lastresults);
231
232    // print the results
233    var resultCount = results.length;
234    function displayNextItem() {
235      // results left, load the summary and display it
236      if (results.length) {
237        var item = results.pop();
238        var listItem = $('<li style="display:none"></li>');
239        if (DOCUMENTATION_OPTIONS.FILE_SUFFIX === '') {
240          // dirhtml builder
241          var dirname = item[0] + '/';
242          if (dirname.match(/\/index\/$/)) {
243            dirname = dirname.substring(0, dirname.length-6);
244          } else if (dirname == 'index/') {
245            dirname = '';
246          }
247          listItem.append($('<a/>').attr('href',
248            DOCUMENTATION_OPTIONS.URL_ROOT + dirname +
249            highlightstring + item[2]).html(item[1]));
250        } else {
251          // normal html builders
252          listItem.append($('<a/>').attr('href',
253            item[0] + DOCUMENTATION_OPTIONS.FILE_SUFFIX +
254            highlightstring + item[2]).html(item[1]));
255        }
256        if (item[3]) {
257          listItem.append($('<span> (' + item[3] + ')</span>'));
258          Search.output.append(listItem);
259          listItem.slideDown(5, function() {
260            displayNextItem();
261          });
262        } else if (DOCUMENTATION_OPTIONS.HAS_SOURCE) {
263          var suffix = DOCUMENTATION_OPTIONS.SOURCELINK_SUFFIX;
264          if (suffix === undefined) {
265            suffix = '.txt';
266          }
267          $.ajax({url: DOCUMENTATION_OPTIONS.URL_ROOT + '_sources/' + item[5] + (item[5].slice(-suffix.length) === suffix ? '' : suffix),
268                  dataType: "text",
269                  complete: function(jqxhr, textstatus) {
270                    var data = jqxhr.responseText;
271                    if (data !== '' && data !== undefined) {
272                      listItem.append(Search.makeSearchSummary(data, searchterms, hlterms));
273                    }
274                    Search.output.append(listItem);
275                    listItem.slideDown(5, function() {
276                      displayNextItem();
277                    });
278                  }});
279        } else {
280          // no source available, just display title
281          Search.output.append(listItem);
282          listItem.slideDown(5, function() {
283            displayNextItem();
284          });
285        }
286      }
287      // search finished, update title and status message
288      else {
289        Search.stopPulse();
290        Search.title.text(_('Search Results'));
291        if (!resultCount)
292          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.'));
293        else
294            Search.status.text(_('Search finished, found %s page(s) matching the search query.').replace('%s', resultCount));
295        Search.status.fadeIn(500);
296      }
297    }
298    displayNextItem();
299  },
300
301  /**
302   * search for object names
303   */
304  performObjectSearch : function(object, otherterms) {
305    var filenames = this._index.filenames;
306    var docnames = this._index.docnames;
307    var objects = this._index.objects;
308    var objnames = this._index.objnames;
309    var titles = this._index.titles;
310
311    var i;
312    var results = [];
313
314    for (var prefix in objects) {
315      for (var name in objects[prefix]) {
316        var fullname = (prefix ? prefix + '.' : '') + name;
317        if (fullname.toLowerCase().indexOf(object) > -1) {
318          var score = 0;
319          var parts = fullname.split('.');
320          // check for different match types: exact matches of full name or
321          // "last name" (i.e. last dotted part)
322          if (fullname == object || parts[parts.length - 1] == object) {
323            score += Scorer.objNameMatch;
324          // matches in last name
325          } else if (parts[parts.length - 1].indexOf(object) > -1) {
326            score += Scorer.objPartialMatch;
327          }
328          var match = objects[prefix][name];
329          var objname = objnames[match[1]][2];
330          var title = titles[match[0]];
331          // If more than one term searched for, we require other words to be
332          // found in the name/title/description
333          if (otherterms.length > 0) {
334            var haystack = (prefix + ' ' + name + ' ' +
335                            objname + ' ' + title).toLowerCase();
336            var allfound = true;
337            for (i = 0; i < otherterms.length; i++) {
338              if (haystack.indexOf(otherterms[i]) == -1) {
339                allfound = false;
340                break;
341              }
342            }
343            if (!allfound) {
344              continue;
345            }
346          }
347          var descr = objname + _(', in ') + title;
348
349          var anchor = match[3];
350          if (anchor === '')
351            anchor = fullname;
352          else if (anchor == '-')
353            anchor = objnames[match[1]][1] + '-' + fullname;
354          // add custom score for some objects according to scorer
355          if (Scorer.objPrio.hasOwnProperty(match[2])) {
356            score += Scorer.objPrio[match[2]];
357          } else {
358            score += Scorer.objPrioDefault;
359          }
360          results.push([docnames[match[0]], fullname, '#'+anchor, descr, score, filenames[match[0]]]);
361        }
362      }
363    }
364
365    return results;
366  },
367
368  /**
369   * search for full-text terms in the index
370   */
371  performTermsSearch : function(searchterms, excluded, terms, titleterms) {
372    var docnames = this._index.docnames;
373    var filenames = this._index.filenames;
374    var titles = this._index.titles;
375
376    var i, j, file;
377    var fileMap = {};
378    var scoreMap = {};
379    var results = [];
380
381    // perform the search on the required terms
382    for (i = 0; i < searchterms.length; i++) {
383      var word = searchterms[i];
384      var files = [];
385      var _o = [
386        {files: terms[word], score: Scorer.term},
387        {files: titleterms[word], score: Scorer.title}
388      ];
389
390      // no match but word was a required one
391      if ($u.every(_o, function(o){return o.files === undefined;})) {
392        break;
393      }
394      // found search word in contents
395      $u.each(_o, function(o) {
396        var _files = o.files;
397        if (_files === undefined)
398          return
399
400        if (_files.length === undefined)
401          _files = [_files];
402        files = files.concat(_files);
403
404        // set score for the word in each file to Scorer.term
405        for (j = 0; j < _files.length; j++) {
406          file = _files[j];
407          if (!(file in scoreMap))
408            scoreMap[file] = {}
409          scoreMap[file][word] = o.score;
410        }
411      });
412
413      // create the mapping
414      for (j = 0; j < files.length; j++) {
415        file = files[j];
416        if (file in fileMap)
417          fileMap[file].push(word);
418        else
419          fileMap[file] = [word];
420      }
421    }
422
423    // now check if the files don't contain excluded terms
424    for (file in fileMap) {
425      var valid = true;
426
427      // check if all requirements are matched
428      if (fileMap[file].length != searchterms.length)
429          continue;
430
431      // ensure that none of the excluded terms is in the search result
432      for (i = 0; i < excluded.length; i++) {
433        if (terms[excluded[i]] == file ||
434            titleterms[excluded[i]] == file ||
435            $u.contains(terms[excluded[i]] || [], file) ||
436            $u.contains(titleterms[excluded[i]] || [], file)) {
437          valid = false;
438          break;
439        }
440      }
441
442      // if we have still a valid result we can add it to the result list
443      if (valid) {
444        // select one (max) score for the file.
445        // for better ranking, we should calculate ranking by using words statistics like basic tf-idf...
446        var score = $u.max($u.map(fileMap[file], function(w){return scoreMap[file][w]}));
447        results.push([docnames[file], titles[file], '', null, score, filenames[file]]);
448      }
449    }
450    return results;
451  },
452
453  /**
454   * helper function to return a node containing the
455   * search summary for a given text. keywords is a list
456   * of stemmed words, hlwords is the list of normal, unstemmed
457   * words. the first one is used to find the occurrence, the
458   * latter for highlighting it.
459   */
460  makeSearchSummary : function(text, keywords, hlwords) {
461    var textLower = text.toLowerCase();
462    var start = 0;
463    $.each(keywords, function() {
464      var i = textLower.indexOf(this.toLowerCase());
465      if (i > -1)
466        start = i;
467    });
468    start = Math.max(start - 120, 0);
469    var excerpt = ((start > 0) ? '...' : '') +
470      $.trim(text.substr(start, 240)) +
471      ((start + 240 - text.length) ? '...' : '');
472    var rv = $('<div class="context"></div>').text(excerpt);
473    $.each(hlwords, function() {
474      rv = rv.highlightText(this, 'highlighted');
475    });
476    return rv;
477  }
478};
479
480$(document).ready(function() {
481  Search.init();
482});
Note: See TracBrowser for help on using the repository browser.
hosted by ZAMG