Soft validation in an editor environment
Martin Middel, FontoXML
REMEMBER TO START DOCBOOK SERVER at 8080
Note: the original presentation contains a demo of the FontoXML docbook editor. A similar editor is available at dita.fontoxml.com
Fonto is an editor easily configured for most schemas
DiTa, TEI, HTML, office open xml, even docbook
This is fonto donfugred for docbook, in which i wrote my paper
This is fonto!
Our authors no not know XML, we guide them into writing schema valid
open the structure tab, then the source tab, press ctrl+b to make something bold.
Schematron
A set of queries for nodes
Followed by assertions and tests for them
An example schema
A paragraph must have a class
Most of you know schematron already. For those who don't: queries and asserts/tests
Schematron in Fonto
…
…
footnote must not occur in the descendants of footnote
Real life use case
This is the actual docbook.sch file
Note: the original presentation contains a demo of the FontoXML docbook editor. A similar editor is available at dita.fontoxml.com . At the moment of writing, this does not contain a schematron implementation though./
Open the soft validation panel, observe no errors.
If I open a footnote: new error.
Click on solve, press delete, observe: error gone.
Observe, no anymation, this is a development version for release next week
Performance
3500+ (generated) Schematron rules
Look like this:
rule: /chunk[@class="achievement"]/ chunk-body/descendant::ol [@class="achievement-list"]
assert: count(child::li[not(@class)]) < 1
One on our clients 3500+ rules.
Generated, so we can make them absolute.
We can give them an exit early
Not possible to run them every keystroke
Cant even run them every new element
We need a way to prevent re-running them
Options
Server side
Big Update Button
XPath 3.1 in the browser
How we did it
Parse the XPath expression
PegJS parser generator
Outputs an ast in JSON:
[
"absolutePath",
[
"path",
[
"descendant-or-self",
["kindTest", "node()"]
]
]
]
This is saved in 'indexedDB', no parser hit next time
We used PEGJS to generate a parser from a grammar
This grammar already drops unneeded levels
to a JSON ast
We do not want to re-parse every XPath
That is why we cache them
in a persistent storage, so that we can save them accross browser instances. Which is good, because our authors keepcoming back
Execute it
Parse the ast to JavaScript models
Resolve functions
Propagate variables on the dynamic context
Instant feedback
We need instant feedback
Remember: non-technical authors
Need to be guided
How to do it live
Optimize all the queries
Only run affected queries
We can make individual queries as fast as possible
Or we can less of them
We chose the latter, this introduces the need for dep track
Dependency tracking
class DependencyTrackingDomFacade {
getParentNode (node) {
addDep('childList', node.parentNode);
return node.parentNode;
}
getNextSibling (node) {
addDep('childList', node.parentNode);
return node.nextSibling;
}
getAttribute (node, name) {
addDep('attributes', node);
return node.getAttribute(name);
}
}
We catch all the (changeable) DOM accessors
This allows us to determine if a mutation could affect a query
MutationObserver
{
type: 'childList' | 'attribute' | 'characterData',
target: Node,
addedNodes: Node[],
removedNodes: Node[],
nextSibling: Node?,
previousSibling: Node?,
attributeName: String?,
oldValue: String?
}
Demo time
/chunk/chunk-body/ol/li
Some text
Some more text
A list item
Summarized situation
everything with a red border is a result
everything with a pink border has its' child axis accessed
Done?
Not nearly
Static analysis / optimization
Partial queries / result caching
Conclusion
Client is happy
Good editing performance
Next year: write your paper in Fonto