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 arguements.
The following example is a quick intro 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"
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.