lib/parsers/dimension_parser.rb
module TaxonomyParser
class DimensionParser < BaseParser
class << self
def parse(current_dts)
@id = 1
@current_dts = current_dts
@def_index = {}
@bucket = Store.instance.data[:dimension_nodes] = {}
parse_definition_files
generate_nodes_and_indices
add_dimension_information_to_elements
end
def parse_definition_files
path = File.join(__dir__, "/../../dts_assets/#{@current_dts.name}/**/*")
@def_linkbases = Dir.glob(path).grep(/definition.xml/) do |file|
parsed_file = Nokogiri::XML(File.open(file))
end
end
def generate_nodes_and_indices
@def_linkbases.each do |linkbase|
linkbase.search('definitionArc').each do |def_arc|
index_parent_link(def_arc)
create_and_index_node(def_arc)
end
end
end
def index_parent_link(def_arc)
arcrole = def_arc.attributes["arcrole"].value
@def_index[arcrole] ||= { to: {}, from: {} }
if to = def_arc.attributes["to"].value
(@def_index[arcrole][:to][to] ||= []) << def_arc.attributes["from"].value
end
end
def create_and_index_node(def_arc)
if from = def_arc.attributes["from"].value
arcrole = def_arc.attributes["arcrole"].value
order = def_arc.attributes["order"]&.value || "0"
to = def_arc.attributes["to"].value
model = add_dimension_node(element_id: to, order: order, arcrole: arcrole)
(@def_index[arcrole][:from][from] ||= []) << model
end
end
def add_dimension_information_to_elements
Store.instance.data[:elements].each do |id, element|
element.dimension_nodes = dimension_node_tree(id, element)
end
end
def dimension_node_tree(element_id, element)
@nodes = {}
# tuples, dimension and hypercube nodes do not have dimensions
if grouping_items = find_grouping_items(element_id)
hypercubes = grouping_items.flat_map { |id| find_grouping_item_hypercubes(id) }
if hypercubes.any?
hypercubes.each do |hypercube|
@nodes[hypercube.id] ||= hypercube
add_hypercube_dimensions_to_nodes(hypercube, element)
end
end
end
@nodes.values
end
def add_hypercube_dimensions_to_nodes(hypercube, element)
# can have empty hypercubes e.g. uk-bus_EmptyHypercube
if dimensions = find_hypercube_dimensions(hypercube.element_id)
dimensions.each do |dimension|
# create dimension and link to parent hypercube
@nodes[dimension.id] = dimension
dimension.parent ||= hypercube
# check for defaults and update dimension if present
dimension.default = find_dimension_default(dimension.element_id)
add_dimension_domains_to_nodes(dimension)
end
# Indicate if hypercube is covered by defaults
if dimensions.any? { |node| node.default.nil? }
hypercube.has_defaults = false
element.must_choose_dimension = true
end
end
end
def add_dimension_domains_to_nodes(dimension)
find_dimension_domains(dimension.element_id).each do |domain|
# create domain set dimension to parent
if domain.parent && (domain.parent.parent.element_id != dimension.parent.element_id)
domain = alias_dimension(domain)
end
domain.parent = dimension
@nodes[domain.id] ||= domain
add_domain_members_to_nodes(domain)
end
end
def add_domain_members_to_nodes(domain)
find_domain_members([domain]).each do |member|
# create member with domain as parent
if member.parent
member = alias_dimension(member)
end
member.parent = domain
@nodes[member.id] ||= member
end
end
def add_dimension_node(element_id:, parent: nil, order:, arcrole:)
model = DimensionNode.new(id: @id, element_id: element_id, parent: parent, arcrole: arcrole, order: order)
model.element = Store.instance.data[:elements][element_id]
@bucket[@id] = model
@id += 1
model
end
def alias_dimension(model)
node = model.dup
node.id = @id
@bucket[@id] = node
@id += 1
node
end
def find_grouping_items(element_id)
arcrole = 'http://xbrl.org/int/dim/arcrole/domain-member'
parents = @def_index[arcrole][:to][element_id]
return nil unless parents
parents.flat_map do |parent|
find_grouping_items(parent) || parent
end.compact.uniq
end
def find_grouping_item_hypercubes(grouping_item_id)
arcrole = "http://xbrl.org/int/dim/arcrole/all"
@def_index[arcrole][:from][grouping_item_id]
end
def find_hypercube_dimensions(hypercube_id)
arcrole = "http://xbrl.org/int/dim/arcrole/hypercube-dimension"
@def_index[arcrole][:from][hypercube_id]
end
def find_dimension_default(dimension_id)
arcrole = "http://xbrl.org/int/dim/arcrole/dimension-default"
@def_index[arcrole][:from][dimension_id]&.first
end
def find_dimension_domains(dimension_id)
arcrole = "http://xbrl.org/int/dim/arcrole/dimension-domain"
@def_index[arcrole][:from][dimension_id] || []
end
def find_domain_members(domains)
return [] unless domains
arcrole = "http://xbrl.org/int/dim/arcrole/domain-member"
domains.flat_map do |domain|
children = @def_index[arcrole][:from][domain.element_id]
(domain.arcrole == arcrole) ? [domain] + find_domain_members(children) : find_domain_members(children)
end
end
end
end
end