arelle-release 2.37.48__py3-none-any.whl → 2.37.50__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/ModelDocument.py +16 -14
- arelle/ModelInstanceObject.py +1 -1
- arelle/ModelXbrl.py +21 -10
- arelle/WebCache.py +26 -16
- arelle/_version.py +2 -2
- arelle/api/Session.py +5 -2
- arelle/plugin/validate/DBA/PluginValidationDataExtension.py +0 -1
- arelle/plugin/validate/EDINET/Constants.py +15 -0
- arelle/plugin/validate/EDINET/PluginValidationDataExtension.py +145 -11
- arelle/plugin/validate/EDINET/Statement.py +139 -0
- arelle/plugin/validate/EDINET/rules/contexts.py +9 -12
- arelle/plugin/validate/EDINET/rules/edinet.py +105 -45
- arelle/plugin/validate/EDINET/rules/gfm.py +51 -6
- arelle/plugin/validate/ESEF/ESEF_2021/ValidateXbrlFinally.py +2 -2
- arelle/plugin/validate/ESEF/ESEF_Current/ValidateXbrlFinally.py +2 -2
- arelle/plugin/validate/NL/PluginValidationDataExtension.py +1 -1
- arelle/plugin/validate/NL/rules/fr_nl.py +6 -7
- arelle/plugin/validate/UK/ValidateUK.py +31 -66
- {arelle_release-2.37.48.dist-info → arelle_release-2.37.50.dist-info}/METADATA +20 -16
- {arelle_release-2.37.48.dist-info → arelle_release-2.37.50.dist-info}/RECORD +24 -23
- {arelle_release-2.37.48.dist-info → arelle_release-2.37.50.dist-info}/WHEEL +0 -0
- {arelle_release-2.37.48.dist-info → arelle_release-2.37.50.dist-info}/entry_points.txt +0 -0
- {arelle_release-2.37.48.dist-info → arelle_release-2.37.50.dist-info}/licenses/LICENSE.md +0 -0
- {arelle_release-2.37.48.dist-info → arelle_release-2.37.50.dist-info}/top_level.txt +0 -0
|
@@ -4,10 +4,9 @@ See COPYRIGHT.md for copyright information.
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
6
|
from collections import defaultdict
|
|
7
|
+
from itertools import chain
|
|
7
8
|
from typing import Any, Iterable
|
|
8
9
|
|
|
9
|
-
import regex
|
|
10
|
-
|
|
11
10
|
from arelle import XbrlConst
|
|
12
11
|
from arelle.ModelDtsObject import ModelConcept
|
|
13
12
|
from arelle.ValidateXbrl import ValidateXbrl
|
|
@@ -163,17 +162,15 @@ def rule_EC8054W(
|
|
|
163
162
|
EDINET.EC8054W: For any context with ID containing "NonConsolidatedMember",
|
|
164
163
|
the scenario element within must be set to "NonConsolidatedMember".
|
|
165
164
|
"""
|
|
166
|
-
|
|
167
|
-
|
|
165
|
+
allContexts = chain(val.modelXbrl.contexts.values(), val.modelXbrl.ixdsUnmappedContexts.values())
|
|
166
|
+
for context in allContexts:
|
|
167
|
+
if context.id is None or pluginData.nonConsolidatedMemberQn.localName not in context.id:
|
|
168
168
|
continue
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
):
|
|
175
|
-
memberQnames.add(memberElt.xValue)
|
|
176
|
-
if pluginData.nonConsolidatedMemberQn not in memberQnames:
|
|
169
|
+
member = context.dimMemberQname(
|
|
170
|
+
pluginData.consolidatedOrNonConsolidatedAxisQn,
|
|
171
|
+
includeDefaults=True
|
|
172
|
+
)
|
|
173
|
+
if member != pluginData.nonConsolidatedMemberQn:
|
|
177
174
|
yield Validation.warning(
|
|
178
175
|
codes='EDINET.EC8054W',
|
|
179
176
|
msg=_("For the context ID (%(contextId)s), \"NonConsolidatedMember\" "
|
|
@@ -3,9 +3,7 @@ See COPYRIGHT.md for copyright information.
|
|
|
3
3
|
"""
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
|
-
from
|
|
7
|
-
from decimal import Decimal
|
|
8
|
-
from typing import Any, Iterable, cast
|
|
6
|
+
from typing import Any, Iterable
|
|
9
7
|
|
|
10
8
|
from arelle import XbrlConst, ValidateDuplicateFacts
|
|
11
9
|
from arelle.LinkbaseType import LinkbaseType
|
|
@@ -15,12 +13,71 @@ from arelle.typing import TypeGetText
|
|
|
15
13
|
from arelle.utils.PluginHooks import ValidationHook
|
|
16
14
|
from arelle.utils.validate.Decorator import validation
|
|
17
15
|
from arelle.utils.validate.Validation import Validation
|
|
16
|
+
from ..Constants import AccountingStandard
|
|
18
17
|
from ..DisclosureSystems import (DISCLOSURE_SYSTEM_EDINET)
|
|
19
18
|
from ..PluginValidationDataExtension import PluginValidationDataExtension
|
|
19
|
+
from ..Statement import StatementType
|
|
20
20
|
|
|
21
21
|
_: TypeGetText
|
|
22
22
|
|
|
23
23
|
|
|
24
|
+
@validation(
|
|
25
|
+
hook=ValidationHook.XBRL_FINALLY,
|
|
26
|
+
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
27
|
+
)
|
|
28
|
+
def rule_balances(
|
|
29
|
+
pluginData: PluginValidationDataExtension,
|
|
30
|
+
val: ValidateXbrl,
|
|
31
|
+
*args: Any,
|
|
32
|
+
**kwargs: Any,
|
|
33
|
+
) -> Iterable[Validation]:
|
|
34
|
+
"""
|
|
35
|
+
EDINET.EC8057W: On the consolidated balance sheet, the sum of all liabilities and
|
|
36
|
+
equity must equal the sum of all assets.
|
|
37
|
+
EDINET.EC8058W: On the nonconsolidated balance sheet, the sum of all liabilities and
|
|
38
|
+
equity must equal the sum of all assets.
|
|
39
|
+
EDINET.EC8062W: On the consolidated statement of financial position, the sum of all liabilities and
|
|
40
|
+
equity must equal the sum of all assets.
|
|
41
|
+
EDINET.EC8064W: On the nonconsolidated statement of financial position, the sum of all liabilities and
|
|
42
|
+
equity must equal the sum of all assets.
|
|
43
|
+
"""
|
|
44
|
+
for statementInstance in pluginData.getStatementInstances(val.modelXbrl):
|
|
45
|
+
statement = statementInstance.statement
|
|
46
|
+
for balanceSheet in statementInstance.balanceSheets:
|
|
47
|
+
if balanceSheet.assetsTotal == balanceSheet.liabilitiesAndEquityTotal:
|
|
48
|
+
continue
|
|
49
|
+
code = None
|
|
50
|
+
if statement.statementType == StatementType.BALANCE_SHEET:
|
|
51
|
+
if statement.isConsolidated:
|
|
52
|
+
code = "EDINET.EC8057W"
|
|
53
|
+
else:
|
|
54
|
+
code = "EDINET.EC8058W"
|
|
55
|
+
elif statement.statementType == StatementType.STATEMENT_OF_FINANCIAL_POSITION:
|
|
56
|
+
if statement.isConsolidated:
|
|
57
|
+
code = "EDINET.EC8062W"
|
|
58
|
+
else:
|
|
59
|
+
code = "EDINET.EC8064W"
|
|
60
|
+
assert code is not None, "Unknown balance sheet encountered."
|
|
61
|
+
yield Validation.warning(
|
|
62
|
+
codes=code,
|
|
63
|
+
msg=_("The %(consolidated)s %(balanceSheet)s is not balanced. "
|
|
64
|
+
"The sum of all liabilities and equity must equal the sum of all assets. "
|
|
65
|
+
"Please correct the debit (%(liabilitiesAndEquitySum)s) and credit (%(assetSum)s) "
|
|
66
|
+
"values so that they match "
|
|
67
|
+
"<roleUri=%(roleUri)s> <contextID=%(contextId)s> <unitID=%(unitId)s>."),
|
|
68
|
+
consolidated=_("consolidated") if statement.isConsolidated
|
|
69
|
+
else _("nonconsolidated"),
|
|
70
|
+
balanceSheet=_("balance sheet") if statement.statementType == StatementType.BALANCE_SHEET
|
|
71
|
+
else _("statement of financial position"),
|
|
72
|
+
liabilitiesAndEquitySum=f"{balanceSheet.liabilitiesAndEquityTotal:,}",
|
|
73
|
+
assetSum=f"{balanceSheet.assetsTotal:,}",
|
|
74
|
+
roleUri=statement.roleUri,
|
|
75
|
+
contextId=balanceSheet.contextId,
|
|
76
|
+
unitId=balanceSheet.unitId,
|
|
77
|
+
modelObject=balanceSheet.facts,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
24
81
|
@validation(
|
|
25
82
|
hook=ValidationHook.XBRL_FINALLY,
|
|
26
83
|
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
@@ -90,6 +147,35 @@ def rule_EC5002E(
|
|
|
90
147
|
)
|
|
91
148
|
|
|
92
149
|
|
|
150
|
+
@validation(
|
|
151
|
+
hook=ValidationHook.XBRL_FINALLY,
|
|
152
|
+
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
153
|
+
)
|
|
154
|
+
def rule_EC5613W(
|
|
155
|
+
pluginData: PluginValidationDataExtension,
|
|
156
|
+
val: ValidateXbrl,
|
|
157
|
+
*args: Any,
|
|
158
|
+
**kwargs: Any,
|
|
159
|
+
) -> Iterable[Validation]:
|
|
160
|
+
"""
|
|
161
|
+
EDINET.EC5613W: Please set the DEI "Accounting Standard" value to one
|
|
162
|
+
of the following: "Japan GAAP", "US GAAP", "IFRS".
|
|
163
|
+
"""
|
|
164
|
+
validAccountingStandards = {s.value for s in AccountingStandard}
|
|
165
|
+
errorFacts = [
|
|
166
|
+
fact for fact in pluginData.iterValidNonNilFacts(val.modelXbrl, pluginData.accountingStandardsDeiQn)
|
|
167
|
+
if fact.xValue not in validAccountingStandards
|
|
168
|
+
]
|
|
169
|
+
if len(errorFacts) > 0:
|
|
170
|
+
yield Validation.warning(
|
|
171
|
+
codes='EDINET.EC5613W',
|
|
172
|
+
msg=_("Please set the DEI \"Accounting Standard\" value to one "
|
|
173
|
+
"of the following: %(values)s."),
|
|
174
|
+
values=', '.join(f'"{s.value}"' for s in AccountingStandard),
|
|
175
|
+
modelObject=errorFacts,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
|
|
93
179
|
@validation(
|
|
94
180
|
hook=ValidationHook.XBRL_FINALLY,
|
|
95
181
|
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
@@ -179,67 +265,41 @@ def rule_EC8027W(
|
|
|
179
265
|
hook=ValidationHook.XBRL_FINALLY,
|
|
180
266
|
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
181
267
|
)
|
|
182
|
-
def
|
|
268
|
+
def rule_EC8075W(
|
|
183
269
|
pluginData: PluginValidationDataExtension,
|
|
184
270
|
val: ValidateXbrl,
|
|
185
271
|
*args: Any,
|
|
186
272
|
**kwargs: Any,
|
|
187
273
|
) -> Iterable[Validation]:
|
|
188
274
|
"""
|
|
189
|
-
EDINET.
|
|
275
|
+
EDINET.EC8075W: The percentage of female executives has not been tagged in detail. Ensure that there is
|
|
276
|
+
a nonnil value disclosed for jpcrp_cor:RatioOfFemaleDirectorsAndOtherOfficers.
|
|
190
277
|
"""
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
factsByContextId = defaultdict(list)
|
|
194
|
-
for fact in deduplicatedFacts:
|
|
195
|
-
if fact.qname not in (pluginData.assetsIfrsQn, pluginData.liabilitiesAndEquityIfrsQn):
|
|
196
|
-
continue
|
|
197
|
-
if fact.contextID is None or not pluginData.contextIdPattern.fullmatch(fact.contextID):
|
|
198
|
-
continue
|
|
199
|
-
factsByContextId[fact.contextID].append(fact)
|
|
200
|
-
|
|
201
|
-
for facts in factsByContextId.values():
|
|
202
|
-
assetSum = Decimal(0)
|
|
203
|
-
liabilitiesAndEquitySum = Decimal(0)
|
|
204
|
-
for fact in facts:
|
|
205
|
-
if isinstance(fact.xValue, float):
|
|
206
|
-
value = Decimal(fact.xValue)
|
|
207
|
-
else:
|
|
208
|
-
value = cast(Decimal, fact.xValue)
|
|
209
|
-
if fact.qname == pluginData.assetsIfrsQn:
|
|
210
|
-
assetSum += value
|
|
211
|
-
elif fact.qname == pluginData.liabilitiesAndEquityIfrsQn:
|
|
212
|
-
liabilitiesAndEquitySum += value
|
|
213
|
-
if assetSum != liabilitiesAndEquitySum:
|
|
278
|
+
if pluginData.isCorporateForm(val.modelXbrl):
|
|
279
|
+
if not pluginData.hasValidNonNilFact(val.modelXbrl, pluginData.ratioOfFemaleDirectorsAndOtherOfficersQn):
|
|
214
280
|
yield Validation.warning(
|
|
215
|
-
codes='EDINET.
|
|
216
|
-
msg=_("The
|
|
217
|
-
|
|
218
|
-
"Please correct the debit (%(liabilitiesAndEquitySum)s) and credit (%(assetSum)s) "
|
|
219
|
-
"values so that they match."),
|
|
220
|
-
liabilitiesAndEquitySum=f"{liabilitiesAndEquitySum:,}",
|
|
221
|
-
assetSum=f"{assetSum:,}",
|
|
222
|
-
modelObject=facts,
|
|
223
|
-
)
|
|
281
|
+
codes='EDINET.EC8075W',
|
|
282
|
+
msg=_("The percentage of female executives has not been tagged in detail."),
|
|
283
|
+
)
|
|
224
284
|
|
|
225
285
|
|
|
226
286
|
@validation(
|
|
227
287
|
hook=ValidationHook.XBRL_FINALLY,
|
|
228
288
|
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
229
289
|
)
|
|
230
|
-
def
|
|
290
|
+
def rule_EC8076W(
|
|
231
291
|
pluginData: PluginValidationDataExtension,
|
|
232
292
|
val: ValidateXbrl,
|
|
233
293
|
*args: Any,
|
|
234
294
|
**kwargs: Any,
|
|
235
295
|
) -> Iterable[Validation]:
|
|
236
296
|
"""
|
|
237
|
-
EDINET.
|
|
238
|
-
|
|
297
|
+
EDINET.EC8076W: "Issued Shares, Total Number of Shares, etc. [Text Block]" (IssuedSharesTotalNumberOfSharesEtcTextBlock) is not tagged.
|
|
298
|
+
Applies to forms 3 and 4.
|
|
239
299
|
"""
|
|
240
|
-
if pluginData.
|
|
241
|
-
if not pluginData.hasValidNonNilFact(val.modelXbrl, pluginData.
|
|
300
|
+
if pluginData.isStockForm(val.modelXbrl) and pluginData.isCorporateReport(val.modelXbrl):
|
|
301
|
+
if not pluginData.hasValidNonNilFact(val.modelXbrl, pluginData.issuedSharesTotalNumberOfSharesEtcQn):
|
|
242
302
|
yield Validation.warning(
|
|
243
|
-
codes='EDINET.
|
|
244
|
-
msg=_("
|
|
245
|
-
|
|
303
|
+
codes='EDINET.EC8076W',
|
|
304
|
+
msg=_('"Issued Shares, Total Number of Shares, etc. [Text Block]" (IssuedSharesTotalNumberOfSharesEtcTextBlock) is not tagged.'),
|
|
305
|
+
)
|
|
@@ -3,8 +3,10 @@ See COPYRIGHT.md for copyright information.
|
|
|
3
3
|
"""
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
|
+
import os
|
|
6
7
|
from collections import defaultdict
|
|
7
8
|
from datetime import timedelta
|
|
9
|
+
from lxml.etree import XML, DTD
|
|
8
10
|
from typing import Any, cast, Iterable
|
|
9
11
|
|
|
10
12
|
import regex
|
|
@@ -28,6 +30,7 @@ from arelle.utils.validate.Validation import Validation
|
|
|
28
30
|
from arelle.utils.validate.ValidationUtil import etreeIterWithDepth
|
|
29
31
|
from ..DisclosureSystems import (DISCLOSURE_SYSTEM_EDINET)
|
|
30
32
|
from ..PluginValidationDataExtension import PluginValidationDataExtension
|
|
33
|
+
from ..Constants import xhtmlDtdExtension
|
|
31
34
|
|
|
32
35
|
_: TypeGetText
|
|
33
36
|
|
|
@@ -254,13 +257,14 @@ def rule_gfm_1_2_8(
|
|
|
254
257
|
EDINET.EC5700W: [GFM 1.2.8] Every xbrli:context element must appear in at least one
|
|
255
258
|
contextRef attribute in the same instance.
|
|
256
259
|
"""
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
+
unusedContexts = list(set(val.modelXbrl.contexts.values()) - set(val.modelXbrl.contextsInUse))
|
|
261
|
+
unusedContexts.extend(val.modelXbrl.ixdsUnmappedContexts.values())
|
|
262
|
+
unusedContexts.sort(key=lambda x: x.id if x.id is not None else "")
|
|
263
|
+
for context in unusedContexts:
|
|
260
264
|
yield Validation.warning(
|
|
261
265
|
codes='EDINET.EC5700W.GFM.1.2.8',
|
|
262
266
|
msg=_('If you are not using a context, delete it if it is not needed.'),
|
|
263
|
-
modelObject
|
|
267
|
+
modelObject=context
|
|
264
268
|
)
|
|
265
269
|
|
|
266
270
|
|
|
@@ -349,6 +353,44 @@ def rule_gfm_1_2_13(
|
|
|
349
353
|
)
|
|
350
354
|
|
|
351
355
|
|
|
356
|
+
@validation(
|
|
357
|
+
hook=ValidationHook.XBRL_FINALLY,
|
|
358
|
+
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
359
|
+
)
|
|
360
|
+
def rule_gfm_1_2_14(
|
|
361
|
+
pluginData: PluginValidationDataExtension,
|
|
362
|
+
val: ValidateXbrl,
|
|
363
|
+
*args: Any,
|
|
364
|
+
**kwargs: Any,
|
|
365
|
+
) -> Iterable[Validation]:
|
|
366
|
+
"""
|
|
367
|
+
EDINET.EC5700W: [GFM.1.2.14] The content of an element with a data type of nonnum:textBlockItemType is not well-formed XML
|
|
368
|
+
(a format that conforms to XML grammar, such as all start and end tags being paired, and the end tag of a nested tag not coming after the end tag of its parent tag, etc.).
|
|
369
|
+
Please modify it so that it is well-formed.
|
|
370
|
+
"""
|
|
371
|
+
CDATApattern = regex.compile(r"<!\[CDATA\[(.+)\]\]")
|
|
372
|
+
dtd = DTD(os.path.join(val.modelXbrl.modelManager.cntlr.configDir, xhtmlDtdExtension))
|
|
373
|
+
htmlBodyTemplate = "<body><div>\n{0}\n</div></body>\n"
|
|
374
|
+
namedEntityPattern = regex.compile("&[_A-Za-z\xC0-\xD6\xD8-\xF6\xF8-\xFF\u0100-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]"
|
|
375
|
+
r"[_\-\.:"
|
|
376
|
+
"\xB7A-Za-z0-9\xC0-\xD6\xD8-\xF6\xF8-\xFF\u0100-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u0300-\u036F\u203F-\u2040]*;")
|
|
377
|
+
XMLpattern = regex.compile(r".*(<|<|<|<)[A-Za-z_]+[A-Za-z0-9_:]*[^>]*(/>|>|>|/>).*", regex.DOTALL)
|
|
378
|
+
|
|
379
|
+
for fact in val.modelXbrl.facts:
|
|
380
|
+
concept = fact.concept
|
|
381
|
+
if not fact.isNil and concept is not None and concept.isTextBlock and XMLpattern.match(fact.value):
|
|
382
|
+
for xmlText in [fact.value] + CDATApattern.findall(fact.value):
|
|
383
|
+
xmlBodyWithoutEntities = htmlBodyTemplate.format(namedEntityPattern.sub("", xmlText).replace('&','&'))
|
|
384
|
+
textblockXml = XML(xmlBodyWithoutEntities)
|
|
385
|
+
if not dtd.validate(textblockXml):
|
|
386
|
+
yield Validation.warning(
|
|
387
|
+
codes='EDINET.EC5700W.GFM.1.2.14',
|
|
388
|
+
msg=_('The content of an element with a data type of nonnum:textBlockItemType is not well-formed XML (a format that conforms to XML grammar, '
|
|
389
|
+
'such as all start and end tags being in pairs, and the end tag of a nested tag not coming after the end tag of its parent tag). '
|
|
390
|
+
'Correct the content so that it is well-formed.'),
|
|
391
|
+
modelObject = fact
|
|
392
|
+
)
|
|
393
|
+
|
|
352
394
|
|
|
353
395
|
@validation(
|
|
354
396
|
hook=ValidationHook.XBRL_FINALLY,
|
|
@@ -443,6 +485,7 @@ def rule_gfm_1_2_25(
|
|
|
443
485
|
XbrlConst.qnXbrliEndDate.clarkNotation,
|
|
444
486
|
XbrlConst.qnXbrliInstant.clarkNotation
|
|
445
487
|
):
|
|
488
|
+
elt = cast(ModelObject, elt)
|
|
446
489
|
dateText = XmlUtil.text(elt)
|
|
447
490
|
if not GFM_CONTEXT_DATE_PATTERN.match(dateText):
|
|
448
491
|
errors.append(elt)
|
|
@@ -517,12 +560,14 @@ def rule_gfm_1_2_27(
|
|
|
517
560
|
EDINET.EC5700W: [GFM 1.2.27] An instance must not contain unused units.
|
|
518
561
|
"""
|
|
519
562
|
# TODO: Consolidate validations involving unused units
|
|
520
|
-
unusedUnits = set(val.modelXbrl.units.values()) -
|
|
563
|
+
unusedUnits = list(set(val.modelXbrl.units.values()) - set(val.modelXbrl.unitsInUse))
|
|
564
|
+
unusedUnits.extend(val.modelXbrl.ixdsUnmappedUnits.values())
|
|
565
|
+
unusedUnits.sort(key=lambda x: x.hash)
|
|
521
566
|
if len(unusedUnits) > 0:
|
|
522
567
|
yield Validation.warning(
|
|
523
568
|
codes='EDINET.EC5700W.GFM.1.2.27',
|
|
524
569
|
msg=_("Delete unused units from the instance."),
|
|
525
|
-
modelObject=
|
|
570
|
+
modelObject=unusedUnits
|
|
526
571
|
)
|
|
527
572
|
|
|
528
573
|
|
|
@@ -529,11 +529,11 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
|
|
|
529
529
|
if contextsWithDisallowedOCEs:
|
|
530
530
|
modelXbrl.error("ESEF.2.1.3.segmentUsed",
|
|
531
531
|
_("xbrli:segment container MUST NOT be used in contexts: %(contextIds)s"),
|
|
532
|
-
modelObject=contextsWithDisallowedOCEs, contextIds=", ".join(c.id for c in contextsWithDisallowedOCEs))
|
|
532
|
+
modelObject=contextsWithDisallowedOCEs, contextIds=", ".join(c.id for c in contextsWithDisallowedOCEs if c.id is not None))
|
|
533
533
|
if contextsWithDisallowedOCEcontent:
|
|
534
534
|
modelXbrl.error("ESEF.2.1.3.scenarioContainsNonDimensionalContent",
|
|
535
535
|
_("xbrli:scenario in contexts MUST NOT contain any other content than defined in XBRL Dimensions specification: %(contextIds)s"),
|
|
536
|
-
modelObject=contextsWithDisallowedOCEcontent, contextIds=", ".join(c.id for c in contextsWithDisallowedOCEcontent))
|
|
536
|
+
modelObject=contextsWithDisallowedOCEcontent, contextIds=", ".join(c.id for c in contextsWithDisallowedOCEcontent if c.id is not None))
|
|
537
537
|
if len(contextIdentifiers) > 1:
|
|
538
538
|
modelXbrl.error("ESEF.2.1.4.multipleIdentifiers",
|
|
539
539
|
_("All entity identifiers in contexts MUST have identical content: %(contextIds)s"),
|
|
@@ -589,11 +589,11 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
|
|
|
589
589
|
if contextsWithDisallowedOCEs:
|
|
590
590
|
modelXbrl.error("ESEF.2.1.3.segmentUsed",
|
|
591
591
|
_("xbrli:segment container MUST NOT be used in contexts: %(contextIds)s"),
|
|
592
|
-
modelObject=contextsWithDisallowedOCEs, contextIds=", ".join(c.id for c in contextsWithDisallowedOCEs))
|
|
592
|
+
modelObject=contextsWithDisallowedOCEs, contextIds=", ".join(c.id for c in contextsWithDisallowedOCEs if c.id is not None))
|
|
593
593
|
if contextsWithDisallowedOCEcontent:
|
|
594
594
|
modelXbrl.error("ESEF.2.1.3.scenarioContainsNonDimensionalContent",
|
|
595
595
|
_("xbrli:scenario in contexts MUST NOT contain any other content than defined in XBRL Dimensions specification: %(contextIds)s"),
|
|
596
|
-
modelObject=contextsWithDisallowedOCEcontent, contextIds=", ".join(c.id for c in contextsWithDisallowedOCEcontent))
|
|
596
|
+
modelObject=contextsWithDisallowedOCEcontent, contextIds=", ".join(c.id for c in contextsWithDisallowedOCEcontent if c.id is not None))
|
|
597
597
|
if len(contextIdentifiers) > 1:
|
|
598
598
|
modelXbrl.error("ESEF.2.1.4.multipleIdentifiers",
|
|
599
599
|
_("All entity identifiers in contexts MUST have identical content: %(contextIds)s"),
|
|
@@ -265,7 +265,7 @@ class PluginValidationDataExtension(PluginData):
|
|
|
265
265
|
contextsWithPeriodTimeZone.append(context)
|
|
266
266
|
if context.hasSegment:
|
|
267
267
|
contextsWithSegments.append(context)
|
|
268
|
-
if context.nonDimValues("scenario"):
|
|
268
|
+
if context.nonDimValues("scenario"):
|
|
269
269
|
contextsWithImproperContent.append(context)
|
|
270
270
|
return ContextData(
|
|
271
271
|
contextsWithImproperContent=contextsWithImproperContent,
|
|
@@ -552,9 +552,9 @@ def rule_fr_nl_3_03(
|
|
|
552
552
|
"""
|
|
553
553
|
FR-NL-3.03: An XBRL instance document MUST NOT contain unused contexts
|
|
554
554
|
"""
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
for context in
|
|
555
|
+
unusedContexts = list(set(val.modelXbrl.contexts.values()) - set(val.modelXbrl.contextsInUse))
|
|
556
|
+
unusedContexts.sort(key=lambda x: x.id if x.id is not None else "")
|
|
557
|
+
for context in unusedContexts:
|
|
558
558
|
yield Validation.error(
|
|
559
559
|
codes='NL.FR-NL-3.03',
|
|
560
560
|
msg=_('Unused context must not exist in XBRL instance document'),
|
|
@@ -637,10 +637,9 @@ def rule_fr_nl_4_02(
|
|
|
637
637
|
"""
|
|
638
638
|
FR-NL-4.02: An XBRL instance document MUST NOT contain unused 'xbrli:unit' elements
|
|
639
639
|
"""
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
for unit in unused_units:
|
|
640
|
+
unusedUnits = list(set(val.modelXbrl.units.values()) - set(val.modelXbrl.unitsInUse))
|
|
641
|
+
unusedUnits.sort(key=lambda x: x.hash)
|
|
642
|
+
for unit in unusedUnits:
|
|
644
643
|
yield Validation.error(
|
|
645
644
|
codes='NL.FR-NL-4.02',
|
|
646
645
|
msg=_('Unused unit must not exist in the XBRL instance document'),
|
|
@@ -7,12 +7,13 @@ from dataclasses import dataclass
|
|
|
7
7
|
from dataclasses import field
|
|
8
8
|
from enum import Enum
|
|
9
9
|
from functools import cached_property
|
|
10
|
-
from typing import Any, cast
|
|
10
|
+
from typing import Any, cast, Iterable
|
|
11
11
|
|
|
12
12
|
import regex as re
|
|
13
13
|
|
|
14
14
|
from arelle.ModelInstanceObject import ModelFact
|
|
15
15
|
from arelle.ModelXbrl import ModelXbrl
|
|
16
|
+
from arelle.XmlValidateConst import VALID
|
|
16
17
|
|
|
17
18
|
# Error codes
|
|
18
19
|
CH_AUDIT = 'Char.Audit'
|
|
@@ -430,10 +431,11 @@ class ValidateUK:
|
|
|
430
431
|
_codeResultMap: dict[str, CodeResult] = field(default_factory=dict)
|
|
431
432
|
|
|
432
433
|
def _checkValidFact(self, fact: ModelFact ) -> bool:
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
434
|
+
return (
|
|
435
|
+
fact is not None and
|
|
436
|
+
not fact.isNil and
|
|
437
|
+
fact.context is not None
|
|
438
|
+
)
|
|
437
439
|
|
|
438
440
|
def _errorOnMissingFact(self, conceptLocalName: str) -> None:
|
|
439
441
|
"""
|
|
@@ -628,16 +630,12 @@ class ValidateUK:
|
|
|
628
630
|
elif code == CH_CHAR_FUND:
|
|
629
631
|
concept = CONCEPT_CHARITY_FUNDS
|
|
630
632
|
trading = False
|
|
631
|
-
for
|
|
632
|
-
if
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
NotTrading.CONCEPT_ENTITY_NO_LONGER_TRADING.value,
|
|
638
|
-
NotTrading.CONCEPT_ENTITY_HAS_NEVER_TRADED.value,
|
|
639
|
-
}:
|
|
640
|
-
trading = True
|
|
633
|
+
for value in self._getDimensionValues(CONCEPT_ENTITY_TRADING_STATUS, CONCEPT_ENTITY_TRADING_STATUS_DIMENSION):
|
|
634
|
+
if value in {
|
|
635
|
+
NotTrading.CONCEPT_ENTITY_NO_LONGER_TRADING.value,
|
|
636
|
+
NotTrading.CONCEPT_ENTITY_HAS_NEVER_TRADED.value,
|
|
637
|
+
}:
|
|
638
|
+
trading = True
|
|
641
639
|
if not self._getAndCheckValidFacts([concept]) and not trading:
|
|
642
640
|
return CodeResult(
|
|
643
641
|
conceptLocalName=concept,
|
|
@@ -686,13 +684,11 @@ class ValidateUK:
|
|
|
686
684
|
"""
|
|
687
685
|
Determines if the language is set to Welsh, otherwise defaults to English.
|
|
688
686
|
"""
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
if value.xValue.localName == CONCEPT_WELSH:
|
|
695
|
-
return HmrcLang.WELSH
|
|
687
|
+
if any(
|
|
688
|
+
lang == CONCEPT_WELSH
|
|
689
|
+
for lang in self._getDimensionValues(CONCEPT_REPORT_PRINCIPAL_LANGUAGE, CONCEPT_LANGUAGES_DIMENSION)
|
|
690
|
+
):
|
|
691
|
+
return HmrcLang.WELSH
|
|
696
692
|
return HmrcLang.ENGLISH
|
|
697
693
|
|
|
698
694
|
def _yieldErrorOrWarning(self, code: str, result: CodeResult) -> None:
|
|
@@ -725,47 +721,19 @@ class ValidateUK:
|
|
|
725
721
|
|
|
726
722
|
@cached_property
|
|
727
723
|
def accountStatus(self) -> str | None:
|
|
728
|
-
|
|
729
|
-
for fact in facts:
|
|
730
|
-
if not self._checkValidFact(fact):
|
|
731
|
-
continue
|
|
732
|
-
for qname, value in fact.context.qnameDims.items():
|
|
733
|
-
if qname.localName == CONCEPT_ACCOUNTS_STATUS_DIMENSION:
|
|
734
|
-
return cast(str, value.xValue.localName)
|
|
735
|
-
return None
|
|
724
|
+
return next(iter(self._getDimensionValues(CONCEPT_ACCOUNTS_STATUS, CONCEPT_ACCOUNTS_STATUS_DIMENSION)), None)
|
|
736
725
|
|
|
737
726
|
@cached_property
|
|
738
727
|
def accountsType(self) -> str | None:
|
|
739
|
-
|
|
740
|
-
for fact in facts:
|
|
741
|
-
if not self._checkValidFact(fact):
|
|
742
|
-
continue
|
|
743
|
-
for qname, value in fact.context.qnameDims.items():
|
|
744
|
-
if qname.localName == CONCEPT_ACCOUNTS_TYPE_DIMENSION:
|
|
745
|
-
return cast(str, value.xValue.localName)
|
|
746
|
-
return None
|
|
728
|
+
return next(iter(self._getDimensionValues(CONCEPT_ACCOUNTS_TYPE_FULL_OR_ABBREVIATED, CONCEPT_ACCOUNTS_TYPE_DIMENSION)), None)
|
|
747
729
|
|
|
748
730
|
@cached_property
|
|
749
731
|
def accountingStandardsApplied(self) -> str | None:
|
|
750
|
-
|
|
751
|
-
for fact in facts:
|
|
752
|
-
if not self._checkValidFact(fact):
|
|
753
|
-
continue
|
|
754
|
-
for qname, value in fact.context.qnameDims.items():
|
|
755
|
-
if qname.localName == CONCEPT_ACCOUNTING_STANDARDS_DIMENSION:
|
|
756
|
-
return cast(str, value.xValue.localName)
|
|
757
|
-
return None
|
|
732
|
+
return next(iter(self._getDimensionValues(CONCEPT_ACCOUNTING_STANDARDS_APPLIED, CONCEPT_ACCOUNTING_STANDARDS_DIMENSION)), None)
|
|
758
733
|
|
|
759
734
|
@cached_property
|
|
760
735
|
def applicableLegislation(self) -> str | None:
|
|
761
|
-
|
|
762
|
-
for fact in facts:
|
|
763
|
-
if not self._checkValidFact(fact):
|
|
764
|
-
continue
|
|
765
|
-
for qname, value in fact.context.qnameDims.items():
|
|
766
|
-
if qname.localName == CONCEPT_APPLICABLE_LEGISLATION_DIMENSION:
|
|
767
|
-
return cast(str, value.xValue.localName)
|
|
768
|
-
return None
|
|
736
|
+
return next(iter(self._getDimensionValues(CONCEPT_APPLICABLE_LEGISLATION, CONCEPT_APPLICABLE_LEGISLATION_DIMENSION)), None)
|
|
769
737
|
|
|
770
738
|
@cached_property
|
|
771
739
|
def isEntityDormant(self) -> bool:
|
|
@@ -776,25 +744,22 @@ class ValidateUK:
|
|
|
776
744
|
|
|
777
745
|
@cached_property
|
|
778
746
|
def legalFormEntity(self) -> str | None:
|
|
779
|
-
|
|
780
|
-
for fact in facts:
|
|
781
|
-
if not self._checkValidFact(fact):
|
|
782
|
-
continue
|
|
783
|
-
for qname, value in fact.context.qnameDims.items():
|
|
784
|
-
if qname.localName == CONCEPT_LEGAL_FORM_ENTIY_DIMENSION:
|
|
785
|
-
return cast(str, value.xValue.localName)
|
|
786
|
-
return None
|
|
747
|
+
return next(iter(self._getDimensionValues(CONCEPT_LEGAL_FORM_ENTIY, CONCEPT_LEGAL_FORM_ENTIY_DIMENSION)), None)
|
|
787
748
|
|
|
788
749
|
@cached_property
|
|
789
750
|
def scopeAccounts(self) -> str | None:
|
|
790
|
-
|
|
751
|
+
return next(iter(self._getDimensionValues(CONCEPT_SCOPE_ACCOUNTS, CONCEPT_SCOPE_ACCOUNTS_DIMENSION)), None)
|
|
752
|
+
|
|
753
|
+
def _getDimensionValues(self, conceptLocalName: str, dimensionLocalName: str) -> Iterable[str]:
|
|
754
|
+
facts = self._getFacts(conceptLocalName)
|
|
791
755
|
for fact in facts:
|
|
792
756
|
if not self._checkValidFact(fact):
|
|
793
757
|
continue
|
|
794
758
|
for qname, value in fact.context.qnameDims.items():
|
|
795
|
-
if
|
|
796
|
-
|
|
797
|
-
|
|
759
|
+
if value.xValid < VALID:
|
|
760
|
+
continue
|
|
761
|
+
if qname.localName == dimensionLocalName:
|
|
762
|
+
yield cast(str, value.xValue.localName)
|
|
798
763
|
|
|
799
764
|
def validate(self) -> None:
|
|
800
765
|
"""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: arelle-release
|
|
3
|
-
Version: 2.37.
|
|
3
|
+
Version: 2.37.50
|
|
4
4
|
Summary: An open source XBRL platform.
|
|
5
5
|
Author-email: "arelle.org" <support@arelle.org>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -31,34 +31,38 @@ License-File: LICENSE.md
|
|
|
31
31
|
Requires-Dist: bottle<0.14,>=0.13
|
|
32
32
|
Requires-Dist: certifi
|
|
33
33
|
Requires-Dist: filelock
|
|
34
|
-
Requires-Dist:
|
|
35
|
-
Requires-Dist:
|
|
36
|
-
Requires-Dist: lxml
|
|
34
|
+
Requires-Dist: isodate<1,>=0
|
|
35
|
+
Requires-Dist: jsonschema<5,>=4
|
|
36
|
+
Requires-Dist: lxml!=6.0.0,<7,>=4
|
|
37
37
|
Requires-Dist: numpy<3,>=1
|
|
38
|
-
Requires-Dist: openpyxl
|
|
38
|
+
Requires-Dist: openpyxl<4,>=3
|
|
39
39
|
Requires-Dist: pillow<12,>=10
|
|
40
|
-
Requires-Dist: pyparsing
|
|
41
|
-
Requires-Dist: python-dateutil
|
|
40
|
+
Requires-Dist: pyparsing<4,>=3
|
|
41
|
+
Requires-Dist: python-dateutil<3,>=2
|
|
42
42
|
Requires-Dist: regex
|
|
43
|
-
Requires-Dist: truststore
|
|
44
|
-
Requires-Dist: typing-extensions
|
|
43
|
+
Requires-Dist: truststore<1,>=0; python_version > "3.9"
|
|
44
|
+
Requires-Dist: typing-extensions<5,>=4
|
|
45
45
|
Provides-Extra: crypto
|
|
46
|
-
Requires-Dist: pycryptodome
|
|
46
|
+
Requires-Dist: pycryptodome<4,>=3; extra == "crypto"
|
|
47
47
|
Provides-Extra: db
|
|
48
|
-
Requires-Dist: pg8000
|
|
49
|
-
Requires-Dist: PyMySQL
|
|
48
|
+
Requires-Dist: pg8000<2,>=1; extra == "db"
|
|
49
|
+
Requires-Dist: PyMySQL<2,>=1; extra == "db"
|
|
50
50
|
Requires-Dist: pyodbc<6,>=4; extra == "db"
|
|
51
51
|
Requires-Dist: rdflib<8,>=5; extra == "db"
|
|
52
52
|
Provides-Extra: efm
|
|
53
|
-
Requires-Dist:
|
|
53
|
+
Requires-Dist: aniso8601<11,>=10; extra == "efm"
|
|
54
|
+
Requires-Dist: holidays<1,>=0; extra == "efm"
|
|
55
|
+
Requires-Dist: matplotlib<4,>=3; extra == "efm"
|
|
54
56
|
Requires-Dist: pytz; extra == "efm"
|
|
55
57
|
Provides-Extra: esef
|
|
56
|
-
Requires-Dist: tinycss2
|
|
58
|
+
Requires-Dist: tinycss2<2,>=1; extra == "esef"
|
|
57
59
|
Provides-Extra: objectmaker
|
|
58
|
-
Requires-Dist: graphviz
|
|
60
|
+
Requires-Dist: graphviz<1,>=0; extra == "objectmaker"
|
|
59
61
|
Provides-Extra: webserver
|
|
60
62
|
Requires-Dist: cheroot<11,>=8; extra == "webserver"
|
|
61
|
-
Requires-Dist: tornado
|
|
63
|
+
Requires-Dist: tornado<7,>=6; extra == "webserver"
|
|
64
|
+
Provides-Extra: xule
|
|
65
|
+
Requires-Dist: aniso8601<11,>=10; extra == "xule"
|
|
62
66
|
Dynamic: license-file
|
|
63
67
|
|
|
64
68
|
# Arelle
|