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.

Files changed (28) hide show
  1. arelle/ModelRelationshipSet.py +3 -0
  2. arelle/Updater.py +7 -3
  3. arelle/ValidateDuplicateFacts.py +13 -7
  4. arelle/XbrlConst.py +22 -0
  5. arelle/_version.py +2 -2
  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/LinkbaseType.py +17 -0
  10. arelle/plugin/validate/NL/PluginValidationDataExtension.py +155 -4
  11. arelle/plugin/validate/NL/resources/config.xml +6 -0
  12. arelle/plugin/validate/NL/rules/fr_kvk.py +1 -1
  13. arelle/plugin/validate/NL/rules/nl_kvk.py +656 -22
  14. arelle/utils/validate/DetectScriptsInXhtml.py +1 -4
  15. arelle/utils/validate/ESEFImage.py +274 -0
  16. {arelle_release-2.37.22.dist-info → arelle_release-2.37.25.dist-info}/METADATA +1 -1
  17. {arelle_release-2.37.22.dist-info → arelle_release-2.37.25.dist-info}/RECORD +27 -27
  18. tests/integration_tests/validation/conformance_suite_configurations/efm_current.py +2 -2
  19. tests/integration_tests/validation/conformance_suite_configurations/nl_inline_2024.py +52 -28
  20. tests/integration_tests/validation/conformance_suite_configurations/nl_inline_2024_gaap_other.py +10 -0
  21. tests/resources/conformance_suites_timing/efm_current.json +8499 -8583
  22. tests/unit_tests/arelle/plugin/validate/ESEF/ESEF_Current/test_validate_css_url.py +10 -2
  23. tests/unit_tests/arelle/test_updater.py +43 -14
  24. arelle/plugin/validate/ESEF/ESEF_Current/Image.py +0 -213
  25. {arelle_release-2.37.22.dist-info → arelle_release-2.37.25.dist-info}/WHEEL +0 -0
  26. {arelle_release-2.37.22.dist-info → arelle_release-2.37.25.dist-info}/entry_points.txt +0 -0
  27. {arelle_release-2.37.22.dist-info → arelle_release-2.37.25.dist-info}/licenses/LICENSE.md +0 -0
  28. {arelle_release-2.37.22.dist-info → arelle_release-2.37.25.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,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]
@@ -177,6 +204,8 @@ class LinkbaseData:
177
204
  basename: str
178
205
  element: _Element
179
206
  linkbaseType: LinkbaseType | None
207
+ prohibitedBaseConcepts: list[ModelConcept]
208
+ prohibitingLabelElements: list[_Element]
180
209
 
181
210
  @property
182
211
  def hasArcs(self) -> bool:
@@ -249,6 +278,23 @@ class PluginValidationDataExtension(PluginData):
249
278
  contextsWithSegments=contextsWithSegments,
250
279
  )
251
280
 
281
+ def checkLabels(self, issues: set[ModelConcept| None], modelXbrl: ModelXbrl, parent: ModelConcept, relSet: ModelRelationshipSet, labelrole: str | None, visited: set[ModelConcept]) -> set[ModelConcept| None]:
282
+ visited.add(parent)
283
+ conceptRels = defaultdict(list) # counts for concepts without preferred label role
284
+ for rel in relSet.fromModelObject(parent):
285
+ child = rel.toModelObject
286
+ if child is not None:
287
+ labelrole = rel.preferredLabel
288
+ if not labelrole:
289
+ conceptRels[child].append(rel)
290
+ if child not in visited:
291
+ self.checkLabels(issues, modelXbrl, child, relSet, labelrole, visited)
292
+ for concept, rels in conceptRels.items():
293
+ if len(rels) > 1:
294
+ issues.add(concept)
295
+ visited.remove(parent)
296
+ return issues
297
+
252
298
  @lru_cache(1)
253
299
  def checkHiddenElements(self, modelXbrl: ModelXbrl) -> HiddenElementsData:
254
300
  cssHiddenFacts = set()
@@ -366,6 +412,70 @@ class PluginValidationDataExtension(PluginData):
366
412
  factLangs.add(fact.xmlLang)
367
413
  return factLangs
368
414
 
415
+ @lru_cache(1)
416
+ def getAnchorData(self, modelXbrl: ModelXbrl) -> AnchorData:
417
+ extLineItemsNotAnchored = set()
418
+ extLineItemsWronglyAnchored = set()
419
+ widerNarrowerRelSet = modelXbrl.relationshipSet(XbrlConst.widerNarrower)
420
+ generalSpecialRelSet = modelXbrl.relationshipSet(XbrlConst.generalSpecial)
421
+ calcRelSet = modelXbrl.relationshipSet(XbrlConst.summationItems)
422
+ dimensionalData = self.getDimensionalData(modelXbrl)
423
+ primaryItems = dimensionalData.primaryItems
424
+ domainMembers = dimensionalData.domainMembers
425
+ extensionData = self.getExtensionData(modelXbrl)
426
+ for concept in extensionData.extensionConcepts:
427
+ extLineItem = False
428
+ if concept.isPrimaryItem and \
429
+ not concept.isAbstract and \
430
+ concept in primaryItems and \
431
+ not widerNarrowerRelSet.contains(concept) and \
432
+ not calcRelSet.fromModelObject(concept):
433
+ extLineItem = True
434
+ elif concept.isAbstract and \
435
+ concept not in domainMembers and \
436
+ concept.type is not None and \
437
+ not concept.type.isDomainItemType and \
438
+ not concept.isHypercubeItem and \
439
+ not concept.isDimensionItem and \
440
+ not widerNarrowerRelSet.contains(concept):
441
+ extLineItem = True
442
+ if extLineItem:
443
+ if not generalSpecialRelSet.contains(concept):
444
+ extLineItemsNotAnchored.add(concept)
445
+ else:
446
+ extLineItemsWronglyAnchored.add(concept)
447
+ elrsContainingDimensionalRelationships = set(
448
+ ELR
449
+ for arcrole, ELR, linkqname, arcqname in modelXbrl.baseSets.keys()
450
+ if arcrole == "XBRL-dimensions" and ELR is not None)
451
+ anchorsNotInBase = set()
452
+ anchorsWithDomainItem = set()
453
+ anchorsWithDimensionItem = set()
454
+ anchorsInDimensionalElrs = defaultdict(set)
455
+ for anchoringRel in widerNarrowerRelSet.modelRelationships:
456
+ elr = anchoringRel.linkrole
457
+ fromObj = anchoringRel.fromModelObject
458
+ toObj = anchoringRel.toModelObject
459
+ if fromObj is not None and toObj is not None and fromObj.type is not None and toObj.type is not None:
460
+ if not ((not self.isExtensionUri(fromObj.modelDocument.uri, modelXbrl)) ^ (not self.isExtensionUri(toObj.modelDocument.uri, modelXbrl))):
461
+ anchorsNotInBase.add(anchoringRel)
462
+ if fromObj.type.isDomainItemType or toObj.type.isDomainItemType:
463
+ anchorsWithDomainItem.add(anchoringRel)
464
+ elif fromObj.isDimensionItem or toObj.isDimensionItem:
465
+ anchorsWithDimensionItem.add(anchoringRel)
466
+ else:
467
+ if elr in elrsContainingDimensionalRelationships:
468
+ anchorsInDimensionalElrs[elr].add(anchoringRel)
469
+ return AnchorData(
470
+ anchorsInDimensionalElrs={x: frozenset(y) for x, y in anchorsInDimensionalElrs.items()},
471
+ anchorsNotInBase=frozenset(anchorsNotInBase),
472
+ anchorsWithDimensionItem=frozenset(anchorsWithDimensionItem),
473
+ anchorsWithDomainItem=frozenset(anchorsWithDomainItem),
474
+ extLineItemsNotAnchored=frozenset(extLineItemsNotAnchored),
475
+ extLineItemsWronglyAnchored=frozenset(extLineItemsWronglyAnchored),
476
+ )
477
+
478
+
369
479
  def getBaseElements(self, modelXbrl: ModelXbrl) -> set[Any | None]:
370
480
  return self.checkInlineHTMLElements(modelXbrl).baseElements
371
481
 
@@ -399,10 +509,13 @@ class PluginValidationDataExtension(PluginData):
399
509
  _getDocumentsInDts(modelXbrl.modelDocument)
400
510
  return modelDocuments
401
511
 
402
- def getDomainMembers(self, modelXbrl: ModelXbrl) -> set[ModelConcept]:
512
+ @lru_cache(1)
513
+ def getDimensionalData(self, modelXbrl: ModelXbrl) -> DimensionalData:
403
514
  domainMembers = set() # concepts which are dimension domain members
515
+ elrPrimaryItems = defaultdict(set)
404
516
  hcPrimaryItems: set[ModelConcept] = set()
405
517
  hcMembers: set[Any] = set()
518
+ primaryItems: set[ModelConcept] = set()
406
519
  for hasHypercubeArcrole in (XbrlConst.all, XbrlConst.notAll):
407
520
  hasHypercubeRelationships = modelXbrl.relationshipSet(hasHypercubeArcrole).fromModelObjects()
408
521
  for hasHcRels in hasHypercubeRelationships.values():
@@ -413,6 +526,7 @@ class PluginValidationDataExtension(PluginData):
413
526
  for domMbrRel in modelXbrl.relationshipSet(XbrlConst.domainMember).fromModelObject(sourceConcept):
414
527
  if domMbrRel.consecutiveLinkrole == hasHcRel.linkrole: # only those related to this hc
415
528
  self.addDomMbrs(modelXbrl, domMbrRel.toModelObject, domMbrRel.consecutiveLinkrole, hcPrimaryItems)
529
+ primaryItems.update(hcPrimaryItems)
416
530
  hc = hasHcRel.toModelObject
417
531
  for hcDimRel in modelXbrl.relationshipSet(XbrlConst.hypercubeDimension, hasHcRel.consecutiveLinkrole).fromModelObject(hc):
418
532
  dim = hcDimRel.toModelObject
@@ -422,7 +536,18 @@ class PluginValidationDataExtension(PluginData):
422
536
  if isinstance(dom, ModelConcept):
423
537
  self.addDomMbrs(modelXbrl, dom, dimDomRel.consecutiveLinkrole, hcMembers)
424
538
  domainMembers.update(hcMembers)
425
- return domainMembers
539
+ if hasHcRel.linkrole in NON_DIMENSIONALIZED_LINE_ITEM_LINKROLES or hcMembers:
540
+ for hcPrimaryItem in hcPrimaryItems:
541
+ if not hcPrimaryItem.isAbstract:
542
+ elrPrimaryItems[hasHcRel.linkrole].add(hcPrimaryItem)
543
+ elrPrimaryItems["*"].add(hcPrimaryItem) # members of any ELR
544
+ hcPrimaryItems.clear()
545
+ hcMembers.clear()
546
+ return DimensionalData(
547
+ domainMembers=frozenset(domainMembers),
548
+ elrPrimaryItems=elrPrimaryItems,
549
+ primaryItems=frozenset(primaryItems),
550
+ )
426
551
 
427
552
  def getEligibleForTransformHiddenFacts(self, modelXbrl: ModelXbrl) -> set[ModelInlineFact]:
428
553
  return self.checkHiddenElements(modelXbrl).eligibleForTransformHiddenFacts
@@ -475,6 +600,7 @@ class PluginValidationDataExtension(PluginData):
475
600
  def getIxdsDocBasenames(self, modelXbrl: ModelXbrl) -> set[str]:
476
601
  return set(Path(url).name for url in getattr(modelXbrl, "ixdsDocUrls", []))
477
602
 
603
+ @lru_cache(1)
478
604
  def getExtensionConcepts(self, modelXbrl: ModelXbrl) -> list[ModelConcept]:
479
605
  """
480
606
  Returns a list of extension concepts in the DTS.
@@ -517,11 +643,36 @@ class PluginValidationDataExtension(PluginData):
517
643
  for linkElt in modelDocument.xmlRootElement.iterdescendants(tag=linkbaseType.getLinkQn().clarkNotation):
518
644
  arcQn = linkbaseType.getArcQn()
519
645
  arcs = list(linkElt.iterdescendants(tag=arcQn.clarkNotation))
646
+ prohibitingLabelElements = []
647
+ prohibitedBaseConcepts = []
648
+ if linkbaseType in (LinkbaseType.LABEL, LinkbaseType.REFERENCE):
649
+ prohibitedArcFroms = defaultdict(list)
650
+ prohibitedArcTos = defaultdict(list)
651
+ for arcElt in linkElt.iterchildren(
652
+ LinkbaseType.LABEL.getArcQn().clarkNotation,
653
+ LinkbaseType.REFERENCE.getArcQn().clarkNotation,
654
+ ):
655
+ if arcElt.get("use") == "prohibited":
656
+ prohibitedArcFroms[arcElt.get(XbrlConst.qnXlinkFrom.clarkNotation)].append(arcElt)
657
+ prohibitedArcTos[arcElt.get(XbrlConst.qnXlinkTo.clarkNotation)].append(arcElt)
658
+ for locElt in linkElt.iterchildren(XbrlConst.qnLinkLoc.clarkNotation):
659
+ if self.isExtensionUri(locElt.get(XbrlConst.qnXlinkHref.clarkNotation), modelDocument.modelXbrl):
660
+ continue
661
+ prohibitingArcs = prohibitedArcTos.get(locElt.get(XbrlConst.qnXlinkLabel.clarkNotation))
662
+ if prohibitingArcs:
663
+ prohibitingLabelElements.extend(prohibitingArcs)
664
+ prohibitingArcs = prohibitedArcFroms.get(locElt.get(XbrlConst.qnXlinkLabel.clarkNotation))
665
+ if prohibitingArcs:
666
+ prohibitingLabelElements.extend(prohibitingArcs)
667
+ prohibitedBaseConcepts.append(locElt.dereference())
668
+
520
669
  linkbases.append(LinkbaseData(
521
670
  arcs=arcs,
522
671
  basename=modelDocument.basename,
523
672
  element=linkElt,
524
673
  linkbaseType=linkbaseType,
674
+ prohibitedBaseConcepts=prohibitedBaseConcepts,
675
+ prohibitingLabelElements=prohibitingLabelElements,
525
676
  ))
526
677
  return linkbases
527
678
 
@@ -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,