1 # -*- coding: iso-8859-1 -*- 2 """ 3 MoinMoin - ImprovedMoinSearch library 4 5 @copyright: 2010 Paul Boddie <paul@boddie.org.uk> 6 @license: GNU GPL (v2 or later), see COPYING.txt for details. 7 """ 8 9 from MoinMoin.search import searchPages 10 from MoinMoin.Page import Page 11 from MoinMoin import wikiutil 12 import re 13 14 heading_regexp = re.compile(r"^(?P<level>=+)(?P<heading>.*?)(?P=level)$", re.UNICODE | re.MULTILINE) 15 paragraph_regexp = re.compile(r"(?P<paragraph>(?:^[^#=\s].*$\n)+)", re.UNICODE | re.MULTILINE) 16 17 def range_groups(min_name, max_name): 18 return r"(?P<%s>-?\d+)?(?:\s*-\s*(?P<%s>-?\d+))?" % (min_name, max_name) 19 20 format_options_regexp = re.compile(r"(" 21 "(?P<heading>(heading|title|h)\s*" + range_groups("min_heading", "max_heading") + ")" 22 "|(?P<paragraph>(paragraph|para|p)\s*(?P<paragraph_number>\d+)?)" 23 "|(?P<name>(name|page)\s*" + range_groups("first", "last") + ")" 24 ")", re.UNICODE) 25 26 def convert_index(i, length): 27 28 """ 29 Convert from a 1-based indexing scheme to a 0-based scheme for the given 30 index 'i' in a sequence having the given 'length'. 31 """ 32 33 if i is None: 34 return i 35 elif i > 0: 36 return i - 1 37 elif i < 0: 38 return length + i 39 else: 40 return i 41 42 def getSearchResultPages(request, query, **kw): 43 44 """ 45 Return matching pages using the given 'request' and search 'query'. Optional 46 keyword arguments are passed to the underlying search infrastructure. 47 """ 48 49 results = searchPages(request, query, **kw) 50 return results.hits 51 52 def getFirstPageHeading(request, page, start=0, min_level=None, max_level=None): 53 54 """ 55 Using the given 'request', return the first heading in the given 'page' 56 from the given 'start' point (optional, defaulting to the start of the page) 57 having a heading level of at least 'min_level' (which is undefined if not 58 specified) and at most 'max_level' (which is undefined if not specified). 59 60 A tuple containing the heading and the span (the start offset and the end 61 offset as a tuple) is returned for a successful retrieval. Otherwise, None 62 is returned. 63 """ 64 65 full_page = Page(request, page.page_name) 66 body = full_page.get_raw_body() 67 if start != 0: 68 body = body[start:] 69 70 for match in heading_regexp.finditer(body): 71 level = len(match.group("level")) 72 73 if (min_level is None or level >= min_level) and \ 74 (max_level is None or level <= max_level): 75 76 return match.group("heading"), match.span() 77 78 return None 79 80 def getParagraph(request, page, start=0, number=None): 81 82 """ 83 Using the given 'request', return from the given 'page', starting from the 84 optional 'start' offset (or the beginning, if no such offset is specified), 85 the first paragraph or, if the optional 'number' is given, the paragraph 86 whose position corresponds to that number, with a number of 1 being the 87 first paragraph found, 2 being the second, and so on. 88 """ 89 90 full_page = Page(request, page.page_name) 91 body = full_page.get_raw_body() 92 if start != 0: 93 body = body[start:] 94 95 for i, match in enumerate(paragraph_regexp.finditer(body)): 96 if number is None or i == max(0, number - 1): 97 return match.group("paragraph"), match.span() 98 99 return None 100 101 def getPageName(request, page, start=0, first=None, last=None): 102 103 """ 104 Using the given 'request', return the name of the given 'page'. The optional 105 'start' offset refers to the body of the page and is returned as the start 106 and end of the result span if specified. 107 108 If the optional 'first' or 'last' parameters are specified, only the 109 specified span of parts extracted from the page name will be returned, where 110 the parts of the name are obtained by splitting the full name where the 111 slash ("/") character is found. The first part has an index of 1, and the 112 last part can be referred to using an index of -1. 113 """ 114 115 parts = page.page_name.split("/") 116 117 first = convert_index(first, len(parts)) 118 last = convert_index(last, len(parts)) 119 120 if first is None: 121 if last is None: 122 pass 123 else: 124 parts = parts[:last+1] 125 else: 126 if last is None: 127 parts = parts[first:] 128 else: 129 parts = parts[first:last+1] 130 131 return "/".join(parts), (start, start) 132 133 def formatResultPages(request, formatter, pages, paging, format, page_from=0): 134 135 """ 136 Using the given 'request' and 'formatter', return a formatted string showing 137 the result 'pages', providing paging controls when 'paging' is set to a true 138 value, and providing page details according to the given 'format'. 139 140 If the optional 'pages_from' parameter is set, the result pages from the 141 given result (specified within a range from 0 to the length of the 'pages' 142 collection) will be shown. 143 """ 144 145 actions = [] 146 147 if format: 148 for match in format_options_regexp.finditer(format): 149 if match.group("heading"): 150 actions.append((getFirstPageHeading, map(int_or_none, (match.group("min_heading"), match.group("max_heading"))))) 151 elif match.group("paragraph"): 152 actions.append((getParagraph, map(int_or_none, (match.group("paragraph_number"),)))) 153 elif match.group("name"): 154 actions.append((getPageName, map(int_or_none, (match.group("first"), match.group("last"))))) 155 else: 156 actions.append((getPageName, ())) 157 158 # Use paging only when there are enough results. 159 160 results_per_page = request.cfg.search_results_per_page 161 paging = paging and len(pages) > results_per_page 162 163 if paging: 164 pages_to_show = pages[page_from:page_from + results_per_page] 165 else: 166 pages_to_show = pages 167 168 # Prepare the output. 169 170 output = [] 171 output.append(formatter.number_list(on=1, start=page_from + 1)) 172 173 for page in pages_to_show: 174 output.append(formatter.listitem(on=1)) 175 176 start = 0 177 first = 1 178 for action, args in actions: 179 result = action(request, page, start, *args) 180 181 if result is not None: 182 if first: 183 output.append(formatter.pagelink(on=1, pagename=page.page_name)) 184 else: 185 output.append(" ") 186 187 text, span = result 188 output.append(formatter.text(text)) 189 190 # Position the search for the next action. 191 192 _start, _end = span 193 start = _end + 1 194 195 if first: 196 output.append(formatter.pagelink(on=0)) 197 198 first = 0 199 200 output.append(formatter.listitem(on=0)) 201 202 output.append(formatter.number_list(on=0)) 203 204 # Show paging navigation. 205 206 if paging: 207 output.append(formatPagingNavigation(request, formatter, pages, page_from)) 208 209 return "".join(output) 210 211 def formatPagingNavigation(request, formatter, pages, page_from=0): 212 213 """ 214 Using the given 'request' and 'formatter', return a formatted string showing 215 the paging navigation for the result 'pages', according to the 'page_from' 216 indicator which provides the current position in the result set. 217 """ 218 219 page = formatter.page 220 pagename = page.page_name 221 _ = request.getText 222 223 output = [] 224 225 results_per_page = request.cfg.search_results_per_page 226 number_of_results = len(pages) 227 228 pages_total = number_of_results / results_per_page 229 pages_before = page_from / results_per_page 230 pages_after = ((number_of_results - page_from) / results_per_page) - 1 231 232 querydict = wikiutil.parseQueryString(request.query_string) 233 234 output.append(formatter.paragraph(on=1)) 235 output.append(formatter.text(_("Result pages:"))) 236 output.append(formatter.text(" ")) 237 238 n = 0 239 while n < pages_before: 240 output.append(formatter.pagelink(on=1, pagename=pagename, querystr=getPagingQueryString(querydict, n * results_per_page))) 241 output.append(formatter.text(str(n + 1))) 242 output.append(formatter.pagelink(on=0)) 243 output.append(formatter.text(" ")) 244 n += 1 245 246 output.append(formatter.text(str(n + 1))) 247 output.append(formatter.text(" ")) 248 n += 1 249 250 while n <= pages_total: 251 output.append(formatter.pagelink(on=1, pagename=pagename, querystr=getPagingQueryString(querydict, n * results_per_page))) 252 output.append(formatter.text(str(n + 1))) 253 output.append(formatter.pagelink(on=0)) 254 output.append(formatter.text(" ")) 255 n += 1 256 257 output.append(formatter.paragraph(on=0)) 258 259 return "".join(output) 260 261 def getPagingQueryString(querydict, page_from): 262 querydict["from"] = page_from 263 return wikiutil.makeQueryString(querydict) 264 265 def int_or_none(x): 266 if x is None: 267 return x 268 else: 269 return int(x) 270 271 # vim: tabstop=4 expandtab shiftwidth=4