Document Purpose: Explain how XBRL processors discover and assemble all information about concepts through taxonomy traversal
Target Audience: XBRL processor developers, architects
Last Updated: January 2026
The Challenge:
Information about a single XBRL concept is scattered across multiple XML documents. A processor must discover and assemble ALL information about a concept by traversing taxonomy structures.
Two Types of Traversal:
Explicit Traversal: Following schema imports and includes
Implicit Traversal: Following linkbase arcs to discover related information
Why It Matters:
Without implicit traversal, processors miss critical information about concepts. Labels, calculations, and relationships remain undiscovered, rendering the taxonomy unusable.
The Pattern:
Concept (in Schema)
↓
Linkbase (referenced from Schema)
↓
Locator (points to Concept)
↓
Arc (connects Locator to Resource)
↓
Resource (Label, Reference, etc.)
↓
ATTACH to Concept as Child Node
When Traversal Happens:
At design-time, when the entry point is loaded. NOT at runtime when processing instances.
Example: A Simple Concept
<!-- In schema: concepts/company-concepts.xsd -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:company="http://example.com/company/2024"
targetNamespace="http://example.com/company/2024">
<!-- The concept definition -->
<xs:element name="Revenue"
type="xbrli:monetaryItemType"
substitutionGroup="xbrli:item"/>
</xs:schema>
Question: Where is the information about this concept?
Answer: Scattered across MANY documents:
Information about company:Revenue:
1. Schema (concepts/company-concepts.xsd)
├─ Name: "Revenue"
├─ Type: xbrli:monetaryItemType
├─ Substitution group: xbrli:item
└─ Abstract: false (default)
2. Label Linkbase (labels/company-labels-en.xml)
├─ Standard label: "Revenue"
├─ Terse label: "Rev"
├─ Verbose label: "Revenue from Operations"
└─ Documentation: "Total revenue from all sources"
3. Reference Linkbase (refs/company-references.xml)
├─ GAAP reference: "ASC 606-10-50-12"
└─ IAS reference: "IAS 1.82(a)"
4. Presentation Linkbase (presentation/company-presentation.xml)
├─ Parent: "IncomeStatement"
├─ Order: 1.0
└─ Preferred label: standard
5. Calculation Linkbase (calculation/company-calculation.xml)
├─ Parent: "GrossProfit"
├─ Weight: 1.0
└─ Order: 1.0
6. Definition Linkbase (definition/company-definition.xml)
└─ Dimensions: Available for all segments
ALL this information must be discovered and assembled!
Design Decision:
XBRL separates concerns:
Benefits:
Cost:
Assembling Information:
After traversal, all information is attached to a Node structure:
ConceptNode: company:Revenue
├─ Schema Information
│ ├─ Name: "Revenue"
│ ├─ Type: monetaryItemType
│ ├─ Substitution Group: item
│ └─ Period Type: duration
│
├─ Labels (Child Nodes)
│ ├─ LabelNode [en, standard]: "Revenue"
│ ├─ LabelNode [en, terse]: "Rev"
│ ├─ LabelNode [en, verbose]: "Revenue from Operations"
│ ├─ LabelNode [fr, standard]: "Revenu"
│ └─ LabelNode [de, standard]: "Umsatz"
│
├─ References (Child Nodes)
│ ├─ ReferenceNode [GAAP]: "ASC 606-10-50-12"
│ └─ ReferenceNode [IAS]: "IAS 1.82(a)"
│
├─ Presentation Relationships (Child Nodes)
│ └─ PresentationNode
│ ├─ Parent: IncomeStatement
│ ├─ Order: 1.0
│ └─ Preferred Label: standard
│
├─ Calculation Relationships (Child Nodes)
│ └─ CalculationNode
│ ├─ Parent: GrossProfit
│ ├─ Weight: 1.0
│ └─ Order: 1.0
│
└─ Definition Relationships (Child Nodes)
└─ DefinitionNode
├─ Type: Dimension-default
└─ ...
After Traversal:
The ConceptNode is complete with ALL information attached as child nodes.
Definition:
Following direct references from one document to another:
<xs:import>)<xs:include>)<link:linkbaseRef>)These are EXPLICIT because they're clearly stated in the XML.
Starting Point: Entry Point Schema
<!-- entrypoint.xsd -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:company="http://example.com/company/2024"
targetNamespace="http://example.com/company/2024">
<!-- Import standard XBRL namespaces -->
<xs:import namespace="http://www.xbrl.org/2003/instance"
schemaLocation="http://www.xbrl.org/2003/xbrl-instance-2003-12-31.xsd"/>
<!-- Import other company schemas -->
<xs:import namespace="http://example.com/company/2024/concepts"
schemaLocation="concepts/company-concepts.xsd"/>
<!-- Linkbase references -->
<link:linkbaseRef xlink:type="simple"
xlink:href="labels/company-labels-en.xml"
xlink:role="http://www.xbrl.org/2003/role/labelLinkbaseRef"/>
<link:linkbaseRef xlink:type="simple"
xlink:href="presentation/company-presentation.xml"
xlink:role="http://www.xbrl.org/2003/role/presentationLinkbaseRef"/>
</xs:schema>
Explicit Traversal Process:
1. Load: entrypoint.xsd
2. Find: <xs:import> for xbrl-instance
3. Load: xbrl-instance-2003-12-31.xsd
4. Find: <xs:import> for xbrl-linkbase
5. Load: xbrl-linkbase-2003-12-31.xsd
6. Find: <xs:import> for company-concepts
7. Load: concepts/company-concepts.xsd
8. Find: <link:linkbaseRef> for labels
9. Load: labels/company-labels-en.xml
10. Find: <link:linkbaseRef> for presentation
11. Load: presentation/company-presentation.xml
... continue until all imports/includes/linkbaseRefs followed
Result:
All schemas and linkbases are loaded into memory.
For Debugging:
TRAVERSAL_TRACE: Loading entry point
├─ LOAD: entrypoint.xsd
├─ IMPORT: http://www.xbrl.org/2003/instance
│ └─ LOAD: xbrl-instance-2003-12-31.xsd
│ └─ IMPORT: http://www.xbrl.org/2003/linkbase
│ └─ LOAD: xbrl-linkbase-2003-12-31.xsd
├─ IMPORT: http://example.com/company/2024/concepts
│ └─ LOAD: concepts/company-concepts.xsd
├─ LINKBASE_REF: labels/company-labels-en.xml
│ └─ LOAD: labels/company-labels-en.xml
└─ LINKBASE_REF: presentation/company-presentation.xml
└─ LOAD: presentation/company-presentation.xml
Definition:
Following linkbase arcs to discover information about concepts that is NOT directly referenced from the concept's schema.
The Problem:
A concept in a schema doesn't directly reference its labels, references, calculations, etc. These are discovered by following linkbase structures.
This is IMPLICIT because:
Core XBRL Linking Pattern:
<!-- Label Linkbase: labels/company-labels-en.xml -->
<link:labelLink xlink:type="extended">
<!-- Locator: Points to the Concept -->
<link:loc xlink:type="locator"
xlink:href="concepts/company-concepts.xsd#Revenue"
xlink:label="revenue_loc"/>
<!-- Arc: Connects Locator to Label -->
<link:labelArc xlink:type="arc"
xlink:from="revenue_loc"
xlink:to="revenue_label"
xlink:arcrole="http://www.xbrl.org/2003/arcrole/concept-label"/>
<!-- Resource: The Label -->
<link:label xlink:type="resource"
xlink:label="revenue_label"
xml:lang="en"
xlink:role="http://www.xbrl.org/2003/role/label">
Revenue
</link:label>
</link:labelLink>
The Pattern:
1. Locator (revenue_loc)
├─ Points to: concepts/company-concepts.xsd#Revenue
└─ Identifies: The concept we're talking about
2. Arc (labelArc)
├─ From: revenue_loc (the concept)
├─ To: revenue_label (the label)
└─ Meaning: "This label belongs to this concept"
3. Resource (label)
├─ xlink:label: revenue_label
├─ Language: en
└─ Value: "Revenue"
The concept doesn't know about the label:
<!-- In concepts/company-concepts.xsd -->
<xs:element name="Revenue"
type="xbrli:monetaryItemType"
substitutionGroup="xbrli:item"/>
<!-- NO MENTION of labels! -->
The label linkbase knows about the concept:
<!-- In labels/company-labels-en.xml -->
<link:loc xlink:href="concepts/company-concepts.xsd#Revenue"/>
<!-- This points BACK to the concept -->
Processor Must:
This is IMPLICIT discovery!
Step-by-Step: Discovering Everything About company:Revenue
Step 1: Load Schema (Explicit Traversal)
<!-- concepts/company-concepts.xsd -->
<xs:element name="Revenue"
type="xbrli:monetaryItemType"
substitutionGroup="xbrli:item"
id="Revenue"/>
Create ConceptNode:
ConceptNode: company:Revenue
├─ Name: "Revenue"
├─ Type: monetaryItemType
└─ (No labels, references, relationships yet)
Step 2: Load Label Linkbase (Explicit Traversal)
<!-- labels/company-labels-en.xml -->
<link:labelLink>
<link:loc xlink:href="../concepts/company-concepts.xsd#Revenue"
xlink:label="revenue_loc"/>
<!-- Standard label -->
<link:labelArc xlink:from="revenue_loc"
xlink:to="revenue_std_label"/>
<link:label xlink:label="revenue_std_label"
xml:lang="en"
xlink:role="http://www.xbrl.org/2003/role/label">
Revenue
</link:label>
<!-- Terse label -->
<link:labelArc xlink:from="revenue_loc"
xlink:to="revenue_terse_label"/>
<link:label xlink:label="revenue_terse_label"
xml:lang="en"
xlink:role="http://www.xbrl.org/2003/role/terseLabel">
Rev
</link:label>
</link:labelLink>
Implicit Traversal Process:
1. Find locator: revenue_loc
└─ Points to: concepts/company-concepts.xsd#Revenue
└─ MATCH to ConceptNode: company:Revenue
2. Find arcs from revenue_loc:
├─ Arc to revenue_std_label
└─ Arc to revenue_terse_label
3. Follow arc to revenue_std_label:
├─ Find label resource
├─ Language: en
├─ Role: standard
├─ Value: "Revenue"
└─ CREATE LabelNode and ATTACH to ConceptNode
4. Follow arc to revenue_terse_label:
├─ Find label resource
├─ Language: en
├─ Role: terse
├─ Value: "Rev"
└─ CREATE LabelNode and ATTACH to ConceptNode
Updated ConceptNode:
ConceptNode: company:Revenue
├─ Name: "Revenue"
├─ Type: monetaryItemType
└─ Labels:
├─ LabelNode [en, standard]: "Revenue"
└─ LabelNode [en, terse]: "Rev"
Step 3: Load Reference Linkbase (Explicit Traversal)
<!-- references/company-references.xml -->
<link:referenceLink>
<link:loc xlink:href="../concepts/company-concepts.xsd#Revenue"
xlink:label="revenue_loc"/>
<link:referenceArc xlink:from="revenue_loc"
xlink:to="revenue_gaap_ref"/>
<link:reference xlink:label="revenue_gaap_ref"
xlink:role="http://www.xbrl.org/2003/role/reference">
<ref:Publisher>FASB</ref:Publisher>
<ref:Name>Accounting Standards Codification</ref:Name>
<ref:Topic>606</ref:Topic>
<ref:SubTopic>10</ref:SubTopic>
<ref:Section>50</ref:Section>
<ref:Paragraph>12</ref:Paragraph>
</link:reference>
</link:referenceLink>
Implicit Traversal Process:
1. Find locator: revenue_loc
└─ MATCH to ConceptNode: company:Revenue
2. Find arc from revenue_loc to revenue_gaap_ref
3. Follow arc to revenue_gaap_ref:
├─ Find reference resource
├─ Extract: FASB ASC 606-10-50-12
└─ CREATE ReferenceNode and ATTACH to ConceptNode
Updated ConceptNode:
ConceptNode: company:Revenue
├─ Name: "Revenue"
├─ Type: monetaryItemType
├─ Labels:
│ ├─ LabelNode [en, standard]: "Revenue"
│ └─ LabelNode [en, terse]: "Rev"
└─ References:
└─ ReferenceNode [GAAP]: "ASC 606-10-50-12"
Step 4: Load Calculation Linkbase (Explicit Traversal)
<!-- calculation/company-calculation.xml -->
<link:calculationLink xlink:role="http://www.xbrl.org/2003/role/link">
<!-- Parent: GrossProfit -->
<link:loc xlink:href="../concepts/company-concepts.xsd#GrossProfit"
xlink:label="grossprofit_loc"/>
<!-- Child: Revenue -->
<link:loc xlink:href="../concepts/company-concepts.xsd#Revenue"
xlink:label="revenue_loc"/>
<!-- Arc: GrossProfit = Revenue - CostOfRevenue -->
<link:calculationArc xlink:from="grossprofit_loc"
xlink:to="revenue_loc"
order="1.0"
weight="1.0"/>
</link:calculationLink>
Implicit Traversal Process:
1. Find locator: revenue_loc
└─ MATCH to ConceptNode: company:Revenue
2. Find arcs involving revenue_loc:
├─ Arc FROM grossprofit_loc TO revenue_loc
└─ Meaning: Revenue is child of GrossProfit with weight 1.0
3. CREATE CalculationNode and ATTACH to ConceptNode:
├─ Parent: GrossProfit
├─ Weight: 1.0
└─ Order: 1.0
Updated ConceptNode:
ConceptNode: company:Revenue
├─ Name: "Revenue"
├─ Type: monetaryItemType
├─ Labels:
│ ├─ LabelNode [en, standard]: "Revenue"
│ └─ LabelNode [en, terse]: "Rev"
├─ References:
│ └─ ReferenceNode [GAAP]: "ASC 606-10-50-12"
└─ Calculations:
└─ CalculationNode
├─ Parent: GrossProfit
├─ Weight: 1.0
└─ Order: 1.0
Step 5: Continue for All Linkbases
Same process for:
Final ConceptNode: COMPLETE
ConceptNode: company:Revenue
├─ Schema Information
│ ├─ Name: "Revenue"
│ ├─ Type: monetaryItemType
│ ├─ Substitution Group: item
│ └─ Period Type: duration
│
├─ Labels (from label linkbase)
│ ├─ LabelNode [en, standard]: "Revenue"
│ ├─ LabelNode [en, terse]: "Rev"
│ ├─ LabelNode [en, verbose]: "Revenue from Operations"
│ └─ LabelNode [en, documentation]: "Total revenue..."
│
├─ References (from reference linkbase)
│ └─ ReferenceNode [GAAP]: "ASC 606-10-50-12"
│
├─ Calculations (from calculation linkbase)
│ └─ CalculationNode
│ ├─ Parent: GrossProfit
│ ├─ Weight: 1.0
│ └─ Order: 1.0
│
└─ Presentations (from presentation linkbase)
└─ PresentationNode
├─ Parent: IncomeStatement
├─ Order: 1.0
└─ Preferred Label: standard
Challenge: Documents Reference Each Other
Schema A
├─ Defines: company:Revenue
└─ References: labels/company-labels-en.xml
labels/company-labels-en.xml
└─ Locator points back to: Schema A#Revenue
This is CIRCULAR!
How Processor Handles:
1. Load Schema A (explicit)
└─ Create ConceptNode for Revenue (incomplete)
2. Load label linkbase (explicit, from Schema A reference)
└─ Find locator pointing BACK to Schema A#Revenue
└─ MATCH locator to existing ConceptNode
└─ Follow arcs and ATTACH labels to ConceptNode
No infinite loop because:
├─ Schema A already loaded (don't reload)
└─ Locator just REFERENCES concept (doesn't load it again)
Most developers assume:
"If I load the schema, I get the concept.
If I load the label linkbase, I get labels.
I'll match them up later."
WRONG!
Correct understanding:
"When I load the schema, I get concepts (incomplete).
When I load the label linkbase, I must:
1. Find locators pointing to concepts
2. Follow arcs to resources
3. ATTACH resources to concepts
Only then are concepts complete."
Why it's confusing:
Design-Time (Entry Point Load):
Application Startup:
├─ Load configuration
├─ User selects entry point
└─ TRAVERSAL BEGINS:
├─ Explicit traversal (load all documents)
└─ Implicit traversal (assemble concept information)
TRAVERSAL COMPLETES → Taxonomy ready for use
Runtime (Instance Processing):
├─ No traversal!
├─ Taxonomy already loaded
└─ Just look up concepts in completed taxonomy
Design-Time vs. Runtime:
Design-Time (Slow, One-Time):
├─ Load all schemas
├─ Load all linkbases
├─ Follow all locators
├─ Follow all arcs
├─ Attach all resources
└─ Build complete concept nodes
Runtime (Fast, Many Times):
├─ Look up concept: company:Revenue
├─ Get label: revenue.getLabel("en", "standard")
├─ Get parent: revenue.getCalculationParent()
└─ All information already attached!
GLOMIDCO Logging: TRAVERSAL_TRACE
// Enable traversal trace
Logger.setLevel("TRAVERSAL_TRACE");
// During entry point load
TRAVERSAL_TRACE: Starting explicit traversal
TRAVERSAL_TRACE: Loading entry point: company-2024.xsd
TRAVERSAL_TRACE: Import: http://www.xbrl.org/2003/instance
TRAVERSAL_TRACE: Loading: xbrl-instance-2003-12-31.xsd
TRAVERSAL_TRACE: Import: http://example.com/company/2024
TRAVERSAL_TRACE: Loading: concepts/company-concepts.xsd
TRAVERSAL_TRACE: Found concept: company:Revenue
TRAVERSAL_TRACE: Found concept: company:Expenses
TRAVERSAL_TRACE: LinkbaseRef: labels/company-labels-en.xml
TRAVERSAL_TRACE: Loading: labels/company-labels-en.xml
TRAVERSAL_TRACE: Starting implicit traversal
TRAVERSAL_TRACE: Processing label linkbase: company-labels-en.xml
TRAVERSAL_TRACE: Locator: revenue_loc → company:Revenue
TRAVERSAL_TRACE: Arc: revenue_loc → revenue_std_label
TRAVERSAL_TRACE: Label [en, standard]: "Revenue"
TRAVERSAL_TRACE: ATTACHED to company:Revenue
TRAVERSAL_TRACE: Arc: revenue_loc → revenue_terse_label
TRAVERSAL_TRACE: Label [en, terse]: "Rev"
TRAVERSAL_TRACE: ATTACHED to company:Revenue
GLOMIDCO Logging: TRAVERSAL_ULTRA_TRACE
Even more detailed, shows every step:
Logger.setLevel("TRAVERSAL_ULTRA_TRACE");
TRAVERSAL_ULTRA_TRACE: Processing extended link: labelLink
TRAVERSAL_ULTRA_TRACE: Resource locator found
TRAVERSAL_ULTRA_TRACE: xlink:type="locator"
TRAVERSAL_ULTRA_TRACE: xlink:href="concepts/company-concepts.xsd#Revenue"
TRAVERSAL_ULTRA_TRACE: xlink:label="revenue_loc"
TRAVERSAL_ULTRA_TRACE: Resolved to concept: company:Revenue
TRAVERSAL_ULTRA_TRACE: Resource arc found
TRAVERSAL_ULTRA_TRACE: xlink:type="arc"
TRAVERSAL_ULTRA_TRACE: xlink:from="revenue_loc"
TRAVERSAL_ULTRA_TRACE: xlink:to="revenue_std_label"
TRAVERSAL_ULTRA_TRACE: xlink:arcrole="concept-label"
TRAVERSAL_ULTRA_TRACE: Resource label found
TRAVERSAL_ULTRA_TRACE: xlink:type="resource"
TRAVERSAL_ULTRA_TRACE: xlink:label="revenue_std_label"
TRAVERSAL_ULTRA_TRACE: xml:lang="en"
TRAVERSAL_ULTRA_TRACE: xlink:role="http://www.xbrl.org/2003/role/label"
TRAVERSAL_ULTRA_TRACE: value="Revenue"
TRAVERSAL_ULTRA_TRACE: Creating LabelNode
TRAVERSAL_ULTRA_TRACE: Language: en
TRAVERSAL_ULTRA_TRACE: Role: standard
TRAVERSAL_ULTRA_TRACE: Value: Revenue
TRAVERSAL_ULTRA_TRACE: Attaching LabelNode to ConceptNode
TRAVERSAL_ULTRA_TRACE: Concept: company:Revenue
TRAVERSAL_ULTRA_TRACE: Labels count: 1
Use Cases:
ConceptNode Class:
public class ConceptNode {
// Schema information
private QName name;
private QName type;
private QName substitutionGroup;
private boolean isAbstract;
private PeriodType periodType;
// Child nodes (discovered through implicit traversal)
private List<LabelNode> labels = new ArrayList<>();
private List<ReferenceNode> references = new ArrayList<>();
private List<CalculationNode> calculations = new ArrayList<>();
private List<PresentationNode> presentations = new ArrayList<>();
private List<DefinitionNode> definitions = new ArrayList<>();
// Methods to attach discovered information
public void addLabel(LabelNode label) {
this.labels.add(label);
}
public void addReference(ReferenceNode reference) {
this.references.add(reference);
}
public void addCalculation(CalculationNode calc) {
this.calculations.add(calc);
}
// Methods to retrieve information
public String getLabel(String lang, String role) {
for (LabelNode label : labels) {
if (label.getLanguage().equals(lang) &&
label.getRole().equals(role)) {
return label.getValue();
}
}
return null;
}
public List<CalculationNode> getCalculationParents() {
return calculations.stream()
.filter(c -> c.isParent())
.collect(Collectors.toList());
}
}
LabelNode Class:
public class LabelNode {
private String language; // "en", "fr", "de"
private String role; // "standard", "terse", "verbose"
private String value; // "Revenue"
private ConceptNode concept; // Back-reference to parent concept
// Constructor
public LabelNode(String language, String role, String value) {
this.language = language;
this.role = role;
this.value = value;
}
}
Similar classes for:
High-Level Algorithm:
public class TaxonomyLoader {
private Map<QName, ConceptNode> concepts = new HashMap<>();
public void loadEntryPoint(String entryPointUri) {
// Phase 1: Explicit Traversal
log("Starting explicit traversal");
explicitTraversal(entryPointUri);
// Phase 2: Implicit Traversal
log("Starting implicit traversal");
implicitTraversal();
log("Taxonomy loaded: " + concepts.size() + " concepts");
}
private void explicitTraversal(String schemaUri) {
// Load schema
Schema schema = loadSchema(schemaUri);
// Extract concepts
for (Element element : schema.getElements()) {
ConceptNode concept = new ConceptNode(element);
concepts.put(concept.getQName(), concept);
}
// Follow imports
for (Import imp : schema.getImports()) {
explicitTraversal(imp.getSchemaLocation());
}
// Follow linkbase references
for (LinkbaseRef ref : schema.getLinkbaseRefs()) {
loadLinkbase(ref.getHref());
}
}
private void implicitTraversal() {
// Process all loaded linkbases
for (Linkbase linkbase : loadedLinkbases) {
if (linkbase instanceof LabelLinkbase) {
processLabelLinkbase((LabelLinkbase) linkbase);
} else if (linkbase instanceof CalculationLinkbase) {
processCalculationLinkbase((CalculationLinkbase) linkbase);
}
// ... other linkbase types
}
}
private void processLabelLinkbase(LabelLinkbase linkbase) {
for (ExtendedLink link : linkbase.getLabelLinks()) {
// Find all locators
Map<String, Locator> locators = new HashMap<>();
for (Locator loc : link.getLocators()) {
locators.put(loc.getLabel(), loc);
}
// Find all labels
Map<String, Label> labels = new HashMap<>();
for (Label label : link.getLabels()) {
labels.put(label.getLabel(), label);
}
// Process arcs
for (LabelArc arc : link.getLabelArcs()) {
// Get locator (points to concept)
Locator locator = locators.get(arc.getFrom());
QName conceptQName = resolveLocator(locator);
ConceptNode concept = concepts.get(conceptQName);
if (concept == null) {
log("WARNING: Concept not found: " + conceptQName);
continue;
}
// Get label resource
Label label = labels.get(arc.getTo());
// Create LabelNode
LabelNode labelNode = new LabelNode(
label.getLang(),
label.getRole(),
label.getValue()
);
// ATTACH to concept
concept.addLabel(labelNode);
logUltraTrace("Attached label [" + label.getLang() + ", " +
label.getRole() + "] to " + conceptQName);
}
}
}
private QName resolveLocator(Locator locator) {
// Parse href: "concepts/company-concepts.xsd#Revenue"
String href = locator.getHref();
String[] parts = href.split("#");
String schemaUri = parts[0];
String elementId = parts[1];
// Find schema
Schema schema = loadedSchemas.get(schemaUri);
// Find element
Element element = schema.getElementById(elementId);
// Return qualified name
return new QName(
schema.getTargetNamespace(),
element.getName()
);
}
}
Scenario: Locator Points to Locator
<!-- In linkbase -->
<link:loc xlink:href="schema1.xsd#ConceptA" xlink:label="loc1"/>
<link:loc xlink:href="#loc1" xlink:label="loc2"/>
<link:arc xlink:from="loc2" xlink:to="label1"/>
<link:label xlink:label="label1">Label Text</link:label>
Processor must:
Rare but legal in XBRL!
Scenario: Override Default Relationships
<!-- Base taxonomy says: A is parent of B -->
<link:calculationArc xlink:from="A" xlink:to="B"
weight="1.0"
use="optional"/>
<!-- Extension taxonomy prohibits this -->
<link:calculationArc xlink:from="A" xlink:to="B"
use="prohibited"/>
Processor must:
Critical for extensions!
Scenario: Multiple Documents Reference Same Concept
Document 1: concepts/core.xsd
├─ Defines: company:Revenue
Document 2: labels/en.xml
└─ Labels for: company:Revenue
Document 3: labels/fr.xml
└─ Labels for: company:Revenue
Document 4: calculation/income.xml
└─ Calculations for: company:Revenue
Document 5: presentation/statement.xml
└─ Presentation for: company:Revenue
All must be discovered and attached to single ConceptNode!
Scenario: Concept with Dimensions
<!-- Definition linkbase -->
<link:definitionLink>
<!-- Concept: Revenue -->
<link:loc xlink:href="concepts.xsd#Revenue" xlink:label="revenue_loc"/>
<!-- Hypercube: RevenueTable -->
<link:loc xlink:href="concepts.xsd#RevenueTable" xlink:label="table_loc"/>
<!-- Dimension: SegmentAxis -->
<link:loc xlink:href="concepts.xsd#SegmentAxis" xlink:label="dim_loc"/>
<!-- Revenue → RevenueTable -->
<link:definitionArc xlink:from="revenue_loc" xlink:to="table_loc"
xlink:arcrole="http://xbrl.org/int/dim/arcrole/all"/>
<!-- RevenueTable → SegmentAxis -->
<link:definitionArc xlink:from="table_loc" xlink:to="dim_loc"
xlink:arcrole="http://xbrl.org/int/dim/arcrole/hypercube-dimension"/>
</link:definitionLink>
Processor must:
Complex but necessary for dimensional reporting!
Symptom:
ConceptNode revenue = taxonomy.getConcept("company:Revenue");
String label = revenue.getLabel("en", "standard");
// Returns: null (even though label exists in taxonomy!)
Cause:
Processor loaded linkbase but didn't follow arcs and attach labels.
Fix:
Implement implicit traversal properly!
Problem:
1. Load label linkbase
2. Try to attach labels
3. ERROR: Concepts don't exist yet!
4. Load schema with concepts
5. Too late - labels already processed
Solution:
1. Load all schemas (explicit traversal)
└─ Create all ConceptNodes
2. Load all linkbases (explicit traversal)
3. Process all linkbases (implicit traversal)
└─ Attach information to existing ConceptNodes
Problem:
Schema A imports Schema B
Schema B imports Schema A
Solution:
Track loaded schemas, don't reload:
private Set<String> loadedSchemaUris = new HashSet<>();
private void loadSchema(String uri) {
if (loadedSchemaUris.contains(uri)) {
return; // Already loaded
}
loadedSchemaUris.add(uri);
// Actually load schema...
}
Problem:
<link:loc xlink:href="concepts.xsd#element(Revenue)"/>
Not just #Revenue but #element(Revenue) (XPointer syntax)
Solution:
Parse XPointer schemes:
#id - Simple ID#element(name) - Element by name#xpointer(...) - Full XPointerProblem:
Locator points to: "concepts.xsd#Revenue"
Concept QName: {http://example.com/2024}Revenue
How to match?
Solution:
Challenge:
Large taxonomies (US GAAP: 18,000+ concepts) take time to load.
Solution:
Don't traverse everything immediately:
public class ConceptNode {
private boolean labelsLoaded = false;
private List<LabelNode> labels = new ArrayList<>();
public List<LabelNode> getLabels() {
if (!labelsLoaded) {
loadLabels(); // Lazy load on demand
labelsLoaded = true;
}
return labels;
}
private void loadLabels() {
// Traverse label linkbases NOW
// Attach labels
}
}
Trade-off:
Strategy:
Cache resolved locators:
private Map<String, QName> locatorCache = new HashMap<>();
private QName resolveLocator(String href) {
if (locatorCache.containsKey(href)) {
return locatorCache.get(href);
}
QName qname = doResolveLocator(href);
locatorCache.put(href, qname);
return qname;
}
Strategy:
Process linkbases in parallel (after all loaded):
ExecutorService executor = Executors.newFixedThreadPool(4);
List<Future<?>> futures = new ArrayList<>();
for (Linkbase linkbase : loadedLinkbases) {
futures.add(executor.submit(() -> processLinkbase(linkbase)));
}
// Wait for all to complete
for (Future<?> future : futures) {
future.get();
}
Careful:
Must synchronize access to ConceptNodes!
Test: Every Concept Has Expected Information
@Test
public void testConceptCompleteness() {
Taxonomy taxonomy = loadTaxonomy("company-2024.xsd");
ConceptNode revenue = taxonomy.getConcept("company:Revenue");
// Schema information
assertNotNull(revenue.getType());
assertNotNull(revenue.getSubstitutionGroup());
// Labels (implicit traversal)
assertNotNull(revenue.getLabel("en", "standard"));
assertNotNull(revenue.getLabel("en", "terse"));
// References (implicit traversal)
assertFalse(revenue.getReferences().isEmpty());
// Calculations (implicit traversal)
assertFalse(revenue.getCalculations().isEmpty());
}
Test: All Locators Resolve
@Test
public void testLocatorResolution() {
Taxonomy taxonomy = loadTaxonomy("company-2024.xsd");
for (Linkbase linkbase : taxonomy.getLinkbases()) {
for (Locator locator : linkbase.getLocators()) {
QName concept = taxonomy.resolveLocator(locator);
assertNotNull("Failed to resolve: " + locator.getHref(),
concept);
assertNotNull("Concept not found: " + concept,
taxonomy.getConcept(concept));
}
}
}
Test: All Arcs Lead Somewhere
@Test
public void testArcCompleteness() {
Taxonomy taxonomy = loadTaxonomy("company-2024.xsd");
for (ExtendedLink link : taxonomy.getLabelLinks()) {
for (LabelArc arc : link.getArcs()) {
// From must exist
assertNotNull("From label not found: " + arc.getFrom(),
link.getLocator(arc.getFrom()));
// To must exist
assertNotNull("To label not found: " + arc.getTo(),
link.getLabel(arc.getTo()));
}
}
}
// In GLOMIDCO configuration
Logger.setLevel("TRAVERSAL_TRACE");
// or
Logger.setLevel("TRAVERSAL_ULTRA_TRACE");
Issue 1: Concept Has No Labels
Symptom: revenue.getLabel("en", "standard") returns null
Diagnosis:
1. Check TRAVERSAL_TRACE output
2. Search for: "Processing label linkbase"
3. Search for: "Locator: ... → company:Revenue"
4. Search for: "ATTACHED to company:Revenue"
If not found:
├─ Linkbase not loaded? (check explicit traversal)
├─ Locator href wrong? (check XPointer)
├─ Arc not followed? (check implicit traversal)
└─ Concept QName mismatch? (check namespace)
Issue 2: Calculation Relationships Missing
Symptom: revenue.getCalculationParents() returns empty list
Diagnosis:
1. Check TRAVERSAL_TRACE output
2. Search for: "Processing calculation linkbase"
3. Search for: "Arc: ... → revenue_loc"
4. Check if arc direction is correct (parent → child)
If not found:
├─ Calculation linkbase not loaded?
├─ Arc direction reversed?
└─ Locator not resolved?
Tool: Taxonomy Inspector
Shows concept with all attached information:
Concept: company:Revenue
├─ [Schema] Type: monetaryItemType
├─ [Schema] Period: duration
├─ [Label] en/standard: "Revenue"
├─ [Label] en/terse: "Rev"
├─ [Label] fr/standard: "Revenu"
├─ [Reference] GAAP: ASC 606-10-50-12
├─ [Calculation] Parent: GrossProfit (weight: 1.0)
└─ [Presentation] Parent: IncomeStatement (order: 1.0)
If anything missing: Traversal issue!
Recommended Structure:
TaxonomyLoader
├─ ExplicitTraverser
│ ├─ Follows schema imports
│ ├─ Follows linkbase references
│ └─ Loads all documents
│
├─ ImplicitTraverser
│ ├─ Processes loaded linkbases
│ ├─ Follows locators and arcs
│ └─ Attaches information to concepts
│
└─ ConceptBuilder
├─ Creates ConceptNodes from schemas
└─ Manages concept lifecycle
// DON'T mix explicit and implicit traversal
public void loadSchema(String uri) {
Schema schema = parser.parse(uri);
extractConcepts(schema);
processLinkbases(schema); // WRONG! Too early!
}
// DO separate phases
public void loadTaxonomy(String entryPoint) {
// Phase 1: Explicit
explicitTraversal(entryPoint);
// Phase 2: Implicit
implicitTraversal();
}
private void attachLabel(ConceptNode concept, LabelNode label) {
if (concept == null) {
log("ERROR: Cannot attach label to null concept");
return;
}
if (label == null) {
log("ERROR: Cannot attach null label");
return;
}
concept.addLabel(label);
logTrace("Attached label to " + concept.getQName());
}
Document Your Traversal:
/**
* Implicit traversal: Discovers and attaches information from linkbases
* to concept nodes.
*
* Process:
* 1. For each loaded linkbase
* 2. For each extended link in linkbase
* 3. Build locator map (label → Locator)
* 4. Build resource map (label → Resource)
* 5. For each arc:
* a. Resolve 'from' locator to ConceptNode
* b. Find 'to' resource
* c. Create appropriate node (LabelNode, etc.)
* d. Attach to ConceptNode
*
* This is called AFTER explicit traversal completes.
*/
private void implicitTraversal() {
// Implementation...
}
Traversal is Critical:
Without proper traversal (especially implicit), XBRL processor cannot function.
Two Phases:
Key Pattern:
Locator → Arc → Resource
↓
Concept ← Attach Resource
When It Happens:
Design-time, at entry point load. NOT runtime.
Implementation:
Why It's Hard:
Why It's Essential:
Without it, concepts are incomplete shells. Labels, relationships, references all missing. Taxonomy unusable.
If you're implementing an XBRL processor:
Don't Skip Implicit Traversal!
It's the difference between a broken processor and a working one.
This document explains the critical but poorly-documented concept of XBRL taxonomy traversal, especially implicit traversal for discovering and assembling concept information.