arelle-release 2.37.22__py3-none-any.whl → 2.37.25__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/Updater.py +7 -3
- arelle/ValidateDuplicateFacts.py +13 -7
- arelle/XbrlConst.py +22 -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/LinkbaseType.py +17 -0
- arelle/plugin/validate/NL/PluginValidationDataExtension.py +155 -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 +656 -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.25.dist-info}/METADATA +1 -1
- {arelle_release-2.37.22.dist-info → arelle_release-2.37.25.dist-info}/RECORD +27 -27
- tests/integration_tests/validation/conformance_suite_configurations/efm_current.py +2 -2
- tests/integration_tests/validation/conformance_suite_configurations/nl_inline_2024.py +52 -28
- tests/integration_tests/validation/conformance_suite_configurations/nl_inline_2024_gaap_other.py +10 -0
- tests/resources/conformance_suites_timing/efm_current.json +8499 -8583
- tests/unit_tests/arelle/plugin/validate/ESEF/ESEF_Current/test_validate_css_url.py +10 -2
- tests/unit_tests/arelle/test_updater.py +43 -14
- arelle/plugin/validate/ESEF/ESEF_Current/Image.py +0 -213
- {arelle_release-2.37.22.dist-info → arelle_release-2.37.25.dist-info}/WHEEL +0 -0
- {arelle_release-2.37.22.dist-info → arelle_release-2.37.25.dist-info}/entry_points.txt +0 -0
- {arelle_release-2.37.22.dist-info → arelle_release-2.37.25.dist-info}/licenses/LICENSE.md +0 -0
- {arelle_release-2.37.22.dist-info → arelle_release-2.37.25.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/Updater.py
CHANGED
|
@@ -17,6 +17,7 @@ from urllib.error import URLError
|
|
|
17
17
|
import regex
|
|
18
18
|
|
|
19
19
|
from arelle import Version
|
|
20
|
+
from arelle.SystemInfo import getSystemInfo
|
|
20
21
|
from arelle.typing import TypeGetText
|
|
21
22
|
|
|
22
23
|
if TYPE_CHECKING:
|
|
@@ -90,7 +91,9 @@ def _getLatestArelleRelease(cntlr: CntlrWinMain) -> ArelleRelease:
|
|
|
90
91
|
|
|
91
92
|
def _getArelleReleaseDownloadUrl(assets: list[dict[str, Any]]) -> str | None:
|
|
92
93
|
if sys.platform == "darwin":
|
|
93
|
-
|
|
94
|
+
if getSystemInfo().get('arch') == 'arm64':
|
|
95
|
+
return _getArelleReleaseDownloadUrlByFileExtension(assets, ".dmg", nameIncludes='-arm64')
|
|
96
|
+
return _getArelleReleaseDownloadUrlByFileExtension(assets, ".dmg", nameIncludes='-x64')
|
|
94
97
|
elif sys.platform == "win32":
|
|
95
98
|
return _getArelleReleaseDownloadUrlByFileExtension(assets, ".exe")
|
|
96
99
|
else:
|
|
@@ -98,12 +101,13 @@ def _getArelleReleaseDownloadUrl(assets: list[dict[str, Any]]) -> str | None:
|
|
|
98
101
|
|
|
99
102
|
|
|
100
103
|
def _getArelleReleaseDownloadUrlByFileExtension(
|
|
101
|
-
assets: list[dict[str, Any]], fileExtension: str
|
|
104
|
+
assets: list[dict[str, Any]], fileExtension: str, nameIncludes: str | None = None,
|
|
102
105
|
) -> str | None:
|
|
103
106
|
for asset in assets:
|
|
104
107
|
downloadUrl = asset.get("browser_download_url")
|
|
105
108
|
if isinstance(downloadUrl, str) and downloadUrl.endswith(fileExtension):
|
|
106
|
-
|
|
109
|
+
if nameIncludes is None or nameIncludes in downloadUrl:
|
|
110
|
+
return downloadUrl
|
|
107
111
|
return None
|
|
108
112
|
|
|
109
113
|
|
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")
|
|
@@ -111,6 +112,9 @@ qnXbrldiTypedMember = qname("{http://xbrl.org/2006/xbrldi}xbrldi:typedMember")
|
|
|
111
112
|
xlink = "http://www.w3.org/1999/xlink"
|
|
112
113
|
qnXlinkArcRole = qname("{http://www.w3.org/1999/xlink}xlink:arcrole")
|
|
113
114
|
qnXlinkFrom = qname("{http://www.w3.org/1999/xlink}xlink:from")
|
|
115
|
+
qnXlinkHref = qname("{http://www.w3.org/1999/xlink}xlink:href")
|
|
116
|
+
qnXlinkLabel = qname("{http://www.w3.org/1999/xlink}xlink:label")
|
|
117
|
+
qnXlinkTo = qname("{http://www.w3.org/1999/xlink}xlink:to")
|
|
114
118
|
qnXlinkType = qname("{http://www.w3.org/1999/xlink}xlink:type")
|
|
115
119
|
xl = "http://www.xbrl.org/2003/XLink"
|
|
116
120
|
qnXlExtended = qname("{http://www.xbrl.org/2003/XLink}xl:extended")
|
|
@@ -831,6 +835,24 @@ def isNumericRole(role: str) -> bool:
|
|
|
831
835
|
}
|
|
832
836
|
|
|
833
837
|
|
|
838
|
+
standardDimensionArcroles = frozenset({
|
|
839
|
+
all,
|
|
840
|
+
notAll,
|
|
841
|
+
hypercubeDimension,
|
|
842
|
+
dimensionDomain,
|
|
843
|
+
domainMember,
|
|
844
|
+
dimensionDefault,
|
|
845
|
+
})
|
|
846
|
+
|
|
847
|
+
|
|
848
|
+
standardDefinitionArcroles = frozenset(standardDimensionArcroles | {
|
|
849
|
+
essenceAlias,
|
|
850
|
+
generalSpecial,
|
|
851
|
+
requiresElement,
|
|
852
|
+
similarTuples,
|
|
853
|
+
})
|
|
854
|
+
|
|
855
|
+
|
|
834
856
|
def isStandardArcrole(role: str) -> bool:
|
|
835
857
|
return role in {
|
|
836
858
|
"http://www.w3.org/1999/xlink/properties/linkbase",
|
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)
|
|
@@ -32,6 +32,12 @@ class LinkbaseType(Enum):
|
|
|
32
32
|
"""
|
|
33
33
|
return LINKBASE_ARC_QN[self]
|
|
34
34
|
|
|
35
|
+
def getArcroles(self) -> frozenset[str]:
|
|
36
|
+
"""
|
|
37
|
+
Returns the standard arcrole URIs associated with this LinkbaseType.
|
|
38
|
+
"""
|
|
39
|
+
return LINKBASE_ARCROLES[self]
|
|
40
|
+
|
|
35
41
|
def getLinkQn(self) -> QName:
|
|
36
42
|
"""
|
|
37
43
|
Returns the qname of the link associated with this LinkbaseType.
|
|
@@ -59,6 +65,17 @@ LINKBASE_ARC_QN = {
|
|
|
59
65
|
LinkbaseType.REFERENCE: XbrlConst.qnLinkReferenceArc,
|
|
60
66
|
}
|
|
61
67
|
|
|
68
|
+
LINKBASE_ARCROLES = {
|
|
69
|
+
LinkbaseType.CALCULATION: frozenset({
|
|
70
|
+
XbrlConst.summationItem,
|
|
71
|
+
XbrlConst.summationItem11,
|
|
72
|
+
}),
|
|
73
|
+
LinkbaseType.DEFINITION: XbrlConst.standardDefinitionArcroles,
|
|
74
|
+
LinkbaseType.LABEL: frozenset({XbrlConst.conceptLabel}),
|
|
75
|
+
LinkbaseType.PRESENTATION: frozenset({XbrlConst.parentChild}),
|
|
76
|
+
LinkbaseType.REFERENCE: frozenset({XbrlConst.conceptReference}),
|
|
77
|
+
}
|
|
78
|
+
|
|
62
79
|
LINKBASE_LINK_QN = {
|
|
63
80
|
LinkbaseType.CALCULATION: XbrlConst.qnLinkCalculationLink,
|
|
64
81
|
LinkbaseType.DEFINITION: XbrlConst.qnLinkDefinitionLink,
|