<!-- returns a set of N <n/> elements to replace [1 to N] -->
<xsl:template name="range-expression">
<xsl:param name="n"/>
<xsl:param name="counter" select="1"/>
<xsl:choose>
<xsl:when test="$counter > $n"/>
<xsl:when test="$counter = $n">
<n><xsl:number value="$counter"/></n>
</xsl:when></code>
<xsl:otherwise>
<n><xsl:number value="$counter"/></n>
<xsl:call-template name="range-expression">
<xsl:with-param name="n" select="$n"/>
<xsl:with-param name="counter" select="$counter + 1"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Regular expressions
Process server-side and replace with more template-friendly XML. Some simpler
regexes can be approximated using contains(),
substring-before(), and substring-after(), but we do not
advise that approach generally.
xsl:for-each-group
Double Muenchian grouping with a second, compound keySee "Trying to pull of a double Muenchian,"
https://www.oxygenxml.com/archives/xsl-list/200602/msg00687.html
In our initial design, we considered the ordering of questions displayed by the wizard
to be a grouping problem, prompting a serious discussion about switching to an XSLT 2.0+
implementation for its superior grouping support. The double Muenchian grouping technique
with compound keys, noted above, had become increasingly elaborate and difficult to
maintain, as we needed to order questions on several different levels.This led us to step back and question assumptions we made about the problem we were
solving generally, and we determined that we needed to revisit our data model. As we
explain in more detail later in this paper, instead of focusing solely on the form
documents that were mostly pertinent as input and output to the interview process, we
changed the model to center around the interview process itself. In our new model, we
could more easily manage relationships between data, which allowed us maintain questions
in the correct order throughout processing and incidentally avoid the need for grouping
entirely. Working in XSLT 1.0’s more constrained environment forced us to acknowledge and
address flaws in our initial design that the added conveniences of XSLT 2.0 might have
allowed us to work around, and we arrived at a more robust solution sooner than if we had
pressed on with band-aid fixes.The workarounds needed to achieve anything useful in multiple environments are real
impediments, but our judgment is that relative to the work required to set up nearly any
modern programming environment, the scale is small. With awareness of these issues
upfront, getting up and running should take a matter of hours, not days. One
disappointment throughout this experiment, however, was the consistency with which browser
vendors have apparently de-prioritized fixing XSLT bugs. Most of the bugs have lain
dormant in issue trackers for years, many including the tag “WontFix." However, that was
not universally true. From the time we started this project to the time we began drafting
charts for this paper, Microsoft fixed the Edge bugSee "XSLT transformToFragment fails if the XSLT contains an IMPORT,"
https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8685115/ preventing the use of xsl:import. Progress is possible.PerformanceThe results of our performance testing strongly affirmed our decision to stick with
browser-native XSLT processors, to a remarkable degree. We had not expected desktop
browser processors to perform comparably to Saxon-EE, but they did and even reliably
outperformed Saxon-EE for one of our test cases. We tested Saxon-JS with SEFs generated
using Saxon 9.8 and found that it was at best an order of magnitude slower than the
built-in processors.Saxon-JS UpdateBefore publication, we shared our tests and results with Saxonica. After investigating our
workload, they attributed the disparity in XSLT evaluation times primarily to repeated copying of
DOM subtrees and assured us that they are working to address this in future versions
of Saxon-JS.Even mobile browsers’ native XSLT processors returned results that,
while slower than desktop equivalents, were acceptable for our application. Using native
client-side XSLT 1.0 provided not only more minimal architecture and lower server load and
round-trip latencies but comparable and sometimes much faster processing times than any
other solution we considered.Testing environments"macOS 10.13 notebook" hardware: MacBook Pro (Retina, 13-inch, Early 2015), 16GB
RAM. "Windows 10 desktop" hardware: Dell OptiPlex 7040, 3.4 GHz Intel Core i7 (4-core),
16 GB RAM. Saxon-EE tests were run using Java 1.8.0. Test documents: Test 1: 980 kB
& complex XML, Test 2: 588 kB & simple XML, Test 3: 700 kB & complex XML,
Test 4: 189 kB & simple XML.We had not expected our experiment using browser-native XSLT processors to be
ultimately successful. It would have been easy to assume that the combination of XSLT
1.0's limited feature set and an accumulation of browser processor bugs was enough
disqualify the browser-native XSLT environment for any serious project, and that
perception is certainly real. But the breadth and complexity of our application and our
ability to achieve better performance with more minimal architecture is strong evidence to
counter that perception. JavaScript implementationAn all-XSLT or an all-JavaScript client development environment would lead to additional
complexity, either in the management of the UI or in the development of the automation
engine, so we sought a solution that could unify XSLT with a modern JavaScript UI. Defining
modern in the context of JS technologies is a contentious subject and one with a moving
target. We attempt to define it as simply as possible, based on the observation that a new
pattern has emerged in web development. Most recently, front-end web development was
dominated by monolithic two-way data binding frameworks like AngularJS or EmberJS []. Now, those large frameworks are giving way to preferences for a looser
mix-and-match approach to assembling tailor-made frameworks centering around a virtual
DOM-based view library like React or Vue.Modern architectureThe virtual DOM is an abstraction of the browser DOM, and its key innovation is its
ability to very quickly compare an updated virtual DOM to the current one, then calculate
optimal steps to update the browser DOM based on the computed difference. This unburdens a
front end developer in at least two important ways. First, it completely abstracts away
the problem of managing explicit browser DOM updates—a revolution for front end developers
and its primary appeal. Second, and most relevant to our application, it enables us to
build UIs with functional architectures.Virtual DOM libraries are intentionally narrowly focused architecturally on only the
view component, whereas the previous frameworks provided a full architecture like MVCSee "Model-View-Controller," Wikipedia,
https://en.wikipedia.org/wiki/Model–view–controller or MVVMSee "Model-View-ViewModel," Wikipedia,
https://en.wikipedia.org/wiki/Model–view–viewmodel. Several popular functional reference architectures have emerged to fill the
void, available as minimalist frameworks that wrap a virtual DOM library and manage
application state. They all share a common theme: strict unidirectional data flow, a
significant departure from the bidirectional data flow of the previous generation of
frameworks. The first such framework from the creators of ReactJS, Flux, demonstrates an
architecture that separates concerns using this new idea.Because the virtual DOM engine will automatically apply surgical updates to the
browser DOM based only on what has changed in the view, developers are free to reason
about a view as if the page is being completely re-rendered every time. For some, suddenly
this design may seem familiar. If you go back to the early Web, pre-Web 2.0, this is
essentially how page interaction worked. A form was submitted, sending a payload of
parameters describing the action to the server, and the server built a complete webpage
and returned it to the user. Unidirectional flow was enforced automatically by the
limitations of the technology. Now, most of this process occurs in the client instead of
on the server, and it is designed to update the view at 60 fps, but the architecture is
similar because the fundamental assumptions we can make about rendering a view is similar.
What’s old is new again.XSLT symbiosisThe Flux pattern, using a virtual DOM-based view, provides a functional pipeline for
rendering views in the browser in JavaScript. In our application, we also needed to
include a functional pipeline for processing XML data based in XSLT. The architectural
equivalence gives us a bridge to unify these two-year-old and two-decade-old
technologies.Under the Flux pattern, Stores are responsible for the application state and logic,
performing updates and transformations in response to Action inputs from the view or from
external actions. Our XML documents represent the state of the document automation
process, a subset of the application state, and the XSLT engine is our logic for
transitioning from one state to the next, so Stores were the most appropriate injection
point.XML-centric patternWe established a pattern for working with XSLT within a Store. The application
initializes by requesting a payload from the server that contains the document to be
automated and a set of initial user data. On the initial page load, the engine XSLT
transforms the document, using the user-supplied data, into a new state. Then the render
XSLT transforms the updated document into an XHTML element, which is appended to the
virtual DOM. Subsequent actions are handled by the framework, and upon re-entering the
store, changes to user data captured by the UI are marshaled into the user data XML
element, followed again by the XSLT pipeline.Within the overarching JavaScript framework, we are able to leverage XSLT in a
couple of important ways. We use it as a drop-in replacement for JavaScript to natively
processes application state on XML data in a JS-based framework without much ceremony,
and we use it to transform from hard-to-use (in the JS environment) generic XML to
XHTML, which is useable directly in the view.In a traditional implementation of the Flux pattern, however, no components of a
view would be rendered in a store, so one could argue that our design leaks abstractions
between the store and the view. Our justifications for this are practical. This
application will be responsible for rendering large preview documents, and XSLT is
simply much better suited for performing that work. Abstractions are imperfect, and
sometimes allowing a leak is the only sensible solution to prevent overcomplicating code
or bad performance.JSON-centric patternBefore coming to this conclusion, we considered another pattern to give complete
control over rendering to the virtual DOM for tighter integration into the traditional
virtual DOM architecture, but we rejected it because it added complexity with only
marginal benefit. However, the trade-off may be appropriate or necessary when composable
component hierarchies using combinations of XSLT-generated and virtual DOM-native
components are needed—not possible with XHTML-rendered components—or where it is
important to take advantage of virtual DOM-rendering performance optimizations.The goal of this pattern is to render the view using only virtual DOM-native
components, so the XHTML-rendering step is replaced by a step that transforms
view-relevant XML data into JSONSeveral convenient methods exist to generate JSON from XML. Badgerfish is a
convention for converting XML into JSON for which JavaScript and XSLT 1.0 libraries
are available, giving you the choice to decide on which side of the fence the
conversion is best suited. Similarly, the JsonML format is designed for round trip
conversions of XML and JSON, supported by libraries in JavaScript and XSLT 1.0 (and
other languages). The conversion can also be done in XSLT 3.0 natively, without the
need for any libraries [].. Each virtual DOM library has a slightly different approach to rendering,
but they have in common supplying JSON properties as input to a functional
transformation that renders a component. Compared to an XSLT transformation, properties
are analogous to an input document, and the component (in some virtual DOM
implementations, literally a function) is analogous to the XSLT. The JSON output can
either be used wholesale to render the component, or it can be further reorganized by JS
to maintain a greater separation between data models and view models.A polyglot webModern client-side JavaScript development appears to be converging on functional
patterns resembling early web architecture, and we found it useful as a bridge to
another artifact of the early web, client-side XSLT. Using this architecture, we had the
benefit of leveraging both JavaScript and XSLT for their strengths without being forced
to break boundaries between the two environments in ways that would complicate overall
application development and maintenance. But we see implications for its use beyond
XSLT.With the recent adoption of the WebAssembly standardSee "WebAssembly.org," https://webassembly.org by major browsers, we are hopeful for a future where language symbiosisSee "Awesome WebAssembly Languages,"
https://github.com/appcypher/awesome-wasm-langs resembling server-side polyglot environments, like the Oracle JVMSee "Alternative Languages for the JVM,"
http://www.oracle.com/technetwork/articles/java/architect-languages-2266279.html and the Microsoft CLRSee "List of CLI languages," Wikipedia,
https://en.wikipedia.org/wiki/List_of_CLI_languages, can flourish in the browser.RedesignMany of the difficulties we encountered working with native client-side XSLT were not
from limitations of the language or the environment, but the result of a fundamental design
problem. We assumed that processing documents in their canonical form would be the simplest
and most idiomatic solution, and successfully working under that assumption in early stages
of prototyping led us to establish a design centered on the wizard’s final output, the
completed form, rather than the information needed to process the wizard itself. As our
application and its requirements expanded, those assumptions failed, and it eventually
became clear we needed a different model. After overhauling the design to reflect this
insight, we were finally confident that we had arrived at the appropriate idiom for modeling
our document transformation problem, affirming our choice to implement the engine in
XSLT.The canonical documents were isomorphic to our initial conception of document processing
—walk the document tree, applying known information from other documents, and stop when more
information is needed, just as a human would—and we were satisfied using them to manage the
state of the interview through our first stages of requirements. But these documents did not
explicitly model relationships between data—the form, questions, and their answers were
separate self-contained data structures—and those relationships had to be reestablished at
every iteration of the processor. As a direct consequence of this separated design, we
employed increasingly complicated strategies to avoid repeating significant amounts of
processing at every step. But the engine ultimately wasn't transforming a incomplete form
into a completed form; it was transforming an incomplete form into an interview, and we
needed sensible and distinct data models for each.We understood from the outset what problems could arise from applying a document-centric
model to a UI, but it wasn't until much later into development that we understood what was
fundamentally the same problem inside our engine. As we needed to support higher variability
within a form document, it became harder to step from one final form state to another. The
bulk of the time spent processing each answer was for handling cascading effects, and the
approach scaled poorly with the complexity of the forms. We needed to shift the work
upfront, transforming our data into a structure that would allow quick handling of new
information and keeping all related data in one place.Our document processor is supplied three types of input: a form document, questions, and
answers. The engine assumes the form document includes all conditional content and any
questions needed to satisfy them. As the interview progresses, answers are added to the set
of documents, and the form document is reevaluated to discover new references to fields and
conditionals. The central objective of the form processor is to identify and resolve these
references.In the original design of our application, these inputs doubled as the data model, but
they were a poor reflection of that central objective. As the processor walked the form
document tree, it needed to re-evaluate conditions and identify form references to determine
what content was included.The dependency relationships between the references, or which references controlled the
visibility or evaluation of what other references, were not explicit. As a direct
consequence, the information needed to respond to each reference's discovery or resolution
required tracing recursively through three three different data structures, a level of
indirection that was painful to manage at run-time.As we expanded our prototype to meet the demands of more sophisticated documents, our
application became unwieldy, and implementing new features became disproportionately
difficult. The design had become hard to understand, sluggish, and we still had more
features to implement. Significant changes required carefully working around several
elaborate features, such as the grouping problem described above. The need to trace
cascading changes after new answers were provided to previously answered questions was the
last straw.The change would have required building a new dependency-tracking layer on top of an
implementation already suffering from complex layers of indirection. Suddenly we were
experiencing deja vu. What had begun as a liberating and productive process, without the
baggage of a third-party system, declined into a slow and frustrating affair. Our approach,
again, needed to change. It was clear that the dependency-tracking information we knew we
needed for tracing changes was so fundamental to the objective of the processor that the
data and processing model should be redesigned instead of expanding the run-time patchwork
of indirection built on top of our current one. The data model should target an idiom
centered around the information-gathering process, not the form document.To redesign the data model around references, we inverted the documents in a
post-authoring step. The new data structure formed a sort of stack, with elements that could
be processed sequentially to trace their dependencies, inserting new dependencies following
their references in the stack.Figure 18 illustrates the issue that finally motivated the redesign of our data model:
cascading effects handling changed answers. Working with our original data model, if a user
changes their answer to A to value y, there is no way to determine that B and C are no
longer required without reprocessing the entire form. By focusing only on the relevant parts
of the document, the problem becomes simpler and avoids repeating a great deal of work. The
ordering requirement on the reference stack ensures that preceding siblings of reference A
will not be affected by A's changed answer. Only the references between A and the next
unconditional reference need to be reevaluated.One of our early stated principles was to maintain simplicity in our design for the
document engine, but we had misjudged the implications of avoiding a transformation from the
canonical document format into an engine-specific document. This was quite ironic,
considering that document transformation is the most powerful aspect of the language we had
chosen to work in. Compounding the irony, our missing insight about data modeling was
something that had been obvious to us about the UI all along: the structure of the final
document was only incidental in the problem we needed to solve. Focusing on the progression
of information, rather than its consequences to the final document, eliminated the layer of
complexity that led us to question our choice to write the engine in XSLT, and
post-redesign, we had an even stronger case for its use.ConclusionOver the last twenty years, the Web has grown increasingly separated from its early XML
influences. Despite its evolution into a towering JavaScript monoculture, its ecosystems are
now converging on architectures that are fundamentally capable of bridging the gap to other
languages. Out of a desire to test that theory, and having a suitable use case for XSLT,
emerged a great opportunity to put browser XSLT engines through their paces—and we found them
nearly as capable as ever!Truly unified with a modern JavaScript UI, we built an XSLT-based application on our
terms, uncompromised by the gravity of top-down XML and JavaScript frameworks, and we believe
there are strong incentives for combining technologies, each with their best foot forward. But
our unlikely love story between JavaScript and XSLT is just a single example of what is
possible. The emerging WebAssembly standard is being adopted quickly by browsers and promises
to bring dozens of new languages and ecosystems into the fray. Years of virtuous growth may
have been lost by separating the XML and Web communities, but it seems possible the silos are
only temporary.The prospect of a future unconstrained by browser technology also underscores the need for
a lucid understanding of the problems we are trying to solve. We are taught to empathize with
the end users of technology who blame themselves for its flaws. But maybe as developers we
should be more hesitant to adopt that perspective. The problem might not be the
technology.BibliographyAllen, Ian. “The Brutal Lifecycle of
JavaScript Frameworks.” StackOverflow, January 11 2018. https://stackoverflow.blog/2018/01/11/brutal-lifecycle-javascript-frameworks/.Barth, Adam. “Intent to Deprecate and Remove:
XSLT.” 2013. https://groups.google.com/a/chromium.org/forum/#!msg/blink-dev/zIg2KC7PyH0/Ho1tm5mo7qAJ.Denicola, Domenic. “Non-Extensible Markup Language.” Presented at Symposium on
HTML5 and XML: Mending Fences, Washington, DC, August 4, 2014. In Proceedings of the Symposium
on HTML5 and XML: Mending Fences. Balisage Series on Markup Technologies, vol. 14 (2014). doi:10.4242/BalisageVol14.Denicola01.Delpratt, O'Neil and Kay,
Michael. “Multi-user interaction using client-side XSLT.” In Proceedings of XMLPrague 2013,
pp1-23 (2013) [online]. http://archive.xmlprague.cz/2013/files/xmlprague-2013-proceedings.pdf.Delpratt, O'Neil, and Kay,
Michael. “Interactive XSLT in the browser.” Presented at Balisage: The Markup Conference 2013,
Montréal, Canada, August 6 - 9, 2013. In Proceedings of Balisage: The Markup Conference 2013.
Balisage Series on Markup Technologies, vol. 10 (2013). doi:10.4242/BalisageVol10.Delpratt01.Kay, Michael. “Transforming JSON using XSLT
3.0.” In Proceedings of XMLPrague 2016, pp167-183 (2016) [online].
http://archive.xmlprague.cz/2016/files/xmlprague-2016-proceedings.pdf.Reschke, Julian. “Test Cases for XSLT
support in browsers.” greenbytes, 2015. http://test.greenbytes.de/tech/tc/xslt/.