3421N/A/*
3421N/A * searchtools.js_t
3421N/A * ~~~~~~~~~~~~~~~~
3421N/A *
3421N/A * Sphinx JavaScript utilties for the full-text search.
3421N/A *
3421N/A * :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS.
3421N/A * :license: BSD, see LICENSE for details.
3421N/A *
3421N/A */
3421N/A
3421N/A
3421N/A/**
3421N/A * Porter Stemmer
3421N/A */
3421N/Avar Stemmer = function() {
3421N/A
3421N/A var step2list = {
3421N/A ational: 'ate',
3421N/A tional: 'tion',
3421N/A enci: 'ence',
3421N/A anci: 'ance',
3421N/A izer: 'ize',
3421N/A bli: 'ble',
3421N/A alli: 'al',
3421N/A entli: 'ent',
3421N/A eli: 'e',
3421N/A ousli: 'ous',
3421N/A ization: 'ize',
3421N/A ation: 'ate',
3421N/A ator: 'ate',
3421N/A alism: 'al',
3421N/A iveness: 'ive',
3421N/A fulness: 'ful',
3421N/A ousness: 'ous',
3421N/A aliti: 'al',
3421N/A iviti: 'ive',
3421N/A biliti: 'ble',
3421N/A logi: 'log'
3421N/A };
3421N/A
3421N/A var step3list = {
3421N/A icate: 'ic',
3421N/A ative: '',
3421N/A alize: 'al',
3421N/A iciti: 'ic',
3421N/A ical: 'ic',
3421N/A ful: '',
3421N/A ness: ''
3421N/A };
3421N/A
3421N/A var c = "[^aeiou]"; // consonant
3421N/A var v = "[aeiouy]"; // vowel
3421N/A var C = c + "[^aeiouy]*"; // consonant sequence
3421N/A var V = v + "[aeiou]*"; // vowel sequence
3421N/A
3421N/A var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0
3421N/A var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1
3421N/A var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1
3421N/A var s_v = "^(" + C + ")?" + v; // vowel in stem
3421N/A
3421N/A this.stemWord = function (w) {
3421N/A var stem;
3421N/A var suffix;
3421N/A var firstch;
3421N/A var origword = w;
3421N/A
3421N/A if (w.length < 3)
3421N/A return w;
3421N/A
3421N/A var re;
3421N/A var re2;
3421N/A var re3;
3421N/A var re4;
3421N/A
3421N/A firstch = w.substr(0,1);
3421N/A if (firstch == "y")
3421N/A w = firstch.toUpperCase() + w.substr(1);
3421N/A
3421N/A // Step 1a
3421N/A re = /^(.+?)(ss|i)es$/;
3421N/A re2 = /^(.+?)([^s])s$/;
3421N/A
3421N/A if (re.test(w))
3421N/A w = w.replace(re,"$1$2");
3421N/A else if (re2.test(w))
3421N/A w = w.replace(re2,"$1$2");
3421N/A
3421N/A // Step 1b
3421N/A re = /^(.+?)eed$/;
3421N/A re2 = /^(.+?)(ed|ing)$/;
3421N/A if (re.test(w)) {
3421N/A var fp = re.exec(w);
3421N/A re = new RegExp(mgr0);
3421N/A if (re.test(fp[1])) {
3421N/A re = /.$/;
3421N/A w = w.replace(re,"");
3421N/A }
3421N/A }
3421N/A else if (re2.test(w)) {
3421N/A var fp = re2.exec(w);
3421N/A stem = fp[1];
3421N/A re2 = new RegExp(s_v);
3421N/A if (re2.test(stem)) {
3421N/A w = stem;
3421N/A re2 = /(at|bl|iz)$/;
3421N/A re3 = new RegExp("([^aeiouylsz])\\1$");
3421N/A re4 = new RegExp("^" + C + v + "[^aeiouwxy]$");
3421N/A if (re2.test(w))
3421N/A w = w + "e";
3421N/A else if (re3.test(w)) {
3421N/A re = /.$/;
3421N/A w = w.replace(re,"");
3421N/A }
3421N/A else if (re4.test(w))
3421N/A w = w + "e";
3421N/A }
3421N/A }
3421N/A
3421N/A // Step 1c
3421N/A re = /^(.+?)y$/;
3421N/A if (re.test(w)) {
3421N/A var fp = re.exec(w);
3421N/A stem = fp[1];
3421N/A re = new RegExp(s_v);
3421N/A if (re.test(stem))
3421N/A w = stem + "i";
3421N/A }
3421N/A
3421N/A // Step 2
3421N/A re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;
3421N/A if (re.test(w)) {
3421N/A var fp = re.exec(w);
3421N/A stem = fp[1];
3421N/A suffix = fp[2];
3421N/A re = new RegExp(mgr0);
3421N/A if (re.test(stem))
3421N/A w = stem + step2list[suffix];
3421N/A }
3421N/A
3421N/A // Step 3
3421N/A re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;
3421N/A if (re.test(w)) {
3421N/A var fp = re.exec(w);
3421N/A stem = fp[1];
3421N/A suffix = fp[2];
3421N/A re = new RegExp(mgr0);
3421N/A if (re.test(stem))
3421N/A w = stem + step3list[suffix];
3421N/A }
3421N/A
3421N/A // Step 4
3421N/A re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;
3421N/A re2 = /^(.+?)(s|t)(ion)$/;
3421N/A if (re.test(w)) {
3421N/A var fp = re.exec(w);
3421N/A stem = fp[1];
3421N/A re = new RegExp(mgr1);
3421N/A if (re.test(stem))
3421N/A w = stem;
3421N/A }
3421N/A else if (re2.test(w)) {
3421N/A var fp = re2.exec(w);
3421N/A stem = fp[1] + fp[2];
3421N/A re2 = new RegExp(mgr1);
3421N/A if (re2.test(stem))
3421N/A w = stem;
3421N/A }
3421N/A
3421N/A // Step 5
3421N/A re = /^(.+?)e$/;
3421N/A if (re.test(w)) {
3421N/A var fp = re.exec(w);
3421N/A stem = fp[1];
3421N/A re = new RegExp(mgr1);
3421N/A re2 = new RegExp(meq1);
3421N/A re3 = new RegExp("^" + C + v + "[^aeiouwxy]$");
3421N/A if (re.test(stem) || (re2.test(stem) && !(re3.test(stem))))
3421N/A w = stem;
3421N/A }
3421N/A re = /ll$/;
3421N/A re2 = new RegExp(mgr1);
3421N/A if (re.test(w) && re2.test(w)) {
3421N/A re = /.$/;
3421N/A w = w.replace(re,"");
3421N/A }
3421N/A
3421N/A // and turn initial Y back to y
3421N/A if (firstch == "y")
3421N/A w = firstch.toLowerCase() + w.substr(1);
3421N/A return w;
3421N/A }
3421N/A}
3421N/A
3421N/A
3421N/A
3421N/A/**
3421N/A * Simple result scoring code.
3421N/A */
3421N/Avar Scorer = {
3421N/A // Implement the following function to further tweak the score for each result
3421N/A // The function takes a result array [filename, title, anchor, descr, score]
3421N/A // and returns the new score.
3421N/A /*
3421N/A score: function(result) {
3421N/A return result[4];
3421N/A },
3421N/A */
3421N/A
3421N/A // query matches the full name of an object
3421N/A objNameMatch: 11,
3421N/A // or matches in the last dotted part of the object name
3421N/A objPartialMatch: 6,
3421N/A // Additive scores depending on the priority of the object
3421N/A objPrio: {0: 15, // used to be importantResults
3421N/A 1: 5, // used to be objectResults
3421N/A 2: -5}, // used to be unimportantResults
3421N/A // Used when the priority is not in the mapping.
3421N/A objPrioDefault: 0,
3421N/A
3421N/A // query found in title
3421N/A title: 15,
3421N/A // query found in terms
3421N/A term: 5
3421N/A};
3421N/A
3421N/A
3421N/A/**
3421N/A * Search Module
3421N/A */
3421N/Avar Search = {
3421N/A
3421N/A _index : null,
3421N/A _queued_query : null,
3421N/A _pulse_status : -1,
3421N/A
3421N/A init : function() {
3421N/A var params = $.getQueryParameters();
3421N/A if (params.q) {
3421N/A var query = params.q[0];
3421N/A $('input[name="q"]')[0].value = query;
3421N/A this.performSearch(query);
3421N/A }
3421N/A },
3421N/A
3421N/A loadIndex : function(url) {
3421N/A $.ajax({type: "GET", url: url, data: null,
3421N/A dataType: "script", cache: true,
3421N/A complete: function(jqxhr, textstatus) {
3421N/A if (textstatus != "success") {
3421N/A document.getElementById("searchindexloader").src = url;
3421N/A }
3421N/A }});
3421N/A },
3421N/A
3421N/A setIndex : function(index) {
3421N/A var q;
3421N/A this._index = index;
3421N/A if ((q = this._queued_query) !== null) {
3421N/A this._queued_query = null;
3421N/A Search.query(q);
3421N/A }
3421N/A },
3421N/A
3421N/A hasIndex : function() {
3421N/A return this._index !== null;
3421N/A },
3421N/A
3421N/A deferQuery : function(query) {
3421N/A this._queued_query = query;
3421N/A },
3421N/A
3421N/A stopPulse : function() {
3421N/A this._pulse_status = 0;
3421N/A },
3421N/A
3421N/A startPulse : function() {
3421N/A if (this._pulse_status >= 0)
3421N/A return;
3421N/A function pulse() {
3421N/A var i;
3421N/A Search._pulse_status = (Search._pulse_status + 1) % 4;
3421N/A var dotString = '';
3421N/A for (i = 0; i < Search._pulse_status; i++)
3421N/A dotString += '.';
3421N/A Search.dots.text(dotString);
3421N/A if (Search._pulse_status > -1)
3421N/A window.setTimeout(pulse, 500);
3421N/A }
3421N/A pulse();
3421N/A },
3421N/A
3421N/A /**
3421N/A * perform a search for something (or wait until index is loaded)
3421N/A */
3421N/A performSearch : function(query) {
3421N/A // create the required interface elements
3421N/A this.out = $('#search-results');
3421N/A this.title = $('<h2>' + _('Searching') + '</h2>').appendTo(this.out);
3421N/A this.dots = $('<span></span>').appendTo(this.title);
3421N/A this.status = $('<p style="display: none"></p>').appendTo(this.out);
3421N/A this.output = $('<ul class="search"/>').appendTo(this.out);
3421N/A
3421N/A $('#search-progress').text(_('Preparing search...'));
3421N/A this.startPulse();
3421N/A
3421N/A // index already loaded, the browser was quick!
3421N/A if (this.hasIndex())
3421N/A this.query(query);
3421N/A else
3421N/A this.deferQuery(query);
3421N/A },
3421N/A
3421N/A /**
3421N/A * execute search (requires search index to be loaded)
3421N/A */
3421N/A query : function(query) {
3421N/A var i;
3421N/A 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"];
3421N/A
3421N/A // stem the searchterms and add them to the correct list
3421N/A var stemmer = new Stemmer();
3421N/A var searchterms = [];
3421N/A var excluded = [];
3421N/A var hlterms = [];
3421N/A var tmp = query.split(/\s+/);
3421N/A var objectterms = [];
3421N/A for (i = 0; i < tmp.length; i++) {
3421N/A if (tmp[i] !== "") {
3421N/A objectterms.push(tmp[i].toLowerCase());
3421N/A }
3421N/A
3421N/A if ($u.indexOf(stopwords, tmp[i].toLowerCase()) != -1 || tmp[i].match(/^\d+$/) ||
3421N/A tmp[i] === "") {
3421N/A // skip this "word"
3421N/A continue;
3421N/A }
3421N/A // stem the word
3421N/A var word = stemmer.stemWord(tmp[i].toLowerCase());
3421N/A var toAppend;
3421N/A // select the correct list
3421N/A if (word[0] == '-') {
3421N/A toAppend = excluded;
3421N/A word = word.substr(1);
3421N/A }
3421N/A else {
3421N/A toAppend = searchterms;
3421N/A hlterms.push(tmp[i].toLowerCase());
3421N/A }
3421N/A // only add if not already in the list
3421N/A if (!$u.contains(toAppend, word))
3421N/A toAppend.push(word);
3421N/A }
3421N/A var highlightstring = '?highlight=' + $.urlencode(hlterms.join(" "));
3421N/A
3421N/A // console.debug('SEARCH: searching for:');
3421N/A // console.info('required: ', searchterms);
3421N/A // console.info('excluded: ', excluded);
3421N/A
3421N/A // prepare search
3421N/A var terms = this._index.terms;
3421N/A var titleterms = this._index.titleterms;
3421N/A
3421N/A // array of [filename, title, anchor, descr, score]
3421N/A var results = [];
3421N/A $('#search-progress').empty();
3421N/A
3421N/A // lookup as object
3421N/A for (i = 0; i < objectterms.length; i++) {
3421N/A var others = [].concat(objectterms.slice(0, i),
3421N/A objectterms.slice(i+1, objectterms.length));
3421N/A results = results.concat(this.performObjectSearch(objectterms[i], others));
3421N/A }
3421N/A
3421N/A // lookup as search terms in fulltext
3421N/A results = results.concat(this.performTermsSearch(searchterms, excluded, terms, Scorer.term))
3421N/A .concat(this.performTermsSearch(searchterms, excluded, titleterms, Scorer.title));
3421N/A
3421N/A // let the scorer override scores with a custom scoring function
3421N/A if (Scorer.score) {
3421N/A for (i = 0; i < results.length; i++)
3421N/A results[i][4] = Scorer.score(results[i]);
3421N/A }
3421N/A
3421N/A // now sort the results by score (in opposite order of appearance, since the
3421N/A // display function below uses pop() to retrieve items) and then
3421N/A // alphabetically
3421N/A results.sort(function(a, b) {
3421N/A var left = a[4];
3421N/A var right = b[4];
3421N/A if (left > right) {
3421N/A return 1;
3421N/A } else if (left < right) {
3421N/A return -1;
3421N/A } else {
3421N/A // same score: sort alphabetically
3421N/A left = a[1].toLowerCase();
3421N/A right = b[1].toLowerCase();
3421N/A return (left > right) ? -1 : ((left < right) ? 1 : 0);
3421N/A }
3421N/A });
3421N/A
3421N/A // for debugging
3421N/A //Search.lastresults = results.slice(); // a copy
3421N/A //console.info('search results:', Search.lastresults);
3421N/A
3421N/A // print the results
3421N/A var resultCount = results.length;
3421N/A function displayNextItem() {
3421N/A // results left, load the summary and display it
3421N/A if (results.length) {
3421N/A var item = results.pop();
3421N/A var listItem = $('<li style="display:none"></li>');
3421N/A if (DOCUMENTATION_OPTIONS.FILE_SUFFIX === '') {
3421N/A // dirhtml builder
3421N/A var dirname = item[0] + '/';
3421N/A if (dirname.match(/\/index\/$/)) {
3421N/A dirname = dirname.substring(0, dirname.length-6);
3421N/A } else if (dirname == 'index/') {
3421N/A dirname = '';
3421N/A }
3421N/A listItem.append($('<a/>').attr('href',
3421N/A DOCUMENTATION_OPTIONS.URL_ROOT + dirname +
3421N/A highlightstring + item[2]).html(item[1]));
3421N/A } else {
3421N/A // normal html builders
3421N/A listItem.append($('<a/>').attr('href',
3421N/A item[0] + DOCUMENTATION_OPTIONS.FILE_SUFFIX +
3421N/A highlightstring + item[2]).html(item[1]));
3421N/A }
3421N/A if (item[3]) {
3421N/A listItem.append($('<span> (' + item[3] + ')</span>'));
3421N/A Search.output.append(listItem);
3421N/A listItem.slideDown(5, function() {
3421N/A displayNextItem();
3421N/A });
3421N/A } else if (DOCUMENTATION_OPTIONS.HAS_SOURCE) {
3421N/A $.ajax({url: DOCUMENTATION_OPTIONS.URL_ROOT + '_sources/' + item[0] + '.txt',
3421N/A dataType: "text",
3421N/A complete: function(jqxhr, textstatus) {
3421N/A var data = jqxhr.responseText;
3421N/A if (data !== '') {
3421N/A listItem.append(Search.makeSearchSummary(data, searchterms, hlterms));
3421N/A }
3421N/A Search.output.append(listItem);
3421N/A listItem.slideDown(5, function() {
3421N/A displayNextItem();
3421N/A });
3421N/A }});
3421N/A } else {
3421N/A // no source available, just display title
3421N/A Search.output.append(listItem);
3421N/A listItem.slideDown(5, function() {
3421N/A displayNextItem();
3421N/A });
3421N/A }
3421N/A }
3421N/A // search finished, update title and status message
3421N/A else {
3421N/A Search.stopPulse();
3421N/A Search.title.text(_('Search Results'));
3421N/A if (!resultCount)
3421N/A 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.'));
3421N/A else
3421N/A Search.status.text(_('Search finished, found %s page(s) matching the search query.').replace('%s', resultCount));
3421N/A Search.status.fadeIn(500);
3421N/A }
3421N/A }
3421N/A displayNextItem();
3421N/A },
3421N/A
3421N/A /**
3421N/A * search for object names
3421N/A */
3421N/A performObjectSearch : function(object, otherterms) {
3421N/A var filenames = this._index.filenames;
3421N/A var objects = this._index.objects;
3421N/A var objnames = this._index.objnames;
3421N/A var titles = this._index.titles;
3421N/A
3421N/A var i;
3421N/A var results = [];
3421N/A
3421N/A for (var prefix in objects) {
3421N/A for (var name in objects[prefix]) {
3421N/A var fullname = (prefix ? prefix + '.' : '') + name;
3421N/A if (fullname.toLowerCase().indexOf(object) > -1) {
3421N/A var score = 0;
3421N/A var parts = fullname.split('.');
3421N/A // check for different match types: exact matches of full name or
3421N/A // "last name" (i.e. last dotted part)
3421N/A if (fullname == object || parts[parts.length - 1] == object) {
3421N/A score += Scorer.objNameMatch;
3421N/A // matches in last name
3421N/A } else if (parts[parts.length - 1].indexOf(object) > -1) {
3421N/A score += Scorer.objPartialMatch;
3421N/A }
3421N/A var match = objects[prefix][name];
3421N/A var objname = objnames[match[1]][2];
3421N/A var title = titles[match[0]];
3421N/A // If more than one term searched for, we require other words to be
3421N/A // found in the name/title/description
3421N/A if (otherterms.length > 0) {
3421N/A var haystack = (prefix + ' ' + name + ' ' +
3421N/A objname + ' ' + title).toLowerCase();
3421N/A var allfound = true;
3421N/A for (i = 0; i < otherterms.length; i++) {
3421N/A if (haystack.indexOf(otherterms[i]) == -1) {
3421N/A allfound = false;
3421N/A break;
3421N/A }
3421N/A }
3421N/A if (!allfound) {
3421N/A continue;
3421N/A }
3421N/A }
3421N/A var descr = objname + _(', in ') + title;
3421N/A
3421N/A var anchor = match[3];
3421N/A if (anchor === '')
3421N/A anchor = fullname;
3421N/A else if (anchor == '-')
3421N/A anchor = objnames[match[1]][1] + '-' + fullname;
3421N/A // add custom score for some objects according to scorer
3421N/A if (Scorer.objPrio.hasOwnProperty(match[2])) {
3421N/A score += Scorer.objPrio[match[2]];
3421N/A } else {
3421N/A score += Scorer.objPrioDefault;
3421N/A }
3421N/A results.push([filenames[match[0]], fullname, '#'+anchor, descr, score]);
3421N/A }
3421N/A }
3421N/A }
3421N/A
3421N/A return results;
3421N/A },
3421N/A
3421N/A /**
3421N/A * search for full-text terms in the index
3421N/A */
3421N/A performTermsSearch : function(searchterms, excluded, terms, score) {
3421N/A var filenames = this._index.filenames;
3421N/A var titles = this._index.titles;
3421N/A
3421N/A var i, j, file, files;
3421N/A var fileMap = {};
3421N/A var results = [];
3421N/A
3421N/A // perform the search on the required terms
3421N/A for (i = 0; i < searchterms.length; i++) {
3421N/A var word = searchterms[i];
3421N/A // no match but word was a required one
3421N/A if ((files = terms[word]) === undefined)
3421N/A break;
3421N/A if (files.length === undefined) {
3421N/A files = [files];
3421N/A }
3421N/A // create the mapping
3421N/A for (j = 0; j < files.length; j++) {
3421N/A file = files[j];
3421N/A if (file in fileMap)
3421N/A fileMap[file].push(word);
3421N/A else
3421N/A fileMap[file] = [word];
3421N/A }
3421N/A }
3421N/A
3421N/A // now check if the files don't contain excluded terms
3421N/A for (file in fileMap) {
3421N/A var valid = true;
3421N/A
3421N/A // check if all requirements are matched
3421N/A if (fileMap[file].length != searchterms.length)
3421N/A continue;
3421N/A
3421N/A // ensure that none of the excluded terms is in the search result
3421N/A for (i = 0; i < excluded.length; i++) {
3421N/A if (terms[excluded[i]] == file ||
3421N/A $u.contains(terms[excluded[i]] || [], file)) {
3421N/A valid = false;
3421N/A break;
3421N/A }
3421N/A }
3421N/A
3421N/A // if we have still a valid result we can add it to the result list
3421N/A if (valid) {
3421N/A results.push([filenames[file], titles[file], '', null, score]);
3421N/A }
3421N/A }
3421N/A return results;
3421N/A },
3421N/A
3421N/A /**
3421N/A * helper function to return a node containing the
3421N/A * search summary for a given text. keywords is a list
3421N/A * of stemmed words, hlwords is the list of normal, unstemmed
3421N/A * words. the first one is used to find the occurance, the
3421N/A * latter for highlighting it.
3421N/A */
3421N/A makeSearchSummary : function(text, keywords, hlwords) {
3421N/A var textLower = text.toLowerCase();
3421N/A var start = 0;
3421N/A $.each(keywords, function() {
3421N/A var i = textLower.indexOf(this.toLowerCase());
3421N/A if (i > -1)
3421N/A start = i;
3421N/A });
3421N/A start = Math.max(start - 120, 0);
3421N/A var excerpt = ((start > 0) ? '...' : '') +
3421N/A $.trim(text.substr(start, 240)) +
3421N/A ((start + 240 - text.length) ? '...' : '');
3421N/A var rv = $('<div class="context"></div>').text(excerpt);
3421N/A $.each(hlwords, function() {
3421N/A rv = rv.highlightText(this, 'highlighted');
3421N/A });
3421N/A return rv;
3421N/A }
3421N/A};
3421N/A
3421N/A$(document).ready(function() {
3421N/A Search.init();
3421N/A});