C. M. Sperberg-McQueen
Initial description 4 April 2018
Note October 2022: this document is out of date.
This document describes a design problem faced in the development (and again in the revision) of the XForms-based editor for TLRR trials data known as Dexter. It was begun as email to a friend under the subject line “cardboard system designer needed” and it assumes a certain amount of technical knowledge and patience with jargon and half-explained concepts. (The reference in the subject line is to the cardboard programmers to whom or which many programmers find it helpful to describe problems they are encountering: by the time one has finished describing the problem clearly, the solution is often clear without the cardboard programmer having to say a word. Some programmers report good results with plush toys.
I'm rewriting the XForms editor for trials from push style to pull style, and seem to be running into a problem.
By push and pull style, I mean roughly what people mean when they use those terms to describe XSLT stylesheets. The pull style editor has the structure
if not(NB) then { button to add NB ) if (NB) then { display NB, button to edit } { display date, button to edit } if not(ccGrp) then { button to add ccGrp } if (ccGrp) then { display ccGrp, button to edit } if not(defGrp) then { button to add defGrp } if (defGrp) then { display defGrp, button to edit } if not(ppGrp ) then { button to add ppGrp } if (ppGrp ) then { display ppGrp , button to edit } ...
etc. The "if" construct here has no 'else' because if (select) { X } is shorthand for <xf:group ref="select"> X </xf:group>
The push style editor has the structure
for $n in (child::* | */*) do if (NB could precede . immediately and not(../NB)) then { offer to add NB } if (date could precede . immediately and not(../date)) then { offer to add ccGrp } if (ccGrp could precede . immediately and not(../ccGrp)) then { offer to add ccGrp } if (defGrp could precede . immediately and not(../defGrp)) then { offer to add defGrp } if (ppGrp could precede . immediately and not(../ppGrp) then { offer to add ppGrp } if (self::NB) then { display NB, button to edit } if (self::date) then { display date, button to edit } if (self::ccGrp) then { display ccGrp, button to edit } if (self::defGrp) then { display defGrp, button to edit } if (self::ppGrp) then { display ppGrp, button to edit } ... if (last node in repeat) then do if (ccGrp could follow . immediately and not(../ccGrp)) then { offer to add ccGrp } if (defGrp could follow . immediately and not(../defGrp)) then { offer to add defGrp } if (ppGrp could follow . immediately and not(../ppGrp) then { offer to add ppGrp } ... end do end do
The editor started out as a pull-style editor, but I could not figure out how to make it deal with variation in order of the top-level fields. The DTD for the initial version of the data included the following content model entity for trial, needed to deal with the variation seen in the records.
<!--* To cover the data, we introduce the following changes: * ix before date (59) or defGrp (trials 159, 383) * witGrp before jurGrp (trial 61) * advGrp without defGrp or ppGrp or partiesGrp (several: * (85, 145, 150, 359, 372, 373, 374) * allow a witGrp to precede ppGrp (trials 84, 271) * (determinism issues!) * allow partiesGrp, advGrp to repeat (366) * allow witGrp, advGrp (trial 372) *--> <!ENTITY % trials-lax ' (ix?, NB?, date?, ccGrp?, ( ( (defGrp, advGrp?, (ppGrp, advGrp?)? ) | (ppGrp, advGrp?) ) | (partiesGrp, advGrp?)+ | advGrp) ?, magGrp?, ( (jurGrp, witGrp?) | (witGrp, (jurGrp | advGrp)?) )?, other?, (outcome, other?)?, sources) '>
So the tests given above as "if GI1 can immediately precede GI2" are all kind of complicated. The evaluation of those tests (which must happen for every child and grandchild of trial, and must be repeated every time the data changes) was the single greatest cost in time of the editor, when I looked at it some time ago. At that time, I made varous changes to speed it up by nesting tests (e.g. trying to avoid repeatedly testing the same thing as part of different compound tests), but Alain Couthures' advice was simply "use a fixed order and pull the elements in that order".
However, the sequence of fields seemed to be part of the information in the record, and I was determined not to force the project into a rigid order for my convenience.
Over a period of time, it became clear that the sequence of fields WAS part of the information, and that for the most part that information could be better captured in the markup. The introduction says records list defendants, prosecutors or plaintiffs, advocates (etc.), in that order; when an advocate is listed immediately after the defendant and before the plaintiff or prosecutor, it's to signal that that is the advocate for the defense. Ditto for witnesses etc. Moving advocates into the defendant group when appropriate, and similar changes, simplified the order a great deal. A few variations in sequence proved to be just unintentional inconsistencies. We now have a more nearly fixed order:
NB?, date, ccGrp?, ((defGrp?, ppGrp?) | (partiesGrp, partiesGrp?) | advGrp), magGrp?, jurGrp?, witGrp?, other? (outcome, other?)?, sources, description, unmatched-index-entries, revisionHistory
There is still some oddity related to 'other', but this is suitable for a pull-style editor.
I hope that moving to a pull-style editor will improve performance.
However, there is a snag.
Every top-level element is treated using the following outline:
display buttons for adding potential immediately-preceding sibling elements that are not already present in the trial
display a heading for the field
display an XForms case construct with two cases:
Here, item 1 is replaced in pull style by an 'Add X' button for each possible top-level field X that is not present. The buttons have a fixed order and all is well.
Item 2a is also simple.
Item 2b, the display of source information and the code for calling a subform to edit it, is exactly the same for every top-level field. The current code is about 55 lines long and appears once in the repeat, guarded by a condition to display it only for top-level items (the repeat includes second-level items as well). The naive translation into pull form requires this code to appear for all top-level elements that can carry the @source attribute, 13 of them.
My initial solution was to use the xf:include element, an XSLTForms extension that works like XInclude.
The xf:include mechanism allows subordinate parts of a form to be stored separately so the main form is shorter and easier to edit and understand; it also allows things like Next / Prev buttons to appear both at the top and at the bottom of a list with the same non-trivial logic. When the same document is included more than once, it may also reduce bandwidth needs, since the second request can be satisfied from cache.
So in the current draft pull stylesheet the relevant block for each top-level element has lines similar to these for defGrp:
<xf:group ref="defGrp"> <div class="L1 label"> <span>defendant group (defGrp element)</span> <xf:include src="ianua-sourceinfo.xml"/> </div> <xf:include src="ianua-source-attribution.xml"/> ...
where ianua-sourceinfo.xml contains the display and the edit button, and ianua-source-attribution.xml contains the toggle with the editor.
The problem is that the cases in each XForms toggle construct must bear unique IDs. When ianua-source-attribution.xml is included more than once, the resulting form has duplicate IDs. (I have encountered this in the past; it meant I could not use xf:include in some places where I wanted to use it.)
So: the snag is that the current form of the pull stylesheet won't work, and none of the obvious solutions looks like a clear winner.
I can think of several ways to solve the problem. To avoid any suggestion of preference, I'll name these with colors, not with the letters A, B, C, etc.
RED: Expand the calls to xf:include manually, now, in the source of the form. Modify the IDs to make them unique.
Pro: simple.
Con: ugly: clutters up the source code. Error-prone: every change to the logic of source display and editing will need to be made 13 times.
ORANGE: Have just one toggle for the editor for source attribution; each source display should set up the source-attribution buffer and call the editor.
I'd need to find a way to have the source-attribution editor pop up at the same location on the screen each time. That's not something I don't currently know how to do, and don't know if it's possible in this context.
Pro: relatively simple, if it works.
Con: have to solve a potentially tricky CSS problem.
YELLOW: Keep the calls to xf:include, and add to each one an attribute named (for example) @tlrr:id-prefix, with a different prefix each time. Write a simple XSLT pre-processor which performs a near-identity transformation, and expands all instances of xf:include which carry the @tlrr:id-prefix attribute. While performing the inclusion, the pre-processor will also add the designated prefix to each ID in the included source. So the calls
<xf:include src="sources.xml" tlrr:id-prefix-'def-'/> <xf:include src="sources.xml" tlrr:id-prefix='cc-'/>
will no longer produce duplicate IDs.
Pro: keeps the source code simple. The pre-processor might also form the basis for simple extensions to XForms. If it were extended to handle simple parameterized macros, it could simplify the development and maintenance of things like Dexter, and allow at least rudimentary implementation of XForms extension proposals.
Con: requires an additional step between editing the form and testing. Some risk that the temptation to experiment with more general macro processing will distract me from the immediate goal of getting a pull-style editor working and shippable.
GREEN: Develop a dynamic macro facility based on in-browser calls to XSLT and injection of the results into the HTML document.
Pro: nice solution.
Con: the macro processor must produce not XForms but the compiled HTML equivalent. That is, it must reproduce the logic of the main xsltforms stylesheet. That in turn requires either duplication of logic (not satisfactory) or a two-step compilation requiring node-set extension support. (But all browsers have the node-set extension; what am I worried about?)
Right now (4 April), the YELLOW pre-processor seems most promising. It will work, it doesn't require solving tricky CSS problems, it provides a basis for further work, and it would be a useful preliminary step if we decided to try the GREEN solution (dynamic macros, in the form itself).
Last updated 19 March 2018
Photo "Foro romano in crepusculo
© 2013 by MauroPPP;
some rights reserved.