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.
Files changed (204) hide show
  1. arelle/BetaFeatures.py +0 -21
  2. arelle/Cntlr.py +15 -8
  3. arelle/CntlrCmdLine.py +121 -56
  4. arelle/CntlrWinMain.py +143 -70
  5. arelle/DialogFind.py +1 -1
  6. arelle/DialogPluginManager.py +6 -4
  7. arelle/DisclosureSystem.py +7 -0
  8. arelle/ErrorManager.py +21 -6
  9. arelle/FileSource.py +11 -4
  10. arelle/FunctionIxt.py +16 -11
  11. arelle/HtmlUtil.py +5 -4
  12. arelle/LeiUtil.py +63 -43
  13. arelle/ModelDocument.py +20 -15
  14. arelle/ModelDtsObject.py +8 -0
  15. arelle/ModelInstanceObject.py +1 -1
  16. arelle/ModelObject.py +16 -18
  17. arelle/ModelObjectFactory.py +35 -17
  18. arelle/ModelXbrl.py +28 -11
  19. arelle/PluginManager.py +130 -105
  20. arelle/RuntimeOptions.py +1 -0
  21. arelle/UrlUtil.py +14 -0
  22. arelle/Validate.py +17 -12
  23. arelle/ValidateDuplicateFacts.py +3 -1
  24. arelle/ValidateFileSource.py +38 -0
  25. arelle/ValidateFilingText.py +3 -3
  26. arelle/ValidateXbrl.py +5 -2
  27. arelle/ValidateXbrlCalcs.py +210 -186
  28. arelle/ValidateXbrlDTS.py +1 -1
  29. arelle/ViewFile.py +1 -0
  30. arelle/ViewFileFactTable.py +2 -2
  31. arelle/ViewWinDTS.py +4 -1
  32. arelle/WebCache.py +28 -24
  33. arelle/XbrlConst.py +22 -0
  34. arelle/XmlUtil.py +16 -21
  35. arelle/XmlValidate.py +6 -9
  36. arelle/_version.py +16 -3
  37. arelle/api/Session.py +11 -2
  38. arelle/config/disclosuresystems.xsd +2 -0
  39. arelle/config/rosettaEntitlements.plist +8 -0
  40. arelle/conformance/CSVTestcaseLoader.py +1 -1
  41. arelle/formula/XPathContext.py +3 -3
  42. arelle/logging/formatters/LogFormatter.py +3 -1
  43. arelle/packages/report/ReportPackage.py +26 -13
  44. arelle/packages/report/ReportPackageConst.py +0 -1
  45. arelle/plugin/inlineXbrlDocumentSet.py +19 -5
  46. arelle/plugin/validate/DBA/DisclosureSystems.py +19 -1
  47. arelle/plugin/validate/DBA/PluginValidationDataExtension.py +2 -4
  48. arelle/plugin/validate/DBA/ValidationPluginExtension.py +2 -1
  49. arelle/plugin/validate/DBA/resources/config.xml +5 -0
  50. arelle/plugin/validate/DBA/rules/__init__.py +2 -2
  51. arelle/plugin/validate/DBA/rules/fr.py +19 -2
  52. arelle/plugin/validate/DBA/rules/tc.py +2 -0
  53. arelle/plugin/validate/DBA/rules/th.py +6 -0
  54. arelle/plugin/validate/DBA/rules/tm.py +18 -5
  55. arelle/plugin/validate/DBA/rules/tr.py +11 -5
  56. arelle/plugin/validate/EDINET/Constants.py +193 -9
  57. arelle/plugin/validate/EDINET/ContextRequirement.py +58 -0
  58. arelle/plugin/validate/EDINET/ControllerPluginData.py +220 -1
  59. arelle/plugin/validate/EDINET/CoverItemRequirements.py +42 -0
  60. arelle/plugin/validate/EDINET/DeiRequirements.py +118 -0
  61. arelle/plugin/validate/EDINET/FilingFormat.py +275 -0
  62. arelle/plugin/validate/EDINET/FormType.py +134 -0
  63. arelle/plugin/validate/EDINET/ManifestInstance.py +72 -5
  64. arelle/plugin/validate/EDINET/NamespaceConfig.py +50 -0
  65. arelle/plugin/validate/EDINET/PluginValidationDataExtension.py +493 -132
  66. arelle/plugin/validate/EDINET/{InstanceType.py → ReportFolderType.py} +72 -15
  67. arelle/plugin/validate/EDINET/Statement.py +139 -0
  68. arelle/plugin/validate/EDINET/TableOfContentsBuilder.py +595 -0
  69. arelle/plugin/validate/EDINET/UploadContents.py +48 -0
  70. arelle/plugin/validate/EDINET/ValidationPluginExtension.py +20 -2
  71. arelle/plugin/validate/EDINET/__init__.py +31 -6
  72. arelle/plugin/validate/EDINET/resources/config.xml +8 -1
  73. arelle/plugin/validate/EDINET/resources/cover-item-requirements.json +793 -0
  74. arelle/plugin/validate/EDINET/resources/dei-requirements.csv +27 -0
  75. arelle/plugin/validate/EDINET/resources/edinet-taxonomies.xml +2 -0
  76. arelle/plugin/validate/EDINET/rules/contexts.py +375 -14
  77. arelle/plugin/validate/EDINET/rules/edinet.py +1934 -45
  78. arelle/plugin/validate/EDINET/rules/frta.py +122 -3
  79. arelle/plugin/validate/EDINET/rules/gfm.py +1907 -11
  80. arelle/plugin/validate/EDINET/rules/upload.py +989 -141
  81. arelle/plugin/validate/ESEF/Const.py +3 -1
  82. arelle/plugin/validate/ESEF/ESEF_2021/DTS.py +5 -0
  83. arelle/plugin/validate/ESEF/ESEF_2021/Image.py +2 -2
  84. arelle/plugin/validate/ESEF/ESEF_2021/ValidateXbrlFinally.py +23 -20
  85. arelle/plugin/validate/ESEF/ESEF_Current/DTS.py +47 -14
  86. arelle/plugin/validate/ESEF/ESEF_Current/ValidateXbrlFinally.py +100 -25
  87. arelle/plugin/validate/ESEF/__init__.py +20 -6
  88. arelle/plugin/validate/ESEF/resources/authority-validations.json +76 -9
  89. arelle/plugin/validate/ESEF/resources/config.xml +20 -0
  90. arelle/plugin/validate/NL/DisclosureSystems.py +22 -0
  91. arelle/plugin/validate/NL/PluginValidationDataExtension.py +27 -9
  92. arelle/plugin/validate/NL/ValidationPluginExtension.py +51 -7
  93. arelle/plugin/validate/NL/resources/config.xml +18 -0
  94. arelle/plugin/validate/NL/rules/br_kvk.py +17 -61
  95. arelle/plugin/validate/NL/rules/fg_nl.py +7 -38
  96. arelle/plugin/validate/NL/rules/fr_kvk.py +7 -42
  97. arelle/plugin/validate/NL/rules/fr_nl.py +31 -147
  98. arelle/plugin/validate/NL/rules/nl_kvk.py +142 -28
  99. arelle/plugin/validate/ROS/PluginValidationDataExtension.py +2 -0
  100. arelle/plugin/validate/ROS/ValidationPluginExtension.py +4 -1
  101. arelle/plugin/validate/ROS/rules/ros.py +41 -9
  102. arelle/plugin/validate/UK/ValidateUK.py +130 -66
  103. arelle/plugin/validate/UK/__init__.py +89 -103
  104. arelle/utils/EntryPointDetection.py +79 -13
  105. arelle/utils/PluginHooks.py +125 -0
  106. arelle/utils/validate/ESEFImage.py +6 -6
  107. arelle/utils/validate/Validation.py +18 -0
  108. arelle/utils/validate/ValidationPlugin.py +76 -11
  109. arelle/utils/validate/ValidationUtil.py +35 -3
  110. {arelle_release-2.37.46.dist-info → arelle_release-2.38.0.dist-info}/METADATA +30 -20
  111. {arelle_release-2.37.46.dist-info → arelle_release-2.38.0.dist-info}/RECORD +115 -191
  112. {arelle_release-2.37.46.dist-info → arelle_release-2.38.0.dist-info}/licenses/LICENSE.md +0 -3
  113. arelle/archive/CustomLogger.py +0 -43
  114. arelle/archive/LoadEFMvalidate.py +0 -32
  115. arelle/archive/LoadSavePreLbCsv.py +0 -26
  116. arelle/archive/LoadValidate.cs +0 -31
  117. arelle/archive/LoadValidate.py +0 -36
  118. arelle/archive/LoadValidateCmdLine.java +0 -69
  119. arelle/archive/LoadValidatePostedZip.java +0 -57
  120. arelle/archive/LoadValidateWebService.java +0 -34
  121. arelle/archive/SaveTableToExelle.py +0 -140
  122. arelle/archive/TR3toTR4.py +0 -88
  123. arelle/archive/plugin/ESEF_2022/__init__.py +0 -47
  124. arelle/archive/plugin/bigInstance.py +0 -394
  125. arelle/archive/plugin/cmdWebServerExtension.py +0 -43
  126. arelle/archive/plugin/crashTest.py +0 -38
  127. arelle/archive/plugin/functionsXmlCreation.py +0 -106
  128. arelle/archive/plugin/hello_i18n.pot +0 -26
  129. arelle/archive/plugin/hello_i18n.py +0 -32
  130. arelle/archive/plugin/importTestChild1.py +0 -21
  131. arelle/archive/plugin/importTestChild2.py +0 -22
  132. arelle/archive/plugin/importTestGrandchild1.py +0 -21
  133. arelle/archive/plugin/importTestGrandchild2.py +0 -21
  134. arelle/archive/plugin/importTestImported1.py +0 -23
  135. arelle/archive/plugin/importTestImported11.py +0 -22
  136. arelle/archive/plugin/importTestParent.py +0 -48
  137. arelle/archive/plugin/instanceInfo.py +0 -306
  138. arelle/archive/plugin/loadFromOIM-2018.py +0 -1282
  139. arelle/archive/plugin/locale/fr/LC_MESSAGES/hello_i18n.po +0 -25
  140. arelle/archive/plugin/objectmaker.py +0 -285
  141. arelle/archive/plugin/packagedImportTest/__init__.py +0 -47
  142. arelle/archive/plugin/packagedImportTest/importTestChild1.py +0 -21
  143. arelle/archive/plugin/packagedImportTest/importTestChild2.py +0 -22
  144. arelle/archive/plugin/packagedImportTest/importTestGrandchild1.py +0 -21
  145. arelle/archive/plugin/packagedImportTest/importTestGrandchild2.py +0 -21
  146. arelle/archive/plugin/packagedImportTest/importTestImported1.py +0 -24
  147. arelle/archive/plugin/packagedImportTest/importTestImported11.py +0 -21
  148. arelle/archive/plugin/packagedImportTest/subdir/importTestImported111.py +0 -21
  149. arelle/archive/plugin/packagedImportTest/subdir/subsubdir/importTestImported1111.py +0 -21
  150. arelle/archive/plugin/sakaCalendar.py +0 -215
  151. arelle/archive/plugin/saveInstanceInfoset.py +0 -121
  152. arelle/archive/plugin/sphinx/FormulaGenerator.py +0 -823
  153. arelle/archive/plugin/sphinx/SphinxContext.py +0 -404
  154. arelle/archive/plugin/sphinx/SphinxEvaluator.py +0 -783
  155. arelle/archive/plugin/sphinx/SphinxMethods.py +0 -1287
  156. arelle/archive/plugin/sphinx/SphinxParser.py +0 -1093
  157. arelle/archive/plugin/sphinx/SphinxValidator.py +0 -163
  158. arelle/archive/plugin/sphinx/US-GAAP Ratios Example.xsr +0 -52
  159. arelle/archive/plugin/sphinx/__init__.py +0 -285
  160. arelle/archive/plugin/streamingExtensions.py +0 -335
  161. arelle/archive/plugin/updateTableLB.py +0 -242
  162. arelle/archive/plugin/validate/SBRnl/CustomLoader.py +0 -19
  163. arelle/archive/plugin/validate/SBRnl/DTS.py +0 -305
  164. arelle/archive/plugin/validate/SBRnl/Dimensions.py +0 -357
  165. arelle/archive/plugin/validate/SBRnl/Document.py +0 -799
  166. arelle/archive/plugin/validate/SBRnl/Filing.py +0 -467
  167. arelle/archive/plugin/validate/SBRnl/__init__.py +0 -75
  168. arelle/archive/plugin/validate/SBRnl/config.xml +0 -26
  169. arelle/archive/plugin/validate/SBRnl/sbr-nl-taxonomies.xml +0 -754
  170. arelle/archive/plugin/validate/USBestPractices.py +0 -570
  171. arelle/archive/plugin/validate/USCorpAction.py +0 -557
  172. arelle/archive/plugin/validate/USSecTagging.py +0 -337
  173. arelle/archive/plugin/validate/XDC/__init__.py +0 -77
  174. arelle/archive/plugin/validate/XDC/config.xml +0 -20
  175. arelle/archive/plugin/validate/XFsyntax/__init__.py +0 -64
  176. arelle/archive/plugin/validate/XFsyntax/xf.py +0 -2227
  177. arelle/archive/plugin/validate/calc2.py +0 -536
  178. arelle/archive/plugin/validateSchemaLxml.py +0 -156
  179. arelle/archive/plugin/validateTableInfoset.py +0 -52
  180. arelle/archive/us-gaap-dei-docType-extraction-frm.xml +0 -90
  181. arelle/archive/us-gaap-dei-ratio-cash-frm.xml +0 -150
  182. arelle/examples/plugin/formulaSuiteConverter.py +0 -212
  183. arelle/examples/plugin/functionsCustom.py +0 -59
  184. arelle/examples/plugin/hello_dolly.py +0 -64
  185. arelle/examples/plugin/multi.py +0 -58
  186. arelle/examples/plugin/rssSaveOim.py +0 -96
  187. arelle/examples/plugin/validate/XYZ/DisclosureSystems.py +0 -2
  188. arelle/examples/plugin/validate/XYZ/PluginValidationDataExtension.py +0 -10
  189. arelle/examples/plugin/validate/XYZ/ValidationPluginExtension.py +0 -49
  190. arelle/examples/plugin/validate/XYZ/__init__.py +0 -75
  191. arelle/examples/plugin/validate/XYZ/resources/config.xml +0 -16
  192. arelle/examples/plugin/validate/XYZ/rules/__init__.py +0 -0
  193. arelle/examples/plugin/validate/XYZ/rules/rules01.py +0 -110
  194. arelle/examples/plugin/validate/XYZ/rules/rules02.py +0 -59
  195. arelle/model/CommentBase.py +0 -9
  196. arelle/model/ElementBase.py +0 -11
  197. arelle/model/PIBase.py +0 -10
  198. arelle/model/__init__.py +0 -15
  199. arelle/scripts-macOS/startWebServer.command +0 -3
  200. arelle/scripts-unix/startWebServer.sh +0 -1
  201. arelle/scripts-windows/startWebServer.bat +0 -5
  202. {arelle_release-2.37.46.dist-info → arelle_release-2.38.0.dist-info}/WHEEL +0 -0
  203. {arelle_release-2.37.46.dist-info → arelle_release-2.38.0.dist-info}/entry_points.txt +0 -0
  204. {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 Any, Iterable
9
+ from typing import TYPE_CHECKING, Any
10
10
 
11
- from arelle.ValidateXbrl import ValidateXbrl
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 ..DisclosureSystems import (DISCLOSURE_SYSTEM_EDINET)
17
- from ..InstanceType import InstanceType, HTML_EXTENSIONS, IMAGE_EXTENSIONS
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 = re.compile(r'[a-zA-Z0-9_-]*')
59
+ FILENAME_STEM_PATTERN = regex.compile(r'[a-zA-Z0-9_-]*')
35
60
 
36
61
 
37
62
  @validation(
38
- hook=ValidationHook.XBRL_FINALLY,
63
+ hook=ValidationHook.FILESOURCE,
39
64
  disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
40
65
  )
41
- def rule_EC0121E(
42
- pluginData: PluginValidationDataExtension,
43
- val: ValidateXbrl,
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.EC0121E: There is a directory or file that contains more than 31 characters
49
- or uses characters other than those allowed (alphanumeric characters, '-' and '_').
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
- Note: Sample instances from EDINET almost always violate this rule based on our
52
- current interpretation. The exception being files placed outside the XBRL directory,
53
- i.e. amendment documents. For now, we will only check amendment documents, directory
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
- if not pluginData.shouldValidateUpload(val):
89
+ uploadContents = pluginData.getUploadContents()
90
+ if uploadContents is None:
57
91
  return
58
- uploadContents = pluginData.getUploadContents(val.modelXbrl)
59
- paths = set(uploadContents.directories | uploadContents.unknownPaths)
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.EC0121E',
66
- msg=_("There is a directory or file in '%(directory)s' that contains more than 31 characters "
67
- "or uses characters other than those allowed (alphanumeric characters, '-' and '_'). "
68
- "Directory or file name: '%(basename)s'. "
69
- "Please change the file name (or folder name) to within 31 characters and to usable "
70
- "characters, and upload again."),
71
- directory=str(path.parent),
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.XBRL_FINALLY,
106
+ hook=ValidationHook.FILESOURCE,
79
107
  disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
80
108
  )
81
- def rule_EC0124E(
82
- pluginData: PluginValidationDataExtension,
83
- val: ValidateXbrl,
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
- if not pluginData.shouldValidateUpload(val):
91
- return
92
- uploadFilepaths = pluginData.getUploadFilepaths(val.modelXbrl)
120
+ uploadFilepaths = pluginData.getUploadFilepaths(fileSource)
93
121
  emptyDirectories = []
94
- for path in uploadFilepaths:
95
- if path.suffix:
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(str(path))
126
+ emptyDirectories.append(path)
99
127
  for emptyDirectory in emptyDirectories:
100
- yield Validation.error(
101
- codes='EDINET.EC0124E',
102
- msg=_("There is no file directly under '%(emptyDirectory)s'. "
103
- "No empty folders. "
104
- "Please store the file in the appropriate folder or delete the folder and upload again."),
105
- emptyDirectory=emptyDirectory,
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.XBRL_FINALLY,
147
+ hook=ValidationHook.FILESOURCE,
111
148
  disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
112
149
  )
113
150
  def rule_EC0129E(
114
- pluginData: PluginValidationDataExtension,
115
- val: ValidateXbrl,
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
- if not pluginData.shouldValidateUpload(val):
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.XBRL_FINALLY,
183
+ hook=ValidationHook.FILESOURCE,
148
184
  disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
149
185
  )
150
186
  def rule_EC0130E(
151
- pluginData: PluginValidationDataExtension,
152
- val: ValidateXbrl,
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
- if not pluginData.shouldValidateUpload(val):
196
+ uploadContents = pluginData.getUploadContents()
197
+ if uploadContents is None:
160
198
  return
161
- uploadContents = pluginData.getUploadContents(val.modelXbrl)
162
- checks = []
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 = instanceType.getValidExtensions(isAmendment, isSubdirectory)
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.XBRL_FINALLY,
223
+ hook=ValidationHook.FILESOURCE,
196
224
  disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
197
225
  )
198
226
  def rule_EC0132E(
199
- pluginData: PluginValidationDataExtension,
200
- val: ValidateXbrl,
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: Store the manifest file directly under the relevant folder.
234
+ EDINET.EC0132E: Cover page or manifest file is missing.
235
+
236
+ Note: Cover page is not required in AuditDoc.
206
237
  """
207
- if not pluginData.shouldValidateUpload(val):
238
+ uploadContents = pluginData.getUploadContents()
239
+ if uploadContents is None:
208
240
  return
209
- uploadContents = pluginData.getUploadContents(val.modelXbrl)
210
- for instanceType in (InstanceType.AUDIT_DOC, InstanceType.PRIVATE_DOC, InstanceType.PUBLIC_DOC):
211
- if instanceType not in uploadContents.instances:
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
- yield Validation.error(
216
- codes='EDINET.EC0132E',
217
- msg=_("'%(expectedManifestName)s' does not exist in '%(expectedManifestDirectory)s'. "
218
- "Please store the manifest file (or cover file) directly under the relevant folder and upload it again. "),
219
- expectedManifestName=instanceType.manifestPath.name,
220
- expectedManifestDirectory=str(instanceType.manifestPath.parent),
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.XBRL_FINALLY,
271
+ hook=ValidationHook.FILESOURCE,
226
272
  disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
227
273
  )
228
274
  def rule_EC0183E(
229
- pluginData: PluginValidationDataExtension,
230
- val: ValidateXbrl,
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
- if not pluginData.shouldValidateUpload(val):
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.XBRL_FINALLY,
296
+ hook=ValidationHook.FILESOURCE,
252
297
  disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
253
298
  )
254
299
  def rule_EC0188E(
255
- pluginData: PluginValidationDataExtension,
256
- val: ValidateXbrl,
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
- if not pluginData.shouldValidateUpload(val):
264
- return
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.XBRL_FINALLY,
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: PluginValidationDataExtension,
290
- val: ValidateXbrl,
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(val.modelXbrl)
301
- for path in uploadFilepaths:
302
- if len(path.suffix) == 0:
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.XBRL_FINALLY,
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: PluginValidationDataExtension,
327
- val: ValidateXbrl,
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
- if not pluginData.shouldValidateUpload(val):
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.XBRL_FINALLY,
505
+ hook=ValidationHook.FILESOURCE,
352
506
  disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
353
507
  )
354
508
  def rule_EC0206E(
355
- pluginData: PluginValidationDataExtension,
356
- val: ValidateXbrl,
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
- if not pluginData.shouldValidateUpload(val):
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 rule_EC1016E(
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.EC1016E: The image file is over 300KB.
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
- if not pluginData.shouldValidateUpload(val):
784
+ uploadContents = pluginData.getUploadContents(val.modelXbrl)
785
+ if uploadContents is None:
392
786
  return
393
- for path, size in pluginData.getUploadFileSizes(val.modelXbrl).items():
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 rule_EC1020E(
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
- Also, please modify the relevant file so that there is only one html tag, one head tag, and one body tag each.
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 this rule (such as multiple DOCTYPE declarations) prevent Arelle from parsing
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
- name = elt.qname.localName
438
- if name not in checkNames:
1047
+ if not isinstance(elt, ModelObject):
439
1048
  continue
440
- eltCounts[name] = eltCounts.get(name, 0) + 1
441
- pass
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 rule_manifest_preferredFilename(
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
- if not pluginData.shouldValidateUpload(val):
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