Package

eu.cdevreeze.yaidom

core

Permalink

package core

This package contains the core concepts, such as qualified names, expanded names, namespace declarations, in-scope namespaces, paths and path builders.

This package depends on no other packages in yaidom, but almost all other packages do depend on this one.

Linear Supertypes
AnyRef, Any
Ordering
  1. Alphabetic
  2. By Inheritance
Inherited
  1. core
  2. AnyRef
  3. Any
  1. Hide All
  2. Show All
Visibility
  1. Public
  2. All

Type Members

  1. final case class AncestryPath(ancestorOrSelfEntries: List[Entry]) extends Immutable with Product with Serializable

    Permalink

    The ancestry path of an element in an element tree.

    The ancestry path of an element in an element tree. The first entry represents the "current" element, and the last one the "root" element. Each entry is like an element without its children, but with the element QName, Scope and attributes (mapping QNames to attribute values).

    An AncestryPath is quite different from a Path. The former represents the ancestry-or-self of an element in an element tree, while the latter represents a series of steps to navigate through an element tree, to descendant-or-self elements. Ancestry paths have no knowledge of the siblings of "this" element or of its ancestors, while (navigation) paths can be used to navigate to any descendant-or-self element. Hence an ancestry path is a property of an element in a tree, whereas a (navigation) path is a navigation notion (through an element tree). The latter should really be called NavigationPath. Both concepts have in common that they only know about element nodes.

    An AncestryPath can be thought of as a stack where the head is the "current" element and the tail is the ancestor elements.

  2. final case class Declarations(prefixNamespaceMap: Map[String, String]) extends Immutable with Product with Serializable

    Permalink

    Namespace declarations (and undeclarations), typically at the level of one element.

    Namespace declarations (and undeclarations), typically at the level of one element.

    For example, consider the following 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:Bookstore>

    Then only the root element contains namespace declarations, viz.:

    Declarations.from("book" -> "http://bookstore/book", "auth" -> "http://bookstore/author")

    The Declarations is backed by a map from prefixes (or the empty string for the default namespace) to namespace URIs (or the empty string). If the mapped value is the empty string, it is an undeclaration.

    Prefix 'xml' is not allowed as key in this map. That prefix, mapping to namespace URI 'http://www.w3.org/XML/1998/namespace', is always available, without needing any declaration.

    This class does not depend on the Scope class.

    There are no methods for subset relationships on namespace declarations (unlike for class Scope). After all, in the presence of namespace undeclarations, such a subset relationship would become a bit unnatural.

  3. final case class EName(namespaceUriOption: Option[String], localPart: String) extends Immutable with Product with Serializable

    Permalink

    Expanded name.

    Expanded name. See http://www.w3.org/TR/xml-names11/. It has a localPart and an optional namespace URI. Semantically like a QName in Java, but not keeping the prefix.

    To get an eu.cdevreeze.yaidom.core.EName from a eu.cdevreeze.yaidom.core.QName, the latter needs to be resolved against a eu.cdevreeze.yaidom.core.Scope.

    The short class name illustrates that expanded names are at least as important as qualified names, and should be equally easy to construct (using the companion object).

    Typical usage may lead to an explosion of different EName objects that are equal. Therefore, application code is encouraged to define and use constants for frequently used ENames. For example, for the XML Schema namespace (and analogous to the XLink constants in yaidom):

    val XsNamespace = "http://www.w3.org/2001/XMLSchema"
    
    val XsElementEName = EName(XsNamespace, "element")
    val XsAttributeEName = EName(XsNamespace, "attribute")
    val XsComplexTypeEName = EName(XsNamespace, "complexType")
    val XsSimpleTypeEName = EName(XsNamespace, "simpleType")
    // ...

    In this example, the EName constant names are in upper camel case, starting with the ("preferred") prefix, followed by the local part, and ending with suffix "EName".

    Implementation note: It was tried as alternative implementation to define EName as (Scala 2.10) value class. The EName would then wrap the expanded name as string representation (in James Clark notation). One cost would be that parsing the (optional) namespace URI and the local name would occur far more frequently. Another cost would be that the alternative implementation would not directly express that an EName is a combination of an optional namespace URI and a local part. Therefore that alternative implementation has been abandoned.

  4. trait ENameProvider extends AnyRef

    Permalink

    Provider of ENames, possibly from a cache of ENames.

    Provider of ENames, possibly from a cache of ENames. Typical implementations cache EName instances, to prevent any explosion of equal EName instances, thus unnecessarily increasing the memory footprint.

    Implementation notes

    It may seem rather lame that only the global ENameProvider variable can be updated. On the other hand, implicit ENameProvider parameters in many places in the API would change the API quite a bit. These implicit parameters would be implementation details leaking into the API. It was therefore decided not to introduce those implicit parameters, with the exception of only a few places inside implementation code in the library.

  5. final class Path extends Immutable

    Permalink

    Unique identification of a descendant (or self) Elem given a root Elem.

    Unique identification of a descendant (or self) Elem given a root Elem. It represents a unique path to an element, given a root element, independent of other types of nodes, as if the XML tree only consists of elements.

    In other words, a Path is a sequence of instructions, each of them stating how to get to a specific child element. Each such instruction is a Path.Entry. So Paths do not contain the root element, and we can talk about Paths in isolation, without referring to any specific DOM-like tree.

    For example, consider the following 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>

    Then the last name of the first author of the Scala book (viz. Odersky) has the following path:

    Path.from(
      EName("{http://bookstore/book}Book") -> 1,
      EName("{http://bookstore/book}Authors") -> 0,
      EName("{http://bookstore/author}Author") -> 0,
      EName("{http://bookstore/author}Last_Name") -> 0
    )

    or:

    PathBuilder.from(
      QName("book:Book") -> 1,
      QName("book:Authors") -> 0,
      QName("auth:Author") -> 0,
      QName("auth:Last_Name") -> 0).build(Scope.from("book" -> "http://bookstore/book", "auth" -> "http://bookstore/author"))

    Path instances are useful when navigating (see eu.cdevreeze.yaidom.queryapi.IsNavigable), and in "functional updates" (see eu.cdevreeze.yaidom.queryapi.UpdatableElemLike).

    An eu.cdevreeze.yaidom.core.Path corresponds to one and only one canonical path of the element (modulo prefix names), which is the corresponding (canonical) XPath expression. See http://ns.inria.org/active-tags/glossary/glossary.html#canonical-path. There is one catch, though. The Path does not know the root element name, so that is not a part of the corresponding canonical XPath expression. See the documentation of method toResolvedCanonicalXPath.

    The Path contains an IndexedSeq of path entries for a specific child element, grandchild element etc., but the (root) element itself is referred to by an empty list of path entries.

    As an alternative to class Path, each element in a tree could be uniquely identified by "path entries" that only contained a child index instead of an element name plus element child index (of element children with the given name). Yet that would be far less easy to use. Hence Path.Entry instances each contain an element name plus index.

  6. final class PathBuilder extends Immutable

    Permalink

    Builder for Path instances.

    Builder for Path instances.

    For example:

    val path: Path = PathBuilder.from(QName("parent") -> 0, QName("child") -> 2).build(Scope.Empty)

    Note that the indexes are 0-based. Also note that the Scope passed to the build method must be invertible. Otherwise the resolution of QNames can break the indexes of the path builder components.

  7. final case class PrefixedName(prefix: String, localPart: String) extends QName with Product with Serializable

    Permalink
  8. sealed trait QName extends Immutable with Serializable

    Permalink

    Qualified name.

    Qualified name. See http://www.w3.org/TR/xml-names11/. It is the combination of an optional prefix and a mandatory local part. It is not like a QName in Java, which is more like what yaidom calls an expanded name.

    There are 2 types of QNames:

    QNames are meaningless outside their scope, which resolves the QName as an eu.cdevreeze.yaidom.core.EName.

    Typical usage may lead to an explosion of different QName objects that are equal. Therefore, application code is encouraged to define and use constants for frequently used QNames. For example, for the XML Schema namespace:

    val XsPrefix = "xs"
    
    val XsElementQName = QName(XsPrefix, "element")
    val XsAttributeQName = QName(XsPrefix, "attribute")
    val XsComplexTypeQName = QName(XsPrefix, "complexType")
    val XsSimpleTypeQName = QName(XsPrefix, "simpleType")
    // ...

    In this example, the QName constant names are in upper camel case, starting with the prefix, followed by the local part, and ending with suffix "QName".

    Implementation note: It was tried as alternative implementation to define the QName subclasses as (Scala 2.10) value classes. The QName would then wrap the qualified name as string representation (with or without prefix). One cost would be that parsing the (optional) prefix and the local name would occur far more frequently. Another cost would be that the alternative implementation would not directly express that a QName is a combination of an optional prefix and a local part. Therefore that alternative implementation has been abandoned.

  9. trait QNameProvider extends AnyRef

    Permalink

    Provider of QNames, possibly from a cache of QNames.

    Provider of QNames, possibly from a cache of QNames. Typical implementations cache QName instances, to prevent any explosion of equal QName instances, thus unnecessarily increasing the memory footprint.

    Implementation notes

    It may seem rather lame that only the global QNameProvider variable can be updated. On the other hand, implicit QNameProvider parameters in many places in the API would change the API quite a bit. These implicit parameters would be implementation details leaking into the API. It was therefore decided not to introduce those implicit parameters, with the exception of only a few places inside implementation code in the library.

  10. final case class Scope(prefixNamespaceMap: Map[String, String]) extends Immutable with Product with Serializable

    Permalink

    Scope mapping prefixes to namespace URIs, as well as holding an optional default namespace.

    Scope mapping prefixes to namespace URIs, as well as holding an optional default namespace. In other words, in-scope namespaces.

    The purpose of a eu.cdevreeze.yaidom.core.Scope is to resolve eu.cdevreeze.yaidom.core.QNames as eu.cdevreeze.yaidom.core.ENames.

    For example, consider the following XML:

    <book:Bookstore xmlns:book="http://bookstore/book">
      <book:Book ISBN="978-0321356680" Price="35" Edition="2">
        <book:Title>Effective Java (2nd Edition)</book:Title>
        <book:Authors>
          <auth:Author xmlns:auth="http://bookstore/author">
            <auth:First_Name>Joshua</auth:First_Name>
            <auth:Last_Name>Bloch</auth:Last_Name>
          </auth:Author>
        </book:Authors>
      </book:Book>
    </book:Bookstore>

    Then the (only) author element has the following scope:

    Scope.from("book" -> "http://bookstore/book", "auth" -> "http://bookstore/author")

    After all, the root element has the following scope:

    Scope.Empty.resolve(Declarations.from("book" -> "http://bookstore/book"))

    which is the same as:

    Scope.from("book" -> "http://bookstore/book")

    The (only) book element has no namespace declarations, so it has the same scope. That is also true for the authors element inside the book element. The (only) author element introduces a new namespace, and its scope is as follows:

    Scope.from("book" -> "http://bookstore/book").resolve(Declarations.from("auth" -> "http://bookstore/author"))

    which is indeed:

    Scope.from("book" -> "http://bookstore/book", "auth" -> "http://bookstore/author")

    The author element QName("auth:Author") has (optional) resolved name:

    Scope.from("book" -> "http://bookstore/book", "auth" -> "http://bookstore/author").resolveQNameOption(QName("auth:Author"))

    which is:

    Some(EName("{http://bookstore/author}Author"))

    A Scope must not contain prefix "xmlns" and must not contain namespace URI "http://www.w3.org/2000/xmlns/". Moreover, a Scope must not contain the XML namespace (prefix "xml", namespace URI "http://www.w3.org/XML/1998/namespace").

    The Scope is backed by a map from prefixes (or the empty string for the default namespace) to (non-empty) namespace URIs.

    This class depends on Declarations, but not the other way around.

    Concise querying using QNames which are converted to ENames

    EName-based querying is more robust than QName-based querying, but ENames are more verbose than QNames. We can get the best of both ENames and QNames by querying using (concise) QNames, and by using a Scope converting them to (stable) ENames.

    For example:

    val scope = Scope.from("xs" -> "http://www.w3.org/2001/XMLSchema")
    import scope._
    
    val elemDecls = schemaElem \\ withEName(QName("xs", "element").res)

    This is exactly equivalent to the following query:

    import HasENameApi._
    
    val elemDecls = schemaElem \\ withEName("http://www.w3.org/2001/XMLSchema", "element")

    Scope more formally

    In order to get started using the class, this more formal section can safely be skipped. On the other hand, this section may provide a deeper understanding of the class.

    Method resolve resolves a Declarations against this Scope, returning a new Scope. It could be defined by the following equality:

    scope.resolve(declarations) == {
      val m = (scope.prefixNamespaceMap ++ declarations.withoutUndeclarations.prefixNamespaceMap) --
        declarations.retainingUndeclarations.prefixNamespaceMap.keySet
      Scope(m)
    }

    The actual implementation may be more efficient than that, but it is consistent with this definition.

    Method relativize relativizes a Scope against this Scope, returning a Declarations. It could be defined by the following equality:

    scope1.relativize(scope2) == {
      val declared = scope2.prefixNamespaceMap filter { case (pref, ns) => scope1.prefixNamespaceMap.getOrElse(pref, "") != ns }
      val undeclared = scope1.prefixNamespaceMap.keySet -- scope2.prefixNamespaceMap.keySet
      Declarations(declared) ++ Declarations.undeclaring(undeclared)
    }

    Again, the actual implementation may be more efficient than that, but it is consistent with this definition.

    1. Property about two Scopes, and its proof

    Methods relativize and resolve obey the following equality:

    scope1.resolve(scope1.relativize(scope2)) == scope2

    Below follows the proof. We distinguish among the following cases:

    • Prefix p has the same mappings in scope1 and scope2
    • Prefix p has different mappings in scope1 and scope2
    • Prefix p only belongs to scope1
    • Prefix p only belongs to scope2
    • Prefix p belongs to neither scope

    Prefix p can be the empty string, for the default namespace. For each of these cases, we prove that:

    scope1.resolve(scope1.relativize(scope2)).prefixNamespaceMap.get(p) == scope2.prefixNamespaceMap.get(p)

    Since there are no other cases, that would complete the proof.

    If prefix p has the same mappings in both scopes, then:

    scope1.relativize(scope2).prefixNamespaceMap.get(p).isEmpty

    so the following equalities hold:

    scope1.resolve(scope1.relativize(scope2)).prefixNamespaceMap(p)
    scope1.prefixNamespaceMap(p)
    scope2.prefixNamespaceMap(p)

    so:

    scope1.resolve(scope1.relativize(scope2)).prefixNamespaceMap.get(p) == scope2.prefixNamespaceMap.get(p)

    If prefix p has different mappings in both scopes, then:

    scope1.relativize(scope2).prefixNamespaceMap(p) == scope2.prefixNamespaceMap(p)
    scope1.resolve(scope1.relativize(scope2)).prefixNamespaceMap(p) == scope2.prefixNamespaceMap(p)
    scope1.resolve(scope1.relativize(scope2)).prefixNamespaceMap.get(p) == scope2.prefixNamespaceMap.get(p)

    If prefix p only belongs to scope1, then:

    scope1.relativize(scope2).prefixNamespaceMap(p) == "" // undeclaration
    scope1.resolve(scope1.relativize(scope2)).prefixNamespaceMap.get(p).isEmpty
    scope1.resolve(scope1.relativize(scope2)).prefixNamespaceMap.get(p) == scope2.prefixNamespaceMap.get(p) // both empty

    if prefix p only belongs to scope2, then:

    scope1.relativize(scope2).prefixNamespaceMap(p) == scope2.prefixNamespaceMap(p)
    scope1.resolve(scope1.relativize(scope2)).prefixNamespaceMap(p) == scope2.prefixNamespaceMap(p)
    scope1.resolve(scope1.relativize(scope2)).prefixNamespaceMap.get(p) == scope2.prefixNamespaceMap.get(p)

    if prefix p belongs to neither scope, then obviously:

    scope1.resolve(scope1.relativize(scope2)).prefixNamespaceMap.get(p).isEmpty
    scope1.resolve(scope1.relativize(scope2)).prefixNamespaceMap.get(p) == scope2.prefixNamespaceMap.get(p) // both empty
    2. Property about Scope and Declarations

    Methods relativize and resolve also obey the following equality:

    scope.relativize(scope.resolve(declarations)) == scope.minimize(declarations)

    where scope.minimize(declarations) is defined by the following equality:

    scope.minimize(declarations) == {
      val declared = declarations.withoutUndeclarations.prefixNamespaceMap filter { case (pref, ns) => scope.prefixNamespaceMap.getOrElse(pref, "") != ns }
      val undeclared = declarations.retainingUndeclarations.prefixNamespaceMap.keySet.intersect(scope.prefixNamespaceMap.keySet)
      Declarations(declared) ++ Declarations.undeclaring(undeclared)
    }

    It can be proven by distinguishing among the following cases:

    • Prefix p has the same mappings in scope and declarations (so no undeclaration)
    • Prefix p has different mappings in scope and declarations (but no undeclaration)
    • Prefix p belongs to scope and is undeclared in declarations
    • Prefix p only belongs to scope, and does not occur in declarations
    • Prefix p only occurs in declarations, without being undeclared, and does not occur in scope
    • Prefix p only occurs in declarations, in an undeclaration, and does not occur in scope
    • Prefix p neither occurs in scope nor in declarations

    Prefix p can be the empty string, for the default namespace. For each of these cases, it can be proven that:

    scope.relativize(scope.resolve(declarations)).prefixNamespaceMap.get(p) == scope.minimize(declarations).prefixNamespaceMap.get(p)

    Since there are no other cases, that would complete the proof. The proof itself is left as an exercise for the reader, as they say.

    This and the preceding (proven) property are analogous to corresponding properties in the URI class.

  11. final case class UnprefixedName(localPart: String) extends QName with Product with Serializable

    Permalink
  12. final case class XmlDeclaration(version: String, encodingOption: Option[Charset], standaloneOption: Option[Boolean]) extends Immutable with Product with Serializable

    Permalink

    The XML Declaration, which can only occur as the first line in an XML document.

Value Members

  1. object AncestryPath extends Serializable

    Permalink
  2. object Declarations extends Serializable

    Permalink
  3. object EName extends Serializable

    Permalink
  4. object ENameProvider

    Permalink
  5. object Path

    Permalink
  6. object PathBuilder

    Permalink
  7. object QName extends Serializable

    Permalink
  8. object QNameProvider

    Permalink
  9. object Scope extends Serializable

    Permalink
  10. object XmlDeclaration extends Serializable

    Permalink

Inherited from AnyRef

Inherited from Any

Ungrouped