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