src/build/html/_static/searchtools.js
changeset 10 1d12ff3f036a
parent 9 562bde22e104
child 11 ba0b83b57c0c
equal deleted inserted replaced
9:562bde22e104 10:1d12ff3f036a
     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           if (suffix === undefined) {
       
   544             suffix = '.txt';
       
   545           }
       
   546           $.ajax({url: DOCUMENTATION_OPTIONS.URL_ROOT + '_sources/' + item[5] + (item[5].slice(-suffix.length) === suffix ? '' : suffix),
       
   547                   dataType: "text",
       
   548                   complete: function(jqxhr, textstatus) {
       
   549                     var data = jqxhr.responseText;
       
   550                     if (data !== '' && data !== undefined) {
       
   551                       listItem.append(Search.makeSearchSummary(data, searchterms, hlterms));
       
   552                     }
       
   553                     Search.output.append(listItem);
       
   554                     listItem.slideDown(5, function() {
       
   555                       displayNextItem();
       
   556                     });
       
   557                   }});
       
   558         } else {
       
   559           // no source available, just display title
       
   560           Search.output.append(listItem);
       
   561           listItem.slideDown(5, function() {
       
   562             displayNextItem();
       
   563           });
       
   564         }
       
   565       }
       
   566       // search finished, update title and status message
       
   567       else {
       
   568         Search.stopPulse();
       
   569         Search.title.text(_('Search Results'));
       
   570         if (!resultCount)
       
   571           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.'));
       
   572         else
       
   573             Search.status.text(_('Search finished, found %s page(s) matching the search query.').replace('%s', resultCount));
       
   574         Search.status.fadeIn(500);
       
   575       }
       
   576     }
       
   577     displayNextItem();
       
   578   },
       
   579 
       
   580   /**
       
   581    * search for object names
       
   582    */
       
   583   performObjectSearch : function(object, otherterms) {
       
   584     var filenames = this._index.filenames;
       
   585     var docnames = this._index.docnames;
       
   586     var objects = this._index.objects;
       
   587     var objnames = this._index.objnames;
       
   588     var titles = this._index.titles;
       
   589 
       
   590     var i;
       
   591     var results = [];
       
   592 
       
   593     for (var prefix in objects) {
       
   594       for (var name in objects[prefix]) {
       
   595         var fullname = (prefix ? prefix + '.' : '') + name;
       
   596         if (fullname.toLowerCase().indexOf(object) > -1) {
       
   597           var score = 0;
       
   598           var parts = fullname.split('.');
       
   599           // check for different match types: exact matches of full name or
       
   600           // "last name" (i.e. last dotted part)
       
   601           if (fullname == object || parts[parts.length - 1] == object) {
       
   602             score += Scorer.objNameMatch;
       
   603           // matches in last name
       
   604           } else if (parts[parts.length - 1].indexOf(object) > -1) {
       
   605             score += Scorer.objPartialMatch;
       
   606           }
       
   607           var match = objects[prefix][name];
       
   608           var objname = objnames[match[1]][2];
       
   609           var title = titles[match[0]];
       
   610           // If more than one term searched for, we require other words to be
       
   611           // found in the name/title/description
       
   612           if (otherterms.length > 0) {
       
   613             var haystack = (prefix + ' ' + name + ' ' +
       
   614                             objname + ' ' + title).toLowerCase();
       
   615             var allfound = true;
       
   616             for (i = 0; i < otherterms.length; i++) {
       
   617               if (haystack.indexOf(otherterms[i]) == -1) {
       
   618                 allfound = false;
       
   619                 break;
       
   620               }
       
   621             }
       
   622             if (!allfound) {
       
   623               continue;
       
   624             }
       
   625           }
       
   626           var descr = objname + _(', in ') + title;
       
   627 
       
   628           var anchor = match[3];
       
   629           if (anchor === '')
       
   630             anchor = fullname;
       
   631           else if (anchor == '-')
       
   632             anchor = objnames[match[1]][1] + '-' + fullname;
       
   633           // add custom score for some objects according to scorer
       
   634           if (Scorer.objPrio.hasOwnProperty(match[2])) {
       
   635             score += Scorer.objPrio[match[2]];
       
   636           } else {
       
   637             score += Scorer.objPrioDefault;
       
   638           }
       
   639           results.push([docnames[match[0]], fullname, '#'+anchor, descr, score, filenames[match[0]]]);
       
   640         }
       
   641       }
       
   642     }
       
   643 
       
   644     return results;
       
   645   },
       
   646 
       
   647   /**
       
   648    * search for full-text terms in the index
       
   649    */
       
   650   performTermsSearch : function(searchterms, excluded, terms, titleterms) {
       
   651     var docnames = this._index.docnames;
       
   652     var filenames = this._index.filenames;
       
   653     var titles = this._index.titles;
       
   654 
       
   655     var i, j, file;
       
   656     var fileMap = {};
       
   657     var scoreMap = {};
       
   658     var results = [];
       
   659 
       
   660     // perform the search on the required terms
       
   661     for (i = 0; i < searchterms.length; i++) {
       
   662       var word = searchterms[i];
       
   663       var files = [];
       
   664       var _o = [
       
   665         {files: terms[word], score: Scorer.term},
       
   666         {files: titleterms[word], score: Scorer.title}
       
   667       ];
       
   668 
       
   669       // no match but word was a required one
       
   670       if ($u.every(_o, function(o){return o.files === undefined;})) {
       
   671         break;
       
   672       }
       
   673       // found search word in contents
       
   674       $u.each(_o, function(o) {
       
   675         var _files = o.files;
       
   676         if (_files === undefined)
       
   677           return
       
   678 
       
   679         if (_files.length === undefined)
       
   680           _files = [_files];
       
   681         files = files.concat(_files);
       
   682 
       
   683         // set score for the word in each file to Scorer.term
       
   684         for (j = 0; j < _files.length; j++) {
       
   685           file = _files[j];
       
   686           if (!(file in scoreMap))
       
   687             scoreMap[file] = {}
       
   688           scoreMap[file][word] = o.score;
       
   689         }
       
   690       });
       
   691 
       
   692       // create the mapping
       
   693       for (j = 0; j < files.length; j++) {
       
   694         file = files[j];
       
   695         if (file in fileMap)
       
   696           fileMap[file].push(word);
       
   697         else
       
   698           fileMap[file] = [word];
       
   699       }
       
   700     }
       
   701 
       
   702     // now check if the files don't contain excluded terms
       
   703     for (file in fileMap) {
       
   704       var valid = true;
       
   705 
       
   706       // check if all requirements are matched
       
   707       if (fileMap[file].length != searchterms.length)
       
   708           continue;
       
   709 
       
   710       // ensure that none of the excluded terms is in the search result
       
   711       for (i = 0; i < excluded.length; i++) {
       
   712         if (terms[excluded[i]] == file ||
       
   713             titleterms[excluded[i]] == file ||
       
   714             $u.contains(terms[excluded[i]] || [], file) ||
       
   715             $u.contains(titleterms[excluded[i]] || [], file)) {
       
   716           valid = false;
       
   717           break;
       
   718         }
       
   719       }
       
   720 
       
   721       // if we have still a valid result we can add it to the result list
       
   722       if (valid) {
       
   723         // select one (max) score for the file.
       
   724         // for better ranking, we should calculate ranking by using words statistics like basic tf-idf...
       
   725         var score = $u.max($u.map(fileMap[file], function(w){return scoreMap[file][w]}));
       
   726         results.push([docnames[file], titles[file], '', null, score, filenames[file]]);
       
   727       }
       
   728     }
       
   729     return results;
       
   730   },
       
   731 
       
   732   /**
       
   733    * helper function to return a node containing the
       
   734    * search summary for a given text. keywords is a list
       
   735    * of stemmed words, hlwords is the list of normal, unstemmed
       
   736    * words. the first one is used to find the occurrence, the
       
   737    * latter for highlighting it.
       
   738    */
       
   739   makeSearchSummary : function(text, keywords, hlwords) {
       
   740     var textLower = text.toLowerCase();
       
   741     var start = 0;
       
   742     $.each(keywords, function() {
       
   743       var i = textLower.indexOf(this.toLowerCase());
       
   744       if (i > -1)
       
   745         start = i;
       
   746     });
       
   747     start = Math.max(start - 120, 0);
       
   748     var excerpt = ((start > 0) ? '...' : '') +
       
   749       $.trim(text.substr(start, 240)) +
       
   750       ((start + 240 - text.length) ? '...' : '');
       
   751     var rv = $('<div class="context"></div>').text(excerpt);
       
   752     $.each(hlwords, function() {
       
   753       rv = rv.highlightText(this, 'highlighted');
       
   754     });
       
   755     return rv;
       
   756   }
       
   757 };
       
   758 
       
   759 $(document).ready(function() {
       
   760   Search.init();
       
   761 });