Thanks for your response !
Here is the errors i logged from Aspose/Arielle :
tests validations.7z (43,4 Ko)
Now here is the xbrl file :
CSRD.7z (2,3 Ko)
and here the code snippet i use (very simple) :
public class XBRLService : IXBRLService
{
private readonly IExerciseServiceForReporting _exerciseServiceForReporting;
private readonly IExerciseService _exerciseService;
private readonly HelperService _helperService;
public XBRLService(
IExerciseServiceForReporting exerciseServiceForReporting,
HelperService helperService,
IExerciseService exerciseService
)
{
_exerciseServiceForReporting = exerciseServiceForReporting;
_helperService = helperService;
_exerciseService = exerciseService;
}
public List<ValidationErrorForXBRL> CreateXBRLFile(int exerciseId, AspNetUser user, out string filePath)
{
Exercise exercise = _exerciseService.GetExerciseById(exerciseId);
List<IndicatorForXBRL> indicators = _exerciseServiceForReporting.GenerateIndicatorsForXBRL(exerciseId, user);
XBRLCreationBuilder builder = new();
for(int i = 0; i < indicators.Count; i++)
{
IndicatorForXBRL indicator = indicators[i];
for(int j = 0; j < indicator.Values.Count; j++)
{
IndicatorValueForXBRL value = indicator.Values[j];
builder.AddFact(indicator, value);
}
}
builder.Validate();
filePath = Path.Combine(_helperService.GED_PATH, exercise.Id.ToString(), $"{exercise.Name}.xbrl");
builder.BuildXBRL(filePath);
return builder.ValidationsErrors;
}
}
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();
// TODO fix for TypedDimensions with NormalizedName empty/null => should not occurred
contextForXBRL.Scenario.TypedDimensions.Where(x => !string.IsNullOrEmpty(x.NormalizedName)).ForEach((TypedDimensionForXBRL typedDimensionForXBRL) => {
Concept typedConcept = GetConceptByXbrlCode(typedDimensionForXBRL.DimensionNormalizedName);
Element element = XBRLInstance.CreateElement(XBRLNamespace.ESRS.Namespace, typedDimensionForXBRL.NormalizedName, XBRLNamespace.ESRS.LocalName);
element.TextContent = typedDimensionForXBRL.Value;
context.Scenario.DimensionMemberList.Add(new DimensionMember(typedConcept, element));
});
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 });
}
private ValidationErrorForXBRL GenerateClassicError(ValidationError errorFromLib)
{
ValidationErrorForXBRL classicError = new();
classicError.Message = errorFromLib.Message;
classicError.Target = errorFromLib switch
{
ArcValidationError arcError => arcError.Object,
ContextValidationError contextError => contextError.Object,
FactValidationError factError => factError.Object,
InlineFactValidationError inlineFactError => inlineFactError.Object,
InlineRelationshipError inlineRelationshipError => inlineRelationshipError.Object,
InlineValidationError inlineError => inlineError.Object,
SchemaRefValidationError schemaRefError => schemaRefError.Object,
_ => null
};
return classicError;
}
}
public class ValidationErrorForXBRL
{
public string Message { get; set; }
public ValidationErrorTypeForXBRL Type { get; set; }
public object Target { get; set; }
public string TargetUri { get; set; }
}