arelle-release 2.37.18__py3-none-any.whl → 2.37.20__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 (59) hide show
  1. arelle/CntlrWinMain.py +4 -1
  2. arelle/FileSource.py +2 -2
  3. arelle/ModelInstanceObject.py +1 -1
  4. arelle/ModelValue.py +1 -0
  5. arelle/ModelXbrl.py +1 -1
  6. arelle/PythonUtil.py +132 -24
  7. arelle/ViewWinTree.py +15 -9
  8. arelle/_version.py +2 -2
  9. arelle/formula/XPathContext.py +22 -22
  10. arelle/formula/XPathParser.py +2 -2
  11. arelle/plugin/OimTaxonomy/ModelValueMore.py +15 -0
  12. arelle/plugin/OimTaxonomy/ValidateDTS.py +484 -0
  13. arelle/plugin/OimTaxonomy/ViewXbrlTxmyObj.py +239 -0
  14. arelle/plugin/OimTaxonomy/XbrlAbstract.py +16 -0
  15. arelle/plugin/OimTaxonomy/XbrlConcept.py +68 -0
  16. arelle/plugin/OimTaxonomy/XbrlConst.py +261 -0
  17. arelle/plugin/OimTaxonomy/XbrlCube.py +91 -0
  18. arelle/plugin/OimTaxonomy/XbrlDimension.py +38 -0
  19. arelle/plugin/OimTaxonomy/XbrlDts.py +152 -0
  20. arelle/plugin/OimTaxonomy/XbrlEntity.py +16 -0
  21. arelle/plugin/OimTaxonomy/XbrlGroup.py +22 -0
  22. arelle/plugin/OimTaxonomy/XbrlImportedTaxonomy.py +22 -0
  23. arelle/plugin/OimTaxonomy/XbrlLabel.py +31 -0
  24. arelle/plugin/OimTaxonomy/XbrlNetwork.py +100 -0
  25. arelle/plugin/OimTaxonomy/XbrlProperty.py +28 -0
  26. arelle/plugin/OimTaxonomy/XbrlReference.py +33 -0
  27. arelle/plugin/OimTaxonomy/XbrlReport.py +24 -0
  28. arelle/plugin/OimTaxonomy/XbrlTableTemplate.py +35 -0
  29. arelle/plugin/OimTaxonomy/XbrlTaxonomy.py +93 -0
  30. arelle/plugin/OimTaxonomy/XbrlTaxonomyObject.py +154 -0
  31. arelle/plugin/OimTaxonomy/XbrlTransform.py +17 -0
  32. arelle/plugin/OimTaxonomy/XbrlTypes.py +23 -0
  33. arelle/plugin/OimTaxonomy/XbrlUnit.py +17 -0
  34. arelle/plugin/OimTaxonomy/__init__.py +1037 -0
  35. arelle/plugin/OimTaxonomy/resources/iso4217.json +4479 -0
  36. arelle/plugin/OimTaxonomy/resources/oim-taxonomy-schema.json +935 -0
  37. arelle/plugin/OimTaxonomy/resources/ref.json +333 -0
  38. arelle/plugin/OimTaxonomy/resources/transform-types.json +2481 -0
  39. arelle/plugin/OimTaxonomy/resources/types.json +727 -0
  40. arelle/plugin/OimTaxonomy/resources/utr.json +3046 -0
  41. arelle/plugin/OimTaxonomy/resources/xbrlSpec.json +1082 -0
  42. arelle/plugin/saveOIMTaxonomy.py +311 -0
  43. arelle/plugin/validate/NL/PluginValidationDataExtension.py +35 -1
  44. arelle/plugin/validate/NL/__init__.py +7 -0
  45. arelle/plugin/validate/NL/rules/nl_kvk.py +144 -2
  46. {arelle_release-2.37.18.dist-info → arelle_release-2.37.20.dist-info}/METADATA +3 -1
  47. {arelle_release-2.37.18.dist-info → arelle_release-2.37.20.dist-info}/RECORD +59 -24
  48. tests/integration_tests/validation/README.md +2 -0
  49. tests/integration_tests/validation/conformance_suite_configurations/nl_inline_2024.py +19 -5
  50. tests/integration_tests/validation/run_conformance_suites.py +10 -1
  51. tests/integration_tests/validation/validation_util.py +10 -5
  52. tests/unit_tests/arelle/test_frozen_dict.py +176 -0
  53. tests/unit_tests/arelle/test_frozen_ordered_set.py +315 -0
  54. tests/unit_tests/arelle/test_import.py +1 -0
  55. tests/unit_tests/arelle/test_ordered_set.py +272 -0
  56. {arelle_release-2.37.18.dist-info → arelle_release-2.37.20.dist-info}/WHEEL +0 -0
  57. {arelle_release-2.37.18.dist-info → arelle_release-2.37.20.dist-info}/entry_points.txt +0 -0
  58. {arelle_release-2.37.18.dist-info → arelle_release-2.37.20.dist-info}/licenses/LICENSE.md +0 -0
  59. {arelle_release-2.37.18.dist-info → arelle_release-2.37.20.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,311 @@
1
+ # -*- coding: utf-8 -*-
2
+ '''
3
+ saveOIMTaxonomy.py is a plug-in that saves an extension taxonomy in the json OIM taxonomy format
4
+
5
+ See COPYRIGHT.md for copyright information.
6
+ '''
7
+ import os, io, json
8
+ from arelle.Version import authorLabel, copyrightLabel
9
+ from arelle import XbrlConst
10
+ from collections import OrderedDict
11
+
12
+ jsonDocumentType = "https://xbrl.org/PWD/2023-05-17/cti"
13
+ jsonTxmyVersion = "1.0"
14
+ primaryLang = "en"
15
+
16
+ excludeImportNamespaces = {XbrlConst.xbrli, XbrlConst.xbrldt}
17
+
18
+ def saveOIMTaxonomy(dts, jsonFile):
19
+ from arelle import ModelDocument, XmlUtil
20
+
21
+ # identify extension schema
22
+ namespacePrefixes = dict((ns, prefix) for prefix, ns in dts.prefixedNamespaces.items())
23
+ namespacesInUse = set()
24
+ extensionSchemaDoc = None
25
+ if dts.modelDocument.type == ModelDocument.Type.SCHEMA:
26
+ extensionSchemaDoc = dts.modelDocument
27
+ elif dts.modelDocument.type in (ModelDocument.Type.INSTANCE, ModelDocument.Type.INLINEXBRL, ModelDocument.Type.INLINEXBRLDOCUMENTSET):
28
+ for doc, docReference in dts.modelDocument.referencesDocument.items():
29
+ if "href" in docReference.referenceTypes:
30
+ extensionSchemaDoc = doc
31
+ extensionPrefix = namespacePrefixes[extensionSchemaDoc.targetNamespace]
32
+ namespacesInUse.add(extensionSchemaDoc.targetNamespace)
33
+ break
34
+ if extensionSchemaDoc is None:
35
+ dts.info("error:saveLoadableOIMTaxonomy",
36
+ _("Unable to identify extension taxonomy."),
37
+ modelObject=dts)
38
+ return
39
+
40
+ oimTxmy = OrderedDict()
41
+ oimTxmy["documentInfo"] = docInfo = OrderedDict()
42
+ docInfo["documentType"] = jsonDocumentType
43
+ docInfo["namespaces"] = namespaces = []
44
+ oimTxmy["taxonomy"] = txmy = OrderedDict()
45
+
46
+ # provide consistent order to taxonomy properties and objects
47
+ txmy["name"] = os.path.splitext(extensionSchemaDoc.basename)[0]
48
+ txmy["namespace"] = extensionSchemaDoc.targetNamespace
49
+ txmy["version"] = jsonTxmyVersion
50
+ txmy["entryPoint"] = os.path.basename(jsonFile)
51
+ txmy["importedTaxonomies"] = imports = []
52
+ txmy["concepts"] = concepts = []
53
+ txmy["cubes"] = cubes = []
54
+ txmy["domains"] = domains = []
55
+ txmy["networks"] = networks = []
56
+ txmy["relationships"] = relationships = []
57
+ txmy["labels"] = labels = []
58
+ sharedLabelRefs = {}
59
+ txmy["references"] = references = []
60
+
61
+ # taxonomy object
62
+ extensionLinkbaseDocs = {doc
63
+ for doc, docReference in extensionSchemaDoc.referencesDocument.items()
64
+ if "href" in docReference.referenceTypes and docReference.referringModelObject.elementQname == XbrlConst.qnLinkLinkbaseRef}
65
+ extensionLinkbaseDocs.add(extensionSchemaDoc)
66
+
67
+ labelsRelationshipSet = dts.relationshipSet(XbrlConst.conceptLabel)
68
+ for concept in sorted(set(dts.qnameConcepts.values()), key=lambda c:c.name): # may be twice if unqualified, with and without namespace
69
+ if concept.modelDocument == extensionSchemaDoc:
70
+ c = OrderedDict()
71
+ concepts.append(c)
72
+ c["name"] = str(concept.qname)
73
+ if concept.typeQname: # may be absent
74
+ c["dataType"] = str(concept.typeQname)
75
+ if concept.substitutionGroupQname:
76
+ c["substitutionGroup"] = str(concept.substitutionGroupQname)
77
+ if concept.periodType:
78
+ c["periodType"] = concept.periodType
79
+ if concept.balance:
80
+ c["balance"] = concept.balance
81
+ c["abstract"] = str(concept.abstract).lower()
82
+ c["nillable"] = str(concept.nillable).lower()
83
+ conceptLabels = dict(((lbl.role, lbl.xmlLang), lbl)
84
+ for rel in labelsRelationshipSet.fromModelObject(concept)
85
+ for lbl in (rel.toModelObject,)
86
+ if lbl is not None)
87
+ for _key, label in sorted(conceptLabels.items(), key=lambda i:i[0]):
88
+ labelDupKey = (label.xmlLang, label.role, label.textValue)
89
+ if labelDupKey in sharedLabelRefs:
90
+ sharedLabelRefs[labelDupKey]["relatedId"].append(str(concept.qname))
91
+ else:
92
+ l = OrderedDict((
93
+ ("relatedId", [str(concept.qname)]),
94
+ ("language", label.xmlLang),
95
+ ("labelType", os.path.basename(label.role)),
96
+ ("value", label.textValue)))
97
+ labels.append(l)
98
+ sharedLabelRefs[labelDupKey] = l
99
+ # TBD add references here
100
+
101
+ # extended link roles defined in this document
102
+ networkOrder = 0
103
+ for roleURI, roleTypes in sorted(dts.roleTypes.items(),
104
+ # sort on definition if any else URI
105
+ key=lambda item: (item[1][0].definition if len(item[1]) and item[1][0].definition else item[0])):
106
+ for roleType in roleTypes:
107
+ if roleType.modelDocument == extensionSchemaDoc:
108
+ name = f"{extensionPrefix}:_{os.path.basename(roleURI)}_"
109
+ definition = roleType.definition
110
+ # define network concept (not in XBRL 2.1
111
+ c = OrderedDict((("name", name),
112
+ ("dataType","dtr:networkItemType"),
113
+ ("periodType", "duration"),
114
+ ("substitutionGroup", "xbrli:item"),
115
+ ("abstract", True),
116
+ ("nillable", True)))
117
+ concepts.append(c)
118
+ networks.append( OrderedDict((
119
+ ("networkURI", roleType.roleURI),
120
+ ("name", name))))
121
+ networkOrder += 1
122
+ relationships.append( OrderedDict((
123
+ ("source", "xbrli:networkConcept"),
124
+ ("target", name),
125
+ ("order", networkOrder),
126
+ ("networkURI", "https://www.xbrl.org/Network"),
127
+ ("relationshipType", "root-child"))))
128
+ if definition:
129
+ labels.append( OrderedDict((
130
+ ("relatedId", [name]),
131
+ ("language", primaryLang),
132
+ ("labelType", XbrlConst.standardLabel),
133
+ ("value", definition))))
134
+
135
+ # cubes by linkrole
136
+ for arcrole, linkrole, arcQN, linkQN in dts.baseSets.keys():
137
+ if arcrole == XbrlConst.all and linkrole is not None and arcQN is None and linkQN is None:
138
+ for rel in dts.relationshipSet(XbrlConst.all, linkrole).modelRelationships:
139
+ if rel.modelDocument not in extensionLinkbaseDocs:
140
+ continue
141
+ hc = rel.toModelObject
142
+ priItem = rel.fromModelObject
143
+ if hc is not None and priItem is not None:
144
+ dims = []
145
+ dim = OrderedDict((("dimensionConcept", "xbrl:PrimaryDimension"),
146
+ ("domainID", priItem.name),
147
+ ("dimensionType", "xbrl:concept")))
148
+ dims.append(dim)
149
+ # priItem domains
150
+ rels = []
151
+ domains.append( OrderedDict((
152
+ ("domainId", priItem.name),
153
+ ("domainConcept", str(priItem.qname)),
154
+ ("networkURI", rel.linkrole),
155
+ ("relationships", rels))) )
156
+ for domRel in dts.relationshipSet(XbrlConst.domainMember, rel.consecutiveLinkrole).fromModelObject(priItem):
157
+ domObj = domRel.toModelObject
158
+ if domObj is not None:
159
+ rels.append( OrderedDict((
160
+ ("source", str(priItem.qname)),
161
+ ("target", str(domObj.qname)),
162
+ ("order", domRel.order))) )
163
+ # dimension domains
164
+ for dimRel in dts.relationshipSet(XbrlConst.hypercubeDimension, rel.consecutiveLinkrole).fromModelObject(hc):
165
+ dimObj = dimRel.toModelObject
166
+ if dimObj is not None:
167
+ dim = OrderedDict((("dimensionConcept", str(dimObj.qname)),
168
+ ("domainId", dimObj.name),
169
+ ("dimensionType", "explicit")))
170
+ dims.append(dim)
171
+ domRels = []
172
+ domains.append( OrderedDict((
173
+ ("domainId", dimObj.name),
174
+ ("domainConcept", str(dimObj.qname)),
175
+ ("networkURI", rel.linkrole),
176
+ ("relationships", domRels))) )
177
+ for domRel in dts.relationshipSet(XbrlConst.dimensionDomain, rel.consecutiveLinkrole).fromModelObject(dimObj):
178
+ domObj = domRel.toModelObject
179
+ if domObj is not None:
180
+ domRels.append( OrderedDict((
181
+ ("source", str(dimObj.qname)),
182
+ ("target", str(domObj.qname)),
183
+ ("order", domRel.order))) )
184
+ cubes.append( OrderedDict((
185
+ ("name", str(hc.qname)),
186
+ ("networkURI", rel.linkrole),
187
+ ("cubeType", "aggregate"),
188
+ ("dimensions", dims))) )
189
+
190
+ # domains
191
+
192
+ # tree walk recursive function
193
+ def treeWalk(depth, concept, arcrole, relSet, visited):
194
+ if concept is not None:
195
+ if concept not in visited:
196
+ visited.add(concept)
197
+ for rel in relSet.fromModelObject(concept):
198
+ toConcept = rel.toModelObject
199
+ if toConcept is not None:
200
+ namespacesInUse.add(concept.qname.namespaceURI)
201
+ namespacesInUse.add(toConcept.qname.namespaceURI)
202
+ if rel.modelDocument in extensionLinkbaseDocs:
203
+ relationships.append( OrderedDict((
204
+ ("source", str(concept.qname)),
205
+ ("target", str(toConcept.qname)),
206
+ ("order", rel.order),
207
+ ("networkURI", rel.linkrole),
208
+ ("relationshipType", os.path.basename(rel.arcrole))) +
209
+ ((("weight", rel.weight),) if rel.weight is not None else ())
210
+ ) )
211
+ row = treeWalk(depth + 1, toConcept, arcrole, relSet, visited)
212
+ visited.remove(concept)
213
+
214
+ # use presentation relationships for conceptsWs
215
+ for arcrole in (XbrlConst.parentChild,) + XbrlConst.summationItems:
216
+ # sort URIs by definition
217
+ linkroleUris = []
218
+ relationshipSet = dts.relationshipSet(arcrole)
219
+ if relationshipSet:
220
+ for linkroleUri in relationshipSet.linkRoleUris:
221
+ modelRoleTypes = dts.roleTypes.get(linkroleUri)
222
+ if modelRoleTypes:
223
+ roledefinition = (modelRoleTypes[0].genLabel(strip=True) or modelRoleTypes[0].definition or linkroleUri)
224
+ else:
225
+ roledefinition = linkroleUri
226
+ linkroleUris.append((roledefinition, linkroleUri))
227
+ linkroleUris.sort()
228
+
229
+ # for each URI in definition order
230
+ for roledefinition, linkroleUri in linkroleUris:
231
+ # elr relationships for tree walk
232
+ linkRelationshipSet = dts.relationshipSet(arcrole, linkroleUri)
233
+ for rootConcept in linkRelationshipSet.rootConcepts:
234
+ # is root child required?
235
+ treeWalk(0, rootConcept, arcrole, linkRelationshipSet, set())
236
+
237
+ for ns in sorted(namespacesInUse, key=lambda ns: namespacePrefixes[ns]):
238
+ namespaces.append(OrderedDict((("prefix", namespacePrefixes[ns]),
239
+ ("uri", ns))))
240
+
241
+ try:
242
+ with open(jsonFile, "w") as fh:
243
+ fh.write(json.dumps(oimTxmy, indent=3))
244
+
245
+ dts.info("info:saveOIMTaxonomy",
246
+ _("Saved OIM Taxonomy file: %(excelFile)s"),
247
+ excelFile=os.path.basename(jsonFile),
248
+ modelXbrl=dts)
249
+ except Exception as ex:
250
+ dts.error("exception:saveOIMTaxonomy",
251
+ _("File saving exception: %(error)s"), error=ex,
252
+ modelXbrl=dts)
253
+
254
+ def saveOIMTaxonomyMenuEntender(cntlr, menu, *args, **kwargs):
255
+ # Extend menu with an item for the savedts plugin
256
+ menu.add_command(label="Save OIM Taxonomy",
257
+ underline=0,
258
+ command=lambda: saveOIMTaxonomyMenuCommand(cntlr) )
259
+
260
+ def saveOIMTaxonomyMenuCommand(cntlr):
261
+ # save DTS menu item has been invoked
262
+ if cntlr.modelManager is None or cntlr.modelManager.modelXbrl is None:
263
+ cntlr.addToLog("No taxonomy loaded.")
264
+ return
265
+ # get file name into which to save log file while in foreground thread
266
+ jsonFile = cntlr.uiFileDialog("save",
267
+ title=_("arelle - Save OIM Taxonomy file"),
268
+ initialdir=cntlr.config.setdefault("OIMTaxonomyFileDir","."),
269
+ filetypes=[(_("OIM Taxonomy .json"), "*.json")],
270
+ defaultextension=".json")
271
+ if not jsonFile:
272
+ return False
273
+ import os
274
+ cntlr.config["OIMTaxonomyFileDir"] = os.path.dirname(jsonFile)
275
+ cntlr.saveConfig()
276
+
277
+ import threading
278
+ thread = threading.Thread(target=lambda
279
+ _dts=cntlr.modelManager.modelXbrl,
280
+ _jsonFile=jsonFile:
281
+ saveOIMTaxonomy(_dts, _jsonFile))
282
+ thread.daemon = True
283
+ thread.start()
284
+
285
+ def saveOIMTaxonomyCommandLineOptionExtender(parser, *args, **kwargs):
286
+ # extend command line options with a save DTS option
287
+ parser.add_option("--save-OIM-taxonomy",
288
+ dest="saveOIMTaxonomy",
289
+ help=_("Save OIM Taxonomy file"))
290
+
291
+ def saveOIMTaxonomyCommandLineXbrlRun(cntlr, options, modelXbrl, *args, **kwargs):
292
+ # extend XBRL-loaded run processing for this option
293
+ jsonFile = getattr(options, "saveOIMTaxonomy", None)
294
+ if jsonFile:
295
+ if cntlr.modelManager is None or cntlr.modelManager.modelXbrl is None:
296
+ cntlr.addToLog("No taxonomy loaded.")
297
+ return
298
+ saveOIMTaxonomy(cntlr.modelManager.modelXbrl, jsonFile)
299
+
300
+ __pluginInfo__ = {
301
+ 'name': 'Save OIM Taxonomy',
302
+ 'version': '0.9',
303
+ 'description': "This plug-in saves an OIM Taxonomy.",
304
+ 'license': 'Apache-2',
305
+ 'author': authorLabel,
306
+ 'copyright': copyrightLabel,
307
+ # classes of mount points (required)
308
+ 'CntlrWinMain.Menu.Tools': saveOIMTaxonomyMenuEntender,
309
+ 'CntlrCmdLine.Options': saveOIMTaxonomyCommandLineOptionExtender,
310
+ 'CntlrCmdLine.Xbrl.Run': saveOIMTaxonomyCommandLineXbrlRun,
311
+ }
@@ -13,6 +13,8 @@ import regex as re
13
13
  from lxml.etree import _Comment, _ElementTree, _Entity, _ProcessingInstruction
14
14
 
15
15
  from arelle.FunctionIxt import ixtNamespaces
16
+ from arelle import ModelDocument as ModelDocumentFile
17
+ from arelle.ModelDocument import ModelDocument
16
18
  from arelle.ModelInstanceObject import ModelContext, ModelFact, ModelInlineFootnote, ModelUnit, ModelInlineFact
17
19
  from arelle.ModelObject import ModelObject
18
20
  from arelle.ModelValue import QName
@@ -20,13 +22,14 @@ from arelle.ModelXbrl import ModelXbrl
20
22
  from arelle.typing import assert_type
21
23
  from arelle.utils.PluginData import PluginData
22
24
  from arelle.utils.validate.ValidationUtil import etreeIterWithDepth
25
+ from arelle.ValidateXbrl import ValidateXbrl
23
26
  from arelle.XbrlConst import ixbrl11, xhtmlBaseIdentifier, xmlBaseIdentifier
24
27
  from arelle.XmlValidate import lexicalPatterns
25
28
  from arelle.XmlValidateConst import VALID
26
29
 
27
30
  XBRLI_IDENTIFIER_PATTERN = re.compile(r"^(?!00)\d{8}$")
28
31
  XBRLI_IDENTIFIER_SCHEMA = 'http://www.kvk.nl/kvk-id'
29
-
32
+ MAX_REPORT_PACKAGE_SIZE_MBS = 100
30
33
 
31
34
  DISALLOWED_IXT_NAMESPACES = frozenset((
32
35
  ixtNamespaces["ixt v1"],
@@ -54,6 +57,11 @@ ALLOWABLE_LANGUAGES = frozenset((
54
57
  'fr'
55
58
  ))
56
59
 
60
+ EFFECTIVE_TAXONOMY_URLS = frozenset((
61
+ 'https://www.nltaxonomie.nl/kvk/2024-12-31/kvk-annual-report-nlgaap-ext.xsd',
62
+ 'https://www.nltaxonomie.nl/kvk/2024-12-31/kvk-annual-report-ifrs-ext.xsd',
63
+ ))
64
+
57
65
  @dataclass(frozen=True)
58
66
  class ContextData:
59
67
  contextsWithImproperContent: list[ModelContext | None]
@@ -136,6 +144,21 @@ class PluginValidationDataExtension(PluginData):
136
144
  contextsWithSegments=contextsWithSegments,
137
145
  )
138
146
 
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
+
139
162
  @lru_cache(1)
140
163
  def checkHiddenElements(self, modelXbrl: ModelXbrl) -> HiddenElementsData:
141
164
  cssHiddenFacts = set()
@@ -338,6 +361,17 @@ class PluginValidationDataExtension(PluginData):
338
361
  firstRootmostXmlLangDepth = depth
339
362
  return reportXmlLang
340
363
 
364
+ @lru_cache(1)
365
+ def getTargetElements(self, modelXbrl: ModelXbrl) -> list[Any]:
366
+ targetElements = []
367
+ for ixdsHtmlRootElt in modelXbrl.ixdsHtmlElements:
368
+ ixNStag = getattr(ixdsHtmlRootElt.modelDocument, "ixNStag", ixbrl11)
369
+ ixTags = set(ixNStag + ln for ln in ("nonNumeric", "nonFraction", "references", "relationship"))
370
+ for elt, depth in etreeIterWithDepth(ixdsHtmlRootElt):
371
+ if elt.tag in ixTags and elt.get("target"):
372
+ targetElements.append(elt)
373
+ return targetElements
374
+
341
375
  @lru_cache(1)
342
376
  def isFilenameValidCharacters(self, filename: str) -> bool:
343
377
  match = self.getFilenameAllowedCharactersPattern().match(filename)
@@ -43,6 +43,11 @@ 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 validateFinally(*args: Any, **kwargs: Any) -> None:
47
+ return validationPlugin.validateFinally(*args, **kwargs)
48
+
49
+ def validateXbrlStart(val: ValidateXbrl, parameters: dict[Any, Any], *args: Any, **kwargs: Any) -> None:
50
+ val.extensionImportedUrls = set()
46
51
 
47
52
  def validateXbrlFinally(*args: Any, **kwargs: Any) -> None:
48
53
  return validationPlugin.validateXbrlFinally(*args, **kwargs)
@@ -59,5 +64,7 @@ __pluginInfo__ = {
59
64
  "DisclosureSystem.Types": disclosureSystemTypes,
60
65
  "DisclosureSystem.ConfigURL": disclosureSystemConfigURL,
61
66
  "ModelXbrl.LoadComplete": modelXbrlLoadComplete,
67
+ "Validate.XBRL.Start": validateXbrlStart,
62
68
  "Validate.XBRL.Finally": validateXbrlFinally,
69
+ "ValidateFormula.Finished": validateFinally,
63
70
  }
@@ -4,6 +4,7 @@ See COPYRIGHT.md for copyright information.
4
4
  from __future__ import annotations
5
5
 
6
6
  from datetime import date
7
+ import zipfile
7
8
 
8
9
  from arelle.ModelInstanceObject import ModelInlineFact
9
10
  from arelle.ValidateDuplicateFacts import getDuplicateFactSets
@@ -20,8 +21,10 @@ from arelle.utils.validate.Validation import Validation
20
21
  from arelle.ValidateDuplicateFacts import getHashEquivalentFactGroups, getAspectEqualFacts
21
22
  from arelle.utils.validate.ValidationUtil import etreeIterWithDepth
22
23
  from ..DisclosureSystems import DISCLOSURE_SYSTEM_NL_INLINE_2024
23
- from ..PluginValidationDataExtension import (PluginValidationDataExtension, XBRLI_IDENTIFIER_PATTERN,
24
- XBRLI_IDENTIFIER_SCHEMA, DISALLOWED_IXT_NAMESPACES, ALLOWABLE_LANGUAGES)
24
+ from ..PluginValidationDataExtension import (PluginValidationDataExtension, ALLOWABLE_LANGUAGES,
25
+ DISALLOWED_IXT_NAMESPACES, EFFECTIVE_TAXONOMY_URLS,
26
+ MAX_REPORT_PACKAGE_SIZE_MBS, XBRLI_IDENTIFIER_PATTERN,
27
+ XBRLI_IDENTIFIER_SCHEMA)
25
28
 
26
29
  if TYPE_CHECKING:
27
30
  from arelle.ModelXbrl import ModelXbrl
@@ -700,6 +703,30 @@ def rule_nl_kvk_3_5_2_3(
700
703
  )
701
704
 
702
705
 
706
+ @validation(
707
+ hook=ValidationHook.XBRL_FINALLY,
708
+ disclosureSystems=[
709
+ DISCLOSURE_SYSTEM_NL_INLINE_2024
710
+ ],
711
+ )
712
+ def rule_nl_kvk_3_5_3_1(
713
+ pluginData: PluginValidationDataExtension,
714
+ val: ValidateXbrl,
715
+ *args: Any,
716
+ **kwargs: Any,
717
+ ) -> Iterable[Validation]:
718
+ """
719
+ NL-KVK.3.5.3.1: The default target attribute MUST be used for the annual report content.
720
+ """
721
+ targetElements = pluginData.getTargetElements(val.modelXbrl)
722
+ if targetElements:
723
+ yield Validation.error(
724
+ codes='NL.NL-KVK.3.5.3.1.defaultTargetAttributeNotUsed',
725
+ msg=_('Target attribute must not be used for the annual report content.'),
726
+ modelObject=targetElements
727
+ )
728
+
729
+
703
730
  @validation(
704
731
  hook=ValidationHook.XBRL_FINALLY,
705
732
  disclosureSystems=[
@@ -818,3 +845,118 @@ def rule_nl_kvk_3_6_3_3(
818
845
  'Allowed characters include: A-Z, a-z, 0-9, underscore ( _ ), period ( . ), and hyphen ( - ). '
819
846
  'Update filing naming to review unallowed characters. '
820
847
  'Invalid filenames: %(invalidBasenames)s'))
848
+
849
+
850
+ @validation(
851
+ hook=ValidationHook.FINALLY,
852
+ disclosureSystems=[
853
+ DISCLOSURE_SYSTEM_NL_INLINE_2024
854
+ ],
855
+ )
856
+ def rule_nl_kvk_3_7_1_1(
857
+ pluginData: PluginValidationDataExtension,
858
+ val: ValidateXbrl,
859
+ *args: Any,
860
+ **kwargs: Any,
861
+ ) -> Iterable[Validation]:
862
+ """
863
+ NL-KVK.3.7.1.1: The filing MUST be valid against the formula linkbase assertions with error severity.
864
+ """
865
+ modelXbrl = val.modelXbrl
866
+ sumErrMsgs = 0
867
+ for e in modelXbrl.errors:
868
+ if isinstance(e,dict):
869
+ for id, (numSat, numUnsat, numOkMsgs, numWrnMsgs, numErrMsgs) in e.items():
870
+ sumErrMsgs += numErrMsgs
871
+ if sumErrMsgs > 0:
872
+ yield Validation.error(
873
+ codes='NL.NL-KVK.3.7.1.1.targetXBRLDocumentWithFormulaErrors',
874
+ msg=_("The filing is not valid against the formula linkbase assertions with error severity. Address the %(numUnsatisfied)s unresolved formula linkbase validation errors."),
875
+ modelObject=modelXbrl,
876
+ numUnsatisfied=sumErrMsgs
877
+ )
878
+
879
+
880
+ @validation(
881
+ hook=ValidationHook.FINALLY,
882
+ disclosureSystems=[
883
+ DISCLOSURE_SYSTEM_NL_INLINE_2024
884
+ ],
885
+ )
886
+ def rule_nl_kvk_3_7_1_2(
887
+ pluginData: PluginValidationDataExtension,
888
+ val: ValidateXbrl,
889
+ *args: Any,
890
+ **kwargs: Any,
891
+ ) -> Iterable[Validation]:
892
+ """
893
+ NL-KVK.3.7.1.2: The filing MUST be valid against the formula linkbase assertions with error warning.
894
+ """
895
+ modelXbrl = val.modelXbrl
896
+ sumWrnMsgs = 0
897
+ for e in modelXbrl.errors:
898
+ if isinstance(e,dict):
899
+ for id, (numSat, numUnsat, numOkMsgs, numWrnMsgs, numErrMsgs) in e.items():
900
+ sumWrnMsgs += numWrnMsgs
901
+ if sumWrnMsgs > 0:
902
+ yield Validation.warning(
903
+ codes='NL.NL-KVK.3.7.1.2.targetXBRLDocumentWithFormulaWarnings',
904
+ msg=_("The filing is not valid against the formula linkbase assertions with warning severity. Address the %(numUnsatisfied)s unresolved formula linkbase validation warnings."),
905
+ modelObject=modelXbrl,
906
+ numUnsatisfied=sumWrnMsgs
907
+ )
908
+
909
+
910
+ @validation(
911
+ hook=ValidationHook.XBRL_FINALLY,
912
+ disclosureSystems=[
913
+ DISCLOSURE_SYSTEM_NL_INLINE_2024
914
+ ],
915
+ )
916
+ def rule_nl_kvk_4_1_2_1(
917
+ pluginData: PluginValidationDataExtension,
918
+ val: ValidateXbrl,
919
+ *args: Any,
920
+ **kwargs: Any,
921
+ ) -> Iterable[Validation]:
922
+ """
923
+ NL-KVK.4.1.2.1: Validate that the imported taxonomy matches the KVK-specified entry point.
924
+ - https://www.nltaxonomie.nl/kvk/2024-12-31/kvk-annual-report-nlgaap-ext.xsd,
925
+ - https://www.nltaxonomie.nl/kvk/2024-12-31/kvk-annual-report-ifrs-ext.xsd.
926
+ """
927
+ if val.modelXbrl.modelDocument is not None:
928
+ pluginData.checkFilingDTS(val, val.modelXbrl.modelDocument, [])
929
+ if not any(e in val.extensionImportedUrls for e in EFFECTIVE_TAXONOMY_URLS):
930
+ yield Validation.error(
931
+ codes='NL.NL-KVK.4.1.2.1.requiredEntryPointNotImported',
932
+ msg=_('The extension taxonomy must import the entry point of the taxonomy files prepared by KVK.'),
933
+ modelObject=val.modelXbrl.modelDocument
934
+ )
935
+
936
+
937
+ @validation(
938
+ hook=ValidationHook.XBRL_FINALLY,
939
+ disclosureSystems=[
940
+ DISCLOSURE_SYSTEM_NL_INLINE_2024
941
+ ],
942
+ )
943
+ def rule_nl_kvk_6_1_1_1(
944
+ pluginData: PluginValidationDataExtension,
945
+ val: ValidateXbrl,
946
+ *args: Any,
947
+ **kwargs: Any,
948
+ ) -> Iterable[Validation]:
949
+ """
950
+ NL-KVK.6.1.1.1: The size of the report package MUST NOT exceed 100 MB.
951
+ """
952
+ if val.modelXbrl.fileSource.fs and isinstance(val.modelXbrl.fileSource.fs, zipfile.ZipFile):
953
+ maxMB = float(MAX_REPORT_PACKAGE_SIZE_MBS)
954
+ # The following code computes report package size by adding the compressed file sizes within the package.
955
+ # This method of computation is over 99% accurate and gets more accurate the larger the filesize is.
956
+ _size = sum(zi.compress_size for zi in val.modelXbrl.fileSource.fs.infolist())
957
+ if _size > maxMB * 1000000:
958
+ yield Validation.error(
959
+ codes='NL.NL-KVK.6.1.1.1.reportPackageMaximumSizeExceeded',
960
+ msg=_('The size of the report package must not exceed %(maxSize)s MBs, size is %(size)s MBs.'),
961
+ modelObject=val.modelXbrl, maxSize=MAX_REPORT_PACKAGE_SIZE_MBS, size=int(_size/1000000)
962
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arelle-release
3
- Version: 2.37.18
3
+ Version: 2.37.20
4
4
  Summary: An open source XBRL platform.
5
5
  Author-email: "arelle.org" <support@arelle.org>
6
6
  License: Apache-2.0
@@ -32,6 +32,7 @@ License-File: LICENSE.md
32
32
  Requires-Dist: bottle<0.14,>=0.13
33
33
  Requires-Dist: certifi
34
34
  Requires-Dist: filelock
35
+ Requires-Dist: jsonschema==4.*
35
36
  Requires-Dist: isodate==0.*
36
37
  Requires-Dist: lxml<6,>=4
37
38
  Requires-Dist: numpy<3,>=1
@@ -40,6 +41,7 @@ Requires-Dist: pillow<12,>=10
40
41
  Requires-Dist: pyparsing==3.*
41
42
  Requires-Dist: python-dateutil==2.*
42
43
  Requires-Dist: regex
44
+ Requires-Dist: typing-extensions==4.*
43
45
  Provides-Extra: crypto
44
46
  Requires-Dist: pycryptodome==3.*; extra == "crypto"
45
47
  Provides-Extra: db