Converter from A
(which can be anything) to eu.cdevreeze.yaidom.simple.Document.
Converter from A
(which can be anything) to eu.cdevreeze.yaidom.simple.Document.
the type of the value to convert
Converter from A
(which can be anything) to eu.cdevreeze.yaidom.simple.Elem.
Converter from A
(which can be anything) to eu.cdevreeze.yaidom.simple.Elem.
the type of the value to convert
Builder of a yaidom Document.
Builder of a yaidom Document. Called DocBuilder
instead of DocumentBuilder
, because often a JAXP DocumentBuilder
is in scope too.
A DocBuilder
is itself not a NodeBuilder
.
A DocBuilder
is constructed from an optional URI, an optional XML declaration, a document element (as ElemBuilder
), top-level processing
instruction builders, if any, and top-level comment builders, if any.
Document
.
Document
. Although at first sight the document root element seems to be the root node, this is not entirely true.
For example, there may be comments at top level, outside the document root element.
The document is itself not a Node
. This choice has the following advantages:
toTreeRepr
should
not be passed a parent scope. By not considering a Document a Node, it is more visible that parent scopes
are irrelevant for Documents, unlike for "elements".A Document
is constructed from an optional URI, an optional XML declaration, a document element (as Elem
), top-level processing instructions,
if any, and top-level comments, if any.
Note that class Document
does not have any query methods for Elem
instances. In particular, the ElemApi
does not
apply to documents. Therefore, given a document, querying for elements (other than the document element itself) always goes
via the document element.
Converter from eu.cdevreeze.yaidom.simple.Document to A
(which can be anything, such as a DOM Document
).
Converter from eu.cdevreeze.yaidom.simple.Document to A
(which can be anything, such as a DOM Document
).
the result type of the conversion
Immutable, thread-safe element node.
Immutable, thread-safe element node. It is the default element implementation in yaidom. As the default element implementation among several alternative element implementations, it strikes a balance between loss-less roundtripping and composability.
The parsers and serializers in packages eu.cdevreeze.yaidom.parse and eu.cdevreeze.yaidom.print return and take
these default elements (or the corresponding Document
instances), respectively.
As for its query API, class eu.cdevreeze.yaidom.simple.Elem is among the most powerful element implementations offered by yaidom. These elements offer all of the eu.cdevreeze.yaidom.queryapi.ElemApi, eu.cdevreeze.yaidom.queryapi.UpdatableElemApi and eu.cdevreeze.yaidom.queryapi.TransformableElemApi query APIs, and more.
See the documentation of the mixed-in query API traits for more details on the uniform query API offered by this class.
The following example illustrates the use of the yaidom uniform query API in combination with some Elem-specific methods. In this XML scripting example the namespace prefix "xsd" is replaced by prefix "xs", including those in QName-valued attributes. The trivial XML file of this example is the following XML Schema:
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://book" elementFormDefault="qualified"> <xsd:element name="book"> <xsd:complexType> <xsd:sequence> <xsd:element name="isbn" type="xsd:string" /> <xsd:element name="title" type="xsd:string" /> <xsd:element name="authors" type="xsd:string" /> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:schema>
The edit action can be performed on this schemaElem
as follows, starting with some checks:
// All descendant-or-self elements have the same Scope, mapping only prefix "xsd". require(schemaElem.findAllElemsOrSelf.map(_.scope).distinct == List(Scope.from("xsd" -> "http://www.w3.org/2001/XMLSchema"))) // All descendant-or-self elements have a QName with prefix "xsd". require(schemaElem.findAllElemsOrSelf.map(_.qname.prefixOption).distinct == List(Some("xsd"))) // All descendant-or-self elements have unprefixed attributes only. require(schemaElem.findAllElemsOrSelf.flatMap(_.attributes.toMap.keySet.map(_.prefixOption)).distinct == List(None)) // All descendant-or-self elements with "type" attributes contain only QNames with prefix "xsd" in the values of those attributes. require(schemaElem.filterElemsOrSelf(e => (e \@ EName("type")).isDefined).forall(e => e.attributeAsQName(EName("type")).prefixOption == Some("xsd"))) // Replaces prefix "xsd" by "xs" throughout the element tree, including in "type" attributes. val editedSchemaElem = schemaElem transformElemsOrSelf { elem => val newScope = (elem.scope -- Set("xsd")) ++ Scope.from("xs" -> "http://www.w3.org/2001/XMLSchema") val newQName = QName("xs", elem.qname.localPart) val newTypeAttrOption = elem.attributeAsQNameOption(EName("type")).map(attr => QName("xs", attr.localPart).toString) elem.copy(qname = newQName, scope = newScope).plusAttributeOption(QName("type"), newTypeAttrOption) }
Note that besides the uniform query API, this example uses some Elem
-specific methods, such as attributeAsQName
, copy
and
plusAttributeOption
.
Class Elem
is immutable, and (should be) thread-safe. Hence, Elems do not know about their parent element, if any.
An Elem has the following state:
Note that namespace declarations are not considered to be attributes in Elem
, just like in the rest of yaidom.
Elem construction is unsuccessful if the element name and/or some attribute names cannot be resolved using the Scope
of the
element (ignoring the default namespace, if any, for attributes). As can be seen from the above-mentioned state,
namespaces are first-class citizens.
Elems can (relatively easily) be constructed manually in a bottom-up manner. Yet care must be taken to give the element and its
descendants the correct Scope
. Otherwise it is easy to introduce (prefixed) namespace undeclarations, which are not
allowed in XML 1.0. The underlying issue is that functional Elem trees are created in a bottom-up manner,
whereas namespace scoping works in a top-down manner. This is not a big issue in practice, since manual Elem creation
is rather rare, and it is always possible to call method notUndeclaringPrefixes
afterwards. An alternative method to create
element trees by hand uses class eu.cdevreeze.yaidom.simple.ElemBuilder. A manually created ElemBuilder
can be converted to
an Elem
by calling method build
.
Round-tripping (parsing and serializing) is not entirely loss-less, but (in spite of the good composability and rather small
state) not much is lost. Comments, processing instructions and entity references are retained. Attribute order is retained,
although according to the XML Infoset this order is irrelevant. Namespace declaration order is not necessarily retained,
however. Superfluous namespace declarations are also lost. (That is because namespace declarations are not explicitly
stored in Elems, but are implicit, viz. parentElem.scope.relativize(this.scope)
). The short versus long form of an empty
element is also not remembered.
Equality has not been defined for class Elem
(that is, it is reference equality). There is no clear sensible notion of equality
for XML trees at the abstraction level of Elem
. For example, think about prefixes, "ignorable whitespace", DTDs and XSDs, etc.
Builder for elements.
Builder for elements. See eu.cdevreeze.yaidom.simple.NodeBuilder.
See the documentation of the mixed-in query API trait(s) for more details on the uniform query API offered by this class.
Converter from eu.cdevreeze.yaidom.simple.Elem to A
(which can be anything, such as a DOM Element
).
Converter from eu.cdevreeze.yaidom.simple.Elem to A
(which can be anything, such as a DOM Element
).
the result type of the conversion
An entity reference.
An entity reference. For example:
&hello;
We obtain this entity reference as follows:
EntityRef("hello")
Immutable XML Node.
Immutable XML Node. It is the default XML node type in yaidom. There are subclasses for different types of nodes, such as elements, text nodes, comments, entity references and processing instructions. See eu.cdevreeze.yaidom.simple.Elem for the default element type in yaidom.
DSL to build Elem
s (or Document
s) without having to pass parent Scope
s around.
DSL to build Elem
s (or Document
s) without having to pass parent Scope
s around.
For example:
import NodeBuilder._ elem( qname = QName("dbclass:Magazine"), attributes = Vector(QName("Month") -> "February", QName("Year") -> "2009"), namespaces = Declarations.from("dbclass" -> "http://www.db-class.org"), children = Vector( elem( qname = QName("dbclass:Title"), children = Vector(text("Newsweek"))))).build()
The latter expression could also be written as follows:
elem( qname = QName("dbclass:Magazine"), attributes = Vector(QName("Month") -> "February", QName("Year") -> "2009"), namespaces = Declarations.from("dbclass" -> "http://www.db-class.org"), children = Vector( textElem(QName("dbclass:Title"), "Newsweek"))).build()
There is an impedance mismatch between XML's scoping rules (which are top-down, from root to leaves) and "functional trees"
(which are built bottom-up, from leaves to root). In the context of the Anti-XML library, Daniel Spiewak explained this
impedance mismatch in https://github.com/djspiewak/anti-xml/issues/78. In yaidom, however, this impedance mismatch
is far less severe. Yaidom distinguishes between eu.cdevreeze.yaidom.simple.Node and eu.cdevreeze.yaidom.simple.NodeBuilder,
and eu.cdevreeze.yaidom.simple.Elem and eu.cdevreeze.yaidom.simple.ElemBuilder in particular. Elem
s have (fixed, resolved) Scope
s,
but ElemBuilder
s do not. Using NodeBuilder
s, Scope
determination is postponed. Only ElemBuilder
s
can have unbound prefixes, but only Elem
s have (resolved) scopes. Instead of a eu.cdevreeze.yaidom.core.Scope, an ElemBuilder
has a eu.cdevreeze.yaidom.core.Declarations.
Another reason that the above-mentioned impedance mismatch is less of a problem in practice is that typically the XML
trees (as NodeBuilder
s or directly as Node
s) are built in a top-down manner. The eu.cdevreeze.yaidom.simple.ConverterToDocuments
in package eu.cdevreeze.yaidom.convert recursively build Elem
s in a top-down manner, possibly creating an Elem
instance (for each element) twice (first without children, and finally as a copy with children added).
When using NodeBuilder
s to create a Document
, this Document
typically contains no "ignorable whitespace". This may cause
the Document
not to be pretty-printed when using a (default) eu.cdevreeze.yaidom.print.DocumentPrinter to convert the Document
to an XML string. See also the classes in package eu.cdevreeze.yaidom.print.
This singleton object contains a DSL to easily create deeply nested Elems.
This singleton object contains a DSL to easily create deeply nested Elems. It looks a lot like the DSL for NodeBuilders, using the same method names (so a local import for Node singleton object members may be needed).
There is a catch, though. When using this DSL, scopes must be passed throughout the tree. These Scopes should typically be the same (or parent element scopes should be subscopes of child element scopes), because otherwise the corresponding XML may contain a lot of namespace undeclarations.
Another thing to watch out for is that the "tree representations" conform to the NodeBuilder DSL, not to this one.
In summary, the NodeBuilder DSL does have the advantage over this DSL that scopes do not have to be passed around. On the other hand, this Node DSL has the advantage that exceptions due to missing scope data are thrown immediately instead of later (when calling the build method, in the case of the NodeBuilder DSL).
For example:
import Node._ val scope = Scope.from("dbclass" -> "http://www.db-class.org") elem( qname = QName("dbclass:Magazine"), attributes = Vector(QName("Month") -> "February", QName("Year") -> "2009"), scope = scope, children = Vector( elem( qname = QName("dbclass:Title"), scope = scope, children = Vector(text("Newsweek")))))
The latter expression could also be written as follows:
elem( qname = QName("dbclass:Magazine"), attributes = Vector(QName("Month") -> "February", QName("Year") -> "2009"), scope = scope, children = Vector( textElem(QName("dbclass:Title"), scope, "Newsweek")))
This package contains the default element implementation.
This package depends only on the core and queryapi packages in yaidom, but many other packages do depend on this one.