private void WriteConceptMember(IPresenterWriter presenterWriter, ConceptMember conceptMember, Axis verticalAxis, IList <Member> horizontalAxisMembers, FactSet facts) { var hypercubeAxis = conceptMember.HypercubeAxis; if (hypercubeAxis != null) { if (horizontalAxisMembers.Count != 1) { throw new InvalidOperationException($"Hypercubes require a single period, but the hypercube '{conceptMember.Label}' has {horizontalAxisMembers.Count} periods."); } // It is crucial to include related aspects to keep beginning/end of period // facts in the fact set. var periodSlicedFactSet = facts.Slice(horizontalAxisMembers.Single(), includeRelatedAspects: true); var reducedFactSet = periodSlicedFactSet.Reduce(hypercubeAxis); if (!reducedFactSet.FactModels.Any()) { // No fact in this hypercube axis: nothing to show. // This should not happen since the concept axis got reduced already. return; } var verticalChildAxis = verticalAxis.CreateChildAxis(conceptMember); // Clear the hypercube axis to signal consumption and avoid infinite loop. conceptMember.HypercubeAxis = null; WriteTable(presenterWriter, verticalChildAxis, hypercubeAxis, reducedFactSet); return; } var verticallySlicedFactSet = facts.Slice(conceptMember, false); var cellFacts = new Dictionary <Member, FactModel>(); foreach (var horizontalAxisMember in horizontalAxisMembers) { // It is crucial to include related aspects to keep beginning/end of period // facts in the fact set. var horizontallySlicedFactSet = verticallySlicedFactSet.Slice(horizontalAxisMember, true); var cellFact = horizontallySlicedFactSet.GetCellFact(); cellFacts[horizontalAxisMember] = cellFact; } presenterWriter.WriteConcept(conceptMember, cellFacts); foreach (var childMember in conceptMember.Children.Cast <ConceptMember>()) { WriteConceptMember(presenterWriter, childMember, verticalAxis, horizontalAxisMembers, facts); } }
/// <summary> /// Builds up the specified presentation networks by filling in the facts from the XBRL instance. /// The format-specific rendering is done by the writer which assembles the final result. /// </summary> public void Present(IPresenterWriter presenterWriter, ScopeSettings settings) { _settings = settings; var factSet = new FactSet(Instance.Facts); factSet.EnsureNoCollisions(); foreach (var factModel in factSet.FactModels) { factModel.EnsureDistinctDimensions(); factModel.EnsureConsistentPeriodTypes(Instance.Dts); } var standardAxes = BuildStandardAxes(); factSet.EnsureStandardAxesContainFactAspects(standardAxes); presenterWriter.WriteBeginExport(Instance); // 1. Slice by single entity. var entityAxis = FindAxis(standardAxes, Dimension.EntityDimension.Name); var reducedEntityAxis = entityAxis.Reduce(factSet); if (!reducedEntityAxis.Roots.Any()) { throw new InstanceExportException("The reduced entity axis is empty. What the ?!"); } if (reducedEntityAxis.Roots.Count > 1) { throw new InstanceExportException("The instance contains more than one entity. This is not supported."); } var entityMember = entityAxis.Roots[0]; var entityAspect = (EntityAspect)entityMember.Aspect; factSet = factSet.Slice(entityMember, false); // 2. Slice by single currency unit. var unitAxis = FindAxis(standardAxes, Dimension.UnitDimension.Name); var reducedUnitAxis = unitAxis.Reduce(factSet); if (!reducedUnitAxis.Roots.Any()) { throw new InstanceExportException("The reduced unit axis is empty. What the ?!"); } var hasNonCurrencyAspects = reducedUnitAxis.Roots.Select(r => (UnitAspect)r.Aspect).Any(ua => !(ua.Unit is CurrencyUnit)); if (hasNonCurrencyAspects) { throw new InstanceExportException("The instance contains non-currency units. This is not supported yet."); } var currencyMembers = reducedUnitAxis.Roots.Where(m => ((UnitAspect)m.Aspect).Unit is CurrencyUnit).ToList(); if (currencyMembers.Count > 1) { throw new InstanceExportException("The instance contains more than one currency unit. This is not supported."); } var currencyMember = currencyMembers[0]; var currencyAspect = (UnitAspect)currencyMember.Aspect; factSet = factSet.Slice(currencyMember, true); // textual facts will remain because the 'text unit' is related to all currency units. // 3. Determine first duration period. var periodAxis = FindAxis(standardAxes, Dimension.PeriodDimension.Name); var reducedPeriodAxis = periodAxis.Reduce(factSet); if (!reducedPeriodAxis.Roots.Any()) { throw new InstanceExportException("The reduced period axis is empty. What the ?!"); } //var firstDurationAspect = reducedPeriodAxis.Roots.Select(r => (PeriodAspect) r.Aspect).FirstOrDefault(ua => ua.Period is DurationPeriod); //if (firstDurationAspect == null) //{ // throw new XbrlExportException("The instance does not contain any duration period. This is not supported."); //} // Instance looks okay. Ready to write it out. presenterWriter.WriteIntro(entityAspect.Entity, currencyAspect.Unit as CurrencyUnit); WriteNetworks(standardAxes, factSet, settings.PresentationNetworks, presenterWriter); presenterWriter.WriteEndExport(); }