Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.

searchtools.js 14 KiB

14 anni fa
14 anni fa
14 anni fa
14 anni fa
14 anni fa
14 anni fa
14 anni fa
14 anni fa
14 anni fa
14 anni fa
14 anni fa
14 anni fa
14 anni fa
14 anni fa
14 anni fa
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  1. /*
  2. * searchtools.js
  3. * ~~~~~~~~~~~~~~
  4. *
  5. * Sphinx JavaScript utilties for the full-text search.
  6. *
  7. * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
  8. * :license: BSD, see LICENSE for details.
  9. *
  10. */
  11. /**
  12. * helper function to return a node containing the
  13. * search summary for a given text. keywords is a list
  14. * of stemmed words, hlwords is the list of normal, unstemmed
  15. * words. the first one is used to find the occurance, the
  16. * latter for highlighting it.
  17. */
  18. jQuery.makeSearchSummary = function(text, keywords, hlwords) {
  19. var textLower = text.toLowerCase();
  20. var start = 0;
  21. $.each(keywords, function() {
  22. var i = textLower.indexOf(this.toLowerCase());
  23. if (i > -1)
  24. start = i;
  25. });
  26. start = Math.max(start - 120, 0);
  27. var excerpt = ((start > 0) ? '...' : '') +
  28. $.trim(text.substr(start, 240)) +
  29. ((start + 240 - text.length) ? '...' : '');
  30. var rv = $('<div class="context"></div>').text(excerpt);
  31. $.each(hlwords, function() {
  32. rv = rv.highlightText(this, 'highlighted');
  33. });
  34. return rv;
  35. }
  36. /**
  37. * Porter Stemmer
  38. */
  39. var PorterStemmer = function() {
  40. var step2list = {
  41. ational: 'ate',
  42. tional: 'tion',
  43. enci: 'ence',
  44. anci: 'ance',
  45. izer: 'ize',
  46. bli: 'ble',
  47. alli: 'al',
  48. entli: 'ent',
  49. eli: 'e',
  50. ousli: 'ous',
  51. ization: 'ize',
  52. ation: 'ate',
  53. ator: 'ate',
  54. alism: 'al',
  55. iveness: 'ive',
  56. fulness: 'ful',
  57. ousness: 'ous',
  58. aliti: 'al',
  59. iviti: 'ive',
  60. biliti: 'ble',
  61. logi: 'log'
  62. };
  63. var step3list = {
  64. icate: 'ic',
  65. ative: '',
  66. alize: 'al',
  67. iciti: 'ic',
  68. ical: 'ic',
  69. ful: '',
  70. ness: ''
  71. };
  72. var c = "[^aeiou]"; // consonant
  73. var v = "[aeiouy]"; // vowel
  74. var C = c + "[^aeiouy]*"; // consonant sequence
  75. var V = v + "[aeiou]*"; // vowel sequence
  76. var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0
  77. var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1
  78. var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1
  79. var s_v = "^(" + C + ")?" + v; // vowel in stem
  80. this.stemWord = function (w) {
  81. var stem;
  82. var suffix;
  83. var firstch;
  84. var origword = w;
  85. if (w.length < 3)
  86. return w;
  87. var re;
  88. var re2;
  89. var re3;
  90. var re4;
  91. firstch = w.substr(0,1);
  92. if (firstch == "y")
  93. w = firstch.toUpperCase() + w.substr(1);
  94. // Step 1a
  95. re = /^(.+?)(ss|i)es$/;
  96. re2 = /^(.+?)([^s])s$/;
  97. if (re.test(w))
  98. w = w.replace(re,"$1$2");
  99. else if (re2.test(w))
  100. w = w.replace(re2,"$1$2");
  101. // Step 1b
  102. re = /^(.+?)eed$/;
  103. re2 = /^(.+?)(ed|ing)$/;
  104. if (re.test(w)) {
  105. var fp = re.exec(w);
  106. re = new RegExp(mgr0);
  107. if (re.test(fp[1])) {
  108. re = /.$/;
  109. w = w.replace(re,"");
  110. }
  111. }
  112. else if (re2.test(w)) {
  113. var fp = re2.exec(w);
  114. stem = fp[1];
  115. re2 = new RegExp(s_v);
  116. if (re2.test(stem)) {
  117. w = stem;
  118. re2 = /(at|bl|iz)$/;
  119. re3 = new RegExp("([^aeiouylsz])\\1$");
  120. re4 = new RegExp("^" + C + v + "[^aeiouwxy]$");
  121. if (re2.test(w))
  122. w = w + "e";
  123. else if (re3.test(w)) {
  124. re = /.$/;
  125. w = w.replace(re,"");
  126. }
  127. else if (re4.test(w))
  128. w = w + "e";
  129. }
  130. }
  131. // Step 1c
  132. re = /^(.+?)y$/;
  133. if (re.test(w)) {
  134. var fp = re.exec(w);
  135. stem = fp[1];
  136. re = new RegExp(s_v);
  137. if (re.test(stem))
  138. w = stem + "i";
  139. }
  140. // Step 2
  141. re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;
  142. if (re.test(w)) {
  143. var fp = re.exec(w);
  144. stem = fp[1];
  145. suffix = fp[2];
  146. re = new RegExp(mgr0);
  147. if (re.test(stem))
  148. w = stem + step2list[suffix];
  149. }
  150. // Step 3
  151. re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;
  152. if (re.test(w)) {
  153. var fp = re.exec(w);
  154. stem = fp[1];
  155. suffix = fp[2];
  156. re = new RegExp(mgr0);
  157. if (re.test(stem))
  158. w = stem + step3list[suffix];
  159. }
  160. // Step 4
  161. re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;
  162. re2 = /^(.+?)(s|t)(ion)$/;
  163. if (re.test(w)) {
  164. var fp = re.exec(w);
  165. stem = fp[1];
  166. re = new RegExp(mgr1);
  167. if (re.test(stem))
  168. w = stem;
  169. }
  170. else if (re2.test(w)) {
  171. var fp = re2.exec(w);
  172. stem = fp[1] + fp[2];
  173. re2 = new RegExp(mgr1);
  174. if (re2.test(stem))
  175. w = stem;
  176. }
  177. // Step 5
  178. re = /^(.+?)e$/;
  179. if (re.test(w)) {
  180. var fp = re.exec(w);
  181. stem = fp[1];
  182. re = new RegExp(mgr1);
  183. re2 = new RegExp(meq1);
  184. re3 = new RegExp("^" + C + v + "[^aeiouwxy]$");
  185. if (re.test(stem) || (re2.test(stem) && !(re3.test(stem))))
  186. w = stem;
  187. }
  188. re = /ll$/;
  189. re2 = new RegExp(mgr1);
  190. if (re.test(w) && re2.test(w)) {
  191. re = /.$/;
  192. w = w.replace(re,"");
  193. }
  194. // and turn initial Y back to y
  195. if (firstch == "y")
  196. w = firstch.toLowerCase() + w.substr(1);
  197. return w;
  198. }
  199. }
  200. /**
  201. * Search Module
  202. */
  203. var Search = {
  204. _index : null,
  205. _queued_query : null,
  206. _pulse_status : -1,
  207. init : function() {
  208. var params = $.getQueryParameters();
  209. if (params.q) {
  210. var query = params.q[0];
  211. $('input[name="q"]')[0].value = query;
  212. this.performSearch(query);
  213. }
  214. },
  215. loadIndex : function(url) {
  216. $.ajax({type: "GET", url: url, data: null, success: null,
  217. dataType: "script", cache: true});
  218. },
  219. setIndex : function(index) {
  220. var q;
  221. this._index = index;
  222. if ((q = this._queued_query) !== null) {
  223. this._queued_query = null;
  224. Search.query(q);
  225. }
  226. },
  227. hasIndex : function() {
  228. return this._index !== null;
  229. },
  230. deferQuery : function(query) {
  231. this._queued_query = query;
  232. },
  233. stopPulse : function() {
  234. this._pulse_status = 0;
  235. },
  236. startPulse : function() {
  237. if (this._pulse_status >= 0)
  238. return;
  239. function pulse() {
  240. Search._pulse_status = (Search._pulse_status + 1) % 4;
  241. var dotString = '';
  242. for (var i = 0; i < Search._pulse_status; i++)
  243. dotString += '.';
  244. Search.dots.text(dotString);
  245. if (Search._pulse_status > -1)
  246. window.setTimeout(pulse, 500);
  247. };
  248. pulse();
  249. },
  250. /**
  251. * perform a search for something
  252. */
  253. performSearch : function(query) {
  254. // create the required interface elements
  255. this.out = $('#search-results');
  256. this.title = $('<h2>' + _('Searching') + '</h2>').appendTo(this.out);
  257. this.dots = $('<span></span>').appendTo(this.title);
  258. this.status = $('<p style="display: none"></p>').appendTo(this.out);
  259. this.output = $('<ul class="search"/>').appendTo(this.out);
  260. $('#search-progress').text(_('Preparing search...'));
  261. this.startPulse();
  262. // index already loaded, the browser was quick!
  263. if (this.hasIndex())
  264. this.query(query);
  265. else
  266. this.deferQuery(query);
  267. },
  268. query : function(query) {
  269. var stopwords = ['and', 'then', 'into', 'it', 'as', 'are', 'in',
  270. 'if', 'for', 'no', 'there', 'their', 'was', 'is',
  271. 'be', 'to', 'that', 'but', 'they', 'not', 'such',
  272. 'with', 'by', 'a', 'on', 'these', 'of', 'will',
  273. 'this', 'near', 'the', 'or', 'at'];
  274. // stem the searchterms and add them to the correct list
  275. var stemmer = new PorterStemmer();
  276. var searchterms = [];
  277. var excluded = [];
  278. var hlterms = [];
  279. var tmp = query.split(/\s+/);
  280. var object = (tmp.length == 1) ? tmp[0].toLowerCase() : null;
  281. for (var i = 0; i < tmp.length; i++) {
  282. if ($u.indexOf(stopwords, tmp[i]) != -1 || tmp[i].match(/^\d+$/) ||
  283. tmp[i] == "") {
  284. // skip this "word"
  285. continue;
  286. }
  287. // stem the word
  288. var word = stemmer.stemWord(tmp[i]).toLowerCase();
  289. // select the correct list
  290. if (word[0] == '-') {
  291. var toAppend = excluded;
  292. word = word.substr(1);
  293. }
  294. else {
  295. var toAppend = searchterms;
  296. hlterms.push(tmp[i].toLowerCase());
  297. }
  298. // only add if not already in the list
  299. if (!$.contains(toAppend, word))
  300. toAppend.push(word);
  301. };
  302. var highlightstring = '?highlight=' + $.urlencode(hlterms.join(" "));
  303. // console.debug('SEARCH: searching for:');
  304. // console.info('required: ', searchterms);
  305. // console.info('excluded: ', excluded);
  306. // prepare search
  307. var filenames = this._index.filenames;
  308. var titles = this._index.titles;
  309. var terms = this._index.terms;
  310. var objects = this._index.objects;
  311. var objtypes = this._index.objtypes;
  312. var objnames = this._index.objnames;
  313. var fileMap = {};
  314. var files = null;
  315. // different result priorities
  316. var importantResults = [];
  317. var objectResults = [];
  318. var regularResults = [];
  319. var unimportantResults = [];
  320. $('#search-progress').empty();
  321. // lookup as object
  322. if (object != null) {
  323. for (var prefix in objects) {
  324. for (var name in objects[prefix]) {
  325. var fullname = (prefix ? prefix + '.' : '') + name;
  326. if (fullname.toLowerCase().indexOf(object) > -1) {
  327. match = objects[prefix][name];
  328. descr = objnames[match[1]] + _(', in ') + titles[match[0]];
  329. // XXX the generated anchors are not generally correct
  330. // XXX there may be custom prefixes
  331. result = [filenames[match[0]], fullname, '#'+fullname, descr];
  332. switch (match[2]) {
  333. case 1: objectResults.push(result); break;
  334. case 0: importantResults.push(result); break;
  335. case 2: unimportantResults.push(result); break;
  336. }
  337. }
  338. }
  339. }
  340. }
  341. // sort results descending
  342. objectResults.sort(function(a, b) {
  343. return (a[1] > b[1]) ? -1 : ((a[1] < b[1]) ? 1 : 0);
  344. });
  345. importantResults.sort(function(a, b) {
  346. return (a[1] > b[1]) ? -1 : ((a[1] < b[1]) ? 1 : 0);
  347. });
  348. unimportantResults.sort(function(a, b) {
  349. return (a[1] > b[1]) ? -1 : ((a[1] < b[1]) ? 1 : 0);
  350. });
  351. // perform the search on the required terms
  352. for (var i = 0; i < searchterms.length; i++) {
  353. var word = searchterms[i];
  354. // no match but word was a required one
  355. if ((files = terms[word]) == null)
  356. break;
  357. if (files.length == undefined) {
  358. files = [files];
  359. }
  360. // create the mapping
  361. for (var j = 0; j < files.length; j++) {
  362. var file = files[j];
  363. if (file in fileMap)
  364. fileMap[file].push(word);
  365. else
  366. fileMap[file] = [word];
  367. }
  368. }
  369. // now check if the files don't contain excluded terms
  370. for (var file in fileMap) {
  371. var valid = true;
  372. // check if all requirements are matched
  373. if (fileMap[file].length != searchterms.length)
  374. continue;
  375. // ensure that none of the excluded terms is in the
  376. // search result.
  377. for (var i = 0; i < excluded.length; i++) {
  378. if (terms[excluded[i]] == file ||
  379. $.contains(terms[excluded[i]] || [], file)) {
  380. valid = false;
  381. break;
  382. }
  383. }
  384. // if we have still a valid result we can add it
  385. // to the result list
  386. if (valid)
  387. regularResults.push([filenames[file], titles[file], '', null]);
  388. }
  389. // delete unused variables in order to not waste
  390. // memory until list is retrieved completely
  391. delete filenames, titles, terms;
  392. // now sort the regular results descending by title
  393. regularResults.sort(function(a, b) {
  394. var left = a[1].toLowerCase();
  395. var right = b[1].toLowerCase();
  396. return (left > right) ? -1 : ((left < right) ? 1 : 0);
  397. });
  398. // combine all results
  399. var results = unimportantResults.concat(regularResults)
  400. .concat(objectResults).concat(importantResults);
  401. // print the results
  402. var resultCount = results.length;
  403. function displayNextItem() {
  404. // results left, load the summary and display it
  405. if (results.length) {
  406. var item = results.pop();
  407. var listItem = $('<li style="display:none"></li>');
  408. if (DOCUMENTATION_OPTIONS.FILE_SUFFIX == '') {
  409. // dirhtml builder
  410. var dirname = item[0] + '/';
  411. if (dirname.match(/\/index\/$/)) {
  412. dirname = dirname.substring(0, dirname.length-6);
  413. } else if (dirname == 'index/') {
  414. dirname = '';
  415. }
  416. listItem.append($('<a/>').attr('href',
  417. DOCUMENTATION_OPTIONS.URL_ROOT + dirname +
  418. highlightstring + item[2]).html(item[1]));
  419. } else {
  420. // normal html builders
  421. listItem.append($('<a/>').attr('href',
  422. item[0] + DOCUMENTATION_OPTIONS.FILE_SUFFIX +
  423. highlightstring + item[2]).html(item[1]));
  424. }
  425. if (item[3]) {
  426. listItem.append($('<span> (' + item[3] + ')</span>'));
  427. Search.output.append(listItem);
  428. listItem.slideDown(5, function() {
  429. displayNextItem();
  430. });
  431. } else if (DOCUMENTATION_OPTIONS.HAS_SOURCE) {
  432. $.get(DOCUMENTATION_OPTIONS.URL_ROOT + '_sources/' +
  433. item[0] + '.txt', function(data) {
  434. if (data != '') {
  435. listItem.append($.makeSearchSummary(data, searchterms, hlterms));
  436. Search.output.append(listItem);
  437. }
  438. listItem.slideDown(5, function() {
  439. displayNextItem();
  440. });
  441. });
  442. } else {
  443. // no source available, just display title
  444. Search.output.append(listItem);
  445. listItem.slideDown(5, function() {
  446. displayNextItem();
  447. });
  448. }
  449. }
  450. // search finished, update title and status message
  451. else {
  452. Search.stopPulse();
  453. Search.title.text(_('Search Results'));
  454. if (!resultCount)
  455. 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.'));
  456. else
  457. Search.status.text(_('Search finished, found %s page(s) matching the search query.').replace('%s', resultCount));
  458. Search.status.fadeIn(500);
  459. }
  460. }
  461. displayNextItem();
  462. }
  463. }
  464. $(document).ready(function() {
  465. Search.init();
  466. });