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