arelle-release 2.37.19__py3-none-any.whl → 2.37.21__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 (36) hide show
  1. arelle/CntlrCmdLine.py +8 -0
  2. arelle/FileSource.py +2 -2
  3. arelle/ModelInstanceObject.py +1 -1
  4. arelle/ModelXbrl.py +1 -1
  5. arelle/RuntimeOptions.py +0 -6
  6. arelle/XbrlConst.py +2 -0
  7. arelle/_version.py +2 -2
  8. arelle/api/Session.py +7 -5
  9. arelle/formula/XPathContext.py +22 -22
  10. arelle/formula/XPathParser.py +2 -2
  11. arelle/plugin/OimTaxonomy/ModelValueMore.py +2 -2
  12. arelle/plugin/OimTaxonomy/ViewXbrlTxmyObj.py +2 -3
  13. arelle/plugin/OimTaxonomy/XbrlConcept.py +2 -1
  14. arelle/plugin/OimTaxonomy/XbrlCube.py +8 -8
  15. arelle/plugin/OimTaxonomy/XbrlDts.py +5 -5
  16. arelle/plugin/OimTaxonomy/XbrlImportedTaxonomy.py +3 -3
  17. arelle/plugin/OimTaxonomy/XbrlNetwork.py +3 -3
  18. arelle/plugin/OimTaxonomy/XbrlProperty.py +3 -3
  19. arelle/plugin/OimTaxonomy/XbrlReport.py +3 -3
  20. arelle/plugin/OimTaxonomy/XbrlTableTemplate.py +3 -3
  21. arelle/plugin/OimTaxonomy/XbrlTypes.py +1 -1
  22. arelle/plugin/OimTaxonomy/__init__.py +4 -5
  23. arelle/plugin/validate/NL/LinkbaseType.py +77 -0
  24. arelle/plugin/validate/NL/PluginValidationDataExtension.py +195 -26
  25. arelle/plugin/validate/NL/ValidationPluginExtension.py +1 -0
  26. arelle/plugin/validate/NL/__init__.py +3 -3
  27. arelle/plugin/validate/NL/rules/nl_kvk.py +465 -17
  28. {arelle_release-2.37.19.dist-info → arelle_release-2.37.21.dist-info}/METADATA +2 -1
  29. {arelle_release-2.37.19.dist-info → arelle_release-2.37.21.dist-info}/RECORD +36 -35
  30. tests/integration_tests/validation/conformance_suite_configurations/nl_inline_2024.py +23 -17
  31. tests/unit_tests/arelle/test_import.py +0 -30
  32. tests/unit_tests/arelle/test_runtimeoptions.py +0 -13
  33. {arelle_release-2.37.19.dist-info → arelle_release-2.37.21.dist-info}/WHEEL +0 -0
  34. {arelle_release-2.37.19.dist-info → arelle_release-2.37.21.dist-info}/entry_points.txt +0 -0
  35. {arelle_release-2.37.19.dist-info → arelle_release-2.37.21.dist-info}/licenses/LICENSE.md +0 -0
  36. {arelle_release-2.37.19.dist-info → arelle_release-2.37.21.dist-info}/top_level.txt +0 -0
@@ -7,14 +7,15 @@ 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
10
+ from typing import Any, TYPE_CHECKING, cast, Iterable
11
11
 
12
12
  import regex as re
13
- from lxml.etree import _Comment, _ElementTree, _Entity, _ProcessingInstruction
13
+ from lxml.etree import _Comment, _ElementTree, _Entity, _ProcessingInstruction, _Element
14
14
 
15
+ from arelle import XbrlConst
15
16
  from arelle.FunctionIxt import ixtNamespaces
16
- from arelle import ModelDocument as ModelDocumentFile
17
- from arelle.ModelDocument import ModelDocument
17
+ from arelle.ModelDocument import ModelDocument, Type as ModelDocumentType
18
+ from arelle.ModelDtsObject import ModelConcept
18
19
  from arelle.ModelInstanceObject import ModelContext, ModelFact, ModelInlineFootnote, ModelUnit, ModelInlineFact
19
20
  from arelle.ModelObject import ModelObject
20
21
  from arelle.ModelValue import QName
@@ -22,10 +23,10 @@ from arelle.ModelXbrl import ModelXbrl
22
23
  from arelle.typing import assert_type
23
24
  from arelle.utils.PluginData import PluginData
24
25
  from arelle.utils.validate.ValidationUtil import etreeIterWithDepth
25
- from arelle.ValidateXbrl import ValidateXbrl
26
26
  from arelle.XbrlConst import ixbrl11, xhtmlBaseIdentifier, xmlBaseIdentifier
27
27
  from arelle.XmlValidate import lexicalPatterns
28
28
  from arelle.XmlValidateConst import VALID
29
+ from .LinkbaseType import LinkbaseType
29
30
 
30
31
  XBRLI_IDENTIFIER_PATTERN = re.compile(r"^(?!00)\d{8}$")
31
32
  XBRLI_IDENTIFIER_SCHEMA = 'http://www.kvk.nl/kvk-id'
@@ -57,11 +58,43 @@ ALLOWABLE_LANGUAGES = frozenset((
57
58
  'fr'
58
59
  ))
59
60
 
60
- EFFECTIVE_TAXONOMY_URLS = frozenset((
61
+ EFFECTIVE_KVK_GAAP_IFRS_ENTRYPOINT_FILES = frozenset((
61
62
  'https://www.nltaxonomie.nl/kvk/2024-12-31/kvk-annual-report-nlgaap-ext.xsd',
62
63
  'https://www.nltaxonomie.nl/kvk/2024-12-31/kvk-annual-report-ifrs-ext.xsd',
63
64
  ))
64
65
 
66
+ TAXONOMY_URLS_BY_YEAR = {
67
+ '2024': {
68
+ 'https://www.nltaxonomie.nl/kvk/2024-12-31/kvk-annual-report-nlgaap-ext.xsd',
69
+ 'https://www.nltaxonomie.nl/kvk/2024-12-31/kvk-annual-report-ifrs-ext.xsd',
70
+ 'https://www.nltaxonomie.nl/kvk/2024-12-31/kvk-annual-report-other-gaap.xsd',
71
+ }
72
+ }
73
+
74
+ STANDARD_TAXONOMY_URLS = frozenset((
75
+ 'http://www.nltaxonomie.nl/ifrs/20',
76
+ 'https://www.nltaxonomie.nl/ifrs/20',
77
+ 'http://www.nltaxonomie.nl/',
78
+ 'https://www.nltaxonomie.nl/',
79
+ 'http://www.xbrl.org/taxonomy/int/lei/',
80
+ 'https://www.xbrl.org/taxonomy/int/lei/',
81
+ 'http://www.xbrl.org/20',
82
+ 'https://www.xbrl.org/20',
83
+ 'http://www.xbrl.org/lrr/',
84
+ 'https://www.xbrl.org/lrr/',
85
+ 'http://xbrl.org/20',
86
+ 'https://xbrl.org/20',
87
+ 'http://xbrl.ifrs.org/',
88
+ 'https://xbrl.ifrs.org/',
89
+ 'http://www.xbrl.org/dtr/',
90
+ 'https://www.xbrl.org/dtr/',
91
+ 'http://xbrl.org/2020/extensible-enumerations-2.0',
92
+ 'https://xbrl.org/2020/extensible-enumerations-2.0',
93
+ 'http://www.w3.org/1999/xlink',
94
+ 'https://www.w3.org/1999/xlink'
95
+ ))
96
+
97
+
65
98
  @dataclass(frozen=True)
66
99
  class ContextData:
67
100
  contextsWithImproperContent: list[ModelContext | None]
@@ -69,6 +102,48 @@ class ContextData:
69
102
  contextsWithPeriodTimeZone: list[ModelContext | None]
70
103
  contextsWithSegments: list[ModelContext | None]
71
104
 
105
+
106
+ @dataclass(frozen=True)
107
+ class ExtensionData:
108
+ extensionConcepts: list[ModelConcept]
109
+ extensionDocuments: dict[ModelDocument, ExtensionDocumentData]
110
+ extensionImportedUrls: frozenset[str]
111
+
112
+
113
+ @dataclass(frozen=True)
114
+ class ExtensionDocumentData:
115
+ basename: str
116
+ hrefXlinkRole: str | None
117
+ linkbases: list[LinkbaseData]
118
+
119
+ def iterArcsByType(
120
+ self,
121
+ linkbaseType: LinkbaseType,
122
+ includeArcroles: set[str] | None = None,
123
+ excludeArcroles: set[str] | None = None,
124
+ ) -> Iterable[_Element]:
125
+ """
126
+ Returns a list of LinkbaseData objects for the specified LinkbaseType.
127
+ """
128
+ for linkbase in self.iterLinkbasesByType(linkbaseType):
129
+ for arc in linkbase.arcs:
130
+ if includeArcroles is not None:
131
+ if arc.get(XbrlConst.qnXlinkArcRole.clarkNotation) not in includeArcroles:
132
+ continue
133
+ if excludeArcroles is not None:
134
+ if arc.get(XbrlConst.qnXlinkArcRole.clarkNotation) in excludeArcroles:
135
+ continue
136
+ yield arc
137
+
138
+ def iterLinkbasesByType(self, linkbaseType: LinkbaseType) -> Iterable[LinkbaseData]:
139
+ """
140
+ Returns a list of LinkbaseData objects for the specified LinkbaseType.
141
+ """
142
+ for linkbase in self.linkbases:
143
+ if linkbase.linkbaseType == linkbaseType:
144
+ yield linkbase
145
+
146
+
72
147
  @dataclass(frozen=True)
73
148
  class HiddenElementsData:
74
149
  cssHiddenFacts: set[ModelInlineFact]
@@ -76,6 +151,7 @@ class HiddenElementsData:
76
151
  hiddenFactsOutsideHiddenSection: set[ModelInlineFact]
77
152
  requiredToDisplayFacts: set[ModelInlineFact]
78
153
 
154
+
79
155
  @dataclass(frozen=True)
80
156
  class InlineHTMLData:
81
157
  baseElements: set[Any]
@@ -85,6 +161,19 @@ class InlineHTMLData:
85
161
  factLangFootnotes: dict[ModelInlineFootnote, set[str]]
86
162
  fractionElements: set[Any]
87
163
 
164
+
165
+ @dataclass(frozen=True)
166
+ class LinkbaseData:
167
+ arcs: list[_Element]
168
+ basename: str
169
+ element: _Element
170
+ linkbaseType: LinkbaseType | None
171
+
172
+ @property
173
+ def hasArcs(self) -> bool:
174
+ return len(self.arcs) > 0
175
+
176
+
88
177
  @dataclass
89
178
  class PluginValidationDataExtension(PluginData):
90
179
  chamberOfCommerceRegistrationNumberQn: QName
@@ -93,6 +182,7 @@ class PluginValidationDataExtension(PluginData):
93
182
  documentResubmissionUnsurmountableInaccuraciesQn: QName
94
183
  entrypointRoot: str
95
184
  entrypoints: set[str]
185
+ financialReportingPeriodQn: QName
96
186
  financialReportingPeriodCurrentStartDateQn: QName
97
187
  financialReportingPeriodCurrentEndDateQn: QName
98
188
  financialReportingPeriodPreviousStartDateQn: QName
@@ -144,21 +234,6 @@ class PluginValidationDataExtension(PluginData):
144
234
  contextsWithSegments=contextsWithSegments,
145
235
  )
146
236
 
147
- def checkFilingDTS(
148
- self,
149
- val: ValidateXbrl,
150
- modelDocument: ModelDocument,
151
- visited: list[ModelDocument]
152
- ) -> None:
153
- visited.append(modelDocument)
154
- for referencedDocument, modelDocumentReference in modelDocument.referencesDocument.items():
155
- if referencedDocument not in visited and referencedDocument.inDTS:
156
- self.checkFilingDTS(val, referencedDocument, visited)
157
- if modelDocument.type == ModelDocumentFile.Type.SCHEMA:
158
- for doc, docRef in modelDocument.referencesDocument.items():
159
- if "import" in docRef.referenceTypes:
160
- val.extensionImportedUrls.add(doc.uri)
161
-
162
237
  @lru_cache(1)
163
238
  def checkHiddenElements(self, modelXbrl: ModelXbrl) -> HiddenElementsData:
164
239
  cssHiddenFacts = set()
@@ -169,7 +244,7 @@ class PluginValidationDataExtension(PluginData):
169
244
  presentedHiddenEltIds = defaultdict(list)
170
245
  requiredToDisplayFacts = set()
171
246
  for ixdsHtmlRootElt in modelXbrl.ixdsHtmlElements:
172
- ixNStag = getattr(ixdsHtmlRootElt.modelDocument, "ixNStag", ixbrl11)
247
+ ixNStag = str(getattr(ixdsHtmlRootElt.modelDocument, "ixNStag", ixbrl11))
173
248
  for ixHiddenElt in ixdsHtmlRootElt.iterdescendants(tag=ixNStag + "hidden"):
174
249
  for tag in (ixNStag + "nonNumeric", ixNStag+"nonFraction"):
175
250
  for ixElt in ixHiddenElt.iterdescendants(tag=tag):
@@ -218,7 +293,7 @@ class PluginValidationDataExtension(PluginData):
218
293
  tupleElements = set()
219
294
  orphanedFootnotes = set()
220
295
  for ixdsHtmlRootElt in modelXbrl.ixdsHtmlElements:
221
- ixNStag = getattr(ixdsHtmlRootElt.modelDocument, "ixNStag", ixbrl11)
296
+ ixNStag = str(getattr(ixdsHtmlRootElt.modelDocument, "ixNStag", ixbrl11))
222
297
  ixTupleTag = ixNStag + "tuple"
223
298
  ixFractionTag = ixNStag + "fraction"
224
299
  for elts in modelXbrl.ixdsEltById.values(): # type: ignore[attr-defined]
@@ -291,6 +366,24 @@ class PluginValidationDataExtension(PluginData):
291
366
  def getContextsWithSegments(self, modelXbrl: ModelXbrl) -> list[ModelContext | None]:
292
367
  return self.checkContexts(modelXbrl).contextsWithSegments
293
368
 
369
+ @lru_cache(1)
370
+ def getDocumentsInDts(self, modelXbrl: ModelXbrl) -> dict[ModelDocument, str | None]:
371
+ modelDocuments: dict[ModelDocument, str | None] = {}
372
+ if modelXbrl.modelDocument is None:
373
+ return modelDocuments
374
+
375
+ def _getDocumentsInDts(modelDocument: ModelDocument) -> None:
376
+ for referencedDocument, modelDocumentReference in modelDocument.referencesDocument.items():
377
+ if referencedDocument in modelDocuments:
378
+ continue
379
+ if referencedDocument.inDTS:
380
+ modelDocuments[referencedDocument] = modelDocumentReference.referringXlinkRole
381
+ _getDocumentsInDts(referencedDocument)
382
+
383
+ modelDocuments[modelXbrl.modelDocument] = None
384
+ _getDocumentsInDts(modelXbrl.modelDocument)
385
+ return modelDocuments
386
+
294
387
  def getEligibleForTransformHiddenFacts(self, modelXbrl: ModelXbrl) -> set[ModelInlineFact]:
295
388
  return self.checkHiddenElements(modelXbrl).eligibleForTransformHiddenFacts
296
389
 
@@ -321,8 +414,19 @@ class PluginValidationDataExtension(PluginData):
321
414
  )
322
415
 
323
416
  @lru_cache(1)
324
- def getFilenameParts(self, filename: str) -> dict[str, Any] | None:
325
- match = self.getFilenameFormatPattern().match(filename)
417
+ def getExtensionFilenameFormatPattern(self) -> re.Pattern[str]:
418
+ return re.compile(
419
+ r"^(?<base>[^-]*)"
420
+ r"-(?<year>\d{4})-(?<month>0[1-9]|1[012])-(?<day>0?[1-9]|[12][0-9]|3[01])"
421
+ r"(?<suffix>[_pre|_cal|_lab|_def]*)"
422
+ r"(?<lang>-*[^-]*)"
423
+ r"\.(?<extension>xsd|xml)$",
424
+ flags=re.ASCII
425
+ )
426
+
427
+ @lru_cache(1)
428
+ def getFilenameParts(self, filename: str, filenamePattern: re.Pattern[str]) -> dict[str, Any] | None:
429
+ match = filenamePattern.match(filename)
326
430
  if match:
327
431
  return match.groupdict()
328
432
  return None
@@ -331,6 +435,56 @@ class PluginValidationDataExtension(PluginData):
331
435
  def getIxdsDocBasenames(self, modelXbrl: ModelXbrl) -> set[str]:
332
436
  return set(Path(url).name for url in getattr(modelXbrl, "ixdsDocUrls", []))
333
437
 
438
+ def getExtensionConcepts(self, modelXbrl: ModelXbrl) -> list[ModelConcept]:
439
+ """
440
+ Returns a list of extension concepts in the DTS.
441
+ """
442
+ extensionConcepts = []
443
+ for concepts in modelXbrl.nameConcepts.values():
444
+ for concept in concepts:
445
+ if self.isExtensionUri(concept.qname.namespaceURI, modelXbrl):
446
+ extensionConcepts.append(concept)
447
+ return extensionConcepts
448
+
449
+ @lru_cache(1)
450
+ def getExtensionData(self, modelXbrl: ModelXbrl) -> ExtensionData:
451
+ extensionDocuments = {}
452
+ extensionImportedUrls = set()
453
+ documentsInDts = self.getDocumentsInDts(modelXbrl)
454
+ for modelDocument, hrefXlinkRole in documentsInDts.items():
455
+ if not self.isExtensionUri(modelDocument.uri, modelDocument.modelXbrl):
456
+ # Skip non-extension documents
457
+ continue
458
+ if modelDocument.type in (ModelDocumentType.LINKBASE, ModelDocumentType.SCHEMA):
459
+ extensionDocuments[modelDocument] = ExtensionDocumentData(
460
+ basename=modelDocument.basename,
461
+ hrefXlinkRole=hrefXlinkRole,
462
+ linkbases=self.getLinkbaseData(modelDocument),
463
+ )
464
+ if modelDocument.type == ModelDocumentType.SCHEMA:
465
+ for doc, docRef in modelDocument.referencesDocument.items():
466
+ if "import" in docRef.referenceTypes:
467
+ extensionImportedUrls.add(doc.uri)
468
+ return ExtensionData(
469
+ extensionConcepts=self.getExtensionConcepts(modelXbrl),
470
+ extensionDocuments=extensionDocuments,
471
+ extensionImportedUrls=frozenset(sorted(extensionImportedUrls)),
472
+ )
473
+
474
+ def getLinkbaseData(self, modelDocument: ModelDocument) -> list[LinkbaseData]:
475
+ linkbases = []
476
+ for linkbaseType in LinkbaseType:
477
+ for linkElt in modelDocument.xmlRootElement.iterdescendants(tag=linkbaseType.getLinkQn().clarkNotation):
478
+ arcQn = linkbaseType.getArcQn()
479
+ arcs = list(linkElt.iterdescendants(tag=arcQn.clarkNotation))
480
+ linkbases.append(LinkbaseData(
481
+ arcs=arcs,
482
+ basename=modelDocument.basename,
483
+ element=linkElt,
484
+ linkbaseType=linkbaseType,
485
+ ))
486
+ return linkbases
487
+
334
488
  def getNoMatchLangFootnotes(self, modelXbrl: ModelXbrl) -> set[ModelInlineFootnote]:
335
489
  return self.checkInlineHTMLElements(modelXbrl).noMatchLangFootnotes
336
490
 
@@ -346,6 +500,14 @@ class PluginValidationDataExtension(PluginData):
346
500
  def getTupleElements(self, modelXbrl: ModelXbrl) -> set[tuple[Any]]:
347
501
  return self.checkInlineHTMLElements(modelXbrl).tupleElements
348
502
 
503
+ @lru_cache(1)
504
+ def getReportingPeriod(self, modelXbrl: ModelXbrl) -> str | None:
505
+ reportingPeriodFacts = modelXbrl.factsByQname.get(self.financialReportingPeriodQn, set())
506
+ for fact in reportingPeriodFacts:
507
+ if fact.xValid >= VALID:
508
+ return cast(str, fact.xValue)
509
+ return None
510
+
349
511
  @lru_cache(1)
350
512
  def getReportXmlLang(self, modelXbrl: ModelXbrl) -> str | None:
351
513
  reportXmlLang = None
@@ -365,13 +527,20 @@ class PluginValidationDataExtension(PluginData):
365
527
  def getTargetElements(self, modelXbrl: ModelXbrl) -> list[Any]:
366
528
  targetElements = []
367
529
  for ixdsHtmlRootElt in modelXbrl.ixdsHtmlElements:
368
- ixNStag = getattr(ixdsHtmlRootElt.modelDocument, "ixNStag", ixbrl11)
530
+ ixNStag = str(getattr(ixdsHtmlRootElt.modelDocument, "ixNStag", ixbrl11))
369
531
  ixTags = set(ixNStag + ln for ln in ("nonNumeric", "nonFraction", "references", "relationship"))
370
532
  for elt, depth in etreeIterWithDepth(ixdsHtmlRootElt):
371
533
  if elt.tag in ixTags and elt.get("target"):
372
534
  targetElements.append(elt)
373
535
  return targetElements
374
536
 
537
+ def isExtensionUri(self, uri: str, modelXbrl: ModelXbrl) -> bool:
538
+ if uri.startswith(modelXbrl.uriDir):
539
+ return True
540
+ if not any(uri.startswith(taxonomyUri) for taxonomyUri in STANDARD_TAXONOMY_URLS):
541
+ return True
542
+ return False
543
+
375
544
  @lru_cache(1)
376
545
  def isFilenameValidCharacters(self, filename: str) -> bool:
377
546
  match = self.getFilenameAllowedCharactersPattern().match(filename)
@@ -186,6 +186,7 @@ class ValidationPluginExtension(ValidationPlugin):
186
186
  documentResubmissionUnsurmountableInaccuraciesQn=qname(f'{{{kvkINamespace}}}DocumentResubmissionDueToUnsurmountableInaccuracies'),
187
187
  entrypointRoot=entrypointRoot,
188
188
  entrypoints=entrypoints,
189
+ financialReportingPeriodQn=qname(f'{{{jenvNamespace}}}FinancialReportingPeriod'),
189
190
  financialReportingPeriodCurrentStartDateQn=qname(f'{{{jenvNamespace}}}FinancialReportingPeriodCurrentStartDate'),
190
191
  financialReportingPeriodCurrentEndDateQn=qname(f'{{{jenvNamespace}}}FinancialReportingPeriodCurrentEndDate'),
191
192
  financialReportingPeriodPreviousStartDateQn=qname(f'{{{jenvNamespace}}}FinancialReportingPeriodPreviousStartDate'),
@@ -43,8 +43,8 @@ def disclosureSystemConfigURL(*args: Any, **kwargs: Any) -> str:
43
43
  def modelXbrlLoadComplete(*args: Any, **kwargs: Any) -> ModelDocument | LoadingException | None:
44
44
  return validationPlugin.modelXbrlLoadComplete(*args, **kwargs)
45
45
 
46
- def validateXbrlStart(val: ValidateXbrl, parameters: dict[Any, Any], *args: Any, **kwargs: Any) -> None:
47
- val.extensionImportedUrls = set()
46
+ def validateFinally(*args: Any, **kwargs: Any) -> None:
47
+ return validationPlugin.validateFinally(*args, **kwargs)
48
48
 
49
49
  def validateXbrlFinally(*args: Any, **kwargs: Any) -> None:
50
50
  return validationPlugin.validateXbrlFinally(*args, **kwargs)
@@ -61,6 +61,6 @@ __pluginInfo__ = {
61
61
  "DisclosureSystem.Types": disclosureSystemTypes,
62
62
  "DisclosureSystem.ConfigURL": disclosureSystemConfigURL,
63
63
  "ModelXbrl.LoadComplete": modelXbrlLoadComplete,
64
- "Validate.XBRL.Start": validateXbrlStart,
65
64
  "Validate.XBRL.Finally": validateXbrlFinally,
65
+ "ValidateFormula.Finished": validateFinally,
66
66
  }