arelle-release 2.37.56__py3-none-any.whl → 2.37.58__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/Cntlr.py +1 -0
- arelle/CntlrCmdLine.py +1 -0
- arelle/DialogPluginManager.py +6 -4
- arelle/ErrorManager.py +7 -1
- arelle/PluginManager.py +129 -100
- arelle/ValidateDuplicateFacts.py +1 -1
- arelle/XbrlConst.py +1 -0
- arelle/_version.py +2 -2
- arelle/plugin/validate/EDINET/Constants.py +19 -18
- arelle/plugin/validate/EDINET/ControllerPluginData.py +11 -4
- arelle/plugin/validate/EDINET/CoverPageRequirements.py +118 -0
- arelle/plugin/validate/EDINET/FilingFormat.py +253 -0
- arelle/plugin/validate/EDINET/FormType.py +81 -0
- arelle/plugin/validate/EDINET/PluginValidationDataExtension.py +171 -33
- arelle/plugin/validate/EDINET/resources/cover-page-requirements.csv +27 -0
- arelle/plugin/validate/EDINET/rules/edinet.py +1 -1
- arelle/plugin/validate/EDINET/rules/gfm.py +271 -2
- arelle/plugin/validate/EDINET/rules/upload.py +183 -10
- arelle/plugin/validate/ROS/PluginValidationDataExtension.py +2 -0
- arelle/plugin/validate/ROS/ValidationPluginExtension.py +1 -0
- arelle/plugin/validate/ROS/rules/ros.py +37 -7
- {arelle_release-2.37.56.dist-info → arelle_release-2.37.58.dist-info}/METADATA +1 -1
- {arelle_release-2.37.56.dist-info → arelle_release-2.37.58.dist-info}/RECORD +27 -23
- {arelle_release-2.37.56.dist-info → arelle_release-2.37.58.dist-info}/WHEEL +0 -0
- {arelle_release-2.37.56.dist-info → arelle_release-2.37.58.dist-info}/entry_points.txt +0 -0
- {arelle_release-2.37.56.dist-info → arelle_release-2.37.58.dist-info}/licenses/LICENSE.md +0 -0
- {arelle_release-2.37.56.dist-info → arelle_release-2.37.58.dist-info}/top_level.txt +0 -0
arelle/Cntlr.py
CHANGED
arelle/CntlrCmdLine.py
CHANGED
|
@@ -950,6 +950,7 @@ class CntlrCmdLine(Cntlr.Cntlr):
|
|
|
950
950
|
if options.testcaseFilters:
|
|
951
951
|
fo.testcaseFilters = options.testcaseFilters
|
|
952
952
|
if options.testcaseResultsCaptureWarnings:
|
|
953
|
+
self.errorManager.setErrorCaptureLevel(logging._checkLevel("WARNING"))
|
|
953
954
|
fo.testcaseResultsCaptureWarnings = True
|
|
954
955
|
if options.testcaseResultOptions:
|
|
955
956
|
fo.testcaseResultOptions = options.testcaseResultOptions
|
arelle/DialogPluginManager.py
CHANGED
|
@@ -386,15 +386,17 @@ class DialogPluginManager(Toplevel):
|
|
|
386
386
|
group = {
|
|
387
387
|
"ixbrl-viewer": "1", # pip installed Arelle viewer
|
|
388
388
|
"iXBRLViewerPlugin": "2", # git clone installed Arelle viewer
|
|
389
|
-
"
|
|
389
|
+
"EDGAR": "3",
|
|
390
|
+
"Inline XBRL Document Set": "4",
|
|
391
|
+
"XBRL rule processor (xule)": "5",
|
|
390
392
|
}.get(key)
|
|
391
393
|
if not group:
|
|
392
394
|
if key.startswith("Validate"):
|
|
393
|
-
group = "
|
|
395
|
+
group = "6"
|
|
394
396
|
elif key.startswith("xbrlDB"):
|
|
395
|
-
group = "
|
|
397
|
+
group = "7"
|
|
396
398
|
else:
|
|
397
|
-
group = "
|
|
399
|
+
group = "8"
|
|
398
400
|
return group + key.lower()
|
|
399
401
|
|
|
400
402
|
@staticmethod
|
arelle/ErrorManager.py
CHANGED
|
@@ -180,7 +180,10 @@ class ErrorManager:
|
|
|
180
180
|
objectUrl = arg
|
|
181
181
|
else:
|
|
182
182
|
try:
|
|
183
|
-
|
|
183
|
+
if isinstance(arg, ObjectPropertyViewWrapper):
|
|
184
|
+
objectUrl = arg.modelObject.modelDocument.displayUri
|
|
185
|
+
else:
|
|
186
|
+
objectUrl = arg.modelDocument.displayUri
|
|
184
187
|
except AttributeError:
|
|
185
188
|
try:
|
|
186
189
|
objectUrl = arg.displayUri
|
|
@@ -313,3 +316,6 @@ class ErrorManager:
|
|
|
313
316
|
if isinstance(argValue, dict):
|
|
314
317
|
return dict((self._loggableValue(k), self._loggableValue(v)) for k, v in argValue.items())
|
|
315
318
|
return str(argValue)
|
|
319
|
+
|
|
320
|
+
def setErrorCaptureLevel(self, errorCaptureLevel: int) -> None:
|
|
321
|
+
self._errorCaptureLevel = errorCaptureLevel
|
arelle/PluginManager.py
CHANGED
|
@@ -1,31 +1,41 @@
|
|
|
1
1
|
'''
|
|
2
2
|
See COPYRIGHT.md for copyright information.
|
|
3
|
-
|
|
4
|
-
based on pull request 4
|
|
5
|
-
|
|
6
3
|
'''
|
|
7
4
|
from __future__ import annotations
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
|
|
6
|
+
import ast
|
|
7
|
+
import gettext
|
|
8
|
+
from glob import glob
|
|
11
9
|
import importlib.util
|
|
10
|
+
import json
|
|
12
11
|
import logging
|
|
13
|
-
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
import time
|
|
15
|
+
import traceback
|
|
16
|
+
import types
|
|
17
|
+
from collections import defaultdict
|
|
18
|
+
from collections.abc import Callable, Iterator
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from importlib.metadata import EntryPoint, entry_points
|
|
21
|
+
from numbers import Number
|
|
22
|
+
from pathlib import Path
|
|
14
23
|
from types import ModuleType
|
|
15
24
|
from typing import TYPE_CHECKING, Any, cast
|
|
16
|
-
|
|
25
|
+
|
|
17
26
|
import arelle.FileSource
|
|
27
|
+
from arelle.Locale import getLanguageCodes
|
|
18
28
|
from arelle.PythonUtil import isLegacyAbs
|
|
29
|
+
from arelle.typing import TypeGetText
|
|
19
30
|
from arelle.UrlUtil import isAbsolute
|
|
20
|
-
from pathlib import Path
|
|
21
|
-
from collections import OrderedDict, defaultdict
|
|
22
|
-
from collections.abc import Callable, Iterator
|
|
23
|
-
|
|
24
31
|
|
|
25
32
|
if TYPE_CHECKING:
|
|
26
33
|
# Prevent potential circular import error
|
|
27
34
|
from .Cntlr import Cntlr
|
|
28
35
|
|
|
36
|
+
|
|
37
|
+
_: TypeGetText
|
|
38
|
+
|
|
29
39
|
PLUGIN_TRACE_FILE = None
|
|
30
40
|
# PLUGIN_TRACE_FILE = "c:/temp/pluginerr.txt"
|
|
31
41
|
PLUGIN_TRACE_LEVEL = logging.WARNING
|
|
@@ -58,7 +68,7 @@ def init(cntlr: Cntlr, loadPluginConfig: bool = True) -> None:
|
|
|
58
68
|
if loadPluginConfig:
|
|
59
69
|
try:
|
|
60
70
|
pluginJsonFile = cntlr.userAppDir + os.sep + "plugins.json"
|
|
61
|
-
with
|
|
71
|
+
with open(pluginJsonFile, encoding='utf-8') as f:
|
|
62
72
|
pluginConfig = json.load(f)
|
|
63
73
|
freshenModuleInfos()
|
|
64
74
|
except Exception:
|
|
@@ -79,29 +89,45 @@ def reset() -> None: # force reloading modules and plugin infos
|
|
|
79
89
|
pluginMethodsForClasses.clear() # dict by class of list of ordered callable function objects
|
|
80
90
|
|
|
81
91
|
def orderedPluginConfig():
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
92
|
+
fieldOrder = [
|
|
93
|
+
'name',
|
|
94
|
+
'status',
|
|
95
|
+
'fileDate',
|
|
96
|
+
'version',
|
|
97
|
+
'description',
|
|
98
|
+
'moduleURL',
|
|
99
|
+
'localeURL',
|
|
100
|
+
'localeDomain',
|
|
101
|
+
'license',
|
|
102
|
+
'author',
|
|
103
|
+
'copyright',
|
|
104
|
+
'classMethods',
|
|
105
|
+
]
|
|
106
|
+
priorityIndex = {k: i for i, k in enumerate(fieldOrder)}
|
|
107
|
+
|
|
108
|
+
def sortModuleInfo(moduleInfo):
|
|
109
|
+
# Prioritize known fields by the index in fieldOrder; sort others alphabetically
|
|
110
|
+
orderedKeys = sorted(
|
|
111
|
+
moduleInfo.keys(),
|
|
112
|
+
key=lambda k: (priorityIndex.get(k, len(priorityIndex)), k)
|
|
113
|
+
)
|
|
114
|
+
return {k: moduleInfo[k] for k in orderedKeys}
|
|
115
|
+
|
|
116
|
+
orderedModules = {
|
|
117
|
+
moduleName: sortModuleInfo(pluginConfig['modules'][moduleName])
|
|
118
|
+
for moduleName in sorted(pluginConfig['modules'].keys())
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
'modules': orderedModules,
|
|
123
|
+
'classes': dict(sorted(pluginConfig['classes'].items()))
|
|
124
|
+
}
|
|
99
125
|
|
|
100
126
|
def save(cntlr: Cntlr) -> None:
|
|
101
127
|
global pluginConfigChanged
|
|
102
128
|
if pluginConfigChanged and cntlr.hasFileSystem and not cntlr.disablePersistentConfig:
|
|
103
129
|
pluginJsonFile = cntlr.userAppDir + os.sep + "plugins.json"
|
|
104
|
-
with
|
|
130
|
+
with open(pluginJsonFile, 'w', encoding='utf-8') as f:
|
|
105
131
|
jsonStr = str(json.dumps(orderedPluginConfig(), ensure_ascii=False, indent=2)) # might not be unicode in 2.7
|
|
106
132
|
f.write(jsonStr)
|
|
107
133
|
pluginConfigChanged = False
|
|
@@ -155,7 +181,6 @@ moduleInfo = {
|
|
|
155
181
|
|
|
156
182
|
'''
|
|
157
183
|
|
|
158
|
-
|
|
159
184
|
def logPluginTrace(message: str, level: Number) -> None:
|
|
160
185
|
"""
|
|
161
186
|
If plugin trace file logging is configured, logs `message` to it.
|
|
@@ -274,6 +299,14 @@ def getModuleFilename(moduleURL: str, reload: bool, normalize: bool, base: str |
|
|
|
274
299
|
if moduleFilename:
|
|
275
300
|
# `moduleFilename` normalized to an existing script
|
|
276
301
|
return moduleFilename, None
|
|
302
|
+
if base and not _isAbsoluteModuleURL(moduleURL):
|
|
303
|
+
# Search for a matching plugin deeper in the plugin directory tree.
|
|
304
|
+
# Handles cases where a plugin exists in a nested structure, such as
|
|
305
|
+
# when a developer clones an entire repository into the plugin directory.
|
|
306
|
+
# Example: arelle/plugin/xule/plugin/xule/__init__.py
|
|
307
|
+
for path in glob("**/" + moduleURL.replace('\\', '/'), recursive=True):
|
|
308
|
+
if normalizedPath := normalizeModuleFilename(path):
|
|
309
|
+
return normalizedPath, None
|
|
277
310
|
# `moduleFilename` did not map to a local filepath or did not normalize to a script
|
|
278
311
|
# Try using `moduleURL` to search for pip-installed entry point
|
|
279
312
|
entryPointRef = EntryPointRef.get(moduleURL)
|
|
@@ -416,7 +449,7 @@ def moduleModuleInfo(
|
|
|
416
449
|
|
|
417
450
|
if moduleFilename:
|
|
418
451
|
try:
|
|
419
|
-
logPluginTrace("Scanning module for plug-in info: {}"
|
|
452
|
+
logPluginTrace(f"Scanning module for plug-in info: {moduleFilename}", logging.INFO)
|
|
420
453
|
moduleInfo = parsePluginInfo(moduleURL, moduleFilename, entryPoint)
|
|
421
454
|
if moduleInfo is None:
|
|
422
455
|
return None
|
|
@@ -426,38 +459,50 @@ def moduleModuleInfo(
|
|
|
426
459
|
del moduleInfo["importURLs"]
|
|
427
460
|
moduleImports = moduleInfo["moduleImports"]
|
|
428
461
|
del moduleInfo["moduleImports"]
|
|
429
|
-
|
|
462
|
+
moduleImportsSubtree = False
|
|
430
463
|
mergedImportURLs = []
|
|
431
464
|
|
|
432
|
-
for
|
|
433
|
-
if
|
|
465
|
+
for url in importURLs:
|
|
466
|
+
if url.startswith("module_import"):
|
|
434
467
|
for moduleImport in moduleImports:
|
|
435
468
|
mergedImportURLs.append(moduleImport + ".py")
|
|
436
|
-
if
|
|
437
|
-
|
|
438
|
-
elif
|
|
469
|
+
if url == "module_import_subtree":
|
|
470
|
+
moduleImportsSubtree = True
|
|
471
|
+
elif url == "module_subtree":
|
|
439
472
|
for _dir in os.listdir(moduleDir):
|
|
440
|
-
|
|
441
|
-
if os.path.isdir(
|
|
442
|
-
mergedImportURLs.append(
|
|
473
|
+
subtreeModule = os.path.join(moduleDir,_dir)
|
|
474
|
+
if os.path.isdir(subtreeModule) and _dir != "__pycache__":
|
|
475
|
+
mergedImportURLs.append(subtreeModule)
|
|
443
476
|
else:
|
|
444
|
-
mergedImportURLs.append(
|
|
445
|
-
if parentImportsSubtree and not
|
|
446
|
-
|
|
477
|
+
mergedImportURLs.append(url)
|
|
478
|
+
if parentImportsSubtree and not moduleImportsSubtree:
|
|
479
|
+
moduleImportsSubtree = True
|
|
447
480
|
for moduleImport in moduleImports:
|
|
448
481
|
mergedImportURLs.append(moduleImport + ".py")
|
|
449
482
|
imports = []
|
|
450
|
-
for
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
483
|
+
for url in mergedImportURLs:
|
|
484
|
+
importURL = url
|
|
485
|
+
if not _isAbsoluteModuleURL(url):
|
|
486
|
+
# Handle relative imports when plugin is loaded from external directory.
|
|
487
|
+
# When EDGAR/render imports EDGAR/validate, this works if EDGAR is in the plugin directory
|
|
488
|
+
# but fails if loaded externally (e.g., dev repo clone at /dev/path/to/EDGAR/).
|
|
489
|
+
# Solution: Find common path segments to resolve /dev/path/to/EDGAR/validate
|
|
490
|
+
# from the importing module at /dev/path/to/EDGAR/render.
|
|
491
|
+
modulePath = Path(moduleFilename)
|
|
492
|
+
importPath = Path(url)
|
|
493
|
+
if importPath.parts:
|
|
494
|
+
importFirstPart = importPath.parts[0]
|
|
495
|
+
for i, modulePathPart in enumerate(reversed(modulePath.parts)):
|
|
496
|
+
if modulePathPart != importFirstPart:
|
|
497
|
+
continue
|
|
498
|
+
# Found a potential branching point, construct and check a new path
|
|
499
|
+
candidateImportURL = str(modulePath.parents[i] / importPath)
|
|
500
|
+
if normalizeModuleFilename(candidateImportURL):
|
|
501
|
+
importURL = candidateImportURL
|
|
502
|
+
importModuleInfo = moduleModuleInfo(moduleURL=importURL, reload=reload, parentImportsSubtree=moduleImportsSubtree)
|
|
503
|
+
if importModuleInfo:
|
|
504
|
+
importModuleInfo["isImported"] = True
|
|
505
|
+
imports.append(importModuleInfo)
|
|
461
506
|
moduleInfo["imports"] = imports
|
|
462
507
|
logPluginTrace(f"Successful module plug-in info: {moduleFilename}", logging.INFO)
|
|
463
508
|
return moduleInfo
|
|
@@ -477,43 +522,30 @@ def moduleInfo(pluginInfo):
|
|
|
477
522
|
moduleInfo.getdefault('classes', []).append(name)
|
|
478
523
|
|
|
479
524
|
|
|
480
|
-
def
|
|
481
|
-
|
|
482
|
-
pluginBase: str,
|
|
483
|
-
moduleURL: str,
|
|
484
|
-
packagePrefix: str = "",
|
|
485
|
-
) -> tuple[str, str, str] | tuple[None, None, None]:
|
|
486
|
-
"""Get the name, directory and prefix of a module."""
|
|
487
|
-
moduleFilename: str
|
|
488
|
-
moduleDir: str
|
|
489
|
-
packageImportPrefix: str
|
|
525
|
+
def _isAbsoluteModuleURL(moduleURL: str) -> bool:
|
|
526
|
+
return isAbsolute(moduleURL) or isLegacyAbs(moduleURL)
|
|
490
527
|
|
|
491
|
-
moduleFilename = controller.webCache.getfilename(
|
|
492
|
-
url=moduleURL, normalize=True, base=pluginBase, allowTransformation=False
|
|
493
|
-
)
|
|
494
528
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
os.path.join(moduleFilename, "__init__.py")
|
|
505
|
-
):
|
|
506
|
-
moduleDir = os.path.dirname(moduleFilename)
|
|
507
|
-
moduleName = os.path.basename(moduleFilename)
|
|
508
|
-
packageImportPrefix = moduleName + "."
|
|
509
|
-
else:
|
|
510
|
-
moduleName = os.path.basename(moduleFilename).partition(".")[0]
|
|
511
|
-
moduleDir = os.path.dirname(moduleFilename)
|
|
512
|
-
packageImportPrefix = packagePrefix
|
|
529
|
+
def _get_name_dir_prefix(modulePath: Path, packagePrefix: str = "") -> tuple[str, str, str] | tuple[None, None, None]:
|
|
530
|
+
"""Get the name, directory and prefix of a module."""
|
|
531
|
+
moduleName = None
|
|
532
|
+
moduleDir = None
|
|
533
|
+
packageImportPrefix = None
|
|
534
|
+
initFileName = "__init__.py"
|
|
535
|
+
|
|
536
|
+
if modulePath.is_file() and modulePath.name == initFileName:
|
|
537
|
+
modulePath = modulePath.parent
|
|
513
538
|
|
|
514
|
-
|
|
539
|
+
if modulePath.is_dir() and (modulePath / initFileName).is_file():
|
|
540
|
+
moduleName = modulePath.name
|
|
541
|
+
moduleDir = str(modulePath.parent)
|
|
542
|
+
packageImportPrefix = moduleName + "."
|
|
543
|
+
elif modulePath.is_file() and modulePath.suffix == ".py":
|
|
544
|
+
moduleName = modulePath.stem
|
|
545
|
+
moduleDir = str(modulePath.parent)
|
|
546
|
+
packageImportPrefix = packagePrefix
|
|
515
547
|
|
|
516
|
-
return (
|
|
548
|
+
return (moduleName, moduleDir, packageImportPrefix)
|
|
517
549
|
|
|
518
550
|
def _get_location(moduleDir: str, moduleName: str) -> str:
|
|
519
551
|
"""Get the file name of a plugin."""
|
|
@@ -543,13 +575,9 @@ def _find_and_load_module(moduleDir: str, moduleName: str) -> ModuleType | None:
|
|
|
543
575
|
def loadModule(moduleInfo: dict[str, Any], packagePrefix: str="") -> None:
|
|
544
576
|
name = moduleInfo['name']
|
|
545
577
|
moduleURL = moduleInfo['moduleURL']
|
|
578
|
+
modulePath = Path(moduleInfo['path'])
|
|
546
579
|
|
|
547
|
-
moduleName, moduleDir, packageImportPrefix = _get_name_dir_prefix(
|
|
548
|
-
controller=_cntlr,
|
|
549
|
-
pluginBase=_pluginBase,
|
|
550
|
-
moduleURL=moduleURL,
|
|
551
|
-
packagePrefix=packagePrefix,
|
|
552
|
-
)
|
|
580
|
+
moduleName, moduleDir, packageImportPrefix = _get_name_dir_prefix(modulePath, packagePrefix)
|
|
553
581
|
|
|
554
582
|
if all(p is None for p in [moduleName, moduleDir, packageImportPrefix]):
|
|
555
583
|
_cntlr.addToLog(message=_ERROR_MESSAGE_IMPORT_TEMPLATE.format(name), level=logging.ERROR)
|
|
@@ -566,10 +594,12 @@ def loadModule(moduleInfo: dict[str, Any], packagePrefix: str="") -> None:
|
|
|
566
594
|
localeDir = os.path.dirname(module.__file__) + os.sep + pluginInfo['localeURL']
|
|
567
595
|
try:
|
|
568
596
|
_gettext = gettext.translation(pluginInfo['localeDomain'], localeDir, getLanguageCodes())
|
|
569
|
-
except
|
|
570
|
-
_gettext
|
|
597
|
+
except OSError:
|
|
598
|
+
def _gettext(x):
|
|
599
|
+
return x # no translation
|
|
571
600
|
else:
|
|
572
|
-
_gettext
|
|
601
|
+
def _gettext(x):
|
|
602
|
+
return x
|
|
573
603
|
for key, value in pluginInfo.items():
|
|
574
604
|
if key == 'name':
|
|
575
605
|
if name:
|
|
@@ -627,8 +657,7 @@ def pluginClassMethods(className: str) -> Iterator[Callable[..., Any]]:
|
|
|
627
657
|
if className in pluginInfo:
|
|
628
658
|
pluginMethodsForClass.append(pluginInfo[className])
|
|
629
659
|
pluginMethodsForClasses[className] = pluginMethodsForClass
|
|
630
|
-
|
|
631
|
-
yield method
|
|
660
|
+
yield from pluginMethodsForClass
|
|
632
661
|
|
|
633
662
|
|
|
634
663
|
def addPluginModule(name: str) -> dict[str, Any] | None:
|
arelle/ValidateDuplicateFacts.py
CHANGED
arelle/XbrlConst.py
CHANGED
|
@@ -139,6 +139,7 @@ qnXlLocatorType = qname("{http://www.xbrl.org/2003/XLink}xl:locatorType")
|
|
|
139
139
|
qnXlResourceType = qname("{http://www.xbrl.org/2003/XLink}xl:resourceType")
|
|
140
140
|
qnXlArcType = qname("{http://www.xbrl.org/2003/XLink}xl:arcType")
|
|
141
141
|
xhtml = "http://www.w3.org/1999/xhtml"
|
|
142
|
+
qnXhtmlMeta = qname("{http://www.w3.org/1999/xhtml}meta")
|
|
142
143
|
ixbrl = "http://www.xbrl.org/2008/inlineXBRL"
|
|
143
144
|
ixbrl11 = "http://www.xbrl.org/2013/inlineXBRL"
|
|
144
145
|
ixbrlAll = {ixbrl, ixbrl11}
|
arelle/_version.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '2.37.
|
|
32
|
-
__version_tuple__ = version_tuple = (2, 37,
|
|
31
|
+
__version__ = version = '2.37.58'
|
|
32
|
+
__version_tuple__ = version_tuple = (2, 37, 58)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -10,25 +10,7 @@ class AccountingStandard(Enum):
|
|
|
10
10
|
JAPAN_GAAP = 'Japan GAAP'
|
|
11
11
|
US_GAAP = 'US GAAP'
|
|
12
12
|
|
|
13
|
-
class FormType(Enum):
|
|
14
|
-
FORM_2_4 = '第二号の四様式'
|
|
15
|
-
FORM_2_7 = '第二号の七様式'
|
|
16
|
-
FORM_3 = '第三号様式'
|
|
17
|
-
FORM_4 = '第四号様式'
|
|
18
13
|
|
|
19
|
-
@property
|
|
20
|
-
def isStockReport(self) -> bool:
|
|
21
|
-
return self in STOCK_REPORT_FORMS
|
|
22
|
-
|
|
23
|
-
CORPORATE_FORMS =frozenset([
|
|
24
|
-
FormType.FORM_2_4,
|
|
25
|
-
FormType.FORM_2_7,
|
|
26
|
-
FormType.FORM_3,
|
|
27
|
-
])
|
|
28
|
-
STOCK_REPORT_FORMS = frozenset([
|
|
29
|
-
FormType.FORM_3,
|
|
30
|
-
FormType.FORM_4,
|
|
31
|
-
])
|
|
32
14
|
qnEdinetManifestInsert = qname("{http://disclosure.edinet-fsa.go.jp/2013/manifest}insert")
|
|
33
15
|
qnEdinetManifestInstance = qname("{http://disclosure.edinet-fsa.go.jp/2013/manifest}instance")
|
|
34
16
|
qnEdinetManifestItem = qname("{http://disclosure.edinet-fsa.go.jp/2013/manifest}item")
|
|
@@ -76,3 +58,22 @@ PROHIBITED_HTML_TAGS = frozenset({
|
|
|
76
58
|
'select',
|
|
77
59
|
'textarea',
|
|
78
60
|
})
|
|
61
|
+
|
|
62
|
+
NUMERIC_LABEL_ROLES = frozenset({
|
|
63
|
+
'http://www.xbrl.org/2003/role/positiveLabel',
|
|
64
|
+
'http://www.xbrl.org/2003/role/positiveTerseLabel',
|
|
65
|
+
'http://www.xbrl.org/2003/role/positiveVerboseLabel',
|
|
66
|
+
'http://www.xbrl.org/2003/role/negativeLabel',
|
|
67
|
+
'http://www.xbrl.org/2003/role/negativeTerseLabel',
|
|
68
|
+
'http://www.xbrl.org/2003/role/negativeVerboseLabel',
|
|
69
|
+
'http://www.xbrl.org/2003/role/zeroLabel',
|
|
70
|
+
'http://www.xbrl.org/2003/role/zeroTerseLabel',
|
|
71
|
+
'http://www.xbrl.org/2003/role/zeroVerboseLabel',
|
|
72
|
+
'http://www.xbrl.org/2003/role/totalLabel',
|
|
73
|
+
'http://www.xbrl.org/2009/role/negatedLabel',
|
|
74
|
+
'http://www.xbrl.org/2009/role/negatedPeriodEndLabel',
|
|
75
|
+
'http://www.xbrl.org/2009/role/negatedPeriodStartLabel',
|
|
76
|
+
'http://www.xbrl.org/2009/role/negatedTotalLabel',
|
|
77
|
+
'http://www.xbrl.org/2009/role/negatedNetLabel',
|
|
78
|
+
'http://www.xbrl.org/2009/role/negatedTerseLabel',
|
|
79
|
+
})
|
|
@@ -12,9 +12,12 @@ from typing import TYPE_CHECKING
|
|
|
12
12
|
|
|
13
13
|
from arelle.Cntlr import Cntlr
|
|
14
14
|
from arelle.FileSource import FileSource
|
|
15
|
+
from arelle.ModelValue import QName
|
|
15
16
|
from arelle.typing import TypeGetText
|
|
16
17
|
from arelle.utils.PluginData import PluginData
|
|
17
18
|
from . import Constants
|
|
19
|
+
from .CoverPageRequirements import CoverPageRequirements
|
|
20
|
+
from .FilingFormat import FilingFormat
|
|
18
21
|
from .ReportFolderType import ReportFolderType
|
|
19
22
|
from .UploadContents import UploadContents, UploadPathInfo
|
|
20
23
|
|
|
@@ -45,6 +48,10 @@ class ControllerPluginData(PluginData):
|
|
|
45
48
|
"""
|
|
46
49
|
self._manifestInstancesById[manifestInstance.id] = manifestInstance
|
|
47
50
|
|
|
51
|
+
@lru_cache(1)
|
|
52
|
+
def getCoverPageRequirements(self, csvPath: Path, coverPageItems: tuple[QName, ...], filingFormats: tuple[FilingFormat, ...]) -> CoverPageRequirements:
|
|
53
|
+
return CoverPageRequirements(csvPath, coverPageItems, filingFormats)
|
|
54
|
+
|
|
48
55
|
def getManifestInstances(self) -> list[ManifestInstance]:
|
|
49
56
|
"""
|
|
50
57
|
Retrieve all loaded manifest instances.
|
|
@@ -147,10 +154,10 @@ class ControllerPluginData(PluginData):
|
|
|
147
154
|
if (fileSource.fs is None or
|
|
148
155
|
not isinstance(fileSource.fs, zipfile.ZipFile)):
|
|
149
156
|
if fileSource.cntlr is not None:
|
|
150
|
-
fileSource.cntlr.
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
157
|
+
fileSource.cntlr.addToLog(
|
|
158
|
+
_("The target file is not a zip file, so upload validation was not performed."),
|
|
159
|
+
messageCode="EDINET.uploadNotValidated",
|
|
160
|
+
file=str(fileSource.url)
|
|
154
161
|
)
|
|
155
162
|
return False
|
|
156
163
|
return True
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""
|
|
2
|
+
See COPYRIGHT.md for copyright information.
|
|
3
|
+
"""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from arelle.ModelValue import QName
|
|
10
|
+
from .FilingFormat import FilingFormat
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# Cover page requirements parsing is designed so that the contents of Attachment #5
|
|
14
|
+
# in "(Appendix) Taxonomy Extension Guideline" (ESE140111.zip), or future versions,
|
|
15
|
+
# can be easily exported to a CSV file where the rows correspond to Cover Page items
|
|
16
|
+
# andt the columns correspond to different formats.
|
|
17
|
+
class CoverPageRequirements:
|
|
18
|
+
_coverPageItems: tuple[QName, ...]
|
|
19
|
+
_csvPath: Path
|
|
20
|
+
_data: dict[QName, dict[FilingFormat, CoverPageItemStatus | None]] | None
|
|
21
|
+
_filingFormats: tuple[FilingFormat, ...]
|
|
22
|
+
|
|
23
|
+
def __init__(self, csvPath: Path, coverPageItems: tuple[QName, ...], filingFormats: tuple[FilingFormat, ...]):
|
|
24
|
+
self._coverPageItems = coverPageItems
|
|
25
|
+
self._csvPath = csvPath
|
|
26
|
+
self._data = None
|
|
27
|
+
self._filingFormats = filingFormats
|
|
28
|
+
|
|
29
|
+
def _load(self) -> dict[QName, dict[FilingFormat, CoverPageItemStatus | None]]:
|
|
30
|
+
if self._data is None:
|
|
31
|
+
with open(self._csvPath, encoding='utf-8') as f:
|
|
32
|
+
data = [
|
|
33
|
+
[
|
|
34
|
+
CoverPageItemStatus.parse(cell) for cell in line.strip().split(',')
|
|
35
|
+
]
|
|
36
|
+
for line in f.readlines()
|
|
37
|
+
]
|
|
38
|
+
self._data = {}
|
|
39
|
+
assert len(data) == len(self._coverPageItems), \
|
|
40
|
+
"Unexpected number of rows in cover page requirements CSV."
|
|
41
|
+
for rowIndex, row in enumerate(data):
|
|
42
|
+
assert len(row) == len(self._filingFormats), \
|
|
43
|
+
f"Unexpected number of columns in cover page requirements CSV at row {rowIndex}."
|
|
44
|
+
coverPageItem = self._coverPageItems[rowIndex]
|
|
45
|
+
self._data[coverPageItem] = {}
|
|
46
|
+
for colIndex, cell in enumerate(row):
|
|
47
|
+
filingFormat = self._filingFormats[colIndex]
|
|
48
|
+
self._data[coverPageItem][filingFormat] = cell
|
|
49
|
+
return self._data
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get(self, coverPageItem: QName, filingFormat: FilingFormat) -> CoverPageItemStatus | None:
|
|
53
|
+
data = self._load()
|
|
54
|
+
if coverPageItem not in data:
|
|
55
|
+
return None
|
|
56
|
+
if filingFormat not in data[coverPageItem]:
|
|
57
|
+
return None
|
|
58
|
+
return data[coverPageItem][filingFormat]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class CoverPageItemStatus(Enum):
|
|
62
|
+
# The values of the enum correspond to the symbols used in the spreadsheet.
|
|
63
|
+
PROHIBITED = '×'
|
|
64
|
+
OPTIONAL = '△'
|
|
65
|
+
CONDITIONAL = '○'
|
|
66
|
+
REQUIRED = '◎'
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def parse(cls, value: str) -> CoverPageItemStatus | None:
|
|
70
|
+
try:
|
|
71
|
+
return cls(value)
|
|
72
|
+
except ValueError:
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
# The below values are based on Attachment #5 in "(Appendix) Taxonomy Extension Guideline" (ESE140111.zip).
|
|
76
|
+
# Column D lists the elements of the cover page. Rows purely for grouping are omitted.
|
|
77
|
+
# The order is preserved. The index is used to map to other data structures.
|
|
78
|
+
COVER_PAGE_ITEM_LOCAL_NAMES = (
|
|
79
|
+
# Submitter Information (提出者情報)
|
|
80
|
+
'EDINETCodeDEI',
|
|
81
|
+
'FundCodeDEI',
|
|
82
|
+
'SecurityCodeDEI',
|
|
83
|
+
'FilerNameInJapaneseDEI',
|
|
84
|
+
'FilerNameInEnglishDEI',
|
|
85
|
+
'FundNameInJapaneseDEI',
|
|
86
|
+
'FundNameInEnglishDEI',
|
|
87
|
+
|
|
88
|
+
# Document Submission Information (提出書類情報)
|
|
89
|
+
'CabinetOfficeOrdinanceDEI',
|
|
90
|
+
'DocumentTypeDEI',
|
|
91
|
+
'AccountingStandardsDEI',
|
|
92
|
+
'WhetherConsolidatedFinancialStatementsArePreparedDEI',
|
|
93
|
+
'IndustryCodeWhenConsolidatedFinancialStatementsArePreparedInAccordanceWithIndustrySpecificRegulationsDEI',
|
|
94
|
+
'IndustryCodeWhenFinancialStatementsArePreparedInAccordanceWithIndustrySpecificRegulationsDEI',
|
|
95
|
+
|
|
96
|
+
# Current Fiscal Year (当会計期間)
|
|
97
|
+
'CurrentFiscalYearStartDateDEI',
|
|
98
|
+
'CurrentPeriodEndDateDEI',
|
|
99
|
+
'TypeOfCurrentPeriodDEI',
|
|
100
|
+
'CurrentFiscalYearEndDateDEI',
|
|
101
|
+
|
|
102
|
+
# Previous Fiscal Year (比較対象会計期間)
|
|
103
|
+
'PreviousFiscalYearStartDateDEI',
|
|
104
|
+
'ComparativePeriodEndDateDEI',
|
|
105
|
+
'PreviousFiscalYearEndDateDEI',
|
|
106
|
+
|
|
107
|
+
# Next Fiscal Year (次の中間期の会計期間)
|
|
108
|
+
'NextFiscalYearStartDateDEI',
|
|
109
|
+
'EndDateOfQuarterlyOrSemiAnnualPeriodOfNextFiscalYearDEI',
|
|
110
|
+
|
|
111
|
+
'NumberOfSubmissionDEI',
|
|
112
|
+
'AmendmentFlagDEI',
|
|
113
|
+
'IdentificationOfDocumentSubjectToAmendmentDEI',
|
|
114
|
+
|
|
115
|
+
# Type of Correction (訂正の種類)
|
|
116
|
+
'ReportAmendmentFlagDEI',
|
|
117
|
+
'XBRLAmendmentFlagDEI',
|
|
118
|
+
)
|