1 # -*- coding: iso-8859-1 -*- 2 """ 3 MoinMoin - MoinForms library 4 5 @copyright: 2012, 2013 by Paul Boddie <paul@boddie.org.uk> 6 @license: GNU GPL (v2 or later), see COPYING.txt for details. 7 """ 8 9 from compiler import parse 10 from compiler.ast import Const, Dict, Discard, List, Module, Stmt 11 from MoinMoin.action import do_show 12 from MoinMoin.Page import Page 13 from MoinMoin import security, wikiutil 14 from MoinSupport import * 15 import re 16 17 __version__ = "0.1" 18 19 form_field_regexp_str = r"<<Form(Field|Message)\((.*?)\)>>" 20 form_field_regexp = re.compile(form_field_regexp_str, re.DOTALL) 21 22 class MoinFormDataError(Exception): 23 24 "An exception indicating a problem with form data." 25 26 pass 27 28 # Common action functionality. 29 30 class MoinFormHandlerAction: 31 32 "A handler action that can be specialised for individual forms." 33 34 def __init__(self, pagename, request): 35 self.pagename = pagename 36 self.request = request 37 self.access_handler = None 38 self.attributes = None 39 40 def getAccessHandler(self): 41 42 """ 43 Return an access handler for the form whose attributes have been 44 obtained and stored in this instance. 45 """ 46 47 return FormAccess(self.pagename, self.request, self.attributes) 48 49 def processForm(self): 50 51 """ 52 Interpret the request details and modify them according to the structure 53 of the interpreted information. 54 """ 55 56 _ = self.request.getText 57 58 # Get the form fields and obtain the hierarchical field structure. 59 60 form = get_form(self.request) 61 fields = getFields(form, remove=True) 62 63 # Detect any request to load data. 64 65 if fields.has_key("load"): 66 try: 67 number = int(fields["load"][0]) 68 except ValueError: 69 fields = {} 70 else: 71 self.attributes, text = self.getFormForFragment(fields) 72 self.access_handler = self.getAccessHandler() 73 74 # Attempt to load the form. 75 76 try: 77 fields = self.loadFields(number) 78 79 # Absent or inaccessible forms will result in an IndexError. 80 81 except IndexError: 82 self.request.theme.add_msg(_("The stored data for this form cannot be accessed."), "error") 83 do_show(self.pagename, self.request) 84 return 85 86 # Bad data will result in a MoinFormDataError. 87 88 except MoinFormDataError: 89 self.request.theme.add_msg(_("The stored data for this form is in the wrong format."), "error") 90 do_show(self.pagename, self.request) 91 return 92 93 # Otherwise, process any supplied data. 94 95 else: 96 # Modify and validate the form. 97 98 self.modifyFields(fields) 99 100 # Get the form definition. 101 102 self.attributes, text = self.getFormForFragment(fields) 103 self.access_handler = self.getAccessHandler() 104 structure = getFormStructure(text, self.request) 105 106 # Check the permissions on the form. 107 108 if not self.checkPermissions("write"): 109 self.request.theme.add_msg(_("You do not appear to have access to this form."), "error") 110 do_show(self.pagename, self.request) 111 return 112 113 # Without any form definition, the page is probably the wrong one. 114 115 if not structure: 116 self.request.theme.add_msg(_("This page does not provide a form."), "error") 117 do_show(self.pagename, self.request) 118 return 119 120 # With a form definition, attempt to validate the fields. 121 122 if self.validateFields(fields, structure): 123 if self.shouldFinish(fields): 124 self.finished(fields, form) 125 return 126 127 self.unfinished(fields, form) 128 129 def finished(self, fields, form): 130 131 "Handle the finished 'fields' and 'form'." 132 133 self.storeFields(fields) 134 self.unfinished(fields, form) 135 136 def unfinished(self, fields, form): 137 138 "Handle the unfinished 'fields' and 'form'." 139 140 # Serialise and show the form. 141 142 self.serialiseFields(fields, form) 143 do_show(self.pagename, self.request) 144 145 def shouldFinish(self, fields): 146 147 """ 148 Subject to the attributes stored for the form in this instance, return 149 whether any field referenced by the "finishing" attribute is present 150 and thus indicate whether the form handling should finish. 151 """ 152 153 finishing = self.attributes.has_key("finishing") and self.attributes["finishing"].split(",") 154 155 if finishing: 156 for name in finishing: 157 if fields.has_key(name): 158 return True 159 160 return False 161 162 def getFormForFragment(self, fields): 163 164 "Return the attributes and text of the form being handled." 165 166 fragment = fields.get("fragment", [None])[0] 167 text = Page(self.request, self.pagename).get_raw_body() 168 return getFormForFragment(text, fragment) 169 170 def checkPermissions(self, action): 171 172 """ 173 Check the permissions of the user against any restrictions specified in 174 the form's 'attributes'. 175 """ 176 177 return self.access_handler.checkPermissions(action) 178 179 def validateFields(self, fields, structure): 180 181 """ 182 Validate the given 'fields' using the given form 'structure', 183 introducing error fields where the individual fields do not conform to 184 their descriptions. 185 """ 186 187 return self.validateFieldsUsingStructure(fields, structure) 188 189 def validateFieldsUsingStructure(self, fields, structure): 190 191 "Validate the given 'fields' using the given 'structure'." 192 193 _ = self.request.getText 194 valid = True 195 196 for key, definition in structure.items(): 197 value = fields.get(key) 198 199 # Enter form sections and validate them. 200 201 if isinstance(definition, dict): 202 if value: 203 for element in getSectionElements(value): 204 valid = self.validateFieldsUsingStructure(element, structure[key]) and valid 205 206 # Validate individual fields. 207 208 elif structure.has_key(key): 209 path, dictpage, label, section, field_args, allowed_values = definition 210 errors = [] 211 212 # Test for obligatory values. 213 214 if not value or not value[0]: 215 if field_args.get("required"): 216 217 # Detect new parts of the structure and avoid producing 218 # premature error messages. 219 220 if not fields.has_key("_new"): 221 errors.append(_("This field must be filled out.")) 222 else: 223 valid = False 224 else: 225 # Test for unacceptable values. 226 227 if allowed_values and set(value).difference(allowed_values): 228 errors.append(_("At least one of the choices is not acceptable.")) 229 230 # Test the number of values. 231 232 if field_args.get("type") == "select": 233 if field_args.has_key("maxselected"): 234 if len(value) > int(field_args["maxselected"]): 235 errors.append(_("Incorrect number of choices given: need %s.") % field_args["maxselected"]) 236 237 if errors: 238 fields["%s-error" % key] = errors 239 valid = False 240 241 return valid 242 243 def serialiseFields(self, fields, form, path=None): 244 245 """ 246 Serialise the given 'fields' to the given 'form', using the given 'path' 247 to name the entries. 248 """ 249 250 for key, value in fields.items(): 251 252 # Serialise sections. 253 254 if isinstance(value, dict): 255 for index, element in enumerate(getSectionElements(value)): 256 element_ref = "%s$%s" % (key, index) 257 258 self.serialiseFields(element, form, 259 path and ("%s/%s" % (path, element_ref)) or element_ref 260 ) 261 262 # Serialise fields. 263 264 else: 265 form[path and ("%s/%s" % (path, key)) or key] = value 266 267 def modifyFields(self, fields): 268 269 "Modify the given 'fields', removing and adding items." 270 271 # First, remove fields. 272 273 for key in fields.keys(): 274 if key.startswith("_remove="): 275 self.removeField(key[8:], fields) 276 277 # Then, add fields. 278 279 for key in fields.keys(): 280 if key.startswith("_add="): 281 self.addField(key[5:], fields) 282 283 def removeField(self, path, fields): 284 285 """ 286 Remove the section element indicated by the given 'path' from the 287 'fields'. 288 """ 289 290 section, (name, index) = getSectionForPath(path, fields) 291 try: 292 del section[name][index] 293 except KeyError: 294 pass 295 296 def addField(self, path, fields): 297 298 """ 299 Add a section element indicated by the given 'path' to the 'fields'. 300 """ 301 302 section, (name, index) = getSectionForPath(path, fields) 303 placeholder = {"_new" : ""} 304 305 if section.has_key(name): 306 indexes = section[name].keys() 307 max_index = max(map(int, indexes)) 308 section[name][max_index + 1] = placeholder 309 else: 310 max_index = -1 311 section[name] = {0 : placeholder} 312 313 # Storage of form submissions. 314 315 def storeFields(self, fields): 316 317 "Store the given 'fields' as a Python object representation." 318 319 store = FormStore(self.access_handler) 320 store.append(repr(fields)) 321 322 def loadFields(self, number): 323 324 "Load the fields associated with the given submission 'number'." 325 326 store = FormStore(self.access_handler) 327 return loadFields(store, number) 328 329 def loadFields(store, number): 330 331 """ 332 Load the fields from the 'store' that are associated with the given 333 submission 'number'. 334 """ 335 336 text = store[number] 337 module = parse(text) 338 if checkStoredFormData(module): 339 return eval(text) 340 else: 341 raise MoinFormDataError, text 342 343 def checkStoredFormData(node): 344 345 """ 346 Check the syntax 'node' and its descendants for suitability as parts of 347 a field definition. 348 """ 349 350 for child in node.getChildNodes(): 351 if isinstance(child, Const): 352 pass 353 elif not isinstance(child, (Dict, Discard, List, Module, Stmt)) or not checkStoredFormData(child): 354 return False 355 356 return True 357 358 class FormAccess: 359 360 "A means of checking access to form data." 361 362 def __init__(self, pagename, request, attributes): 363 self.pagename = pagename 364 self.request = request 365 self.attributes = attributes 366 367 def checkPermissions(self, action): 368 369 """ 370 Check the permissions of the user against any restrictions specified in 371 the form's 'attributes'. 372 """ 373 374 user = self.request.user 375 376 # Use the access definition if one is given. 377 378 if self.attributes.has_key("access"): 379 access = self.attributes["access"] 380 acl = security.AccessControlList(self.request.cfg, [access]) 381 policy = lambda request, pagename, username, action: acl.may(request, username, action) 382 383 # Otherwise, use the page permissions. 384 385 else: 386 policy = security._check 387 388 # The "read" action is only satisfied by the "admin" role. 389 390 return user and ( 391 action != "read" and policy(self.request, self.pagename, user.name, action) or 392 action == "read" and policy(self.request, self.pagename, user.name, "admin") 393 ) 394 395 class FormStore(ItemStore): 396 397 "A form-specific storage mechanism." 398 399 def __init__(self, handler): 400 401 "Initialise the store with the form 'handler'." 402 403 self.handler = handler 404 page = Page(handler.request, handler.pagename) 405 fragment = handler.attributes.get("fragment") 406 suffix = fragment and ("_%s" % fragment) or "" 407 formdir = wikiutil.quoteWikinameFS("form%s" % suffix) 408 lockdir = wikiutil.quoteWikinameFS("lock%s" % suffix) 409 ItemStore.__init__(self, page, ("forms", formdir), ("form_locks", lockdir)) 410 411 def can_write(self): 412 413 """ 414 Permit writing of form data using the form attributes or page 415 permissions. 416 """ 417 418 return self.handler.checkPermissions("write") 419 420 def can_read(self): 421 422 """ 423 Permit reading of form data using the form attributes or page 424 permissions. 425 """ 426 427 return self.handler.checkPermissions("read") 428 429 # Form and field information. 430 431 def getFormStructure(text, request, path=None, structure=None): 432 433 """ 434 For the given form 'text' and using the 'request', return details of the 435 form for the section at the given 'path' (or the entire form if 'path' is 436 omitted), populating the given 'structure' (or populating a new structure if 437 'structure' is omitted). 438 """ 439 440 if structure is None: 441 structure = {} 442 443 for format, attributes, body in getFragments(text, True): 444 445 # Get field details at the current level. 446 447 if format is None: 448 structure.update(getFormFields(body, path, request)) 449 450 # Where a section is found, get details from within the section. 451 452 elif format == "form": 453 if attributes.has_key("section"): 454 section_name = attributes["section"] 455 section = structure[section_name] = {} 456 getFormStructure(body, request, path and ("%s/%s" % (path, section_name)) or section_name, section) 457 elif attributes.has_key("message"): 458 getFormStructure(body, request, path, structure) 459 elif attributes.has_key("not-message"): 460 getFormStructure(body, request, path, structure) 461 462 # Get field details from other kinds of region. 463 464 elif format != "form": 465 getFormStructure(body, request, path, structure) 466 467 return structure 468 469 def getFormForFragment(text, fragment=None): 470 471 """ 472 Return the form region from the given 'text' for the specified 'fragment'. 473 If no fragment is specified, the first form region is returned. The form 474 region is described using a tuple containing the attributes for the form 475 and the body text of the form. 476 """ 477 478 for format, attributes, body in getFragments(text): 479 if format == "form" and (not fragment or attributes.get("fragment") == fragment): 480 return attributes, body 481 482 return {}, None 483 484 def getFieldArguments(field_definition): 485 486 "Return the parsed arguments from the given 'field_definition' string." 487 488 field_args = {} 489 490 for field_arg in field_definition.split(): 491 if field_arg == "required": 492 field_args[field_arg] = True 493 continue 494 495 # Record the key-value details. 496 497 try: 498 argname, argvalue = field_arg.split("=", 1) 499 field_args[argname] = argvalue 500 501 # Single keywords are interpreted as type descriptions. 502 503 except ValueError: 504 if not field_args.has_key("type"): 505 field_args["type"] = field_arg 506 507 return field_args 508 509 # Common formatting functions. 510 511 def getFormOutput(text, fields, form_fragment=None, path=None, fragment=None, repeating=None, index=None): 512 513 """ 514 Combine regions found in the given 'text' and then return them as a single 515 block. The reason for doing this, as opposed to just passing each region to 516 a suitable parser for formatting, is that form sections may break up 517 regions, and such sections may not define separate subregions but instead 518 act as a means of conditional inclusion of text into an outer region. 519 520 The given 'fields' are used to populate fields provided in forms and to 521 control whether sections are populated or not. 522 523 The optional 'form_fragment' is used to indicate the form to which the 524 fields belong. 525 526 The optional 'path' is used to adjust form fields to refer to the correct 527 part of the form hierarchy. 528 529 The optional 'fragment' is used to indicate the form being output. If this 530 value is different to 'form_fragment', the structure of the form should not 531 be influenced by the 'fields'. 532 533 The optional 'repeating' and 'index' is used to refer to individual values 534 of a designated field. 535 """ 536 537 this_form = fragment and form_fragment == fragment or not fragment and not form_fragment 538 539 output = [] 540 section = fields 541 542 for region in getRegions(text, True): 543 format, attributes, body, header, close = getFragmentFromRegion(region) 544 545 # Adjust any FormField macros to use hierarchical names. 546 547 if format is None: 548 output.append((path or fragment or repeating) and 549 adjustFormFields(body, path, fragment, repeating, index) or body) 550 551 # Include form sections only if fields exist for those sections. 552 553 elif format == "form": 554 section_name = attributes.get("section") 555 message_name = attributes.get("message") 556 absent_message_name = attributes.get("not-message") 557 558 # Ignore sections not related to the supplied field data. 559 560 if not this_form: 561 pass 562 563 # Sections are groups of fields in their own namespace. 564 565 elif section_name and section.has_key(section_name): 566 567 # Iterate over the section contents ignoring the given indexes. 568 569 for index, element in enumerate(getSectionElements(section[section_name])): 570 element_ref = "%s$%s" % (section_name, index) 571 572 # Get the output for the section. 573 574 output.append(getFormOutput(body, element, form_fragment, 575 path and ("%s/%s" % (path, element_ref)) or element_ref, fragment)) 576 577 # Message regions are conditional on a particular field and 578 # reference the current namespace. 579 580 elif message_name and section.has_key(message_name): 581 582 if attributes.get("repeating"): 583 for index in range(0, len(section[message_name])): 584 output.append(getFormOutput(body, section, form_fragment, path, fragment, message_name, index)) 585 else: 586 output.append(getFormOutput(body, section, form_fragment, path, fragment)) 587 588 # Not-message regions are conditional on a particular field being 589 # absent. They reference the current namespace. 590 591 elif absent_message_name and not section.has_key(absent_message_name): 592 output.append(getFormOutput(body, section, form_fragment, path, fragment)) 593 594 # Inspect and include other regions. 595 596 else: 597 output.append(header) 598 output.append(getFormOutput(body, section, form_fragment, path, fragment, repeating, index)) 599 output.append(close) 600 601 return "".join(output) 602 603 def getFormFields(body, path, request): 604 605 "Return a dictionary of fields from the given 'body' at the given 'path'." 606 607 fields = {} 608 cache = {} 609 type = None 610 611 for i, match in enumerate(form_field_regexp.split(body)): 612 state = i % 3 613 614 if state == 1: 615 type = match 616 elif state == 2 and type == "Field": 617 args = {} 618 619 # Obtain the macro arguments, adjusted to consider the path. 620 621 name, path, dictpage, label, section, fragment = \ 622 getMacroArguments(adjustMacroArguments(parseMacroArguments(match), path)) 623 624 # Obtain field information from the cache, if possible. 625 626 cache_key = (name, dictpage) 627 628 if cache.has_key(cache_key): 629 field_args, allowed_values = cache[cache_key] 630 631 # Otherwise, obtain field information from any WikiDict. 632 633 else: 634 field_args = {} 635 allowed_values = None 636 637 if dictpage: 638 wikidict = getWikiDict(dictpage, request) 639 if wikidict: 640 field_definition = wikidict.get(name) 641 if field_definition: 642 field_args = getFieldArguments(field_definition) 643 if field_args.has_key("source"): 644 sourcedict = getWikiDict(field_args["source"], request) 645 if sourcedict: 646 allowed_values = sourcedict.keys() 647 648 cache[cache_key] = field_args, allowed_values 649 650 # Store the field information. 651 652 fields[name] = path, dictpage, label, section, field_args, allowed_values 653 654 return fields 655 656 def adjustFormFields(body, path, fragment, repeating=None, index=None): 657 658 """ 659 Return a version of the 'body' with the names in FormField macros updated to 660 incorporate the given 'path' and 'fragment'. If 'repeating' is specified, 661 any field with such a name will be adjusted to reference the value with the 662 given 'index'. 663 """ 664 665 result = [] 666 type = None 667 668 for i, match in enumerate(form_field_regexp.split(body)): 669 state = i % 3 670 671 # Reproduce normal text as is. 672 673 if state == 0: 674 result.append(match) 675 676 # Capture the macro type. 677 678 elif state == 1: 679 type = match 680 681 # Substitute the macro and modified arguments. 682 683 else: 684 result.append("<<Form%s(%s)>>" % (type, 685 quoteMacroArguments( 686 adjustMacroArguments( 687 parseMacroArguments(match), path, fragment, repeating, index 688 ) 689 ) 690 )) 691 692 return "".join(result) 693 694 def adjustMacroArguments(args, path, fragment=None, repeating=None, index=None): 695 696 """ 697 Adjust the given 'args' so that the path incorporates the given 698 'path' and 'fragment', returning a new list containing the revised path, 699 fragment and remaining arguments. If 'repeating' is specified, any field 700 with such a name will be adjusted to reference the value with the given 701 'index'. 702 """ 703 704 if not path and not fragment and not repeating: 705 return args 706 707 result = [] 708 old_path = None 709 found_name = None 710 711 for name, value in args: 712 if name == "path": 713 old_path = value 714 elif name == "fragment" and fragment: 715 pass 716 else: 717 result.append((name, value)) 718 719 # Remember any explicitly given name or where a keyword appears. 720 721 if name == "name" or name is None and found_name is None: 722 found_name = value 723 724 if path: 725 qualified = old_path and ("%s/%s" % (old_path, path)) or path 726 result.append(("path", qualified)) 727 728 if fragment: 729 result.append(("fragment", fragment)) 730 731 if repeating and repeating == found_name: 732 result.append(("index", index)) 733 734 return result 735 736 def parseMacroArguments(args): 737 738 """ 739 Interpret the arguments. To support commas in labels, the label argument 740 should be quoted. For example: 741 742 "label=No, thanks!" 743 """ 744 745 try: 746 parsed_args = args and wikiutil.parse_quoted_separated(args, name_value=False) or [] 747 except AttributeError: 748 parsed_args = args.split(",") 749 750 pairs = [] 751 for arg in parsed_args: 752 if arg: 753 pair = arg.split("=", 1) 754 if len(pair) < 2: 755 pairs.append((None, arg)) 756 else: 757 pairs.append(tuple(pair)) 758 759 return pairs 760 761 def quoteMacroArguments(args): 762 763 """ 764 Quote the given 'args' - a collection of (name, value) tuples - returning a 765 string containing the comma-separated, quoted arguments. 766 """ 767 768 quoted = [] 769 770 for name, value in args: 771 value = value.replace('"', '""') 772 if name is None: 773 quoted.append('"%s"' % value) 774 else: 775 quoted.append('"%s=%s"' % (name, value)) 776 777 return ",".join(quoted) 778 779 def getMacroArguments(parsed_args): 780 781 "Return the macro arguments decoded from 'parsed_args'." 782 783 found_name = None 784 path = None 785 dictpage = None 786 label = None 787 section = None 788 fragment = None 789 790 for name, value in parsed_args: 791 if name == "name": 792 found_name = value 793 794 elif name == "path": 795 path = value 796 797 elif name == "dict": 798 dictpage = value 799 800 elif name == "label": 801 label = value 802 803 elif name == "section": 804 section = value 805 806 elif name == "fragment": 807 fragment = value 808 809 # Keywords are interpreted as certain kinds of values. 810 811 elif name is None: 812 if found_name is None: 813 found_name = value 814 815 elif dictpage is None: 816 dictpage = value 817 818 return found_name, path, dictpage, label, section, fragment 819 820 def getFields(d, remove=False): 821 822 """ 823 Return the form fields hierarchy for the given dictionary 'd'. If the 824 optional 'remove' parameter is set to a true value, remove the entries for 825 the fields from 'd'. 826 """ 827 828 fields = {} 829 830 for key, value in d.items(): 831 832 # Detect modifying fields. 833 834 if key.find("=") != -1: 835 fields[key] = value 836 if remove: 837 del d[key] 838 continue 839 840 # Reproduce the original hierarchy of the fields. 841 842 section = fields 843 parts = getPathDetails(key) 844 845 for name, index in parts[:-1]: 846 847 # Add an entry for instances of the section. 848 849 if not section.has_key(name): 850 section[name] = {} 851 852 # Add an entry for the specific instance of the section. 853 854 if not section[name].has_key(index): 855 section[name][index] = {} 856 857 section = section[name][index] 858 859 section[parts[-1][0]] = value 860 861 if remove: 862 del d[key] 863 864 return fields 865 866 def getPathDetails(path): 867 868 """ 869 Return the given 'path' as a list of (name, index) tuples providing details 870 of section instances, with any specific field appearing as the last element 871 and having the form (name, None). 872 """ 873 874 parts = [] 875 876 for part in path.split("/"): 877 try: 878 name, index = part.split("$", 1) 879 index = int(index) 880 except ValueError: 881 name, index = part, None 882 883 parts.append((name, index)) 884 885 return parts 886 887 def getSectionForPath(path, fields): 888 889 """ 890 Obtain the section indicated by the given 'path' from the 'fields', 891 returning a tuple of the form (parent section, (name, index)), where the 892 parent section contains the referenced section, where name is the name of 893 the referenced section, and where index, if not None, is the index of a 894 specific section instance within the named section. 895 """ 896 897 parts = getPathDetails(path) 898 section = fields 899 900 for name, index in parts[:-1]: 901 section = fields[name][index] 902 903 return section, parts[-1] 904 905 def getSectionElements(section_elements): 906 907 "Return the given 'section_elements' as an ordered collection." 908 909 keys = map(int, section_elements.keys()) 910 keys.sort() 911 912 elements = [] 913 914 for key in keys: 915 elements.append(section_elements[key]) 916 917 return elements 918 919 # Parser-related formatting functions. 920 921 def formatForm(text, request, fmt, attrs=None, write=None): 922 923 """ 924 Format the given 'text' using the specified 'request' and formatter 'fmt'. 925 The optional 'attrs' can be used to control the presentation of the form. 926 927 If the 'write' parameter is specified, use it to write output; otherwise, 928 write output using the request. 929 """ 930 931 write = write or request.write 932 page = request.page 933 934 form = get_form(request) 935 form_fragment = form.get("fragment", [None])[0] 936 fields = getFields(form) 937 938 # Prepare the query string for the form action URL. 939 940 queryparams = [] 941 942 for argname, default in [("fragment", None), ("action", "MoinFormHandler")]: 943 if attrs and attrs.has_key(argname): 944 queryparams.append("%s=%s" % (argname, attrs[argname])) 945 elif default: 946 queryparams.append("%s=%s" % (argname, default)) 947 948 querystr = "&".join(queryparams) 949 fragment = attrs.get("fragment") 950 951 write(fmt.rawHTML('<form method="post" action="%s%s"%s>' % ( 952 escattr(page.url(request, querystr)), 953 fragment and ("#%s" % escattr(fragment)) or "", 954 fragment and (' id="%s"' % escattr(fragment)) or "" 955 ))) 956 957 # Obtain page text for the form, incorporating subregions and applicable 958 # sections. 959 960 output = getFormOutput(text, fields, form_fragment=form_fragment, fragment=fragment) 961 write(formatText(output, request, fmt, inhibit_p=False)) 962 963 write(fmt.rawHTML('</form>')) 964 965 def formatFormForOutputType(text, request, mimetype, attrs=None, write=None): 966 967 """ 968 Format the given 'text' using the specified 'request' for the given output 969 'mimetype'. 970 971 The optional 'attrs' can be used to control the presentation of the form. 972 973 If the 'write' parameter is specified, use it to write output; otherwise, 974 write output using the request. 975 """ 976 977 write = write or request.write 978 979 if mimetype == "text/html": 980 write('<html>') 981 write('<body>') 982 fmt = request.html_formatter 983 fmt.setPage(request.page) 984 formatForm(text, request, fmt, attrs, write) 985 write('</body>') 986 write('</html>') 987 988 # vim: tabstop=4 expandtab shiftwidth=4