meta language to define forms

Chris Angelico rosuav at gmail.com
Thu Mar 27 23:02:08 EDT 2014


On Fri, Mar 28, 2014 at 7:56 AM, Sells, Fred
<fred.sells at adventistcare.org> wrote:
> I don't have a lot of time or management support to do something elegant like XML and then parse it, I'm thinking more like
>
> Class  FyFormNumber001(GeneralForm):
>         Section1 = Section(title="Enter Patient Vital Signs")
>                 Question1 = NumberQuestion(title="Enter pulse rate", format="%d3")
>                 Question2 = Dropdown(title="Enter current status")
>                 Question2.choices = [ (1, "Alive and Kicking"), (2, "Comatose"), (3, "Dead"), ...]

Rule of Python: XML is not the answer. XML is the question, and "NO!"
is the answer :)

Your syntax there looks reasonable already. I'd recommend you make it
a flat data file, though, don't try to make it a programming language
- unless you actively need it to be one. Here are a couple of ways you
could format this. Any would be fairly easy to code a parser for.

(I'm guessing your format there should probably be "%3d", if you mean
printf notation. But I'll keep your "%d3" example in the below.)

1) Using indentation for nesting, and a "command, space, args" model:

GeneralForm FyFormNumber001
        Section "Enter Patient Vital Signs"
                NumberQuestion "Enter pulse rate" format="%d3"
                Dropdown "Enter current status"
                        Choice 1 "Alive and Kicking"
                        Choice 2 "Comatose"
                        Choice 3 "Dead"
                        ...

This would be parsed with an algorithm like this:
* Maintain an indentation stack, which starts with a top level entry.
* Read one line. Parse it into three parts: leading indentation, command, args.
* Remove from the indentation stack everything equal to or greater
than this line's indent.
* Send the command to the last entry on your indentation stack, with its args.
* Capture the result of that command and put it on the indentation stack.
* Iterate until end of file.

You'd then have a top-level object that understands a "GeneralForm"
command and returns a form object; the form object understands the
"Section" command and returns a section object; the "Dropdown" object
understands "Choice"; and so on.


2) Rendering the whole thing as JSON:

{"type": "GeneralForm", "FormName": "FyFormNumber001", "children": [
        {"type":"Section", "Title": "Enter Patient Vital Signs", "children": [
                {"type": "NumberQuestion", "title":"Enter pulse rate",
"format":"%d3"},
                {"type": "Dropdown", "title":"Enter current status",
"children": [
                        {"type":"Choice", "value":1, "name":"Alive and
Kicking"},
                        {"type":"Choice", "value":2, "name":"Comatose"},
                        {"type":"Choice", "value":3, "name":"Dead"},
                        ...
                ]}
        ]}
]}

This would be parsed by first converting the JSON into a tree of dicts
and lists, and then at each step, checking the type, and processing
its children. The bulk of the work could be done generically, and it's
easy to add attributes to anything. The cost, though, is that you need
to be a bit verbose about the common attributes.


3) Actual Python code:

FyFormNumber001 = GeneralForm(
        Sections = [Section("Enter Patient Vital Signs", Questions=[
                NumberQuestion("Enter pulse rate", format="%d3"),
                Dropdown("Enter current status",
                        choices=[ (1, "Alive and Kicking"), (2,
"Comatose"), (3, "Dead"), ...]
                ),
        ]),
])

This would be parsed by creating functions or classes named
GeneralForm, Section, NumberQuestion, etc, and having them handle
positional arguments for the obvious things, and keyword args for the
less obvious. (I may have misjudged which is which, but you should
make a design decision about that.) I've used keyword args for
everything that's a list, but that could be done differently if you
choose.


4) Actual Pike code.

This came up in the thread Terry's referring to. Its primary advantage
was that it *already existed* (we were talking about GTK2), where all
the above options do require code. It would look something like this:

object FyFormNumber001=GeneralForm()
        ->add(Section("Enter Patient Vital Signs")
                ->add(NumberQuestion("Enter pulse rate", "%d3"))
                ->add(Dropdown("Enter current status")
                        ->add_choice(1, "Alive and Kicking")
                        ->add_choice(2, "Comatose")
                        ->add_choice(3, "Dead")
                        ...
                )
        )
;

I don't recommend this above the options listed above; but I'd say
it's at least 95% as good as the others, and if you find a way to get
something that looks like this with practically zero effort, I would
strongly advise going with it. (Another advantage of the Pike GTK2
system is that you often want to manipulate the objects post-creation
and pre-insertion into the tree. I don't think you need that
flexibility here, so again, this one is just for comparison.)

If you can, try to go for the first option. It's clean, and it's not
terribly hard to parse. Failing that, the third option isn't too bad,
but I'd advise against going with JSON; one misplaced comma or bracket
and you have a tough debugging problem to solve. You might get that
problem with the Python-code version too, but you have a bit of a
better chance at a readable traceback there.

ChrisA



More information about the Python-list mailing list