Core method that returns all child elements, in the correct order.
Core method that returns all child elements, in the correct order. Other operations can be defined in terms of this one.
Shorthand for filterChildElems(p)
.
Shorthand for filterChildElems(p)
. Use this shorthand only if the predicate is a short expression.
Shorthand for filterElemsOrSelf(p)
.
Shorthand for filterElemsOrSelf(p)
. Use this shorthand only if the predicate is a short expression.
Shorthand for findTopmostElemsOrSelf(p)
.
Shorthand for findTopmostElemsOrSelf(p)
. Use this shorthand only if the predicate is a short expression.
Returns the child elements obeying the given predicate.
Returns the child elements obeying the given predicate. This method could be defined as:
def filterChildElems(p: E => Boolean): immutable.IndexedSeq[E] = this.findAllChildElems.filter(p)
Returns the descendant elements obeying the given predicate.
Returns the descendant elements obeying the given predicate. This method could be defined as:
this.findAllChildElems flatMap (_.filterElemsOrSelf(p))
Returns the descendant-or-self elements obeying the given predicate.
Returns the descendant-or-self elements obeying the given predicate. This method could be defined as:
def filterElemsOrSelf(p: E => Boolean): immutable.IndexedSeq[E] = Vector(this).filter(p) ++ (this.findAllChildElems flatMap (_.filterElemsOrSelf(p)))
It can be proven that the result is equivalent to findAllElemsOrSelf filter p
.
Returns all descendant elements (not including this element).
Returns all descendant elements (not including this element). This method could be defined as filterElems { e => true }
.
Equivalent to findAllElemsOrSelf.drop(1)
.
Returns this element followed by all descendant elements (that is, the descendant-or-self elements).
Returns this element followed by all descendant elements (that is, the descendant-or-self elements).
This method could be defined as filterElemsOrSelf { e => true }
.
Returns the first found child element obeying the given predicate, if any, wrapped in an Option
.
Returns the first found child element obeying the given predicate, if any, wrapped in an Option
.
This method could be defined as filterChildElems(p).headOption
.
Returns the first found (topmost) descendant element obeying the given predicate, if any, wrapped in an Option
.
Returns the first found (topmost) descendant element obeying the given predicate, if any, wrapped in an Option
.
This method could be defined as filterElems(p).headOption
.
Returns the first found (topmost) descendant-or-self element obeying the given predicate, if any, wrapped in an Option
.
Returns the first found (topmost) descendant-or-self element obeying the given predicate, if any, wrapped in an Option
.
This method could be defined as filterElemsOrSelf(p).headOption
.
Returns the descendant elements obeying the given predicate that have no ancestor obeying the predicate.
Returns the descendant elements obeying the given predicate that have no ancestor obeying the predicate. This method could be defined as:
this.findAllChildElems flatMap (_.findTopmostElemsOrSelf(p))
Returns the descendant-or-self elements obeying the given predicate, such that no ancestor obeys the predicate.
Returns the descendant-or-self elements obeying the given predicate, such that no ancestor obeys the predicate. This method could be defined as:
def findTopmostElemsOrSelf(p: E => Boolean): immutable.IndexedSeq[E] = if (p(this)) Vector(this) else (this.findAllChildElems flatMap (_.findTopmostElemsOrSelf(p)))
Returns the single child element obeying the given predicate, and throws an exception otherwise.
Returns the single child element obeying the given predicate, and throws an exception otherwise.
This method could be defined as findChildElem(p).get
.
API and implementation trait for elements as containers of elements, as element nodes in a node tree. This trait knows very little about elements. It does not know about names, attributes, etc. All it knows about elements is that elements can have element children (other node types are entirely out of scope in this trait).
The purely abstract API offered by this trait is ParentElemApi. See the documentation of that trait for examples of usage, and for a more formal treatment. Below follows an even more formal treatment, with proofs by induction of important properties obeyed by methods of this API. It shows the mathematical rigor of the yaidom query API. API users that are only interested in how to use the API can safely skip that formal treatment.
ParentElemLike more formally
In order to get started using the API, this more formal section can safely be skipped. On the other hand, this section may provide a deeper understanding of the API.
The only abstract method is
findAllChildElems
. Based on this method alone, this trait offers a rich API for querying elements. This is entirely consistent with the semantics defined in theParentElemApi
trait. Indeed, the implementation of the methods follows the semantics defined there.In the
ParentElemApi
trait, some (simple) provable laws were mentioned. Some proofs follow below.1. Proving property about filterElemsOrSelf
Below follows a proof by structural induction of one of the laws mentioned in the documentation of trait
ParentElemApi
.First we make a few assumptions, for this proof, and (implicitly) for the other proofs:
Based on these assumptions, we prove by induction that:
Base case
If
elm
has no child elements, then the LHS can be rewritten as follows:which is the RHS.
Inductive step
For the inductive step, we use the following (general) properties:
(xs.filter(p) ++ ys.filter(p)) == ((xs ++ ys) filter p) // referred to below as property (a)
and:
If
elm
does have child elements, the LHS can be rewritten as:which is the RHS.
This completes the proof. Other above-mentioned properties can be proven by induction in a similar way.
2. Proving property about filterElems
From the preceding proven property it easily follows (without using a proof by induction) that:
After all, the LHS can be rewritten as follows:
which is the RHS.
3. Proving property about findTopmostElemsOrSelf
Given the above-mentioned assumptions, we prove by structural induction that:
Base case
If
elm
has no child elements, andp(elm)
holds, then LHS and RHS evaluate toimmutable.IndexedSeq(elm)
.If
elm
has no child elements, andp(elm)
does not hold, then LHS and RHS evaluate toimmutable.IndexedSeq()
.Inductive step
For the inductive step, we introduce the following additional (general) property, if
f
andg
have the same types:This is also known as the "composition law for monads" (see http://james-iry.blogspot.nl/2007/10/monads-are-elephants-part-3.html).
If
elm
does have child elements, andp(elm)
holds, the LHS can be rewritten as:which is the RHS. In this case, we did not even need the induction hypothesis.
If
elm
does have child elements, andp(elm)
does not hold, the LHS can be rewritten as:which is the RHS.
4. Proving property about findTopmostElems
From the preceding proven property it easily follows (without using a proof by induction) that:
After all, the LHS can be rewritten to:
which is the RHS.
5. Properties used in the proofs above
There are several (unproven) properties that were used in the proofs above:
Property (a) is obvious, and stated without proof. Property (c) is known as the "composition law for monads". Property (b) is proven below.
To prove property (b), we use property (c), as well as the following property (d):
Then property (b) can be proven as follows:
Implementation notes
Methods
findAllElemsOrSelf
,filterElemsOrSelf
,findTopmostElemsOrSelf
andfindElemOrSelf
use recursion in their implementations, but not tail-recursion. The lack of tail-recursion should not be a problem, due to limited XML tree depths in practice. It is comparable to an "idiomatic" Scala quicksort implementation in its lack of tail-recursion. Also in the case of quicksort, the lack of tail-recursion is acceptable due to limited recursion depths. If we want tail-recursive implementations of the above-mentioned methods (in particular the first 3 ones), we either lose the ordering of result elements in document order (depth-first), or we lose performance and/or clarity. That just is not worth it.The captured element subtype