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