/// <summary> /// A definition network attaches one (or at least one?) hypercube to a concept. /// </summary> /// <remarks> /// The role of some definition linkbases have a suffix (a, b, ...) added to the role of the presentation network. /// We have to disregard this suffix to reliably find the attached hypercubes. /// /// For example, for the presentation network with the role http://xbrl.ifrs.org/role/ifrs/ias_19_2017-03-09_role-834480 /// there exist these definition network roles, each defining a single hypercube: /// http://xbrl.ifrs.org/role/ifrs/ias_19_2017-03-09_role-834480 /// http://xbrl.ifrs.org/role/ifrs/ias_19_2017-03-09_role-834480a /// http://xbrl.ifrs.org/role/ifrs/ias_19_2017-03-09_role-834480b /// http://xbrl.ifrs.org/role/ifrs/ias_19_2017-03-09_role-834480c /// http://xbrl.ifrs.org/role/ifrs/ias_19_2017-03-09_role-834480d /// (All bundled inside the def_ias_19_2017-03-09_role-834480.xml linkbase) /// /// Clearly, the authors of the IFRS taxonomy wanted to have a single hypercube per definition link. /// Question: is that a requirement from XBRL Dimensions? /// </remarks> /// <remarks> /// A hypercube arc (all or notAll) connects to a *concept* inside a specific presentation network, which is not actually precise. /// Instead, it should attach to a *locator node*. /// The same issue appears with extensions of existing presentation, definition or calculation networks. /// </remarks> private Hypercube GetAttachedHypercube(LocatorNode node) { var item = node.Item; // Oddly, the role of some definition linkbase have a suffix (a, b, ...) added to the role of the presentation network. // We have to disregard this suffix to reliably find the attached hypercubes. var hypercubeArc = item.Nodes .SelectMany(n => n.FromArcs.OfType <DefinitionArc>()) .SingleOrDefault(a => a.Link.Role.AbsoluteUri.StartsWith(node.Link.Role.AbsoluteUri) && a.Role == ArcRoles.PositiveHypercube); if (hypercubeArc == null) { return(null); } var hypercube = new Hypercube(hypercubeArc.ToLocator.Item); var dimensionNodes = hypercubeArc.ToLocator.FromArcs.OfType <DefinitionArc>().Select(a => a.ToLocator); foreach (var dimensionNode in dimensionNodes) { var axis = BuildHypercubeAxis(dimensionNode); hypercube.Axes.Add(axis); } return(hypercube); }
private ConceptMember TraversePresentationNetwork(LocatorNode node, Uri preferredLabelRole, ISet <XName> hypercubeDimensionNames) { var concept = node.Item; var aspect = new ConceptAspect(concept.Name); var member = new ConceptMember(aspect) { Item = concept, PreferredLabelRole = preferredLabelRole, Label = concept.FindLabel(preferredLabelRole), HypercubeDimensionNames = hypercubeDimensionNames }; var outgoingArcs = node.GetOrderedOutgoingHierarchicalArcs <PresentationArc>(); var attachedHypercube = GetAttachedHypercube(node); if (attachedHypercube != null) { if (outgoingArcs.Count != 1) { throw new InvalidOperationException($"The hypercube {attachedHypercube.HypercubeItem.Name} has {outgoingArcs.Count} line items children, but exactly one is expected."); } var outgoingArc = outgoingArcs.Single(); // We are replicating and adding the line items tree for each axis, including the respective dimension name to each concept member. foreach (var hypercubeAxis in attachedHypercube.Axes) { var childHypercubeDimensionNames = new HashSet <XName>(hypercubeDimensionNames) { hypercubeAxis.Dimension.Name }; var child = TraversePresentationNetwork(outgoingArc.ToLocator, outgoingArc.PreferredLabelRole, childHypercubeDimensionNames); child.HypercubeAxis = hypercubeAxis; member.AddChild(child); } } else { foreach (var outgoingArc in outgoingArcs) { var child = TraversePresentationNetwork(outgoingArc.ToLocator, outgoingArc.PreferredLabelRole, hypercubeDimensionNames); member.AddChild(child); } } return(member); }
private Axis BuildHypercubeAxis(LocatorNode dimensionNode) { var rootNodes = dimensionNode.FromArcs.OfType <DefinitionArc>() .Where(a => a.Role == ArcRoles.DimensionToDomain) .Select(a => a.ToLocator) .ToList(); // Just like for presentation networks, the IFRS taxonomy only has single roots for hypercube dimensions. if (rootNodes.Count > 1) { throw new InvalidOperationException($"The hypercube dimension {dimensionNode.Item.Name} has multiple root nodes."); } var dimension = new Dimension(dimensionNode.Item.Name); var rootMembers = rootNodes.Select(n => TraverseHypercubeDimension(n, dimension)).ToList(); return(new Axis(dimension, dimension.Name, rootMembers)); }
private ExplicitMember TraverseHypercubeDimension(LocatorNode node, Dimension dimension) { var memberItem = node.Item; var aspect = new ExplicitMemberAspect(dimension, memberItem.Name); var member = new ExplicitMember(aspect) { Item = memberItem, Label = memberItem.FindLabel(LabelRoles.Standard) }; var outgoingArcs = node.GetOrderedOutgoingHierarchicalArcs <DefinitionArc>(); foreach (var outgoingArc in outgoingArcs) { var child = TraverseHypercubeDimension(outgoingArc.ToLocator, dimension); member.AddChild(child); } return(member); }
private LocatorNode FindParentNode(LocatorNode locatorNode, XName parentName) { if (locatorNode.Item.Name == parentName) { return(locatorNode); } var outgoingArcs = locatorNode.GetOrderedOutgoingHierarchicalArcs <DefinitionArc>(); foreach (var outgoingArc in outgoingArcs) { var childResult = FindParentNode(outgoingArc.ToLocator, parentName); if (childResult != null) { return(childResult); } } return(null); }
private Linkbase CreateDefinitionLinkbase(Taxonomy taxonomy, DiscoverableTaxonomySet dts) { var linkbase = new Linkbase(GetLinkbaseFileName(taxonomy, "def")); var memberItems = ExtensionItems.OfType <ExtensionMember>().ToList(); var groupedByHypercube = memberItems.SelectMany(i => i.Locations.Select(l => new { l.HypercubeName, Location = l, Item = i })) .GroupBy(d => d.HypercubeName) .ToList(); foreach (var groupByHypercube in groupedByHypercube) { var hypercubeNode = dts.GetAllLinks() .Where(l => l.ElementName == LinkbaseXNames.Definition) .SelectMany(l => l.NodesMap.Values) .OfType <LocatorNode>() .SingleOrDefault(ln => ln.Item.Name == groupByHypercube.Key); if (hypercubeNode == null) { throw new InvalidOperationException($"Could not find a hypercube (aka table) {groupByHypercube.Key} in the DTS."); } var definitionLink = new DefinitionLink(LinkbaseXNames.Definition, hypercubeNode.Link.Role); linkbase.AddLink(definitionLink, dts); var locCount = 0; foreach (var locationAndItem in groupByHypercube) { var dimensionLocationNode = hypercubeNode.FromArcs .OfType <DefinitionArc>() .Select(da => da.ToLocator) .SingleOrDefault(ln => ln.Item.Name == locationAndItem.Location.DimensionName); if (dimensionLocationNode == null) { throw new InvalidOperationException($"Could not find a dimension (aka axis) {locationAndItem.Location.DimensionName} in the hypercube (aka table) {groupByHypercube.Key}."); } var parentNodeInBaseTaxonomy = FindParentNode(dimensionLocationNode, locationAndItem.Location.ParentName); if (parentNodeInBaseTaxonomy == null) { throw new InvalidOperationException($"There is no member {locationAndItem.Location.ParentName} in the dimension {groupByHypercube.Key}."); } var siblingArcs = parentNodeInBaseTaxonomy.GetOrderedOutgoingHierarchicalArcs <DefinitionArc>(); double newOrder; try { newOrder = DetermineOrder(locationAndItem.Location.PrecedingSiblingName, siblingArcs); } catch (InvalidOperationException ex) { throw new InvalidOperationException($"The sibling {locationAndItem.Location.PrecedingSiblingName} was not found as a child of {locationAndItem.Location.ParentName} in the dimension {groupByHypercube.Key}.", ex); } var parentLocLabel = $"loc_{locCount}"; locCount += 1; var parentNode = new LocatorNode(parentLocLabel, parentNodeInBaseTaxonomy.Item); definitionLink.AddNode(parentNode); var locLabel = $"loc_{locCount}"; locCount += 1; var locNode = CreateLocatorNode(locLabel, locationAndItem.Item, dts); definitionLink.AddNode(locNode); var arc = new DefinitionArc(parentNode, locNode, newOrder); definitionLink.AddArc(arc); } } return(linkbase); }
private Linkbase CreatePresentationLinkbase(Taxonomy taxonomy, DiscoverableTaxonomySet dts) { var conceptItems = ExtensionItems.OfType <ExtensionConcept>().ToList(); // Does that include abstracts? var linkbase = new Linkbase(GetLinkbaseFileName(taxonomy, "pre")); var groupedByNetworkRole = conceptItems.SelectMany(i => i.Locations.Select(l => new { l.NetworkRole, Location = l, Item = i })) .GroupBy(d => d.NetworkRole) .ToList(); foreach (var groupByNetworkRole in groupedByNetworkRole) { var presentationLink = new PresentationLink(LinkbaseXNames.Presentation, groupByNetworkRole.Key); linkbase.AddLink(presentationLink, dts); var locCount = 0; foreach (var locationAndItem in groupByNetworkRole) { var parentItem = dts.FindItem(locationAndItem.Location.ParentName); if (parentItem == null) { throw new InvalidOperationException($"There is no concept {locationAndItem.Location.ParentName} in the DTS."); } var parentLocatorNodes = parentItem.Nodes .Where(n => n.Link.Role == groupByNetworkRole.Key).ToList(); if (!parentLocatorNodes.Any()) { throw new InvalidOperationException($"The concept {locationAndItem.Location.ParentName} is not in the presentation network {groupByNetworkRole.Key}."); } var siblingArcs = parentLocatorNodes.First().GetOrderedOutgoingHierarchicalArcs <PresentationArc>(); double newOrder; try { newOrder = DetermineOrder(locationAndItem.Location.PrecedingSiblingName, siblingArcs); } catch (InvalidOperationException ex) { throw new InvalidOperationException($"The sibling {locationAndItem.Location.PrecedingSiblingName} was not found as a child of {locationAndItem.Location.ParentName} in the presentation network {groupByNetworkRole.Key}.", ex); } var parentLocLabel = $"loc_{locCount}"; locCount += 1; var parentNode = new LocatorNode(parentLocLabel, parentItem); presentationLink.AddNode(parentNode); var childLocLabel = $"loc_{locCount}"; locCount += 1; var childNode = CreateLocatorNode(childLocLabel, locationAndItem.Item, dts); presentationLink.AddNode(childNode); var arc = new PresentationArc(parentNode, childNode, newOrder, locationAndItem.Location.PreferredLabelRole); presentationLink.AddArc(arc); } } return(linkbase); }