Incorrect or incomplete validation

Hi again,

Still testing the library, I wanted to evaluate the validation and compare the results with another library (XBRL-certified): Arelle (by no means am I intending to criticize or make complaints).

However, I notice differences in the validations: Aspose only detects a few errors and also finds errors in the validation files (XSD), which is less relevant to me. Attached, you will find a ZIP file containing all the logs (with and without corrections to the namespaces of the dimensions). Do you have any idea or suggestion?

Thanks a lot !

tests validations.7z (43,4 Ko)

@Deboveq

Could you please provide more details about the specific validation errors you are encountering with Aspose and how they differ from the results in Arelle?

@Deboveq,

Aspose.Finance emphasizes file structure, adherence to schema (XSD), and fundamental XBRL validations. In contrast, it seems Arelle appears to be built to meet the XBRL standards and may conduct more comprehensive semantic validations. Regardless, we would be happy to thoroughly assess your issue and implement improvements to the API (if appropriate). Could you please provide your sample file(s) and a code snippet using Aspose.Finance so we can investigate further.

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; }
}

@Deboveq,

We need to evaluate and investigate your issue regarding incorrect or incomplete validations by Aspose.Finance. We have opened the following new ticket(s) in our internal issue tracking system and will deliver their fixes according to the terms mentioned in Free Support Policies.

Issue ID(s): FINANCENET-392

You can obtain Paid Support Services if you need support on a priority basis, along with the direct access to our Paid Support management team.

1 Like