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 return loadFieldsFromString(store[number]) 353 354 def loadFieldsFromString(s): 355 356 """ 357 Load the fields from the given string 's', returning the metadata headers 358 and field structure. 359 """ 360 361 f = StringIO(s) 362 363 headers = [] 364 lines = [] 365 366 # Find all lines before a blank line, marking the end of any headers. 367 368 line = f.readline() 369 while line.strip(): 370 lines.append(line) 371 line = f.readline() 372 373 # Get the remaining text. 374 375 text = f.read() 376 377 # If there were headers, converted the recorded lines. 378 379 if text: 380 for line in lines: 381 name, value = [s.strip() for s in line.split(":", 1)] 382 headers.append((name, value)) 383 384 # Otherwise, rewind to obtain the entire item text for field data. 385 386 else: 387 f.seek(0) 388 text = f.read() 389 390 # Check the text and evaluate it if it is well-formed. 391 392 module = parse(text) 393 394 if checkStoredFormData(module): 395 return headers, eval(text) 396 else: 397 raise MoinFormDataError, text 398 399 def checkStoredFormData(node): 400 401 """ 402 Check the syntax 'node' and its descendants for suitability as parts of 403 a field definition. 404 """ 405 406 have_child = False 407 408 for child in node.getChildNodes(): 409 have_child = True 410 if isinstance(child, Const): 411 pass 412 elif not isinstance(child, (Dict, Discard, List, Module, Stmt)) or not checkStoredFormData(child): 413 return False 414 415 return have_child 416 417 class FormAccess: 418 419 "A means of checking access to form data." 420 421 def __init__(self, pagename, request, attributes): 422 self.pagename = pagename 423 self.request = request 424 self.attributes = attributes 425 426 def getACL(self): 427 428 """ 429 Return the access control list for the form. Where no form-specific 430 policy is specified, the page's ACL will be returned. 431 """ 432 433 if self.attributes.has_key("access"): 434 access = self.attributes["access"] 435 return security.AccessControlList(self.request.cfg, [access]) 436 else: 437 return Page(self.request, self.pagename).getACL(self.request) 438 439 def getSubpageACL(self): 440 441 """ 442 Return the access control list for the form for data that will be 443 stored in subpages. Where no form-specific policy is specified, the 444 page's ACL will be used as the basis of the subpage ACL. 445 """ 446 447 cfg = self.request.cfg 448 449 acl = self.getACL() 450 new_acl_lines = [] 451 452 for acl_str in acl.acl_lines: 453 new_acl_line = [] 454 455 for op, users, rights in security.ACLStringIterator(cfg.acl_rights_valid, acl_str): 456 457 # Remove "read" rights unless the "admin" right is also present. 458 459 if op != "-" and "read" in rights and not "admin" in rights: 460 rights.remove("read") 461 462 # Add "read" rights if absent and "admin" is present. 463 464 elif op != "-" and not "read" in rights and "admin" in rights: 465 rights.append("read") 466 467 new_acl_line.append((op, users, rights)) 468 469 new_acl_lines.append(" ".join([ 470 "%s%s:%s" % (op, ",".join(users), ",".join(rights)) for (op, users, rights) in new_acl_line 471 ])) 472 473 # Add an extra read-disable rule just to make sure. 474 475 new_acl_lines.append("-All:read") 476 477 return security.AccessControlList(cfg, new_acl_lines) 478 479 def checkPermissions(self, action): 480 481 """ 482 Check the permissions of the user against any restrictions specified in 483 the form's 'attributes'. 484 """ 485 486 user = self.request.user 487 488 # Use the access definition if one is given. 489 490 if self.attributes.has_key("access"): 491 access = self.attributes["access"] 492 acl = security.AccessControlList(self.request.cfg, [access]) 493 policy = lambda request, pagename, username, action: acl.may(request, username, action) 494 495 # Otherwise, use the page permissions. 496 497 else: 498 policy = security._check 499 500 # The "read" action is only satisfied by the "admin" role. 501 502 return user and ( 503 action != "read" and policy(self.request, self.pagename, user.name, action) or 504 action == "read" and policy(self.request, self.pagename, user.name, "admin") 505 ) 506 507 class FormStore(ItemStoreBase): 508 509 "A form-specific storage mechanism." 510 511 def __init__(self, handler): 512 513 "Initialise the store with the form 'handler'." 514 515 self.handler = handler 516 page = Page(handler.request, handler.pagename) 517 fragment = handler.attributes.get("fragment") 518 suffix = fragment and ("_%s" % fragment) or "" 519 formdir = wikiutil.quoteWikinameFS("form%s" % suffix) 520 lockdir = wikiutil.quoteWikinameFS("lock%s" % suffix) 521 522 # Use an alternative store type if indicated. 523 524 self.storetype = handler.attributes.get("storetype") 525 if self.storetype == "subpage": 526 store = getSubpageItemStoreForPage(page, "form_locks/%s" % lockdir) 527 else: 528 store = getDirectoryItemStoreForPage(page, "forms/%s" % formdir, "form_locks/%s" % lockdir) 529 ItemStoreBase.__init__(self, page, store) 530 531 def can_write(self): 532 533 """ 534 Permit writing of form data using the form attributes or page 535 permissions. 536 """ 537 538 return self.handler.checkPermissions("write") 539 540 def can_read(self): 541 542 """ 543 Permit reading of form data using the form attributes or page 544 permissions. 545 """ 546 547 return self.handler.checkPermissions("read") 548 549 def append(self, item): 550 551 "Append the given 'item' to the store." 552 553 if self.storetype == "subpage": 554 555 # Add an ACL to restrict direct access to subpages. 556 557 request = self.page.request 558 acl = self.handler.getSubpageACL() 559 item = acl.getString() + item 560 561 # Add a format header to the page for parsers to use. 562 563 item = "#FORMAT formdata\n" + item 564 565 ItemStoreBase.append(self, item) 566 567 def __getitem__(self, number): 568 569 "Return the item for the given 'number'." 570 571 body = ItemStoreBase.__getitem__(self, number) 572 573 if self.storetype == "subpage": 574 575 # Remove any page directives. 576 577 directives, body = wikiutil.get_processing_instructions(body) 578 579 return body 580 581 # Form and field information. 582 583 def getFormStructure(text, request, path=None, structure=None): 584 585 """ 586 For the given form 'text' and using the 'request', return details of the 587 form for the section at the given 'path' (or the entire form if 'path' is 588 omitted), populating the given 'structure' (or populating a new structure if 589 'structure' is omitted). 590 """ 591 592 if structure is None: 593 structure = {} 594 595 for format, attributes, body in getFragments(text, True): 596 597 # Get field details at the current level. 598 599 if format is None: 600 structure.update(getFormFields(body, path, request)) 601 602 # Where a section is found, get details from within the section. 603 604 elif format == "form": 605 if attributes.has_key("section"): 606 section_name = attributes["section"] 607 section = structure[section_name] = {} 608 getFormStructure(body, request, path and ("%s/%s" % (path, section_name)) or section_name, section) 609 elif attributes.has_key("message"): 610 getFormStructure(body, request, path, structure) 611 elif attributes.has_key("not-message"): 612 getFormStructure(body, request, path, structure) 613 614 # Get field details from other kinds of region. 615 616 elif format != "form": 617 getFormStructure(body, request, path, structure) 618 619 return structure 620 621 def getFormForFragment(text, fragment=None): 622 623 """ 624 Return the form region from the given 'text' for the specified 'fragment'. 625 If no fragment is specified, the first form region is returned. The form 626 region is described using a tuple containing the attributes for the form 627 and the body text of the form. 628 """ 629 630 for format, attributes, body in getFragments(text): 631 if format == "form" and (not fragment or attributes.get("fragment") == fragment): 632 return attributes, body 633 634 return {}, None 635 636 def getFieldArguments(field_definition): 637 638 "Return the parsed arguments from the given 'field_definition' string." 639 640 field_args = {} 641 642 for field_arg in field_definition.split(): 643 if field_arg == "required": 644 field_args[field_arg] = True 645 continue 646 647 # Record the key-value details. 648 649 try: 650 argname, argvalue = field_arg.split("=", 1) 651 field_args[argname] = argvalue 652 653 # Single keywords are interpreted as type descriptions. 654 655 except ValueError: 656 if not field_args.has_key("type"): 657 field_args["type"] = field_arg 658 659 return field_args 660 661 # Common formatting functions. 662 663 def getFormOutput(text, fields, form_fragment=None, path=None, fragment=None, repeating=None, index=None): 664 665 """ 666 Combine regions found in the given 'text' and then return them as a single 667 block. The reason for doing this, as opposed to just passing each region to 668 a suitable parser for formatting, is that form sections may break up 669 regions, and such sections may not define separate subregions but instead 670 act as a means of conditional inclusion of text into an outer region. 671 672 The given 'fields' are used to populate fields provided in forms and to 673 control whether sections are populated or not. 674 675 The optional 'form_fragment' is used to indicate the form to which the 676 fields belong. 677 678 The optional 'path' is used to adjust form fields to refer to the correct 679 part of the form hierarchy. 680 681 The optional 'fragment' is used to indicate the form being output. If this 682 value is different to 'form_fragment', the structure of the form should not 683 be influenced by the 'fields'. 684 685 The optional 'repeating' and 'index' is used to refer to individual values 686 of a designated field. 687 """ 688 689 this_form = fragment and form_fragment == fragment or not fragment and not form_fragment 690 691 output = [] 692 section = fields 693 694 for region in getRegions(text, True): 695 format, attributes, body, header, close = getFragmentFromRegion(region) 696 697 # Adjust any FormField macros to use hierarchical names. 698 699 if format is None: 700 output.append((path or fragment or repeating) and 701 adjustFormFields(body, path, fragment, repeating, index) or body) 702 703 # Include form sections only if fields exist for those sections. 704 705 elif format == "form": 706 section_name = attributes.get("section") 707 message_name = attributes.get("message") 708 absent_message_name = attributes.get("not-message") 709 710 # Ignore sections not related to the supplied field data. 711 712 if not this_form: 713 pass 714 715 # Sections are groups of fields in their own namespace. 716 717 elif section_name and section.has_key(section_name): 718 719 # Iterate over the section contents ignoring the given indexes. 720 721 for index, element in enumerate(getSectionElements(section[section_name])): 722 element_ref = "%s$%s" % (section_name, index) 723 724 # Get the output for the section. 725 726 output.append(getFormOutput(body, element, form_fragment, 727 path and ("%s/%s" % (path, element_ref)) or element_ref, fragment)) 728 729 # Message regions are conditional on a particular field and 730 # reference the current namespace. 731 732 elif message_name and section.has_key(message_name): 733 734 if attributes.get("repeating"): 735 for index in range(0, len(section[message_name])): 736 output.append(getFormOutput(body, section, form_fragment, path, fragment, message_name, index)) 737 else: 738 output.append(getFormOutput(body, section, form_fragment, path, fragment)) 739 740 # Not-message regions are conditional on a particular field being 741 # absent. They reference the current namespace. 742 743 elif absent_message_name and not section.has_key(absent_message_name): 744 output.append(getFormOutput(body, section, form_fragment, path, fragment)) 745 746 # Inspect and include other regions. 747 748 else: 749 output.append(header) 750 output.append(getFormOutput(body, section, form_fragment, path, fragment, repeating, index)) 751 output.append(close) 752 753 return "".join(output) 754 755 def getFormFields(body, path, request): 756 757 "Return a dictionary of fields from the given 'body' at the given 'path'." 758 759 fields = {} 760 cache = {} 761 type = None 762 763 for i, match in enumerate(form_field_regexp.split(body)): 764 state = i % 3 765 766 if state == 1: 767 type = match 768 elif state == 2 and type == "Field": 769 args = {} 770 771 # Obtain the macro arguments, adjusted to consider the path. 772 773 name, path, dictpage, label, section, fragment = \ 774 getMacroArguments(adjustMacroArguments(parseMacroArguments(match), path)) 775 776 # Obtain field information from the cache, if possible. 777 778 cache_key = (name, dictpage) 779 780 if cache.has_key(cache_key): 781 field_args, allowed_values = cache[cache_key] 782 783 # Otherwise, obtain field information from any WikiDict. 784 785 else: 786 field_args = {} 787 allowed_values = None 788 789 if dictpage: 790 wikidict = getWikiDict(dictpage, request) 791 if wikidict: 792 field_definition = wikidict.get(name) 793 if field_definition: 794 field_args = getFieldArguments(field_definition) 795 if field_args.has_key("source"): 796 sourcedict = getWikiDict(field_args["source"], request) 797 if sourcedict: 798 allowed_values = sourcedict.keys() 799 800 cache[cache_key] = field_args, allowed_values 801 802 # Store the field information. 803 804 fields[name] = path, dictpage, label, section, field_args, allowed_values 805 806 return fields 807 808 def adjustFormFields(body, path, fragment, repeating=None, index=None): 809 810 """ 811 Return a version of the 'body' with the names in FormField macros updated to 812 incorporate the given 'path' and 'fragment'. If 'repeating' is specified, 813 any field with such a name will be adjusted to reference the value with the 814 given 'index'. 815 """ 816 817 result = [] 818 type = None 819 820 for i, match in enumerate(form_field_regexp.split(body)): 821 state = i % 3 822 823 # Reproduce normal text as is. 824 825 if state == 0: 826 result.append(match) 827 828 # Capture the macro type. 829 830 elif state == 1: 831 type = match 832 833 # Substitute the macro and modified arguments. 834 835 else: 836 result.append("<<Form%s(%s)>>" % (type, 837 quoteMacroArguments( 838 adjustMacroArguments( 839 parseMacroArguments(match), path, fragment, repeating, index 840 ) 841 ) 842 )) 843 844 return "".join(result) 845 846 def adjustMacroArguments(args, path, fragment=None, repeating=None, index=None): 847 848 """ 849 Adjust the given 'args' so that the path incorporates the given 850 'path' and 'fragment', returning a new list containing the revised path, 851 fragment and remaining arguments. If 'repeating' is specified, any field 852 with such a name will be adjusted to reference the value with the given 853 'index'. 854 """ 855 856 if not path and not fragment and not repeating: 857 return args 858 859 result = [] 860 old_path = None 861 found_name = None 862 863 for name, value in args: 864 if name == "path": 865 old_path = value 866 elif name == "fragment" and fragment: 867 pass 868 else: 869 result.append((name, value)) 870 871 # Remember any explicitly given name or where a keyword appears. 872 873 if name == "name" or name is None and found_name is None: 874 found_name = value 875 876 if path: 877 qualified = old_path and ("%s/%s" % (old_path, path)) or path 878 result.append(("path", qualified)) 879 880 if fragment: 881 result.append(("fragment", fragment)) 882 883 if repeating and repeating == found_name: 884 result.append(("index", index)) 885 886 return result 887 888 def getMacroArguments(parsed_args): 889 890 "Return the macro arguments decoded from 'parsed_args'." 891 892 found_name = None 893 path = None 894 dictpage = None 895 label = None 896 section = None 897 fragment = None 898 899 for name, value in parsed_args: 900 if name == "name": 901 found_name = value 902 903 elif name == "path": 904 path = value 905 906 elif name == "dict": 907 dictpage = value 908 909 elif name == "label": 910 label = value 911 912 elif name == "section": 913 section = value 914 915 elif name == "fragment": 916 fragment = value 917 918 # Keywords are interpreted as certain kinds of values. 919 920 elif name is None: 921 if found_name is None: 922 found_name = value 923 924 elif dictpage is None: 925 dictpage = value 926 927 return found_name, path, dictpage, label, section, fragment 928 929 def getFields(d, remove=False): 930 931 """ 932 Return the form fields hierarchy for the given dictionary 'd'. If the 933 optional 'remove' parameter is set to a true value, remove the entries for 934 the fields from 'd'. 935 """ 936 937 fields = {} 938 939 for key, value in d.items(): 940 941 # Detect modifying fields. 942 943 if key.find("=") != -1: 944 fields[key] = value 945 if remove: 946 del d[key] 947 continue 948 949 # Reproduce the original hierarchy of the fields. 950 951 section = fields 952 parts = getPathDetails(key) 953 954 for name, index in parts[:-1]: 955 956 # Add an entry for instances of the section. 957 958 if not section.has_key(name): 959 section[name] = {} 960 961 # Add an entry for the specific instance of the section. 962 963 if not section[name].has_key(index): 964 section[name][index] = {} 965 966 section = section[name][index] 967 968 section[parts[-1][0]] = value 969 970 if remove: 971 del d[key] 972 973 return fields 974 975 def getPathDetails(path): 976 977 """ 978 Return the given 'path' as a list of (name, index) tuples providing details 979 of section instances, with any specific field appearing as the last element 980 and having the form (name, None). 981 """ 982 983 parts = [] 984 985 for part in path.split("/"): 986 try: 987 name, index = part.split("$", 1) 988 index = int(index) 989 except ValueError: 990 name, index = part, None 991 992 parts.append((name, index)) 993 994 return parts 995 996 def getSectionForPath(path, fields): 997 998 """ 999 Obtain the section indicated by the given 'path' from the 'fields', 1000 returning a tuple of the form (parent section, (name, index)), where the 1001 parent section contains the referenced section, where name is the name of 1002 the referenced section, and where index, if not None, is the index of a 1003 specific section instance within the named section. 1004 """ 1005 1006 parts = getPathDetails(path) 1007 section = fields 1008 1009 for name, index in parts[:-1]: 1010 section = fields[name][index] 1011 1012 return section, parts[-1] 1013 1014 def getSectionElements(section_elements): 1015 1016 "Return the given 'section_elements' as an ordered collection." 1017 1018 keys = map(int, section_elements.keys()) 1019 keys.sort() 1020 1021 elements = [] 1022 1023 for key in keys: 1024 elements.append(section_elements[key]) 1025 1026 return elements 1027 1028 # Parser-related formatting functions. 1029 1030 def formatForm(text, request, fmt, attrs=None, write=None): 1031 1032 """ 1033 Format the given 'text' using the specified 'request' and formatter 'fmt'. 1034 The optional 'attrs' can be used to control the presentation of the form. 1035 1036 If the 'write' parameter is specified, use it to write output; otherwise, 1037 write output using the request. 1038 """ 1039 1040 write = write or request.write 1041 page = request.page 1042 1043 form = get_form(request) 1044 form_fragment = form.get("fragment", [None])[0] 1045 fields = getFields(form) 1046 1047 # Prepare the query string for the form action URL. 1048 1049 queryparams = [] 1050 1051 for argname, default in [("fragment", None), ("action", "MoinFormHandler")]: 1052 if attrs and attrs.has_key(argname): 1053 queryparams.append("%s=%s" % (argname, attrs[argname])) 1054 elif default: 1055 queryparams.append("%s=%s" % (argname, default)) 1056 1057 querystr = "&".join(queryparams) 1058 fragment = attrs.get("fragment") 1059 1060 write(fmt.rawHTML('<form method="post" action="%s%s"%s>' % ( 1061 escattr(page.url(request, querystr)), 1062 fragment and ("#%s" % escattr(fragment)) or "", 1063 fragment and (' id="%s"' % escattr(fragment)) or "" 1064 ))) 1065 1066 # Obtain page text for the form, incorporating subregions and applicable 1067 # sections. 1068 1069 output = getFormOutput(text, fields, form_fragment=form_fragment, fragment=fragment) 1070 write(formatText(output, request, fmt, inhibit_p=False)) 1071 1072 write(fmt.rawHTML('</form>')) 1073 1074 def formatFormForOutputType(text, request, mimetype, attrs=None, write=None): 1075 1076 """ 1077 Format the given 'text' using the specified 'request' for the given output 1078 'mimetype'. 1079 1080 The optional 'attrs' can be used to control the presentation of the form. 1081 1082 If the 'write' parameter is specified, use it to write output; otherwise, 1083 write output using the request. 1084 """ 1085 1086 write = write or request.write 1087 1088 if mimetype == "text/html": 1089 write('<html>') 1090 write('<body>') 1091 fmt = request.html_formatter 1092 fmt.setPage(request.page) 1093 formatForm(text, request, fmt, attrs, write) 1094 write('</body>') 1095 write('</html>') 1096 1097 # vim: tabstop=4 expandtab shiftwidth=4