arelle-release 2.37.46__py3-none-any.whl → 2.38.0__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.
- arelle/BetaFeatures.py +0 -21
- arelle/Cntlr.py +15 -8
- arelle/CntlrCmdLine.py +121 -56
- arelle/CntlrWinMain.py +143 -70
- arelle/DialogFind.py +1 -1
- arelle/DialogPluginManager.py +6 -4
- arelle/DisclosureSystem.py +7 -0
- arelle/ErrorManager.py +21 -6
- arelle/FileSource.py +11 -4
- arelle/FunctionIxt.py +16 -11
- arelle/HtmlUtil.py +5 -4
- arelle/LeiUtil.py +63 -43
- arelle/ModelDocument.py +20 -15
- arelle/ModelDtsObject.py +8 -0
- arelle/ModelInstanceObject.py +1 -1
- arelle/ModelObject.py +16 -18
- arelle/ModelObjectFactory.py +35 -17
- arelle/ModelXbrl.py +28 -11
- arelle/PluginManager.py +130 -105
- arelle/RuntimeOptions.py +1 -0
- arelle/UrlUtil.py +14 -0
- arelle/Validate.py +17 -12
- arelle/ValidateDuplicateFacts.py +3 -1
- arelle/ValidateFileSource.py +38 -0
- arelle/ValidateFilingText.py +3 -3
- arelle/ValidateXbrl.py +5 -2
- arelle/ValidateXbrlCalcs.py +210 -186
- arelle/ValidateXbrlDTS.py +1 -1
- arelle/ViewFile.py +1 -0
- arelle/ViewFileFactTable.py +2 -2
- arelle/ViewWinDTS.py +4 -1
- arelle/WebCache.py +28 -24
- arelle/XbrlConst.py +22 -0
- arelle/XmlUtil.py +16 -21
- arelle/XmlValidate.py +6 -9
- arelle/_version.py +16 -3
- arelle/api/Session.py +11 -2
- arelle/config/disclosuresystems.xsd +2 -0
- arelle/config/rosettaEntitlements.plist +8 -0
- arelle/conformance/CSVTestcaseLoader.py +1 -1
- arelle/formula/XPathContext.py +3 -3
- arelle/logging/formatters/LogFormatter.py +3 -1
- arelle/packages/report/ReportPackage.py +26 -13
- arelle/packages/report/ReportPackageConst.py +0 -1
- arelle/plugin/inlineXbrlDocumentSet.py +19 -5
- arelle/plugin/validate/DBA/DisclosureSystems.py +19 -1
- arelle/plugin/validate/DBA/PluginValidationDataExtension.py +2 -4
- arelle/plugin/validate/DBA/ValidationPluginExtension.py +2 -1
- arelle/plugin/validate/DBA/resources/config.xml +5 -0
- arelle/plugin/validate/DBA/rules/__init__.py +2 -2
- arelle/plugin/validate/DBA/rules/fr.py +19 -2
- arelle/plugin/validate/DBA/rules/tc.py +2 -0
- arelle/plugin/validate/DBA/rules/th.py +6 -0
- arelle/plugin/validate/DBA/rules/tm.py +18 -5
- arelle/plugin/validate/DBA/rules/tr.py +11 -5
- arelle/plugin/validate/EDINET/Constants.py +193 -9
- arelle/plugin/validate/EDINET/ContextRequirement.py +58 -0
- arelle/plugin/validate/EDINET/ControllerPluginData.py +220 -1
- arelle/plugin/validate/EDINET/CoverItemRequirements.py +42 -0
- arelle/plugin/validate/EDINET/DeiRequirements.py +118 -0
- arelle/plugin/validate/EDINET/FilingFormat.py +275 -0
- arelle/plugin/validate/EDINET/FormType.py +134 -0
- arelle/plugin/validate/EDINET/ManifestInstance.py +72 -5
- arelle/plugin/validate/EDINET/NamespaceConfig.py +50 -0
- arelle/plugin/validate/EDINET/PluginValidationDataExtension.py +493 -132
- arelle/plugin/validate/EDINET/{InstanceType.py → ReportFolderType.py} +72 -15
- arelle/plugin/validate/EDINET/Statement.py +139 -0
- arelle/plugin/validate/EDINET/TableOfContentsBuilder.py +595 -0
- arelle/plugin/validate/EDINET/UploadContents.py +48 -0
- arelle/plugin/validate/EDINET/ValidationPluginExtension.py +20 -2
- arelle/plugin/validate/EDINET/__init__.py +31 -6
- arelle/plugin/validate/EDINET/resources/config.xml +8 -1
- arelle/plugin/validate/EDINET/resources/cover-item-requirements.json +793 -0
- arelle/plugin/validate/EDINET/resources/dei-requirements.csv +27 -0
- arelle/plugin/validate/EDINET/resources/edinet-taxonomies.xml +2 -0
- arelle/plugin/validate/EDINET/rules/contexts.py +375 -14
- arelle/plugin/validate/EDINET/rules/edinet.py +1934 -45
- arelle/plugin/validate/EDINET/rules/frta.py +122 -3
- arelle/plugin/validate/EDINET/rules/gfm.py +1907 -11
- arelle/plugin/validate/EDINET/rules/upload.py +989 -141
- arelle/plugin/validate/ESEF/Const.py +3 -1
- arelle/plugin/validate/ESEF/ESEF_2021/DTS.py +5 -0
- arelle/plugin/validate/ESEF/ESEF_2021/Image.py +2 -2
- arelle/plugin/validate/ESEF/ESEF_2021/ValidateXbrlFinally.py +23 -20
- arelle/plugin/validate/ESEF/ESEF_Current/DTS.py +47 -14
- arelle/plugin/validate/ESEF/ESEF_Current/ValidateXbrlFinally.py +100 -25
- arelle/plugin/validate/ESEF/__init__.py +20 -6
- arelle/plugin/validate/ESEF/resources/authority-validations.json +76 -9
- arelle/plugin/validate/ESEF/resources/config.xml +20 -0
- arelle/plugin/validate/NL/DisclosureSystems.py +22 -0
- arelle/plugin/validate/NL/PluginValidationDataExtension.py +27 -9
- arelle/plugin/validate/NL/ValidationPluginExtension.py +51 -7
- arelle/plugin/validate/NL/resources/config.xml +18 -0
- arelle/plugin/validate/NL/rules/br_kvk.py +17 -61
- arelle/plugin/validate/NL/rules/fg_nl.py +7 -38
- arelle/plugin/validate/NL/rules/fr_kvk.py +7 -42
- arelle/plugin/validate/NL/rules/fr_nl.py +31 -147
- arelle/plugin/validate/NL/rules/nl_kvk.py +142 -28
- arelle/plugin/validate/ROS/PluginValidationDataExtension.py +2 -0
- arelle/plugin/validate/ROS/ValidationPluginExtension.py +4 -1
- arelle/plugin/validate/ROS/rules/ros.py +41 -9
- arelle/plugin/validate/UK/ValidateUK.py +130 -66
- arelle/plugin/validate/UK/__init__.py +89 -103
- arelle/utils/EntryPointDetection.py +79 -13
- arelle/utils/PluginHooks.py +125 -0
- arelle/utils/validate/ESEFImage.py +6 -6
- arelle/utils/validate/Validation.py +18 -0
- arelle/utils/validate/ValidationPlugin.py +76 -11
- arelle/utils/validate/ValidationUtil.py +35 -3
- {arelle_release-2.37.46.dist-info → arelle_release-2.38.0.dist-info}/METADATA +30 -20
- {arelle_release-2.37.46.dist-info → arelle_release-2.38.0.dist-info}/RECORD +115 -191
- {arelle_release-2.37.46.dist-info → arelle_release-2.38.0.dist-info}/licenses/LICENSE.md +0 -3
- arelle/archive/CustomLogger.py +0 -43
- arelle/archive/LoadEFMvalidate.py +0 -32
- arelle/archive/LoadSavePreLbCsv.py +0 -26
- arelle/archive/LoadValidate.cs +0 -31
- arelle/archive/LoadValidate.py +0 -36
- arelle/archive/LoadValidateCmdLine.java +0 -69
- arelle/archive/LoadValidatePostedZip.java +0 -57
- arelle/archive/LoadValidateWebService.java +0 -34
- arelle/archive/SaveTableToExelle.py +0 -140
- arelle/archive/TR3toTR4.py +0 -88
- arelle/archive/plugin/ESEF_2022/__init__.py +0 -47
- arelle/archive/plugin/bigInstance.py +0 -394
- arelle/archive/plugin/cmdWebServerExtension.py +0 -43
- arelle/archive/plugin/crashTest.py +0 -38
- arelle/archive/plugin/functionsXmlCreation.py +0 -106
- arelle/archive/plugin/hello_i18n.pot +0 -26
- arelle/archive/plugin/hello_i18n.py +0 -32
- arelle/archive/plugin/importTestChild1.py +0 -21
- arelle/archive/plugin/importTestChild2.py +0 -22
- arelle/archive/plugin/importTestGrandchild1.py +0 -21
- arelle/archive/plugin/importTestGrandchild2.py +0 -21
- arelle/archive/plugin/importTestImported1.py +0 -23
- arelle/archive/plugin/importTestImported11.py +0 -22
- arelle/archive/plugin/importTestParent.py +0 -48
- arelle/archive/plugin/instanceInfo.py +0 -306
- arelle/archive/plugin/loadFromOIM-2018.py +0 -1282
- arelle/archive/plugin/locale/fr/LC_MESSAGES/hello_i18n.po +0 -25
- arelle/archive/plugin/objectmaker.py +0 -285
- arelle/archive/plugin/packagedImportTest/__init__.py +0 -47
- arelle/archive/plugin/packagedImportTest/importTestChild1.py +0 -21
- arelle/archive/plugin/packagedImportTest/importTestChild2.py +0 -22
- arelle/archive/plugin/packagedImportTest/importTestGrandchild1.py +0 -21
- arelle/archive/plugin/packagedImportTest/importTestGrandchild2.py +0 -21
- arelle/archive/plugin/packagedImportTest/importTestImported1.py +0 -24
- arelle/archive/plugin/packagedImportTest/importTestImported11.py +0 -21
- arelle/archive/plugin/packagedImportTest/subdir/importTestImported111.py +0 -21
- arelle/archive/plugin/packagedImportTest/subdir/subsubdir/importTestImported1111.py +0 -21
- arelle/archive/plugin/sakaCalendar.py +0 -215
- arelle/archive/plugin/saveInstanceInfoset.py +0 -121
- arelle/archive/plugin/sphinx/FormulaGenerator.py +0 -823
- arelle/archive/plugin/sphinx/SphinxContext.py +0 -404
- arelle/archive/plugin/sphinx/SphinxEvaluator.py +0 -783
- arelle/archive/plugin/sphinx/SphinxMethods.py +0 -1287
- arelle/archive/plugin/sphinx/SphinxParser.py +0 -1093
- arelle/archive/plugin/sphinx/SphinxValidator.py +0 -163
- arelle/archive/plugin/sphinx/US-GAAP Ratios Example.xsr +0 -52
- arelle/archive/plugin/sphinx/__init__.py +0 -285
- arelle/archive/plugin/streamingExtensions.py +0 -335
- arelle/archive/plugin/updateTableLB.py +0 -242
- arelle/archive/plugin/validate/SBRnl/CustomLoader.py +0 -19
- arelle/archive/plugin/validate/SBRnl/DTS.py +0 -305
- arelle/archive/plugin/validate/SBRnl/Dimensions.py +0 -357
- arelle/archive/plugin/validate/SBRnl/Document.py +0 -799
- arelle/archive/plugin/validate/SBRnl/Filing.py +0 -467
- arelle/archive/plugin/validate/SBRnl/__init__.py +0 -75
- arelle/archive/plugin/validate/SBRnl/config.xml +0 -26
- arelle/archive/plugin/validate/SBRnl/sbr-nl-taxonomies.xml +0 -754
- arelle/archive/plugin/validate/USBestPractices.py +0 -570
- arelle/archive/plugin/validate/USCorpAction.py +0 -557
- arelle/archive/plugin/validate/USSecTagging.py +0 -337
- arelle/archive/plugin/validate/XDC/__init__.py +0 -77
- arelle/archive/plugin/validate/XDC/config.xml +0 -20
- arelle/archive/plugin/validate/XFsyntax/__init__.py +0 -64
- arelle/archive/plugin/validate/XFsyntax/xf.py +0 -2227
- arelle/archive/plugin/validate/calc2.py +0 -536
- arelle/archive/plugin/validateSchemaLxml.py +0 -156
- arelle/archive/plugin/validateTableInfoset.py +0 -52
- arelle/archive/us-gaap-dei-docType-extraction-frm.xml +0 -90
- arelle/archive/us-gaap-dei-ratio-cash-frm.xml +0 -150
- arelle/examples/plugin/formulaSuiteConverter.py +0 -212
- arelle/examples/plugin/functionsCustom.py +0 -59
- arelle/examples/plugin/hello_dolly.py +0 -64
- arelle/examples/plugin/multi.py +0 -58
- arelle/examples/plugin/rssSaveOim.py +0 -96
- arelle/examples/plugin/validate/XYZ/DisclosureSystems.py +0 -2
- arelle/examples/plugin/validate/XYZ/PluginValidationDataExtension.py +0 -10
- arelle/examples/plugin/validate/XYZ/ValidationPluginExtension.py +0 -49
- arelle/examples/plugin/validate/XYZ/__init__.py +0 -75
- arelle/examples/plugin/validate/XYZ/resources/config.xml +0 -16
- arelle/examples/plugin/validate/XYZ/rules/__init__.py +0 -0
- arelle/examples/plugin/validate/XYZ/rules/rules01.py +0 -110
- arelle/examples/plugin/validate/XYZ/rules/rules02.py +0 -59
- arelle/model/CommentBase.py +0 -9
- arelle/model/ElementBase.py +0 -11
- arelle/model/PIBase.py +0 -10
- arelle/model/__init__.py +0 -15
- arelle/scripts-macOS/startWebServer.command +0 -3
- arelle/scripts-unix/startWebServer.sh +0 -1
- arelle/scripts-windows/startWebServer.bat +0 -5
- {arelle_release-2.37.46.dist-info → arelle_release-2.38.0.dist-info}/WHEEL +0 -0
- {arelle_release-2.37.46.dist-info → arelle_release-2.38.0.dist-info}/entry_points.txt +0 -0
- {arelle_release-2.37.46.dist-info → arelle_release-2.38.0.dist-info}/top_level.txt +0 -0
|
@@ -3,22 +3,47 @@ See COPYRIGHT.md for copyright information.
|
|
|
3
3
|
"""
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
|
-
import re
|
|
7
6
|
from collections import defaultdict
|
|
7
|
+
from collections.abc import Iterable
|
|
8
8
|
from pathlib import Path
|
|
9
|
-
from typing import
|
|
9
|
+
from typing import TYPE_CHECKING, Any
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
import regex
|
|
12
|
+
|
|
13
|
+
from arelle import UrlUtil, XbrlConst
|
|
14
|
+
from arelle.Cntlr import Cntlr
|
|
15
|
+
from arelle.FileSource import FileSource
|
|
16
|
+
from arelle.ModelDocument import Type as ModelDocumentType
|
|
17
|
+
from arelle.ModelInstanceObject import ModelFact, ModelInlineFact
|
|
18
|
+
from arelle.ModelObject import ModelObject
|
|
12
19
|
from arelle.typing import TypeGetText
|
|
13
20
|
from arelle.utils.PluginHooks import ValidationHook
|
|
14
21
|
from arelle.utils.validate.Decorator import validation
|
|
15
22
|
from arelle.utils.validate.Validation import Validation
|
|
16
|
-
from
|
|
17
|
-
from
|
|
23
|
+
from arelle.ValidateXbrl import ValidateXbrl
|
|
24
|
+
from arelle.XmlValidateConst import VALID
|
|
25
|
+
|
|
26
|
+
from ..Constants import JAPAN_LANGUAGE_CODES
|
|
27
|
+
from ..DisclosureSystems import DISCLOSURE_SYSTEM_EDINET
|
|
28
|
+
from ..FilingFormat import Ordinance, Taxonomy
|
|
18
29
|
from ..PluginValidationDataExtension import PluginValidationDataExtension
|
|
30
|
+
from ..ReportFolderType import HTML_EXTENSIONS, IMAGE_EXTENSIONS, ReportFolderType
|
|
31
|
+
|
|
32
|
+
if TYPE_CHECKING:
|
|
33
|
+
from ..ControllerPluginData import ControllerPluginData
|
|
19
34
|
|
|
20
35
|
_: TypeGetText
|
|
21
36
|
|
|
37
|
+
ALLOWED_ROOT_FOLDERS = {
|
|
38
|
+
"AttachDoc",
|
|
39
|
+
"AuditDoc",
|
|
40
|
+
"PrivateAttach",
|
|
41
|
+
"PrivateDoc",
|
|
42
|
+
"PublicAttach",
|
|
43
|
+
"PublicDoc",
|
|
44
|
+
"XBRL",
|
|
45
|
+
}
|
|
46
|
+
|
|
22
47
|
FILE_COUNT_LIMITS = {
|
|
23
48
|
Path("AttachDoc"): 990,
|
|
24
49
|
Path("AuditDoc"): 990,
|
|
@@ -31,88 +56,101 @@ FILE_COUNT_LIMITS = {
|
|
|
31
56
|
Path("XBRL"): 99_990,
|
|
32
57
|
}
|
|
33
58
|
|
|
34
|
-
FILENAME_STEM_PATTERN =
|
|
59
|
+
FILENAME_STEM_PATTERN = regex.compile(r'[a-zA-Z0-9_-]*')
|
|
35
60
|
|
|
36
61
|
|
|
37
62
|
@validation(
|
|
38
|
-
hook=ValidationHook.
|
|
63
|
+
hook=ValidationHook.FILESOURCE,
|
|
39
64
|
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
40
65
|
)
|
|
41
|
-
def
|
|
42
|
-
pluginData:
|
|
43
|
-
|
|
66
|
+
def rule_EC0100E(
|
|
67
|
+
pluginData: ControllerPluginData,
|
|
68
|
+
cntlr: Cntlr,
|
|
69
|
+
fileSource: FileSource,
|
|
44
70
|
*args: Any,
|
|
45
71
|
**kwargs: Any,
|
|
46
72
|
) -> Iterable[Validation]:
|
|
47
73
|
"""
|
|
48
|
-
EDINET.
|
|
49
|
-
|
|
74
|
+
EDINET.EC0100E: An illegal directory is found directly under the transferred directory.
|
|
75
|
+
Only the following root folders are allowed:
|
|
76
|
+
AttachDoc
|
|
77
|
+
AuditDoc*
|
|
78
|
+
PrivateAttach
|
|
79
|
+
PrivateDoc*
|
|
80
|
+
PublicAttach
|
|
81
|
+
PublicDoc*
|
|
82
|
+
XBRL
|
|
83
|
+
* Only when reporting corrections
|
|
50
84
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
names, or other files in unexpected locations.
|
|
85
|
+
NOTE: since we do not have access to the submission type, we can't determine if the submission is a correction or not.
|
|
86
|
+
For this implementation, we will allow all directories that may be valid for at least one submission type.
|
|
87
|
+
This allows for a false-negative outcome when a non-correction submission has a correction-only root directory.
|
|
55
88
|
"""
|
|
56
|
-
|
|
89
|
+
uploadContents = pluginData.getUploadContents()
|
|
90
|
+
if uploadContents is None:
|
|
57
91
|
return
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
for amendmentPaths in uploadContents.amendmentPaths.values():
|
|
61
|
-
paths.update(amendmentPaths)
|
|
62
|
-
for path in paths:
|
|
63
|
-
if len(str(path.name)) > 31 or not FILENAME_STEM_PATTERN.match(path.stem):
|
|
92
|
+
for path, pathInfo in uploadContents.uploadPathsByPath.items():
|
|
93
|
+
if pathInfo.isRoot and path.name not in ALLOWED_ROOT_FOLDERS:
|
|
64
94
|
yield Validation.error(
|
|
65
|
-
codes='EDINET.
|
|
66
|
-
msg=_("
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
basename=path.name,
|
|
73
|
-
file=str(path)
|
|
95
|
+
codes='EDINET.EC0100E',
|
|
96
|
+
msg=_("An illegal directory is found directly under the transferred directory. "
|
|
97
|
+
"Directory name or file name: '%(rootDirectory)s'. "
|
|
98
|
+
"Delete all folders except the following folders that exist directly "
|
|
99
|
+
"under the root folder, and then upload again: %(allowedDirectories)s."),
|
|
100
|
+
rootDirectory=path.name,
|
|
101
|
+
allowedDirectories=', '.join(f"'{d}'" for d in ALLOWED_ROOT_FOLDERS)
|
|
74
102
|
)
|
|
75
103
|
|
|
76
104
|
|
|
77
105
|
@validation(
|
|
78
|
-
hook=ValidationHook.
|
|
106
|
+
hook=ValidationHook.FILESOURCE,
|
|
79
107
|
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
80
108
|
)
|
|
81
|
-
def
|
|
82
|
-
pluginData:
|
|
83
|
-
|
|
109
|
+
def rule_EC0124E_EC0187E(
|
|
110
|
+
pluginData: ControllerPluginData,
|
|
111
|
+
cntlr: Cntlr,
|
|
112
|
+
fileSource: FileSource,
|
|
84
113
|
*args: Any,
|
|
85
114
|
**kwargs: Any,
|
|
86
115
|
) -> Iterable[Validation]:
|
|
87
116
|
"""
|
|
88
|
-
EDINET.EC0124E: There are no empty directories.
|
|
117
|
+
EDINET.EC0124E: There are no empty root directories.
|
|
118
|
+
EDINET.EC0187E: There are no empty subdirectories.
|
|
89
119
|
"""
|
|
90
|
-
|
|
91
|
-
return
|
|
92
|
-
uploadFilepaths = pluginData.getUploadFilepaths(val.modelXbrl)
|
|
120
|
+
uploadFilepaths = pluginData.getUploadFilepaths(fileSource)
|
|
93
121
|
emptyDirectories = []
|
|
94
|
-
for path in uploadFilepaths:
|
|
95
|
-
if
|
|
122
|
+
for path, zipPath in uploadFilepaths.items():
|
|
123
|
+
if not zipPath.is_dir():
|
|
96
124
|
continue
|
|
97
125
|
if not any(path in p.parents for p in uploadFilepaths):
|
|
98
|
-
emptyDirectories.append(
|
|
126
|
+
emptyDirectories.append(path)
|
|
99
127
|
for emptyDirectory in emptyDirectories:
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
128
|
+
if len(emptyDirectory.parts) <= 1:
|
|
129
|
+
yield Validation.error(
|
|
130
|
+
codes='EDINET.EC0124E',
|
|
131
|
+
msg=_("There is no file directly under '%(emptyDirectory)s'. "
|
|
132
|
+
"No empty root folders. "
|
|
133
|
+
"Please store the file in the appropriate folder or delete the folder and upload again."),
|
|
134
|
+
emptyDirectory=str(emptyDirectory),
|
|
135
|
+
)
|
|
136
|
+
else:
|
|
137
|
+
yield Validation.error(
|
|
138
|
+
codes='EDINET.EC0187E',
|
|
139
|
+
msg=_("'%(parentDirectory)s' contains a subordinate directory ('%(emptyDirectory)s') with no files. "
|
|
140
|
+
"Please store the file in the corresponding subfolder or delete the subfolder and upload again."),
|
|
141
|
+
parentDirectory=str(emptyDirectory.parent),
|
|
142
|
+
emptyDirectory=str(emptyDirectory),
|
|
143
|
+
)
|
|
107
144
|
|
|
108
145
|
|
|
109
146
|
@validation(
|
|
110
|
-
hook=ValidationHook.
|
|
147
|
+
hook=ValidationHook.FILESOURCE,
|
|
111
148
|
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
112
149
|
)
|
|
113
150
|
def rule_EC0129E(
|
|
114
|
-
pluginData:
|
|
115
|
-
|
|
151
|
+
pluginData: ControllerPluginData,
|
|
152
|
+
cntlr: Cntlr,
|
|
153
|
+
fileSource: FileSource,
|
|
116
154
|
*args: Any,
|
|
117
155
|
**kwargs: Any,
|
|
118
156
|
) -> Iterable[Validation]:
|
|
@@ -120,9 +158,7 @@ def rule_EC0129E(
|
|
|
120
158
|
EDINET.EC0129E: Limit the number of subfolders to 3 or less from the XBRL directory.
|
|
121
159
|
"""
|
|
122
160
|
startingDirectory = 'XBRL'
|
|
123
|
-
|
|
124
|
-
return
|
|
125
|
-
uploadFilepaths = pluginData.getUploadFilepaths(val.modelXbrl)
|
|
161
|
+
uploadFilepaths = pluginData.getUploadFilepaths(fileSource)
|
|
126
162
|
for path in uploadFilepaths:
|
|
127
163
|
parents = [parent.name for parent in path.parents]
|
|
128
164
|
if startingDirectory in parents:
|
|
@@ -144,37 +180,29 @@ def rule_EC0129E(
|
|
|
144
180
|
|
|
145
181
|
|
|
146
182
|
@validation(
|
|
147
|
-
hook=ValidationHook.
|
|
183
|
+
hook=ValidationHook.FILESOURCE,
|
|
148
184
|
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
149
185
|
)
|
|
150
186
|
def rule_EC0130E(
|
|
151
|
-
pluginData:
|
|
152
|
-
|
|
187
|
+
pluginData: ControllerPluginData,
|
|
188
|
+
cntlr: Cntlr,
|
|
189
|
+
fileSource: FileSource,
|
|
153
190
|
*args: Any,
|
|
154
191
|
**kwargs: Any,
|
|
155
192
|
) -> Iterable[Validation]:
|
|
156
193
|
"""
|
|
157
194
|
EDINET.EC0130E: File extensions must match the file extensions allowed in Figure 2-1-3 and Figure 2-1-5.
|
|
158
195
|
"""
|
|
159
|
-
|
|
196
|
+
uploadContents = pluginData.getUploadContents()
|
|
197
|
+
if uploadContents is None:
|
|
160
198
|
return
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
for instanceType, amendmentPaths in uploadContents.amendmentPaths.items():
|
|
164
|
-
for amendmentPath in amendmentPaths:
|
|
165
|
-
isSubdirectory = amendmentPath.parent.name != instanceType.value
|
|
166
|
-
checks.append((amendmentPath, True, instanceType, isSubdirectory))
|
|
167
|
-
for instanceType, formPaths in uploadContents.instances.items():
|
|
168
|
-
for amendmentPath in formPaths:
|
|
169
|
-
isSubdirectory = amendmentPath.parent.name != instanceType.value
|
|
170
|
-
checks.append((amendmentPath, False, instanceType, isSubdirectory))
|
|
171
|
-
for path, isAmendment, instanceType, isSubdirectory in checks:
|
|
172
|
-
ext = path.suffix
|
|
173
|
-
if len(ext) == 0:
|
|
199
|
+
for path, pathInfo in uploadContents.uploadPathsByPath.items():
|
|
200
|
+
if pathInfo.reportFolderType is None or pathInfo.isDirectory:
|
|
174
201
|
continue
|
|
175
|
-
validExtensions =
|
|
202
|
+
validExtensions = pathInfo.reportFolderType.getValidExtensions(pathInfo.isCorrection, pathInfo.isSubdirectory)
|
|
176
203
|
if validExtensions is None:
|
|
177
204
|
continue
|
|
205
|
+
ext = path.suffix
|
|
178
206
|
if ext not in validExtensions:
|
|
179
207
|
yield Validation.error(
|
|
180
208
|
codes='EDINET.EC0130E',
|
|
@@ -192,51 +220,68 @@ def rule_EC0130E(
|
|
|
192
220
|
|
|
193
221
|
|
|
194
222
|
@validation(
|
|
195
|
-
hook=ValidationHook.
|
|
223
|
+
hook=ValidationHook.FILESOURCE,
|
|
196
224
|
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
197
225
|
)
|
|
198
226
|
def rule_EC0132E(
|
|
199
|
-
pluginData:
|
|
200
|
-
|
|
227
|
+
pluginData: ControllerPluginData,
|
|
228
|
+
cntlr: Cntlr,
|
|
229
|
+
fileSource: FileSource,
|
|
201
230
|
*args: Any,
|
|
202
231
|
**kwargs: Any,
|
|
203
232
|
) -> Iterable[Validation]:
|
|
204
233
|
"""
|
|
205
|
-
EDINET.EC0132E:
|
|
234
|
+
EDINET.EC0132E: Cover page or manifest file is missing.
|
|
235
|
+
|
|
236
|
+
Note: Cover page is not required in AuditDoc.
|
|
206
237
|
"""
|
|
207
|
-
|
|
238
|
+
uploadContents = pluginData.getUploadContents()
|
|
239
|
+
if uploadContents is None:
|
|
208
240
|
return
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
continue
|
|
213
|
-
if instanceType.manifestPath in uploadContents.instances.get(instanceType, []):
|
|
241
|
+
for reportFolderType, paths in uploadContents.reports.items():
|
|
242
|
+
if reportFolderType.isAttachment:
|
|
243
|
+
# These rules don't apply to "Attach" directories
|
|
214
244
|
continue
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
245
|
+
coverPageFound = False
|
|
246
|
+
manifestFound = False
|
|
247
|
+
for path in paths:
|
|
248
|
+
pathInfo = uploadContents.uploadPathsByPath[path]
|
|
249
|
+
if pathInfo.isCoverPage:
|
|
250
|
+
coverPageFound = True
|
|
251
|
+
if path == reportFolderType.manifestPath:
|
|
252
|
+
manifestFound = True
|
|
253
|
+
if not coverPageFound and reportFolderType != ReportFolderType.AUDIT_DOC:
|
|
254
|
+
yield Validation.error(
|
|
255
|
+
codes='EDINET.EC0132E',
|
|
256
|
+
msg=_("Cover page does not exist in '%(expectedManifestDirectory)s'. "
|
|
257
|
+
"Please store the cover file directly under the relevant folder and upload it again. "),
|
|
258
|
+
expectedManifestDirectory=str(reportFolderType.manifestPath.parent),
|
|
259
|
+
)
|
|
260
|
+
if not manifestFound:
|
|
261
|
+
yield Validation.error(
|
|
262
|
+
codes='EDINET.EC0132E',
|
|
263
|
+
msg=_("'%(expectedManifestName)s' does not exist in '%(expectedManifestDirectory)s'. "
|
|
264
|
+
"Please store the manifest file directly under the relevant folder and upload it again. "),
|
|
265
|
+
expectedManifestName=reportFolderType.manifestPath.name,
|
|
266
|
+
expectedManifestDirectory=str(reportFolderType.manifestPath.parent),
|
|
267
|
+
)
|
|
222
268
|
|
|
223
269
|
|
|
224
270
|
@validation(
|
|
225
|
-
hook=ValidationHook.
|
|
271
|
+
hook=ValidationHook.FILESOURCE,
|
|
226
272
|
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
227
273
|
)
|
|
228
274
|
def rule_EC0183E(
|
|
229
|
-
pluginData:
|
|
230
|
-
|
|
275
|
+
pluginData: ControllerPluginData,
|
|
276
|
+
cntlr: Cntlr,
|
|
277
|
+
fileSource: FileSource,
|
|
231
278
|
*args: Any,
|
|
232
279
|
**kwargs: Any,
|
|
233
280
|
) -> Iterable[Validation]:
|
|
234
281
|
"""
|
|
235
282
|
EDINET.EC0183E: The compressed file size exceeds 55MB.
|
|
236
283
|
"""
|
|
237
|
-
|
|
238
|
-
return
|
|
239
|
-
size = val.modelXbrl.fileSource.getBytesSize()
|
|
284
|
+
size = fileSource.getBytesSize()
|
|
240
285
|
if size is None:
|
|
241
286
|
return # File size is not available, cannot validate
|
|
242
287
|
if size > 55_000_000: # Interpretting MB as megabytes (1,000,000 bytes)
|
|
@@ -248,22 +293,21 @@ def rule_EC0183E(
|
|
|
248
293
|
|
|
249
294
|
|
|
250
295
|
@validation(
|
|
251
|
-
hook=ValidationHook.
|
|
296
|
+
hook=ValidationHook.FILESOURCE,
|
|
252
297
|
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
253
298
|
)
|
|
254
299
|
def rule_EC0188E(
|
|
255
|
-
pluginData:
|
|
256
|
-
|
|
300
|
+
pluginData: ControllerPluginData,
|
|
301
|
+
cntlr: Cntlr,
|
|
302
|
+
fileSource: FileSource,
|
|
257
303
|
*args: Any,
|
|
258
304
|
**kwargs: Any,
|
|
259
305
|
) -> Iterable[Validation]:
|
|
260
306
|
"""
|
|
261
307
|
EDINET.EC0188E: There is an HTML file directly under PublicDoc or PrivateDoc whose first 7 characters are not numbers.
|
|
262
308
|
"""
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
pattern = re.compile(r'^\d{7}')
|
|
266
|
-
uploadFilepaths = pluginData.getUploadFilepaths(val.modelXbrl)
|
|
309
|
+
pattern = regex.compile(r'^\d{7}')
|
|
310
|
+
uploadFilepaths = pluginData.getUploadFilepaths(fileSource)
|
|
267
311
|
docFolders = frozenset({"PublicDoc", "PrivateDoc"})
|
|
268
312
|
for path in uploadFilepaths:
|
|
269
313
|
if path.suffix not in HTML_EXTENSIONS:
|
|
@@ -282,24 +326,55 @@ def rule_EC0188E(
|
|
|
282
326
|
|
|
283
327
|
|
|
284
328
|
@validation(
|
|
285
|
-
hook=ValidationHook.
|
|
329
|
+
hook=ValidationHook.FILESOURCE,
|
|
330
|
+
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
331
|
+
)
|
|
332
|
+
def rule_EC0192E(
|
|
333
|
+
pluginData: ControllerPluginData,
|
|
334
|
+
cntlr: Cntlr,
|
|
335
|
+
fileSource: FileSource,
|
|
336
|
+
*args: Any,
|
|
337
|
+
**kwargs: Any,
|
|
338
|
+
) -> Iterable[Validation]:
|
|
339
|
+
"""
|
|
340
|
+
EDINET.EC0192E: The cover file for PrivateDoc cannot be set because it uses a
|
|
341
|
+
PublicDoc cover file. Please delete the cover file from PrivateDoc and upload
|
|
342
|
+
it again.
|
|
343
|
+
"""
|
|
344
|
+
uploadContents = pluginData.getUploadContents()
|
|
345
|
+
if uploadContents is None:
|
|
346
|
+
return
|
|
347
|
+
for path, pathInfo in uploadContents.uploadPathsByPath.items():
|
|
348
|
+
if not pathInfo.isCoverPage:
|
|
349
|
+
continue
|
|
350
|
+
# Only applies to PrivateDoc correction reports
|
|
351
|
+
if pathInfo.isCorrection and pathInfo.reportFolderType == ReportFolderType.PRIVATE_DOC:
|
|
352
|
+
yield Validation.error(
|
|
353
|
+
codes='EDINET.EC0192E',
|
|
354
|
+
msg=_("The cover file for PrivateDoc ('%(file)s') cannot be set because it uses a PublicDoc cover file. "
|
|
355
|
+
"Please delete the cover file from PrivateDoc and upload it again."),
|
|
356
|
+
file=str(path),
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
@validation(
|
|
361
|
+
hook=ValidationHook.FILESOURCE,
|
|
286
362
|
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
287
363
|
)
|
|
288
364
|
def rule_EC0198E(
|
|
289
|
-
pluginData:
|
|
290
|
-
|
|
365
|
+
pluginData: ControllerPluginData,
|
|
366
|
+
cntlr: Cntlr,
|
|
367
|
+
fileSource: FileSource,
|
|
291
368
|
*args: Any,
|
|
292
369
|
**kwargs: Any,
|
|
293
370
|
) -> Iterable[Validation]:
|
|
294
371
|
"""
|
|
295
372
|
EDINET.EC0198E: The number of files in the total submission and directories can not exceed the upper limit.
|
|
296
373
|
"""
|
|
297
|
-
if not pluginData.shouldValidateUpload(val):
|
|
298
|
-
return
|
|
299
374
|
fileCounts: dict[Path, int] = defaultdict(int)
|
|
300
|
-
uploadFilepaths = pluginData.getUploadFilepaths(
|
|
301
|
-
for path in uploadFilepaths:
|
|
302
|
-
if
|
|
375
|
+
uploadFilepaths = pluginData.getUploadFilepaths(fileSource)
|
|
376
|
+
for path, zipPath in uploadFilepaths.items():
|
|
377
|
+
if zipPath.is_dir():
|
|
303
378
|
continue
|
|
304
379
|
for directory in FILE_COUNT_LIMITS.keys():
|
|
305
380
|
if directory in path.parents:
|
|
@@ -319,21 +394,100 @@ def rule_EC0198E(
|
|
|
319
394
|
|
|
320
395
|
|
|
321
396
|
@validation(
|
|
322
|
-
hook=ValidationHook.
|
|
397
|
+
hook=ValidationHook.FILESOURCE,
|
|
398
|
+
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
399
|
+
)
|
|
400
|
+
def rule_EC0233E(
|
|
401
|
+
pluginData: ControllerPluginData,
|
|
402
|
+
cntlr: Cntlr,
|
|
403
|
+
fileSource: FileSource,
|
|
404
|
+
*args: Any,
|
|
405
|
+
**kwargs: Any,
|
|
406
|
+
) -> Iterable[Validation]:
|
|
407
|
+
"""
|
|
408
|
+
EDINET.EC0233E: There is a file in the report directory that comes before the cover file
|
|
409
|
+
in file name sort order.
|
|
410
|
+
|
|
411
|
+
NOTE: This includes files in subdirectories. For example, PublicDoc/00000000_images/image.png
|
|
412
|
+
comes before PublicDoc/0000000_header_*.htm
|
|
413
|
+
"""
|
|
414
|
+
uploadContents = pluginData.getUploadContents()
|
|
415
|
+
if uploadContents is None:
|
|
416
|
+
return
|
|
417
|
+
directories = defaultdict(list)
|
|
418
|
+
for path in uploadContents.sortedPaths:
|
|
419
|
+
pathInfo = uploadContents.uploadPathsByPath[path]
|
|
420
|
+
if pathInfo.isDirectory:
|
|
421
|
+
continue
|
|
422
|
+
if pathInfo.reportFolderType in (ReportFolderType.PRIVATE_DOC, ReportFolderType.PUBLIC_DOC):
|
|
423
|
+
directories[pathInfo.reportPath].append(pathInfo)
|
|
424
|
+
for reportPath, pathInfos in directories.items():
|
|
425
|
+
coverPagePath = next(iter(p for p in pathInfos if p.isCoverPage), None)
|
|
426
|
+
if coverPagePath is None:
|
|
427
|
+
continue
|
|
428
|
+
errorPathInfos = pathInfos[:pathInfos.index(coverPagePath)]
|
|
429
|
+
for pathInfo in errorPathInfos:
|
|
430
|
+
yield Validation.error(
|
|
431
|
+
codes='EDINET.EC0233E',
|
|
432
|
+
msg=_("There is a file in the report directory in '%(reportPath)s' that comes before the cover "
|
|
433
|
+
"file ('%(coverPage)s') in file name sort order. "
|
|
434
|
+
"Directory name or file name: '%(path)s'. "
|
|
435
|
+
"Please make sure that there are no files that come before the cover file in the file "
|
|
436
|
+
"name sort order, and then upload again."),
|
|
437
|
+
reportPath=str(reportPath),
|
|
438
|
+
coverPage=str(coverPagePath.path.name),
|
|
439
|
+
path=str(pathInfo.path),
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
@validation(
|
|
444
|
+
hook=ValidationHook.FILESOURCE,
|
|
445
|
+
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
446
|
+
)
|
|
447
|
+
def rule_EC0234E(
|
|
448
|
+
pluginData: ControllerPluginData,
|
|
449
|
+
cntlr: Cntlr,
|
|
450
|
+
fileSource: FileSource,
|
|
451
|
+
*args: Any,
|
|
452
|
+
**kwargs: Any,
|
|
453
|
+
) -> Iterable[Validation]:
|
|
454
|
+
"""
|
|
455
|
+
EDINET.EC0234E: A cover file exists in an unsupported subdirectory.
|
|
456
|
+
"""
|
|
457
|
+
uploadContents = pluginData.getUploadContents()
|
|
458
|
+
if uploadContents is None:
|
|
459
|
+
return
|
|
460
|
+
for path, pathInfo in uploadContents.uploadPathsByPath.items():
|
|
461
|
+
if pathInfo.isDirectory:
|
|
462
|
+
continue
|
|
463
|
+
if pathInfo.reportFolderType not in (ReportFolderType.PRIVATE_DOC, ReportFolderType.PUBLIC_DOC):
|
|
464
|
+
continue
|
|
465
|
+
if pathInfo.isSubdirectory and pathInfo.isCoverPage:
|
|
466
|
+
yield Validation.error(
|
|
467
|
+
codes='EDINET.EC0234E',
|
|
468
|
+
msg=_("A cover file ('%(coverPage)s') exists in an unsupported subdirectory. "
|
|
469
|
+
"Directory: '%(directory)s'. "
|
|
470
|
+
"Please make sure there is no cover file in the subfolder and upload again."),
|
|
471
|
+
coverPage=str(path.name),
|
|
472
|
+
directory=str(path.parent),
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
@validation(
|
|
477
|
+
hook=ValidationHook.FILESOURCE,
|
|
323
478
|
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
324
479
|
)
|
|
325
480
|
def rule_EC0237E(
|
|
326
|
-
pluginData:
|
|
327
|
-
|
|
481
|
+
pluginData: ControllerPluginData,
|
|
482
|
+
cntlr: Cntlr,
|
|
483
|
+
fileSource: FileSource,
|
|
328
484
|
*args: Any,
|
|
329
485
|
**kwargs: Any,
|
|
330
486
|
) -> Iterable[Validation]:
|
|
331
487
|
"""
|
|
332
488
|
EDINET.EC0237E: The directory or file path to the lowest level exceeds the maximum value (259 characters).
|
|
333
489
|
"""
|
|
334
|
-
|
|
335
|
-
return
|
|
336
|
-
uploadFilepaths = pluginData.getUploadFilepaths(val.modelXbrl)
|
|
490
|
+
uploadFilepaths = pluginData.getUploadFilepaths(fileSource)
|
|
337
491
|
for path in uploadFilepaths:
|
|
338
492
|
if len(str(path)) <= 259:
|
|
339
493
|
continue
|
|
@@ -348,21 +502,20 @@ def rule_EC0237E(
|
|
|
348
502
|
|
|
349
503
|
|
|
350
504
|
@validation(
|
|
351
|
-
hook=ValidationHook.
|
|
505
|
+
hook=ValidationHook.FILESOURCE,
|
|
352
506
|
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
353
507
|
)
|
|
354
508
|
def rule_EC0206E(
|
|
355
|
-
pluginData:
|
|
356
|
-
|
|
509
|
+
pluginData: ControllerPluginData,
|
|
510
|
+
cntlr: Cntlr,
|
|
511
|
+
fileSource: FileSource,
|
|
357
512
|
*args: Any,
|
|
358
513
|
**kwargs: Any,
|
|
359
514
|
) -> Iterable[Validation]:
|
|
360
515
|
"""
|
|
361
516
|
EDINET.EC0206E: Empty files are not permitted.
|
|
362
517
|
"""
|
|
363
|
-
|
|
364
|
-
return
|
|
365
|
-
for path, size in pluginData.getUploadFileSizes(val.modelXbrl).items():
|
|
518
|
+
for path, size in pluginData.getUploadFileSizes(fileSource).items():
|
|
366
519
|
if size > 0:
|
|
367
520
|
continue
|
|
368
521
|
yield Validation.error(
|
|
@@ -375,22 +528,409 @@ def rule_EC0206E(
|
|
|
375
528
|
)
|
|
376
529
|
|
|
377
530
|
|
|
531
|
+
@validation(
|
|
532
|
+
hook=ValidationHook.FILESOURCE,
|
|
533
|
+
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
534
|
+
)
|
|
535
|
+
def rule_EC0349E(
|
|
536
|
+
pluginData: ControllerPluginData,
|
|
537
|
+
cntlr: Cntlr,
|
|
538
|
+
fileSource: FileSource,
|
|
539
|
+
*args: Any,
|
|
540
|
+
**kwargs: Any,
|
|
541
|
+
) -> Iterable[Validation]:
|
|
542
|
+
"""
|
|
543
|
+
EDINET.EC0349E: An unexpected directory or file exists directly beneath the XBRL directory.
|
|
544
|
+
Only PublicDoc, PrivateDoc, or AuditDoc directories may exist directly beneath the XBRL directory.
|
|
545
|
+
"""
|
|
546
|
+
uploadContents = pluginData.getUploadContents()
|
|
547
|
+
if uploadContents is None:
|
|
548
|
+
return
|
|
549
|
+
xbrlDirectoryPath = Path('XBRL')
|
|
550
|
+
allowedPaths = {p.xbrlDirectory for p in (
|
|
551
|
+
ReportFolderType.AUDIT_DOC,
|
|
552
|
+
ReportFolderType.PRIVATE_DOC,
|
|
553
|
+
ReportFolderType.PUBLIC_DOC,
|
|
554
|
+
)}
|
|
555
|
+
for path, pathInfo in uploadContents.uploadPathsByPath.items():
|
|
556
|
+
if path.parent != xbrlDirectoryPath:
|
|
557
|
+
continue
|
|
558
|
+
if path not in allowedPaths:
|
|
559
|
+
yield Validation.error(
|
|
560
|
+
codes='EDINET.EC0349E',
|
|
561
|
+
msg=_("An unexpected directory or file exists directly beneath the XBRL directory. "
|
|
562
|
+
"Directory or file name: '%(file)s'."),
|
|
563
|
+
file=path.name,
|
|
564
|
+
)
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
@validation(
|
|
568
|
+
hook=ValidationHook.FILESOURCE,
|
|
569
|
+
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
570
|
+
)
|
|
571
|
+
def rule_EC0352E(
|
|
572
|
+
pluginData: ControllerPluginData,
|
|
573
|
+
cntlr: Cntlr,
|
|
574
|
+
fileSource: FileSource,
|
|
575
|
+
*args: Any,
|
|
576
|
+
**kwargs: Any,
|
|
577
|
+
) -> Iterable[Validation]:
|
|
578
|
+
"""
|
|
579
|
+
EDINET.EC0352E: An XBRL file with an invalid name exists.
|
|
580
|
+
"""
|
|
581
|
+
uploadContents = pluginData.getUploadContents()
|
|
582
|
+
if uploadContents is None:
|
|
583
|
+
return
|
|
584
|
+
for path, pathInfo in uploadContents.uploadPathsByPath.items():
|
|
585
|
+
if (
|
|
586
|
+
pathInfo.isDirectory or
|
|
587
|
+
pathInfo.isCorrection or
|
|
588
|
+
pathInfo.isSubdirectory or
|
|
589
|
+
pathInfo.isAttachment or
|
|
590
|
+
pathInfo.reportFolderType is None or
|
|
591
|
+
any(path == t.manifestPath for t in ReportFolderType)
|
|
592
|
+
):
|
|
593
|
+
continue
|
|
594
|
+
patterns = pathInfo.reportFolderType.ixbrlFilenamePatterns
|
|
595
|
+
if not any(pattern.fullmatch(path.name) for pattern in patterns):
|
|
596
|
+
yield Validation.error(
|
|
597
|
+
codes='EDINET.EC0352E',
|
|
598
|
+
msg=_("A file with an invalid name exists. "
|
|
599
|
+
"File path: '%(path)s'."),
|
|
600
|
+
path=str(path),
|
|
601
|
+
)
|
|
602
|
+
|
|
603
|
+
|
|
378
604
|
@validation(
|
|
379
605
|
hook=ValidationHook.XBRL_FINALLY,
|
|
380
606
|
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
381
607
|
)
|
|
382
|
-
def
|
|
608
|
+
def rule_cover_items(
|
|
383
609
|
pluginData: PluginValidationDataExtension,
|
|
384
610
|
val: ValidateXbrl,
|
|
385
611
|
*args: Any,
|
|
386
612
|
**kwargs: Any,
|
|
387
613
|
) -> Iterable[Validation]:
|
|
388
614
|
"""
|
|
389
|
-
EDINET.
|
|
615
|
+
EDINET.EC1000E: Cover page must contain "【表紙】".
|
|
616
|
+
EDINET.EC1001E: A required item is missing from the cover page.
|
|
617
|
+
EDINET.EC1002E: A duplicate item is included on the cover page.
|
|
618
|
+
EDINET.EC1003E: An unnecessary item is included on the cover page.
|
|
619
|
+
EDINET.EC1004E: An item on the cover page is out of order.
|
|
620
|
+
EDINET.EC1005E: A required item on the cover page is missing a valid value.
|
|
621
|
+
"""
|
|
622
|
+
uploadContents = pluginData.getUploadContents(val.modelXbrl)
|
|
623
|
+
if uploadContents is None:
|
|
624
|
+
return
|
|
625
|
+
for url, doc in val.modelXbrl.urlDocs.items():
|
|
626
|
+
path = Path(url)
|
|
627
|
+
pathInfo = uploadContents.uploadPathsByFullPath.get(path)
|
|
628
|
+
if pathInfo is None or not pathInfo.isCoverPage:
|
|
629
|
+
continue
|
|
630
|
+
rootElt = doc.xmlRootElement
|
|
631
|
+
coverPageTextFound = False
|
|
632
|
+
for elt in rootElt.iterdescendants():
|
|
633
|
+
if not coverPageTextFound and elt.text and '【表紙】' in elt.text:
|
|
634
|
+
coverPageTextFound = True
|
|
635
|
+
break
|
|
636
|
+
if not coverPageTextFound:
|
|
637
|
+
yield Validation.error(
|
|
638
|
+
codes='EDINET.EC1000E',
|
|
639
|
+
msg=_("There is no '【表紙】' on the cover page. "
|
|
640
|
+
"File name: '%(file)s'. "
|
|
641
|
+
"Please add '【表紙】' to the relevant file."),
|
|
642
|
+
file=doc.basename,
|
|
643
|
+
)
|
|
644
|
+
filingFormat = pluginData.getFilingFormat(val.modelXbrl)
|
|
645
|
+
if filingFormat is None:
|
|
646
|
+
return
|
|
647
|
+
allCoverItems = pluginData.getCoverItems(val.modelXbrl)
|
|
648
|
+
requiredCoverItems = pluginData.getCoverItemRequirements(val.modelXbrl)
|
|
649
|
+
if requiredCoverItems is None:
|
|
650
|
+
return
|
|
651
|
+
prohibitedCoverItems = allCoverItems - set(requiredCoverItems)
|
|
652
|
+
sequenceQueue = list(requiredCoverItems)
|
|
653
|
+
|
|
654
|
+
ixNStag = doc.ixNStag
|
|
655
|
+
rootElt = doc.xmlRootElement
|
|
656
|
+
foundFactsByQname = defaultdict(list)
|
|
657
|
+
outOfSequence = False
|
|
658
|
+
seenInSequence = set()
|
|
659
|
+
for elt in rootElt.iterdescendants(ixNStag + "nonNumeric", ixNStag + "nonFraction", ixNStag + "fraction"):
|
|
660
|
+
if not isinstance(elt, ModelFact):
|
|
661
|
+
continue
|
|
662
|
+
if not elt.qname in allCoverItems:
|
|
663
|
+
continue
|
|
664
|
+
if elt.qname in prohibitedCoverItems:
|
|
665
|
+
yield Validation.error(
|
|
666
|
+
codes='EDINET.EC1003E',
|
|
667
|
+
msg=_("Cover item %(localName)s is not necessary. "
|
|
668
|
+
"File name: '%(file)s' (line %(line)s). "
|
|
669
|
+
"Please add the cover item %(localName)s to the relevant file."),
|
|
670
|
+
localName=elt.qname.localName,
|
|
671
|
+
file=doc.basename,
|
|
672
|
+
line=elt.sourceline,
|
|
673
|
+
modelObject=elt,
|
|
674
|
+
)
|
|
675
|
+
continue
|
|
676
|
+
foundFactsByQname[elt.qname].append(elt)
|
|
677
|
+
if elt.qname in seenInSequence:
|
|
678
|
+
yield Validation.error(
|
|
679
|
+
codes='EDINET.EC1002E',
|
|
680
|
+
msg=_("Cover item %(localName)s is duplicated. "
|
|
681
|
+
"File name: '%(file)s'. "
|
|
682
|
+
"Please check the cover item %(localName)s of the relevant file "
|
|
683
|
+
"and make sure there are no duplicates."),
|
|
684
|
+
localName=elt.qname.localName,
|
|
685
|
+
file=doc.basename,
|
|
686
|
+
modelObject=elt,
|
|
687
|
+
)
|
|
688
|
+
continue
|
|
689
|
+
seenInSequence.add(elt.qname)
|
|
690
|
+
if len(sequenceQueue) == 0:
|
|
691
|
+
continue
|
|
692
|
+
if outOfSequence:
|
|
693
|
+
continue
|
|
694
|
+
if not sequenceQueue[0] == elt.qname:
|
|
695
|
+
outOfSequence = True
|
|
696
|
+
yield Validation.error(
|
|
697
|
+
codes='EDINET.EC1004E',
|
|
698
|
+
msg=_("Cover item %(localName)s is not in the correct order. "
|
|
699
|
+
"File name: '%(file)s'. "
|
|
700
|
+
"Please correct the order of cover items in the appropriate file."),
|
|
701
|
+
localName=elt.qname.localName,
|
|
702
|
+
file=doc.basename,
|
|
703
|
+
modelObject=elt,
|
|
704
|
+
)
|
|
705
|
+
if elt.qname in sequenceQueue:
|
|
706
|
+
sequenceQueue.remove(elt.qname)
|
|
707
|
+
|
|
708
|
+
for qname in requiredCoverItems:
|
|
709
|
+
foundFacts = foundFactsByQname.get(qname, [])
|
|
710
|
+
# No facts found.
|
|
711
|
+
if len(foundFacts) == 0:
|
|
712
|
+
yield Validation.error(
|
|
713
|
+
codes='EDINET.EC1001E',
|
|
714
|
+
msg=_("Cover item %(localName)s is missing. "
|
|
715
|
+
"File name: '%(file)s'. "
|
|
716
|
+
"Please add the cover item %(localName)s to the relevant file."),
|
|
717
|
+
localName=qname.localName,
|
|
718
|
+
file=doc.basename,
|
|
719
|
+
)
|
|
720
|
+
# Fact(s) found, but no valid, non-nil value.
|
|
721
|
+
elif not any(f.xValid >= VALID and not f.isNil for f in foundFacts):
|
|
722
|
+
yield Validation.error(
|
|
723
|
+
codes='EDINET.EC1005E',
|
|
724
|
+
msg=_("Cover item %(localName)s is missing a valid value. "
|
|
725
|
+
"File name: '%(file)s'. "
|
|
726
|
+
"Please enter a valid value for %(localName)s in the relevant file."),
|
|
727
|
+
localName=qname.localName,
|
|
728
|
+
file=doc.basename,
|
|
729
|
+
modelObject=foundFacts,
|
|
730
|
+
)
|
|
731
|
+
|
|
732
|
+
|
|
733
|
+
@validation(
|
|
734
|
+
hook=ValidationHook.XBRL_FINALLY,
|
|
735
|
+
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
736
|
+
)
|
|
737
|
+
def rule_EC1006E(
|
|
738
|
+
pluginData: PluginValidationDataExtension,
|
|
739
|
+
val: ValidateXbrl,
|
|
740
|
+
*args: Any,
|
|
741
|
+
**kwargs: Any,
|
|
742
|
+
) -> Iterable[Validation]:
|
|
743
|
+
"""
|
|
744
|
+
EDINET.EC1006E: Prohibited tag is used in HTML.
|
|
745
|
+
"""
|
|
746
|
+
for doc in val.modelXbrl.urlDocs.values():
|
|
747
|
+
for elt in pluginData.getProhibitedTagElements(doc):
|
|
748
|
+
yield Validation.error(
|
|
749
|
+
codes='EDINET.EC1006E',
|
|
750
|
+
msg=_("Prohibited tag (%(tag)s) is used in HTML. File name: %(file)s (line %(line)s). "
|
|
751
|
+
"Please correct the prohibited tags for the relevant files. "
|
|
752
|
+
"For information on prohibited tags, please refer to \"4-1-4 Prohibited Rules\" "
|
|
753
|
+
"in the Validation Guidelines."),
|
|
754
|
+
tag=elt.qname.localName,
|
|
755
|
+
file=doc.basename,
|
|
756
|
+
line=elt.sourceline,
|
|
757
|
+
modelObject=elt,
|
|
758
|
+
)
|
|
759
|
+
|
|
760
|
+
|
|
761
|
+
@validation(
|
|
762
|
+
hook=ValidationHook.XBRL_FINALLY,
|
|
763
|
+
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
764
|
+
)
|
|
765
|
+
def rule_uri_references(
|
|
766
|
+
pluginData: PluginValidationDataExtension,
|
|
767
|
+
val: ValidateXbrl,
|
|
768
|
+
*args: Any,
|
|
769
|
+
**kwargs: Any,
|
|
770
|
+
) -> Iterable[Validation]:
|
|
771
|
+
"""
|
|
772
|
+
EDINET.EC1007E: A URI in an HTML file must not be a URL or absolute path.
|
|
773
|
+
EDINET.EC1013E: A URI in an HTML file directly beneath a report folder
|
|
774
|
+
must specify a path under a subdirectory.
|
|
775
|
+
EDINET.EC1014E: A URI in an HTML file must not specify a path to a directory.
|
|
776
|
+
EDINET.EC1015E: A URI in an HTML file within a subdirectory
|
|
777
|
+
must not specify a path directly beneath the report folder.
|
|
778
|
+
EDINET.EC1021E: A URI in an HTML file must not specify a path to a file that doesn't exist.
|
|
779
|
+
EDINET.EC1023E: A URI in an HTML file must not specify a path to a PDF file.
|
|
780
|
+
EDINET.EC1035E: A URI in an HTML file must not specify a path to a location higher than the report path.
|
|
781
|
+
|
|
782
|
+
Note: See "図表 3-4-8 PublicDoc フォルダ全体のイメージ" in "File Specification for EDINET".
|
|
390
783
|
"""
|
|
391
|
-
|
|
784
|
+
uploadContents = pluginData.getUploadContents(val.modelXbrl)
|
|
785
|
+
if uploadContents is None:
|
|
392
786
|
return
|
|
393
|
-
for
|
|
787
|
+
for uriReference in pluginData.uriReferences:
|
|
788
|
+
if UrlUtil.isAbsolute(uriReference.attributeValue):
|
|
789
|
+
yield Validation.error(
|
|
790
|
+
codes='EDINET.EC1007E',
|
|
791
|
+
msg=_("The URI in the HTML specifies a URL or absolute path. "
|
|
792
|
+
"File name: '%(file)s' (line %(line)s). "
|
|
793
|
+
"Please change the links in the files to relative paths."),
|
|
794
|
+
file=uriReference.document.basename,
|
|
795
|
+
line=uriReference.element.sourceline,
|
|
796
|
+
modelObject=uriReference.element,
|
|
797
|
+
)
|
|
798
|
+
continue
|
|
799
|
+
|
|
800
|
+
uriPath = Path(uriReference.attributeValue)
|
|
801
|
+
documentFullPath = Path(uriReference.document.uri)
|
|
802
|
+
referenceFullPath = (documentFullPath.parent / uriPath).resolve()
|
|
803
|
+
documentPathInfo = uploadContents.uploadPathsByFullPath.get(documentFullPath)
|
|
804
|
+
assert documentPathInfo is not None # Should always be present, as it must exist to have a uriReference discovered.
|
|
805
|
+
reportFullPath = Path(str(val.modelXbrl.fileSource.baseurl)) / (documentPathInfo.reportPath or "")
|
|
806
|
+
|
|
807
|
+
if reportFullPath not in referenceFullPath.parents:
|
|
808
|
+
yield Validation.error(
|
|
809
|
+
codes='EDINET.EC1035E',
|
|
810
|
+
msg=_("The URI in the HTML specifies a path that navigates "
|
|
811
|
+
"outside of the report folder '%(reportPath)s'. "
|
|
812
|
+
"File name: '%(file)s' (line %(line)s). "
|
|
813
|
+
"You cannot create a link from a subfolder to a parent folder. "
|
|
814
|
+
"Please delete the link."),
|
|
815
|
+
reportPath=str(documentPathInfo.reportPath),
|
|
816
|
+
file=uriReference.document.basename,
|
|
817
|
+
line=uriReference.element.sourceline,
|
|
818
|
+
modelObject=uriReference.element,
|
|
819
|
+
)
|
|
820
|
+
continue
|
|
821
|
+
|
|
822
|
+
if not documentPathInfo.isSubdirectory:
|
|
823
|
+
if documentFullPath.parent not in referenceFullPath.parent.parents:
|
|
824
|
+
yield Validation.error(
|
|
825
|
+
codes='EDINET.EC1013E',
|
|
826
|
+
msg=_("The URI in the HTML file directly beneath '%(reportPath)s' "
|
|
827
|
+
"specifies a path not under a subdirectory. "
|
|
828
|
+
"File name: '%(file)s' (line %(line)s). "
|
|
829
|
+
"Please move the referenced file into a subfolder beneath "
|
|
830
|
+
"'%(reportPath)s', or correct the URI."),
|
|
831
|
+
reportPath=str(documentPathInfo.reportPath),
|
|
832
|
+
file=uriReference.document.basename,
|
|
833
|
+
line=uriReference.element.sourceline,
|
|
834
|
+
modelObject=uriReference.element,
|
|
835
|
+
)
|
|
836
|
+
continue
|
|
837
|
+
|
|
838
|
+
elif referenceFullPath.parent == reportFullPath:
|
|
839
|
+
yield Validation.error(
|
|
840
|
+
codes='EDINET.EC1015E',
|
|
841
|
+
msg=_("The URI in the HTML file within a subdirectory specifies a "
|
|
842
|
+
"path to a file located directly beneath '%(reportPath)s'. "
|
|
843
|
+
"File name: '%(file)s' (line %(line)s). "
|
|
844
|
+
"You cannot create a link from a subfolder to this parent folder. "
|
|
845
|
+
"Please correct the relevant link."),
|
|
846
|
+
reportPath=str(documentPathInfo.reportPath),
|
|
847
|
+
file=uriReference.document.basename,
|
|
848
|
+
line=uriReference.element.sourceline,
|
|
849
|
+
modelObject=uriReference.element,
|
|
850
|
+
)
|
|
851
|
+
continue
|
|
852
|
+
|
|
853
|
+
referencePathInfo = uploadContents.uploadPathsByFullPath.get(referenceFullPath)
|
|
854
|
+
if referencePathInfo is not None and referencePathInfo.isDirectory:
|
|
855
|
+
yield Validation.error(
|
|
856
|
+
codes='EDINET.EC1014E',
|
|
857
|
+
msg=_("The URI in the HTML specifies a path to a directory. "
|
|
858
|
+
"File name: '%(file)s' (line %(line)s). "
|
|
859
|
+
"Please update the URI to reference a file."),
|
|
860
|
+
file=uriReference.document.basename,
|
|
861
|
+
line=uriReference.element.sourceline,
|
|
862
|
+
modelObject=uriReference.element,
|
|
863
|
+
)
|
|
864
|
+
continue
|
|
865
|
+
|
|
866
|
+
if referenceFullPath.suffix.lower() == '.pdf':
|
|
867
|
+
yield Validation.error(
|
|
868
|
+
codes='EDINET.EC1023E',
|
|
869
|
+
msg=_("The URI in the HTML specifies a path to a PDF file. "
|
|
870
|
+
"File name: '%(file)s' (line %(line)s). "
|
|
871
|
+
"Please remove the link from the relevant file."),
|
|
872
|
+
file=uriReference.document.basename,
|
|
873
|
+
line=uriReference.element.sourceline,
|
|
874
|
+
modelObject=uriReference.element,
|
|
875
|
+
)
|
|
876
|
+
continue
|
|
877
|
+
|
|
878
|
+
if not val.modelXbrl.fileSource.exists(str(referenceFullPath)):
|
|
879
|
+
yield Validation.error(
|
|
880
|
+
codes='EDINET.EC1021E',
|
|
881
|
+
msg=_("The linked file ('%(path)s') does not exist. "
|
|
882
|
+
"File name: '%(file)s' (line %(line)s). "
|
|
883
|
+
"Please update the URI to reference a file."),
|
|
884
|
+
path=str(uriPath),
|
|
885
|
+
file=uriReference.document.basename,
|
|
886
|
+
line=uriReference.element.sourceline,
|
|
887
|
+
modelObject=uriReference.element,
|
|
888
|
+
)
|
|
889
|
+
continue
|
|
890
|
+
|
|
891
|
+
|
|
892
|
+
@validation(
|
|
893
|
+
hook=ValidationHook.FILESOURCE,
|
|
894
|
+
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
895
|
+
)
|
|
896
|
+
def rule_EC1009R(
|
|
897
|
+
pluginData: ControllerPluginData,
|
|
898
|
+
cntlr: Cntlr,
|
|
899
|
+
fileSource: FileSource,
|
|
900
|
+
*args: Any,
|
|
901
|
+
**kwargs: Any,
|
|
902
|
+
) -> Iterable[Validation]:
|
|
903
|
+
"""
|
|
904
|
+
EDINET.EC1009R: The HTML file size must be 2.5MB (megabytes) or less.
|
|
905
|
+
"""
|
|
906
|
+
for path, size in pluginData.getUploadFileSizes(fileSource).items():
|
|
907
|
+
if path.suffix not in HTML_EXTENSIONS:
|
|
908
|
+
continue
|
|
909
|
+
if size > 2_500_000:
|
|
910
|
+
yield Validation.warning(
|
|
911
|
+
codes='EDINET.EC1009R',
|
|
912
|
+
msg=_("The HTML file size exceeds the maximum limit. "
|
|
913
|
+
"File name: '%(path)s'. "
|
|
914
|
+
"Please split the file so that the file size is 2.5MB or less."),
|
|
915
|
+
path=str(path),
|
|
916
|
+
)
|
|
917
|
+
|
|
918
|
+
|
|
919
|
+
@validation(
|
|
920
|
+
hook=ValidationHook.FILESOURCE,
|
|
921
|
+
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
922
|
+
)
|
|
923
|
+
def rule_EC1016E(
|
|
924
|
+
pluginData: ControllerPluginData,
|
|
925
|
+
cntlr: Cntlr,
|
|
926
|
+
fileSource: FileSource,
|
|
927
|
+
*args: Any,
|
|
928
|
+
**kwargs: Any,
|
|
929
|
+
) -> Iterable[Validation]:
|
|
930
|
+
"""
|
|
931
|
+
EDINET.EC1016E: The image file is over 300KB.
|
|
932
|
+
"""
|
|
933
|
+
for path, size in pluginData.getUploadFileSizes(fileSource).items():
|
|
394
934
|
if path.suffix not in IMAGE_EXTENSIONS:
|
|
395
935
|
continue
|
|
396
936
|
if size <= 300_000: # Interpretting KB as kilobytes (1,000 bytes)
|
|
@@ -405,25 +945,95 @@ def rule_EC1016E(
|
|
|
405
945
|
)
|
|
406
946
|
|
|
407
947
|
|
|
948
|
+
@validation(
|
|
949
|
+
hook=ValidationHook.COMPLETE,
|
|
950
|
+
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
951
|
+
)
|
|
952
|
+
def rule_EC1017E(
|
|
953
|
+
pluginData: ControllerPluginData,
|
|
954
|
+
cntlr: Cntlr,
|
|
955
|
+
fileSource: FileSource,
|
|
956
|
+
*args: Any,
|
|
957
|
+
**kwargs: Any,
|
|
958
|
+
) -> Iterable[Validation]:
|
|
959
|
+
"""
|
|
960
|
+
EDINET.EC1017E: There is an unused file.
|
|
961
|
+
"""
|
|
962
|
+
uploadContents = pluginData.getUploadContents()
|
|
963
|
+
if uploadContents is None:
|
|
964
|
+
return
|
|
965
|
+
existingSubdirectoryFilepaths = {
|
|
966
|
+
path
|
|
967
|
+
for path, pathInfo in uploadContents.uploadPathsByPath.items()
|
|
968
|
+
if pathInfo.isSubdirectory and not pathInfo.isDirectory
|
|
969
|
+
}
|
|
970
|
+
usedFilepaths = pluginData.getUsedFilepaths()
|
|
971
|
+
unusedSubdirectoryFilepaths = existingSubdirectoryFilepaths - usedFilepaths
|
|
972
|
+
for path in unusedSubdirectoryFilepaths:
|
|
973
|
+
yield Validation.error(
|
|
974
|
+
codes='EDINET.EC1017E',
|
|
975
|
+
msg=_("There is an unused file. "
|
|
976
|
+
"File name: '%(file)s'. "
|
|
977
|
+
"Please remove the file or reference it in the HTML."),
|
|
978
|
+
file=str(path),
|
|
979
|
+
)
|
|
980
|
+
|
|
981
|
+
|
|
982
|
+
@validation(
|
|
983
|
+
hook=ValidationHook.COMPLETE,
|
|
984
|
+
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
985
|
+
)
|
|
986
|
+
def rule_toc(
|
|
987
|
+
pluginData: ControllerPluginData,
|
|
988
|
+
cntlr: Cntlr,
|
|
989
|
+
fileSource: FileSource,
|
|
990
|
+
*args: Any,
|
|
991
|
+
**kwargs: Any,
|
|
992
|
+
) -> Iterable[Validation]:
|
|
993
|
+
"""
|
|
994
|
+
Performs validation via controller-level TableOfContentsBuilder.
|
|
995
|
+
"""
|
|
996
|
+
tocBuilder = pluginData.getTableOfContentsBuilder()
|
|
997
|
+
yield from tocBuilder.validate()
|
|
998
|
+
|
|
999
|
+
|
|
408
1000
|
@validation(
|
|
409
1001
|
hook=ValidationHook.XBRL_FINALLY,
|
|
410
1002
|
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
411
1003
|
)
|
|
412
|
-
def
|
|
1004
|
+
def rule_toc_pre(
|
|
413
1005
|
pluginData: PluginValidationDataExtension,
|
|
414
1006
|
val: ValidateXbrl,
|
|
415
1007
|
*args: Any,
|
|
416
1008
|
**kwargs: Any,
|
|
417
1009
|
) -> Iterable[Validation]:
|
|
418
1010
|
"""
|
|
1011
|
+
Doesn't perform validations, but prepares data for TableOfContentsBuilder.
|
|
1012
|
+
"""
|
|
1013
|
+
manifestInstance = pluginData.getManifestInstance(val.modelXbrl)
|
|
1014
|
+
if manifestInstance is not None and manifestInstance.type == ReportFolderType.PUBLIC_DOC.value:
|
|
1015
|
+
pluginData.addToTableOfContents(val.modelXbrl)
|
|
1016
|
+
return iter(())
|
|
1017
|
+
|
|
1018
|
+
|
|
1019
|
+
@validation(
|
|
1020
|
+
hook=ValidationHook.XBRL_FINALLY,
|
|
1021
|
+
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
1022
|
+
)
|
|
1023
|
+
def rule_html_elements(
|
|
1024
|
+
pluginData: PluginValidationDataExtension,
|
|
1025
|
+
val: ValidateXbrl,
|
|
1026
|
+
*args: Any,
|
|
1027
|
+
**kwargs: Any,
|
|
1028
|
+
) -> Iterable[Validation]:
|
|
1029
|
+
"""
|
|
1030
|
+
EDINET.EC1011E: The HTML lang attribute is not Japanese.
|
|
419
1031
|
EDINET.EC1020E: When writing a DOCTYPE declaration, do not define it multiple times.
|
|
420
|
-
|
|
1032
|
+
Also, please modify the relevant file so that there is only one html tag, one head tag, and one body tag each.
|
|
421
1033
|
|
|
422
|
-
Note: Some violations of
|
|
1034
|
+
Note: Some violations of EC1020E (such as multiple DOCTYPE declarations) prevent Arelle from parsing
|
|
423
1035
|
the XML at all, and thus an XML schema error will be triggered rather than this validation error.
|
|
424
1036
|
"""
|
|
425
|
-
if not pluginData.shouldValidateUpload(val):
|
|
426
|
-
return
|
|
427
1037
|
checkNames = frozenset({'body', 'head', 'html'})
|
|
428
1038
|
for modelDocument in val.modelXbrl.urlDocs.values():
|
|
429
1039
|
path = Path(modelDocument.uri)
|
|
@@ -434,11 +1044,25 @@ def rule_EC1020E(
|
|
|
434
1044
|
rootElt.qname.localName: 1
|
|
435
1045
|
}
|
|
436
1046
|
for elt in rootElt.iterdescendants():
|
|
437
|
-
|
|
438
|
-
if name not in checkNames:
|
|
1047
|
+
if not isinstance(elt, ModelObject):
|
|
439
1048
|
continue
|
|
440
|
-
|
|
441
|
-
|
|
1049
|
+
name = elt.qname.localName
|
|
1050
|
+
if name in checkNames:
|
|
1051
|
+
eltCounts[name] = eltCounts.get(name, 0) + 1
|
|
1052
|
+
if not isinstance(elt, ModelFact):
|
|
1053
|
+
lang = elt.get(XbrlConst.qnXmlLang.clarkNotation)
|
|
1054
|
+
if lang is not None and lang not in JAPAN_LANGUAGE_CODES:
|
|
1055
|
+
yield Validation.error(
|
|
1056
|
+
codes='EDINET.EC1011E',
|
|
1057
|
+
msg=_("The language setting is not Japanese. "
|
|
1058
|
+
"File name: %(file)s (line %(line)s). "
|
|
1059
|
+
"Please set the lang attribute on the given line of the "
|
|
1060
|
+
"relevant file to one of the following: %(langValues)s."),
|
|
1061
|
+
file=modelDocument.basename,
|
|
1062
|
+
line=elt.sourceline,
|
|
1063
|
+
langValues=', '.join(JAPAN_LANGUAGE_CODES),
|
|
1064
|
+
)
|
|
1065
|
+
|
|
442
1066
|
if any(count > 1 for count in eltCounts.values()):
|
|
443
1067
|
yield Validation.error(
|
|
444
1068
|
codes='EDINET.EC1020E',
|
|
@@ -456,11 +1080,202 @@ def rule_EC1020E(
|
|
|
456
1080
|
hook=ValidationHook.XBRL_FINALLY,
|
|
457
1081
|
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
458
1082
|
)
|
|
459
|
-
def
|
|
1083
|
+
def rule_EC1031E(
|
|
1084
|
+
pluginData: PluginValidationDataExtension,
|
|
1085
|
+
val: ValidateXbrl,
|
|
1086
|
+
*args: Any,
|
|
1087
|
+
**kwargs: Any,
|
|
1088
|
+
) -> Iterable[Validation]:
|
|
1089
|
+
"""
|
|
1090
|
+
EDINET.EC1031E: Prohibited attribute is used in HTML.
|
|
1091
|
+
"""
|
|
1092
|
+
for doc in val.modelXbrl.urlDocs.values():
|
|
1093
|
+
for elt, attributeName in pluginData.getProhibitedAttributeElements(doc):
|
|
1094
|
+
yield Validation.error(
|
|
1095
|
+
codes='EDINET.EC1031E',
|
|
1096
|
+
msg=_("Prohibited attribute '%(attributeName)s' is used in HTML. "
|
|
1097
|
+
"File name: %(file)s (line %(line)s). "
|
|
1098
|
+
"Please correct the tag attributes of the relevant file."),
|
|
1099
|
+
attributeName=attributeName,
|
|
1100
|
+
file=doc.basename,
|
|
1101
|
+
line=elt.sourceline,
|
|
1102
|
+
modelObject=elt,
|
|
1103
|
+
)
|
|
1104
|
+
|
|
1105
|
+
|
|
1106
|
+
@validation(
|
|
1107
|
+
hook=ValidationHook.FILESOURCE,
|
|
1108
|
+
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
1109
|
+
)
|
|
1110
|
+
def rule_EC5032E(
|
|
1111
|
+
pluginData: ControllerPluginData,
|
|
1112
|
+
cntlr: Cntlr,
|
|
1113
|
+
fileSource: FileSource,
|
|
1114
|
+
*args: Any,
|
|
1115
|
+
**kwargs: Any,
|
|
1116
|
+
) -> Iterable[Validation]:
|
|
1117
|
+
"""
|
|
1118
|
+
EDINET.EC5032E: A manifest file for an IFRS submission must not define multiple instances.
|
|
1119
|
+
"""
|
|
1120
|
+
instances = pluginData.getManifestInstances()
|
|
1121
|
+
instancesByManifest = defaultdict(list)
|
|
1122
|
+
for instance in instances:
|
|
1123
|
+
instancesByManifest[instance.path].append(instance)
|
|
1124
|
+
for manifestPath, instances in instancesByManifest.items():
|
|
1125
|
+
if len(instances) < 2:
|
|
1126
|
+
continue
|
|
1127
|
+
for instance in instances:
|
|
1128
|
+
if instance.filingFormat is None:
|
|
1129
|
+
continue
|
|
1130
|
+
if (
|
|
1131
|
+
instance.filingFormat.ordinance == Ordinance.IFRS or
|
|
1132
|
+
Taxonomy.IFRS in instance.filingFormat.taxonomies
|
|
1133
|
+
):
|
|
1134
|
+
yield Validation.error(
|
|
1135
|
+
codes='EDINET.EC5032E',
|
|
1136
|
+
msg=_("A manifest file for an IFRS submission defines multiple instances. "
|
|
1137
|
+
"File: '%(path)s'. "
|
|
1138
|
+
"If you use the IFRS taxonomy, please specify only one instance."),
|
|
1139
|
+
path=str(manifestPath),
|
|
1140
|
+
)
|
|
1141
|
+
break
|
|
1142
|
+
|
|
1143
|
+
|
|
1144
|
+
@validation(
|
|
1145
|
+
hook=ValidationHook.XBRL_FINALLY,
|
|
1146
|
+
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
1147
|
+
)
|
|
1148
|
+
def rule_EC8023W(
|
|
460
1149
|
pluginData: PluginValidationDataExtension,
|
|
461
1150
|
val: ValidateXbrl,
|
|
462
1151
|
*args: Any,
|
|
463
1152
|
**kwargs: Any,
|
|
1153
|
+
) -> Iterable[Validation]:
|
|
1154
|
+
"""
|
|
1155
|
+
EDINET.EC8023W: In IXBRL files, 'nonFraction' elements should be immediately preceded by
|
|
1156
|
+
'△' if and only if the sign attribute is '-'.
|
|
1157
|
+
|
|
1158
|
+
* Tagging using International Financial Reporting Standards taxonomy elements is not checked.
|
|
1159
|
+
* Tagging using Japanese GAAP notes or IFRS financial statement filer-specific additional elements
|
|
1160
|
+
may be identified as an exception and a warning displayed, even if the data content is correct.
|
|
1161
|
+
|
|
1162
|
+
Note: This implementation interprets "immediately preceded" to mean that the symbol is present in the text
|
|
1163
|
+
immediately before the target element, not nested within, or separated by, siblings elements. The use of
|
|
1164
|
+
this symbol in sample filings support this interpretation.
|
|
1165
|
+
"""
|
|
1166
|
+
negativeChar = '△'
|
|
1167
|
+
for fact in val.modelXbrl.facts:
|
|
1168
|
+
if not isinstance(fact, ModelInlineFact):
|
|
1169
|
+
continue
|
|
1170
|
+
if fact.localName != 'nonFraction':
|
|
1171
|
+
continue
|
|
1172
|
+
if fact.qname.namespaceURI == pluginData.namespaces.jpigp:
|
|
1173
|
+
continue
|
|
1174
|
+
|
|
1175
|
+
precedingChar = None
|
|
1176
|
+
precedingSibling = fact.getprevious()
|
|
1177
|
+
# Check for the tail of the preceding sibling first.
|
|
1178
|
+
if precedingSibling is not None:
|
|
1179
|
+
if precedingSibling.tail:
|
|
1180
|
+
strippedText = precedingSibling.tail.strip()
|
|
1181
|
+
if strippedText:
|
|
1182
|
+
precedingChar = strippedText[-1]
|
|
1183
|
+
# If nothing found, check the parent element if this is the first child.
|
|
1184
|
+
elif (parent := fact.getparent()) is not None:
|
|
1185
|
+
if fact == list(parent)[0] and parent.text:
|
|
1186
|
+
strippedText = parent.text.strip()
|
|
1187
|
+
if strippedText:
|
|
1188
|
+
precedingChar = strippedText[-1]
|
|
1189
|
+
|
|
1190
|
+
if fact.sign == '-' :
|
|
1191
|
+
if precedingChar != negativeChar:
|
|
1192
|
+
yield Validation.error(
|
|
1193
|
+
codes='EDINET.EC8023W',
|
|
1194
|
+
msg=_("In an inline XBRL file, if the sign attribute of the ix:nonFraction "
|
|
1195
|
+
"element is set to \"-\" (minus), you must set \"△\" immediately "
|
|
1196
|
+
"before the ix:nonFraction element tag."),
|
|
1197
|
+
modelObject=fact,
|
|
1198
|
+
)
|
|
1199
|
+
else:
|
|
1200
|
+
if precedingChar == negativeChar:
|
|
1201
|
+
yield Validation.error(
|
|
1202
|
+
codes='EDINET.EC8023W',
|
|
1203
|
+
msg=_("In an inline XBRL file, if the sign attribute of the ix:nonFraction "
|
|
1204
|
+
"element is not set to \"-\" (minus), there is no need to set \"△\" "
|
|
1205
|
+
"immediately before the ix:nonFraction element tag."),
|
|
1206
|
+
modelObject=fact,
|
|
1207
|
+
)
|
|
1208
|
+
|
|
1209
|
+
|
|
1210
|
+
|
|
1211
|
+
@validation(
|
|
1212
|
+
hook=ValidationHook.FILESOURCE,
|
|
1213
|
+
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
1214
|
+
)
|
|
1215
|
+
def rule_filenames(
|
|
1216
|
+
pluginData: ControllerPluginData,
|
|
1217
|
+
cntlr: Cntlr,
|
|
1218
|
+
fileSource: FileSource,
|
|
1219
|
+
*args: Any,
|
|
1220
|
+
**kwargs: Any,
|
|
1221
|
+
) -> Iterable[Validation]:
|
|
1222
|
+
"""
|
|
1223
|
+
EDINET.EC0121E: There is a directory or file that contains
|
|
1224
|
+
more than 31 characters or uses characters other than those allowed (alphanumeric characters,
|
|
1225
|
+
'-' and '_').
|
|
1226
|
+
Note: Applies to everything EXCEPT files directly beneath non-correction report folders.
|
|
1227
|
+
|
|
1228
|
+
EDINET.EC0200E: There is a file that uses characters other
|
|
1229
|
+
than those allowed (alphanumeric characters, '-' and '_').
|
|
1230
|
+
Note: Applies ONLY to files directly beneath non-correction report folders.
|
|
1231
|
+
"""
|
|
1232
|
+
uploadContents = pluginData.getUploadContents()
|
|
1233
|
+
if uploadContents is None:
|
|
1234
|
+
return
|
|
1235
|
+
for path, pathInfo in uploadContents.uploadPathsByPath.items():
|
|
1236
|
+
isReportFile = (
|
|
1237
|
+
not pathInfo.isAttachment and
|
|
1238
|
+
not pathInfo.isCorrection and
|
|
1239
|
+
not pathInfo.isDirectory and
|
|
1240
|
+
not pathInfo.isSubdirectory
|
|
1241
|
+
)
|
|
1242
|
+
charactersAreValid = FILENAME_STEM_PATTERN.fullmatch(path.stem)
|
|
1243
|
+
lengthIsValid = isReportFile or (len(path.name) <= 31)
|
|
1244
|
+
if charactersAreValid and lengthIsValid:
|
|
1245
|
+
continue
|
|
1246
|
+
if isReportFile:
|
|
1247
|
+
yield Validation.error(
|
|
1248
|
+
codes='EDINET.EC0200E',
|
|
1249
|
+
msg=_("There is a file inside the XBRL directory that uses characters "
|
|
1250
|
+
"other than those allowed (alphanumeric characters, '-' and '_'). "
|
|
1251
|
+
"File: '%(path)s'. "
|
|
1252
|
+
"Please change the filename to usable characters, and upload again."),
|
|
1253
|
+
path=str(path)
|
|
1254
|
+
)
|
|
1255
|
+
else:
|
|
1256
|
+
yield Validation.error(
|
|
1257
|
+
codes='EDINET.EC0121E',
|
|
1258
|
+
msg=_("There is a directory or file in '%(directory)s' that contains more "
|
|
1259
|
+
"than 31 characters or uses characters other than those allowed "
|
|
1260
|
+
"(alphanumeric characters, '-' and '_'). "
|
|
1261
|
+
"Directory or filename: '%(basename)s'. "
|
|
1262
|
+
"Please change the file name (or folder name) to within 31 characters and to usable "
|
|
1263
|
+
"characters, and upload again."),
|
|
1264
|
+
directory=str(path.parent),
|
|
1265
|
+
basename=path.name,
|
|
1266
|
+
)
|
|
1267
|
+
|
|
1268
|
+
|
|
1269
|
+
@validation(
|
|
1270
|
+
hook=ValidationHook.FILESOURCE,
|
|
1271
|
+
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
1272
|
+
)
|
|
1273
|
+
def rule_manifest_preferredFilename(
|
|
1274
|
+
pluginData: ControllerPluginData,
|
|
1275
|
+
cntlr: Cntlr,
|
|
1276
|
+
fileSource: FileSource,
|
|
1277
|
+
*args: Any,
|
|
1278
|
+
**kwargs: Any,
|
|
464
1279
|
) -> Iterable[Validation]:
|
|
465
1280
|
"""
|
|
466
1281
|
EDINET.EC5804E: The preferredFilename attribute must be set on the instance
|
|
@@ -473,10 +1288,14 @@ def rule_manifest_preferredFilename(
|
|
|
473
1288
|
EDINET.EC5806E: The same instance file name is set multiple times. File name: xxx
|
|
474
1289
|
The preferredFilename attribute value of the instance element in the manifest
|
|
475
1290
|
file must be unique within the same file.
|
|
1291
|
+
|
|
1292
|
+
EDINET.EC8008W: The file name of the report instance set in the manifest file
|
|
1293
|
+
does not conform to the rules.
|
|
1294
|
+
|
|
1295
|
+
EDINET.EC8009W: The file name of the audit report instance set in the manifest file
|
|
1296
|
+
does not conform to the rules.
|
|
476
1297
|
"""
|
|
477
|
-
|
|
478
|
-
return
|
|
479
|
-
instances = pluginData.getManifestInstances(val.modelXbrl)
|
|
1298
|
+
instances = pluginData.getManifestInstances()
|
|
480
1299
|
preferredFilenames: dict[Path, set[str]] = defaultdict(set)
|
|
481
1300
|
duplicateFilenames = defaultdict(set)
|
|
482
1301
|
for instance in instances:
|
|
@@ -490,6 +1309,7 @@ def rule_manifest_preferredFilename(
|
|
|
490
1309
|
id=instance.id,
|
|
491
1310
|
)
|
|
492
1311
|
continue
|
|
1312
|
+
|
|
493
1313
|
preferredFilename = Path(instance.preferredFilename)
|
|
494
1314
|
if preferredFilename.suffix != '.xbrl':
|
|
495
1315
|
yield Validation.error(
|
|
@@ -504,6 +1324,34 @@ def rule_manifest_preferredFilename(
|
|
|
504
1324
|
id=instance.id,
|
|
505
1325
|
)
|
|
506
1326
|
continue
|
|
1327
|
+
|
|
1328
|
+
reportFolderType = ReportFolderType.parse(instance.type)
|
|
1329
|
+
match = True if reportFolderType is None else any(
|
|
1330
|
+
pattern.fullmatch(preferredFilename.name)
|
|
1331
|
+
for pattern in reportFolderType.xbrlFilenamePatterns
|
|
1332
|
+
)
|
|
1333
|
+
if not match:
|
|
1334
|
+
if reportFolderType == ReportFolderType.AUDIT_DOC:
|
|
1335
|
+
yield Validation.warning(
|
|
1336
|
+
codes='EDINET.EC8009W',
|
|
1337
|
+
msg=_("The file name of the audit report instance set in the manifest "
|
|
1338
|
+
"file does not conform to the rules. "
|
|
1339
|
+
"File name: '%(file)s'. "
|
|
1340
|
+
"Please set the file name of the corresponding audit report instance "
|
|
1341
|
+
"according to the rules. Please correct the contents of the manifest file."),
|
|
1342
|
+
file=preferredFilename.name,
|
|
1343
|
+
)
|
|
1344
|
+
else:
|
|
1345
|
+
yield Validation.warning(
|
|
1346
|
+
codes='EDINET.EC8008W',
|
|
1347
|
+
msg=_("The file name of the report instance set in the manifest "
|
|
1348
|
+
"file does not comply with the regulations. "
|
|
1349
|
+
"File name: '%(file)s'. "
|
|
1350
|
+
"Please set the file name of the corresponding report instance "
|
|
1351
|
+
"according to the rules. Please correct the contents of the manifest file."),
|
|
1352
|
+
file=preferredFilename.name,
|
|
1353
|
+
)
|
|
1354
|
+
|
|
507
1355
|
if instance.preferredFilename in preferredFilenames[instance.path]:
|
|
508
1356
|
duplicateFilenames[instance.path].add(instance.preferredFilename)
|
|
509
1357
|
continue
|