arelle-release 2.37.21__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.

Files changed (28) hide show
  1. arelle/ModelRelationshipSet.py +3 -0
  2. arelle/ValidateDuplicateFacts.py +13 -7
  3. arelle/XbrlConst.py +3 -0
  4. arelle/_version.py +2 -2
  5. arelle/api/Session.py +88 -58
  6. arelle/plugin/validate/DBA/rules/fr.py +10 -10
  7. arelle/plugin/validate/DBA/rules/th.py +1 -1
  8. arelle/plugin/validate/ESEF/ESEF_Current/ValidateXbrlFinally.py +21 -20
  9. arelle/plugin/validate/NL/DisclosureSystems.py +14 -0
  10. arelle/plugin/validate/NL/PluginValidationDataExtension.py +167 -3
  11. arelle/plugin/validate/NL/ValidationPluginExtension.py +10 -1
  12. arelle/plugin/validate/NL/resources/config.xml +12 -1
  13. arelle/plugin/validate/NL/rules/fr_kvk.py +1 -1
  14. arelle/plugin/validate/NL/rules/nl_kvk.py +680 -155
  15. arelle/utils/validate/DetectScriptsInXhtml.py +1 -4
  16. arelle/utils/validate/ESEFImage.py +274 -0
  17. {arelle_release-2.37.21.dist-info → arelle_release-2.37.23.dist-info}/METADATA +1 -1
  18. {arelle_release-2.37.21.dist-info → arelle_release-2.37.23.dist-info}/RECORD +27 -26
  19. tests/integration_tests/validation/conformance_suite_configs.py +2 -0
  20. tests/integration_tests/validation/conformance_suite_configurations/nl_inline_2024.py +30 -43
  21. tests/integration_tests/validation/conformance_suite_configurations/nl_inline_2024_gaap_other.py +244 -0
  22. tests/integration_tests/validation/discover_tests.py +2 -2
  23. tests/unit_tests/arelle/plugin/validate/ESEF/ESEF_Current/test_validate_css_url.py +10 -2
  24. arelle/plugin/validate/ESEF/ESEF_Current/Image.py +0 -213
  25. {arelle_release-2.37.21.dist-info → arelle_release-2.37.23.dist-info}/WHEEL +0 -0
  26. {arelle_release-2.37.21.dist-info → arelle_release-2.37.23.dist-info}/entry_points.txt +0 -0
  27. {arelle_release-2.37.21.dist-info → arelle_release-2.37.23.dist-info}/licenses/LICENSE.md +0 -0
  28. {arelle_release-2.37.21.dist-info → arelle_release-2.37.23.dist-info}/top_level.txt +0 -0
@@ -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, TYPE_CHECKING, cast, Iterable
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,10 +15,11 @@ 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
- from arelle.ModelValue import QName
22
+ from arelle.ModelValue import QName, qname
22
23
  from arelle.ModelXbrl import ModelXbrl
23
24
  from arelle.typing import assert_type
24
25
  from arelle.utils.PluginData import PluginData
@@ -28,6 +29,7 @@ from arelle.XmlValidate import lexicalPatterns
28
29
  from arelle.XmlValidateConst import VALID
29
30
  from .LinkbaseType import LinkbaseType
30
31
 
32
+ DEFAULT_MEMBER_ROLE_URI = 'https://www.nltaxonomie.nl/kvk/role/axis-defaults'
31
33
  XBRLI_IDENTIFIER_PATTERN = re.compile(r"^(?!00)\d{8}$")
32
34
  XBRLI_IDENTIFIER_SCHEMA = 'http://www.kvk.nl/kvk-id'
33
35
  MAX_REPORT_PACKAGE_SIZE_MBS = 100
@@ -63,6 +65,14 @@ EFFECTIVE_KVK_GAAP_IFRS_ENTRYPOINT_FILES = frozenset((
63
65
  'https://www.nltaxonomie.nl/kvk/2024-12-31/kvk-annual-report-ifrs-ext.xsd',
64
66
  ))
65
67
 
68
+ EFFECTIVE_KVK_GAAP_OTHER_ENTRYPOINT_FILES = frozenset((
69
+ 'https://www.nltaxonomie.nl/kvk/2024-12-31/kvk-annual-report-other-gaap.xsd',
70
+ ))
71
+
72
+ NON_DIMENSIONALIZED_LINE_ITEM_LINKROLES = frozenset((
73
+ 'https://www.nltaxonomie.nl/kvk/role/lineitems-nondimensional-usage',
74
+ ))
75
+
66
76
  TAXONOMY_URLS_BY_YEAR = {
67
77
  '2024': {
68
78
  'https://www.nltaxonomie.nl/kvk/2024-12-31/kvk-annual-report-nlgaap-ext.xsd',
@@ -94,6 +104,25 @@ STANDARD_TAXONOMY_URLS = frozenset((
94
104
  'https://www.w3.org/1999/xlink'
95
105
  ))
96
106
 
107
+ QN_DOMAIN_ITEM_TYPES = frozenset((
108
+ qname("{http://www.xbrl.org/dtr/type/2022-03-31}nonnum:domainItemType"),
109
+ ))
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
+
97
126
 
98
127
  @dataclass(frozen=True)
99
128
  class ContextData:
@@ -103,6 +132,13 @@ class ContextData:
103
132
  contextsWithSegments: list[ModelContext | None]
104
133
 
105
134
 
135
+ @dataclass(frozen=True)
136
+ class DimensionalData:
137
+ domainMembers: frozenset[ModelConcept]
138
+ elrPrimaryItems: dict[str, set[ModelConcept]]
139
+ primaryItems: frozenset[ModelConcept]
140
+
141
+
106
142
  @dataclass(frozen=True)
107
143
  class ExtensionData:
108
144
  extensionConcepts: list[ModelConcept]
@@ -195,6 +231,12 @@ class PluginValidationDataExtension(PluginData):
195
231
  def __hash__(self) -> int:
196
232
  return id(self)
197
233
 
234
+ def addDomMbrs(self, modelXbrl: ModelXbrl, sourceDomMbr: ModelConcept, ELR: str, membersSet: set[ModelConcept]) -> None:
235
+ if isinstance(sourceDomMbr, ModelConcept) and sourceDomMbr not in membersSet:
236
+ membersSet.add(sourceDomMbr)
237
+ for domMbrRel in modelXbrl.relationshipSet(XbrlConst.domainMember, ELR).fromModelObject(sourceDomMbr):
238
+ self.addDomMbrs(modelXbrl, domMbrRel.toModelObject, domMbrRel.consecutiveLinkrole, membersSet)
239
+
198
240
  @lru_cache(1)
199
241
  def contextsByDocument(self, modelXbrl: ModelXbrl) -> dict[str, list[ModelContext]]:
200
242
  contextsByDocument = defaultdict(list)
@@ -234,6 +276,23 @@ class PluginValidationDataExtension(PluginData):
234
276
  contextsWithSegments=contextsWithSegments,
235
277
  )
236
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
+
237
296
  @lru_cache(1)
238
297
  def checkHiddenElements(self, modelXbrl: ModelXbrl) -> HiddenElementsData:
239
298
  cssHiddenFacts = set()
@@ -351,6 +410,70 @@ class PluginValidationDataExtension(PluginData):
351
410
  factLangs.add(fact.xmlLang)
352
411
  return factLangs
353
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
+
354
477
  def getBaseElements(self, modelXbrl: ModelXbrl) -> set[Any | None]:
355
478
  return self.checkInlineHTMLElements(modelXbrl).baseElements
356
479
 
@@ -384,6 +507,46 @@ class PluginValidationDataExtension(PluginData):
384
507
  _getDocumentsInDts(modelXbrl.modelDocument)
385
508
  return modelDocuments
386
509
 
510
+ @lru_cache(1)
511
+ def getDimensionalData(self, modelXbrl: ModelXbrl) -> DimensionalData:
512
+ domainMembers = set() # concepts which are dimension domain members
513
+ elrPrimaryItems = defaultdict(set)
514
+ hcPrimaryItems: set[ModelConcept] = set()
515
+ hcMembers: set[Any] = set()
516
+ primaryItems: set[ModelConcept] = set()
517
+ for hasHypercubeArcrole in (XbrlConst.all, XbrlConst.notAll):
518
+ hasHypercubeRelationships = modelXbrl.relationshipSet(hasHypercubeArcrole).fromModelObjects()
519
+ for hasHcRels in hasHypercubeRelationships.values():
520
+ for hasHcRel in hasHcRels:
521
+ sourceConcept: ModelConcept = hasHcRel.fromModelObject
522
+ hcPrimaryItems.add(sourceConcept)
523
+ # find associated primary items to source concept
524
+ for domMbrRel in modelXbrl.relationshipSet(XbrlConst.domainMember).fromModelObject(sourceConcept):
525
+ if domMbrRel.consecutiveLinkrole == hasHcRel.linkrole: # only those related to this hc
526
+ self.addDomMbrs(modelXbrl, domMbrRel.toModelObject, domMbrRel.consecutiveLinkrole, hcPrimaryItems)
527
+ primaryItems.update(hcPrimaryItems)
528
+ hc = hasHcRel.toModelObject
529
+ for hcDimRel in modelXbrl.relationshipSet(XbrlConst.hypercubeDimension, hasHcRel.consecutiveLinkrole).fromModelObject(hc):
530
+ dim = hcDimRel.toModelObject
531
+ if isinstance(dim, ModelConcept):
532
+ for dimDomRel in modelXbrl.relationshipSet(XbrlConst.dimensionDomain, hcDimRel.consecutiveLinkrole).fromModelObject(dim):
533
+ dom = dimDomRel.toModelObject
534
+ if isinstance(dom, ModelConcept):
535
+ self.addDomMbrs(modelXbrl, dom, dimDomRel.consecutiveLinkrole, hcMembers)
536
+ domainMembers.update(hcMembers)
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
+ )
549
+
387
550
  def getEligibleForTransformHiddenFacts(self, modelXbrl: ModelXbrl) -> set[ModelInlineFact]:
388
551
  return self.checkHiddenElements(modelXbrl).eligibleForTransformHiddenFacts
389
552
 
@@ -435,6 +598,7 @@ class PluginValidationDataExtension(PluginData):
435
598
  def getIxdsDocBasenames(self, modelXbrl: ModelXbrl) -> set[str]:
436
599
  return set(Path(url).name for url in getattr(modelXbrl, "ixdsDocUrls", []))
437
600
 
601
+ @lru_cache(1)
438
602
  def getExtensionConcepts(self, modelXbrl: ModelXbrl) -> list[ModelConcept]:
439
603
  """
440
604
  Returns a list of extension concepts in the DTS.
@@ -10,7 +10,9 @@ from arelle.ModelXbrl import ModelXbrl
10
10
  from arelle.ValidateXbrl import ValidateXbrl
11
11
  from arelle.typing import TypeGetText
12
12
  from arelle.utils.validate.ValidationPlugin import ValidationPlugin
13
- from .DisclosureSystems import DISCLOSURE_SYSTEM_NT16, DISCLOSURE_SYSTEM_NT17, DISCLOSURE_SYSTEM_NT18, DISCLOSURE_SYSTEM_NT19, DISCLOSURE_SYSTEM_NL_INLINE_2024
13
+ from .DisclosureSystems import (DISCLOSURE_SYSTEM_NT16, DISCLOSURE_SYSTEM_NT17, DISCLOSURE_SYSTEM_NT18,
14
+ DISCLOSURE_SYSTEM_NT19, DISCLOSURE_SYSTEM_NL_INLINE_2024,
15
+ DISCLOSURE_SYSTEM_NL_INLINE_2024_GAAP_OTHER)
14
16
  from .PluginValidationDataExtension import PluginValidationDataExtension
15
17
 
16
18
  _: TypeGetText
@@ -174,6 +176,13 @@ class ValidationPluginExtension(ValidationPlugin):
174
176
  entrypoints = {entrypointRoot + e for e in [
175
177
  'kvk-annual-report-ifrs-ext.xsd',
176
178
  'kvk-annual-report-nlgaap-ext.xsd',
179
+ ]}
180
+ elif disclosureSystem == DISCLOSURE_SYSTEM_NL_INLINE_2024_GAAP_OTHER:
181
+ jenvNamespace = 'https://www.nltaxonomie.nl/bw2-titel9/2024-12-31/bw2-titel9-cor'
182
+ kvkINamespace = None
183
+ nlTypesNamespace = None
184
+ entrypointRoot = 'http://www.nltaxonomie.nl/kvk/2024-12-31/'
185
+ entrypoints = {entrypointRoot + e for e in [
177
186
  'kvk-annual-report-other-gaap.xsd',
178
187
  ]}
179
188
  else:
@@ -5,27 +5,38 @@
5
5
  <!-- see arelle/config/disclosuresystems.xml for full comments -->
6
6
  <DisclosureSystem
7
7
  names="NL-INLINE-2024|nl-inline-2024|kvk-inline-2024-preview"
8
- description="Checks for NL INLINE 2024"
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"
11
+ />
12
+ <DisclosureSystem
13
+ names="NL-INLINE-2024-GAAP-OTHER|nl-inline-2024-gaap-other|kvk-inline-2024-gaap-other-preview"
14
+ description="Checks for NL INLINE 2024. For use with other GAAP."
15
+ validationType="NL"
16
+ exclusiveTypesPattern="EFM|GFM|FERC|HMRC|SBR.NL|EBA|EIOPA|ESEF"
10
17
  />
11
18
  <DisclosureSystem
12
19
  names="NT19|nt19|NT19-preview|nt19-preview"
13
20
  description="Checks for NT19"
14
21
  validationType="NL"
22
+ exclusiveTypesPattern="EFM|GFM|FERC|HMRC|SBR.NL|EBA|EIOPA|ESEF"
15
23
  />
16
24
  <DisclosureSystem
17
25
  names="NT18|nt18|NT18-preview|nt18-preview"
18
26
  description="Checks for NT18"
19
27
  validationType="NL"
28
+ exclusiveTypesPattern="EFM|GFM|FERC|HMRC|SBR.NL|EBA|EIOPA|ESEF"
20
29
  />
21
30
  <DisclosureSystem
22
31
  names="NT17|nt17|NT17-preview|nt17-preview"
23
32
  description="Checks for NT17"
24
33
  validationType="NL"
34
+ exclusiveTypesPattern="EFM|GFM|FERC|HMRC|SBR.NL|EBA|EIOPA|ESEF"
25
35
  />
26
36
  <DisclosureSystem
27
37
  names="NT16|nt16|NT16-preview|nt16-preview"
28
38
  description="Checks for NT16"
29
39
  validationType="NL"
40
+ exclusiveTypesPattern="EFM|GFM|FERC|HMRC|SBR.NL|EBA|EIOPA|ESEF"
30
41
  />
31
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,