arelle-release 2.37.28__py3-none-any.whl → 2.37.30__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/Locale.py +3 -1
- arelle/ModelTestcaseObject.py +9 -3
- arelle/Validate.py +29 -14
- arelle/WebCache.py +10 -5
- arelle/_version.py +2 -2
- arelle/plugin/validate/EDINET/DisclosureSystems.py +1 -0
- arelle/plugin/validate/EDINET/FormType.py +105 -0
- arelle/plugin/validate/EDINET/PluginValidationDataExtension.py +111 -0
- arelle/plugin/validate/EDINET/ValidationPluginExtension.py +29 -0
- arelle/plugin/validate/EDINET/__init__.py +119 -0
- arelle/plugin/validate/EDINET/resources/config.xml +12 -0
- arelle/plugin/validate/EDINET/rules/__init__.py +0 -0
- arelle/plugin/validate/EDINET/rules/upload.py +321 -0
- arelle/plugin/validate/NL/rules/nl_kvk.py +52 -0
- arelle/utils/EntryPointDetection.py +6 -0
- {arelle_release-2.37.28.dist-info → arelle_release-2.37.30.dist-info}/METADATA +2 -1
- {arelle_release-2.37.28.dist-info → arelle_release-2.37.30.dist-info}/RECORD +62 -18
- tests/integration_tests/validation/conformance_suite_configs.py +2 -0
- tests/integration_tests/validation/conformance_suite_configurations/edinet.py +35 -0
- tests/integration_tests/validation/conformance_suite_configurations/nl_inline_2024.py +13 -3
- tests/integration_tests/validation/conformance_suite_configurations/xbrl_formula_1_0_function_registry.py +4 -0
- tests/integration_tests/validation/discover_tests.py +3 -3
- tests/integration_tests/validation/validation_util.py +9 -1
- tests/resources/conformance_suites/edinet/EC0121E/index.xml +23 -0
- tests/resources/conformance_suites/edinet/EC0121E/invalid01.zip +0 -0
- tests/resources/conformance_suites/edinet/EC0124E/index.xml +23 -0
- tests/resources/conformance_suites/edinet/EC0124E/invalid01.zip +0 -0
- tests/resources/conformance_suites/edinet/EC0129E/index.xml +23 -0
- tests/resources/conformance_suites/edinet/EC0129E/invalid01.zip +0 -0
- tests/resources/conformance_suites/edinet/EC0130E/index.xml +23 -0
- tests/resources/conformance_suites/edinet/EC0130E/invalid01.zip +0 -0
- tests/resources/conformance_suites/edinet/EC0132E/index.xml +23 -0
- tests/resources/conformance_suites/edinet/EC0132E/invalid01.zip +0 -0
- tests/resources/conformance_suites/edinet/EC0188E/index.xml +23 -0
- tests/resources/conformance_suites/edinet/EC0188E/invalid01.zip +0 -0
- tests/resources/conformance_suites/edinet/EC0198E/index.xml +23 -0
- tests/resources/conformance_suites/edinet/EC0198E/invalid01.zip +0 -0
- tests/resources/conformance_suites/edinet/README.md +4 -0
- tests/resources/conformance_suites/edinet/index.xml +11 -0
- tests/resources/conformance_suites/edinet/valid/index.xml +199 -0
- tests/resources/conformance_suites/edinet/valid/valid01.zip +0 -0
- tests/resources/conformance_suites/edinet/valid/valid02.zip +0 -0
- tests/resources/conformance_suites/edinet/valid/valid03.zip +0 -0
- tests/resources/conformance_suites/edinet/valid/valid04.zip +0 -0
- tests/resources/conformance_suites/edinet/valid/valid05.zip +0 -0
- tests/resources/conformance_suites/edinet/valid/valid06.zip +0 -0
- tests/resources/conformance_suites/edinet/valid/valid07.zip +0 -0
- tests/resources/conformance_suites/edinet/valid/valid08.zip +0 -0
- tests/resources/conformance_suites/edinet/valid/valid09.zip +0 -0
- tests/resources/conformance_suites/edinet/valid/valid10.zip +0 -0
- tests/resources/conformance_suites/edinet/valid/valid11.zip +0 -0
- tests/resources/conformance_suites/edinet/valid/valid12.zip +0 -0
- tests/resources/conformance_suites/edinet/valid/valid13.zip +0 -0
- tests/resources/conformance_suites/edinet/valid/valid14.zip +0 -0
- tests/resources/conformance_suites/edinet/valid/valid20.zip +0 -0
- tests/resources/conformance_suites/edinet/valid/valid21.zip +0 -0
- tests/resources/conformance_suites/edinet/valid/valid22.zip +0 -0
- tests/resources/conformance_suites_timing/edinet.json +27 -0
- {arelle_release-2.37.28.dist-info → arelle_release-2.37.30.dist-info}/WHEEL +0 -0
- {arelle_release-2.37.28.dist-info → arelle_release-2.37.30.dist-info}/entry_points.txt +0 -0
- {arelle_release-2.37.28.dist-info → arelle_release-2.37.30.dist-info}/licenses/LICENSE.md +0 -0
- {arelle_release-2.37.28.dist-info → arelle_release-2.37.30.dist-info}/top_level.txt +0 -0
arelle/Locale.py
CHANGED
|
@@ -238,9 +238,11 @@ def getLocale() -> str | None:
|
|
|
238
238
|
systemLocale = tryRunCommand("pwsh", "-Command", "Get-Culture | Select -ExpandProperty IetfLanguageTag")
|
|
239
239
|
if pythonCompatibleLocale := findCompatibleLocale(systemLocale):
|
|
240
240
|
_locale = pythonCompatibleLocale
|
|
241
|
-
elif sys.version_info < (3, 12):
|
|
241
|
+
elif sys.version_info < (3, 12) or (3, 13, 3) <= sys.version_info[:3] <= (3, 13, 4):
|
|
242
242
|
# Using locale.setlocale(...) because getlocale() in Python versions prior to 3.12 incorrectly aliased C.UTF-8 to en_US.UTF-8.
|
|
243
243
|
# https://github.com/python/cpython/issues/74940
|
|
244
|
+
# Similar bug was reintroduced in Python 3.13.3 and fixed in 3.13.5.
|
|
245
|
+
# https://github.com/python/cpython/pull/135347
|
|
244
246
|
_locale = locale.setlocale(locale.LC_CTYPE).partition(POSIX_LOCALE_ENCODING_SEPARATOR)[0]
|
|
245
247
|
else:
|
|
246
248
|
_locale = locale.getlocale()[0]
|
arelle/ModelTestcaseObject.py
CHANGED
|
@@ -319,9 +319,15 @@ class ModelTestcaseVariation(ModelObject):
|
|
|
319
319
|
errorElements = XmlUtil.descendants(self, None, "error")
|
|
320
320
|
resultElement = XmlUtil.descendant(self, None, "result")
|
|
321
321
|
if isinstance(errorElements,list) and len(errorElements) > 0:
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
322
|
+
errorCodes = []
|
|
323
|
+
for errorElement in errorElements:
|
|
324
|
+
if errorElement.get("nonStandardErrorCodes"):
|
|
325
|
+
errorCode = errorElement.stringValue
|
|
326
|
+
else:
|
|
327
|
+
errorCode = ModelValue.qname(errorElement, errorElement.stringValue)
|
|
328
|
+
num = int(errorElement.attr("num") or 1)
|
|
329
|
+
errorCodes.extend([errorCode] * num)
|
|
330
|
+
return errorCodes
|
|
325
331
|
#else:
|
|
326
332
|
# errorElement = errorElements[1]
|
|
327
333
|
# if errorElement is not None and not errorElement.get("nonStandardErrorCodes"):
|
arelle/Validate.py
CHANGED
|
@@ -338,17 +338,18 @@ class Validate:
|
|
|
338
338
|
loadedModels.append(modelXbrl)
|
|
339
339
|
PackageManager.packageInfo(self.modelXbrl.modelManager.cntlr, readMeFirstUri, reload=True, errors=modelXbrl.errors)
|
|
340
340
|
else: # not a multi-schemaRef versioning report
|
|
341
|
+
readMeFirstUriIsArchive = isReportPackageExtension(readMeFirstUri)
|
|
341
342
|
readMeFirstUriIsEmbeddedZipFile = False
|
|
342
343
|
if self.useFileSource.isArchive and not isLegacyAbs(readMeFirstUri):
|
|
343
|
-
if
|
|
344
|
+
if readMeFirstUriIsArchive:
|
|
344
345
|
readMeFirstUriIsEmbeddedZipFile = True
|
|
345
346
|
else:
|
|
346
347
|
normalizedReadMeFirstUri = self.modelXbrl.modelManager.cntlr.webCache.normalizeUrl(readMeFirstUri, baseForElement)
|
|
347
348
|
archivePath = FileSource.archiveFilenameParts(normalizedReadMeFirstUri)
|
|
348
349
|
if archivePath:
|
|
349
350
|
with self.useFileSource.fs.open(archivePath[1]) as embeddedFile:
|
|
350
|
-
readMeFirstUriIsEmbeddedZipFile = zipfile.is_zipfile(embeddedFile)
|
|
351
|
-
if not
|
|
351
|
+
readMeFirstUriIsArchive = readMeFirstUriIsEmbeddedZipFile = zipfile.is_zipfile(embeddedFile)
|
|
352
|
+
if not readMeFirstUriIsArchive:
|
|
352
353
|
modelXbrl = ModelXbrl.load(self.modelXbrl.modelManager,
|
|
353
354
|
readMeFirstUri,
|
|
354
355
|
_("validating"),
|
|
@@ -400,7 +401,16 @@ class Validate:
|
|
|
400
401
|
# resolve an IXDS in entrypoints
|
|
401
402
|
for pluginXbrlMethod in pluginClassMethods("ModelTestcaseVariation.ArchiveIxds"):
|
|
402
403
|
pluginXbrlMethod(self, filesource,entrypoints)
|
|
403
|
-
|
|
404
|
+
for entrypoint in entrypoints:
|
|
405
|
+
filesource.select(entrypoint.get("file", None))
|
|
406
|
+
modelXbrl = ModelXbrl.load(self.modelXbrl.modelManager,
|
|
407
|
+
filesource,
|
|
408
|
+
_("validating"),
|
|
409
|
+
base=filesource.basefile + "/",
|
|
410
|
+
errorCaptureLevel=errorCaptureLevel,
|
|
411
|
+
ixdsTarget=modelTestcaseVariation.ixdsTarget,
|
|
412
|
+
errors=preLoadingErrors)
|
|
413
|
+
loadedModels.append(modelXbrl)
|
|
404
414
|
except Exception as err:
|
|
405
415
|
self.modelXbrl.error("exception:" + type(err).__name__,
|
|
406
416
|
_("Testcase variation validation exception: %(error)s, entry URL: %(instance)s"),
|
|
@@ -427,15 +437,16 @@ class Validate:
|
|
|
427
437
|
# Legacy ESEF conformance suite logic.
|
|
428
438
|
for pluginXbrlMethod in pluginClassMethods("ModelTestcaseVariation.ReportPackageIxds"):
|
|
429
439
|
filesource.select(pluginXbrlMethod(filesource, **_rptPkgIxdsOptions))
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
440
|
+
if len(loadedModels) == 0:
|
|
441
|
+
modelXbrl = ModelXbrl.load(self.modelXbrl.modelManager,
|
|
442
|
+
filesource,
|
|
443
|
+
_("validating"),
|
|
444
|
+
base=baseForElement,
|
|
445
|
+
errorCaptureLevel=errorCaptureLevel,
|
|
446
|
+
ixdsTarget=modelTestcaseVariation.ixdsTarget,
|
|
447
|
+
isLoadable=modelTestcaseVariation.variationDiscoversDTS or filesource.url,
|
|
448
|
+
errors=preLoadingErrors)
|
|
449
|
+
loadedModels.append(modelXbrl)
|
|
439
450
|
|
|
440
451
|
for model in loadedModels:
|
|
441
452
|
modelXbrl.isTestcaseVariation = True
|
|
@@ -730,7 +741,11 @@ class Validate:
|
|
|
730
741
|
indexPath = indexPath[len(baseZipFile) + 1:]
|
|
731
742
|
indexPath = indexPath.replace("\\", "/")
|
|
732
743
|
variationIdPath = f'{indexPath}:{modelTestcaseVariation.id}'
|
|
733
|
-
|
|
744
|
+
userExpectedErrors = []
|
|
745
|
+
for userPattern, userErrors in testcaseExpectedErrors.items():
|
|
746
|
+
if fnmatch.fnmatch(variationIdPath, userPattern):
|
|
747
|
+
userExpectedErrors.extend(userErrors)
|
|
748
|
+
if userExpectedErrors:
|
|
734
749
|
if expected is None:
|
|
735
750
|
expected = []
|
|
736
751
|
if isinstance(expected, str):
|
arelle/WebCache.py
CHANGED
|
@@ -27,6 +27,13 @@ try:
|
|
|
27
27
|
import ssl
|
|
28
28
|
except ImportError:
|
|
29
29
|
ssl = None
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
import truststore
|
|
33
|
+
except ImportError:
|
|
34
|
+
# truststore requires Python > 3.9
|
|
35
|
+
truststore = None
|
|
36
|
+
|
|
30
37
|
from arelle.FileSource import SERVER_WEB_CACHE, archiveFilenameParts
|
|
31
38
|
from arelle.PluginManager import pluginClassMethods
|
|
32
39
|
from arelle.UrlUtil import isHttpUrl
|
|
@@ -267,12 +274,10 @@ class WebCache:
|
|
|
267
274
|
self.http_auth_handler = proxyhandlers.HTTPBasicAuthHandler()
|
|
268
275
|
proxyHandlers = [self.proxy_handler, self.proxy_auth_handler, self.http_auth_handler]
|
|
269
276
|
if ssl:
|
|
270
|
-
#
|
|
271
|
-
context = ssl.create_default_context()
|
|
277
|
+
# Attempt to load the default CA certificates from the OS using truststore if available, else fallback to OpenSSL's default context.
|
|
278
|
+
context = truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT) if truststore else ssl.create_default_context()
|
|
272
279
|
# Include certifi certificates (Mozilla’s carefully curated
|
|
273
|
-
# collection) for systems with outdated certs
|
|
274
|
-
# that we're unable to load certs from (macOS and some Linux
|
|
275
|
-
# distros.)
|
|
280
|
+
# collection) for systems with outdated certs.
|
|
276
281
|
context.load_verify_locations(cafile=certifi.where())
|
|
277
282
|
if self.noCertificateCheck: # this is required in some Akamai environments, such as sec.gov
|
|
278
283
|
context.check_hostname = False
|
arelle/_version.py
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
DISCLOSURE_SYSTEM_EDINET = 'EDINET'
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""
|
|
2
|
+
See COPYRIGHT.md for copyright information.
|
|
3
|
+
"""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from functools import cached_property, lru_cache
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class FormType(Enum):
|
|
12
|
+
ATTACH_DOC = "AttachDoc"
|
|
13
|
+
AUDIT_DOC = "AuditDoc"
|
|
14
|
+
ENGLISH_DOC = "EnglishDoc"
|
|
15
|
+
PRIVATE_ATTACH = "PrivateAttach"
|
|
16
|
+
PRIVATE_DOC = "PrivateDoc"
|
|
17
|
+
PUBLIC_DOC = "PublicDoc"
|
|
18
|
+
|
|
19
|
+
@classmethod
|
|
20
|
+
def parse(cls, value: str) -> FormType | None:
|
|
21
|
+
try:
|
|
22
|
+
return cls(value)
|
|
23
|
+
except ValueError:
|
|
24
|
+
return None
|
|
25
|
+
|
|
26
|
+
@cached_property
|
|
27
|
+
def extensionCategory(self) -> ExtensionCategory | None:
|
|
28
|
+
return FORM_TYPE_EXTENSION_CATEGORIES.get(self, None)
|
|
29
|
+
|
|
30
|
+
@cached_property
|
|
31
|
+
def manifestName(self) -> str:
|
|
32
|
+
return f'manifest_{self.value}.xml'
|
|
33
|
+
|
|
34
|
+
@cached_property
|
|
35
|
+
def manifestPath(self) -> Path:
|
|
36
|
+
return self.xbrlDirectory / self.manifestName
|
|
37
|
+
|
|
38
|
+
@cached_property
|
|
39
|
+
def xbrlDirectory(self) -> Path:
|
|
40
|
+
return Path('XBRL') / str(self.value)
|
|
41
|
+
|
|
42
|
+
@lru_cache(1)
|
|
43
|
+
def getValidExtensions(self, isAmendment: bool, isSubdirectory: bool) -> frozenset[str] | None:
|
|
44
|
+
if self.extensionCategory is None:
|
|
45
|
+
return None
|
|
46
|
+
return self.extensionCategory.getValidExtensions(isAmendment, isSubdirectory)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class ExtensionCategory(Enum):
|
|
50
|
+
ATTACH = 'ATTACH'
|
|
51
|
+
DOC = 'DOC'
|
|
52
|
+
ENGLISH_DOC = 'ENGLISH_DOC'
|
|
53
|
+
|
|
54
|
+
def getValidExtensions(self, isAmendment: bool, isSubdirectory: bool) -> frozenset[str] | None:
|
|
55
|
+
amendmentMap = VALID_EXTENSIONS[isAmendment]
|
|
56
|
+
categoryMap = amendmentMap.get(self, None)
|
|
57
|
+
if categoryMap is None:
|
|
58
|
+
return None
|
|
59
|
+
return categoryMap.get(isSubdirectory, None)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
FORM_TYPE_EXTENSION_CATEGORIES = {
|
|
63
|
+
FormType.ATTACH_DOC: ExtensionCategory.ATTACH,
|
|
64
|
+
FormType.AUDIT_DOC: ExtensionCategory.DOC,
|
|
65
|
+
FormType.ENGLISH_DOC: ExtensionCategory.ENGLISH_DOC,
|
|
66
|
+
FormType.PRIVATE_ATTACH: ExtensionCategory.ATTACH,
|
|
67
|
+
FormType.PRIVATE_DOC: ExtensionCategory.DOC,
|
|
68
|
+
FormType.PUBLIC_DOC: ExtensionCategory.DOC,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
HTML_EXTENSIONS = frozenset({'.htm', '.html', '.xhtml'})
|
|
73
|
+
IMAGE_EXTENSIONS = frozenset({'.jpeg', '.jpg', '.gif', '.png'})
|
|
74
|
+
ASSET_EXTENSIONS = frozenset(HTML_EXTENSIONS | IMAGE_EXTENSIONS)
|
|
75
|
+
XBRL_EXTENSIONS = frozenset(HTML_EXTENSIONS | {'.xml', '.xsd'})
|
|
76
|
+
ATTACH_EXTENSIONS = frozenset(HTML_EXTENSIONS | {'.pdf', })
|
|
77
|
+
ENGLISH_DOC_EXTENSIONS = frozenset(ASSET_EXTENSIONS | frozenset({'.pdf', '.xml', '.txt'}))
|
|
78
|
+
|
|
79
|
+
# Is Amendment -> Category -> Is Subdirectory
|
|
80
|
+
VALID_EXTENSIONS = {
|
|
81
|
+
False: {
|
|
82
|
+
ExtensionCategory.ATTACH: {
|
|
83
|
+
False: ATTACH_EXTENSIONS,
|
|
84
|
+
True: ASSET_EXTENSIONS,
|
|
85
|
+
},
|
|
86
|
+
ExtensionCategory.DOC: {
|
|
87
|
+
False: XBRL_EXTENSIONS,
|
|
88
|
+
True: ASSET_EXTENSIONS
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
True: {
|
|
92
|
+
ExtensionCategory.ATTACH: {
|
|
93
|
+
False: ATTACH_EXTENSIONS,
|
|
94
|
+
True: ASSET_EXTENSIONS,
|
|
95
|
+
},
|
|
96
|
+
ExtensionCategory.DOC: {
|
|
97
|
+
False: HTML_EXTENSIONS,
|
|
98
|
+
True: ASSET_EXTENSIONS,
|
|
99
|
+
},
|
|
100
|
+
ExtensionCategory.ENGLISH_DOC: {
|
|
101
|
+
False: ENGLISH_DOC_EXTENSIONS,
|
|
102
|
+
True: ENGLISH_DOC_EXTENSIONS,
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""
|
|
2
|
+
See COPYRIGHT.md for copyright information.
|
|
3
|
+
"""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import zipfile
|
|
7
|
+
from collections import defaultdict
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from functools import lru_cache
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from arelle.ModelXbrl import ModelXbrl
|
|
13
|
+
from arelle.ValidateXbrl import ValidateXbrl
|
|
14
|
+
from arelle.typing import TypeGetText
|
|
15
|
+
from arelle.utils.PluginData import PluginData
|
|
16
|
+
from .FormType import FormType
|
|
17
|
+
|
|
18
|
+
_: TypeGetText
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True)
|
|
22
|
+
class UploadContents:
|
|
23
|
+
amendmentPaths: dict[FormType, frozenset[Path]]
|
|
24
|
+
directories: frozenset[Path]
|
|
25
|
+
forms: dict[FormType, frozenset[Path]]
|
|
26
|
+
unknownPaths: frozenset[Path]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class PluginValidationDataExtension(PluginData):
|
|
31
|
+
_primaryModelXbrl: ModelXbrl | None = None
|
|
32
|
+
|
|
33
|
+
# Identity hash for caching.
|
|
34
|
+
def __hash__(self) -> int:
|
|
35
|
+
return id(self)
|
|
36
|
+
|
|
37
|
+
@lru_cache(1)
|
|
38
|
+
def shouldValidateUpload(self, val: ValidateXbrl) -> bool:
|
|
39
|
+
"""
|
|
40
|
+
Determine if the upload validation should be performed on this model.
|
|
41
|
+
|
|
42
|
+
Upload validation should not be performed if the target document is
|
|
43
|
+
not a zipfile.
|
|
44
|
+
|
|
45
|
+
Upload validation should only be performed once for the entire package,
|
|
46
|
+
not duplicated for each model. To facilitate this with Arelle's validation
|
|
47
|
+
system which largely prevents referencing other models, we can use `--keepOpen`
|
|
48
|
+
and check if the given model is the first to be loaded.
|
|
49
|
+
:param val: The ValidateXbrl instance with a model to check.
|
|
50
|
+
:return: True if upload validation should be performed, False otherwise.
|
|
51
|
+
"""
|
|
52
|
+
modelXbrl = val.modelXbrl
|
|
53
|
+
if modelXbrl == val.testModelXbrl:
|
|
54
|
+
# Not running within a testcase
|
|
55
|
+
if modelXbrl != modelXbrl.modelManager.loadedModelXbrls[0]:
|
|
56
|
+
return False
|
|
57
|
+
if not modelXbrl.fileSource.fs:
|
|
58
|
+
return False # No stream
|
|
59
|
+
if not isinstance(modelXbrl.fileSource.fs, zipfile.ZipFile):
|
|
60
|
+
return False # Not a zipfile
|
|
61
|
+
return True
|
|
62
|
+
|
|
63
|
+
@lru_cache(1)
|
|
64
|
+
def getUploadContents(self, modelXbrl: ModelXbrl) -> UploadContents:
|
|
65
|
+
uploadFilepaths = self.getUploadFilepaths(modelXbrl)
|
|
66
|
+
amendmentPaths = defaultdict(list)
|
|
67
|
+
unknownPaths = []
|
|
68
|
+
directories = []
|
|
69
|
+
forms = defaultdict(list)
|
|
70
|
+
for path in uploadFilepaths:
|
|
71
|
+
parents = list(reversed([p.name for p in path.parents if len(p.name) > 0]))
|
|
72
|
+
if len(parents) == 0:
|
|
73
|
+
continue
|
|
74
|
+
if parents[0] == 'XBRL':
|
|
75
|
+
if len(parents) > 1:
|
|
76
|
+
formName = parents[1]
|
|
77
|
+
formType = FormType.parse(formName)
|
|
78
|
+
if formType is not None:
|
|
79
|
+
forms[formType].append(path)
|
|
80
|
+
continue
|
|
81
|
+
formName = parents[0]
|
|
82
|
+
formType = FormType.parse(formName)
|
|
83
|
+
if formType is not None:
|
|
84
|
+
amendmentPaths[formType].append(path)
|
|
85
|
+
continue
|
|
86
|
+
if len(path.suffix) == 0:
|
|
87
|
+
directories.append(path)
|
|
88
|
+
continue
|
|
89
|
+
unknownPaths.append(path)
|
|
90
|
+
return UploadContents(
|
|
91
|
+
amendmentPaths={k: frozenset(v) for k, v in amendmentPaths.items() if len(v) > 0},
|
|
92
|
+
directories=frozenset(directories),
|
|
93
|
+
forms={k: frozenset(v) for k, v in forms.items() if len(v) > 0},
|
|
94
|
+
unknownPaths=frozenset(unknownPaths)
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
@lru_cache(1)
|
|
98
|
+
def getUploadFilepaths(self, modelXbrl: ModelXbrl) -> list[Path]:
|
|
99
|
+
if not modelXbrl.fileSource.fs or \
|
|
100
|
+
not isinstance(modelXbrl.fileSource.fs, zipfile.ZipFile):
|
|
101
|
+
modelXbrl.warning(
|
|
102
|
+
codes="EDINET.uploadNotValidated",
|
|
103
|
+
msg=_("The target file is not a zip file, so upload validation could not be performed.")
|
|
104
|
+
)
|
|
105
|
+
return []
|
|
106
|
+
paths = set()
|
|
107
|
+
for name in modelXbrl.fileSource.fs.namelist():
|
|
108
|
+
path = Path(name)
|
|
109
|
+
paths.add(path)
|
|
110
|
+
paths.update(path.parents)
|
|
111
|
+
return sorted(paths)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
See COPYRIGHT.md for copyright information.
|
|
3
|
+
"""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from arelle.ValidateXbrl import ValidateXbrl
|
|
9
|
+
from arelle.typing import TypeGetText
|
|
10
|
+
from arelle.utils.validate.ValidationPlugin import ValidationPlugin
|
|
11
|
+
from .DisclosureSystems import DISCLOSURE_SYSTEM_EDINET
|
|
12
|
+
from .PluginValidationDataExtension import PluginValidationDataExtension
|
|
13
|
+
|
|
14
|
+
_: TypeGetText
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ValidationPluginExtension(ValidationPlugin):
|
|
18
|
+
def newPluginData(self, validateXbrl: ValidateXbrl) -> PluginValidationDataExtension:
|
|
19
|
+
disclosureSystem = validateXbrl.disclosureSystem.name
|
|
20
|
+
if disclosureSystem == DISCLOSURE_SYSTEM_EDINET:
|
|
21
|
+
pass
|
|
22
|
+
else:
|
|
23
|
+
raise ValueError(f'Invalid EDINET disclosure system: {disclosureSystem}')
|
|
24
|
+
return PluginValidationDataExtension(
|
|
25
|
+
self.name,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
def modelXbrlLoadComplete(self, *args: Any, **kwargs: Any) -> None:
|
|
29
|
+
return None
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""
|
|
2
|
+
See COPYRIGHT.md for copyright information.
|
|
3
|
+
- [Operation Guides](https://disclosure2dl.edinet-fsa.go.jp/guide/static/disclosure/WEEK0060.html)
|
|
4
|
+
- [Document Search](https://disclosure2.edinet-fsa.go.jp/week0020.aspx)
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import zipfile
|
|
9
|
+
from collections import defaultdict
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from lxml import etree
|
|
14
|
+
from lxml.etree import _Element
|
|
15
|
+
|
|
16
|
+
from arelle.FileSource import FileSource
|
|
17
|
+
from arelle.Version import authorLabel, copyrightLabel
|
|
18
|
+
from .ValidationPluginExtension import ValidationPluginExtension
|
|
19
|
+
from .rules import upload
|
|
20
|
+
|
|
21
|
+
PLUGIN_NAME = "Validate EDINET"
|
|
22
|
+
DISCLOSURE_SYSTEM_VALIDATION_TYPE = "EDINET"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
validationPlugin = ValidationPluginExtension(
|
|
26
|
+
name=PLUGIN_NAME,
|
|
27
|
+
disclosureSystemConfigUrl=Path(__file__).parent / "resources" / "config.xml",
|
|
28
|
+
validationTypes=[DISCLOSURE_SYSTEM_VALIDATION_TYPE],
|
|
29
|
+
validationRuleModules=[
|
|
30
|
+
upload,
|
|
31
|
+
],
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def disclosureSystemTypes(*args: Any, **kwargs: Any) -> tuple[tuple[str, str], ...]:
|
|
36
|
+
return validationPlugin.disclosureSystemTypes
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def disclosureSystemConfigURL(*args: Any, **kwargs: Any) -> str:
|
|
40
|
+
return validationPlugin.disclosureSystemConfigURL
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _parseManifestDoc(xmlRootElement: _Element, base: Path) -> dict[str, list[Path]]:
|
|
44
|
+
sets = defaultdict(list)
|
|
45
|
+
for instanceElt in xmlRootElement.iter(tag="{http://disclosure.edinet-fsa.go.jp/2013/manifest}instance"):
|
|
46
|
+
instanceId = str(instanceElt.attrib["id"])
|
|
47
|
+
for ixbrlElt in instanceElt.iter(tag="{http://disclosure.edinet-fsa.go.jp/2013/manifest}ixbrl"):
|
|
48
|
+
uri = ixbrlElt.text.strip() if ixbrlElt.text is not None else None
|
|
49
|
+
if uri:
|
|
50
|
+
sets[instanceId].append(base / uri)
|
|
51
|
+
return sets
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def fileSourceEntrypointFiles(filesource: FileSource, inlineOnly: bool, *args: Any, **kwargs: Any) -> list[dict[str, Any]] | None:
|
|
55
|
+
manifests = {}
|
|
56
|
+
if filesource.isArchive:
|
|
57
|
+
if filesource.isTaxonomyPackage:
|
|
58
|
+
return None
|
|
59
|
+
if filesource.reportPackage is not None:
|
|
60
|
+
return None
|
|
61
|
+
for _archiveFile in (filesource.dir or ()):
|
|
62
|
+
if not Path(_archiveFile).stem.startswith('manifest'):
|
|
63
|
+
continue
|
|
64
|
+
assert isinstance(filesource.fs, zipfile.ZipFile), \
|
|
65
|
+
"The EDINET plugin only supports archives in .zip format."
|
|
66
|
+
with filesource.fs.open(_archiveFile) as manifestDoc:
|
|
67
|
+
base = Path(_archiveFile).parent
|
|
68
|
+
xmlRootElement = etree.fromstring(manifestDoc.read())
|
|
69
|
+
manifests.update(_parseManifestDoc(xmlRootElement, base))
|
|
70
|
+
elif (dirpath := Path(str(filesource.url))).is_dir():
|
|
71
|
+
for file in dirpath.rglob("*"):
|
|
72
|
+
if not file.is_file():
|
|
73
|
+
continue
|
|
74
|
+
if not file.stem.startswith('manifest'):
|
|
75
|
+
continue
|
|
76
|
+
with open(file, 'rb') as manifestDoc:
|
|
77
|
+
base = file.parent
|
|
78
|
+
xmlRootElement = etree.fromstring(manifestDoc.read())
|
|
79
|
+
manifests.update(_parseManifestDoc(xmlRootElement, base))
|
|
80
|
+
if len(manifests) == 0:
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
entrypointFiles = []
|
|
84
|
+
for instanceId, uris in manifests.items():
|
|
85
|
+
entrypoints = []
|
|
86
|
+
for uri in uris:
|
|
87
|
+
filesource.select(str(uri))
|
|
88
|
+
entrypoints.append({"file": filesource.url})
|
|
89
|
+
entrypointFiles.append({'ixds': entrypoints})
|
|
90
|
+
return entrypointFiles
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def modelXbrlLoadComplete(*args: Any, **kwargs: Any) -> None:
|
|
94
|
+
return validationPlugin.modelXbrlLoadComplete(*args, **kwargs)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def validateFinally(*args: Any, **kwargs: Any) -> None:
|
|
98
|
+
return validationPlugin.validateFinally(*args, **kwargs)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def validateXbrlFinally(*args: Any, **kwargs: Any) -> None:
|
|
102
|
+
return validationPlugin.validateXbrlFinally(*args, **kwargs)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
__pluginInfo__ = {
|
|
106
|
+
"name": PLUGIN_NAME,
|
|
107
|
+
"version": "0.0.1",
|
|
108
|
+
"description": "Validation plugin for the EDINET taxonomies.",
|
|
109
|
+
"license": "Apache-2",
|
|
110
|
+
"author": authorLabel,
|
|
111
|
+
"copyright": copyrightLabel,
|
|
112
|
+
"import": ("inlineXbrlDocumentSet",),
|
|
113
|
+
"DisclosureSystem.Types": disclosureSystemTypes,
|
|
114
|
+
"DisclosureSystem.ConfigURL": disclosureSystemConfigURL,
|
|
115
|
+
"FileSource.EntrypointFiles": fileSourceEntrypointFiles,
|
|
116
|
+
"ModelXbrl.LoadComplete": modelXbrlLoadComplete,
|
|
117
|
+
"Validate.XBRL.Finally": validateXbrlFinally,
|
|
118
|
+
"ValidateFormula.Finished": validateFinally,
|
|
119
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<DisclosureSystems
|
|
3
|
+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
4
|
+
xsi:noNamespaceSchemaLocation="../../../../config/disclosuresystems.xsd">
|
|
5
|
+
<!-- see arelle/config/disclosuresystems.xml for full comments -->
|
|
6
|
+
<DisclosureSystem
|
|
7
|
+
names="EDINET|edinet"
|
|
8
|
+
description="Checks for EDINET."
|
|
9
|
+
validationType="EDINET"
|
|
10
|
+
exclusiveTypesPattern="EFM|GFM|FERC|HMRC|SBR.NL|EBA|EIOPA|ESEF"
|
|
11
|
+
/>
|
|
12
|
+
</DisclosureSystems>
|
|
File without changes
|