📚 The CoCalc Library - books, templates and other resources
License: OTHER
/*1* searchtools.js_t2* ~~~~~~~~~~~~~~~~3*4* Sphinx JavaScript utilities for the full-text search.5*6* :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS.7* :license: BSD, see LICENSE for details.8*9*/101112/* Non-minified version JS is _stemmer.js if file is provided */13/**14* Porter Stemmer15*/16var Stemmer = function() {1718var step2list = {19ational: 'ate',20tional: 'tion',21enci: 'ence',22anci: 'ance',23izer: 'ize',24bli: 'ble',25alli: 'al',26entli: 'ent',27eli: 'e',28ousli: 'ous',29ization: 'ize',30ation: 'ate',31ator: 'ate',32alism: 'al',33iveness: 'ive',34fulness: 'ful',35ousness: 'ous',36aliti: 'al',37iviti: 'ive',38biliti: 'ble',39logi: 'log'40};4142var step3list = {43icate: 'ic',44ative: '',45alize: 'al',46iciti: 'ic',47ical: 'ic',48ful: '',49ness: ''50};5152var c = "[^aeiou]"; // consonant53var v = "[aeiouy]"; // vowel54var C = c + "[^aeiouy]*"; // consonant sequence55var V = v + "[aeiou]*"; // vowel sequence5657var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>058var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=159var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>160var s_v = "^(" + C + ")?" + v; // vowel in stem6162this.stemWord = function (w) {63var stem;64var suffix;65var firstch;66var origword = w;6768if (w.length < 3)69return w;7071var re;72var re2;73var re3;74var re4;7576firstch = w.substr(0,1);77if (firstch == "y")78w = firstch.toUpperCase() + w.substr(1);7980// Step 1a81re = /^(.+?)(ss|i)es$/;82re2 = /^(.+?)([^s])s$/;8384if (re.test(w))85w = w.replace(re,"$1$2");86else if (re2.test(w))87w = w.replace(re2,"$1$2");8889// Step 1b90re = /^(.+?)eed$/;91re2 = /^(.+?)(ed|ing)$/;92if (re.test(w)) {93var fp = re.exec(w);94re = new RegExp(mgr0);95if (re.test(fp[1])) {96re = /.$/;97w = w.replace(re,"");98}99}100else if (re2.test(w)) {101var fp = re2.exec(w);102stem = fp[1];103re2 = new RegExp(s_v);104if (re2.test(stem)) {105w = stem;106re2 = /(at|bl|iz)$/;107re3 = new RegExp("([^aeiouylsz])\\1$");108re4 = new RegExp("^" + C + v + "[^aeiouwxy]$");109if (re2.test(w))110w = w + "e";111else if (re3.test(w)) {112re = /.$/;113w = w.replace(re,"");114}115else if (re4.test(w))116w = w + "e";117}118}119120// Step 1c121re = /^(.+?)y$/;122if (re.test(w)) {123var fp = re.exec(w);124stem = fp[1];125re = new RegExp(s_v);126if (re.test(stem))127w = stem + "i";128}129130// Step 2131re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;132if (re.test(w)) {133var fp = re.exec(w);134stem = fp[1];135suffix = fp[2];136re = new RegExp(mgr0);137if (re.test(stem))138w = stem + step2list[suffix];139}140141// Step 3142re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;143if (re.test(w)) {144var fp = re.exec(w);145stem = fp[1];146suffix = fp[2];147re = new RegExp(mgr0);148if (re.test(stem))149w = stem + step3list[suffix];150}151152// Step 4153re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;154re2 = /^(.+?)(s|t)(ion)$/;155if (re.test(w)) {156var fp = re.exec(w);157stem = fp[1];158re = new RegExp(mgr1);159if (re.test(stem))160w = stem;161}162else if (re2.test(w)) {163var fp = re2.exec(w);164stem = fp[1] + fp[2];165re2 = new RegExp(mgr1);166if (re2.test(stem))167w = stem;168}169170// Step 5171re = /^(.+?)e$/;172if (re.test(w)) {173var fp = re.exec(w);174stem = fp[1];175re = new RegExp(mgr1);176re2 = new RegExp(meq1);177re3 = new RegExp("^" + C + v + "[^aeiouwxy]$");178if (re.test(stem) || (re2.test(stem) && !(re3.test(stem))))179w = stem;180}181re = /ll$/;182re2 = new RegExp(mgr1);183if (re.test(w) && re2.test(w)) {184re = /.$/;185w = w.replace(re,"");186}187188// and turn initial Y back to y189if (firstch == "y")190w = firstch.toLowerCase() + w.substr(1);191return w;192}193}194195196197/**198* Simple result scoring code.199*/200var Scorer = {201// Implement the following function to further tweak the score for each result202// The function takes a result array [filename, title, anchor, descr, score]203// and returns the new score.204/*205score: function(result) {206return result[4];207},208*/209210// query matches the full name of an object211objNameMatch: 11,212// or matches in the last dotted part of the object name213objPartialMatch: 6,214// Additive scores depending on the priority of the object215objPrio: {0: 15, // used to be importantResults2161: 5, // used to be objectResults2172: -5}, // used to be unimportantResults218// Used when the priority is not in the mapping.219objPrioDefault: 0,220221// query found in title222title: 15,223// query found in terms224term: 5225};226227228/**229* Search Module230*/231var Search = {232233_index : null,234_queued_query : null,235_pulse_status : -1,236237init : function() {238var params = $.getQueryParameters();239if (params.q) {240var query = params.q[0];241$('input[name="q"]')[0].value = query;242this.performSearch(query);243}244},245246loadIndex : function(url) {247$.ajax({type: "GET", url: url, data: null,248dataType: "script", cache: true,249complete: function(jqxhr, textstatus) {250if (textstatus != "success") {251document.getElementById("searchindexloader").src = url;252}253}});254},255256setIndex : function(index) {257var q;258this._index = index;259if ((q = this._queued_query) !== null) {260this._queued_query = null;261Search.query(q);262}263},264265hasIndex : function() {266return this._index !== null;267},268269deferQuery : function(query) {270this._queued_query = query;271},272273stopPulse : function() {274this._pulse_status = 0;275},276277startPulse : function() {278if (this._pulse_status >= 0)279return;280function pulse() {281var i;282Search._pulse_status = (Search._pulse_status + 1) % 4;283var dotString = '';284for (i = 0; i < Search._pulse_status; i++)285dotString += '.';286Search.dots.text(dotString);287if (Search._pulse_status > -1)288window.setTimeout(pulse, 500);289}290pulse();291},292293/**294* perform a search for something (or wait until index is loaded)295*/296performSearch : function(query) {297// create the required interface elements298this.out = $('#search-results');299this.title = $('<h2>' + _('Searching') + '</h2>').appendTo(this.out);300this.dots = $('<span></span>').appendTo(this.title);301this.status = $('<p style="display: none"></p>').appendTo(this.out);302this.output = $('<ul class="search"/>').appendTo(this.out);303304$('#search-progress').text(_('Preparing search...'));305this.startPulse();306307// index already loaded, the browser was quick!308if (this.hasIndex())309this.query(query);310else311this.deferQuery(query);312},313314/**315* execute search (requires search index to be loaded)316*/317query : function(query) {318var i;319var 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"];320321// stem the searchterms and add them to the correct list322var stemmer = new Stemmer();323var searchterms = [];324var excluded = [];325var hlterms = [];326var tmp = query.split(/\s+/);327var objectterms = [];328for (i = 0; i < tmp.length; i++) {329if (tmp[i] !== "") {330objectterms.push(tmp[i].toLowerCase());331}332333if ($u.indexOf(stopwords, tmp[i].toLowerCase()) != -1 || tmp[i].match(/^\d+$/) ||334tmp[i] === "") {335// skip this "word"336continue;337}338// stem the word339var word = stemmer.stemWord(tmp[i].toLowerCase());340var toAppend;341// select the correct list342if (word[0] == '-') {343toAppend = excluded;344word = word.substr(1);345}346else {347toAppend = searchterms;348hlterms.push(tmp[i].toLowerCase());349}350// only add if not already in the list351if (!$u.contains(toAppend, word))352toAppend.push(word);353}354var highlightstring = '?highlight=' + $.urlencode(hlterms.join(" "));355356// console.debug('SEARCH: searching for:');357// console.info('required: ', searchterms);358// console.info('excluded: ', excluded);359360// prepare search361var terms = this._index.terms;362var titleterms = this._index.titleterms;363364// array of [filename, title, anchor, descr, score]365var results = [];366$('#search-progress').empty();367368// lookup as object369for (i = 0; i < objectterms.length; i++) {370var others = [].concat(objectterms.slice(0, i),371objectterms.slice(i+1, objectterms.length));372results = results.concat(this.performObjectSearch(objectterms[i], others));373}374375// lookup as search terms in fulltext376results = results.concat(this.performTermsSearch(searchterms, excluded, terms, titleterms));377378// let the scorer override scores with a custom scoring function379if (Scorer.score) {380for (i = 0; i < results.length; i++)381results[i][4] = Scorer.score(results[i]);382}383384// now sort the results by score (in opposite order of appearance, since the385// display function below uses pop() to retrieve items) and then386// alphabetically387results.sort(function(a, b) {388var left = a[4];389var right = b[4];390if (left > right) {391return 1;392} else if (left < right) {393return -1;394} else {395// same score: sort alphabetically396left = a[1].toLowerCase();397right = b[1].toLowerCase();398return (left > right) ? -1 : ((left < right) ? 1 : 0);399}400});401402// for debugging403//Search.lastresults = results.slice(); // a copy404//console.info('search results:', Search.lastresults);405406// print the results407var resultCount = results.length;408function displayNextItem() {409// results left, load the summary and display it410if (results.length) {411var item = results.pop();412var listItem = $('<li style="display:none"></li>');413if (DOCUMENTATION_OPTIONS.FILE_SUFFIX === '') {414// dirhtml builder415var dirname = item[0] + '/';416if (dirname.match(/\/index\/$/)) {417dirname = dirname.substring(0, dirname.length-6);418} else if (dirname == 'index/') {419dirname = '';420}421listItem.append($('<a/>').attr('href',422DOCUMENTATION_OPTIONS.URL_ROOT + dirname +423highlightstring + item[2]).html(item[1]));424} else {425// normal html builders426listItem.append($('<a/>').attr('href',427item[0] + DOCUMENTATION_OPTIONS.FILE_SUFFIX +428highlightstring + item[2]).html(item[1]));429}430if (item[3]) {431listItem.append($('<span> (' + item[3] + ')</span>'));432Search.output.append(listItem);433listItem.slideDown(5, function() {434displayNextItem();435});436} else if (DOCUMENTATION_OPTIONS.HAS_SOURCE) {437$.ajax({url: DOCUMENTATION_OPTIONS.URL_ROOT + '_sources/' + item[0] + '.txt',438dataType: "text",439complete: function(jqxhr, textstatus) {440var data = jqxhr.responseText;441if (data !== '' && data !== undefined) {442listItem.append(Search.makeSearchSummary(data, searchterms, hlterms));443}444Search.output.append(listItem);445listItem.slideDown(5, function() {446displayNextItem();447});448}});449} else {450// no source available, just display title451Search.output.append(listItem);452listItem.slideDown(5, function() {453displayNextItem();454});455}456}457// search finished, update title and status message458else {459Search.stopPulse();460Search.title.text(_('Search Results'));461if (!resultCount)462Search.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.'));463else464Search.status.text(_('Search finished, found %s page(s) matching the search query.').replace('%s', resultCount));465Search.status.fadeIn(500);466}467}468displayNextItem();469},470471/**472* search for object names473*/474performObjectSearch : function(object, otherterms) {475var filenames = this._index.filenames;476var objects = this._index.objects;477var objnames = this._index.objnames;478var titles = this._index.titles;479480var i;481var results = [];482483for (var prefix in objects) {484for (var name in objects[prefix]) {485var fullname = (prefix ? prefix + '.' : '') + name;486if (fullname.toLowerCase().indexOf(object) > -1) {487var score = 0;488var parts = fullname.split('.');489// check for different match types: exact matches of full name or490// "last name" (i.e. last dotted part)491if (fullname == object || parts[parts.length - 1] == object) {492score += Scorer.objNameMatch;493// matches in last name494} else if (parts[parts.length - 1].indexOf(object) > -1) {495score += Scorer.objPartialMatch;496}497var match = objects[prefix][name];498var objname = objnames[match[1]][2];499var title = titles[match[0]];500// If more than one term searched for, we require other words to be501// found in the name/title/description502if (otherterms.length > 0) {503var haystack = (prefix + ' ' + name + ' ' +504objname + ' ' + title).toLowerCase();505var allfound = true;506for (i = 0; i < otherterms.length; i++) {507if (haystack.indexOf(otherterms[i]) == -1) {508allfound = false;509break;510}511}512if (!allfound) {513continue;514}515}516var descr = objname + _(', in ') + title;517518var anchor = match[3];519if (anchor === '')520anchor = fullname;521else if (anchor == '-')522anchor = objnames[match[1]][1] + '-' + fullname;523// add custom score for some objects according to scorer524if (Scorer.objPrio.hasOwnProperty(match[2])) {525score += Scorer.objPrio[match[2]];526} else {527score += Scorer.objPrioDefault;528}529results.push([filenames[match[0]], fullname, '#'+anchor, descr, score]);530}531}532}533534return results;535},536537/**538* search for full-text terms in the index539*/540performTermsSearch : function(searchterms, excluded, terms, titleterms) {541var filenames = this._index.filenames;542var titles = this._index.titles;543544var i, j, file;545var fileMap = {};546var scoreMap = {};547var results = [];548549// perform the search on the required terms550for (i = 0; i < searchterms.length; i++) {551var word = searchterms[i];552var files = [];553var _o = [554{files: terms[word], score: Scorer.term},555{files: titleterms[word], score: Scorer.title}556];557558// no match but word was a required one559if ($u.every(_o, function(o){return o.files === undefined;})) {560break;561}562// found search word in contents563$u.each(_o, function(o) {564var _files = o.files;565if (_files === undefined)566return567568if (_files.length === undefined)569_files = [_files];570files = files.concat(_files);571572// set score for the word in each file to Scorer.term573for (j = 0; j < _files.length; j++) {574file = _files[j];575if (!(file in scoreMap))576scoreMap[file] = {}577scoreMap[file][word] = o.score;578}579});580581// create the mapping582for (j = 0; j < files.length; j++) {583file = files[j];584if (file in fileMap)585fileMap[file].push(word);586else587fileMap[file] = [word];588}589}590591// now check if the files don't contain excluded terms592for (file in fileMap) {593var valid = true;594595// check if all requirements are matched596if (fileMap[file].length != searchterms.length)597continue;598599// ensure that none of the excluded terms is in the search result600for (i = 0; i < excluded.length; i++) {601if (terms[excluded[i]] == file ||602titleterms[excluded[i]] == file ||603$u.contains(terms[excluded[i]] || [], file) ||604$u.contains(titleterms[excluded[i]] || [], file)) {605valid = false;606break;607}608}609610// if we have still a valid result we can add it to the result list611if (valid) {612// select one (max) score for the file.613// for better ranking, we should calculate ranking by using words statistics like basic tf-idf...614var score = $u.max($u.map(fileMap[file], function(w){return scoreMap[file][w]}));615results.push([filenames[file], titles[file], '', null, score]);616}617}618return results;619},620621/**622* helper function to return a node containing the623* search summary for a given text. keywords is a list624* of stemmed words, hlwords is the list of normal, unstemmed625* words. the first one is used to find the occurrence, the626* latter for highlighting it.627*/628makeSearchSummary : function(text, keywords, hlwords) {629var textLower = text.toLowerCase();630var start = 0;631$.each(keywords, function() {632var i = textLower.indexOf(this.toLowerCase());633if (i > -1)634start = i;635});636start = Math.max(start - 120, 0);637var excerpt = ((start > 0) ? '...' : '') +638$.trim(text.substr(start, 240)) +639((start + 240 - text.length) ? '...' : '');640var rv = $('<div class="context"></div>').text(excerpt);641$.each(hlwords, function() {642rv = rv.highlightText(this, 'highlighted');643});644return rv;645}646};647648$(document).ready(function() {649Search.init();650});651652