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