# HG changeset patch # User Paul Boddie # Date 1354146831 -3600 # Node ID 17a538df78259f9d45e7d571bb508890dee226c5 An extension, macro and parser for incorporating HTML forms in Wiki pages. diff -r 000000000000 -r 17a538df7825 MoinForms.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MoinForms.py Thu Nov 29 00:53:51 2012 +0100 @@ -0,0 +1,145 @@ +# -*- coding: iso-8859-1 -*- +""" + MoinMoin - MoinForms library + + @copyright: 2012 by Paul Boddie + @license: GNU GPL (v2 or later), see COPYING.txt for details. +""" + +from MoinMoin import wikiutil +from StringIO import StringIO +from MoinSupport import * +import re + +__version__ = "0.1" + +# Common formatting functions. + +def formatForm(text, request, fmt, attrs=None, write=None): + + """ + Format the given 'text' using the specified 'request' and formatter 'fmt'. + The optional 'attrs' can be used to control the presentation of the form. + + If the 'write' parameter is specified, use it to write output; otherwise, + write output using the request. + """ + + write = write or request.write + page = request.page + + fields = getFields(get_form(request)) + + querystr = attrs and attrs.has_key("action") and ("action=%s" % attrs["action"]) or None + + write(fmt.rawHTML('
' % + escattr(page.url(request, querystr)) + )) + + output = getFormOutput(text, fields) + write(formatText(output, request, fmt)) + + write(fmt.rawHTML('
')) + +def getFormOutput(text, fields): + + """ + Combine regions found in the given 'text' and then return them as a single + block. The reason for doing this, as opposed to just passing each region to + a suitable parser for formatting, is that form sections may break up + regions, and such sections may not define separate subregions but instead + act as a means of conditional inclusion of text into an outer region. + + The given 'fields' are used to populate fields provided in forms and to + control whether sections are populated or not. + """ + + output = [] + section = fields + + for region in getRegions(text, True): + format, attributes, body, header, close = getFragmentFromRegion(region) + + # NOTE: Need to adjust FormField macros to use hierarchical names. + + # Include bare regions as they are. + + if format is None: + output.append(region) + + # Include form sections only if fields exist for those sections. + + elif format == "form": + section_name = attributes.get("section") + if section_name and section.has_key(section_name): + output.append(header) + output.append(getFormOutput(body, section[section_name])) + output.append(close) + + # Inspect and include other regions. + + else: + output.append(header) + output.append(getFormOutput(body, section)) + output.append(close) + + return "".join(output) + +def getFields(d): + + """ + Return the form fields hierarchy for the given dictionary 'd'. + """ + + fields = {} + + for key, value in d.items(): + + # Reproduce the original hierarchy of the fields. + + section = fields + parts = key.split("/") + + for part in parts[:-1]: + try: + name, index = part.split("$", 1) + index = int(index) + except ValueError: + name, index = part, None + + if not section.has_key(name): + section[name] = {} + + if not section[name].has_key(index): + section[name][index] = {} + + section = section[name][index] + + section[parts[-1]] = value + + return fields + +def formatFormForOutputType(text, request, mimetype, attrs=None, write=None): + + """ + Format the given 'text' using the specified 'request' for the given output + 'mimetype'. + + The optional 'attrs' can be used to control the presentation of the form. + + If the 'write' parameter is specified, use it to write output; otherwise, + write output using the request. + """ + + write = write or request.write + + if mimetype == "text/html": + write('') + write('') + fmt = request.html_formatter + fmt.setPage(request.page) + formatForm(text, request, fmt, attrs, write) + write('') + write('') + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r 000000000000 -r 17a538df7825 macros/FormField.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/macros/FormField.py Thu Nov 29 00:53:51 2012 +0100 @@ -0,0 +1,147 @@ +# -*- coding: iso-8859-1 -*- +""" + MoinMoin - FormField Macro + + @copyright: 2012 by Paul Boddie + @license: GNU GPL (v2 or later), see COPYING.txt for details. +""" + +from MoinMoin import wikiutil +from MoinSupport import * + +Dependencies = ['pages'] + +escape = wikiutil.escape + +# Macro functions. + +def execute(macro, args): + + """ + Execute the 'macro' with the given 'args' to produce a form field element: + + * A field name + * The WikiDict describing form fields + + The following optional named arguments are also supported: + + label=TEXT The label employed by button-like fields + + The nature of each field is described by a WikiDict entry for the given + field name. + """ + + request = macro.request + fmt = macro.formatter + page = fmt.page + _ = request.getText + + # Interpret the arguments. + # NOTE: The argument parsing should really be more powerful in order to + # NOTE: support labels. + + try: + parsed_args = args and wikiutil.parse_quoted_separated(args, name_value=False) or [] + except AttributeError: + parsed_args = args.split(",") + + parsed_args = [arg for arg in parsed_args if arg] + + # Get special arguments. + + name = None + dictpage = None + label = None + + for arg in parsed_args: + if arg.startswith("name="): + name = arg[5:] + + elif arg.startswith("dict="): + dictpage = arg[5:] + + elif arg.startswith("label="): + label = arg[6:] + + elif name is None: + name = arg + + elif dictpage is None: + dictpage = arg + + if not name: + return showError(_("No field name specified."), request) + + if not dictpage: + return showError(_("No WikiDict specified."), request) + + # Get the WikiDict and the field's definition. + + wikidict = getWikiDict(dictpage, request) + + if not wikidict: + return showError(_("WikiDict %s cannot be loaded.") % dictpage, request) + + try: + field_definition = wikidict[name] + except KeyError: + return showError(_("No entry for %s in %s.") % (name, dictpage), request) + + field_args = {} + + for field_arg in field_definition.split(): + + # Record the key-value details. + + try: + argname, argvalue = field_arg.split("=", 1) + field_args[argname] = argvalue + + # Single keywords are interpreted as type descriptions. + + except ValueError: + if not field_args.has_key("type"): + field_args["type"] = field_arg + + # Obtain any request parameters corresponding to the field. + + form = get_form(request) + value = form.get(name, [""])[0] + + # Render the field. + + type = field_args.get("type", "text") + + if type == "text": + return fmt.rawHTML('' % ( + escattr(name), escattr(type), escattr(field_args.get("size", "10")), escattr(value) + )) + + elif type == "textarea": + return fmt.rawHTML('' % ( + escattr(name), escattr(field_args.get("cols", "60")), escattr(field_args.get("rows", "5")), escape(value) + )) + + elif type == "submit": + return fmt.rawHTML('' % ( + escattr(name), escattr(_(label or name)) + )) + + # NOTE: Support select fields at the very least. + + else: + return '' + +def showError(text, request): + fmt = request.formatter + + output = [] + append = output.append + + append(fmt.span(on=1, attrs={"class" : "form-field-error"})) + append(fmt.text(text)) + append(fmt.span(on=0)) + + return "".join(output) + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r 000000000000 -r 17a538df7825 parsers/form.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/parsers/form.py Thu Nov 29 00:53:51 2012 +0100 @@ -0,0 +1,69 @@ +# -*- coding: iso-8859-1 -*- +""" + MoinMoin - form (MoinForms) + + @copyright: 2012 by Paul Boddie + @license: GNU GPL (v2 or later), see COPYING.txt for details. +""" + +from MoinForms import * +from MoinSupport import * + +Dependencies = ["pages"] + +# Parser support. + +class Parser: + + "Manipulate and display forms." + + Dependencies = Dependencies + extensions = [] + + # Output content types preferred by this parser. + + output_mimetypes = ["text/html"] + + def __init__(self, raw, request, **kw): + + """ + Initialise the parser with the given 'raw' data, 'request' and any + keyword arguments that may have been supplied. + """ + + self.raw = raw + self.request = request + attrs = parseAttributes(kw.get("format_args", ""), False) + + self.fragment = attrs.get("fragment") + + def format(self, fmt, write=None): + + """ + Format an event using the given formatter 'fmt'. If the 'write' + parameter is specified, use it to write output; otherwise, write output + using the request. + """ + + formatForm(self.raw, self.request, fmt, write=write) + + # Extra API methods. + + def formatForOutputType(self, mimetype, write=None): + + """ + Format an event for the given 'mimetype'. If the 'write' parameter is + specified, use it to write output; otherwise, write output using the + request. + """ + + formatFormForOutputType(self.raw, self.request, mimetype, write=write) + + # Class methods. + + def getOutputTypes(self): + return self.output_mimetypes + + getOutputTypes = classmethod(getOutputTypes) + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r 000000000000 -r 17a538df7825 setup.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/setup.py Thu Nov 29 00:53:51 2012 +0100 @@ -0,0 +1,13 @@ +#! /usr/bin/env python + +from distutils.core import setup + +setup( + name = "MoinForms", + description = "Support HTML forms within MoinMoin pages", + author = "Paul Boddie", + author_email = "paul@boddie.org.uk", + url = "http://moinmo.in/ParserMarket/MoinForms", + version = "0.1", + py_modules = ["MoinForms"] + )