Package

eu.cdevreeze.yaidom

queryapi

Permalink

package queryapi

This package contains the query API traits. It contains both the purely abstract API traits as well as the partial implementation traits.

Generic code abstracting over yaidom element implementations should either use trait ClarkElemApi or sub-trait ScopedElemApi, depending on the abstraction level.

Most API traits are orthogonal, but some API traits are useful combinations of other ones. Examples include the above-mentioned ClarkElemApi and ScopedElemApi traits.

This package depends only on the core package in yaidom, but many other packages do depend on this one.

Linear Supertypes
AnyRef, Any
Ordering
  1. Alphabetic
  2. By inheritance
Inherited
  1. queryapi
  2. AnyRef
  3. Any
  1. Hide All
  2. Show all
Visibility
  1. Public
  2. All

Type Members

  1. trait ClarkElemApi[E <: ClarkElemApi[E]] extends ElemApi[E] with IsNavigableApi[E] with HasENameApi with HasTextApi

    Permalink

    Shorthand for ElemApi[E] with IsNavigableApi[E] with HasENameApi with HasTextApi.

    Shorthand for ElemApi[E] with IsNavigableApi[E] with HasENameApi with HasTextApi. In other words, the minimal element query API corresponding to James Clark's "labelled element tree" abstraction, which is implemented as yaidom "resolved" elements.

    If a yaidom element implementation (whether in yaidom itself or a "yaidom extension") does not mix in the ClarkElemApi trait, it is probably not to be considered "XML". Indeed, in yaidom only the ElemBuilder class does not mix in this trait, and indeed it is not "XML" (lacking any knowledge about expanded names etc.), only a builder of "XML". Hence this trait is very important in yaidom, as the "minimal XML element query API".

    Generic code abstracting over yaidom element implementations should either use this trait, or sub-trait ScopedElemApi, depending on the abstraction level.

    E

    The captured element subtype

  2. trait ClarkElemLike[E <: ClarkElemLike[E]] extends ClarkElemApi[E] with ElemLike[E] with IsNavigable[E] with HasEName with HasText

    Permalink

    Partial implementation of ClarkElemApi.

    Partial implementation of ClarkElemApi.

    E

    The captured element subtype

  3. trait DocumentApi[E <: ElemApi[E]] extends AnyRef

    Permalink

    Minimal API for Documents, having a type parameter for the element type.

    Minimal API for Documents, having a type parameter for the element type.

    This is a purely abstract API trait. It can be useful in generic code abstracting over multiple element implementations.

    E

    The element type

  4. trait ElemApi[E <: ElemApi[E]] extends AnyRef

    Permalink

    This is the foundation of the yaidom uniform query API.

    This is the foundation of the yaidom uniform query API. Many DOM-like element implementations in yaidom mix in this trait (indirectly, because some implementing sub-trait is mixed in), thus sharing this query API.

    This trait typically does not show up in application code using yaidom, yet its (uniform) API does. Hence, it makes sense to read the documentation of this trait, knowing that the API is offered by multiple element implementations.

    This trait is purely abstract. The most common implementation of this trait is eu.cdevreeze.yaidom.queryapi.ElemLike. That trait only knows about elements (and not about other nodes), and only knows that elements can have child elements (again not knowing about other child nodes). Using this minimal knowledge alone, it offers methods to query for descendant elements, descendant-or-self methods, or sub-collections thereof. It is this minimal knowledge that makes this API uniform.

    This query API leverages the Scala Collections API. Query results can be manipulated using the Collections API, and the query API implementation (in ElemLike) uses the Collections API internally.

    ElemApi examples

    To illustrate usage of this API, consider the following example. Let's say we want to determine if some XML has its namespace declarations (if any) only at the root element level. We show the query code for several yaidom DOM-like element implementations.

    Note that it depends on the DOM-like element implementation how to query for namespace declarations, but the code to query for descendant or descendant-or-self elements remains the same. The method to retrieve all descendant elements is called findAllElems, and the method to retrieve all descendant-or-self elements is called findAllElemsOrSelf. The corresponding "filtering" methods are called filterElems and filterElemsOrSelf, respectively. Knowing this, it is easy to guess the other API method names.

    Let's start with a yaidom DOM wrapper, named rootElem, of type eu.cdevreeze.yaidom.dom.DomElem, and query for the "offending" descendant elements:

    rootElem filterElems (elem => !convert.DomConversions.extractNamespaceDeclarations(elem.wrappedNode.getAttributes).isEmpty)

    This returns all offending elements, that is, all descendant elements of the root element (excluding the root element itself) that have at least one namespace declaration.

    Now let's use an eu.cdevreeze.yaidom.simple.ElemBuilder, again named rootElem:

    rootElem filterElems (elem => !elem.namespaces.isEmpty)

    The query is the same as the preceding one, except for the retrieval of namespace declarations of an element. (It should be noted that class ElemBuilder already has a method allDeclarationsAreAtTopLevel.)

    Finally, let's use a rootElem of type eu.cdevreeze.yaidom.indexed.Elem, which is immutable, but knows its ancestry:

    rootElem filterElems (elem => !elem.namespaces.isEmpty)

    This is exactly the same code as for ElemBuilder, because namespace declarations happen to be retrieved in the same way.

    If we want to query for all elements with namespace declarations, including the root element itself, we could write:

    rootElem filterElemsOrSelf (elem => !elem.namespaces.isEmpty)

    In summary, the extremely simple ElemApi query API is indeed a uniform query API, offered by many different yaidom DOM-like element implementations.

    ElemApi 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 ElemApi trait can be understood in a precise mathematical sense, as shown below.

    The most fundamental method of this trait is findAllChildElems. The semantics of the other methods can be defined directly or indirectly in terms of this method.

    The basic operations definable in terms of that method are \ (alias for filterChildElems), \\ (alias for filterElemsOrSelf) and \\! (alias for findTopmostElemsOrSelf). Their semantics must be as if they had been defined as follows:

    def filterChildElems(p: E => Boolean): immutable.IndexedSeq[E] =
      this.findAllChildElems.filter(p)
    
    def filterElemsOrSelf(p: E => Boolean): immutable.IndexedSeq[E] =
      Vector(this).filter(p) ++ (this.findAllChildElems flatMap (_.filterElemsOrSelf(p)))
    
    def findTopmostElemsOrSelf(p: E => Boolean): immutable.IndexedSeq[E] =
      if (p(this)) Vector(this)
      else (this.findAllChildElems flatMap (_.findTopmostElemsOrSelf(p)))

    Moreover, we could have defined:

    def filterElems(p: E => Boolean): immutable.IndexedSeq[E] =
      this.findAllChildElems flatMap (_.filterElemsOrSelf(p))
    
    def findTopmostElems(p: E => Boolean): immutable.IndexedSeq[E] =
      this.findAllChildElems flatMap (_.findTopmostElemsOrSelf(p))

    and:

    def findAllElemsOrSelf: immutable.IndexedSeq[E] = filterElemsOrSelf(e => true)
    
    def findAllElems: immutable.IndexedSeq[E] = filterElems(e => true)

    The following properties must hold (in the absence of side-effects), and can indeed be proven (given the documented "definitions" of these operations):

    // Filtering
    
    elem.filterElems(p) == elem.findAllElems.filter(p)
    
    elem.filterElemsOrSelf(p) == elem.findAllElemsOrSelf.filter(p)
    
    // Finding topmost
    
    elem.findTopmostElems(p) == {
      elem.filterElems(p) filter { e =>
        val hasNoMatchingAncestor = elem.filterElems(p) forall { _.findElem(_ == e).isEmpty }
        hasNoMatchingAncestor
      }
    }
    
    elem.findTopmostElemsOrSelf(p) == {
      elem.filterElemsOrSelf(p) filter { e =>
        val hasNoMatchingAncestor = elem.filterElemsOrSelf(p) forall { _.findElem(_ == e).isEmpty }
        hasNoMatchingAncestor
      }
    }
    
    (elem.findTopmostElems(p) flatMap (_.filterElemsOrSelf(p))) == (elem.filterElems(p))
    
    (elem.findTopmostElemsOrSelf(p) flatMap (_.filterElemsOrSelf(p))) == (elem.filterElemsOrSelf(p))
    E

    The captured element subtype

  5. trait ElemLike[E <: ElemLike[E]] extends ElemApi[E]

    Permalink

    API and implementation trait for elements as containers of elements, as element nodes in a node tree.

    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 eu.cdevreeze.yaidom.queryapi.ElemApi. 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.

    ElemLike 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 the ElemApi trait. Indeed, the implementation of the methods follows the semantics defined there.

    In the ElemApi 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 ElemApi.

    First we make a few assumptions, for this proof, and (implicitly) for the other proofs:

    • The function literals used in the properties ("element predicates" in this case) have no side-effects
    • These function literals terminate normally, without throwing any exception
    • These function literals are "closed terms", so the function values that are instances of these function literals are not "true closures"
    • These function literals use "fresh" variables, thus avoiding shadowing of variables defined in the context of the function literal
    • Equality on the element type is an equivalence relation (reflexive, symmetric, transitive)

    Based on these assumptions, we prove by induction that:

    elm.filterElemsOrSelf(p) == elm.findAllElemsOrSelf.filter(p)

    Base case

    If elm has no child elements, then the LHS can be rewritten as follows:

    elm.filterElemsOrSelf(p)
    immutable.IndexedSeq(elm).filter(p) ++ (elm.findAllChildElems flatMap (_.filterElemsOrSelf(p))) // definition of filterElemsOrSelf
    immutable.IndexedSeq(elm).filter(p) ++ (Seq() flatMap (_.filterElemsOrSelf(p))) // there are no child elements
    immutable.IndexedSeq(elm).filter(p) ++ Seq() // flatMap on empty sequence returns empty sequence
    immutable.IndexedSeq(elm).filter(p) // property of concatenation: xs ++ Seq() == xs
    (immutable.IndexedSeq(elm) ++ Seq()).filter(p) // property of concatenation: xs ++ Seq() == xs
    (immutable.IndexedSeq(elm) ++ (elm.findAllChildElems flatMap (_ filterElemsOrSelf (e => true)))) filter p // flatMap on empty sequence (of child elements) returns empty sequence
    (immutable.IndexedSeq(elm).filter(e => true) ++ (elm.findAllChildElems flatMap (_ filterElemsOrSelf (e => true)))) filter p // filtering with predicate that is always true
    elm.filterElemsOrSelf(e => true) filter p // definition of filterElemsOrSelf
    elm.findAllElemsOrSelf filter p // definition of findAllElemsOrSelf

    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:

    (xs flatMap (x => f(x) filter p)) == ((xs flatMap f) filter p) // referred to below as property (b)

    If elm does have child elements, the LHS can be rewritten as:

    elm.filterElemsOrSelf(p)
    immutable.IndexedSeq(elm).filter(p) ++ (elm.findAllChildElems flatMap (_.filterElemsOrSelf(p))) // definition of filterElemsOrSelf
    immutable.IndexedSeq(elm).filter(p) ++ (elm.findAllChildElems flatMap (ch => ch.findAllElemsOrSelf filter p)) // induction hypothesis
    immutable.IndexedSeq(elm).filter(p) ++ ((elm.findAllChildElems.flatMap(ch => ch.findAllElemsOrSelf)) filter p) // property (b)
    (immutable.IndexedSeq(elm) ++ (elm.findAllChildElems flatMap (_.findAllElemsOrSelf))) filter p // property (a)
    (immutable.IndexedSeq(elm) ++ (elm.findAllChildElems flatMap (_ filterElemsOrSelf (e => true)))) filter p // definition of findAllElemsOrSelf
    (immutable.IndexedSeq(elm).filter(e => true) ++ (elm.findAllChildElems flatMap (_ filterElemsOrSelf (e => true)))) filter p // filtering with predicate that is always true
    elm.filterElemsOrSelf(e => true) filter p // definition of filterElemsOrSelf
    elm.findAllElemsOrSelf filter p // definition of findAllElemsOrSelf

    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:

    elm.filterElems(p) == elm.findAllElems.filter(p)

    After all, the LHS can be rewritten as follows:

    elm.filterElems(p)
    (elm.findAllChildElems flatMap (_.filterElemsOrSelf(p))) // definition of filterElems
    (elm.findAllChildElems flatMap (e => e.findAllElemsOrSelf.filter(p))) // using the property proven above
    (elm.findAllChildElems flatMap (_.findAllElemsOrSelf)) filter p // using property (b) above
    (elm.findAllChildElems flatMap (_ filterElemsOrSelf (e => true))) filter p // definition of findAllElemsOrSelf
    elm.filterElems(e => true) filter p // definition of filterElems
    elm.findAllElems filter p // definition of findAllElems

    which is the RHS.

    3. Proving property about findTopmostElemsOrSelf

    Given the above-mentioned assumptions, we prove by structural induction that:

    (elm.findTopmostElemsOrSelf(p) flatMap (_.filterElemsOrSelf(p))) == (elm.filterElemsOrSelf(p))

    Base case

    If elm has no child elements, and p(elm) holds, then LHS and RHS evaluate to immutable.IndexedSeq(elm).

    If elm has no child elements, and p(elm) does not hold, then LHS and RHS evaluate to immutable.IndexedSeq().

    Inductive step

    For the inductive step, we introduce the following additional (general) property, if f and g have the same types:

    ((xs flatMap f) flatMap g) == (xs flatMap (x => f(x) flatMap g)) // referred to below as property (c)

    This is also known as the "associativity law for monads". (Monadic types obey 3 laws: associativity, left unit and right unit.)

    If elm does have child elements, and p(elm) holds, the LHS can be rewritten as:

    (elm.findTopmostElemsOrSelf(p) flatMap (_.filterElemsOrSelf(p)))
    immutable.IndexedSeq(elm) flatMap (_.filterElemsOrSelf(p)) // definition of findTopmostElemsOrSelf, knowing that p(elm) holds
    elm.filterElemsOrSelf(p) // definition of flatMap, applied to singleton sequence

    which is the RHS. In this case, we did not even need the induction hypothesis.

    If elm does have child elements, and p(elm) does not hold, the LHS can be rewritten as:

    (elm.findTopmostElemsOrSelf(p) flatMap (_.filterElemsOrSelf(p)))
    (elm.findAllChildElems flatMap (_.findTopmostElemsOrSelf(p))) flatMap (_.filterElemsOrSelf(p)) // definition of findTopmostElemsOrSelf, knowing that p(elm) does not hold
    elm.findAllChildElems flatMap (ch => ch.findTopmostElemsOrSelf(p) flatMap (_.filterElemsOrSelf(p))) // property (c)
    elm.findAllChildElems flatMap (_.filterElemsOrSelf(p)) // induction hypothesis
    immutable.IndexedSeq() ++ (elm.findAllChildElems flatMap (_.filterElemsOrSelf(p))) // definition of concatenation
    immutable.IndexedSeq(elm).filter(p) ++ (elm.findAllChildElems flatMap (_.filterElemsOrSelf(p))) // definition of filter, knowing that p(elm) does not hold
    elm.filterElemsOrSelf(p) // definition of filterElems

    which is the RHS.

    4. Proving property about findTopmostElems

    From the preceding proven property it easily follows (without using a proof by induction) that:

    (elm.findTopmostElems(p) flatMap (_.filterElemsOrSelf(p))) == (elm.filterElems(p))

    After all, the LHS can be rewritten to:

    (elm.findTopmostElems(p) flatMap (_.filterElemsOrSelf(p)))
    (elm.findAllChildElems flatMap (_.findTopmostElemsOrSelf(p))) flatMap (_.filterElemsOrSelf(p)) // definition of findTopmostElems
    elm.findAllChildElems flatMap (ch => ch.findTopmostElemsOrSelf(p) flatMap (_.filterElemsOrSelf(p))) // property (c)
    elm.findAllChildElems flatMap (_.filterElemsOrSelf(p)) // using the property proven above
    elm.filterElems(p) // definition of filterElems

    which is the RHS.

    5. Properties used in the proofs above

    There are several (unproven) properties that were used in the proofs above:

    (xs.filter(p) ++ ys.filter(p)) == ((xs ++ ys) filter p) // property (a); filter distributes over concatenation
    
    (xs flatMap (x => f(x) filter p)) == ((xs flatMap f) filter p) // property (b)
    
    // Associativity law for monads
    ((xs flatMap f) flatMap g) == (xs flatMap (x => f(x) flatMap g)) // property (c)

    Property (a) is obvious, and stated without proof. Property (c) is known as the "associativity law for monads". Property (b) is proven below.

    To prove property (b), we use property (c), as well as the following property (d):

    (xs filter p) == (xs flatMap (y => if (p(y)) List(y) else Nil)) // property (d)

    Then property (b) can be proven as follows:

    xs flatMap (x => f(x) filter p)
    xs flatMap (x => f(x) flatMap (y => if (p(y)) List(y) else Nil))
    (xs flatMap f) flatMap (y => if (p(y)) List(y) else Nil) // property (c)
    (xs flatMap f) filter p

    Implementation notes

    Methods findAllElemsOrSelf, filterElemsOrSelf, findTopmostElemsOrSelf and findElemOrSelf 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.

    E

    The captured element subtype

  6. final class ElemWithPath[E <: IsNavigableApi[E]] extends ElemLike[ElemWithPath[E]]

    Permalink

    Pair of an element and a Path.

    Pair of an element and a Path. These pairs themselves offer the ElemApi query API, so they can be seen as "element implementations" themselves. They are like very light-weight "indexed" elements.

    These "elements" are used in the implementation of bulk update methods in trait UpdatableElemLike, but they can also be used in application code.

    Note that this class renders a separate query API for element-path pairs obsolete. It takes a IsNavigableApi, using its findAllChildElemsWithPathEntries method, and offers the equivalent of an ElemApi for element-path pairs.

  7. trait HasEName extends HasENameApi

    Permalink

    Trait partly implementing the contract for elements that have a EName, as well as attributes with EName keys.

    Trait partly implementing the contract for elements that have a EName, as well as attributes with EName keys.

    Using this trait (possibly in combination with other "element traits") we can abstract over several element implementations.

  8. trait HasENameApi extends AnyRef

    Permalink

    Trait defining the contract for elements that have a EName, as well as attributes with EName keys.

    Trait defining the contract for elements that have a EName, as well as attributes with EName keys.

    Using this trait (possibly in combination with other "element traits") we can abstract over several element implementations.

  9. trait HasParent[E <: HasParent[E]] extends HasParentApi[E]

    Permalink

    Implementation trait for elements that can be asked for the ancestor elements, if any.

    Implementation trait for elements that can be asked for the ancestor elements, if any.

    This trait only knows about elements, not about documents as root element parents.

    Based on abstract method parentOption alone, this trait offers a rich API for querying the element ancestry of an element.

    E

    The captured element subtype

  10. trait HasParentApi[E <: HasParentApi[E]] extends AnyRef

    Permalink

    API trait for elements that can be asked for the ancestor elements, if any.

    API trait for elements that can be asked for the ancestor elements, if any.

    This trait only knows about elements, not about documents as root element parents.

    E

    The captured element subtype

  11. trait HasQNameApi extends AnyRef

    Permalink

    Trait defining the contract for elements that have a QName, as well as attributes with QName keys.

    Trait defining the contract for elements that have a QName, as well as attributes with QName keys.

    Using this trait (possibly in combination with other "element traits") we can abstract over several element implementations.

  12. trait HasScopeApi extends AnyRef

    Permalink

    Trait defining the contract for elements that have a stored Scope.

    Trait defining the contract for elements that have a stored Scope.

    Using this trait (possibly in combination with other "element traits") we can abstract over several element implementations.

  13. trait HasText extends HasTextApi

    Permalink

    Trait partly implementing the contract for elements as text containers.

    Trait partly implementing the contract for elements as text containers. Typical element types are both an eu.cdevreeze.yaidom.queryapi.ElemLike as well as a eu.cdevreeze.yaidom.queryapi.HasText.

  14. trait HasTextApi extends AnyRef

    Permalink

    Trait defining the contract for elements as text containers.

    Trait defining the contract for elements as text containers. Typical element types are both an eu.cdevreeze.yaidom.queryapi.ElemLike as well as a eu.cdevreeze.yaidom.queryapi.HasText.

  15. trait IsNavigable[E <: IsNavigable[E]] extends IsNavigableApi[E]

    Permalink

    API and implementation trait for elements that can be navigated using paths.

    API and implementation trait for elements that can be navigated using paths.

    More precisely, this trait has only the following abstract methods: findChildElemByPathEntry and findAllChildElemsWithPathEntries.

    The purely abstract API offered by this trait is eu.cdevreeze.yaidom.queryapi.IsNavigableApi. See the documentation of that trait for more information.

    E

    The captured element subtype

  16. trait IsNavigableApi[E <: IsNavigableApi[E]] extends AnyRef

    Permalink

    This trait offers Path-based navigation support.

    This trait offers Path-based navigation support.

    This trait typically does not show up in application code using yaidom, yet its (uniform) API does. Hence, it makes sense to read the documentation of this trait, knowing that the API is offered by multiple element implementations.

    This trait is purely abstract. The most common implementation of this trait is eu.cdevreeze.yaidom.queryapi.IsNavigable.

    IsNavigableApi more formally

    Some properties are expected to hold for "navigable elements":

    getElemOrSelfByPath(Path.Root) == self
    
    findElemOrSelfByPath(path1).flatMap(e => e.findElemOrSelfByPath(path2)) == findElemOrSelfByPath(path1.append(path2))
    E

    The captured element subtype

  17. trait ScopedElemApi[E <: ScopedElemApi[E]] extends ClarkElemApi[E] with HasQNameApi with HasScopeApi

    Permalink

    Shorthand for ClarkElemApi[E] with HasQNameApi with HasScopeApi with some additional methods that use the scope for resolving QName-valued text and attribute values.

    Shorthand for ClarkElemApi[E] with HasQNameApi with HasScopeApi with some additional methods that use the scope for resolving QName-valued text and attribute values. In other words, an element query API typically supported by element implementations, because most element implementations know about scopes, QNames, ENames and text content, as well as offering the ElemApi query API.

    Generic code abstracting over yaidom element implementations should either use this trait, or super-trait ClarkElemApi, depending on the abstraction level.

    ScopedElemApi more formally

    Scopes resolve QNames as ENames, so some properties are expected to hold for the element "name":

    this.scope.resolveQNameOption(this.qname) == Some(this.resolvedName)
    
    // Therefore:
    this.resolvedName.localPart == this.qname.localPart
    
    this.resolvedName.namespaceUriOption ==
      this.scope.prefixNamespaceMap.get(this.qname.prefixOption.getOrElse(""))

    For the attribute "name" properties, first define:

    val attributeScope = this.scope.withoutDefaultNamespace
    
    val resolvedAttrs = this.attributes map {
      case (attrQName, attrValue) =>
        val resolvedAttrName = attributeScope.resolveQNameOption(attrQName).get
        (resolvedAttrName -> attrValue)
    }

    Then the following must hold:

    resolvedAttrs.toMap == this.resolvedAttributes.toMap
    E

    The captured element subtype

  18. trait ScopedElemLike[E <: ScopedElemLike[E]] extends ScopedElemApi[E] with ClarkElemLike[E]

    Permalink

    Partial implementation of ScopedElemApi.

    Partial implementation of ScopedElemApi.

    E

    The captured element subtype

  19. trait SubtypeAwareElemApi[A <: SubtypeAwareElemApi[A]] extends ElemApi[A]

    Permalink

    Extension to ElemApi that makes querying for sub-types of the element type easy.

    Extension to ElemApi that makes querying for sub-types of the element type easy.

    For example, XML Schema can be modeled with an object hierarchy, starting with some XsdElem super-type which mixes in trait SubtypeAwareElemApi, among other query traits. The object hierarchy could contain sub-classes of XsdElem such as XsdRootElem, GlobalElementDeclaration, etc. Then the SubtypeAwareElemApi trait makes it easy to query for all or some global element declarations, etc.

    There is no magic in these traits: it is just ElemApi and ElemLike underneath. It is only the syntactic convenience that makes the difference.

    The query methods of this trait take a sub-type as first value parameter. It is intentional that this is a value parameter, and not a second type parameter, since it is conceptually the most important parameter of these query methods. (If it were a second type parameter instead, the article http://hacking-scala.org/post/73854628325/advanced-type-constraints-with-type-classes would show how to make that solution robust, using some @NotNothing annotation.)

    The sub-type parameter could have been a java.lang.Class object, except that type erasure would make it less attractive (when doing pattern matching against that type). Hence the use of a ClassTag parameter, which undoes type erasure for non-generic types, if available implicitly. So ClassTag is used as a better java.lang.Class, yet without polluting the public API with an implicit ClassTag parameter. (Instead, the ClassTag is made implicit inside the method implementations.)

  20. trait SubtypeAwareElemLike[A <: SubtypeAwareElemLike[A]] extends ElemLike[A] with SubtypeAwareElemApi[A]

    Permalink

    Default implementation of SubtypeAwareElemApi.

  21. trait TransformableElemApi[N, E <: N with TransformableElemApi[N, E]] extends AnyRef

    Permalink

    This is the element transformation part of the yaidom query and update API.

    This is the element transformation part of the yaidom query and update API. Only a few DOM-like element implementations in yaidom mix in this trait (indirectly, because some implementing sub-trait is mixed in), thus sharing this API.

    This trait typically does not show up in application code using yaidom, yet its (uniform) API does. Hence, it makes sense to read the documentation of this trait, knowing that the API is offered by multiple element implementations.

    This trait is purely abstract. The most common implementation of this trait is eu.cdevreeze.yaidom.queryapi.TransformableElemLike. That trait only knows how to transform child elements. Using this minimal knowledge, the trait offers methods to transform descendant elements and descendant-or-self elements. Indeed, the trait is similar to ElemLike, except that it transforms elements instead of querying for elements.

    The big conceptual difference with "updatable" elements (in trait UpdatableElemLike[N, E]) is that "transformations" are about applying some transforming function to an element tree, while "(functional) updates" are about "updates" at given paths.

    TransformableElemApi examples

    To illustrate the use of this API, consider the following example XML:

    <book:Bookstore xmlns:book="http://bookstore/book" xmlns:auth="http://bookstore/author">
      <book:Book ISBN="978-0321356680" Price="35" Edition="2">
        <book:Title>Effective Java (2nd Edition)</book:Title>
        <book:Authors>
          <auth:Author>
            <auth:First_Name>Joshua</auth:First_Name>
            <auth:Last_Name>Bloch</auth:Last_Name>
          </auth:Author>
        </book:Authors>
      </book:Book>
      <book:Book ISBN="978-0981531649" Price="35" Edition="2">
        <book:Title>Programming in Scala: A Comprehensive Step-by-Step Guide, 2nd Edition</book:Title>
        <book:Authors>
          <auth:Author>
            <auth:First_Name>Martin</auth:First_Name>
            <auth:Last_Name>Odersky</auth:Last_Name>
          </auth:Author>
          <auth:Author>
            <auth:First_Name>Lex</auth:First_Name>
            <auth:Last_Name>Spoon</auth:Last_Name>
          </auth:Author>
          <auth:Author>
            <auth:First_Name>Bill</auth:First_Name>
            <auth:Last_Name>Venners</auth:Last_Name>
          </auth:Author>
        </book:Authors>
      </book:Book>
    </book:Bookstore>

    Suppose this XML has been parsed into eu.cdevreeze.yaidom.simple.Elem variable named bookstoreElem. Then we can combine author first and last names as follows:

    val authorNamespace = "http://bookstore/author"
    
    bookstoreElem = bookstoreElem transformElems {
      case elem: Elem if elem.resolvedName == EName(authorNamespace, "Author") =>
        val firstName = (elem \ (_.localName == "First_Name")).headOption.map(_.text).getOrElse("")
        val lastName = (elem \ (_.localName == "Last_Name")).headOption.map(_.text).getOrElse("")
        val name = (firstName + " " + lastName).trim
        Node.textElem(QName("auth:Author"), elem.scope ++ Scope.from("auth" -> authorNamespace), name)
      case elem: Elem => elem
    }
    bookstoreElem = bookstoreElem.prettify(2)

    When using the TransformableElemApi API, keep the following in mind:

    • The transformElems and transformElemsOrSelf methods (and their Node sequence producing counterparts) may produce a lot of "garbage". If only a small portion of an element tree needs to be updated, the "update" methods in trait UpdatableElemApi may be a better fit.
    • Transformations operate in a bottom-up manner. This implies that parent scopes cannot be used for transforming child elements. Hence, namespace undeclarations may result, which are not allowed in XML 1.0 (except for the default namespace).

    Top-down transformations are still possible, by combining recursion with method transformChildElems (or transformChildElemsToNodeSeq). For example:

    def removePrefixedNamespaceUndeclarations(elem: Elem): Elem = {
      elem transformChildElems { e =>
        val newE = e.copy(scope = elem.scope.withoutDefaultNamespace ++ e.scope)
        removePrefixedNamespaceUndeclarations(newE)
      }
    }
    N

    The node supertype of the element subtype

    E

    The captured element subtype

  22. trait TransformableElemLike[N, E <: N with TransformableElemLike[N, E]] extends TransformableElemApi[N, E]

    Permalink

    API and implementation trait for transformable elements.

    API and implementation trait for transformable elements.

    More precisely, this trait has abstract methods transformChildElems and transformChildElemsToNodeSeq. Based on these abstract methods, this trait offers a rich API for transforming descendant elements or descendant-or-self elements.

    The purely abstract API offered by this trait is eu.cdevreeze.yaidom.queryapi.TransformableElemApi. See the documentation of that trait for examples of usage.

    N

    The node supertype of the element subtype

    E

    The captured element subtype

  23. trait UpdatableElemApi[N, E <: N with UpdatableElemApi[N, E]] extends IsNavigableApi[E]

    Permalink

    This is the functional update part of the yaidom uniform query API.

    This is the functional update part of the yaidom uniform query API. It is a sub-trait of trait eu.cdevreeze.yaidom.queryapi.IsNavigableApi. Only a few DOM-like element implementations in yaidom mix in this trait (indirectly, because some implementing sub-trait is mixed in), thus sharing this query API.

    This trait typically does not show up in application code using yaidom, yet its (uniform) API does. Hence, it makes sense to read the documentation of this trait, knowing that the API is offered by multiple element implementations.

    This trait is purely abstract. The most common implementation of this trait is eu.cdevreeze.yaidom.queryapi.UpdatableElemLike. The trait has all the knowledge of its super-trait, but in addition to that knows the following:

    • An element has child nodes, which may or may not be elements. Hence the extra type parameter for nodes.
    • An element knows the child node indexes of the path entries of the child elements.

    Obviously methods children, withChildren and collectChildNodeIndexes must be consistent with methods such as findAllChildElems.

    Using this minimal knowledge alone, trait UpdatableElemLike not only offers the methods of its parent trait, but also:

    • methods to functionally update an element by replacing, adding or deleting child nodes
    • methods to functionally update an element by replacing descendant-or-self elements at specified paths

    For the conceptual difference with "transformable" elements, see trait eu.cdevreeze.yaidom.queryapi.TransformableElemApi.

    This query API leverages the Scala Collections API. Query results can be manipulated using the Collections API, and the query API implementation (in UpdatableElemLike) uses the Collections API internally.

    UpdatableElemApi examples

    To illustrate the use of this API, consider the following example XML:

    <book:Bookstore xmlns:book="http://bookstore/book" xmlns:auth="http://bookstore/author">
      <book:Book ISBN="978-0321356680" Price="35" Edition="2">
        <book:Title>Effective Java (2nd Edition)</book:Title>
        <book:Authors>
          <auth:Author>
            <auth:First_Name>Joshua</auth:First_Name>
            <auth:Last_Name>Bloch</auth:Last_Name>
          </auth:Author>
        </book:Authors>
      </book:Book>
      <book:Book ISBN="978-0981531649" Price="35" Edition="2">
        <book:Title>Programming in Scala: A Comprehensive Step-by-Step Guide, 2nd Edition</book:Title>
        <book:Authors>
          <auth:Author>
            <auth:First_Name>Martin</auth:First_Name>
            <auth:Last_Name>Odersky</auth:Last_Name>
          </auth:Author>
          <auth:Author>
            <auth:First_Name>Lex</auth:First_Name>
            <auth:Last_Name>Spoon</auth:Last_Name>
          </auth:Author>
          <auth:Author>
            <auth:First_Name>Bill</auth:First_Name>
            <auth:Last_Name>Venners</auth:Last_Name>
          </auth:Author>
        </book:Authors>
      </book:Book>
    </book:Bookstore>

    Suppose this XML has been parsed into eu.cdevreeze.yaidom.simple.Elem variable named bookstoreElem. Then we can add a book as follows, where we "forget" the 2nd author for the moment:

    import convert.ScalaXmlConversions._
    
    val bookstoreNamespace = "http://bookstore/book"
    val authorNamespace = "http://bookstore/author"
    
    val fpBookXml =
      <book:Book xmlns:book="http://bookstore/book" xmlns:auth="http://bookstore/author" ISBN="978-1617290657" Price="33">
        <book:Title>Functional Programming in Scala</book:Title>
        <book:Authors>
          <auth:Author>
            <auth:First_Name>Paul</auth:First_Name>
            <auth:Last_Name>Chiusano</auth:Last_Name>
          </auth:Author>
        </book:Authors>
      </book:Book>
    val fpBookElem = convertToElem(fpBookXml)
    
    bookstoreElem = bookstoreElem.plusChild(fpBookElem)

    Note that the namespace declarations for prefixes book and auth had to be repeated in the Scala XML literal for the added book, because otherwise the convertToElem method would throw an exception (since Elem instances cannot be created unless all element and attribute QNames can be resolved as ENames).

    The resulting bookstore seems ok, but if we print convertElem(bookstoreElem), the result does not look pretty. This can be fixed if the last assignment is replaced by:

    bookstoreElem = bookstoreElem.plusChild(fpBookElem).prettify(2)

    knowing that an indentation of 2 spaces has been used throughout the original XML. Method prettify is expensive, so it is best not to invoke it within a tight loop. As an alternative, formatting can be left to the DocumentPrinter, of course.

    The assignment above is the same as the following one:

    bookstoreElem = bookstoreElem.withChildren(bookstoreElem.children :+ fpBookElem).prettify(2)

    There are several methods to functionally update the children of an element. For example, method plusChild is overloaded, and the other variant can insert a child at a given 0-based position. Other "children update" methods are minusChild, withPatchedChildren and withUpdatedChildren.

    Let's now turn to functional update methods that take Path instances or collections thereof. In the example above the second author of the added book is missing. Let's fix that:

    val secondAuthorXml =
      <auth:Author xmlns:auth="http://bookstore/author">
        <auth:First_Name>Runar</auth:First_Name>
        <auth:Last_Name>Bjarnason</auth:Last_Name>
      </auth:Author>
    val secondAuthorElem = convertToElem(secondAuthorXml)
    
    val fpBookAuthorsPaths =
      for {
        authorsPath <- indexed.Elem(bookstoreElem) filterElems { e => e.resolvedName == EName(bookstoreNamespace, "Authors") } map (_.path)
        if authorsPath.findAncestorPath(path => path.endsWithName(EName(bookstoreNamespace, "Book")) &&
          bookstoreElem.getElemOrSelfByPath(path).attribute(EName("ISBN")) == "978-1617290657").isDefined
      } yield authorsPath
    
    require(fpBookAuthorsPaths.size == 1)
    val fpBookAuthorsPath = fpBookAuthorsPaths.head
    
    bookstoreElem = bookstoreElem.updateElemOrSelf(fpBookAuthorsPath) { elem =>
      require(elem.resolvedName == EName(bookstoreNamespace, "Authors"))
      val rawResult = elem.plusChild(secondAuthorElem)
      rawResult transformElemsOrSelf (e => e.copy(scope = elem.scope.withoutDefaultNamespace ++ e.scope))
    }
    bookstoreElem = bookstoreElem.prettify(2)

    Clearly the resulting bookstore element is nicely formatted, but there was another possible issue that was taken into account. See the line of code transforming the "raw result". That line was added in order to prevent namespace undeclarations, which for XML version 1.0 are not allowed (with the exception of the default namespace). After all, the XML for the second author was created with only the auth namespace declared. Without the above-mentioned line of code, a namespace undeclaration for prefix book would have occurred in the resulting XML, thus leading to an invalid XML 1.0 element tree.

    To illustrate functional update methods taking collections of paths, let's remove the added book from the book store. Here is one (somewhat inefficient) way to do that:

    val bookPaths = indexed.Elem(bookstoreElem) filterElems (_.resolvedName == EName(bookstoreNamespace, "Book")) map (_.path)
    
    bookstoreElem = bookstoreElem.updateElemsWithNodeSeq(bookPaths.toSet) { (elem, path) =>
      if ((elem \@ EName("ISBN")) == Some("978-1617290657")) Vector() else Vector(elem)
    }
    bookstoreElem = bookstoreElem.prettify(2)

    There are very many ways to write this functional update, using different functional update methods in trait UpdatableElemApi, or even only using transformation methods in trait TransformableElemApi (thus not using paths).

    The example code above is enough to get started using the UpdatableElemApi methods, but it makes sense to study the entire API, and practice with it. Always keep in mind that functional updates typically mess up formatting and/or namespace (un)declarations, unless these aspects are taken into account.

    N

    The node supertype of the element subtype

    E

    The captured element subtype

  24. trait UpdatableElemLike[N, E <: N with UpdatableElemLike[N, E]] extends IsNavigable[E] with UpdatableElemApi[N, E]

    Permalink

    API and implementation trait for functionally updatable elements.

    API and implementation trait for functionally updatable elements. This trait extends trait eu.cdevreeze.yaidom.queryapi.IsNavigable, adding knowledge about child nodes in general, and about the correspondence between child path entries and child indexes.

    More precisely, this trait adds the following abstract methods to the abstract methods required by its super-trait: children, withChildren and collectChildNodeIndexes. Based on these abstract methods (and the super-trait), this trait offers a rich API for functionally updating elements.

    The purely abstract API offered by this trait is eu.cdevreeze.yaidom.queryapi.UpdatableElemApi. See the documentation of that trait for examples of usage, and for a more formal treatment.

    N

    The node supertype of the element subtype

    E

    The captured element subtype

Value Members

  1. object ElemApi

    Permalink
  2. object ElemWithPath

    Permalink
  3. object HasENameApi

    Permalink

    This companion object offers some convenience factory methods for "element predicates", that can be used in yaidom queries.

    This companion object offers some convenience factory methods for "element predicates", that can be used in yaidom queries. These factory objects turn ENames and local names into "element predicates".

    For example:

    elem \\ (_.ename == EName(xsNamespace, "element"))

    can also be written as:

    elem \\ withEName(xsNamespace, "element")

    (thus avoiding EName instance construction, whether or not this makes any difference in practice).

    If the namespace is "obvious", and more friendly local-name-based querying is desired, the following could be written:

    elem \\ withLocalName("element")
  4. object Nodes

    Permalink

    Abstract node (marker) trait hierarchy.

    Abstract node (marker) trait hierarchy. It offers a common minimal API for different kinds of nodes. It also shows what yaidom typically considers to be nodes, and what it does not consider to be nodes. For example, documents are not nodes in yaidom, so it is thus prevented to create documents as element children. Moreover, attributes are typically not nodes in yaidom, although custom element implementations may think otherwise.

    The down-side is that we have to consider mixing in these traits everywhere we create a node/element implementation.

  5. object XmlBaseSupport

    Permalink

    XML Base support, for elements implementing the ClarkElemApi query API.

    XML Base support, for elements implementing the ClarkElemApi query API.

    XML Base is very simple in its algorithm, given an optional start "document URI". Base URI computation for an element then starts with the optional document URI, and processes all XML Base attributes in the reverse ancestry-or-self of the element, resolving each XML Base attribute against the base URI computed so far. According to the XML Base specification, same-document references do not alter this algorithm.

    What is sensitive in XML Base processing is the resolution of an URI against an optional base URI. For example, resolving an empty URI using the java.net.URI.resolve method does not conform to RFC 3986 (see e.g. http://stackoverflow.com/questions/22203111/is-javas-uri-resolve-incompatible-with-rfc-3986-when-the-relative-uri-contains).

    This is why the user of this XML Base support must supply a strategy for resolving URIs against optional base URIs.

    Default attributes and entity resolution are out of scope for this XML Base support.

Inherited from AnyRef

Inherited from Any

Ungrouped