Altova RaptorXML+XBRL API Tutorial

Introduction

Altova RaptorXML is the third-generation, hyper-fast XML and XBRL processor from the makers of XMLSpy©. RaptorXML is built from the ground up to be optimized for the latest standards and parallel computing environments. Designed to be highly cross-platform capable, the engine takes advantage of today’s ubiquitous multi-CPU computers to deliver lightning fast processing of XML and XBRL data.

This short tutorial aims to provide a general tour of XBRL processing using the Python API available in Altova RaptorXML and show example solutions to some common problems. The full RaptorXML Python API documentation can be found at http://manual.altova.com/RaptorXML/pyapiv2/html/.

This tutorial assumes that the reader has already some knowledge of XBRL and a basic understanding of the Python 3 programming language.

Executing scripts in RaptorXML

There are three ways to execute scripts within RaptorXML that provide access to the whole XBRL object model:

  • Using callback hooks after validation

  • Using the script command (available since version 2016)

  • Using the raptorxmlxbrl-python executable (available since version 2016)

The first method using callbacks can be used in scenarios where only a single instance document needs to be processed. Example applications would be performing custom validation logic, extracting specific data or generating custom documentation for the given instance document. Depending on the validation command the script must implement a specific function that is called by RaptorXML after the document has been loaded and validated. See the RaptorXML Python API documentation for a list of available callback functions.

The following example illustrates how to implement a simple custom validation rule that requires all context ids to start with ctx.

import altova_api.v2.xbrl as xbrl

def on_xbrl_finished(job,instance):
    for context in instance.contexts:
        if not context.id.startswith('ctx'):
            # Report a custom error message
            job.error_log.report(xbrl.Error.create('Context {context} must have an id name starting with "ctx"!',context=context))

Now the script can be specified in addition to the standard XBRL validation command using the --script option:

raptorxmlxbrl valxbrl --script=custom_val.py instance.xbrl

If the application logic is more complicated or requires to load and validate multiple documents, then the script command might be more appropriate. The script command allows the execution of full Python scripts within the RaptorXML engine. Using the API provided in Altova specific modules, the script can control the loading and validation of documents and access their object model. The raptorxmlxbrl-python executable behaves exactly like raptorxmlxbrl script.

Regardless of the method used to execute scripts in RaptorXML, all Python scripts must conform to the Python 3.7 language specification.

Importing Altova modules

All Altova RaptorXML specific Python modules are available through the altova_api package. Within this package there is a separate module for each available RaptorXML API version. The current version at the time of writing is v2. Please refer to the Altova RaptorXML Python API document http://manual.altova.com/RaptorXML/pyapiv2/html/index.html#modules for a list of available modules. For example, this is a typical way to import the RaptorXML XBRL module:

import altova_api.v2.xbrl as xbrl

Alternatively, during an interactive session it can be useful to be able to import all available altova_api.v2 modules using a single import statement:

from altova_api.v2 import *

Note

Please note that this technique is generally not accepted in production code due to the possibility of accidentally overwriting symbols in the global namespace.

Only when using validation commands with script callbacks, an additional alias to altova_api.v* is created under the name altova. The actual API version can be specified with the --script-api-version option. Thus, if the script is used with the following command line

raptorxmlxbrl valxbrl --script=custom_val.py --script-api-version=2 instance.xbrl

the next import statement is equivalent with the one above.

from altova import *

Installing thrid party modules

Third party modules can be installed using Python’s pip install command. Please note that to install new packages a shell with administrator rights is required. In the Altova RaptorXML environment pip modules can be installed in the following way (with administrator rights):

raptorxmlxbrl-python -m pip install pyodbc

Loading XBRL instances

The xbrl.Instance Python class represents an XBRL instance document and is the entry point to the object model of the instance and the referenced taxonomies and linkbases (DTS). An XBRL instance can be loaded either from an URL or a (dynamically generated) byte buffer. The xbrl.Instance class provides two class methods for this purpose:

instance,log = xbrl.Instance.create_from_url('/home/user/instance.xbrl')
instance,log = xbrl.Instance.create_from_buffer(b'<xbrl xmlns="http://www.xbrl.org/2003/instance"><!-- Instance content here --></xbrl>')

Both methods return a tuple with the newly created xbrl.Instance and an xml.ErrorLog object. If the instance is not valid, a None object will be returned instead. The error log contains a list of errors, warnings and inconsistencies that occured during the validation episode. For example, raising an exception in case of errors during validation can be done as follows:

instance,log = xbrl.Instance.create_from_url('/home/user/instance.xbrl')
if not instance: raise Exception('\n'.join(error.text for error in log))

If some special URL mappings are required to be able to access the instance or taxonomy documents, an OASIS XML catalog can be specified with the catalog parameter:

custom_catalog,log = xml.Catalog.create_from_url('/home/user/custom_catalog.xml')
instance,log = xbrl.Instance.create_from_url('/home/user/instance.xbrl',catalog=custom_catalog)

Additional validation options can be specified as further keyword arguments. Please refer to the RaptorXML CLI documentation or execute raptorxmlxbrl help valxbrl to get a list of the available options. Please note that any hyphens need to be changed to underscores to be valid Python argument names. Please also note that some options controlling additional post-validation steps like formula or table linkbase execution will be ignored. The following example instructs RaptorXML to abort the validation immediately after the first error has been detected and switches on parallel assessment which tries to utilize all licensed CPU cores during XML validation:

instance,log = xbrl.Instance.create_from_url('/home/user/instance.xbrl',error_limit=1,parallel_assessment=True)

Finally, if multiple instance documents are loaded that reference exactly the same DTS entry points, the DTS can be preloaded once and reused to validate each instance. This can dramatically improve performance, especially when validating lots of small instance files referencing a large taxonomy.

preloaded_dts,log = xbrl.taxonomy.DTS.create_from_url('/home/user/taxonomy.xsd')
instance,log = xbrl.Instance.create_from_url('/home/user/instance.xbrl',dts=preloaded_dts)

Processing multiple XBRL instances (in parallel)

Processing all instances in a directory could be written like this:

def process_instance(url):
    # Load and validate instance
    instance, log = xbrl.Instance.create_from_url(url)
    # Do something with instance...

for url in glob.iglob('/home/xbrl/*.xbrl'):
    process_instance(url)

The xbrl.Instance class methods xbrl.Instance.create_from_url() and xbrl.Instance.create_from_buffer() are both blocking calls, meaning that the execution of the Python script will stop and wait until the instance document has been loaded and validated. RaptorXML’s validation engine can utilize all available cores during document validation, but the application logic in the Python script is only executed on the main thread. Thus, after an instance has been loaded, the script execution continues only on the main thread not utilizing the other cores. It is possible to create additional threads in Python, but please note that due to a technical limitation of the CPython implementation, threads created in Python are only interleaved and never actually run in parallel. In order to maximize the through-put of a multi-core system, one can used the additional Python threads to schedule the loading and validation of many instances. This enables the RaptorXML engine to start loading the next instances utilizing all licensed cores while the Python interpreter is executing the application logic for the available instances on the main thread.

A convenient way to schedule several Python threads on a multi-core system is to use the thread pool implementation in the concurrent.futures module.

with concurrent.futures.ThreadPoolExecutor(max_workers=32) as executor:
    # Schedule processing of all instances as futures
    futures_to_url = {executor.submit(process_instance,url): url for url in glob.iglob('/home/xbrl/*.xbrl')}
    # Wait for all futures to finish their computation
    for future in concurrent.futures.as_completed(futures_to_url):
        url = future_to_url[future]
        try:
            future.result()
        except:
            print('Processing instance %s failed with an exception!' % url)

Retrieving facts

The xbrl.Instance object provides several properties to access the facts in the instance. xbrl.Instance.facts will return all the facts, whereas xbrl.Instance.nil_facts and xbrl.Instance.non_nil_facts will only return facts where @xsi:nil was set to true or false, respectively. All three properties return objects of type xbrl.FactSet which represent a list of facts in document order and without any duplicates. To print the element name and value of all facts:

for fact in instance.facts:
    print('Fact %s has value %s' % (fact.qname, fact.normalized_value))

xbrl.FactSet objects support the usual special methods for Python containers like len(), slices, and iterators. To print only the first ten facts:

print('Total number of facts: %d', len(instance.facts))
for fact in instance.facts[:10]:
    print('Fact %s has value %s' % (fact.qname, fact.normalized_value))

Use the xbrl.FactSet.filter() method to retrieve facts with specific attributes. To list all facts with a particular name:

for fact in instance.facts.filter(xml.QName('NetIncomeLoss','http://fasb.org/us-gaap/2013-01-31')):
    print('Fact %s has value %s' % (fact.qname, fact.normalized_value))

Alternatively, one can also pass a xbrl.taxonomy.Concept object to the xbrl.FactSet.filter() method. To resolve a QName to an XBRL concept use the xbrl.taxonomy.DTS.resolve_concept() method. There is a slight performance advantage using XBRL concept objects when xbrl.FactSet.filter() is called multiple times with the same concept (but different contexts or units).

income_concept = instance.dts.resolve_concept(('NetIncomeLoss','http://fasb.org/us-gaap/2013-01-31'))
for fact in instance.facts.filter(income_concept):
    print('Fact %s has value %s' % (fact.qname, fact.normalized_value))

To limit the search to facts referencing only a particular context, additionally specify the xbrl.Context object:

mycontext = instance.context('FD2014Q2YTD')
for fact in instance.facts.filter(xml.QName('NetIncomeLoss','http://fasb.org/us-gaap/2013-01-31'),mycontext):
    print('Fact %s has value %s' % (fact.qname, fact.normalized_value))

To find facts with a particular unit would work in a similar fashion.

xbrl.FactSet also supports the common set operations like intersection, union and set difference. For example, computing all facts that are not ‘Assets’ could be achieved in such a way:

non_assets_facts = instance.facts - instance.facts.filter(xml.QName('Assets','http://fasb.org/us-gaap/2013-01-31'))

Finding facts by aspect values

Facts can also be filterd by particular aspects. Aspects are additional information about a fact described by the associated context or unit. RaptorXML supports the dimensional aspect model as described in http://www.xbrl.org/specification/variables/rec-2009-06-22/variables-rec-2009-06-22+corrected-errata-2013-11-18.html#term-dimensional-aspect-model. To find facts with certain aspects like a specific period instant date, identifier or dimension value, pass an xbrl.ConstraintSet object to the xbrl.FactSet.filter() method. The xbrl.ConstraintSet object can combine multiple aspect value constraints and the filter method will return only facts that have those matching aspect values.

To find all facts reported in US dollar with a particular CIK entity identifier and a 3 month duration ending at 2014/05/31:

constraints = xbrl.ConstraintSet()
constraints[xbrl.Aspect.ENTITY_IDENTIFIER] = xbrl.EntityIdentifierAspectValue('0000815097','http://www.sec.gov/CIK')
constraints[xbrl.Aspect.PERIOD] = xbrl.PeriodAspectValue.from_duration('2014-03-01','2014-05-31')
constraints[xbrl.Aspect.UNIT] = xbrl.UnitAspectValue.from_iso4217_currency('USD')
for fact in instance.facts.filter(constraints):
    print('Fact %s has value %s' % (fact.qname, fact.normalized_value))

To restrict those facts further assign additional aspect values to the constraint set. Building on the previous example, to retrieve only facts reported as ForeignExchangeOptionMembers, add a constraint for the FinancialInstrumentAxis explicit dimension:

dim = instance.dts.resolve_concept(('FinancialInstrumentAxis','http://fasb.org/us-gaap/2013-01-31'))
member = instance.dts.resolve_concept(('ForeignExchangeOptionMember','http://fasb.org/us-gaap/2013-01-31'))

# Set the explicit dimension aspect value
constraints[dim] = member
facts = instance.facts.filter(constraints)

Adding a typed dimension constraint works in the same way as for explicit dimensions, but obtaining the typed dimension value might require additional work. Typed dimension values are expressed as XML tree fragments (with the typed dimension domain declaration element as root). Therefore, typed dimension constraints can be set using xml.ElementInformationItem objects. One way to obtain an XML tree fragment is by parsing and validating a buffer with the XML source code using the xml.Instance.create_from_buffer() class method. Please note that the XML fragment should be validated against the element declaration in the DTS, otherwise results might differ due to missing type information (e.g. <elem>3</elem> and <elem>03</elem> are only equal if they are both of type xs:integer).

Here is an example for finding facts reported with client code (CC) 33 in the individual clients (INC) dimension in an EBA taxonomy instance.

dim = instance.dts.resolve_concept(xml.QName('INC','http://www.eba.europa.eu/xbrl/crr/dict/dim'))
# Create an XML document fragment and validate it against the element declarations in the DTS
fragment, log = xml.Instance.create_from_buffer(b'<eba_typ:CC xmlns:eba_typ="http://www.eba.europa.eu/xbrl/crr/dict/typ">33</eba_typ:CC>',schema=instance.dts.schema)

# Set the typed dimension aspect value to the root element of the fragment
constraints[dim] = fragment.document_element
facts = instance.facts.filter(constraints)

To retrieve facts reported only with the dimensions values in the constraint set and without any additional dimensions, set the allow_additional_dimensions argument to False.

facts = instance.facts.filter(constraints,allow_additional_dimensions=False)

Implicit filtering

A common scenario is to find groups of facts that share the same aspect values. One way to achieve this is to construct an xbrl.ConstraintSet object by passing in a fact object. This will initialize the constraint set with all the fact’s aspect values as constraints. Then override some of the aspects with more specific values that the matching facts must posses. This technique is similar to the notion of implicit filtering defined in the XBRL Formula specifications. Here is an example that checks if the accounting equation in a balance sheet (Assets must equal LiabilitiesAndStockholdersEquity) really holds:

# For each reported total assets value
for assets_fact in instance.facts.filter(xml.QName('Assets','http://fasb.org/us-gaap/2013-01-31')):
    # Find the matching liabilities and equity total value (with the same instant date!)
    constraints = xbrl.ConstraintSet(assets_fact)
    constraints[xbrl.Aspect.CONCEPT] = instance.dts.resolve_concept(('LiabilitiesAndStockholdersEquity','http://fasb.org/us-gaap/2013-01-31'))
    liabilities_equity_fact = instance.facts.filter(constraints)[0]
    # Check if they are both equal (in balance)
    if assets_fact.effective_numeric_value != liabilities_equity_fact.effective_numeric_value:
        print('Balance sheet does not balance!!!')

Working with facts

All xbrl.Fact objects provide many convenience properties and methods to access parts of the XBRL data model without the need to work directly with the raw XML. But if needed, the underlying XML element can always be accessed using the xbrl.Fact.element property.

fact = instance.facts[0]    # Get the first fact in the instance
fact.id                     # Get the id attribute value (using a convenience property)
fact.element.find_attribute('id').normalized_value    # Get the id attribute value directly from the raw XML

Other common properties available on the xbrl.Fact object are:

fact.qname                  # Returns an `xml.QName` object containing the XML element name and namespace.
fact.xsi_nil                # Returns True if the `xsi:nil` attribute on the XML element was set to true.
fact.concept                # Returns an `xbrl.taxonomy.Concept` object.
fact.footnotes(lang='en')   # Returns an iterator over all footnotes in English that are associated with this fact.

To check if a fact is an item or tuple use the isinstance() method:

if isinstance(fact,xbrl.Item):
    print('Fact is an item')
elif isinstance(fact,xbrl.Tuple):
    print('Fact is a tuple')

Item facts (as opposed to tuples) provide additional properties to access the referenced context and unit as well as the fact’s value.

fact.context                 # Returns an `xbrl.Context` object
fact.unit                    # Returns an `xbrl.Unit` object. Might be None in case of non-numeric items.
fact.normalized_value        # Returns the fact's value as a string (always available).

# Numeric facts expose additional properties to retrieve the value as a decimal number.
if fact.concept.is_numeric():
    fact.numeric_value              # Returns a `decimal.Decimal` object with the value as written in the XML without any rounding.
    fact.effective_numeric_value    # Returns a `decimal.Decimal` object with the rounded value after taking the precision into account.

Tuples don’t store any values directly but group together other child facts. All facts directly contained in a tuple can be accessed using xbrl.Tuple.child_facts.

DTS

The discoverable taxonomy set (DTS) is a set of XBRL taxonomies and linkbases can be found using the XBRL 2.1 discovery rules starting at the instance. The DTS can be accessed using the xbrl.Instance.dts property. xbrl.taxonomy.DTS exposes the object model for all available concepts as well as the resources and networks of relationships defined in linkbases. For example, the xbrl.taxonomy.DTS.documents property lists all documents that are contained in the DTS.

for doc in instance.dts.documents:
    print(doc.uri)

Taxonomy concepts

The xbrl.taxonomy.DTS.concepts property returns an iterator over all the XBRL concepts defined within the DTS.

# List all concepts availabe in the DTS
for concept in instance.dts.concepts:
    # The qname property returns the XML name and namespace of the concept
    print(concept.qname)

There are several different types of XBRL concepts. The XBRL 2.1 specification defines item and tuple concepts. Further, the XBRL Dimensions 1.0 specification defined hypercube and dimension concepts. Concepts of those different types can be accessed using the xbrl.taxonomy.DTS.items, xbrl.taxonomy.DTS.tuples, xbrl.taxonomy.DTS.hypercubes and xbrl.taxonomy.DTS.dimensions properties. To check if a concept object is of a particular type, use the isinstance() method:

if isinstance(concept,xbrl.xdt.Dimension):
    print('Concept %s is a dimension' % concept.qname)

To get a particular concept by name, call the xbrl.taxonomy.DTS.resolve_concept() method with an XML QName:

assets_concept = instance.dts.resolve_concept(xml.QName('Assets','http://fasb.org/us-gaap/2013-01-31'))

The xbrl.taxonomy.Concept class provides all the XSD Element Declaration properties as defined by the XSD Schema 1.1 specification. For more details see http://www.w3.org/TR/xmlschema11-1/#Element_Declaration_details. For example, to check if a concept is abstract:

if concept.abstract:
    print('Concept %s is an abstract concept' % concept.qname)

Any assigned concept labels can be retrieved with the xbrl.taxonomy.Concept.labels() method.

for label in concept.labels():
    print('Label language: %s' % label.xml_lang)
    print('Label role: %s', % label.xlink_role)
    print('Label text: %s', % label.text)

The xbrl.taxonomy.Concept.labels() method also allows to filter labels according to label language and label role. The xbrl.taxonomy.Concept.references() method works in a similar way.

text = next(concept.labels(lang='en-US',label_role='http://www.xbrl.org/2003/role/totalLabel')).text

xbrl.taxonomy.Item class exposes additional properties applicable only to XBRL item concepts. Here are a few examples:

concept.is_non_numeric()    # Returns True if the concept has a non-numeric item type.
concept.is_numeric()        # Returns True if the concept has a numeric item type.
concept.is_monetary()        # Returns True if the concept has the monetaryItemType type.

concept.item_type            # Returns the concept's item type as an enumeration value
concept.item_type == xbrl.taxonomy.ItemType.MONETARY    # equivalent to concept.is_monetary()

concept.period_type            # Returns the period type as an enumeration value
if concept.period_type == xbrl.taxonomy.PeriodType.INSTANT:
    print('Concept %s can only be reported with an instant period context.' % concept.qname)

concept.balance                # Returns the balance type as an enumeration value
if concept.balance == xbrl.taxonomy.Balance.DEBIT:
    print('Concept %s has a debit balance.' % concept.qname)
elif concept.balance == xbrl.taxonomy.Balance.CREDIT:
    print('Concept %s has a credit balance.' % concept.qname)

Networks of relationships

The XBRL 2.1 specification groups all linkbase XLink arcs in into separate base sets according to the arc’s element name, arcrole and the containing extended link element. Arcs within a base set express one or more relationships between concepts and/or resources. Those relationships can override or prohibit other relationships within the base set that have lower priority. Only the effective relationships are then used within the final network of relationships, e.g. the visible presentation tree.

RaptorXML’s engine does all the required XLink processing and provides a simple API to traverse any network of relationships including the standard presentation and calculation networks.

The next example shows a generic solution to traverse all the concepts in depth-first order in acyclic network of relationships (like the standard presentation and calculation networks).

def traverse_node(network,concept):
    # Do something with the concept
    print(concept.qname)
    # Iterate over all relationships starting at this concept
    for rel in network.relationships_from(concept):
        traverse_node(network,rel.target)

def traverse_tree(network):
    # Traverse the network (tree) starting from each root concept
    for concept in network.roots:
        traverse_node(network,concept)

# Traverse the standard presentation tree with the given linkrole
traverse_tree(dts.network_of_relationships(linkrole))

Listing available linkroles

xbrl.taxonomy.DTS.link_roles() can be used to get all available link roles used within the DTS:

for linkrole in instance.dts.link_roles():
    print(linkrole)

It is also possible to get only the link roles used in a specific linkbase, for example the presentation linkbase:

for linkrole in instance.dts.presentation_link_roles():
    print(linkrole)

Each non-standard link role must be declared using a <link:roleType> element in the taxonomy. This construct might also contain an additional human readable definition string. The definition string can be accessed from the xbrl.taxonomy.RoleType object:

def link_role_label(dts,linkrole):
    # Get the RoleType object for the link role
    roletype = dts.role_type(linkrole)
    # Check if it contains a definition object
    if roletype and roletype.definition:
        # Get the definition string value
        return roletype.definition.value
    return linkrole

for linkrole in instance.dts.link_roles():
    print(link_role_label(instance.dts,linkrole))

The XBRL 2.1 standard does not allow multiple definition strings, thus multi-language definition strings are not possible. In a later specification, Generic Labels have been introduced to allow to assign labels to any XML constructs within a taxonomy or linkbase. As a workaround, a taxonomy could assign multi-language labels to <link:roleType> elements using generic labels. For such taxonomies, the generic labels can be easily retrieved using the xbrl.taxonomy.RoleType.labels() method.

for roletype in instance.dts.role_types:
    # Get a list of spanish labels for this RoleType object
    labels = list(roletype.labels(lang='es'))
    if labels:
        # Print the text of the first label
        print(labels[0].text)

Display presentation trees

Below is an example how to display the content of the presentation linkbase. Please note that presentation relationship objects have an additional xbrl.taxonomy.PresentationRelationship.preferred_label property which contains the label role that should be used when displaying the line item caption.

def label(concept,lang=None,label_role=None):
    # Use the XBRL standard label role if no preferred label role was specified
    labels = concept.labels(lang=lang,label_role=label_role if label_role else 'http://www.xbrl.org/2003/role/label')
    try:
        # Return the first label found
        return next(labels).text
    except StopIteration:
        # If no label was found return the XML element name
        return str(concept.qname)

def print_presentation_node(network,concept,lang,preferred_label=None,depth=0):
    # Print the concept label (indented by the tree depth)
    print('    '*depth, label(concept,lang,preferred_label))
    # Iterate over all relationships starting at this concept
    for rel in network.relationships_from(concept):
        print_presentation_node(network,rel.target,lang,rel.preferred_label,depth+1)

def print_presentation_tree(network,lang=None):
    # Traverse the presentation network (tree) starting from each root concept
    for concept in network.roots:
        print_presentation_node(network,concept,lang)

# Print all the presentation trees in the standard presentation linkbase
for linkrole in instance.dts.presentation_link_roles():
    print(link_role_label(instance.dts,linkrole))
    print_presentation_tree(instance.dts.presentation_network(linkrole),lang='en-US')

Display calculation trees

Below is an example how to display the content of the calculation linkbase. Please note that calculation relationship objects have an additional xbrl.taxonomy.CalculationRelationship.weight property which contains the weight of the child (target) concept.

def print_calculation_node(network,concept,lang,weight=None,depth=0):
    # Print the weight and concept label (indented by the tree depth)
    print('    '*depth, weight if weight else '', label(concept,lang))
    # Iterate over all relationships starting at this concept
    for rel in network.relationships_from(concept):
        print_calculation_node(network,rel.target,lang,rel.weight,depth+1)

def print_calculation_tree(network,lang=None):
    # Traverse the calculation network (tree) starting from each root concept
    for concept in network.roots:
        print_calculation_node(network,concept,lang)

# Print the standard calculation tree with the given linkrole
for linkrole in instance.dts.calculation_link_roles():
    print(link_role_label(instance.dts,linkrole))
    print_calculation_tree(instance.dts.calculation_network(linkrole),lang='en-US')

Dimensional Relationship Set

The XBRL Dimensions 1.0 specification is an extension to the core XBRL language. It standardizes how to report and validate facts that are broken down across different dimensions (e.g. different regions or products). To do so, it introduced new arcroles for definition arcs that are used to build a Dimensional Relationship Set. A Dimensional Relationship Set (DRS) expresses the possible dimensions and their domain values that can be used with a specific XBRL concept. The xbrl.xdt.DRS class provides a special API to query and navigate the DRS.

The following code show a generic way to traverse the consecutive relationships in the DRS:

def traverse_consecutive_relationships(drs,rel):
    # Do something with the relationship
    print(rel.source.qname,'-->',rel.target.qname)
    for rel2 in drs.consecutive_relationships(rel):
        traverse_consecutive_relationships(drs,rel2)

drs = instance.dts.dimensional_relationship_set()
for linkrole in drs.link_roles():
    print(linkrole)
    for item in drs.roots(linkrole):
        for rel in drs.hashypercube_relationships(item,linkrole):
            traverse_consecutive_relationships(drs,rel)

The next example shows how to calculate the effective domain) for all dimensions in the DTS.

def collect_usable_domain_members(drs,rel,usable_members):
    if rel.usable:
        usable_members.add(rel.target)
    for rel2 in drs.consecutive_relationships(rel):
        collect_usable_domain_members(drs,rel2,usable_members)

def dimension_domain(drs,dim):
    usable_members = set()
    # Get all usable domain members in all link roles
    for linkrole in drs.link_roles(dim):
        for rel in drs.dimension_domain_relationships(dim,linkrole):
            collect_usable_domain_members(drs,rel,usable_members)
    return usable_members

# List all explicit dimensions and their effective domains
drs = instance.dts.dimensional_relationship_set()
for dim in instance.dts.dimensions:
    if dim.is_explicit():
        domain_members = dimension_domain(drs,dim)
        if domain_members:
            print(dim.qname)
            for member in domain_members:
                print('    ',member.qname)

CSV export

Data from an XBRL instance can be easily exported in the CSV format using the Python csv module. Here is a simple example exporting all fact elements:

import csv

with open(r'/home/user/facts.csv','w',newline='') as csvfile:
    writer = csv.writer(csvfile)
    # Write header row
    header = ['Name','Namespace','Context','Unit','Value']
    writer.writerow(header)

    # Export all facts
    for fact in instance.facts:
        data = [fact.qname.local_name,fact.qname.namespace_name,fact.contextRef,fact.unitRef,fact.normalized_value]
        writer.writerow(data)

XSLX export

The standard Python distribution does not include a module for writing .xslx files, but there are several third party modules which provide this functionality. One popular third party module is XlsxWriter. To be able to use it with the RaptorXML Python API, it needs to be first installed using pip install.

raptorxmlxbrl-python -m pip install XlsxWriter

Note

Please note that the command needs to be executed in a shell with administrator rights!

The following code demonstrates how to create a simple worksheet containing all fact in the instance:

import xlsxwriter

with xlsxwriter.Workbook(r'/home/user/facts.xlsx') as workbook:
    worksheet = workbook.add_worksheet('Facts')

    # Write header row
    header = ['Name','Namespace','Context','Unit','Value']
    worksheet.write_row('A1',header)

    # Export all facts
    for row, fact in enumerate(instance.facts):
        data = [fact.qname.local_name,fact.qname.namespace_name,fact.contextRef,fact.unitRef,fact.normalized_value]
        worksheet.write_row(row+1,0,data)

SQLite export

Python has already builtin support for reading and writing to SQLite 3 databases using the sqlite3 module. The following code demonstrates how to create a simple database with a table containing all facts in the instance:

import sqlite3

with sqlite3.connect(r'/home/user/facts.db3') as con:

    # Create table
    con.execute('CREATE TABLE facts (name TEXT, namespace TEXT, context TEXT, unit TEXT, value TEXT)')

    # Export all facts
    for fact in instance.facts:
        data = [fact.qname.local_name,fact.qname.namespace_name,fact.contextRef,fact.unitRef,fact.normalized_value]
        con.execute('INSERT INTO facts VALUES(?,?,?,?,?)',data)