arelle-release 2.37.18__py3-none-any.whl → 2.37.19__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/CntlrWinMain.py +4 -1
- arelle/ModelValue.py +1 -0
- arelle/PythonUtil.py +132 -24
- arelle/ViewWinTree.py +15 -9
- arelle/_version.py +2 -2
- arelle/plugin/OimTaxonomy/ModelValueMore.py +15 -0
- arelle/plugin/OimTaxonomy/ValidateDTS.py +484 -0
- arelle/plugin/OimTaxonomy/ViewXbrlTxmyObj.py +240 -0
- arelle/plugin/OimTaxonomy/XbrlAbstract.py +16 -0
- arelle/plugin/OimTaxonomy/XbrlConcept.py +67 -0
- arelle/plugin/OimTaxonomy/XbrlConst.py +261 -0
- arelle/plugin/OimTaxonomy/XbrlCube.py +91 -0
- arelle/plugin/OimTaxonomy/XbrlDimension.py +38 -0
- arelle/plugin/OimTaxonomy/XbrlDts.py +152 -0
- arelle/plugin/OimTaxonomy/XbrlEntity.py +16 -0
- arelle/plugin/OimTaxonomy/XbrlGroup.py +22 -0
- arelle/plugin/OimTaxonomy/XbrlImportedTaxonomy.py +22 -0
- arelle/plugin/OimTaxonomy/XbrlLabel.py +31 -0
- arelle/plugin/OimTaxonomy/XbrlNetwork.py +100 -0
- arelle/plugin/OimTaxonomy/XbrlProperty.py +28 -0
- arelle/plugin/OimTaxonomy/XbrlReference.py +33 -0
- arelle/plugin/OimTaxonomy/XbrlReport.py +24 -0
- arelle/plugin/OimTaxonomy/XbrlTableTemplate.py +35 -0
- arelle/plugin/OimTaxonomy/XbrlTaxonomy.py +93 -0
- arelle/plugin/OimTaxonomy/XbrlTaxonomyObject.py +154 -0
- arelle/plugin/OimTaxonomy/XbrlTransform.py +17 -0
- arelle/plugin/OimTaxonomy/XbrlTypes.py +23 -0
- arelle/plugin/OimTaxonomy/XbrlUnit.py +17 -0
- arelle/plugin/OimTaxonomy/__init__.py +1038 -0
- arelle/plugin/OimTaxonomy/resources/iso4217.json +4479 -0
- arelle/plugin/OimTaxonomy/resources/oim-taxonomy-schema.json +935 -0
- arelle/plugin/OimTaxonomy/resources/ref.json +333 -0
- arelle/plugin/OimTaxonomy/resources/transform-types.json +2481 -0
- arelle/plugin/OimTaxonomy/resources/types.json +727 -0
- arelle/plugin/OimTaxonomy/resources/utr.json +3046 -0
- arelle/plugin/OimTaxonomy/resources/xbrlSpec.json +1082 -0
- arelle/plugin/saveOIMTaxonomy.py +311 -0
- arelle/plugin/validate/NL/PluginValidationDataExtension.py +35 -1
- arelle/plugin/validate/NL/__init__.py +3 -0
- arelle/plugin/validate/NL/rules/nl_kvk.py +84 -2
- {arelle_release-2.37.18.dist-info → arelle_release-2.37.19.dist-info}/METADATA +2 -1
- {arelle_release-2.37.18.dist-info → arelle_release-2.37.19.dist-info}/RECORD +54 -19
- tests/integration_tests/validation/README.md +2 -0
- tests/integration_tests/validation/conformance_suite_configurations/nl_inline_2024.py +15 -4
- tests/integration_tests/validation/run_conformance_suites.py +10 -1
- tests/integration_tests/validation/validation_util.py +10 -5
- tests/unit_tests/arelle/test_frozen_dict.py +176 -0
- tests/unit_tests/arelle/test_frozen_ordered_set.py +315 -0
- tests/unit_tests/arelle/test_import.py +31 -0
- tests/unit_tests/arelle/test_ordered_set.py +272 -0
- {arelle_release-2.37.18.dist-info → arelle_release-2.37.19.dist-info}/WHEEL +0 -0
- {arelle_release-2.37.18.dist-info → arelle_release-2.37.19.dist-info}/entry_points.txt +0 -0
- {arelle_release-2.37.18.dist-info → arelle_release-2.37.19.dist-info}/licenses/LICENSE.md +0 -0
- {arelle_release-2.37.18.dist-info → arelle_release-2.37.19.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,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
48
|
|
|
47
49
|
def validateXbrlFinally(*args: Any, **kwargs: Any) -> None:
|
|
48
50
|
return validationPlugin.validateXbrlFinally(*args, **kwargs)
|
|
@@ -59,5 +61,6 @@ __pluginInfo__ = {
|
|
|
59
61
|
"DisclosureSystem.Types": disclosureSystemTypes,
|
|
60
62
|
"DisclosureSystem.ConfigURL": disclosureSystemConfigURL,
|
|
61
63
|
"ModelXbrl.LoadComplete": modelXbrlLoadComplete,
|
|
64
|
+
"Validate.XBRL.Start": validateXbrlStart,
|
|
62
65
|
"Validate.XBRL.Finally": validateXbrlFinally,
|
|
63
66
|
}
|
|
@@ -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,
|
|
24
|
-
|
|
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,58 @@ 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.XBRL_FINALLY,
|
|
852
|
+
disclosureSystems=[
|
|
853
|
+
DISCLOSURE_SYSTEM_NL_INLINE_2024
|
|
854
|
+
],
|
|
855
|
+
)
|
|
856
|
+
def rule_nl_kvk_4_1_2_1(
|
|
857
|
+
pluginData: PluginValidationDataExtension,
|
|
858
|
+
val: ValidateXbrl,
|
|
859
|
+
*args: Any,
|
|
860
|
+
**kwargs: Any,
|
|
861
|
+
) -> Iterable[Validation]:
|
|
862
|
+
"""
|
|
863
|
+
NL-KVK.4.1.2.1: Validate that the imported taxonomy matches the KVK-specified entry point.
|
|
864
|
+
- https://www.nltaxonomie.nl/kvk/2024-12-31/kvk-annual-report-nlgaap-ext.xsd,
|
|
865
|
+
- https://www.nltaxonomie.nl/kvk/2024-12-31/kvk-annual-report-ifrs-ext.xsd.
|
|
866
|
+
"""
|
|
867
|
+
if val.modelXbrl.modelDocument is not None:
|
|
868
|
+
pluginData.checkFilingDTS(val, val.modelXbrl.modelDocument, [])
|
|
869
|
+
if not any(e in val.extensionImportedUrls for e in EFFECTIVE_TAXONOMY_URLS):
|
|
870
|
+
yield Validation.error(
|
|
871
|
+
codes='NL.NL-KVK.4.1.2.1.requiredEntryPointNotImported',
|
|
872
|
+
msg=_('The extension taxonomy must import the entry point of the taxonomy files prepared by KVK.'),
|
|
873
|
+
modelObject=val.modelXbrl.modelDocument
|
|
874
|
+
)
|
|
875
|
+
|
|
876
|
+
|
|
877
|
+
@validation(
|
|
878
|
+
hook=ValidationHook.XBRL_FINALLY,
|
|
879
|
+
disclosureSystems=[
|
|
880
|
+
DISCLOSURE_SYSTEM_NL_INLINE_2024
|
|
881
|
+
],
|
|
882
|
+
)
|
|
883
|
+
def rule_nl_kvk_6_1_1_1(
|
|
884
|
+
pluginData: PluginValidationDataExtension,
|
|
885
|
+
val: ValidateXbrl,
|
|
886
|
+
*args: Any,
|
|
887
|
+
**kwargs: Any,
|
|
888
|
+
) -> Iterable[Validation]:
|
|
889
|
+
"""
|
|
890
|
+
NL-KVK.6.1.1.1: The size of the report package MUST NOT exceed 100 MB.
|
|
891
|
+
"""
|
|
892
|
+
if val.modelXbrl.fileSource.fs and isinstance(val.modelXbrl.fileSource.fs, zipfile.ZipFile):
|
|
893
|
+
maxMB = float(MAX_REPORT_PACKAGE_SIZE_MBS)
|
|
894
|
+
# The following code computes report package size by adding the compressed file sizes within the package.
|
|
895
|
+
# This method of computation is over 99% accurate and gets more accurate the larger the filesize is.
|
|
896
|
+
_size = sum(zi.compress_size for zi in val.modelXbrl.fileSource.fs.infolist())
|
|
897
|
+
if _size > maxMB * 1000000:
|
|
898
|
+
yield Validation.error(
|
|
899
|
+
codes='NL.NL-KVK.6.1.1.1.reportPackageMaximumSizeExceeded',
|
|
900
|
+
msg=_('The size of the report package must not exceed %(maxSize)s MBs, size is %(size)s MBs.'),
|
|
901
|
+
modelObject=val.modelXbrl, maxSize=MAX_REPORT_PACKAGE_SIZE_MBS, size=int(_size/1000000)
|
|
902
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: arelle-release
|
|
3
|
-
Version: 2.37.
|
|
3
|
+
Version: 2.37.19
|
|
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
|