1 # -*- coding: iso-8859-1 -*- 2 """ 3 MoinMoin - MoinForms library 4 5 @copyright: 2012 by Paul Boddie <paul@boddie.org.uk> 6 @license: GNU GPL (v2 or later), see COPYING.txt for details. 7 """ 8 9 from MoinMoin import wikiutil 10 from MoinSupport import * 11 import re 12 13 __version__ = "0.1" 14 15 form_field_regexp_str = r"<<FormField\((.*?)\)>>" 16 form_field_regexp = re.compile(form_field_regexp_str, re.DOTALL) 17 18 # Common formatting functions. 19 20 def formatForm(text, request, fmt, attrs=None, write=None): 21 22 """ 23 Format the given 'text' using the specified 'request' and formatter 'fmt'. 24 The optional 'attrs' can be used to control the presentation of the form. 25 26 If the 'write' parameter is specified, use it to write output; otherwise, 27 write output using the request. 28 """ 29 30 write = write or request.write 31 page = request.page 32 33 fields = getFields(get_form(request)) 34 35 queryparams = [] 36 37 for argname in ["fragment", "action"]: 38 if attrs and attrs.has_key(argname): 39 queryparams.append("%s=%s" % (argname, attrs[argname])) 40 41 querystr = "&".join(queryparams) 42 43 write(fmt.rawHTML('<form method="post" action="%s">' % 44 escattr(page.url(request, querystr)) 45 )) 46 47 output = getFormOutput(text, fields) 48 write(formatText(output, request, fmt, inhibit_p=False)) 49 50 write(fmt.rawHTML('</form>')) 51 52 def getFormOutput(text, fields): 53 54 """ 55 Combine regions found in the given 'text' and then return them as a single 56 block. The reason for doing this, as opposed to just passing each region to 57 a suitable parser for formatting, is that form sections may break up 58 regions, and such sections may not define separate subregions but instead 59 act as a means of conditional inclusion of text into an outer region. 60 61 The given 'fields' are used to populate fields provided in forms and to 62 control whether sections are populated or not. 63 """ 64 65 output = [] 66 section = fields 67 68 for region in getRegions(text, True): 69 format, attributes, body, header, close = getFragmentFromRegion(region) 70 71 # Include bare regions as they are. 72 73 if format is None: 74 output.append(region) 75 76 # Include form sections only if fields exist for those sections. 77 78 elif format == "form": 79 section_name = attributes.get("section") 80 if section_name and section.has_key(section_name): 81 82 # Iterate over the section contents ignoring the given indexes. 83 84 for index, element in enumerate(getSectionElements(section[section_name])): 85 86 # Adjust FormField macros to use hierarchical names. 87 88 adjusted_body = adjustFormFields(body, section_name, index) 89 output.append(getFormOutput(adjusted_body, element)) 90 91 # Inspect and include other regions. 92 93 else: 94 output.append(header) 95 output.append(getFormOutput(body, section)) 96 output.append(close) 97 98 return "".join(output) 99 100 def adjustFormFields(body, section_name, index): 101 102 """ 103 Return a version of the 'body' with the names in FormField macros updated to 104 incorporate the given 'section_name' and 'index'. 105 """ 106 107 result = [] 108 in_macro = False 109 110 for match in form_field_regexp.split(body): 111 if not in_macro: 112 result.append(match) 113 else: 114 result.append("<<FormField(%s)>>" % ",".join( 115 adjustMacroArguments(parseMacroArguments(match), section_name, index) 116 )) 117 118 in_macro = not in_macro 119 120 return "".join(result) 121 122 def adjustMacroArguments(args, section_name, index): 123 124 """ 125 Adjust the given 'args' so that the path incorporates the given 126 'section_name' and 'index', returning a new list containing the revised 127 path and remaining arguments. 128 """ 129 130 result = [] 131 path = None 132 133 for arg in args: 134 if arg.startswith("path="): 135 path = arg[5:] 136 else: 137 result.append(arg) 138 139 qualified = "%s%s$%s" % (path and ("%s/" % path) or "", section_name, index) 140 result.append("path=%s" % qualified) 141 142 return result 143 144 def parseMacroArguments(args): 145 146 """ 147 Interpret the arguments. 148 NOTE: The argument parsing should really be more powerful in order to 149 NOTE: support labels. 150 """ 151 152 try: 153 parsed_args = args and wikiutil.parse_quoted_separated(args, name_value=False) or [] 154 except AttributeError: 155 parsed_args = args.split(",") 156 157 return [arg for arg in parsed_args if arg] 158 159 def getFields(d, remove=False): 160 161 """ 162 Return the form fields hierarchy for the given dictionary 'd'. If the 163 optional 'remove' parameter is set to a true value, remove the entries for 164 the fields from 'd'. 165 """ 166 167 fields = {} 168 169 for key, value in d.items(): 170 171 # Detect modifying fields. 172 173 if key.find("=") != -1: 174 fields[key] = value 175 if remove: 176 del d[key] 177 continue 178 179 # Reproduce the original hierarchy of the fields. 180 181 section = fields 182 parts = getPathDetails(key) 183 184 for name, index in parts[:-1]: 185 186 # Add an entry for instances of the section. 187 188 if not section.has_key(name): 189 section[name] = {} 190 191 # Add an entry for the specific instance of the section. 192 193 if not section[name].has_key(index): 194 section[name][index] = {} 195 196 section = section[name][index] 197 198 section[parts[-1][0]] = value 199 200 if remove: 201 del d[key] 202 203 return fields 204 205 def getPathDetails(path): 206 parts = [] 207 208 for part in path.split("/"): 209 try: 210 name, index = part.split("$", 1) 211 index = int(index) 212 except ValueError: 213 name, index = part, None 214 215 parts.append((name, index)) 216 217 return parts 218 219 def getSectionForPath(path, fields): 220 221 """ 222 Obtain the section indicated by the given 'path' from the 'fields', 223 returning a tuple of the form (parent section, (name, index)), where the 224 parent section contains the referenced section, where name is the name of 225 the referenced section, and where index, if not None, is the index of a 226 specific section instance within the named section. 227 """ 228 229 parts = getPathDetails(path) 230 section = fields 231 232 for name, index in parts[:-1]: 233 section = fields[name][index] 234 235 return section, parts[-1] 236 237 def getSectionElements(section_elements): 238 239 "Return the given 'section_elements' as an ordered collection." 240 241 keys = map(int, section_elements.keys()) 242 keys.sort() 243 244 elements = [] 245 246 for key in keys: 247 elements.append(section_elements[key]) 248 249 return elements 250 251 def formatFormForOutputType(text, request, mimetype, attrs=None, write=None): 252 253 """ 254 Format the given 'text' using the specified 'request' for the given output 255 'mimetype'. 256 257 The optional 'attrs' can be used to control the presentation of the form. 258 259 If the 'write' parameter is specified, use it to write output; otherwise, 260 write output using the request. 261 """ 262 263 write = write or request.write 264 265 if mimetype == "text/html": 266 write('<html>') 267 write('<body>') 268 fmt = request.html_formatter 269 fmt.setPage(request.page) 270 formatForm(text, request, fmt, attrs, write) 271 write('</body>') 272 write('</html>') 273 274 # vim: tabstop=4 expandtab shiftwidth=4