00001
00002 """
00003 MoinMoin - FullSearch Macro
00004
00005 <<FullSearch>>
00006 displays a search dialog, as it always did.
00007
00008 <<FullSearch()>>
00009 does the same as clicking on the page title, only that
00010 the result is embedded into the page. note the '()' after
00011 the macro name, which is an empty argument list.
00012
00013 <<FullSearch(Help)>>
00014 embeds a search result into a page, as if you entered
00015 'Help' into the search box.
00016
00017 The macro creates a page list without context or match info, just
00018 like PageList macro. It does not make sense to have context in non
00019 interactive search, and this kind of search is used usually for
00020 Category pages, where we don't care about the context.
00021
00022 TODO: If we need to have context for some cases, either we add a context argument,
00023 or make another macro that uses context, which may be easier to use.
00024
00025 @copyright: 2000-2004 Juergen Hermann <jh@web.de>,
00026 2006 MoinMoin:FranzPletz
00027 @license: GNU GPL, see COPYING for details.
00028 """
00029
00030 import re
00031 from MoinMoin import wikiutil, search
00032
00033 from MoinMoin.parser import text_moin_wiki as wiki
00034 import string, StringIO
00035
00036 Dependencies = ["pages"]
00037
00038
00039 def search_box(type, macro):
00040 """ Make a search box
00041
00042 Make both Title Search and Full Search boxes, according to type.
00043
00044 @param type: search box type: 'titlesearch' or 'fullsearch'
00045 @rtype: unicode
00046 @return: search box html fragment
00047 """
00048 _ = macro._
00049 if 'value' in macro.form:
00050 default = wikiutil.escape(macro.form["value"][0], quote=1)
00051 else:
00052 default = ''
00053
00054
00055 boxes = ''
00056 button = _("Search Titles")
00057
00058
00059 if type == "fullsearch":
00060 boxes = [
00061 u'<br>',
00062 u'<input type="checkbox" name="context" value="160" checked="checked">',
00063 _('Display context of search results'),
00064 u'<br>',
00065 u'<input type="checkbox" name="case" value="1">',
00066 _('Case-sensitive searching'),
00067 ]
00068 boxes = u'\n'.join(boxes)
00069 button = _("Search Text")
00070
00071
00072 type = (type == "titlesearch")
00073 html = [
00074 u'<form method="get" action="%s/%s">' % (macro.request.getScriptname(), wikiutil.quoteWikinameURL(macro.request.formatter.page.page_name)),
00075 u'<div>',
00076 u'<input type="hidden" name="action" value="fullsearch">',
00077 u'<input type="hidden" name="titlesearch" value="%i">' % type,
00078 u'<input type="text" name="value" size="30" value="%s">' % default,
00079 u'<input type="submit" value="%s">' % button,
00080 boxes,
00081 u'</div>',
00082 u'</form>',
00083 ]
00084 html = u'\n'.join(html)
00085 return macro.formatter.rawHTML(html)
00086
00087 def pageListWithContext(self, macro, request, formatter, info=1, context=180,
00088 maxlines=1, paging=True, hitsFrom=0, hitsInfo=0):
00089 """ Format a list of found pages with context
00090
00091 The default parameter values will create Google-like search
00092 results, as this is the most known search interface. Good
00093 interface is familiar interface, so unless we have much better
00094 solution (we don't), being like Google is the way.
00095
00096 @param request: current request
00097 @param formatter: formatter to use
00098 @param info: show match info near the page link
00099 @param context: how many characters to show around each match.
00100 @param maxlines: how many contexts lines to show.
00101 @param paging: toggle paging
00102 @param hitsFrom: current position in the hits
00103 @param hitsInfo: toggle hits info line
00104 @rtype: unicode
00105 @return formatted page list with context
00106 """
00107 self._reset(request, formatter)
00108 f = formatter
00109 write = self.buffer.write
00110 _ = request.getText
00111
00112 if paging and len(self.hits) <= request.cfg.search_results_per_page:
00113 paging = False
00114
00115 if len(self.hits) == 0:
00116 write(f.definition_list(1) + f.definition_term(1) + "No results found." + f.definition_term(0) + f.definition_list(0))
00117
00118 else:
00119 write(f.number_list(1))
00120
00121 if paging:
00122 hitsTo = hitsFrom + request.cfg.search_results_per_page
00123 displayHits = self.hits[hitsFrom:hitsTo]
00124 else:
00125 displayHits = self.hits
00126
00127 display_results = []
00128
00129 for page in displayHits:
00130
00131 matchInfo = ''
00132 next_page = None
00133 if info:
00134 matchInfo = self.formatInfo(f, page)
00135 if page.attachment:
00136 fmt_context = ""
00137 querydict = {
00138 'action': 'AttachFile',
00139 'do': 'view',
00140 'target': page.attachment,
00141 }
00142 elif page.page_name.startswith('FS/'):
00143 fmt_context = ""
00144 querydict = None
00145 else:
00146 title, fmt_context, next_pages = formatContext(self, macro, page, context, maxlines)
00147 if page.rev and page.rev != page.page.getRevList()[0]:
00148 querydict = {
00149 'rev': page.rev,
00150 }
00151 else:
00152 querydict = None
00153 querystr = self.querystring(querydict)
00154 item = [
00155 f.listitem(1),
00156
00157 f.pagelink(1, page.page_name),
00158 title,
00159 f.pagelink(0),
00160 "<p>",
00161 fmt_context,
00162 "</p>",
00163 f.listitem(0),
00164 ]
00165 display_results.append((page.page_name, next_pages, ''.join(item)))
00166
00167 sorted_display_results = sortResults(display_results)
00168
00169 for node in sorted_display_results:
00170 write(node.body)
00171 write(f.number_list(0))
00172 if paging:
00173 write(self.formatPageLinks(hitsFrom=hitsFrom,
00174 hitsPerPage=request.cfg.search_results_per_page,
00175 hitsNum=len(self.hits)))
00176
00177 return self.getvalue()
00178
00179
00180
00181 class Node:
00182 def __init__(self, pagename, body, dependencies):
00183 self.pagename = pagename
00184 self.body = body
00185 self.dependencies = dependencies
00186
00187 def __repr__(self):
00188 return "<Node %s %s>" % (self.pagename, self.dependencies)
00189
00190 def topoSort(dependencies):
00191 dead = {}
00192 list = []
00193
00194 for node in dependencies.values(): dead[node] = False
00195
00196 nonterminals = []
00197 terminals = []
00198 for node in dependencies.values():
00199 if node.dependencies:
00200 nonterminals.append(node)
00201 else:
00202 terminals.append(node)
00203
00204 for node in nonterminals:
00205 visit(dependencies, terminals, node, list, dead);
00206
00207 list.reverse()
00208
00209 list = list + terminals
00210 return list
00211
00212 def visit(dependencies, terminals, dependency, list, dead):
00213 if dependency is None: return
00214 if dead.get(dependency, False): return
00215
00216 dead[dependency] = True
00217
00218 if dependency.dependencies:
00219 for node in dependency.dependencies:
00220 visit(dependencies, terminals, dependencies.get(node, None), list, dead)
00221 try:
00222 terminals.remove(dependency)
00223 except ValueError: pass
00224
00225 list.append(dependency)
00226
00227 def sortResults(display_results):
00228 dependencies = {}
00229
00230 for pagename, nextpages, body in display_results:
00231 node = Node(pagename, body, nextpages)
00232 dependencies[pagename] = node
00233
00234 results = topoSort(dependencies)
00235
00236 return results
00237
00238
00239
00240 def formatContext(self, macro, page, context, maxlines):
00241 """ Format search context for each matched page
00242
00243 Try to show first maxlines interesting matches context.
00244 """
00245 f = self.formatter
00246 if not page.page:
00247 page.page = Page(self.request, page.page_name)
00248 body = page.page.get_raw_body()
00249 last = len(body) - 1
00250 lineCount = 0
00251 output = ""
00252 next_page = None
00253
00254 pagedict = {}
00255 for line in body.split("\n"):
00256 if line.startswith("##"):
00257 line = line[2:].strip()
00258 parts = line.split("=", 1)
00259 if len(parts) == 2:
00260 pagedict[parts[0].strip()] = parts[1].strip()
00261
00262 title = pagedict.get("title", "No Title")
00263 description = pagedict.get("description", "No Description")
00264
00265 next_pages = []
00266 linkpat = re.compile("\[\[([^|]*)(\|([^]]*))?\]\]")
00267 for key,val in pagedict.items():
00268 if key.startswith("next.") and key.find(".link") != -1:
00269 m = linkpat.search(val)
00270 if m:
00271 next_pages.append(m.group(1))
00272
00273 if description:
00274 out=StringIO.StringIO()
00275 macro.request.redirect(out)
00276 wikiizer = wiki.Parser(description, macro.request)
00277 wikiizer.format(macro.formatter)
00278 description=out.getvalue()
00279 macro.request.redirect()
00280 del out
00281
00282 return title, description, next_pages
00283
00284 def execute(macro, needle):
00285 request = macro.request
00286 _ = request.getText
00287
00288
00289 if needle is None:
00290 return search_box("fullsearch", macro)
00291
00292
00293 elif needle == '':
00294 needle = '"%s"' % macro.formatter.page.page_name
00295
00296
00297
00298 elif needle.isspace():
00299 err = _('Please use a more selective search term instead of '
00300 '{{{"%s"}}}', wiki=True) % needle
00301 return '<span class="error">%s</span>' % err
00302
00303 needle = needle.strip()
00304
00305
00306 results = search.searchPages(request, needle, sort='page_name')
00307 return pageListWithContext(results, macro, request, macro.formatter, paging=False)
00308
00309
00310 ret = []
00311 for result in results:
00312 pass
00313
00314 return string.join(ret)
00315
00316