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