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