@Professionalize.Discourse
Thank you for your answer, here is the Builder I use (every parameter are POCO for encapsulation):
internal class XBRLCreationBuilder : IXBRLCreationBuilder
{
private readonly Dictionary<string, Context> Contexts = [];
private readonly Dictionary<string, Unit> Units = [];
public readonly List<ValidationErrorForXBRL> ValidationsErrors = [];
private readonly XbrlDocument Document;
private readonly SchemaRef Schema;
private readonly XbrlInstance XBRLInstance;
internal XBRLCreationBuilder()
{
Document = new();
(XBRLInstance, Schema) = SetupDocument();
}
public void AddContext(ContextForXBRL contextForXBRL)
{
if(!Contexts.ContainsKey(contextForXBRL.ToString())) {
ContextPeriod period = contextForXBRL.Period.EndDate.HasValue ? new(contextForXBRL.Period.StartDate.Value, contextForXBRL.Period.EndDate.Value) : new(contextForXBRL.Period.StartDate.Value);
ContextEntity entity = new(contextForXBRL.Entity.Scheme, contextForXBRL.Entity.Identifier);
Context context = new (period, entity);
if(contextForXBRL.IsArrayContext) {
context.Scenario = new ContextSenario();
contextForXBRL.Scenario.TypedDimensions.ForEach((KeyValuePair<string, string> pair) => {
Concept typedConcept = GetConceptByXbrlCode(pair.Key);
Concept explicitConcept = GetConceptByXbrlCode(pair.Value);
context.Scenario.DimensionMemberList.Add(new DimensionMember(typedConcept, explicitConcept));
});
contextForXBRL.Scenario.ExplicitDimensions.ForEach((KeyValuePair<string, string> pair) => {
Concept typedConcept = GetConceptByXbrlCode(pair.Key);
Concept explicitConcept = GetConceptByXbrlCode(pair.Value);
context.Scenario.DimensionMemberList.Add(new DimensionMember(typedConcept, explicitConcept));
});
}
context.Id = $"c_{Contexts.Count}";
Contexts.Add(contextForXBRL.ToString(), context);
XBRLInstance.Contexts.Add(context);
}
}
public void AddFact(IndicatorForXBRL indicatorForXBRL, IndicatorValueForXBRL indicatorValueForXBRL)
{
AddContext(indicatorValueForXBRL.Context);
// TODO check if good because in the xbrl norm, normally the unit is mandatory
if(indicatorValueForXBRL.Unit != null)
{
AddUnit(indicatorValueForXBRL.Unit);
}
Concept concept = GetConceptByXbrlCode(indicatorForXBRL.XbrlCode);
if(concept != null)
{
Item fact = new(concept)
{
ContextRef = Contexts[indicatorValueForXBRL.Context.ToString()],
Value = indicatorValueForXBRL.Value,
Name = new QualifiedName(concept.Name, XBRLNamespace.ESRS.LocalName, XBRLNamespace.ESRS.Namespace)
};
// TODO verify if the unit is mandatory
if(indicatorValueForXBRL.Unit != null && Units.ContainsKey(indicatorValueForXBRL.Unit.ToString()))
{
fact.UnitRef = Units[indicatorValueForXBRL.Unit.ToString()];
}
if (indicatorValueForXBRL.Decimals.HasValue)
{
fact.Decimals = indicatorValueForXBRL.Decimals.Value;
}
fact.Id = $"f_{XBRLInstance.Facts.Count}";
XBRLInstance.Facts.Add(fact);
}
}
public void AddUnit(UnitForXBRL unitForXBRL)
{
if(!Units.ContainsKey(unitForXBRL.ToString()))
{
UnitType unitType = unitForXBRL.Numerator != null && unitForXBRL.Denominator != null ? UnitType.Divide : UnitType.Measure;
Unit unit = new (unitType);
if(unitType == UnitType.Divide) {
unit.UnitDenominatorQualifiedNames.Add(new QualifiedName(unitForXBRL.Denominator.NormalizedName, unitForXBRL.Denominator.XBRLNamespace.GetLastPathSegment(), unitForXBRL.Denominator.XBRLNamespace));
unit.UnitNumeratorQualifiedNames.Add(new QualifiedName(unitForXBRL.Numerator.NormalizedName, unitForXBRL.Numerator.XBRLNamespace.GetLastPathSegment(), unitForXBRL.Numerator.XBRLNamespace));
} else {
unit.MeasureQualifiedNames.Add(new QualifiedName(unitForXBRL.Numerator.NormalizedName, unitForXBRL.Numerator.XBRLNamespace.GetLastPathSegment(), unitForXBRL.Numerator.XBRLNamespace));
}
unit.Id = $"u_{Units.Count}";
Units.Add(unitForXBRL.ToString(), unit);
XBRLInstance.Units.Add(unit);
}
}
public void BuildXBRL(string filePathWithExtension)
{
Build(filePathWithExtension, SaveFormat.XBRL);
}
public void BuildIXBRL(string filePathWithExtension)
{
Build(filePathWithExtension, SaveFormat.IXBRL);
}
public void Validate()
{
XBRLInstance.Validate();
ValidationsErrors.AddRange(XBRLInstance.ValidationErrors.Select(GenerateClassicError));
}
private (XbrlInstance, SchemaRef) SetupDocument()
{
XbrlInstanceCollection xbrlInstances = Document.XbrlInstances;
XbrlInstance instance = xbrlInstances[xbrlInstances.Add()];
SchemaRefCollection schemaRefs = instance.SchemaRefs;
schemaRefs.Add(XBRLNamespace.ESRS_XSD.Namespace, XBRLNamespace.ESRS_XSD.LocalName, XBRLNamespace.ESRS.Namespace);
return (instance, schemaRefs[0]);
}
private Concept GetConceptByXbrlCode(string xbrlCode)
{
Concept fixedAssetsConcept = Schema.GetConceptByName(xbrlCode);
return fixedAssetsConcept;
}
private void Build(string filePathWithExtension, SaveFormat format)
{
Directory.CreateDirectory(Path.GetDirectoryName(filePathWithExtension));
if(!File.Exists(filePathWithExtension))
{
File.Create(filePathWithExtension).Close();
}
Document.Save(filePathWithExtension, new SaveOptions() { SaveFormat = format, SaveLinkbasesToDesticationFolder = false, SaveSchemasToDesticationFolder = false });
}
}
So every time i add an ArrayContext (aka dimension), it seems that the code use a random generated prefix namespace.
Also, i observed that the shemaRef i add generate an entry into the xmlns as xmlns:esrs=“https://xbrl.efrag.org/taxonomy/esrs/2023-12-22/entry” xmlns:xbrldi=“http://xbrl.org/2006/xbrldi”>
which is wrong but looking into the https://xbrl.efrag.org/taxonomy/esrs/2023-12-22/esrs_all.xsd file we see that the esrs namespace is linked into targetNamespace from the xsd file…