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