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