XML DOMs traditionally create trees by each element "owning" its children. Scales eschews this model, instead using trees to model the containment, allowing re-use for all of the Scales model.
To simplify both creating and basic manipulation of trees Scales provides a DSL that closely resembles the structure of XML. The point of focus in the tree (or the depth) is controlled by nesting of the arguments. The implementing class is DslBuilder
The following example is a quick introduction into how to create trees (also used within the XPath guide):
val ns = Namespace("test:uri") val nsa = Namespace("test:uri:attribs") val nsp = nsa.prefixed("pre") val builder = ns("Elem") /@ (nsa("pre", "attr1") -> "val1", "attr2" -> "val2", nsp("attr3") -> "val3") /( ns("Child"), "Mixed Content", ns("Child2") /( ns("Subchild") ~> "text" ) )
The tour will use the following definitions:
val ns = Namespace("uri:test") val elem = ns("Elem") val child = ns("Child") val childl = "Child"l val root = ns("Root") val child1 = ns("Child1") val child2 = ns("Child2") val child3 = ns("Child3") val fred = ns("fred")
To start a tree simply use a qname or elem followed by a DSL operator:
val dsl = elem / child // <Elem xmlns="uri:test"><Child/></Elem> asString(dsl)
or for visual distinction use the <( ) function
val dsl2 = <(elem) / child
To add a subelement use:
// <Elem xmlns="uri:test"><Child/><Child2/><Child3/></Elem> val dsl3 = dsl2 /( child2, child3)
The tree can be freely nested and, instead of a sequence of subtrees, a by-name version with taking an Iterable allows you to call other functions to provide the sub-trees.
// <Elem xmlns="uri:test" attr="fred"><Child/><Child2/><Child3/></Elem> val dsl4 = dsl3 /@( "attr" -> "fred" )
As we often set a single string for a given subtree the DSL provides a helpful feature to replace all child nodes with a single text node.
// <Elem xmlns="uri:test" attr="fred">a string</Elem> val dsl5 = dsl4 ~> "a string"
This can, of course, be nested:
// res14: String = <Elem xmlns="uri:test" attr="fred"><Child/> // <Child2/><Child3/><fred>fred's text</fred></Elem> val dsl6 = dsl4 /( fred ~> "fred's text" )
Any child whose QName matches (namespace and localname =:=) will be removed via:
// <Elem xmlns="uri:test" attr="fred"><Child2/><Child3/><fred>fred's t ext</fred><Child xmlns=""/></Elem> val dsl7 = (dsl6 -/ child) / childl
Note that due to infix limitations of Scala that the following won't compile:
// won't compile as its an even number of terms val dsl7 = dsl6 -/ child / childl
If in doubt use brackets or dot accessors to be specific.
Similar to removing Elems QName and a - do the job:
// <Elem xmlns="uri:test"><Child2/><Child3/><fred>fred's t ext</fred><Child xmlns=""/></Elem> val dsl8 = dsl7 -/@ "attr"
When using the DSL to template XML its often necessary to model optional content. This can be expressed via direct use of Option for an attribute, child or text directly:
def template(optionalText : Option[String) = ns("Elem") ~> optionalText val hasTextChild = template(Some("text")) val hasNoChildren = template(None)
As hasNoChildren demonstrates, when using None, there will be no child added. Using "" instead, would add an empty Child, whilst semantically identical upon serialization it changes the meaning of the in-memory DOM itself.
The Attribute and child variants function in the same way, but for some usages a fully cascading solution may be required. If the elem itself should also not be added when there are no children present then the Optional DSL should be used.
The XPath fold facility is also present directly from the DSL, letting you stay within the tree building and manipulate at the same time with the full power of the DSL.
The DSL provides three fold functions, fold - returning an Either (as per the normal fold but wrapped with DslBuilder), fold_! (throws upon an error) and fold_?.
The fold_? function is perhaps the most commonly useful, and returns "this" if no folds took place (NoPaths), but throws if an error was found.
In each case the parameters are XmlPath => XPath (to allow selection) and the folder (what should we do with the selection) and is used thusly:
// remove all ChildX elements regardless of namespace yielding: // <Elem xmlns="uri:test"><fred>fred's text</fred></Elem> val dsl9 = dsl8.fold_?( _.\*( x => localName(x).startsWith("Child"))) { p => Remove() }
See XPath Folds for more information and how to use it for transformations.