Yaidom is een uniforme XML query/transformatie API, geschreven in Scala. De API maakt gebruik van Scala's Collections API en sluit er goed op aan. Daarnaast biedt yaidom diverse specifieke DOM-achtige implementaties achter dezelfde uniforme XML query API.
In dit artikel wordt de yaidom library geïntroduceerd, gebruik maken van voorbeelden uit XBRL (eXtensible Business Reporting Language).
De ontwikkelaars bij EBPI maken vooral bedrijfskritische keteninformatiesystemen. Dit zijn organisatie-overstijgende informatiesystemen. Complexe gegevensverwerking is dan ook bekend terrein voor EBPI. Veel van de verwerkte data is in XML formaat (meestal conformerend aan een XML Schema). Een groot deel van deze XML data is in XBRL formaat. XBRL is een belangrijke standaard voor bedrijfsrapportages (zoals jaarrekeningen en belastingaangiften). Zie Getting Started for Developers voor een beknopte en heldere uitleg over de basis van XBRL. In deze blog gebruiken we XBRL als voorbeeld voor complexe XML processing. Voorkennis over XBRL wordt niet verondersteld om dit artikel te volgen. Basiskennis van XML en van de Scala programmeertaal worden wel verwacht.
XML processing wordt typisch gedaan door inzet van standaarden als XPath, XSLT en XQuery. Het fundament daarvan is het XPath Data Model, samen met o.a. de standaard functie library. XSLT en XQuery zijn complexe standaarden met een fikse leercurve, zeker als deze standaarden worden ingezet voor XBRL processing.
Ontwikkelen in Scala (en Java) rust daarentegen op een heel ander fundament, namelijk de JVM, de Scala (of Java) programmeertaal, en talloze Java en Scala libraries. Bij gebruik van Scala is de Scala Collections API vermoedelijk de belangrijkste van de ingezette Scala API's. Als we non-triviale XML processing doen op de JVM, hebben we dus typisch 2 expertise-sets nodig: ontwikkelexpertise op het Java platform, en (standaard) XML processing expertise, zoals aangegeven in de vorige alinea.
Wat nu als we een basis van Scala (en Java) development expertise zouden inzetten voor XML processing, zonder gebruik te maken van XPath, XSLT en/of XQuery? Zouden we op die basis nog maar een kleine brug nodig hebben om hiervan een krachtige XML processing stack te maken? Zo'n brug is inderdaad mogelijk, en is door EBPI ontwikkeld. De library heet yaidom. Yaidom is een XML query en transformatie API. Het is een alternatief voor Scala's eigen XML library, gekenmerkt door uitstekende interoperabiliteit met JAXP, Saxon(-EE) en andere XML "backends". Zie yaidom op GitHub en yaidom op GitHub pages. Het is de bedoeling dat de library spoedig wordt overgebracht naar het EBPI GitHub account, met behoud van de open source Apache 2.0 licentie.
In deze blog wordt XML processing met Scala en yaidom gedemonstreerd aan de hand van een XBRL voorbeeld. Dit voorbeeld is afkomstig van Charles Hoffman, ook wel bekend als de "vader van XBRL". De bestanden van het voorbeeld zijn te vinden op zijn site. In het vervolg van het artikel wordt getoond hoe Scala en yaidom kunnen helpen om XML processing in het algemeen en XBRL processing in het bijzonder eenvoudiger te maken. Er wordt vervolgens toegewerkt naar een voorbeeld van een validatie op het XBRL voorbeeld-rapport. Deze validatie is een zogenaamde "value assertion". Deze wordt gesimuleerd door Scala code, met gebruik van yaidom. Hoewel het doorgronden van "value assertions" in XBRL moeilijk is zonder voldoende basiskennis van XBRL, is de simulatie daarvan in Scala code eenvoudig te begrijpen voor Scala ontwikkelaars. Dat is dan ook het punt van deze blog: Scala en yaidom (met eventueel een "XML backend" als Saxon-EE voor o.a. type-informatie) kunnen complexe XML processing eenvoudiger maken (met name voor Scala ontwikkelaars).
Het vervolg van het artikel is als volgt georganiseerd:
XBRL (eXtensible Business Reporting Language) is een standaard voor bedrijfsrapportages. Typische XBRL rapportages zijn financieel van aard. XBRL rapporten ("XBRL instanties") zijn XML documenten, die een gespecificeerde structuur volgen.
Stel dat we willen rapporteren dat voor een gegeven organisatie (CIK-nummer: 1234567890) het gemiddelde aantal werknemers in 2003 220 bedroeg, en dat de corresponderende aantallen in 2004 en 2005
respectievelijk 240 en 250 waren. Preciezer gezegd in XBRL terminologie, concept gaap:AverageNumberEmployees
(beschreven door de zogenaamde US-GAAP taxonomie) heeft de
waarde 220
in de gegeven context (CIK-nummer 1234567890, jaar 2003). Dan kunnen we bovenstaande 3 facts als volgt representeren in "XBRL instantie"-formaat:
<xbrl xmlns="http://www.xbrl.org/2003/instance" xmlns:gaap="http://xasb.org/gaap">
<context id="D-2003">
<entity>
<identifier scheme="http://www.sec.gov/CIK">1234567890</identifier>
</entity>
<period>
<startDate>2003-01-01</startDate>
<endDate>2003-12-31</endDate>
</period>
</context>
<context id="D-2004">
<entity>
<identifier scheme="http://www.sec.gov/CIK">1234567890</identifier>
</entity>
<period>
<startDate>2004-01-01</startDate>
<endDate>2004-12-31</endDate>
</period>
</context>
<context id="D-2005">
<entity>
<identifier scheme="http://www.sec.gov/CIK">1234567890</identifier>
</entity>
<period>
<startDate>2005-01-01</startDate>
<endDate>2005-12-31</endDate>
</period>
</context>
<unit id="U-Pure">
<measure>pure</measure>
</unit>
<gaap:AverageNumberEmployees
contextRef="D-2003"
unitRef="U-Pure"
decimals="INF">220</gaap:AverageNumberEmployees>
<gaap:AverageNumberEmployees
contextRef="D-2004"
unitRef="U-Pure"
decimals="INF">240</gaap:AverageNumberEmployees>
<gaap:AverageNumberEmployees
contextRef="D-2005"
unitRef="U-Pure"
decimals="INF">250</gaap:AverageNumberEmployees>
</xbrl>
Dit voorbeeld, geschreven door Charles Hoffman, komt van eerdergenoemde site. De rest van dit artikel werkt met deze voorbeeld XBRL instantie, die sterk lijkt op een uitgebreidere versie van bovenstaand voorbeeld. Yaidom wordt geïntroduceerd aan de hand van dit uitgebreide XBRL voorbeeld, ook al is yaidom een algemene XML library. Hoewel yaidom niet gebonden is aan het domein van XBRL, is XBRL wel een goed domein voor het tonen van de kracht van Scala en yaidom voor XML processing.
Yaidom wordt nu geïntroduceerd door enkele eenvoudige queries op onze XBRL voorbeeld-instantie.
Voordat deze queries worden getoond, volgt nu eerst een korte introductie tot de basis van yaidom XML querying. We hoeven slechts 3 centrale yaidom query API functies te noemen, want de overige functies
zijn daarna snel aan te leren. Deze 3 functies zijn filterChildElems
, filterElems
en filterElemsOrSelf
.
Het volgende geldt voor deze 3 functies:
filterElems
en filterElemsOrSelf
hebben beknopte en zeer precieze definities in termen van functie filterChildElems
.filterElems
en filterElemsOrSelf
typisch een aantal elementen retourneren
die descendants zijn van andere geretourneerde elementen. Indien dit niet gewenst is, kunnen functies als findTopmostElems
en findTopmostElemsOrSelf
gebruikt worden.Om de precisie van yaidom's query API te tonen, volgen hier de definities van filterElemsOrSelf
en filterElems
:
def filterElemsOrSelf(p: E => Boolean): immutable.IndexedSeq[E] = {
val selfResult = immutable.IndexedSeq(this).filter(p)
// Recursive calls
selfResult ++ findAllChildElems.flatMap(_.filterElemsOrSelf(p))
}
def filterElems(p: E => Boolean): immutable.IndexedSeq[E] = {
findAllChildElems.flatMap(_.filterElemsOrSelf(p))
}
def findAllChildElems: immutable.IndexedSeq[E] =
filterChildElems(_ => true)
Voor functies filterChildElems
en filterElemsOrSelf
bestaan respectievelijk de afkortingen \
en \\
.
Functie attributeOption
heeft afkorting \@
. Daarnaast hebben enkele element-predicaten namen, zoals withLocalName
en withEName
.
Het begrip EName
staat daarbij voor "expanded name". Yaidom heeft precieze XML namespace ondersteuning, en maakt een scherp onderscheid tussen "expanded names" enerzijds
en "qualified names" anderzijds. Zie Understanding XML Namespaces voor de basis van yaidom's ondersteuning van XML namespaces.
Qualified names zijn de syntactische namen, en expanded names zijn datgene dat wordt gerepresenteerd door die syntactische namen. Slechts weinig XML libraries maken dit scherpe onderscheid. Dat yaidom dat
wel doet heeft yaidom's namespace support aantoonbaar geholpen.
Sommige yaidom queries op de voorbeeld XBRL-instantie zijn als volgt, ongeacht de "element tree"-implementatie:
// Let variable "doc" be a document of any yaidom document type
val docElem = doc.documentElement
// Check that all gaap:AverageNumberEmployees facts have unit U-Pure.
val xmlNs = "http://www.w3.org/XML/1998/namespace"
val xbrliNs = "http://www.xbrl.org/2003/instance"
val gaapNs = "http://xasb.org/gaap"
val avgNumEmployeesFacts =
docElem.filterChildElems(withEName(gaapNs, "AverageNumberEmployees"))
println(avgNumEmployeesFacts.size) // prints 10
val onlyUPure =
avgNumEmployeesFacts.forall(
_.attributeOption(EName("unitRef")) == Some("U-Pure"))
println(onlyUPure) // prints true
// Check the unit itself, minding the default namespace
val uPureUnit =
docElem.getChildElem(e =>
e.resolvedName == EName(xbrliNs, "unit") &&
(e \@ EName("id")) == Some("U-Pure"))
println(uPureUnit.getChildElem(withEName(xbrliNs, "measure")).text) // prints "pure"
// Now we get the measure element text, as QName, resolving it to an EName
// (expanded name)
println(
uPureUnit.getChildElem(withEName(xbrliNs, "measure")).textAsResolvedQName)
// prints EName(xbrliNs, "pure")
// Having the same unit, the gaap:AverageNumberEmployees facts are uniquely
// identified by contexts.
val avgNumEmployeesFactsByContext =
avgNumEmployeesFacts.groupBy(
_.attribute(EName("contextRef"))).mapValues(_.head)
println(avgNumEmployeesFactsByContext.keySet)
// prints:
// Set("D-2006", "D-2007", "D-2008", "D-2009", "D-2010", "D-2010-BS1", "D-2010-BS2",
// "D-2010-CON", "D-2010-E", "D-2010-ALL")
println(avgNumEmployeesFactsByContext("D-2006").text) // prints 220
Yaidom is minder beknopt in het gebruik dan Scala's XML library. Het voordeel van yaidom is meer precisie. Elke yaidom query API functie heeft een kraakheldere definitie, meestal in termen van een "basis"-functie
zoals filterChildElems
, zoals we eerder zagen. Ook is "chaining" van query functies in yaidom niet mogelijk, terwijl Scala XML dit wel toelaat. Ook dit maakt yaidom preciezer dan Scala XML, omdat yaidom
het onderscheid tussen individuele elementen enerzijds en collecties van elementen anderzijds niet onder tafel veegt.
In een XBRL instantie (als XML document) zien facts met hun contexts en eventuele units er nogal "syntactisch" en weinig "semantisch" uit. De essentie van een fact is immers verspreid over meerdere XML elementen die mogelijk ver uit elkaar staan (facts en hun contexts en eventuele units). Meer semantisch heeft een fact in een instantie naast een waarde een aantal aspects. Denk bijvoorbeeld aan:
Deze (en andere) aspects vormen de basis voor moderne XBRL specificaties rond validaties ("formulas"). Daar zit ook de link met het laatste deel van dit artikel, omdat we daar aspects gaan gebruiken voor validatie. In deze sectie gaan we fact aspects opvragen met Scala en yaidom, zodat we in de volgende sectie aspecten kunnen gebruiken om validaties op XBRL instanties te coderen.
Hieronder volgt code om (sterk vereenvoudigd) fact aspects op te vragen. Variabele idoc
bevat de XBRL instantie als zogenaamd "indexed" document. "Indexed" elementen kennen hun parent elementen,
maar bieden daarnaast dezelfde yaidom query API. De kennis van de ancestor elementen benutten we voor het location aspect. De code, die gebonden is aan het gebruik van een specifieke element-implementatie,
maar toch grotendeels de generieke yaidom element query API gebruikt, is als volgt:
val idocElem = idoc.documentElement
val contextsById: Map[String, indexed.Elem] =
idocElem.filterChildElems(withEName(XbrliNs, "context")).
groupBy(_.attribute(EName("id"))).mapValues(_.head)
val unitsById: Map[String, indexed.Elem] =
idocElem.filterChildElems(withEName(XbrliNs, "unit")).
groupBy(_.attribute(EName("id"))).mapValues(_.head)
// See http://www.xbrl.org/Specification/variables/REC-2009-06-22/.
def conceptAspect(fact: indexed.Elem): EName = fact.resolvedName
// Yaidom Paths wijzen een element binnen een element tree aan.
def locationAspect(fact: indexed.Elem): Path =
fact.path.parentPathOption.getOrElse(Path.Root)
def entityIdentifierAspectOption(fact: indexed.Elem): Option[(String, String)] = {
val contextOption =
fact.attributeOption(EName("contextRef")).map(id => contextsById(id))
val identifierOption =
contextOption.flatMap(_.findElem(withEName(XbrliNs, "identifier")))
val schemeOption =
identifierOption.flatMap(_.attributeOption(EName("scheme")))
val identifierValueOption =
identifierOption.map(_.text)
for {
scheme <- schemeOption
identifierValue <- identifierValueOption
} yield (scheme, identifierValue)
}
def periodAspectOption(fact: indexed.Elem): Option[simple.Elem] = {
val contextOption =
fact.attributeOption(EName("contextRef")).map(id => contextsById(id))
val periodOption =
contextOption.flatMap(_.findElem(withEName(XbrliNs, "period")))
periodOption.map(_.elem)
}
// Forgetting about complete segment, non-XDT segment, complete scenario and
// non-XDT scenario for now. Also ignoring typed dimensions.
def explicitDimensionAspects(fact: indexed.Elem): Map[EName, EName] = {
val contextOption =
fact.attributeOption(EName("contextRef")).map(id => contextsById(id))
val memberElems =
contextOption.toVector.flatMap(_.filterElems(
withEName(XbrldiNs, "explicitMember")))
memberElems.map(e =>
(e.attributeAsResolvedQName(EName("dimension")) -> e.textAsResolvedQName)).toMap
}
// Convenience method
def explicitDimensionAspectOption(
fact: indexed.Elem,
dimension: EName): Option[EName] = {
explicitDimensionAspects(fact).filterKeys(Set(dimension)).headOption.map(_._2)
}
def unitAspectOption(fact: indexed.Elem): Option[simple.Elem] = {
val unitOption =
fact.attributeOption(EName("unitRef")).map(id => unitsById(id))
unitOption.map(_.elem)
}
// Compare aspects, naively, and without knowledge about XML Schema types.
// Period aspect comparisons are more tricky than this naive implementation suggests.
// Use equality on the results of the functions below for (period and unit) aspect
// comparisons.
def comparablePeriodAspectOption(fact: indexed.Elem): Option[Set[resolved.Elem]] = {
periodAspectOption(fact).map(e =>
e.findAllChildElems.map(che =>
resolved.Elem(che).removeAllInterElementWhitespace).toSet)
}
def comparableUnitAspectOption(fact: indexed.Elem): Option[Set[resolved.Elem]] = {
unitAspectOption(fact).map(e =>
e.findAllChildElems.map(che =>
resolved.Elem(che).removeAllInterElementWhitespace).toSet)
}
Enigszins onopvallend zijn hier nog 2 element-implementatie gebruikt, namelijk "simple" en "resolved" elements. Simple elements zijn de default native (immutable) element-implementatie van yaidom. Terwijl "indexed" elements iets toevoegen (aan "simple" elements), namelijk de ancestry als element context, laten "resolved" elements iets weg, namelijk namespace prefixes (de namespace URI's blijven behouden). Dat maakt deze element-implementatie een goede basis voor namespace-aware XML equality-vergelijkingen. Weer geldt dat de kern van de yaidom XML query API hetzelfde is, ook voor "resolved" elements.
Het valt op dat we met weinig Scala/yaidom code redelijk ver komen in de ondersteuning van fact aspects. Het was in dit geval beduidend minder natuurlijk geweest om in plaats hiervan XSLT of XQuery in te zetten. Dit illustreert het nut van Scala en yaidom voor XML processing, als mogelijk alternatief voor standaarden zoals XSLT. Hoe meer we Scala (en Java) API's willen inzetten voor een groot deel van de code, hoe aantrekkelijker deze alternatieve aanpak wordt. Dit voorkomt dat we in de code steeds moeten schakelen tussen 2 sterk verschillende paradigma's: Scala/Java enerzijds en het XPath Data Model anderzijds. De semantiek verschilt ook sterk tussen deze paradigma's. Zo zijn bijvoorbeeld XDM sequences totaal verschillend van Scala of Java collecties (een XDM item is hetzelfde als een singleton sequence met dat item, en sequences kunnen niet genest worden). Equality in XPath verschilt ook sterk van equality in Scala of Java.
Nu komen we bij een validatie terecht, die we op de XBRL instance kunnen loslaten. De validatieregel bevindt zich samen met andere validatieregels in US-GAAP formulas. Zonder verdere XBRL kennis zijn deze formulas niet of zeer moeilijk te doorgronden. Dat is geen probleem. We nemen 1 van deze formulas (een zogenaamde "value assertion"), en simuleren deze validatieregel in Scala code. Er wordt dus een validatieregel uitgelegd zonder dat we het origineel hoeven te begrijpen (of zelfs hoeven kunnen aan te wijzen in deze zogenaamde "formula linkbase"). Weer wordt getoond hoe Scala en yaidom (al dan niet met een Saxon "backend") een aantrekkelijke XML stack kunnen vormen.
Let wel: dit is een alternatieve representatie van een validatieregel voor educatieve doeleinden. In werkelijkheid worden XBRL formulas (waaronder value assertions) op een geheel andere manier verwerkt, al worden ook daar mogelijk Scala en yaidom als onderdeel van de oplossing ingezet.
De value assertion voert het equivalent uit van XPath expressie:
$v:VARIABLE_BalanceStart + $v:VARIABLE_Change = $v:VARIABLE_BalanceEnd
Daarbij wordt binnen de XBRL instantie geitereerd over combinaties van 3 bij elkaar horende facts: een "start balance", en "change" en een "end balance". Voor elk van die combinaties wordt het equivalent van de XPath expressie uitgevoerd als validatie-check. Alleen, welke facts worden ingevuld voor de 3 variabelen ("explicit filtering"), en welke combinaties van die facts worden beschouwd ("implicit filtering")? Dat is te zien in onderstaande code. "Implicit filtering" is gebaseerd op gelijkheid van zogenaamde "uncovered" aspecten. Dit zijn alle aspecten behalve de "covered" concept en period aspecten. (Deze 2 laatste aspecten zijn "covered" want zij zijn expliciet gebruikt om facts te filteren.) Hier volgt eerst een skelet van de code:
val balanceFacts =
facts.filter(withEName(GaapNs, "CashAndCashEquivalentsPerCashFlowStatement"))
val changeFacts =
facts.filter(withEName(GaapNs, "CashFlowNet"))
// Function mustBeEvaluated filters a combination of start balance, change and
// end balance, based on equality of so-called uncovered aspects.
// Function performAssertionTest evaluates one such combination of 3 facts.
val evalResults =
for {
startBalance <- balanceFacts
change <- changeFacts
endBalance <- balanceFacts
if mustBeEvaluated(startBalance, change, endBalance)
} yield {
performAssertionTest(startBalance, change, endBalance)
}
De meer complete code (voor educatieve doeleinden) is als volgt:
final case class EvaluationResult(
val facts: Map[String, indexed.Elem], val result: Boolean) {
override def toString: String = {
s"EvaluationResult(result: $result, facts: ${facts.mapValues(_.elem)})"
}
}
val topLevelFacts =
idocElem.filterChildElems(e =>
!Set(XbrliNs, LinkNs).contains(e.resolvedName.namespaceUriOption.getOrElse("")))
val facts = topLevelFacts.flatMap(_.findAllElemsOrSelf)
val balanceFacts =
facts.filter(withEName(GaapNs, "CashAndCashEquivalentsPerCashFlowStatement"))
val changeFacts =
facts.filter(withEName(GaapNs, "CashFlowNet"))
// Implicit filtering, to filter the cartesian product of 3 fact value spaces
def mustBeEvaluated(
balanceStartFact: indexed.Elem,
changeFact: indexed.Elem,
balanceEndFact: indexed.Elem): Boolean = {
// Compare on so-called uncovered aspects, so all ones except concept and period
val currFacts = List(balanceStartFact, changeFact, balanceEndFact)
val dimensions = currFacts.flatMap(e => explicitDimensionAspects(e).keySet).toSet
currFacts.map(e => locationAspect(e)).distinct.size == 1 &&
currFacts.map(e => entityIdentifierAspectOption(e)).distinct.size == 1 &&
dimensions.forall(dim =>
currFacts.map(e => explicitDimensionAspectOption(e, dim)).toSet.size == 1) &&
currFacts.map(e => unitAspectOption(e)).distinct.size == 1 && {
// Instant-duration
// The comparison is naive, but still verbose
import LocalDate.parse
val balanceStartInstantOption =
periodAspectOption(balanceStartFact).flatMap(
_.findElem(withEName(XbrliNs, "instant"))).map(e => parse(e.text))
val balanceEndInstantOption =
periodAspectOption(balanceEndFact).flatMap(
_.findElem(withEName(XbrliNs, "instant"))).map(e => parse(e.text))
val changeStartOption =
periodAspectOption(changeFact).flatMap(
_.findElem(withEName(XbrliNs, "startDate"))).map(e => parse(e.text))
val changeEndOption =
periodAspectOption(changeFact).flatMap(
_.findElem(withEName(XbrliNs, "endDate"))).map(e => parse(e.text))
balanceStartInstantOption.isDefined && balanceEndInstantOption.isDefined &&
changeStartOption.isDefined && changeEndOption.isDefined && {
val balanceStart = balanceStartInstantOption.get
val balanceEnd = balanceEndInstantOption.get
val changeStart = changeStartOption.get
val changeEnd = changeEndOption.get
(balanceStart == changeStart || balanceStart.plusDays(1) == changeStart) &&
(balanceEnd == changeEnd)
}
}
}
// The assertion test itself
def performAssertionTest(
balanceStartFact: indexed.Elem,
changeFact: indexed.Elem,
balanceEndFact: indexed.Elem): EvaluationResult = {
// Here we recognize the XPath expression shown earlier
val result =
balanceStartFact.text.toInt + changeFact.text.toInt == balanceEndFact.text.toInt
EvaluationResult(
Map(
"startBalance" -> balanceStartFact,
"change" -> changeFact,
"endBalance" -> balanceEndFact), result)
}
// Executing the assertion
val evalResults =
for {
startBalance <- balanceFacts
change <- changeFacts
endBalance <- balanceFacts
if mustBeEvaluated(startBalance, change, endBalance)
} yield {
performAssertionTest(startBalance, change, endBalance)
}
assertResult(2) {
evalResults.size
}
assertResult(true) {
evalResults.forall(_.result)
}
assertResult(Set(
Map("startBalance" -> 1000, "change" -> -1000, "endBalance" -> 0),
Map("startBalance" -> -3000, "change" -> 4000, "endBalance" -> 1000))) {
evalResults.map(_.facts.mapValues(_.text.toInt)).toSet
}
De uitdrukkingskracht van (eenvoudig gebruik van) Scala en de waarde van de yaidom library zijn duidelijk in dit voorbeeld. Yaidom is zelf in dit voorbeeld wat minder te zien, maar is vooral achter de schermen aanwezig, in de implementatie van de verschillende aspect-functies.
Concluderend kan gezegd worden: als je niet-triviale XML processing wilt doen, en daarbij (zonder omwegen) wilt profiteren van Scala en Java libraries, overweeg dan het gebruik van Scala plus de yaidom library als "XML stack", als alternatief voor standaarden als XSLT en XQuery. Yaidom in combinatie met Saxon-EE is een nog krachtiger XML stack, o.a. door Saxon's kennis van XML Schema types.
Scala's eigen XML library mag beknoptere code opleveren, maar het heeft niet de precisie van yaidom, met name met betrekking tot XML namespaces. Bovendien ondersteunt Scala XML geen wrappers rond bestaande volwassen Java XML libraries, en ook ondersteunt het niet meerdere "native" element-implementaties met verschillende sterke punten. Yaidom gaat zelfs nog verder in "extensibility". Het is zelfs mogelijk om type-safe XML dialecten te ondersteunen, zoals het dialect voor XBRL instanties. Dat is in dit artikel niet aan bod gekomen, maar dat maakt yaidom nog veel krachtiger en eleganter in het gebruik dan hier is getoond.
Yaidom is gepresenteerd bij XML London 2015. De library wordt bij EBPI intensief ingezet in productiecode. Daarnaast helpt yaidom bij snelle XML scripting taken. Yaidom helpt ons bij EBPI vooral om gelaagde modellen te bouwen waarmee we bijvoorbeeld XBRL processing robuust maar met een korte time-to-market kunnen realiseren.