""" open/dulcinea/lib/ui/lib/search.qpy """ from dulcinea.ui.table import Table from qp.pub.common import get_path, header, footer, get_request from qp.fill.form import Form from qp.fill.html import href, url_with_query, url_unquote from qp.fill.widget import HiddenWidget, OptionSelectWidget from qp.fill.widget import StringWidget from qp.http.request import parse_query from qpy import xml import re def format_context(keywords, context, delta=40, length=80): sample = '' for keyword in keywords.split(): pos = context.lower().find(keyword.lower()) if pos >= 0: start = max(0, pos - delta) k = start while k < pos: if context[k] == '\n': start = k + 1 break k += 1 if k == pos: k = start while k < pos: if context[k] == ' ': start = k + 1 break k += 1 if k < pos: sample = context[start:start + length] break while sample and sample[-1] not in ' \n': sample = sample[:-1] return highlight_keywords(sample, keywords) def highlight_keywords(content, keywords): if keywords: pat = '|'.join([url_unquote(keyword) for keyword in keywords.split()]) try: regex = re.compile(pat, re.IGNORECASE) except re.error: return xml(content) def highlight_match:xml(matchobj): return '%s' % matchobj.group(0) content, count = regex.subn(highlight_match, content) return xml(content) def keywords_from_query:xml(): query = parse_query(get_request().get_query(), 'ascii') if 'keywords' in query: query['keywords'] class SearchForm (object): def __init__(self, search_space, matches_per_page=20, require_keywords=False, show_all_option=False, exports=None): if show_all_option: search_space = list(search_space) self.form, self.keywords, matches = self.get_form_keywords_matches( search_space, matches_per_page=matches_per_page, require_keywords=require_keywords, show_all_option=show_all_option) try: page_size = int(self.form.get('page_size')) if 0 < page_size <= len(search_space): matches_per_page = page_size except (TypeError, ValueError): pass self.matches_per_page = matches_per_page self.exports = exports self.pages = [] page = [] for item in matches: page.append(item) if len(page) == matches_per_page: self.pages.append(page) page = [] if page: self.pages.append(page) pages = len(self.pages) if pages == 0: self.page_number = None elif pages == 1: self.page_number = 0 else: self.form.add(HiddenWidget, 'page_number', value='0') try: self.page_number = int(self.form.get('page_number') or '0') except ValueError: self.page_number = 0 else: if self.page_number >= pages or self.page_number < 0: self.page_number = 0 def gen_matches(self, search_space, keywords): re_keywords = [re.compile(r'\b%s\b' % re.escape(keyword), re.I).search for keyword in keywords.split()] for item in search_space: text = self.get_search_text(item) for pattern_search in re_keywords: if not pattern_search(text): break else: yield item def get_form_keywords_matches(self, search_space, matches_per_page=20, require_keywords=False, show_all_option=False): if self.get_title(): title = ' ' + self.get_title() else: title = '' search_title = "Search" + title form = Form(method='get', use_tokens=False) form.add(StringWidget, 'keywords', size=40, onfocus="this.value=''", value="Search" + title) keywords = form.get('keywords') or '' if keywords == search_title: keywords = '' if require_keywords and not keywords: matches = [] elif search_space and not keywords or keywords == '*': matches = list(search_space) else: matches = list(self.gen_matches(search_space, keywords)) page_size_options = set([10, 20, 50, 100]) if matches_per_page not in page_size_options: page_size_options.add(matches_per_page) if show_all_option: page_size_options.add(len(search_space)) page_size_options = sorted(page_size_options) form.add(OptionSelectWidget, 'page_size', value=matches_per_page, hint="per page", options=page_size_options) return form, keywords, matches def format_context(self, context, delta=40, length=80): return format_context(self.keywords, context, delta=delta, length=length) def get_search_text(self, item): return " ".join(self.get_search_text_fields(item)) def get_search_text_fields(self, item): return item.get_search_text_fields() def get_title(self): raise RuntimeError('Abstract get_title() method called') def header(self, title): return header(title) def format_no_matches:xml(self): '\n' '
\n' if self.form.is_submitted(): '''

There are no results that match your search terms.

''' else: '''

Enter one or more search terms separated by spaces.

''' def format_search_results_headings:xml(self): raise RuntimeError('Abstract format_search_results_headings() method') def format_search_results_table_row:xml(self, item): raise RuntimeError('Abstract format_search_results_table_row() method') def footer(self, title): return footer(title) def format_matches:xml(self): '' '' self.format_search_results_headings() '' for item in self.pages[self.page_number]: '' self.format_search_results_table_row(item) '' '
' if len(self.pages) > 1: def page_index_link:xml(page_index, title): href(url_with_query(get_path(), keywords=self.keywords or '', page_number=page_index, page_size=self.matches_per_page), title) last_index = len(self.pages) start_index_range = self.page_number - (self.page_number % 10) end_index_range = min(start_index_range + 10, last_index) '
Results Page:' if start_index_range != 0: page_index_link(0, '|<') ' ' page_index_link(start_index_range - 1, '<') for page_index in range(start_index_range, end_index_range): if page_index == self.page_number: '' page_index + 1 '' else: page_index_link(page_index, '%d' % (page_index + 1)) ' ' ' ' if end_index_range < last_index: page_index_link(end_index_range, '>') ' ' page_index_link(last_index - 1 , '>|') '
' '' '
\n' def render:xml(self): if self.page_number is None: title = self.get_title() self.header(title) self.format_no_matches() else: title = '%s: Page %d of %d' % (self.get_title(), self.page_number + 1, len(self.pages)) self.header(title) self.format_matches() self.footer(title) def handle(self): return self.render() class TableSearchForm (SearchForm): def init_table_columns(self, table): table.column(bogus='Bogus') def add_table_row(self, table, item): table.row(bogus="not implemented") def render_table:xml(self, table): table.render() def add_blank_table_row(self, table): pass def render:xml(self): if self.page_number is None: title = self.get_title() self.header(title) self.format_no_matches() else: title = '%s: Page %d of %d' % (self.get_title(), self.page_number + 1, len(self.pages)) self.header(title) self.format_matches() self.footer(title) def format_crumbs:xml(self): if self.exports: '
\n' '\n' '
\n' '
\n' def format_matches:xml(self): '' self.format_crumbs() #'
\n' table = Table() self.init_table_columns(table) for i, item in enumerate(self.pages[self.page_number]): self.add_table_row(table, item) if self.matches_per_page > len(self.pages[self.page_number]) - 1: for j in range(i, self.matches_per_page-1): self.add_blank_table_row(table) if len(self.pages) > 1 and table.get_rows(): def page_index_link:xml(page_index, title): href(url_with_query(get_path(), keywords=self.keywords or '', page_number=page_index, page_size=self.matches_per_page), title) last_index = len(self.pages) start_index_range = self.page_number - (self.page_number % 10) end_index_range = min(start_index_range + 10, last_index) def page_index:xml(): '
' 'Results Page:  ' if start_index_range != 0: page_index_link(0, '|<') ' ' page_index_link(start_index_range - 1, '<') for page_index in range(start_index_range, end_index_range): if page_index == self.page_number: '' page_index + 1 '' else: page_index_link(page_index, '%d' % (page_index + 1)) ' ' ' ' if end_index_range < last_index: page_index_link(end_index_range, '>') ' ' page_index_link(last_index - 1 , '>|') '
' columns = len(table.get_rows()[0]) table.set_footer( '%s' % ( columns, page_index())) self.render_table(table) def format_search_css:str(color_map): # Requires: SELECTED_BG and SEARCH_RESULTS_NAV to be defined in color_map """ div.searchresultspage { margin: 0; padding: 0.2ex; background: %(SEARCH_RESULTS_NAV)s; font-size: small; } div.searchresultspage a { margin: 0; padding: 0.2ex; } span.selectedsearchresultspage { margin: 0; padding: 0.2ex; background: %(SELECTED_BG)s; } div.search div.OptionSelectWidget select, div.search div.CheckboxWidget input, div.search div.StringWidget input { float: left; margin-right: 1ex; } div.search div.hint { display: inline; float: left; } div.search div.content div.hint { margin-bottom: 0.5ex; font-style: normal; float: none; } """ % color_map