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.
- Alphabetic
- By Inheritance
- core
- AnyRef
- Any
- Hide All
- Show All
- Public
- All
Type Members
-
final
class
AbsolutePath extends AnyRef
Absolute path of a descendant (or self)
Elem
relative to and starting with a rootElem
.Absolute path of a descendant (or self)
Elem
relative to and starting with a rootElem
. It represents a unique absolute path to an element, given a root element, independent of other types of nodes, as if the XML tree only consists of elements.Unlike
Path
objects, which are navigation paths and relative to some root element, absolute paths are absolute, containing the root element (as absolute path entry) as well. The absolute paths can never be empty (unlike navigation paths).An eu.cdevreeze.yaidom.core.AbsolutePath corresponds to one and only one canonical path of the element, which is the corresponding (canonical) XPath expression. See http://ns.inria.org/active-tags/glossary/glossary.html#canonical-path. Note, however, that these XPath expression use URI qualified EQ-names rather than qualified names.
The
toString
format of absolute paths is the same as for the equally named class in Saxon. Note the Saxon absolute paths may be empty, whereas yaidom absolute paths can never be empty. After all, yaidom absolute paths can only be used for elements, not for documents. -
final
case class
AncestryPath(ancestorOrSelfEntries: List[Entry]) extends Product with Serializable
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 aPath
. 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.
-
final
case class
Declarations(prefixNamespaceMap: Map[String, String]) extends Product with Serializable
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. -
final
case class
EName(namespaceUriOption: Option[String], localPart: String) extends Product with Serializable
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.
-
trait
ENameProvider extends AnyRef
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.
-
final
class
Path extends AnyRef
Unique identification of a descendant (or self)
Elem
given a rootElem
.Unique identification of a descendant (or self)
Elem
given a rootElem
. 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 aPath.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 methodtoResolvedCanonicalXPath
.The
Path
contains anIndexedSeq
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. HencePath.Entry
instances each contain an element name plus index. -
final
class
PathBuilder extends AnyRef
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. - final case class PrefixedName(prefix: String, localPart: String) extends QName with Product with Serializable
-
sealed
trait
QName extends Serializable
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
QName
s:- eu.cdevreeze.yaidom.core.UnprefixedName, which only contains a local part
- eu.cdevreeze.yaidom.core.PrefixedName, which combines a non-empty prefix with a local part
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.
-
trait
QNameProvider extends AnyRef
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.
-
final
case class
Scope(prefixNamespaceMap: Map[String, String]) extends Product with Serializable
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, aScope
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 ClarkElemApi._ 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 aDeclarations
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 aDeclarations
. 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
andresolve
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 inscope1
andscope2
- Prefix
p
has different mappings inscope1
andscope2
- Prefix
p
only belongs toscope1
- Prefix
p
only belongs toscope2
- 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 toscope1
, 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 toscope2
, 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
andresolve
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 inscope
anddeclarations
(so no undeclaration) - Prefix
p
has different mappings inscope
anddeclarations
(but no undeclaration) - Prefix
p
belongs toscope
and is undeclared indeclarations
- Prefix
p
only belongs toscope
, and does not occur indeclarations
- Prefix
p
only occurs indeclarations
, without being undeclared, and does not occur inscope
- Prefix
p
only occurs indeclarations
, in an undeclaration, and does not occur inscope
- Prefix
p
neither occurs inscope
nor indeclarations
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. - Prefix
- final case class UnprefixedName(localPart: String) extends QName with Product with Serializable
-
final
case class
XmlDeclaration(version: String, encodingOption: Option[Charset], standaloneOption: Option[Boolean]) extends Product with Serializable
The XML Declaration, which can only occur as the first line in an XML document.
Value Members
- object AbsolutePath
- object AncestryPath extends Serializable
- object Declarations extends Serializable
- object EName extends Serializable
- object ENameProvider
- object Path
- object PathBuilder
-
object
PathConversions
Conversions between absolute paths and navigation paths.
- object QName extends Serializable
- object QNameProvider
- object Scope extends Serializable
- object XmlDeclaration extends Serializable