MoinSupport

MoinSupport.py

27:3dae958645ce
2012-11-18 Paul Boddie Fixed the qualified parameter function to not add the prefix when it is blank. Split the category page search function, exposing the category page filtering function to a new, more general search function.
     1 # -*- coding: iso-8859-1 -*-     2 """     3     MoinMoin - MoinSupport library (derived from EventAggregatorSupport)     4      5     @copyright: 2008, 2009, 2010, 2011, 2012 by Paul Boddie <paul@boddie.org.uk>     6     @copyright: 2000-2004 Juergen Hermann <jh@web.de>,     7                 2005-2008 MoinMoin:ThomasWaldmann.     8     @license: GNU GPL (v2 or later), see COPYING.txt for details.     9 """    10     11 from DateSupport import *    12 from MoinMoin.Page import Page    13 from MoinMoin import config, search, wikiutil    14 from StringIO import StringIO    15 from shlex import shlex    16 import re    17 import time    18     19 __version__ = "0.2"    20     21 # Content type parsing.    22     23 encoding_regexp_str = ur'(?P<content_type>[^\s;]*)(?:;\s*charset=(?P<encoding>[-A-Za-z0-9]+))?'    24 encoding_regexp = re.compile(encoding_regexp_str)    25     26 # Accept header parsing.    27     28 accept_regexp_str = ur';\s*q='    29 accept_regexp = re.compile(accept_regexp_str)    30     31 # Extraction of shared fragments.    32     33 marker_regexp_str = r"([{]{3,}|[}]{3,})"    34 marker_regexp = re.compile(marker_regexp_str, re.MULTILINE | re.DOTALL) # {{{... or }}}...    35     36 # Category extraction from pages.    37     38 category_regexp = None    39     40 # Simple content parsing.    41     42 verbatim_regexp = re.compile(ur'(?:'    43     ur'<<Verbatim\((?P<verbatim>.*?)\)>>'    44     ur'|'    45     ur'\[\[Verbatim\((?P<verbatim2>.*?)\)\]\]'    46     ur'|'    47     ur'!(?P<verbatim3>.*?)(\s|$)?'    48     ur'|'    49     ur'`(?P<monospace>.*?)`'    50     ur'|'    51     ur'{{{(?P<preformatted>.*?)}}}'    52     ur')', re.UNICODE)    53     54 # Category discovery.    55     56 def getCategoryPattern(request):    57     global category_regexp    58     59     try:    60         return request.cfg.cache.page_category_regexact    61     except AttributeError:    62     63         # Use regular expression from MoinMoin 1.7.1 otherwise.    64     65         if category_regexp is None:    66             category_regexp = re.compile(u'^%s$' % ur'(?P<all>Category(?P<key>(?!Template)\S+))', re.UNICODE)    67         return category_regexp    68     69 def getCategories(request):    70     71     """    72     From the AdvancedSearch macro, return a list of category page names using    73     the given 'request'.    74     """    75     76     # This will return all pages with "Category" in the title.    77     78     cat_filter = getCategoryPattern(request).search    79     return request.rootpage.getPageList(filter=cat_filter)    80     81 def getCategoryMapping(category_pagenames, request):    82     83     """    84     For the given 'category_pagenames' return a list of tuples of the form    85     (category name, category page name) using the given 'request'.    86     """    87     88     cat_pattern = getCategoryPattern(request)    89     mapping = []    90     for pagename in category_pagenames:    91         name = cat_pattern.match(pagename).group("key")    92         if name != "Category":    93             mapping.append((name, pagename))    94     mapping.sort()    95     return mapping    96     97 def getCategoryPages(pagename, request):    98     99     """   100     Return the pages associated with the given category 'pagename' using the   101     'request'.   102     """   103    104     query = search.QueryParser().parse_query('category:%s' % pagename)   105     results = search.searchPages(request, query, "page_name")   106     return filterCategoryPages(results, request)   107    108 def filterCategoryPages(results, request):   109    110     "Filter category pages from the given 'results' using the 'request'."   111    112     cat_pattern = getCategoryPattern(request)   113     pages = []   114     for page in results.hits:   115         if not cat_pattern.match(page.page_name):   116             pages.append(page)   117     return pages   118    119 def getAllCategoryPages(category_names, request):   120    121     """   122     Return all pages belonging to the categories having the given   123     'category_names', using the given 'request'.   124     """   125    126     pages = []   127     pagenames = set()   128    129     for category_name in category_names:   130    131         # Get the pages and page names in the category.   132    133         pages_in_category = getCategoryPages(category_name, request)   134    135         # Visit each page in the category.   136    137         for page_in_category in pages_in_category:   138             pagename = page_in_category.page_name   139    140             # Only process each page once.   141    142             if pagename in pagenames:   143                 continue   144             else:   145                 pagenames.add(pagename)   146    147             pages.append(page_in_category)   148    149     return pages   150    151 def getPagesForSearch(search_pattern, category_names, request):   152    153     """   154     Return result pages for a search employing the given 'search_pattern' and   155     'category_names', using the given 'request'.   156     """   157    158     if category_names:   159         search_pattern += " (%s)" % " or ".join([   160             ("category:%s" % category_name) for category_name in category_names   161             ])   162    163     query = search.QueryParser().parse_query(search_pattern)   164     results = search.searchPages(request, query, "page_name")   165     return filterCategoryPages(results, request)   166    167 # WikiDict functions.   168    169 def getWikiDict(pagename, request):   170    171     """   172     Return the WikiDict provided by the given 'pagename' using the given   173     'request'.   174     """   175    176     if pagename and Page(request, pagename).exists() and request.user.may.read(pagename):   177         if hasattr(request.dicts, "dict"):   178             return request.dicts.dict(pagename)   179         else:   180             return request.dicts[pagename]   181     else:   182         return None   183    184 # Searching-related functions.   185    186 def getPagesFromResults(result_pages, request):   187    188     "Return genuine pages for the given 'result_pages' using the 'request'."   189    190     return [Page(request, page.page_name) for page in result_pages]   191    192 # Region/section parsing.   193    194 def getRegions(s, include_non_regions=False):   195    196     """   197     Parse the string 's', returning a list of explicitly declared regions.   198    199     If 'include_non_regions' is specified as a true value, fragments will be   200     included for text between explicitly declared regions.   201     """   202    203     regions = []   204     marker = None   205     is_block = True   206    207     # Start a region for exposed text, if appropriate.   208    209     if include_non_regions:   210         regions.append("")   211    212     for match_text in marker_regexp.split(s):   213    214         # Capture section text.   215    216         if is_block:   217             if marker or include_non_regions:   218                 regions[-1] += match_text   219    220         # Handle section markers.   221    222         elif not is_block:   223    224             # Close any open sections, returning to exposed text regions.   225    226             if marker:   227                 if match_text.startswith("}") and len(marker) == len(match_text):   228                     marker = None   229    230                     # Start a region for exposed text, if appropriate.   231    232                     if include_non_regions:   233                         regions.append("")   234    235             # Without a current marker, start a section if an appropriate marker   236             # is given.   237    238             elif match_text.startswith("{"):   239                 marker = match_text   240                 regions.append("")   241    242             # Markers and section text are added to the current region.   243    244             regions[-1] += match_text   245    246         # The match text alternates between text between markers and the markers   247         # themselves.   248    249         is_block = not is_block   250    251     return regions   252    253 def getFragmentsFromRegions(regions):   254    255     """   256     Return fragments from the given 'regions', each having the form   257     (format, arguments, body text).   258     """   259    260     fragments = []   261    262     for region in regions:   263         if region.startswith("{{{"):   264    265             body = region.lstrip("{").rstrip("}").lstrip()   266    267             # Remove any prelude and process metadata.   268    269             if body.startswith("#!"):   270                 body = body[2:]   271    272                 arguments, body = body.split("\n", 1)   273    274                 # Get any parser/format declaration.   275    276                 if arguments and not arguments[0].isspace():   277                     details = arguments.split(None, 1)   278                     if len(details) == 2:   279                         format, arguments = details   280                     else:   281                         format = details[0]   282                         arguments = ""   283                 else:   284                     format = None   285    286                 # Get the attributes/arguments for the region.   287    288                 attributes = parseAttributes(arguments, False)   289    290                 # Add an entry for the format in the attribute dictionary.   291    292                 if format and not attributes.has_key(format):   293                     attributes[format] = True   294    295                 fragments.append((format, attributes, body))   296    297             else:   298                 fragments.append((None, {}, body))   299    300         else:   301             fragments.append((None, {}, region))   302    303     return fragments   304    305 def getFragments(s, include_non_regions=False):   306    307     """   308     Return fragments for the given string 's', each having the form   309     (format, arguments, body text).   310    311     If 'include_non_regions' is specified as a true value, fragments will be   312     included for text between explicitly declared regions.   313     """   314    315     return getFragmentsFromRegions(getRegions(s, include_non_regions))   316    317 # Region/section attribute parsing.   318    319 def parseAttributes(s, escape=True):   320    321     """   322     Parse the section attributes string 's', returning a mapping of names to   323     values. If 'escape' is set to a true value, the attributes will be suitable   324     for use with the formatter API. If 'escape' is set to a false value, the   325     attributes will have any quoting removed.   326     """   327    328     attrs = {}   329     f = StringIO(s)   330     name = None   331     need_value = False   332    333     for token in shlex(f):   334    335         # Capture the name if needed.   336    337         if name is None:   338             name = escape and wikiutil.escape(token) or strip_token(token)   339    340         # Detect either an equals sign or another name.   341    342         elif not need_value:   343             if token == "=":   344                 need_value = True   345             else:   346                 attrs[name.lower()] = escape and "true" or True   347                 name = wikiutil.escape(token)   348    349         # Otherwise, capture a value.   350    351         else:   352             # Quoting of attributes done similarly to wikiutil.parseAttributes.   353    354             if token:   355                 if escape:   356                     if token[0] in ("'", '"'):   357                         token = wikiutil.escape(token)   358                     else:   359                         token = '"%s"' % wikiutil.escape(token, 1)   360                 else:   361                     token = strip_token(token)   362        363             attrs[name.lower()] = token   364             name = None   365             need_value = False   366    367     # Handle any name-only attributes at the end of the collection.   368    369     if name and not need_value:   370         attrs[name.lower()] = escape and "true" or True   371    372     return attrs   373    374 def strip_token(token):   375    376     "Return the given 'token' stripped of quoting."   377    378     if token[0] in ("'", '"') and token[-1] == token[0]:   379         return token[1:-1]   380     else:   381         return token   382    383 # Request-related classes and associated functions.   384    385 class Form:   386    387     """   388     A wrapper preserving MoinMoin 1.8.x (and earlier) behaviour in a 1.9.x   389     environment.   390     """   391    392     def __init__(self, form):   393         self.form = form   394    395     def has_key(self, name):   396         return not not self.form.getlist(name)   397    398     def get(self, name, default=None):   399         values = self.form.getlist(name)   400         if not values:   401             return default   402         else:   403             return values   404    405     def __getitem__(self, name):   406         return self.form.getlist(name)   407    408 class ActionSupport:   409    410     """   411     Work around disruptive MoinMoin changes in 1.9, and also provide useful   412     convenience methods.   413     """   414    415     def get_form(self):   416         return get_form(self.request)   417    418     def _get_selected(self, value, input_value):   419    420         """   421         Return the HTML attribute text indicating selection of an option (or   422         otherwise) if 'value' matches 'input_value'.   423         """   424    425         return input_value is not None and value == input_value and 'selected="selected"' or ''   426    427     def _get_selected_for_list(self, value, input_values):   428    429         """   430         Return the HTML attribute text indicating selection of an option (or   431         otherwise) if 'value' matches one of the 'input_values'.   432         """   433    434         return value in input_values and 'selected="selected"' or ''   435    436     def get_option_list(self, value, values):   437    438         """   439         Return a list of HTML element definitions for options describing the   440         given 'values', selecting the option with the specified 'value' if   441         present.   442         """   443    444         options = []   445         for available_value in values:   446             selected = self._get_selected(available_value, value)   447             options.append('<option value="%s" %s>%s</option>' % (   448                 escattr(available_value), selected, wikiutil.escape(available_value)))   449         return options   450    451     def _get_input(self, form, name, default=None):   452    453         """   454         Return the input from 'form' having the given 'name', returning either   455         the input converted to an integer or the given 'default' (optional, None   456         if not specified).   457         """   458    459         value = form.get(name, [None])[0]   460         if not value: # true if 0 obtained   461             return default   462         else:   463             return int(value)   464    465 def get_form(request):   466    467     "Work around disruptive MoinMoin changes in 1.9."   468    469     if hasattr(request, "values"):   470         return Form(request.values)   471     else:   472         return request.form   473    474 class send_headers_cls:   475    476     """   477     A wrapper to preserve MoinMoin 1.8.x (and earlier) request behaviour in a   478     1.9.x environment.   479     """   480    481     def __init__(self, request):   482         self.request = request   483    484     def __call__(self, headers):   485         for header in headers:   486             parts = header.split(":")   487             self.request.headers.add(parts[0], ":".join(parts[1:]))   488    489 def get_send_headers(request):   490    491     "Return a function that can send response headers."   492    493     if hasattr(request, "http_headers"):   494         return request.http_headers   495     elif hasattr(request, "emit_http_headers"):   496         return request.emit_http_headers   497     else:   498         return send_headers_cls(request)   499    500 def escattr(s):   501     return wikiutil.escape(s, 1)   502    503 def getPathInfo(request):   504     if hasattr(request, "getPathinfo"):   505         return request.getPathinfo()   506     else:   507         return request.path   508    509 def getHeader(request, header_name, prefix=None):   510    511     """   512     Using the 'request', return the value of the header with the given   513     'header_name', using the optional 'prefix' to obtain protocol-specific   514     headers if necessary.   515    516     If no value is found for the given 'header_name', None is returned.   517     """   518    519     if hasattr(request, "getHeader"):   520         return request.getHeader(header_name)   521     elif hasattr(request, "headers"):   522         return request.headers.get(header_name)   523     else:   524         return request.env.get((prefix and prefix + "_" or "") + header_name.upper())   525    526 def writeHeaders(request, mimetype, metadata, status=None):   527    528     """   529     Using the 'request', write resource headers using the given 'mimetype',   530     based on the given 'metadata'. If the optional 'status' is specified, set   531     the status header to the given value.   532     """   533    534     send_headers = get_send_headers(request)   535    536     # Define headers.   537    538     headers = ["Content-Type: %s; charset=%s" % (mimetype, config.charset)]   539    540     # Define the last modified time.   541     # NOTE: Consider using request.httpDate.   542    543     latest_timestamp = metadata.get("last-modified")   544     if latest_timestamp:   545         headers.append("Last-Modified: %s" % latest_timestamp.as_HTTP_datetime_string())   546    547     if status:   548         headers.append("Status: %s" % status)   549    550     send_headers(headers)   551    552 # Content/media type and preferences support.   553    554 class MediaRange:   555    556     "A content/media type value which supports whole categories of data."   557    558     def __init__(self, media_range, accept_parameters=None):   559         self.media_range = media_range   560         self.accept_parameters = accept_parameters or {}   561    562         parts = media_range.split(";")   563         self.media_type = parts[0]   564         self.parameters = getMappingFromParameterStrings(parts[1:])   565    566         # The media type is divided into category and subcategory.   567    568         parts = self.media_type.split("/")   569         self.category = parts[0]   570         self.subcategory = "/".join(parts[1:])   571    572     def get_parts(self):   573    574         "Return the category, subcategory parts."   575    576         return self.category, self.subcategory   577    578     def get_specificity(self):   579    580         """   581         Return the specificity of the media type in terms of the scope of the   582         category and subcategory, and also in terms of any qualifying   583         parameters.   584         """   585    586         if "*" in self.get_parts():   587             return -list(self.get_parts()).count("*")   588         else:   589             return len(self.parameters)   590    591     def permits(self, other):   592    593         """   594         Return whether this media type permits the use of the 'other' media type   595         if suggested as suitable content.   596         """   597    598         if not isinstance(other, MediaRange):   599             other = MediaRange(other)   600    601         category = categoryPermits(self.category, other.category)   602         subcategory = categoryPermits(self.subcategory, other.subcategory)   603    604         if category and subcategory:   605             if "*" not in (category, subcategory):   606                 return not self.parameters or self.parameters == other.parameters   607             else:   608                 return True   609         else:   610             return False   611    612     def __eq__(self, other):   613    614         """   615         Return whether this media type is effectively the same as the 'other'   616         media type.   617         """   618    619         if not isinstance(other, MediaRange):   620             other = MediaRange(other)   621    622         category = categoryMatches(self.category, other.category)   623         subcategory = categoryMatches(self.subcategory, other.subcategory)   624    625         if category and subcategory:   626             if "*" not in (category, subcategory):   627                 return self.parameters == other.parameters or \   628                     not self.parameters or not other.parameters   629             else:   630                 return True   631         else:   632             return False   633    634     def __ne__(self, other):   635         return not self.__eq__(other)   636    637     def __hash__(self):   638         return hash(self.media_range)   639    640     def __repr__(self):   641         return "MediaRange(%r)" % self.media_range   642    643 def categoryMatches(this, that):   644    645     """   646     Return the basis of a match between 'this' and 'that' or False if the given   647     categories do not match.   648     """   649    650     return (this == "*" or this == that) and this or \   651         that == "*" and that or False   652    653 def categoryPermits(this, that):   654    655     """   656     Return whether 'this' category permits 'that' category. Where 'this' is a   657     wildcard ("*"), 'that' should always match. A value of False is returned if   658     the categories do not otherwise match.   659     """   660    661     return (this == "*" or this == that) and this or False   662    663 def getMappingFromParameterStrings(l):   664    665     """   666     Return a mapping representing the list of "name=value" strings given by 'l'.   667     """   668    669     parameters = {}   670    671     for parameter in l:   672         parts = parameter.split("=")   673         name = parts[0].strip()   674         value = "=".join(parts[1:]).strip()   675         parameters[name] = value   676    677     return parameters   678    679 def getContentPreferences(accept):   680    681     """   682     Return a mapping from media types to parameters for content/media types   683     extracted from the given 'accept' header value. The mapping is returned in   684     the form of a list of (media type, parameters) tuples.   685    686     See: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1   687     """   688    689     preferences = []   690    691     for field in accept.split(","):   692    693         # The media type with parameters (defined by the "media-range") is   694         # separated from any other parameters (defined as "accept-extension"   695         # parameters) by a quality parameter.   696    697         fparts = accept_regexp.split(field)   698    699         # The first part is always the media type.   700    701         media_type = fparts[0].strip()   702    703         # Any other parts can be interpreted as extension parameters.   704    705         if len(fparts) > 1:   706             fparts = ("q=" + ";q=".join(fparts[1:])).split(";")   707         else:   708             fparts = []   709    710         # Each field in the preferences can incorporate parameters separated by   711         # semicolon characters.   712    713         parameters = getMappingFromParameterStrings(fparts)   714         media_range = MediaRange(media_type, parameters)   715         preferences.append(media_range)   716    717     return ContentPreferences(preferences)   718    719 class ContentPreferences:   720    721     "A wrapper around content preference information."   722    723     def __init__(self, preferences):   724         self.preferences = preferences   725    726     def __iter__(self):   727         return iter(self.preferences)   728    729     def get_ordered(self, by_quality=0):   730    731         """   732         Return a list of content/media types in descending order of preference.   733         If 'by_quality' is set to a true value, the "q" value will be used as   734         the primary measure of preference; otherwise, only the specificity will   735         be considered.   736         """   737    738         ordered = {}   739    740         for media_range in self.preferences:   741             specificity = media_range.get_specificity()   742    743             if by_quality:   744                 q = float(media_range.accept_parameters.get("q", "1"))   745                 key = q, specificity   746             else:   747                 key = specificity   748    749             if not ordered.has_key(key):   750                 ordered[key] = []   751    752             ordered[key].append(media_range)   753    754         # Return the preferences in descending order of quality and specificity.   755    756         keys = ordered.keys()   757         keys.sort(reverse=True)   758         return [ordered[key] for key in keys]   759    760     def get_acceptable_types(self, available):   761    762         """   763         Return content/media types from those in the 'available' list supported   764         by the known preferences grouped by preference level in descending order   765         of preference.   766         """   767    768         matches = {}   769         available = set(available[:])   770    771         for level in self.get_ordered():   772             for media_range in level:   773    774                 # Attempt to match available types.   775    776                 found = set()   777                 for available_type in available:   778                     if media_range.permits(available_type):   779                         q = float(media_range.accept_parameters.get("q", "1"))   780                         if not matches.has_key(q):   781                             matches[q] = []   782                         matches[q].append(available_type)   783                         found.add(available_type)   784    785                 # Stop looking for matches for matched available types.   786    787                 if found:   788                     available.difference_update(found)   789    790         # Sort the matches in descending order of quality.   791    792         all_q = matches.keys()   793    794         if all_q:   795             all_q.sort(reverse=True)   796             return [matches[q] for q in all_q]   797         else:   798             return []   799    800     def get_preferred_types(self, available):   801    802         """   803         Return the preferred content/media types from those in the 'available'   804         list, given the known preferences.   805         """   806    807         preferred = self.get_acceptable_types(available)   808         if preferred:   809             return preferred[0]   810         else:   811             return []   812    813 # Content type parsing.   814    815 def getContentTypeAndEncoding(content_type):   816    817     """   818     Return a tuple with the content/media type and encoding, extracted from the   819     given 'content_type' header value.   820     """   821    822     m = encoding_regexp.search(content_type)   823     if m:   824         return m.group("content_type"), m.group("encoding")   825     else:   826         return None, None   827    828 # Page access functions.   829    830 def getPageURL(page):   831    832     "Return the URL of the given 'page'."   833    834     request = page.request   835     return request.getQualifiedURL(page.url(request, relative=0))   836    837 def getFormat(page):   838    839     "Get the format used on the given 'page'."   840    841     return page.pi["format"]   842    843 def getMetadata(page):   844    845     """   846     Return a dictionary containing items describing for the given 'page' the   847     page's "created" time, "last-modified" time, "sequence" (or revision number)   848     and the "last-comment" made about the last edit.   849     """   850    851     request = page.request   852    853     # Get the initial revision of the page.   854    855     revisions = page.getRevList()   856    857     if not revisions:   858         return {}   859    860     event_page_initial = Page(request, page.page_name, rev=revisions[-1])   861    862     # Get the created and last modified times.   863    864     initial_revision = getPageRevision(event_page_initial)   865    866     metadata = {}   867     metadata["created"] = initial_revision["timestamp"]   868     latest_revision = getPageRevision(page)   869     metadata["last-modified"] = latest_revision["timestamp"]   870     metadata["sequence"] = len(revisions) - 1   871     metadata["last-comment"] = latest_revision["comment"]   872    873     return metadata   874    875 def getPageRevision(page):   876    877     "Return the revision details dictionary for the given 'page'."   878    879     # From Page.edit_info...   880    881     if hasattr(page, "editlog_entry"):   882         line = page.editlog_entry()   883     else:   884         line = page._last_edited(page.request) # MoinMoin 1.5.x and 1.6.x   885    886     # Similar to Page.mtime_usecs behaviour...   887    888     if line:   889         timestamp = line.ed_time_usecs   890         mtime = wikiutil.version2timestamp(long(timestamp)) # must be long for py 2.2.x   891         comment = line.comment   892     else:   893         mtime = 0   894         comment = ""   895    896     # Leave the time zone empty.   897    898     return {"timestamp" : DateTime(time.gmtime(mtime)[:6] + (None,)), "comment" : comment}   899    900 # Page parsing and formatting of embedded content.   901    902 def getPageParserClass(request):   903    904     "Using 'request', return a parser class for the current page's format."   905    906     return getParserClass(request, getFormat(request.page))   907    908 def getParserClass(request, format):   909    910     """   911     Return a parser class using the 'request' for the given 'format', returning   912     a plain text parser if no parser can be found for the specified 'format'.   913     """   914    915     try:   916         return wikiutil.searchAndImportPlugin(request.cfg, "parser", format or "plain")   917     except wikiutil.PluginMissingError:   918         return wikiutil.searchAndImportPlugin(request.cfg, "parser", "plain")   919    920 def getFormatterClass(request, format):   921    922     """   923     Return a formatter class using the 'request' for the given output 'format',   924     returning a plain text formatter if no formatter can be found for the   925     specified 'format'.   926     """   927    928     try:   929         return wikiutil.searchAndImportPlugin(request.cfg, "formatter", format or "plain")   930     except wikiutil.PluginMissingError:   931         return wikiutil.searchAndImportPlugin(request.cfg, "formatter", "plain")   932    933 def formatText(text, request, fmt, parser_cls=None):   934    935     """   936     Format the given 'text' using the specified 'request' and formatter 'fmt'.   937     Suppress line anchors in the output, and fix lists by indicating that a   938     paragraph has already been started.   939     """   940    941     if not parser_cls:   942         parser_cls = getPageParserClass(request)   943     parser = parser_cls(text, request, line_anchors=False)   944    945     old_fmt = request.formatter   946     request.formatter = fmt   947     try:   948         return redirectedOutput(request, parser, fmt, inhibit_p=True)   949     finally:   950         request.formatter = old_fmt   951    952 def redirectedOutput(request, parser, fmt, **kw):   953    954     "A fixed version of the request method of the same name."   955    956     buf = StringIO()   957     request.redirect(buf)   958     try:   959         parser.format(fmt, **kw)   960         if hasattr(fmt, "flush"):   961             buf.write(fmt.flush(True))   962     finally:   963         request.redirect()   964     text = buf.getvalue()   965     buf.close()   966     return text   967    968 # Textual representations.   969    970 def getSimpleWikiText(text):   971    972     """   973     Return the plain text representation of the given 'text' which may employ   974     certain Wiki syntax features, such as those providing verbatim or monospaced   975     text.   976     """   977    978     # NOTE: Re-implementing support for verbatim text and linking avoidance.   979    980     return "".join([s for s in verbatim_regexp.split(text) if s is not None])   981    982 def getEncodedWikiText(text):   983    984     "Encode the given 'text' in a verbatim representation."   985    986     return "<<Verbatim(%s)>>" % text   987    988 def getPrettyTitle(title):   989    990     "Return a nicely formatted version of the given 'title'."   991    992     return title.replace("_", " ").replace("/", u" ? ")   993    994 # User interface functions.   995    996 def getParameter(request, name, default=None):   997    998     """   999     Using the given 'request', return the value of the parameter with the given  1000     'name', returning the optional 'default' (or None) if no value was supplied  1001     in the 'request'.  1002     """  1003   1004     return get_form(request).get(name, [default])[0]  1005   1006 def getQualifiedParameter(request, prefix, argname, default=None):  1007   1008     """  1009     Using the given 'request', 'prefix' and 'argname', retrieve the value of the  1010     qualified parameter, returning the optional 'default' (or None) if no value  1011     was supplied in the 'request'.  1012     """  1013   1014     argname = getQualifiedParameterName(prefix, argname)  1015     return getParameter(request, argname, default)  1016   1017 def getQualifiedParameterName(prefix, argname):  1018   1019     """  1020     Return the qualified parameter name using the given 'prefix' and 'argname'.  1021     """  1022   1023     if not prefix:  1024         return argname  1025     else:  1026         return "%s-%s" % (prefix, argname)  1027   1028 # Page-related functions.  1029   1030 def getPrettyPageName(page):  1031   1032     "Return a nicely formatted title/name for the given 'page'."  1033   1034     title = page.split_title(force=1)  1035     return getPrettyTitle(title)  1036   1037 def linkToPage(request, page, text, query_string=None, anchor=None, **kw):  1038   1039     """  1040     Using 'request', return a link to 'page' with the given link 'text' and  1041     optional 'query_string' and 'anchor'.  1042     """  1043   1044     text = wikiutil.escape(text)  1045     return page.link_to_raw(request, text, query_string, anchor, **kw)  1046   1047 def linkToResource(url, request, text, query_string=None, anchor=None):  1048   1049     """  1050     Using 'request', return a link to 'url' with the given link 'text' and  1051     optional 'query_string' and 'anchor'.  1052     """  1053   1054     if anchor:  1055         url += "#%s" % anchor  1056   1057     if query_string:  1058         query_string = wikiutil.makeQueryString(query_string)  1059         url += "?%s" % query_string  1060   1061     formatter = request.page and getattr(request.page, "formatter", None) or request.html_formatter  1062   1063     output = []  1064     output.append(formatter.url(1, url))  1065     output.append(formatter.text(text))  1066     output.append(formatter.url(0))  1067     return "".join(output)  1068   1069 def getFullPageName(parent, title):  1070   1071     """  1072     Return a full page name from the given 'parent' page (can be empty or None)  1073     and 'title' (a simple page name).  1074     """  1075   1076     if parent:  1077         return "%s/%s" % (parent.rstrip("/"), title)  1078     else:  1079         return title  1080   1081 # vim: tabstop=4 expandtab shiftwidth=4