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 f.pagelink(1, page.page_name, querystr=querystr),
00157 title,
00158
00159 f.pagelink(0, page.page_name),
00160
00161 f.definition_term(0),
00162
00163 fmt_context,
00164
00165
00166 f.listitem(0),
00167 ]
00168 display_results.append((page.page_name, next_pages, ''.join(item)))
00169
00170 sorted_display_results = sortResults(display_results)
00171
00172 for node in sorted_display_results:
00173 write(node.body)
00174 write(f.number_list(0))
00175 if paging:
00176 write(self.formatPageLinks(hitsFrom=hitsFrom,
00177 hitsPerPage=request.cfg.search_results_per_page,
00178 hitsNum=len(self.hits)))
00179
00180 return self.getvalue()
00181
00182
00183
00184 class Node:
00185 def __init__(self, pagename, body, dependencies):
00186 self.pagename = pagename
00187 self.body = body
00188 self.dependencies = dependencies
00189
00190 def __repr__(self):
00191 return "<Node %s %s>" % (self.pagename, self.dependencies)
00192
00193 def topoSort(dependencies):
00194 dead = {}
00195 list = []
00196
00197 for node in dependencies.values(): dead[node] = False
00198
00199 nonterminals = []
00200 terminals = []
00201 for node in dependencies.values():
00202 if node.dependencies:
00203 nonterminals.append(node)
00204 else:
00205 terminals.append(node)
00206
00207 for node in nonterminals:
00208 visit(dependencies, terminals, node, list, dead);
00209
00210 list.reverse()
00211
00212 list = list + terminals
00213 return list
00214
00215 def visit(dependencies, terminals, dependency, list, dead):
00216 if dependency is None: return
00217 if dead.get(dependency, False): return
00218
00219 dead[dependency] = True
00220
00221 if dependency.dependencies:
00222 for node in dependency.dependencies:
00223 visit(dependencies, terminals, dependencies.get(node, None), list, dead)
00224 try:
00225 terminals.remove(dependency)
00226 except ValueError: pass
00227
00228 list.append(dependency)
00229
00230 def sortResults(display_results):
00231 dependencies = {}
00232
00233 for pagename, nextpages, body in display_results:
00234 node = Node(pagename, body, nextpages)
00235 dependencies[pagename] = node
00236
00237 results = topoSort(dependencies)
00238
00239 return results
00240
00241
00242
00243 def formatContext(self, macro, page, context, maxlines):
00244 """ Format search context for each matched page
00245
00246 Try to show first maxlines interesting matches context.
00247 """
00248 f = self.formatter
00249 if not page.page:
00250 page.page = Page(self.request, page.page_name)
00251 body = page.page.get_raw_body()
00252 last = len(body) - 1
00253 lineCount = 0
00254 output = ""
00255 next_page = None
00256 title = ""
00257
00258 titlepat = re.compile("== (.*) ==", re.I)
00259 m = titlepat.search(body)
00260 if m: title = m.group(1)
00261
00262 pat = re.compile("Description:[']*(.*)", re.I)
00263 m = pat.search(body)
00264 if m: output = m.group(1)
00265
00266 nextpat = re.compile("Next Tutorial:[']*(.*)", re.I)
00267 m = nextpat.search(body)
00268
00269 next_pages = []
00270 if m:
00271 next_page = m.group(1)
00272 i = 0
00273 linkpat = re.compile("\[\[([^|]*)(\|([^]]*))?\]\]")
00274 while 1:
00275 m = linkpat.search(next_page, i)
00276 if not m: break
00277 if m:
00278 next_pages.append(m.group(1))
00279 i = m.end()+1
00280
00281 if output:
00282 out=StringIO.StringIO()
00283 macro.request.redirect(out)
00284 wikiizer = wiki.Parser(output, macro.request)
00285 wikiizer.format(macro.formatter)
00286 output=out.getvalue()
00287 macro.request.redirect()
00288 del out
00289
00290 return title, output, next_pages
00291
00292 def execute(macro, needle):
00293 request = macro.request
00294 _ = request.getText
00295
00296
00297 if needle is None:
00298 return search_box("fullsearch", macro)
00299
00300
00301 elif needle == '':
00302 needle = '"%s"' % macro.formatter.page.page_name
00303
00304
00305
00306 elif needle.isspace():
00307 err = _('Please use a more selective search term instead of '
00308 '{{{"%s"}}}', wiki=True) % needle
00309 return '<span class="error">%s</span>' % err
00310
00311 needle = needle.strip()
00312
00313
00314 results = search.searchPages(request, needle, sort='page_name')
00315 return pageListWithContext(results, macro, request, macro.formatter, paging=False)
00316
00317
00318 ret = []
00319 for result in results:
00320 pass
00321
00322 return string.join(ret)
00323
00324