Yaidom: a Scala XML query and transformation API (Apache 2.0 license)
Showing yaidom by examples using XBRL
Created by chris.de.vreeze@ebpi.nl
Powered by reveal.js
<xbrli:xbrl xmlns:xbrli="http://www.xbrl.org/2003/instance"
xmlns:cc2-i="cc2i" xmlns:cc-t="cct" xmlns:cd="nlcd" xmlns:iso4217="iso4217">
<xbrli:context id="FY14d">
<xbrli:entity>
<xbrli:identifier scheme="http://www.cc.eu/cc-id">30267975
</xbrli:identifier>
</xbrli:entity>
<xbrli:period>
<xbrli:startDate>2014-01-01</xbrli:startDate>
<xbrli:endDate>2014-12-31</xbrli:endDate>
</xbrli:period>
</xbrli:context>
<xbrli:unit id="EUR">
<xbrli:measure>iso4217:EUR</xbrli:measure>
</xbrli:unit>
<cc2-i:Equity contextRef="FY14d" unitRef="EUR"
decimals="INF">95000</cc2-i:Equity>
<cc-t:EntityAddressPresentation>
<cd:POBoxNumber contextRef="FY14d">2312</cd:POBoxNumber>
<cd:PostalCodeNL contextRef="FY14d">2501CD</cd:PostalCodeNL>
<cd:PlaceOfResidenceNL contextRef="FY14d">Den Haag
</cd:PlaceOfResidenceNL>
<cd:CountryName contextRef="FY14d">Nederland</cd:CountryName>
</cc-t:EntityAddressPresentation>
</xbrli:xbrl>
First some yaidom basics:
Below methods "filter" and "map" are shown:
val xbrliNs = "http://www.xbrl.org/2003/instance"
val contexts =
instance.findAllChildElems.filter(e =>
e.resolvedName == EName(xbrliNs, "context"))
val contextIds =
contexts.map(e => e.attribute(EName("id")))
<xbrli:xbrl xmlns:xbrli="http://www.xbrl.org/2003/instance"
xmlns:cc2-i="cc2i" xmlns:cc-t="cct" xmlns:cd="nlcd" xmlns:iso4217="iso4217">
<xbrli:context id="FY14d">
<xbrli:entity>
<xbrli:identifier scheme="http://www.cc.eu/cc-id">30267975
</xbrli:identifier>
</xbrli:entity>
<xbrli:period>
<xbrli:startDate>2014-01-01</xbrli:startDate>
<xbrli:endDate>2014-12-31</xbrli:endDate>
</xbrli:period>
</xbrli:context>
<xbrli:unit id="EUR">
<xbrli:measure>iso4217:EUR</xbrli:measure>
</xbrli:unit>
<cc2-i:Equity contextRef="FY14d" unitRef="EUR"
decimals="INF">95000</cc2-i:Equity>
<cc-t:EntityAddressPresentation>
<cd:POBoxNumber contextRef="FY14d">2312</cd:POBoxNumber>
<cd:PostalCodeNL contextRef="FY14d">2501CD</cd:PostalCodeNL>
<cd:PlaceOfResidenceNL contextRef="FY14d">Den Haag
</cd:PlaceOfResidenceNL>
<cd:CountryName contextRef="FY14d">Nederland</cd:CountryName>
</cc-t:EntityAddressPresentation>
</xbrli:xbrl>
Finding facts, contexts and units (as plain XML elements), regardless of the element implementation:
val ns = "http://www.xbrl.org/2003/instance"
val linkNs = "http://www.xbrl.org/2003/linkbase"
def hasCustomNs(e: Elem): Boolean = {
!Set(Option(ns), Option(linkNs)).contains(
e.resolvedName.namespaceUriOption)
}
val contexts = xbrlInstance.filterChildElems(withEName(ns, "context"))
val units = xbrlInstance.filterChildElems(withEName(ns, "unit"))
val topLevelFacts =
xbrlInstance.filterChildElems(e => hasCustomNs(e))
val nestedFacts =
topLevelFacts.flatMap(_.filterElems(e => hasCustomNs(e)))
val allFacts =
topLevelFacts.flatMap(_.filterElemsOrSelf(e => hasCustomNs(e)))
Non-trivial queries combine facts with their contexts and units:
val contextsById =
contexts.groupBy(_.attribute(EName("id")))
val unitsById =
units.groupBy(_.attribute(EName("id")))
// Use these Maps to look up contexts and units from
// (item) facts, with predictable performance ...
<xbrli:xbrl xmlns:xbrli="http://www.xbrl.org/2003/instance"
xmlns:cc2-i="cc2i" xmlns:cc-t="cct" xmlns:cd="nlcd" xmlns:iso4217="iso4217">
<xbrli:context id="FY14d">
<xbrli:entity>
<xbrli:identifier scheme="http://www.cc.eu/cc-id">30267975
</xbrli:identifier>
</xbrli:entity>
<xbrli:period>
<xbrli:startDate>2014-01-01</xbrli:startDate>
<xbrli:endDate>2014-12-31</xbrli:endDate>
</xbrli:period>
</xbrli:context>
<xbrli:unit id="EUR">
<xbrli:measure>iso4217:EUR</xbrli:measure>
</xbrli:unit>
<cc2-i:Equity contextRef="FY14d" unitRef="EUR"
decimals="INF">95000</cc2-i:Equity>
<cc-t:EntityAddressPresentation>
<cd:POBoxNumber contextRef="FY14d">2312</cd:POBoxNumber>
<cd:PostalCodeNL contextRef="FY14d">2501CD</cd:PostalCodeNL>
<cd:PlaceOfResidenceNL contextRef="FY14d">Den Haag
</cd:PlaceOfResidenceNL>
<cd:CountryName contextRef="FY14d">Nederland</cd:CountryName>
</cc-t:EntityAddressPresentation>
</xbrli:xbrl>
// All namespace declarations must be in the root element
require(
xbrlInstance.findAllElems.forall(_.scope == xbrlInstance.scope))
val standardScope = Scope.from(
"xbrli" -> "http://www.xbrl.org/2003/instance",
"xlink" -> "http://www.w3.org/1999/xlink",
"link" -> "http://www.xbrl.org/2003/linkbase",
"xsi" -> "http://www.w3.org/2001/XMLSchema-instance",
"iso4217" -> "http://www.xbrl.org/2003/iso4217")
val standardPrefixes = standardScope.keySet
val standardNamespaceUris = standardScope.inverse.keySet
val subscope = xbrlInstance.scope.withoutDefaultNamespace filter {
case (pref, ns) =>
standardPrefixes.contains(pref) ||
standardNamespaceUris.contains(ns)
}
require(subscope.subScopeOf(standardScope)) // fails on iso4217
val ns = "http://www.xbrl.org/2003/instance"
val linkNs = "http://www.xbrl.org/2003/linkbase"
def hasCustomNs(e: Elem): Boolean = {
!Set(Option(ns), Option(linkNs)).contains(
e.resolvedName.namespaceUriOption)
}
val contexts = xbrlInstance.filterChildElems(withEName(ns, "context"))
val units = xbrlInstance.filterChildElems(withEName(ns, "unit"))
val topLevelFacts =
xbrlInstance.filterChildElems(e => hasCustomNs(e))
val allFacts =
topLevelFacts.flatMap(_.filterElemsOrSelf(e => hasCustomNs(e)))
val contextIds =
contexts.map(_.attribute(EName("id"))).toSet
val usedContextIds =
allFacts.flatMap(_.attributeOption(EName("contextRef"))).toSet
require(usedContextIds.subsetOf(contextIds))
require(contextIds.subsetOf(usedContextIds))
Expressive and type-safe validation code, using an imaginary yaidom extension for XBRL instances:
val contextIds = xbrlInstance.allContextsById.keySet
val usedContextIds =
xbrlInstance.findAllItems.map(_.contextRef).toSet
require(usedContextIds.subsetOf(contextIds))
require(contextIds.subsetOf(usedContextIds))
Yaidom (Apache 2.0 license) can be found at https://github.com/dvreeze/yaidom