| [2069] | 1 | # -*- coding: utf-8 -*- |
|---|
| [1209] | 2 | # vim: ts=4 expandtab |
|---|
| 3 | # |
|---|
| 4 | # Copyright (C) 2005 Jason Parks <jparks@jparks.net>. All rights reserved. |
|---|
| [2069] | 5 | # Copyright (C) 2006-2007 Christian Boos <cboos@neuf.fr> |
|---|
| [1209] | 6 | # |
|---|
| 7 | |
|---|
| 8 | import os |
|---|
| 9 | import time |
|---|
| 10 | import posixpath |
|---|
| 11 | import re |
|---|
| 12 | import mimetypes |
|---|
| 13 | |
|---|
| [2069] | 14 | from genshi.builder import tag |
|---|
| [3132] | 15 | from genshi.core import Markup |
|---|
| [2069] | 16 | |
|---|
| [1209] | 17 | from trac.config import Option |
|---|
| 18 | from trac.core import * |
|---|
| 19 | from trac.web import IRequestHandler |
|---|
| 20 | from trac.perm import IPermissionRequestor |
|---|
| 21 | from trac.web.chrome import INavigationContributor, ITemplateProvider, \ |
|---|
| [3132] | 22 | add_stylesheet, add_ctxtnav |
|---|
| [2069] | 23 | from trac.search.api import ISearchSource |
|---|
| [3737] | 24 | from trac.util.text import to_unicode |
|---|
| [5596] | 25 | from trac.util.datefmt import to_datetime |
|---|
| [2069] | 26 | from trac.wiki.api import WikiSystem, IWikiSyntaxProvider |
|---|
| [1226] | 27 | from trac.wiki.model import WikiPage |
|---|
| [1251] | 28 | from trac.wiki.formatter import wiki_to_html |
|---|
| [1209] | 29 | |
|---|
| 30 | def compare_rank(x, y): |
|---|
| 31 | if x['rank'] == y['rank']: |
|---|
| 32 | return 0 |
|---|
| 33 | elif x['rank'] > y['rank']: |
|---|
| 34 | return -1 |
|---|
| 35 | return 1 |
|---|
| 36 | |
|---|
| 37 | class DoxygenPlugin(Component): |
|---|
| 38 | implements(IPermissionRequestor, INavigationContributor, IRequestHandler, |
|---|
| [2069] | 39 | ITemplateProvider, ISearchSource, IWikiSyntaxProvider) |
|---|
| [1209] | 40 | |
|---|
| 41 | base_path = Option('doxygen', 'path', '/var/lib/trac/doxygen', |
|---|
| 42 | """Directory containing doxygen generated files.""") |
|---|
| 43 | |
|---|
| 44 | default_doc = Option('doxygen', 'default_documentation', '', |
|---|
| [1245] | 45 | """Default documentation project, relative to `[doxygen] path`. |
|---|
| 46 | When no explicit path is given in a documentation request, |
|---|
| 47 | this path will be prepended to the request before looking |
|---|
| 48 | for documentation files.""") |
|---|
| [1209] | 49 | |
|---|
| [12233] | 50 | html_output = Option('doxygen', 'html_output', '', |
|---|
| [1249] | 51 | """Default documentation project suffix, as generated by Doxygen |
|---|
| 52 | using the HTML_OUTPUT Doxygen configuration setting.""") |
|---|
| 53 | |
|---|
| [1209] | 54 | title = Option('doxygen', 'title', 'Doxygen', |
|---|
| 55 | """Title to use for the main navigation tab.""") |
|---|
| 56 | |
|---|
| 57 | ext = Option('doxygen', 'ext', 'htm html png', |
|---|
| 58 | """Space separated list of extensions for doxygen managed files.""") |
|---|
| 59 | |
|---|
| 60 | source_ext = Option('doxygen', 'source_ext', |
|---|
| 61 | 'idl odl java cs py php php4 inc phtml m ' |
|---|
| 62 | 'cpp cxx c hpp hxx h', |
|---|
| 63 | """Space separated list of source files extensions""") |
|---|
| 64 | |
|---|
| 65 | index = Option('doxygen', 'index', 'main.html', |
|---|
| 66 | """Default index page to pick in the generated documentation.""") |
|---|
| 67 | |
|---|
| 68 | wiki_index = Option('doxygen', 'wiki_index', None, |
|---|
| [1245] | 69 | """Wiki page to use as the default page for the Doxygen main page. |
|---|
| 70 | If set, supersedes the `[doxygen] index` option.""") |
|---|
| [1209] | 71 | |
|---|
| 72 | encoding = Option('doxygen', 'encoding', 'iso-8859-1', |
|---|
| 73 | """Default encoding used by the generated documentation files.""") |
|---|
| 74 | |
|---|
| [3735] | 75 | default_namespace = Option('doxygen', 'default_namespace', '', |
|---|
| 76 | """Default namespace to search for named objects in.""") |
|---|
| 77 | |
|---|
| [1226] | 78 | SUMMARY_PAGES = """ |
|---|
| 79 | annotated classes dirs files functions globals hierarchy |
|---|
| 80 | index inherits main namespaces namespacemembers |
|---|
| 81 | """.split() |
|---|
| 82 | |
|---|
| [1209] | 83 | # IPermissionRequestor methods |
|---|
| 84 | |
|---|
| 85 | def get_permission_actions(self): |
|---|
| 86 | return ['DOXYGEN_VIEW'] |
|---|
| 87 | |
|---|
| 88 | # INavigationContributor methods |
|---|
| 89 | |
|---|
| 90 | def get_active_navigation_item(self, req): |
|---|
| 91 | return 'doxygen' |
|---|
| 92 | |
|---|
| 93 | def get_navigation_items(self, req): |
|---|
| 94 | if req.perm.has_permission('DOXYGEN_VIEW'): |
|---|
| 95 | # Return mainnav buttons. |
|---|
| [2069] | 96 | yield ('mainnav', 'doxygen', |
|---|
| 97 | tag.a(self.title, href=req.href.doxygen())) |
|---|
| [1209] | 98 | |
|---|
| 99 | # IRequestHandler methods |
|---|
| 100 | |
|---|
| 101 | def match_request(self, req): |
|---|
| [1226] | 102 | if re.match(r'^/doxygen(?:$|/)', req.path_info): |
|---|
| [1249] | 103 | if 'path' not in req.args: # not coming from a `doxygen:` link |
|---|
| 104 | segments = filter(None, req.path_info.split('/')) |
|---|
| 105 | segments = segments[1:] # ditch 'doxygen' |
|---|
| 106 | if segments: |
|---|
| 107 | action, path, link = self._doxygen_lookup(segments) |
|---|
| 108 | if action == 'search' and link: |
|---|
| 109 | req.args['query'] = link |
|---|
| 110 | elif action == 'redirect': |
|---|
| 111 | req.args['link'] = link |
|---|
| 112 | else: |
|---|
| 113 | action, path = 'index', '' |
|---|
| 114 | req.args['action'] = action |
|---|
| 115 | req.args['path'] = path |
|---|
| [1209] | 116 | return True |
|---|
| [2651] | 117 | |
|---|
| [1209] | 118 | def process_request(self, req): |
|---|
| 119 | req.perm.assert_permission('DOXYGEN_VIEW') |
|---|
| 120 | |
|---|
| 121 | # Get request arguments |
|---|
| 122 | path = req.args.get('path') |
|---|
| 123 | action = req.args.get('action') |
|---|
| [1244] | 124 | link = req.args.get('link') |
|---|
| [1209] | 125 | |
|---|
| [1244] | 126 | self.log.debug('Performing %s(%s,%s)"' % (action or 'default', |
|---|
| 127 | path, link)) |
|---|
| [1209] | 128 | |
|---|
| 129 | # Redirect search requests. |
|---|
| 130 | if action == 'search': |
|---|
| [1226] | 131 | req.redirect(req.href.search(q=req.args.get('query'), |
|---|
| 132 | doxygen='on')) |
|---|
| [1244] | 133 | if action == 'redirect': |
|---|
| 134 | if link: # we need to really redirect if there is a link |
|---|
| 135 | if path: |
|---|
| 136 | req.redirect(req.href.doxygen(path=path)+link) |
|---|
| 137 | else: |
|---|
| 138 | req.redirect(req.href.doxygen(link)) |
|---|
| 139 | else: |
|---|
| 140 | self.log.warn("redirect without link") |
|---|
| [1209] | 141 | |
|---|
| [1983] | 142 | if req.path_info == '/doxygen': |
|---|
| 143 | req.redirect(req.href.doxygen('/')) |
|---|
| 144 | |
|---|
| [1226] | 145 | # Handle /doxygen request |
|---|
| 146 | if action == 'index': |
|---|
| [1251] | 147 | wiki = self.wiki_index |
|---|
| 148 | if wiki: |
|---|
| 149 | if WikiSystem(self.env).has_page(wiki): |
|---|
| 150 | text = WikiPage(self.env, wiki).text |
|---|
| 151 | else: |
|---|
| 152 | text = 'Doxygen index page [wiki:%s] does not exist.' % \ |
|---|
| 153 | wiki |
|---|
| [3132] | 154 | data = {'doxygen_text': wiki_to_html(text, self.env, req)} |
|---|
| 155 | add_ctxtnav(req, "View %s page" % wiki, req.href.wiki(wiki)) |
|---|
| 156 | return 'doxygen.html', data, 'text/html' |
|---|
| [1251] | 157 | # use configured Doxygen index |
|---|
| 158 | path = os.path.join(self.base_path, self.default_doc, |
|---|
| 159 | self.html_output, self.index) |
|---|
| [2651] | 160 | |
|---|
| 161 | self.log.debug('path: %s' % (path,)) |
|---|
| 162 | |
|---|
| [1983] | 163 | # security check |
|---|
| 164 | path = os.path.abspath(path) |
|---|
| [6328] | 165 | if not path.startswith(os.path.normpath(self.base_path)): |
|---|
| [1983] | 166 | raise TracError("Can't access paths outside of " + self.base_path) |
|---|
| [1209] | 167 | |
|---|
| [1983] | 168 | # view |
|---|
| [1226] | 169 | mimetype = mimetypes.guess_type(path)[0] |
|---|
| 170 | if mimetype == 'text/html': |
|---|
| 171 | add_stylesheet(req, 'doxygen/css/doxygen.css') |
|---|
| [3132] | 172 | # Genshi can't include an unparsed file |
|---|
| 173 | # data = {'doxygen_path': path} |
|---|
| 174 | try: |
|---|
| [3737] | 175 | charset = (self.encoding or |
|---|
| 176 | self.env.config['trac'].get('default_charset')) |
|---|
| 177 | content = Markup(to_unicode(file(path).read(), charset)) |
|---|
| [3132] | 178 | data = {'doxygen_content': content} |
|---|
| 179 | return 'doxygen.html', data, 'text/html' |
|---|
| [6328] | 180 | except (IOError, OSError), e: |
|---|
| [3132] | 181 | raise TracError("Can't read doxygen content: %s" % e) |
|---|
| [1226] | 182 | else: |
|---|
| 183 | req.send_file(path, mimetype) |
|---|
| [1209] | 184 | |
|---|
| 185 | # ITemplateProvider methods |
|---|
| 186 | |
|---|
| 187 | def get_htdocs_dirs(self): |
|---|
| 188 | from pkg_resources import resource_filename |
|---|
| 189 | return [('doxygen', resource_filename(__name__, 'htdocs'))] |
|---|
| 190 | |
|---|
| 191 | def get_templates_dirs(self): |
|---|
| 192 | from pkg_resources import resource_filename |
|---|
| 193 | return [resource_filename(__name__, 'templates')] |
|---|
| 194 | |
|---|
| 195 | # ISearchProvider methods |
|---|
| 196 | |
|---|
| 197 | def get_search_filters(self, req): |
|---|
| 198 | if req.perm.has_permission('DOXYGEN_VIEW'): |
|---|
| 199 | yield('doxygen', self.title) |
|---|
| 200 | |
|---|
| 201 | def get_search_results(self, req, keywords, filters): |
|---|
| [3735] | 202 | self.log.debug("DOXYBUG: kw=%s f=%s" % (keywords, filters)) |
|---|
| [1209] | 203 | if not 'doxygen' in filters: |
|---|
| 204 | return |
|---|
| 205 | |
|---|
| 206 | # We have to search for the raw bytes... |
|---|
| 207 | keywords = [k.encode(self.encoding) for k in keywords] |
|---|
| 208 | |
|---|
| 209 | for doc in os.listdir(self.base_path): |
|---|
| 210 | # Search in documentation directories |
|---|
| 211 | path = os.path.join(self.base_path, doc) |
|---|
| [3550] | 212 | path = os.path.join(path, self.html_output) |
|---|
| [3735] | 213 | self.log.debug("looking in doc (%s) dir: %s:" % (doc, path)) |
|---|
| [1209] | 214 | if os.path.isdir(path): |
|---|
| 215 | index = os.path.join(path, 'search.idx') |
|---|
| 216 | if os.path.exists(index): |
|---|
| 217 | creation = os.path.getctime(index) |
|---|
| 218 | for result in self._search_in_documentation(doc, keywords): |
|---|
| 219 | result['url'] = req.href.doxygen(doc) + '/' \ |
|---|
| 220 | + result['url'] |
|---|
| [5596] | 221 | yield result['url'], result['name'], to_datetime(creation), \ |
|---|
| [1209] | 222 | 'doxygen', None |
|---|
| 223 | |
|---|
| 224 | # Search in common documentation directory |
|---|
| [3550] | 225 | index = os.path.join(self.base_path, self.html_output) |
|---|
| 226 | index = os.path.join(index, 'search.idx') |
|---|
| [3735] | 227 | self.log.debug("looking in doc (%s) search.idx: %s:" % (doc, index)) |
|---|
| [1209] | 228 | if os.path.exists(index): |
|---|
| 229 | creation = os.path.getctime(index) |
|---|
| 230 | for result in self._search_in_documentation('', keywords): |
|---|
| 231 | result['url'] = req.href.doxygen() + '/' + \ |
|---|
| 232 | result['url'] |
|---|
| [5596] | 233 | yield result['url'], result['name'], to_datetime(creation), 'doxygen', \ |
|---|
| [1209] | 234 | None |
|---|
| 235 | |
|---|
| 236 | # IWikiSyntaxProvider |
|---|
| [2651] | 237 | |
|---|
| [1209] | 238 | def get_link_resolvers(self): |
|---|
| [1226] | 239 | def doxygen_link(formatter, ns, params, label): |
|---|
| [1249] | 240 | if '/' not in params: |
|---|
| 241 | params = self.default_doc+'/'+params |
|---|
| 242 | segments = params.split('/') |
|---|
| 243 | if self.html_output: |
|---|
| 244 | segments[-1:-1] = [self.html_output] |
|---|
| 245 | action, path, link = self._doxygen_lookup(segments) |
|---|
| [1244] | 246 | if action == 'index': |
|---|
| [2069] | 247 | return tag.a(label, title=self.title, |
|---|
| 248 | href=formatter.href.doxygen()) |
|---|
| [1249] | 249 | if action == 'redirect' and path: |
|---|
| [2069] | 250 | return tag.a(label, title="Search result for "+params, |
|---|
| 251 | href=formatter.href.doxygen(link,path=path)) |
|---|
| [1249] | 252 | if action == 'search': |
|---|
| [2069] | 253 | return tag.a(label, title=params, class_='missing', |
|---|
| 254 | href=formatter.href.doxygen()) |
|---|
| [1249] | 255 | else: |
|---|
| [2069] | 256 | return tag.a(label, title=params, |
|---|
| 257 | href=formatter.href.doxygen(link, path=path)) |
|---|
| [1226] | 258 | yield ('doxygen', doxygen_link) |
|---|
| [1209] | 259 | |
|---|
| 260 | def get_wiki_syntax(self): |
|---|
| 261 | return [] |
|---|
| 262 | |
|---|
| 263 | # internal methods |
|---|
| [1226] | 264 | |
|---|
| 265 | def _doxygen_lookup(self, segments): |
|---|
| 266 | """Try to interpret path components as a request for doxygen targets |
|---|
| 267 | |
|---|
| [1249] | 268 | Return an `(action,path,link)` tuple, where: |
|---|
| [1226] | 269 | - `action` describes what should be done (one of 'view', |
|---|
| [1249] | 270 | 'redirect', or 'search'), |
|---|
| [1226] | 271 | - `path` is the location on disk of the resource. |
|---|
| 272 | - `link` is the link to the resource, relative to the |
|---|
| [1244] | 273 | req.href.doxygen base or a target in case of 'redirect' |
|---|
| [1226] | 274 | """ |
|---|
| 275 | doc, file = segments[:-1], segments and segments[-1] |
|---|
| [1244] | 276 | |
|---|
| 277 | if not doc and not file: |
|---|
| 278 | return ('index', None, None) |
|---|
| 279 | if doc: |
|---|
| 280 | doc = os.path.join(*doc) |
|---|
| 281 | else: |
|---|
| 282 | if self.default_doc: # we can't stay at the 'doxygen/' level |
|---|
| [1249] | 283 | return 'redirect', None, '/'.join([self.default_doc, |
|---|
| 284 | self.html_output, |
|---|
| 285 | file or self.index]) |
|---|
| [1244] | 286 | else: |
|---|
| [2651] | 287 | doc = self.html_output |
|---|
| 288 | |
|---|
| [1226] | 289 | def lookup(file, category='undefined'): |
|---|
| [1244] | 290 | """Build (full path, relative link) and check if path exists.""" |
|---|
| [1226] | 291 | path = os.path.join(self.base_path, doc, file) |
|---|
| [1249] | 292 | existing_path = os.path.exists(path) and path |
|---|
| 293 | link = doc+'/'+file |
|---|
| 294 | self.log.debug(' %s file %s' % (category, existing_path or |
|---|
| 295 | path+" (not found)")) |
|---|
| 296 | return existing_path, link |
|---|
| [1226] | 297 | |
|---|
| [1249] | 298 | self.log.debug('Looking up "%s" in documentation "%s"' % (file, doc)) |
|---|
| [1226] | 299 | |
|---|
| 300 | # Direct request for searching |
|---|
| 301 | if file == 'search.php': |
|---|
| [1244] | 302 | return 'search', None, None # keep existing 'query' arg |
|---|
| [1226] | 303 | |
|---|
| 304 | # Request for a documentation file. |
|---|
| 305 | doc_ext_re = '|'.join(self.ext.split(' ')) |
|---|
| 306 | if re.match(r'''^(.*)[.](%s)''' % doc_ext_re, file): |
|---|
| 307 | path, link = lookup(file, 'documentation') |
|---|
| 308 | if path: |
|---|
| 309 | return 'view', path, link |
|---|
| 310 | else: |
|---|
| [1244] | 311 | return 'search', None, file |
|---|
| [1226] | 312 | |
|---|
| 313 | # Request for source file documentation. |
|---|
| 314 | source_ext_re = '|'.join(self.source_ext.split(' ')) |
|---|
| 315 | match = re.match(r'''^(.*)[.](%s)''' % source_ext_re, file) |
|---|
| 316 | if match: |
|---|
| 317 | basename, suffix = match.groups() |
|---|
| 318 | basename = basename.replace('_', '__') |
|---|
| 319 | path, link = lookup('%s_8%s.html' % (basename, suffix), 'source') |
|---|
| 320 | if path: |
|---|
| 321 | return 'view', path, link |
|---|
| 322 | else: |
|---|
| [1244] | 323 | return 'search', None, file |
|---|
| [1226] | 324 | |
|---|
| 325 | # Request for summary pages |
|---|
| 326 | if file in self.SUMMARY_PAGES: |
|---|
| 327 | path, link = lookup(file + '.html', 'summary') |
|---|
| 328 | if path: |
|---|
| 329 | return 'view', path, link |
|---|
| 330 | |
|---|
| 331 | # Request for a named object |
|---|
| 332 | # TODO: |
|---|
| 333 | # - do something about dirs |
|---|
| 334 | # - expand with enum, defs, etc. |
|---|
| 335 | # - this doesn't work well with the CREATE_SUBDIRS Doxygen option |
|---|
| [3735] | 336 | |
|---|
| 337 | # do doxygen-style name->file mapping |
|---|
| 338 | # this is a little different than doxygen, but I don't see another way |
|---|
| 339 | # way to make doxygen:Type<bool> links work, as it inserts a ' ' (or |
|---|
| 340 | # '_01') after/before the type name. |
|---|
| 341 | charmap = { '_':'__', ':':'_1', '/':'_2', '<':'_3_01', '>':'_01_4', \ |
|---|
| 342 | '*':'_5', '&':'_6', '|':'_7', '.':'_8', '!':'_9', \ |
|---|
| 343 | ',':'_00',' ':'_01' } |
|---|
| 344 | mangledfile = '' |
|---|
| 345 | for i in file: |
|---|
| 346 | if i in charmap.keys(): |
|---|
| 347 | mangledfile += charmap[i] |
|---|
| 348 | else: |
|---|
| 349 | mangledfile += i |
|---|
| 350 | |
|---|
| 351 | path, link = lookup('class%s.html' % mangledfile, 'class') |
|---|
| [1226] | 352 | if not path: |
|---|
| [3735] | 353 | path, link = lookup('struct%s.html' % mangledfile, 'struct') |
|---|
| [1226] | 354 | if path: |
|---|
| 355 | return 'view', path, link |
|---|
| 356 | |
|---|
| [3735] | 357 | # Try in the default_namespace |
|---|
| 358 | if self.default_namespace != "": |
|---|
| 359 | mangledfile = self.default_namespace + '_1_1' + mangledfile |
|---|
| 360 | path, link = lookup('class%s.html' % mangledfile, 'class') |
|---|
| 361 | if not path: |
|---|
| 362 | path, link = lookup('struct%s.html' % mangledfile, 'struct') |
|---|
| 363 | if path: |
|---|
| 364 | return 'view', path, link |
|---|
| 365 | |
|---|
| 366 | |
|---|
| [1226] | 367 | # Revert to search... |
|---|
| 368 | results = self._search_in_documentation(doc, [file]) |
|---|
| 369 | class_ref = file+' Class Reference' |
|---|
| 370 | for result in results: |
|---|
| 371 | self.log.debug('Reverted to search, found: ' + repr(result)) |
|---|
| 372 | name = result['name'] |
|---|
| 373 | if name == file or name == class_ref: |
|---|
| [1244] | 374 | url = result['url'] |
|---|
| 375 | target = '' |
|---|
| 376 | if '#' in url: |
|---|
| 377 | url, target = url.split('#', 2) |
|---|
| 378 | path, link = lookup(url) |
|---|
| 379 | if path: |
|---|
| [1249] | 380 | return 'redirect', path, link # target # FIXME |
|---|
| [1226] | 381 | self.log.debug('%s not found in %s' % (file, doc)) |
|---|
| [1244] | 382 | return 'search', None, file |
|---|
| [1226] | 383 | |
|---|
| [1209] | 384 | def _search_in_documentation(self, doc, keywords): |
|---|
| 385 | # Open index file for documentation |
|---|
| [3550] | 386 | index = os.path.join(self.base_path, doc, self.html_output, 'search.idx') |
|---|
| [1209] | 387 | if os.path.exists(index): |
|---|
| 388 | fd = open(index) |
|---|
| 389 | |
|---|
| 390 | # Search for keywords in index |
|---|
| 391 | results = [] |
|---|
| 392 | for keyword in keywords: |
|---|
| 393 | results += self._search(fd, keyword) |
|---|
| 394 | results.sort(compare_rank) |
|---|
| 395 | for result in results: |
|---|
| 396 | yield result |
|---|
| 397 | |
|---|
| 398 | def _search(self, fd, word): |
|---|
| 399 | results = [] |
|---|
| 400 | index = self._computeIndex(word) |
|---|
| 401 | if index != -1: |
|---|
| 402 | fd.seek(index * 4 + 4, 0) |
|---|
| 403 | index = self._readInt(fd) |
|---|
| 404 | |
|---|
| 405 | if index: |
|---|
| 406 | fd.seek(index) |
|---|
| 407 | w = self._readString(fd) |
|---|
| 408 | matches = [] |
|---|
| 409 | while w != "": |
|---|
| 410 | statIdx = self._readInt(fd) |
|---|
| 411 | low = word.lower() |
|---|
| 412 | if w.find(low) != -1: |
|---|
| 413 | matches.append({'word': word, 'match': w, |
|---|
| 414 | 'index': statIdx, 'full': len(low) == len(w)}) |
|---|
| 415 | w = self._readString(fd) |
|---|
| 416 | |
|---|
| 417 | count = 0 |
|---|
| 418 | totalHi = 0 |
|---|
| 419 | totalFreqHi = 0 |
|---|
| 420 | totalFreqLo = 0 |
|---|
| 421 | |
|---|
| 422 | for match in matches: |
|---|
| 423 | multiplier = 1 |
|---|
| 424 | if match['full']: |
|---|
| 425 | multiplier = 2 |
|---|
| 426 | |
|---|
| 427 | fd.seek(match['index']) |
|---|
| 428 | numDocs = self._readInt(fd) |
|---|
| 429 | |
|---|
| 430 | for i in range(numDocs): |
|---|
| 431 | idx = self._readInt(fd) |
|---|
| [3550] | 432 | if idx == -1: |
|---|
| 433 | freq = 0 |
|---|
| 434 | else: |
|---|
| 435 | freq = self._readInt(fd) |
|---|
| [1209] | 436 | results.append({'idx': idx, 'freq': freq >> 1, |
|---|
| 437 | 'hi': freq & 1, 'multi': multiplier}) |
|---|
| 438 | if freq & 1: |
|---|
| 439 | totalHi += 1 |
|---|
| 440 | totalFreqHi += freq * multiplier |
|---|
| 441 | else: |
|---|
| 442 | totalFreqLo += freq * multiplier |
|---|
| 443 | |
|---|
| 444 | for i in range(numDocs): |
|---|
| [3550] | 445 | if results[count]['idx'] == -1: |
|---|
| 446 | results[count]['name'] = '' |
|---|
| 447 | results[count]['url'] = '' |
|---|
| 448 | count += 1 |
|---|
| 449 | continue |
|---|
| [1209] | 450 | fd.seek(results[count]['idx']) |
|---|
| 451 | name = self._readString(fd) |
|---|
| 452 | url = self._readString(fd) |
|---|
| 453 | results[count]['name'] = name |
|---|
| [3550] | 454 | results[count]['url'] = self.html_output + '/' + url |
|---|
| [1209] | 455 | count += 1 |
|---|
| 456 | |
|---|
| 457 | totalFreq = (totalHi + 1) * totalFreqLo + totalFreqHi |
|---|
| 458 | for i in range(count): |
|---|
| 459 | freq = results[i]['freq'] |
|---|
| 460 | multi = results[i]['multi'] |
|---|
| 461 | if results[i]['hi']: |
|---|
| 462 | results[i]['rank'] = float(freq*multi + totalFreqLo) \ |
|---|
| 463 | / float(totalFreq) |
|---|
| 464 | else: |
|---|
| 465 | results[i]['rank'] = float(freq*multi) \ |
|---|
| 466 | / float(totalFreq) |
|---|
| 467 | return results |
|---|
| 468 | |
|---|
| 469 | def _computeIndex(self, word): |
|---|
| 470 | if len(word) < 2: |
|---|
| 471 | return -1 |
|---|
| 472 | |
|---|
| 473 | hi = ord(word[0].lower()) |
|---|
| 474 | if hi == 0: |
|---|
| 475 | return -1 |
|---|
| 476 | |
|---|
| 477 | lo = ord(word[1].lower()) |
|---|
| 478 | if lo == 0: |
|---|
| 479 | return -1 |
|---|
| 480 | |
|---|
| 481 | return hi * 256 + lo |
|---|
| 482 | |
|---|
| 483 | def _readInt(self, fd): |
|---|
| 484 | b1 = fd.read(1) |
|---|
| 485 | b2 = fd.read(1) |
|---|
| 486 | b3 = fd.read(1) |
|---|
| 487 | b4 = fd.read(1) |
|---|
| [3550] | 488 | |
|---|
| 489 | if not b1 or not b2 or not b3 or not b4: |
|---|
| 490 | return -1; |
|---|
| [1209] | 491 | |
|---|
| 492 | return (ord(b1) << 24) | (ord(b2) << 16) | (ord(b3) << 8) | ord(b4) |
|---|
| 493 | |
|---|
| 494 | def _readString(self, fd): |
|---|
| 495 | result = '' |
|---|
| 496 | byte = fd.read(1) |
|---|
| 497 | while byte != '\0': |
|---|
| 498 | result = ''.join([result, byte]) |
|---|
| 499 | byte = fd.read(1) |
|---|
| 500 | return result |
|---|