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" % wikiutil.quoteWikinameFS(fragment)) or "" 407 ItemStore.__init__(self, page, "forms%s" % suffix, "form-locks%s" % suffix) 408 409 def can_write(self): 410 411 """ 412 Permit writing of form data using the form attributes or page 413 permissions. 414 """ 415 416 return self.handler.checkPermissions("write") 417 418 def can_read(self): 419 420 """ 421 Permit reading of form data using the form attributes or page 422 permissions. 423 """ 424 425 return self.handler.checkPermissions("read") 426 427 # Form and field information. 428 429 def getFormStructure(text, request, path=None, structure=None): 430 431 """ 432 For the given form 'text' and using the 'request', return details of the 433 form for the section at the given 'path' (or the entire form if 'path' is 434 omitted), populating the given 'structure' (or populating a new structure if 435 'structure' is omitted). 436 """ 437 438 if structure is None: 439 structure = {} 440 441 for format, attributes, body in getFragments(text, True): 442 443 # Get field details at the current level. 444 445 if format is None: 446 structure.update(getFormFields(body, path, request)) 447 448 # Where a section is found, get details from within the section. 449 450 elif format == "form": 451 if attributes.has_key("section"): 452 section_name = attributes["section"] 453 section = structure[section_name] = {} 454 getFormStructure(body, request, path and ("%s/%s" % (path, section_name)) or section_name, section) 455 elif attributes.has_key("message"): 456 getFormStructure(body, request, path, structure) 457 elif attributes.has_key("not-message"): 458 getFormStructure(body, request, path, structure) 459 460 # Get field details from other kinds of region. 461 462 elif format != "form": 463 getFormStructure(body, request, path, structure) 464 465 return structure 466 467 def getFormForFragment(text, fragment=None): 468 469 """ 470 Return the form region from the given 'text' for the specified 'fragment'. 471 If no fragment is specified, the first form region is returned. The form 472 region is described using a tuple containing the attributes for the form 473 and the body text of the form. 474 """ 475 476 for format, attributes, body in getFragments(text): 477 if format == "form" and (not fragment or attributes.get("fragment") == fragment): 478 return attributes, body 479 480 return {}, None 481 482 def getFieldArguments(field_definition): 483 484 "Return the parsed arguments from the given 'field_definition' string." 485 486 field_args = {} 487 488 for field_arg in field_definition.split(): 489 if field_arg == "required": 490 field_args[field_arg] = True 491 continue 492 493 # Record the key-value details. 494 495 try: 496 argname, argvalue = field_arg.split("=", 1) 497 field_args[argname] = argvalue 498 499 # Single keywords are interpreted as type descriptions. 500 501 except ValueError: 502 if not field_args.has_key("type"): 503 field_args["type"] = field_arg 504 505 return field_args 506 507 # Common formatting functions. 508 509 def getFormOutput(text, fields, form_fragment=None, path=None, fragment=None, repeating=None, index=None): 510 511 """ 512 Combine regions found in the given 'text' and then return them as a single 513 block. The reason for doing this, as opposed to just passing each region to 514 a suitable parser for formatting, is that form sections may break up 515 regions, and such sections may not define separate subregions but instead 516 act as a means of conditional inclusion of text into an outer region. 517 518 The given 'fields' are used to populate fields provided in forms and to 519 control whether sections are populated or not. 520 521 The optional 'form_fragment' is used to indicate the form to which the 522 fields belong. 523 524 The optional 'path' is used to adjust form fields to refer to the correct 525 part of the form hierarchy. 526 527 The optional 'fragment' is used to indicate the form being output. If this 528 value is different to 'form_fragment', the structure of the form should not 529 be influenced by the 'fields'. 530 531 The optional 'repeating' and 'index' is used to refer to individual values 532 of a designated field. 533 """ 534 535 this_form = fragment and form_fragment == fragment or not fragment and not form_fragment 536 537 output = [] 538 section = fields 539 540 for region in getRegions(text, True): 541 format, attributes, body, header, close = getFragmentFromRegion(region) 542 543 # Adjust any FormField macros to use hierarchical names. 544 545 if format is None: 546 output.append((path or fragment or repeating) and 547 adjustFormFields(body, path, fragment, repeating, index) or body) 548 549 # Include form sections only if fields exist for those sections. 550 551 elif format == "form": 552 section_name = attributes.get("section") 553 message_name = attributes.get("message") 554 absent_message_name = attributes.get("not-message") 555 556 # Ignore sections not related to the supplied field data. 557 558 if not this_form: 559 pass 560 561 # Sections are groups of fields in their own namespace. 562 563 elif section_name and section.has_key(section_name): 564 565 # Iterate over the section contents ignoring the given indexes. 566 567 for index, element in enumerate(getSectionElements(section[section_name])): 568 element_ref = "%s$%s" % (section_name, index) 569 570 # Get the output for the section. 571 572 output.append(getFormOutput(body, element, form_fragment, 573 path and ("%s/%s" % (path, element_ref)) or element_ref, fragment)) 574 575 # Message regions are conditional on a particular field and 576 # reference the current namespace. 577 578 elif message_name and section.has_key(message_name): 579 580 if attributes.get("repeating"): 581 for index in range(0, len(section[message_name])): 582 output.append(getFormOutput(body, section, form_fragment, path, fragment, message_name, index)) 583 else: 584 output.append(getFormOutput(body, section, form_fragment, path, fragment)) 585 586 # Not-message regions are conditional on a particular field being 587 # absent. They reference the current namespace. 588 589 elif absent_message_name and not section.has_key(absent_message_name): 590 output.append(getFormOutput(body, section, form_fragment, path, fragment)) 591 592 # Inspect and include other regions. 593 594 else: 595 output.append(header) 596 output.append(getFormOutput(body, section, form_fragment, path, fragment, repeating, index)) 597 output.append(close) 598 599 return "".join(output) 600 601 def getFormFields(body, path, request): 602 603 "Return a dictionary of fields from the given 'body' at the given 'path'." 604 605 fields = {} 606 cache = {} 607 type = None 608 609 for i, match in enumerate(form_field_regexp.split(body)): 610 state = i % 3 611 612 if state == 1: 613 type = match 614 elif state == 2 and type == "Field": 615 args = {} 616 617 # Obtain the macro arguments, adjusted to consider the path. 618 619 name, path, dictpage, label, section, fragment = \ 620 getMacroArguments(adjustMacroArguments(parseMacroArguments(match), path)) 621 622 # Obtain field information from the cache, if possible. 623 624 cache_key = (name, dictpage) 625 626 if cache.has_key(cache_key): 627 field_args, allowed_values = cache[cache_key] 628 629 # Otherwise, obtain field information from any WikiDict. 630 631 else: 632 field_args = {} 633 allowed_values = None 634 635 if dictpage: 636 wikidict = getWikiDict(dictpage, request) 637 if wikidict: 638 field_definition = wikidict.get(name) 639 if field_definition: 640 field_args = getFieldArguments(field_definition) 641 if field_args.has_key("source"): 642 sourcedict = getWikiDict(field_args["source"], request) 643 if sourcedict: 644 allowed_values = sourcedict.keys() 645 646 cache[cache_key] = field_args, allowed_values 647 648 # Store the field information. 649 650 fields[name] = path, dictpage, label, section, field_args, allowed_values 651 652 return fields 653 654 def adjustFormFields(body, path, fragment, repeating=None, index=None): 655 656 """ 657 Return a version of the 'body' with the names in FormField macros updated to 658 incorporate the given 'path' and 'fragment'. If 'repeating' is specified, 659 any field with such a name will be adjusted to reference the value with the 660 given 'index'. 661 """ 662 663 result = [] 664 type = None 665 666 for i, match in enumerate(form_field_regexp.split(body)): 667 state = i % 3 668 669 # Reproduce normal text as is. 670 671 if state == 0: 672 result.append(match) 673 674 # Capture the macro type. 675 676 elif state == 1: 677 type = match 678 679 # Substitute the macro and modified arguments. 680 681 else: 682 result.append("<<Form%s(%s)>>" % (type, 683 quoteMacroArguments( 684 adjustMacroArguments( 685 parseMacroArguments(match), path, fragment, repeating, index 686 ) 687 ) 688 )) 689 690 return "".join(result) 691 692 def adjustMacroArguments(args, path, fragment=None, repeating=None, index=None): 693 694 """ 695 Adjust the given 'args' so that the path incorporates the given 696 'path' and 'fragment', returning a new list containing the revised path, 697 fragment and remaining arguments. If 'repeating' is specified, any field 698 with such a name will be adjusted to reference the value with the given 699 'index'. 700 """ 701 702 if not path and not fragment and not repeating: 703 return args 704 705 result = [] 706 old_path = None 707 found_name = None 708 709 for name, value in args: 710 if name == "path": 711 old_path = value 712 elif name == "fragment" and fragment: 713 pass 714 else: 715 result.append((name, value)) 716 717 # Remember any explicitly given name or where a keyword appears. 718 719 if name == "name" or name is None and found_name is None: 720 found_name = value 721 722 if path: 723 qualified = old_path and ("%s/%s" % (old_path, path)) or path 724 result.append(("path", qualified)) 725 726 if fragment: 727 result.append(("fragment", fragment)) 728 729 if repeating and repeating == found_name: 730 result.append(("index", index)) 731 732 return result 733 734 def parseMacroArguments(args): 735 736 """ 737 Interpret the arguments. To support commas in labels, the label argument 738 should be quoted. For example: 739 740 "label=No, thanks!" 741 """ 742 743 try: 744 parsed_args = args and wikiutil.parse_quoted_separated(args, name_value=False) or [] 745 except AttributeError: 746 parsed_args = args.split(",") 747 748 pairs = [] 749 for arg in parsed_args: 750 if arg: 751 pair = arg.split("=", 1) 752 if len(pair) < 2: 753 pairs.append((None, arg)) 754 else: 755 pairs.append(tuple(pair)) 756 757 return pairs 758 759 def quoteMacroArguments(args): 760 761 """ 762 Quote the given 'args' - a collection of (name, value) tuples - returning a 763 string containing the comma-separated, quoted arguments. 764 """ 765 766 quoted = [] 767 768 for name, value in args: 769 value = value.replace('"', '""') 770 if name is None: 771 quoted.append('"%s"' % value) 772 else: 773 quoted.append('"%s=%s"' % (name, value)) 774 775 return ",".join(quoted) 776 777 def getMacroArguments(parsed_args): 778 779 "Return the macro arguments decoded from 'parsed_args'." 780 781 found_name = None 782 path = None 783 dictpage = None 784 label = None 785 section = None 786 fragment = None 787 788 for name, value in parsed_args: 789 if name == "name": 790 found_name = value 791 792 elif name == "path": 793 path = value 794 795 elif name == "dict": 796 dictpage = value 797 798 elif name == "label": 799 label = value 800 801 elif name == "section": 802 section = value 803 804 elif name == "fragment": 805 fragment = value 806 807 # Keywords are interpreted as certain kinds of values. 808 809 elif name is None: 810 if found_name is None: 811 found_name = value 812 813 elif dictpage is None: 814 dictpage = value 815 816 return found_name, path, dictpage, label, section, fragment 817 818 def getFields(d, remove=False): 819 820 """ 821 Return the form fields hierarchy for the given dictionary 'd'. If the 822 optional 'remove' parameter is set to a true value, remove the entries for 823 the fields from 'd'. 824 """ 825 826 fields = {} 827 828 for key, value in d.items(): 829 830 # Detect modifying fields. 831 832 if key.find("=") != -1: 833 fields[key] = value 834 if remove: 835 del d[key] 836 continue 837 838 # Reproduce the original hierarchy of the fields. 839 840 section = fields 841 parts = getPathDetails(key) 842 843 for name, index in parts[:-1]: 844 845 # Add an entry for instances of the section. 846 847 if not section.has_key(name): 848 section[name] = {} 849 850 # Add an entry for the specific instance of the section. 851 852 if not section[name].has_key(index): 853 section[name][index] = {} 854 855 section = section[name][index] 856 857 section[parts[-1][0]] = value 858 859 if remove: 860 del d[key] 861 862 return fields 863 864 def getPathDetails(path): 865 866 """ 867 Return the given 'path' as a list of (name, index) tuples providing details 868 of section instances, with any specific field appearing as the last element 869 and having the form (name, None). 870 """ 871 872 parts = [] 873 874 for part in path.split("/"): 875 try: 876 name, index = part.split("$", 1) 877 index = int(index) 878 except ValueError: 879 name, index = part, None 880 881 parts.append((name, index)) 882 883 return parts 884 885 def getSectionForPath(path, fields): 886 887 """ 888 Obtain the section indicated by the given 'path' from the 'fields', 889 returning a tuple of the form (parent section, (name, index)), where the 890 parent section contains the referenced section, where name is the name of 891 the referenced section, and where index, if not None, is the index of a 892 specific section instance within the named section. 893 """ 894 895 parts = getPathDetails(path) 896 section = fields 897 898 for name, index in parts[:-1]: 899 section = fields[name][index] 900 901 return section, parts[-1] 902 903 def getSectionElements(section_elements): 904 905 "Return the given 'section_elements' as an ordered collection." 906 907 keys = map(int, section_elements.keys()) 908 keys.sort() 909 910 elements = [] 911 912 for key in keys: 913 elements.append(section_elements[key]) 914 915 return elements 916 917 # Parser-related formatting functions. 918 919 def formatForm(text, request, fmt, attrs=None, write=None): 920 921 """ 922 Format the given 'text' using the specified 'request' and formatter 'fmt'. 923 The optional 'attrs' can be used to control the presentation of the form. 924 925 If the 'write' parameter is specified, use it to write output; otherwise, 926 write output using the request. 927 """ 928 929 write = write or request.write 930 page = request.page 931 932 form = get_form(request) 933 form_fragment = form.get("fragment", [None])[0] 934 fields = getFields(form) 935 936 # Prepare the query string for the form action URL. 937 938 queryparams = [] 939 940 for argname, default in [("fragment", None), ("action", "MoinFormHandler")]: 941 if attrs and attrs.has_key(argname): 942 queryparams.append("%s=%s" % (argname, attrs[argname])) 943 elif default: 944 queryparams.append("%s=%s" % (argname, default)) 945 946 querystr = "&".join(queryparams) 947 fragment = attrs.get("fragment") 948 949 write(fmt.rawHTML('<form method="post" action="%s%s"%s>' % ( 950 escattr(page.url(request, querystr)), 951 fragment and ("#%s" % escattr(fragment)) or "", 952 fragment and (' id="%s"' % escattr(fragment)) or "" 953 ))) 954 955 # Obtain page text for the form, incorporating subregions and applicable 956 # sections. 957 958 output = getFormOutput(text, fields, form_fragment=form_fragment, fragment=fragment) 959 write(formatText(output, request, fmt, inhibit_p=False)) 960 961 write(fmt.rawHTML('</form>')) 962 963 def formatFormForOutputType(text, request, mimetype, attrs=None, write=None): 964 965 """ 966 Format the given 'text' using the specified 'request' for the given output 967 'mimetype'. 968 969 The optional 'attrs' can be used to control the presentation of the form. 970 971 If the 'write' parameter is specified, use it to write output; otherwise, 972 write output using the request. 973 """ 974 975 write = write or request.write 976 977 if mimetype == "text/html": 978 write('<html>') 979 write('<body>') 980 fmt = request.html_formatter 981 fmt.setPage(request.page) 982 formatForm(text, request, fmt, attrs, write) 983 write('</body>') 984 write('</html>') 985 986 # vim: tabstop=4 expandtab shiftwidth=4