About structured editing ======================== Terminology ----------- config - structure of the user interface configuration template(s) - skeleton(s) of the output file(s) data - file that contains the data chosen by the user export(s) - file(s) actually written out by tabea. import - legacy (manually edited) file to import data from Files ----- - config Menu structure configuration of the user interface. Described in more detail below under "Syntax". - template(s) Any ASCII file using lib_var syntax for variable expansion. - data A single file containing all data chosen by the user. Format is one record per logical line which a name followed by an (optional) value. A physical line be extended to a long logical line using a trailing BACKSLASH just before the NEWLINE. Fields are separated by SPACE. - export(s) One or more files being written out by tabea. It is/they are build from template(s) by expanding the embedded variables using lib_var and information from the data file. The output files are usually write-only. - import A function may be provided to build a baseline of choices importing a legacy file. This is typically the case when someone has maintained such a file manually before it was taken under tabea control. Syntax ------ The configuration consists of one or more "blocks" which are arbitrary collections of items. Every block consists of one or more items describing the technical and visible aspects of a single variable whose value is stored in the data file. These values are also used when lib_var expands the variables in template(s) for export. Every item has one or more values describing the technical and visible aspects of a user's choices to be assigned to an item. block { name (mandatory) visible description helptext helpurl activate item { name (mandatory) visible description helptext helpurl activate match verify type value { name (mandatory) visible description helptext helpurl activate checked selected }; }; }; Elements common to blocks, items and values ------------------------------------------- A name matches [a-zA-Z][0-9a-zA-Z]* and is used when reading and writing the data file and when expanding variables in the template(s) for export. A visible description for humans can be specified. If omited, name is used verbatim. A helptext is a human readable text for the word "help". If omitted and "helpurl" is set, it defaults to "help". If "helpurl" is omitted no help link is shown at all. The helpurl is the URL to a help page describing the item. An activate condition can be specified to enable a block, item or value. It is a little perl program and uses perl's "eval" to find out the result Elements used in items only --------------------------- A match regex is used when importing a legacy file. When parts of an imported file match the regex, $1 is used as the current value. The verify is a regex to check if the data read or the user input is valid. A "verify xxx" is a shortcut for "eval name =~ xxx". If used together with eval both must match. The eval is a little perl program and uses perl's "eval" to find out the result. It is a more sophisticated method to check for valid values. If used together with verify both must match. The type field is explained below under "All types by example". Elements used in values only ---------------------------- A name describes the default value. The checked element is a flag to highlight the value in the menu. The select element is a flag to highlight the value in the menu. All types by example -------------------- Possible types are taken from RFC1866, Section 8.1.2 - 8.1.4 8.1.2.1. Text Field: INPUT TYPE=TEXT

Street Address:
Postal City code:
Zip Code:
item { name street visible "Street Address:" type text } item { name city visible "Postal City code:" type text[16][16] } item { name zip visible "Zip Code:" type text[10][10] value { name "99999-9999" } } 8.1.2.2. Password Field: INPUT TYPE=PASSWORD

Name: Password: item { name login visible "Name:" type text } item { name passwd visible "Password:" type password } 8.1.2.3. Check Box: INPUT TYPE=CHECKBOX

What flavors do you like? Vanilla
Strawberry
Chocolate
item { name flavor visible "What flavors do you like?" type checkbox value { name vanilla visible "Vanilla" checked } value { name strawberry visible "Strawberry" } value { name chocolate visible "Chocolate" checked } } 8.1.2.4. Radio Button: INPUT TYPE=RADIO

Which is your favorite? Vanilla
Strawberry
Chocolate
item { name flavor visible "Which is your favorite?" type radio value { name vanilla visible "Vanilla" checked } value { name strawberry visible "Strawberry" } value { name chocolate visible "Chocolate" } } 8.1.2.5. Image Pixel: INPUT TYPE=IMAGE In a form this type allows an image to be used as a substitution for a submit button. \"gif item { name point visible "Choose a point on the map:" type image value { name "map.gif" } } 8.1.2.6. Hidden Field: INPUT TYPE=HIDDEN (FIXME do we really need this?) item { name context type hidden value { name "l2k3j4l2k3j4l2k3j4lk23" } } 8.1.2.7. Submit Button: INPUT TYPE=SUBMIT
item { name recipient type submit value { name internal } value { name world } } 8.1.2.8. Reset Button: INPUT TYPE=RESET You may clear the form and start over at any time: item { description "You may clear the form and start over at any time:" type reset } 8.1.3. Selection: SELECT 8.1.3.1. Option: OPTION item { name flavor type select[2] //or type selectmultipe[2] value { name "Vanilla" } value { name "Strawberry" } value { name RumRasin visible "Rum and Raisin" } value { name "Peach and Orange" selected } } 8.1.4. Text Area: TEXTAREA item { name "address" type textarea[64][6] value { name "HaL Computer Systems\n1315 Dell Avenue\nCampbell, California 95008" } } Perl API -------- $handle = new Tabea::Config ($config, $type, $prefix); Reads $config and initializes items with default values. Currently the only supported $type is "HTML2" which causes only tags described in RFC1866 to be used. When a menu is rendered, some elements may require uniqe names in a scope wider than known by this module, i.e. when two configs are presented on one screen or along with other information, so every item is prefixed with $prefix. $rc = $handle->load($data); Reads $data and sets the value of items. $rc = $handle->save($data); Writes items including their values. $rc = $handle->render($buffer); Renders a menu and writes it into the buffer. The contents of the buffer can be merged into a larger output. Values are verified and invalid data is marked (FIXME how? red, reset to default, configurable behaviour, configurable error messages ...) The caller must finally print out the menu and when an input comes back it must identify menu activity (i.e. by checking the prefix) and call render again and again or execute some action. $rc = $handle->import($legacyfile); Reads a legacy (manually edited or previously exported) file and tries to match out values. $rc = $handle->export($template, $exportfile); Applies variable substitution for a template and writes the result out to exportfile. $rc = $handle->destroy(); Issues: - [rse] $type should be not on constructor, but on render() IMHO - [ps] Agree. I think it is better to have the data in an internal format and reformat the data at the time when the output is produced. - [rse] $prefix should be not on constructor, but on render() IMHO - [ps] Perhaps I need $prefix for internal data saving. [thl] agree on both. That came into my mind first but i thought a switch between two types while the program is running will happen almost never and having it fixed during the lifecycle of the constructor might easy coding. - [rse] if thinking about programming a CGI, it is not clear to me how one has to process the POST data and inject it again into the $handle object for re-rendering. I would expect the reverse of render() for loading in the POST results. I recommend: $rc = $handle->parse($type, $post_data); $rc = $handle->render($type, $html_data); - [ps] We must get the value POST data for updating the internal data The idea of having the functions parse and render sounds good. This way we get - load/save for internal/backend I/O - parse/render for input-device-specific I/O - import/export for output-device-specific I/O [thl] agree. I was thinking about render() being a hybrid function. Note that the main program's "outer loop" will - must - preprocess POST data because it might contain information unrelated to render() or parse(). But i agree there are definitly two functionalities so it shouldn't hurt putting them in two separate functions. In fact, it might remove some magic. Example: Generic RAID configuration ----------------------------------- block { name BEGIN visible "RAID configurator" description "Please configure your redundant array of independent disks now" helptext info helpurl http://www.raid.org activate #always item { name numdrives visible "Number of drives [1...9]" description "enter the total number of drives in the array including redundant and spare drives" helptext ? helpurl http://www.counting.org/basics activate #always type text[1] defaultvalue 1 verify ^[123456789]$ eval $numdrives ~= ^[123456789]$ } item { name raidlevel activate numdrives~=. type radio defaultvalue 0 value { name 0 visible "RAID 0" } value { name 1 activate numdrives~=^[2468]$ #even } value { name 4 activate numdrives~=^[3456789]$ #>=3 } value { name 5 activate numdrives~=^[3456789]$ #>=3 } value { name 10 activate numdrives~=^[48]$ #multiple of 4 } value { name 1+HOT activate numdrives~=^[3579]$ #odd AND >=3 } value { name 4+HOT activate numdrives~=^[456789]$ #>=4 } value { name 5+HOT activate numdrives~=^[456789]$ #>=4 } } } block { name END visible "commit suicide now" activate numdrives~=. && raidlevel~=^[0145](+HOT)?$ item { name "Finish" type button } } Example: Jumpstart Disk and Raid Configuration ---------------------------------------------- block { name BEGIN visible "Disk Configuration" description "Please configure the number of disks and the RAID" helptext info helpurl activate #always item { name numdrives visible "Number of drives [1...9]" description "enter the total number of drives in the array including redundant and spare drives" helptext ? helpurl http://www.counting.org/basics activate #always type text[1] (type popup_menu[1] ) defaultvalue 1 verify ^[12]$ } item { name raidlevel activate numdrives~=. type radio defaultvalue 0 item value { name 0 visible "RAID 0" } value { name 1 activate numdrives~=^[2]$ #even } } item { name sizeofdisk } item { name slices visible 'Slice definition' item { name slice0 visible "Definition of slice0" item { name size visible "Define the size of sclice" verify sizeofdisk - size0 - size1 ..... -size7 >= 0 } item { name moutpoint visible "Define the mount point of slice" type popup_menu[7] # Dependencies to other slice types verify # Depencies to other slices } } item { name slice1 visible "Definition of slice1" item { name size visible "Define the size of sclice" } item { name moutpoint visible "Define the mount point of slice" type popup_menu[7] # Dependencies to other slice types verify # } } item { name slice2 visible "Definition of slice2" item { name size visible "Define the size of sclice" } item { name moutpoint visible "Define the mount point of slice" type popup_menu[7] # Dependencies to other slice types verify # } } item { name slice3 visible "Definition of slice3" item { name size visible "Define the size of sclice" } item { name moutpoint visible "Define the mount point of slice" type popup_menu[7] # Dependencies to other slice types verify # } } item { name slice4 visible "Definition of slice4" item { name size visible "Define the size of sclice" } item { name moutpoint visible "Define the mount point of slice" type popup_menu[7] # Dependencies to other slice types verify # } } item { name slice5 visible "Definition of slice5" item { name size visible "Define the size of sclice" } item { name moutpoint visible "Define the mount point of slice" type popup_menu[7] # Dependencies to other slice types verify # } } item { name slice6 visible "Definition of slice6" item { name size visible "Define the size of sclice" } item { name moutpoint visible "Define the mount point of slice" type popup_menu[7] # Dependencies to other slice types verify # } } item { name slice7 visible "Definition of slice7" item { name size visible "Define the size of sclice" } item { name moutpoint visible "Define the mount point of slice" type popup_menu[7] # Dependencies to other slice types verify # } } } } block { name pruefen type submit } block { name clean type reset } block { name END visible "commit suicide now" activate numdrives~=. && raidlevel~=^[0145](+HOT)?$ item { name "Finish" type button } }