arelle-release 2.37.22__py3-none-any.whl → 2.37.23__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of arelle-release might be problematic. Click here for more details.
- arelle/ModelRelationshipSet.py +3 -0
- arelle/ValidateDuplicateFacts.py +13 -7
- arelle/XbrlConst.py +1 -0
- arelle/_version.py +2 -2
- arelle/plugin/validate/DBA/rules/fr.py +10 -10
- arelle/plugin/validate/DBA/rules/th.py +1 -1
- arelle/plugin/validate/ESEF/ESEF_Current/ValidateXbrlFinally.py +21 -20
- arelle/plugin/validate/NL/PluginValidationDataExtension.py +128 -4
- arelle/plugin/validate/NL/resources/config.xml +6 -0
- arelle/plugin/validate/NL/rules/fr_kvk.py +1 -1
- arelle/plugin/validate/NL/rules/nl_kvk.py +457 -22
- arelle/utils/validate/DetectScriptsInXhtml.py +1 -4
- arelle/utils/validate/ESEFImage.py +274 -0
- {arelle_release-2.37.22.dist-info → arelle_release-2.37.23.dist-info}/METADATA +1 -1
- {arelle_release-2.37.22.dist-info → arelle_release-2.37.23.dist-info}/RECORD +21 -21
- tests/integration_tests/validation/conformance_suite_configurations/nl_inline_2024.py +26 -21
- tests/unit_tests/arelle/plugin/validate/ESEF/ESEF_Current/test_validate_css_url.py +10 -2
- arelle/plugin/validate/ESEF/ESEF_Current/Image.py +0 -213
- {arelle_release-2.37.22.dist-info → arelle_release-2.37.23.dist-info}/WHEEL +0 -0
- {arelle_release-2.37.22.dist-info → arelle_release-2.37.23.dist-info}/entry_points.txt +0 -0
- {arelle_release-2.37.22.dist-info → arelle_release-2.37.23.dist-info}/licenses/LICENSE.md +0 -0
- {arelle_release-2.37.22.dist-info → arelle_release-2.37.23.dist-info}/top_level.txt +0 -0
arelle/ModelRelationshipSet.py
CHANGED
|
@@ -218,6 +218,9 @@ class ModelRelationshipSet:
|
|
|
218
218
|
def __bool__(self): # some modelRelationships exist
|
|
219
219
|
return len(self.modelRelationships) > 0
|
|
220
220
|
|
|
221
|
+
def contains(self, modelObject: ModelObject) -> bool:
|
|
222
|
+
return bool(self.fromModelObject(modelObject) or self.toModelObject(modelObject))
|
|
223
|
+
|
|
221
224
|
@property
|
|
222
225
|
def linkRoleUris(self):
|
|
223
226
|
# order by document appearance of linkrole, required for Table Linkbase testcase 3220 v03
|
arelle/ValidateDuplicateFacts.py
CHANGED
|
@@ -162,7 +162,7 @@ class DuplicateFactSet:
|
|
|
162
162
|
decimalValues: dict[float | int, TypeXValue] = {}
|
|
163
163
|
for fact in self.facts:
|
|
164
164
|
value = fact.xValue
|
|
165
|
-
if
|
|
165
|
+
if _isNanOrNone(value):
|
|
166
166
|
# NaN values are not comparable, can't be equal/consistent.
|
|
167
167
|
return False
|
|
168
168
|
decimals = self.getDecimals(fact)
|
|
@@ -224,7 +224,7 @@ class DuplicateFactSet:
|
|
|
224
224
|
groupLower = decimalsMap[decimalLower]
|
|
225
225
|
for factA in groupLower:
|
|
226
226
|
lowerA, upperA = self.getRange(factA)
|
|
227
|
-
if
|
|
227
|
+
if _isNanOrNone(factA.xValue):
|
|
228
228
|
continue
|
|
229
229
|
remove = False
|
|
230
230
|
# Iterate through each higher decimals group
|
|
@@ -232,7 +232,7 @@ class DuplicateFactSet:
|
|
|
232
232
|
groupHigher = decimalsMap[decimalHigher]
|
|
233
233
|
for factB in groupHigher:
|
|
234
234
|
lowerB, upperB = self.getRange(factB)
|
|
235
|
-
if
|
|
235
|
+
if _isNanOrNone(factB.xValue):
|
|
236
236
|
continue
|
|
237
237
|
if lowerB <= upperA and upperB >= lowerA:
|
|
238
238
|
remove = True
|
|
@@ -342,6 +342,12 @@ DUPLICATE_TYPE_ARG_MAP = {
|
|
|
342
342
|
}
|
|
343
343
|
|
|
344
344
|
|
|
345
|
+
def _isNanOrNone(value: TypeXValue) -> bool:
|
|
346
|
+
if value is None:
|
|
347
|
+
return True
|
|
348
|
+
return isnan(cast(SupportsFloat, value))
|
|
349
|
+
|
|
350
|
+
|
|
345
351
|
def doesSetHaveDuplicateType(
|
|
346
352
|
duplicateFacts: DuplicateFactSet, duplicateType: DuplicateType
|
|
347
353
|
) -> bool:
|
|
@@ -504,15 +510,15 @@ def getFactValueEqualityKey(fact: ModelFact) -> TypeFactValueEqualityKey:
|
|
|
504
510
|
:param fact:
|
|
505
511
|
:return: A key to be used for fact-value-equality comparison.
|
|
506
512
|
"""
|
|
507
|
-
if fact.isNil:
|
|
508
|
-
return FactValueEqualityType.DEFAULT, (None,)
|
|
509
513
|
xValue = fact.xValue
|
|
514
|
+
if xValue is None or fact.isNil:
|
|
515
|
+
return FactValueEqualityType.DEFAULT, (None,)
|
|
510
516
|
if fact.isNumeric:
|
|
511
|
-
if
|
|
517
|
+
if _isNanOrNone(xValue):
|
|
512
518
|
return FactValueEqualityType.DEFAULT, (float("nan"),)
|
|
513
519
|
if fact.concept.isLanguage:
|
|
514
520
|
return FactValueEqualityType.LANGUAGE, (
|
|
515
|
-
cast(str, xValue).lower()
|
|
521
|
+
cast(str, xValue).lower(),
|
|
516
522
|
)
|
|
517
523
|
if isinstance(xValue, DateTime): # with/without time makes values unequal
|
|
518
524
|
return FactValueEqualityType.DATETIME, (xValue, xValue.dateOnly)
|
arelle/XbrlConst.py
CHANGED
|
@@ -20,6 +20,7 @@ _: TypeGetText
|
|
|
20
20
|
_tuple = tuple # type: ignore[type-arg]
|
|
21
21
|
|
|
22
22
|
xsd = "http://www.w3.org/2001/XMLSchema"
|
|
23
|
+
qnXsdComplexType = qname("{http://www.w3.org/2001/XMLSchema}xsd:complexType")
|
|
23
24
|
qnXsdSchema = qname("{http://www.w3.org/2001/XMLSchema}xsd:schema")
|
|
24
25
|
qnXsdAppinfo = qname("{http://www.w3.org/2001/XMLSchema}xsd:appinfo")
|
|
25
26
|
qnXsdDefaultType = qname("{http://www.w3.org/2001/XMLSchema}xsd:anyType")
|
arelle/_version.py
CHANGED
|
@@ -437,7 +437,7 @@ def rule_fr48(
|
|
|
437
437
|
if len(foundFacts) > 0:
|
|
438
438
|
yield Validation.warning(
|
|
439
439
|
codes="DBA.FR48",
|
|
440
|
-
msg=_("Annual reports with a start date of 1/1 2016 or later must not use the fields:"
|
|
440
|
+
msg=_("Annual reports with a start date of 1/1 2016 or later must not use the fields: "
|
|
441
441
|
"'Extraordinary profit before tax', 'Extraordinary income', 'Extraordinary costs'."),
|
|
442
442
|
modelObject=foundFacts
|
|
443
443
|
)
|
|
@@ -729,7 +729,7 @@ def rule_fr57(
|
|
|
729
729
|
for pair in equalityErrorPairs:
|
|
730
730
|
yield Validation.error(
|
|
731
731
|
codes="DBA.FR57.Equality",
|
|
732
|
-
msg=_("The total of Assets (fsa:Assets) must be equal to the total of Liabilities and Equity (fsa:LiabilitiesAndEquity)."
|
|
732
|
+
msg=_("The total of Assets (fsa:Assets) must be equal to the total of Liabilities and Equity (fsa:LiabilitiesAndEquity). "
|
|
733
733
|
"Assets: %(Assets)s Liabilities and Equity: %(LiabilitiesAndEquity)s"),
|
|
734
734
|
Assets=pair[0].effectiveValue,
|
|
735
735
|
LiabilitiesAndEquity=pair[1].effectiveValue,
|
|
@@ -745,7 +745,7 @@ def rule_fr57(
|
|
|
745
745
|
for fact in negativeLiabilitiesAndEquityFacts:
|
|
746
746
|
yield Validation.error(
|
|
747
747
|
codes="DBA.FR57.NegativeLiabilitiesAndEquity",
|
|
748
|
-
msg=_("Liabilities and Equity (fsa:LiabilitiesAndEquity) must not be negative."
|
|
748
|
+
msg=_("Liabilities and Equity (fsa:LiabilitiesAndEquity) must not be negative. "
|
|
749
749
|
"Liabilities and Equity was tagged with the value: %(factValue)s"),
|
|
750
750
|
factValue = fact.effectiveValue,
|
|
751
751
|
modelObject=fact
|
|
@@ -753,7 +753,7 @@ def rule_fr57(
|
|
|
753
753
|
if not len(liabilitiesAndEquityErrors) == 0:
|
|
754
754
|
yield Validation.error(
|
|
755
755
|
codes="DBA.FR57.LiabilitiesAndEquity",
|
|
756
|
-
msg=_("Liabilities and equity (fsa:LiabilitiesAndEquity) in the balance sheet must be filled in."
|
|
756
|
+
msg=_("Liabilities and equity (fsa:LiabilitiesAndEquity) in the balance sheet must be filled in. "
|
|
757
757
|
"There is a problem with the reporting period ending: %(periods)s"),
|
|
758
758
|
periods = ", ".join([cast(datetime.datetime, dt).strftime("%Y-%m-%d") for dt in liabilitiesAndEquityErrors])
|
|
759
759
|
)
|
|
@@ -761,7 +761,7 @@ def rule_fr57(
|
|
|
761
761
|
for profitLossError in profitLossErrors:
|
|
762
762
|
yield Validation.error(
|
|
763
763
|
codes="DBA.FR57.ProfitLoss",
|
|
764
|
-
msg=_("The profit for the year (fsa:ProfitLoss) in the income statement must be filled in."
|
|
764
|
+
msg=_("The profit for the year (fsa:ProfitLoss) in the income statement must be filled in. "
|
|
765
765
|
"There is a problem with the reporting periods starting %(start)s and ending: %(end)s"),
|
|
766
766
|
start = profitLossError[0],
|
|
767
767
|
end = profitLossError[1]
|
|
@@ -995,7 +995,7 @@ def rule_fr73(
|
|
|
995
995
|
return
|
|
996
996
|
yield Validation.warning(
|
|
997
997
|
codes='DBA.FR73',
|
|
998
|
-
msg=_("When the field ReportingResponsibilitiesAccordingToTheDanishExecutiveOrderOnApprovedAuditorsReportsExtendedReview is completed"
|
|
998
|
+
msg=_("When the field ReportingResponsibilitiesAccordingToTheDanishExecutiveOrderOnApprovedAuditorsReportsExtendedReview is completed "
|
|
999
999
|
"one or more of the sub-items below must be indicated: "
|
|
1000
1000
|
"ReportingResponsibilitiesAccordingToTheDanishExecutiveOrderOnApprovedAuditorsReportsEspeciallyTheCriminalCodeAndFiscalTaxAndSubsidyLegislationExtendedReview "
|
|
1001
1001
|
"ReportingResponsibilitiesAccordingToTheDanishExecutiveOrderOnApprovedAuditorsReportsEspeciallyTheCompaniesActOrEquivalentLegislationThatTheCompanyIsSubjectToExtendedReview "
|
|
@@ -1036,7 +1036,7 @@ def rule_fr74(
|
|
|
1036
1036
|
if not cast(decimal.Decimal, liabilityFact.xValue) - cast(decimal.Decimal, equityFact.xValue) >= cast(decimal.Decimal, provisionFact.xValue) - ROUNDING_MARGIN:
|
|
1037
1037
|
yield Validation.error(
|
|
1038
1038
|
codes="DBA.FR74a",
|
|
1039
|
-
msg=_("Provisions (fsa:Provisions) must be less than or equal to the balance sheet total (fsa:LiabilitiesAndEquity) minus equity (fsa:Equity)."
|
|
1039
|
+
msg=_("Provisions (fsa:Provisions) must be less than or equal to the balance sheet total (fsa:LiabilitiesAndEquity) minus equity (fsa:Equity). "
|
|
1040
1040
|
"LiabilitiesAndEquity: %(liabilities)s, Equity: %(equity)s, Provisions: %(provisions)s"),
|
|
1041
1041
|
equity=equityFact.effectiveValue,
|
|
1042
1042
|
liabilities=liabilityFact.effectiveValue,
|
|
@@ -1047,7 +1047,7 @@ def rule_fr74(
|
|
|
1047
1047
|
if not cast(decimal.Decimal, liabilityFact.xValue) - cast(decimal.Decimal, equityFact.xValue) >= cast(decimal.Decimal, liabilityOtherFact.xValue) - ROUNDING_MARGIN:
|
|
1048
1048
|
yield Validation.error(
|
|
1049
1049
|
codes="DBA.FR74b",
|
|
1050
|
-
msg=_("Liabilities (fsa:LiabilitiesOtherThanProvisions) must be less than or equal to total assets (fsa:LiabilitiesAndEquity) minus equity (fsa:Equity)."
|
|
1050
|
+
msg=_("Liabilities (fsa:LiabilitiesOtherThanProvisions) must be less than or equal to total assets (fsa:LiabilitiesAndEquity) minus equity (fsa:Equity). "
|
|
1051
1051
|
"LiabilitiesAndEquity: %(liabilities)s, Equity: %(equity)s, LiabilitiesOtherThanProvisions: %(liabilityOther)s"),
|
|
1052
1052
|
equity=equityFact.effectiveValue,
|
|
1053
1053
|
liabilityOther=liabilityOtherFact.effectiveValue,
|
|
@@ -1124,7 +1124,7 @@ def rule_fr77(
|
|
|
1124
1124
|
if not cast(decimal.Decimal, liabilityFact.xValue) - cast(decimal.Decimal, equityFact.xValue) >= cast(decimal.Decimal, longLiabilityFact.xValue) - ROUNDING_MARGIN:
|
|
1125
1125
|
yield Validation.error(
|
|
1126
1126
|
codes="DBA.FR77a",
|
|
1127
|
-
msg=_("Long-term liabilities (fsa:LongtermLiabilitiesOtherThanProvisions) must be less than or equal to the balance sheet total (fsa:LiabilitiesAndEquity) minus equity (fsa:Equity)."
|
|
1127
|
+
msg=_("Long-term liabilities (fsa:LongtermLiabilitiesOtherThanProvisions) must be less than or equal to the balance sheet total (fsa:LiabilitiesAndEquity) minus equity (fsa:Equity). "
|
|
1128
1128
|
"LiabilitiesAndEquity: %(liabilities)s, Equity: %(equity)s, LongtermLiabilitiesOtherThanProvisions: %(longLiabilities)s"),
|
|
1129
1129
|
equity=equityFact.effectiveValue,
|
|
1130
1130
|
liabilities=liabilityFact.effectiveValue,
|
|
@@ -1135,7 +1135,7 @@ def rule_fr77(
|
|
|
1135
1135
|
if not cast(decimal.Decimal, liabilityFact.xValue) - cast(decimal.Decimal, equityFact.xValue) >= cast(decimal.Decimal, shortLiabilityFact.xValue) - ROUNDING_MARGIN:
|
|
1136
1136
|
yield Validation.error(
|
|
1137
1137
|
codes="DBA.FR77b",
|
|
1138
|
-
msg=_("Short-term liabilities (fsa:ShorttermLiabilitiesOtherThanProvisions) must be less than or equal to the balance sheet total (fsa:LiabilitiesAndEquity) minus equity (fsa:Equity)."
|
|
1138
|
+
msg=_("Short-term liabilities (fsa:ShorttermLiabilitiesOtherThanProvisions) must be less than or equal to the balance sheet total (fsa:LiabilitiesAndEquity) minus equity (fsa:Equity). "
|
|
1139
1139
|
"LiabilitiesAndEquity: %(liabilities)s, Equity: %(equity)s, ShorttermLiabilitiesOtherThanProvisions: %(shortLiabilities)s"),
|
|
1140
1140
|
equity=equityFact.effectiveValue,
|
|
1141
1141
|
liabilities=liabilityFact.effectiveValue,
|
|
@@ -41,7 +41,7 @@ def rule_th01(
|
|
|
41
41
|
else:
|
|
42
42
|
yield Validation.error(
|
|
43
43
|
codes="DBA.TH01",
|
|
44
|
-
msg=_("The 'link:schemaRef' must contain '{}'."
|
|
44
|
+
msg=_("The 'link:schemaRef' must contain '{}'. "
|
|
45
45
|
"The 'link:schemaRef' as reported is {}.").format(pluginData.schemaRefUri, href),
|
|
46
46
|
modelObject=doc,
|
|
47
47
|
)
|
|
@@ -26,6 +26,7 @@ from arelle.ModelValue import QName
|
|
|
26
26
|
from arelle.ModelValue import qname
|
|
27
27
|
from arelle.ModelXbrl import ModelXbrl
|
|
28
28
|
|
|
29
|
+
from arelle.utils.validate.ESEFImage import ImageValidationParameters, checkSVGContentElt, validateImageAndLog
|
|
29
30
|
from arelle.utils.validate.ValidationUtil import etreeIterWithDepth
|
|
30
31
|
from arelle.PythonUtil import isLegacyAbs, normalizeSpace
|
|
31
32
|
from arelle.PythonUtil import strTruncate
|
|
@@ -50,7 +51,6 @@ from arelle.XbrlConst import (
|
|
|
50
51
|
from arelle.XmlValidateConst import VALID
|
|
51
52
|
from arelle.typing import TypeGetText
|
|
52
53
|
from .DTS import checkFilingDTS
|
|
53
|
-
from .Image import checkSVGContentElt, validateImage
|
|
54
54
|
from ..Const import (
|
|
55
55
|
DefaultDimensionLinkroles,
|
|
56
56
|
FOOTNOTE_LINK_CHILDREN,
|
|
@@ -60,10 +60,10 @@ from ..Const import (
|
|
|
60
60
|
docTypeXhtmlPattern,
|
|
61
61
|
esefMandatoryElementNames2020,
|
|
62
62
|
esefPrimaryStatementPlaceholderNames,
|
|
63
|
-
esefStatementsOfMonetaryDeclarationNames,
|
|
64
63
|
mandatory,
|
|
65
64
|
styleCssHiddenPattern,
|
|
66
65
|
styleIxHiddenPattern,
|
|
66
|
+
supportedImgTypes,
|
|
67
67
|
untransformableTypes,
|
|
68
68
|
)
|
|
69
69
|
from ..Dimensions import checkFilingDimensions
|
|
@@ -133,7 +133,6 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
|
|
|
133
133
|
esefNotesConcepts = getEsefNotesStatementConcepts(val.modelXbrl)
|
|
134
134
|
|
|
135
135
|
esefPrimaryStatementPlaceholders = set(qname(_ifrsNs, n) for n in esefPrimaryStatementPlaceholderNames)
|
|
136
|
-
esefStatementsOfMonetaryDeclaration = set(qname(_ifrsNs, n) for n in esefStatementsOfMonetaryDeclarationNames)
|
|
137
136
|
esefMandatoryElements2020 = set(qname(_ifrsNs, n) for n in esefMandatoryElementNames2020)
|
|
138
137
|
|
|
139
138
|
if modelDocument.type == ModelDocument.Type.INSTANCE and not val.unconsolidated:
|
|
@@ -302,9 +301,16 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
|
|
|
302
301
|
presentedHiddenEltIds = defaultdict(list)
|
|
303
302
|
eligibleForTransformHiddenFacts = []
|
|
304
303
|
requiredToDisplayFacts = []
|
|
305
|
-
requiredToDisplayFactIds: dict[Any, Any] = {}
|
|
306
304
|
firstIxdsDoc = True
|
|
307
305
|
contentOtherThanXHTMLGuidance = 'ESEF.2.5.1' if val.consolidated else 'ESEF.4.1.3' # Different reference for iXBRL and stand-alone XHTML
|
|
306
|
+
imageValidationParameters = ImageValidationParameters(
|
|
307
|
+
checkMinExternalResourceSize=True,
|
|
308
|
+
consolidated = val.consolidated,
|
|
309
|
+
contentOtherThanXHTMLGuidance=contentOtherThanXHTMLGuidance,
|
|
310
|
+
missingMimeTypeIsIncorrect=True,
|
|
311
|
+
recommendBase64EncodingEmbeddedImages=True,
|
|
312
|
+
supportedImgTypes=supportedImgTypes,
|
|
313
|
+
)
|
|
308
314
|
# ModelDocument.load has None as a return type. For typing reasons, we need to guard against that here.
|
|
309
315
|
assert modelXbrl.modelDocument is not None
|
|
310
316
|
for ixdsHtmlRootElt in (modelXbrl.ixdsHtmlElements if val.consolidated else # ix root elements for all ix docs in IXDS
|
|
@@ -339,12 +345,11 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
|
|
|
339
345
|
_("Inline XBRL documents SHOULD NOT contain any 'mailto' URI: %(element)s"),
|
|
340
346
|
modelObject=elt, element=eltTag)
|
|
341
347
|
elif eltTag == "{http://www.w3.org/2000/svg}svg":
|
|
342
|
-
checkSVGContentElt(elt, elt.modelDocument.baseForElement(elt), modelXbrl, [elt],
|
|
343
|
-
contentOtherThanXHTMLGuidance, val)
|
|
348
|
+
checkSVGContentElt(elt, elt.modelDocument.baseForElement(elt), modelXbrl, [elt], imageValidationParameters, val)
|
|
344
349
|
elif eltTag == "img":
|
|
345
350
|
src = elt.get("src","").strip()
|
|
346
351
|
evaluatedMsg = _('On line {line}, "alt" attribute value: "{alt}"').format(line=elt.sourceline, alt=elt.get("alt"))
|
|
347
|
-
|
|
352
|
+
validateImageAndLog(elt.modelDocument.baseForElement(elt), src, modelXbrl, val, elt, evaluatedMsg, imageValidationParameters)
|
|
348
353
|
# links to external documents are allowed as of 2021 per G.2.5.1
|
|
349
354
|
# Since ESEF is a format requirement and is not expected to impact the 'human readable layer' of a report,
|
|
350
355
|
# this guidance should not be seen as limiting the inclusion of links to external websites, to other documents
|
|
@@ -371,7 +376,7 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
|
|
|
371
376
|
|
|
372
377
|
with elt.modelXbrl.fileSource.file(normalizedUri, binary=True)[0] as fh:
|
|
373
378
|
cssContents = fh.read()
|
|
374
|
-
validateCssUrl(cssContents.decode(), normalizedUri, modelXbrl, val, elt,
|
|
379
|
+
validateCssUrl(cssContents.decode(), normalizedUri, modelXbrl, val, elt, imageValidationParameters)
|
|
375
380
|
cssContents = None
|
|
376
381
|
if val.unconsolidated:
|
|
377
382
|
modelXbrl.warning("ESEF.4.1.4.externalCssFileForXhtmlDocument",
|
|
@@ -388,7 +393,7 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
|
|
|
388
393
|
_("Where an Inline XBRL document set contains a single document, the CSS SHOULD be embedded within the document."),
|
|
389
394
|
modelObject=elt, element=eltTag)
|
|
390
395
|
elif eltTag == "style" and elt.get("type") == "text/css":
|
|
391
|
-
validateCssUrl(elt.stringValue, elt.modelDocument.baseForElement(elt), modelXbrl, val, elt,
|
|
396
|
+
validateCssUrl(elt.stringValue, elt.modelDocument.baseForElement(elt), modelXbrl, val, elt, imageValidationParameters)
|
|
392
397
|
if not val.unconsolidated:
|
|
393
398
|
if len(modelXbrl.ixdsHtmlElements) > 1:
|
|
394
399
|
modelXbrl.warning("ESEF.2.5.4.embeddedCssForMultiHtmlIXbrlDocumentSets",
|
|
@@ -495,7 +500,7 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
|
|
|
495
500
|
for declaration in tinycss2.parse_blocks_contents(styleValue):
|
|
496
501
|
if isinstance(declaration, tinycss2.ast.Declaration):
|
|
497
502
|
validateCssUrlContent(declaration.value, ixElt.modelDocument.baseForElement(ixElt),
|
|
498
|
-
modelXbrl, val, ixElt,
|
|
503
|
+
modelXbrl, val, ixElt, imageValidationParameters)
|
|
499
504
|
elif isinstance(declaration, tinycss2.ast.ParseError):
|
|
500
505
|
modelXbrl.warning("ix.CssParsingError",
|
|
501
506
|
_("The style attribute contains erroneous CSS declaration \"%(styleContent)s\": %(parseError)s"),
|
|
@@ -538,7 +543,6 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
|
|
|
538
543
|
contextsWithPeriodTimeZone: list[ModelContext] = []
|
|
539
544
|
contextsWithWrongInstantDate: list[ModelContext] = []
|
|
540
545
|
contextIdentifiers = defaultdict(list)
|
|
541
|
-
nonStandardTypedDimensions: dict[Any, Any] = defaultdict(set)
|
|
542
546
|
for context in modelXbrl.contexts.values():
|
|
543
547
|
for uncast_elt in context.iterdescendants("{http://www.xbrl.org/2003/instance}startDate",
|
|
544
548
|
"{http://www.xbrl.org/2003/instance}endDate",
|
|
@@ -650,14 +654,11 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
|
|
|
650
654
|
textFactsByConceptContext = defaultdict(list)
|
|
651
655
|
footnotesRelationshipSet = modelXbrl.relationshipSet(XbrlConst.factFootnote, XbrlConst.defaultLinkRole)
|
|
652
656
|
noLangFacts = []
|
|
653
|
-
textFactsMissingReportLang: list[Any] = []
|
|
654
657
|
conceptsUsed = set()
|
|
655
658
|
langsUsedByTextFacts = set()
|
|
656
659
|
|
|
657
|
-
hasNoFacts = True
|
|
658
660
|
factsMissingId = []
|
|
659
661
|
for qn, facts in modelXbrl.factsByQname.items():
|
|
660
|
-
hasNoFacts = False
|
|
661
662
|
if qn in mandatory:
|
|
662
663
|
reportedMandatory.add(qn)
|
|
663
664
|
for f in facts:
|
|
@@ -1099,7 +1100,7 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
|
|
|
1099
1100
|
modelXbrl.modelManager.showStatus(None)
|
|
1100
1101
|
|
|
1101
1102
|
|
|
1102
|
-
def validateCssUrl(cssContent:str, normalizedUri:str, modelXbrl: ModelXbrl, val: ValidateXbrl, elt: ModelObject,
|
|
1103
|
+
def validateCssUrl(cssContent:str, normalizedUri:str, modelXbrl: ModelXbrl, val: ValidateXbrl, elt: ModelObject, params: ImageValidationParameters) -> None:
|
|
1103
1104
|
css_elements = tinycss2.parse_stylesheet(cssContent)
|
|
1104
1105
|
for css_element in css_elements:
|
|
1105
1106
|
if isinstance(css_element, tinycss2.ast.AtRule):
|
|
@@ -1107,22 +1108,22 @@ def validateCssUrl(cssContent:str, normalizedUri:str, modelXbrl: ModelXbrl, val:
|
|
|
1107
1108
|
for css_rule in css_element.content:
|
|
1108
1109
|
if isinstance(css_rule, tinycss2.ast.URLToken) and "data:font" not in css_rule.value:
|
|
1109
1110
|
modelXbrl.warning(
|
|
1110
|
-
"ESEF.%s.fontIncludedAndNotEmbeddedAsBase64EncodedString" % contentOtherThanXHTMLGuidance,
|
|
1111
|
+
"ESEF.%s.fontIncludedAndNotEmbeddedAsBase64EncodedString" % params.contentOtherThanXHTMLGuidance,
|
|
1111
1112
|
_("Fonts SHOULD be included in the XHTML document as a base64 encoded string: %(file)s."),
|
|
1112
1113
|
modelObject=elt, file=css_rule.value)
|
|
1113
1114
|
if isinstance(css_element, tinycss2.ast.QualifiedRule):
|
|
1114
|
-
validateCssUrlContent(css_element.content, normalizedUri, modelXbrl, val, elt,
|
|
1115
|
+
validateCssUrlContent(css_element.content, normalizedUri, modelXbrl, val, elt, params)
|
|
1115
1116
|
|
|
1116
1117
|
|
|
1117
|
-
def validateCssUrlContent(cssRules: list[Any], normalizedUri:str, modelXbrl: ModelXbrl, val: ValidateXbrl, elt: ModelObject,
|
|
1118
|
+
def validateCssUrlContent(cssRules: list[Any], normalizedUri:str, modelXbrl: ModelXbrl, val: ValidateXbrl, elt: ModelObject, params: ImageValidationParameters) -> None:
|
|
1118
1119
|
for css_rule in cssRules:
|
|
1119
1120
|
if isinstance(css_rule, tinycss2.ast.FunctionBlock):
|
|
1120
1121
|
if css_rule.lower_name == "url":
|
|
1121
1122
|
if len(css_rule.arguments):
|
|
1122
1123
|
css_rule_url = css_rule.arguments[0].value # url or base64
|
|
1123
1124
|
evaluatedMsg = _('On line {line}').format(line=1) #css_element.source_line)
|
|
1124
|
-
|
|
1125
|
+
validateImageAndLog(normalizedUri, css_rule_url, modelXbrl, val, elt, evaluatedMsg, params)
|
|
1125
1126
|
elif isinstance(css_rule, tinycss2.ast.URLToken):
|
|
1126
1127
|
value = css_rule.value
|
|
1127
1128
|
evaluatedMsg = _('On line {line}').format(line=1) #css_element.source_line)
|
|
1128
|
-
|
|
1129
|
+
validateImageAndLog(normalizedUri, value, modelXbrl, val, elt, evaluatedMsg, params)
|
|
@@ -7,7 +7,7 @@ from collections import defaultdict
|
|
|
7
7
|
from dataclasses import dataclass
|
|
8
8
|
from functools import lru_cache
|
|
9
9
|
from pathlib import Path
|
|
10
|
-
from typing import Any,
|
|
10
|
+
from typing import Any, cast, Iterable
|
|
11
11
|
|
|
12
12
|
import regex as re
|
|
13
13
|
from lxml.etree import _Comment, _ElementTree, _Entity, _ProcessingInstruction, _Element
|
|
@@ -15,8 +15,9 @@ from lxml.etree import _Comment, _ElementTree, _Entity, _ProcessingInstruction,
|
|
|
15
15
|
from arelle import XbrlConst
|
|
16
16
|
from arelle.FunctionIxt import ixtNamespaces
|
|
17
17
|
from arelle.ModelDocument import ModelDocument, Type as ModelDocumentType
|
|
18
|
-
from arelle.ModelDtsObject import ModelConcept
|
|
18
|
+
from arelle.ModelDtsObject import ModelConcept, ModelRelationship
|
|
19
19
|
from arelle.ModelInstanceObject import ModelContext, ModelFact, ModelInlineFootnote, ModelUnit, ModelInlineFact
|
|
20
|
+
from arelle.ModelRelationshipSet import ModelRelationshipSet
|
|
20
21
|
from arelle.ModelObject import ModelObject
|
|
21
22
|
from arelle.ModelValue import QName, qname
|
|
22
23
|
from arelle.ModelXbrl import ModelXbrl
|
|
@@ -68,6 +69,10 @@ EFFECTIVE_KVK_GAAP_OTHER_ENTRYPOINT_FILES = frozenset((
|
|
|
68
69
|
'https://www.nltaxonomie.nl/kvk/2024-12-31/kvk-annual-report-other-gaap.xsd',
|
|
69
70
|
))
|
|
70
71
|
|
|
72
|
+
NON_DIMENSIONALIZED_LINE_ITEM_LINKROLES = frozenset((
|
|
73
|
+
'https://www.nltaxonomie.nl/kvk/role/lineitems-nondimensional-usage',
|
|
74
|
+
))
|
|
75
|
+
|
|
71
76
|
TAXONOMY_URLS_BY_YEAR = {
|
|
72
77
|
'2024': {
|
|
73
78
|
'https://www.nltaxonomie.nl/kvk/2024-12-31/kvk-annual-report-nlgaap-ext.xsd',
|
|
@@ -103,6 +108,21 @@ QN_DOMAIN_ITEM_TYPES = frozenset((
|
|
|
103
108
|
qname("{http://www.xbrl.org/dtr/type/2022-03-31}nonnum:domainItemType"),
|
|
104
109
|
))
|
|
105
110
|
|
|
111
|
+
SUPPORTED_IMAGE_TYPES_BY_IS_FILE = {
|
|
112
|
+
True: ('gif', 'jpg', 'jpeg', 'png'),
|
|
113
|
+
False: ('gif', 'jpeg', 'png'),
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@dataclass(frozen=True)
|
|
118
|
+
class AnchorData:
|
|
119
|
+
anchorsInDimensionalElrs: dict[str, frozenset[ModelRelationship]]
|
|
120
|
+
anchorsNotInBase: frozenset[ModelRelationship]
|
|
121
|
+
anchorsWithDimensionItem: frozenset[ModelRelationship]
|
|
122
|
+
anchorsWithDomainItem: frozenset[ModelRelationship]
|
|
123
|
+
extLineItemsNotAnchored: frozenset[ModelConcept]
|
|
124
|
+
extLineItemsWronglyAnchored: frozenset[ModelConcept]
|
|
125
|
+
|
|
106
126
|
|
|
107
127
|
@dataclass(frozen=True)
|
|
108
128
|
class ContextData:
|
|
@@ -112,6 +132,13 @@ class ContextData:
|
|
|
112
132
|
contextsWithSegments: list[ModelContext | None]
|
|
113
133
|
|
|
114
134
|
|
|
135
|
+
@dataclass(frozen=True)
|
|
136
|
+
class DimensionalData:
|
|
137
|
+
domainMembers: frozenset[ModelConcept]
|
|
138
|
+
elrPrimaryItems: dict[str, set[ModelConcept]]
|
|
139
|
+
primaryItems: frozenset[ModelConcept]
|
|
140
|
+
|
|
141
|
+
|
|
115
142
|
@dataclass(frozen=True)
|
|
116
143
|
class ExtensionData:
|
|
117
144
|
extensionConcepts: list[ModelConcept]
|
|
@@ -249,6 +276,23 @@ class PluginValidationDataExtension(PluginData):
|
|
|
249
276
|
contextsWithSegments=contextsWithSegments,
|
|
250
277
|
)
|
|
251
278
|
|
|
279
|
+
def checkLabels(self, issues: set[ModelConcept| None], modelXbrl: ModelXbrl, parent: ModelConcept, relSet: ModelRelationshipSet, labelrole: str | None, visited: set[ModelConcept]) -> set[ModelConcept| None]:
|
|
280
|
+
visited.add(parent)
|
|
281
|
+
conceptRels = defaultdict(list) # counts for concepts without preferred label role
|
|
282
|
+
for rel in relSet.fromModelObject(parent):
|
|
283
|
+
child = rel.toModelObject
|
|
284
|
+
if child is not None:
|
|
285
|
+
labelrole = rel.preferredLabel
|
|
286
|
+
if not labelrole:
|
|
287
|
+
conceptRels[child].append(rel)
|
|
288
|
+
if child not in visited:
|
|
289
|
+
self.checkLabels(issues, modelXbrl, child, relSet, labelrole, visited)
|
|
290
|
+
for concept, rels in conceptRels.items():
|
|
291
|
+
if len(rels) > 1:
|
|
292
|
+
issues.add(concept)
|
|
293
|
+
visited.remove(parent)
|
|
294
|
+
return issues
|
|
295
|
+
|
|
252
296
|
@lru_cache(1)
|
|
253
297
|
def checkHiddenElements(self, modelXbrl: ModelXbrl) -> HiddenElementsData:
|
|
254
298
|
cssHiddenFacts = set()
|
|
@@ -366,6 +410,70 @@ class PluginValidationDataExtension(PluginData):
|
|
|
366
410
|
factLangs.add(fact.xmlLang)
|
|
367
411
|
return factLangs
|
|
368
412
|
|
|
413
|
+
@lru_cache(1)
|
|
414
|
+
def getAnchorData(self, modelXbrl: ModelXbrl) -> AnchorData:
|
|
415
|
+
extLineItemsNotAnchored = set()
|
|
416
|
+
extLineItemsWronglyAnchored = set()
|
|
417
|
+
widerNarrowerRelSet = modelXbrl.relationshipSet(XbrlConst.widerNarrower)
|
|
418
|
+
generalSpecialRelSet = modelXbrl.relationshipSet(XbrlConst.generalSpecial)
|
|
419
|
+
calcRelSet = modelXbrl.relationshipSet(XbrlConst.summationItems)
|
|
420
|
+
dimensionalData = self.getDimensionalData(modelXbrl)
|
|
421
|
+
primaryItems = dimensionalData.primaryItems
|
|
422
|
+
domainMembers = dimensionalData.domainMembers
|
|
423
|
+
extensionData = self.getExtensionData(modelXbrl)
|
|
424
|
+
for concept in extensionData.extensionConcepts:
|
|
425
|
+
extLineItem = False
|
|
426
|
+
if concept.isPrimaryItem and \
|
|
427
|
+
not concept.isAbstract and \
|
|
428
|
+
concept in primaryItems and \
|
|
429
|
+
not widerNarrowerRelSet.contains(concept) and \
|
|
430
|
+
not calcRelSet.fromModelObject(concept):
|
|
431
|
+
extLineItem = True
|
|
432
|
+
elif concept.isAbstract and \
|
|
433
|
+
concept not in domainMembers and \
|
|
434
|
+
concept.type is not None and \
|
|
435
|
+
not concept.type.isDomainItemType and \
|
|
436
|
+
not concept.isHypercubeItem and \
|
|
437
|
+
not concept.isDimensionItem and \
|
|
438
|
+
not widerNarrowerRelSet.contains(concept):
|
|
439
|
+
extLineItem = True
|
|
440
|
+
if extLineItem:
|
|
441
|
+
if not generalSpecialRelSet.contains(concept):
|
|
442
|
+
extLineItemsNotAnchored.add(concept)
|
|
443
|
+
else:
|
|
444
|
+
extLineItemsWronglyAnchored.add(concept)
|
|
445
|
+
elrsContainingDimensionalRelationships = set(
|
|
446
|
+
ELR
|
|
447
|
+
for arcrole, ELR, linkqname, arcqname in modelXbrl.baseSets.keys()
|
|
448
|
+
if arcrole == "XBRL-dimensions" and ELR is not None)
|
|
449
|
+
anchorsNotInBase = set()
|
|
450
|
+
anchorsWithDomainItem = set()
|
|
451
|
+
anchorsWithDimensionItem = set()
|
|
452
|
+
anchorsInDimensionalElrs = defaultdict(set)
|
|
453
|
+
for anchoringRel in widerNarrowerRelSet.modelRelationships:
|
|
454
|
+
elr = anchoringRel.linkrole
|
|
455
|
+
fromObj = anchoringRel.fromModelObject
|
|
456
|
+
toObj = anchoringRel.toModelObject
|
|
457
|
+
if fromObj is not None and toObj is not None and fromObj.type is not None and toObj.type is not None:
|
|
458
|
+
if not ((not self.isExtensionUri(fromObj.modelDocument.uri, modelXbrl)) ^ (not self.isExtensionUri(toObj.modelDocument.uri, modelXbrl))):
|
|
459
|
+
anchorsNotInBase.add(anchoringRel)
|
|
460
|
+
if fromObj.type.isDomainItemType or toObj.type.isDomainItemType:
|
|
461
|
+
anchorsWithDomainItem.add(anchoringRel)
|
|
462
|
+
elif fromObj.isDimensionItem or toObj.isDimensionItem:
|
|
463
|
+
anchorsWithDimensionItem.add(anchoringRel)
|
|
464
|
+
else:
|
|
465
|
+
if elr in elrsContainingDimensionalRelationships:
|
|
466
|
+
anchorsInDimensionalElrs[elr].add(anchoringRel)
|
|
467
|
+
return AnchorData(
|
|
468
|
+
anchorsInDimensionalElrs={x: frozenset(y) for x, y in anchorsInDimensionalElrs.items()},
|
|
469
|
+
anchorsNotInBase=frozenset(anchorsNotInBase),
|
|
470
|
+
anchorsWithDimensionItem=frozenset(anchorsWithDimensionItem),
|
|
471
|
+
anchorsWithDomainItem=frozenset(anchorsWithDomainItem),
|
|
472
|
+
extLineItemsNotAnchored=frozenset(extLineItemsNotAnchored),
|
|
473
|
+
extLineItemsWronglyAnchored=frozenset(extLineItemsWronglyAnchored),
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
|
|
369
477
|
def getBaseElements(self, modelXbrl: ModelXbrl) -> set[Any | None]:
|
|
370
478
|
return self.checkInlineHTMLElements(modelXbrl).baseElements
|
|
371
479
|
|
|
@@ -399,10 +507,13 @@ class PluginValidationDataExtension(PluginData):
|
|
|
399
507
|
_getDocumentsInDts(modelXbrl.modelDocument)
|
|
400
508
|
return modelDocuments
|
|
401
509
|
|
|
402
|
-
|
|
510
|
+
@lru_cache(1)
|
|
511
|
+
def getDimensionalData(self, modelXbrl: ModelXbrl) -> DimensionalData:
|
|
403
512
|
domainMembers = set() # concepts which are dimension domain members
|
|
513
|
+
elrPrimaryItems = defaultdict(set)
|
|
404
514
|
hcPrimaryItems: set[ModelConcept] = set()
|
|
405
515
|
hcMembers: set[Any] = set()
|
|
516
|
+
primaryItems: set[ModelConcept] = set()
|
|
406
517
|
for hasHypercubeArcrole in (XbrlConst.all, XbrlConst.notAll):
|
|
407
518
|
hasHypercubeRelationships = modelXbrl.relationshipSet(hasHypercubeArcrole).fromModelObjects()
|
|
408
519
|
for hasHcRels in hasHypercubeRelationships.values():
|
|
@@ -413,6 +524,7 @@ class PluginValidationDataExtension(PluginData):
|
|
|
413
524
|
for domMbrRel in modelXbrl.relationshipSet(XbrlConst.domainMember).fromModelObject(sourceConcept):
|
|
414
525
|
if domMbrRel.consecutiveLinkrole == hasHcRel.linkrole: # only those related to this hc
|
|
415
526
|
self.addDomMbrs(modelXbrl, domMbrRel.toModelObject, domMbrRel.consecutiveLinkrole, hcPrimaryItems)
|
|
527
|
+
primaryItems.update(hcPrimaryItems)
|
|
416
528
|
hc = hasHcRel.toModelObject
|
|
417
529
|
for hcDimRel in modelXbrl.relationshipSet(XbrlConst.hypercubeDimension, hasHcRel.consecutiveLinkrole).fromModelObject(hc):
|
|
418
530
|
dim = hcDimRel.toModelObject
|
|
@@ -422,7 +534,18 @@ class PluginValidationDataExtension(PluginData):
|
|
|
422
534
|
if isinstance(dom, ModelConcept):
|
|
423
535
|
self.addDomMbrs(modelXbrl, dom, dimDomRel.consecutiveLinkrole, hcMembers)
|
|
424
536
|
domainMembers.update(hcMembers)
|
|
425
|
-
|
|
537
|
+
if hasHcRel.linkrole in NON_DIMENSIONALIZED_LINE_ITEM_LINKROLES or hcMembers:
|
|
538
|
+
for hcPrimaryItem in hcPrimaryItems:
|
|
539
|
+
if not hcPrimaryItem.isAbstract:
|
|
540
|
+
elrPrimaryItems[hasHcRel.linkrole].add(hcPrimaryItem)
|
|
541
|
+
elrPrimaryItems["*"].add(hcPrimaryItem) # members of any ELR
|
|
542
|
+
hcPrimaryItems.clear()
|
|
543
|
+
hcMembers.clear()
|
|
544
|
+
return DimensionalData(
|
|
545
|
+
domainMembers=frozenset(domainMembers),
|
|
546
|
+
elrPrimaryItems=elrPrimaryItems,
|
|
547
|
+
primaryItems=frozenset(primaryItems),
|
|
548
|
+
)
|
|
426
549
|
|
|
427
550
|
def getEligibleForTransformHiddenFacts(self, modelXbrl: ModelXbrl) -> set[ModelInlineFact]:
|
|
428
551
|
return self.checkHiddenElements(modelXbrl).eligibleForTransformHiddenFacts
|
|
@@ -475,6 +598,7 @@ class PluginValidationDataExtension(PluginData):
|
|
|
475
598
|
def getIxdsDocBasenames(self, modelXbrl: ModelXbrl) -> set[str]:
|
|
476
599
|
return set(Path(url).name for url in getattr(modelXbrl, "ixdsDocUrls", []))
|
|
477
600
|
|
|
601
|
+
@lru_cache(1)
|
|
478
602
|
def getExtensionConcepts(self, modelXbrl: ModelXbrl) -> list[ModelConcept]:
|
|
479
603
|
"""
|
|
480
604
|
Returns a list of extension concepts in the DTS.
|
|
@@ -7,30 +7,36 @@
|
|
|
7
7
|
names="NL-INLINE-2024|nl-inline-2024|kvk-inline-2024-preview"
|
|
8
8
|
description="Checks for NL INLINE 2024. For use with NL GAAP and NL IFRS"
|
|
9
9
|
validationType="NL"
|
|
10
|
+
exclusiveTypesPattern="EFM|GFM|FERC|HMRC|SBR.NL|EBA|EIOPA|ESEF"
|
|
10
11
|
/>
|
|
11
12
|
<DisclosureSystem
|
|
12
13
|
names="NL-INLINE-2024-GAAP-OTHER|nl-inline-2024-gaap-other|kvk-inline-2024-gaap-other-preview"
|
|
13
14
|
description="Checks for NL INLINE 2024. For use with other GAAP."
|
|
14
15
|
validationType="NL"
|
|
16
|
+
exclusiveTypesPattern="EFM|GFM|FERC|HMRC|SBR.NL|EBA|EIOPA|ESEF"
|
|
15
17
|
/>
|
|
16
18
|
<DisclosureSystem
|
|
17
19
|
names="NT19|nt19|NT19-preview|nt19-preview"
|
|
18
20
|
description="Checks for NT19"
|
|
19
21
|
validationType="NL"
|
|
22
|
+
exclusiveTypesPattern="EFM|GFM|FERC|HMRC|SBR.NL|EBA|EIOPA|ESEF"
|
|
20
23
|
/>
|
|
21
24
|
<DisclosureSystem
|
|
22
25
|
names="NT18|nt18|NT18-preview|nt18-preview"
|
|
23
26
|
description="Checks for NT18"
|
|
24
27
|
validationType="NL"
|
|
28
|
+
exclusiveTypesPattern="EFM|GFM|FERC|HMRC|SBR.NL|EBA|EIOPA|ESEF"
|
|
25
29
|
/>
|
|
26
30
|
<DisclosureSystem
|
|
27
31
|
names="NT17|nt17|NT17-preview|nt17-preview"
|
|
28
32
|
description="Checks for NT17"
|
|
29
33
|
validationType="NL"
|
|
34
|
+
exclusiveTypesPattern="EFM|GFM|FERC|HMRC|SBR.NL|EBA|EIOPA|ESEF"
|
|
30
35
|
/>
|
|
31
36
|
<DisclosureSystem
|
|
32
37
|
names="NT16|nt16|NT16-preview|nt16-preview"
|
|
33
38
|
description="Checks for NT16"
|
|
34
39
|
validationType="NL"
|
|
40
|
+
exclusiveTypesPattern="EFM|GFM|FERC|HMRC|SBR.NL|EBA|EIOPA|ESEF"
|
|
35
41
|
/>
|
|
36
42
|
</DisclosureSystems>
|
|
@@ -120,7 +120,7 @@ def rule_fr_kvk_2_02(
|
|
|
120
120
|
if fact.xmlLang and fact.xmlLang != lang:
|
|
121
121
|
yield Validation.error(
|
|
122
122
|
codes='NL.FR-KVK-2.02',
|
|
123
|
-
msg=_('The attribute \'xml:lang\' can be reported on different elements within an XBRL instance document.'
|
|
123
|
+
msg=_('The attribute \'xml:lang\' can be reported on different elements within an XBRL instance document. '
|
|
124
124
|
'The attribute \'xml:lang\' must always contain the same value within an XBRL instance document. '
|
|
125
125
|
'It is not allowed to report different values here. Document language: %(documentLang)s Element language: %(additionalLang)s'),
|
|
126
126
|
modelObject=fact,
|