arelle-release 2.37.49__py3-none-any.whl → 2.37.51__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of arelle-release might be problematic. Click here for more details.
- arelle/ModelDocument.py +16 -14
- arelle/ModelInstanceObject.py +1 -1
- arelle/ModelXbrl.py +21 -10
- arelle/WebCache.py +26 -16
- arelle/_version.py +16 -3
- arelle/api/Session.py +5 -2
- arelle/plugin/validate/DBA/PluginValidationDataExtension.py +0 -1
- arelle/plugin/validate/EDINET/Constants.py +11 -0
- arelle/plugin/validate/EDINET/ControllerPluginData.py +40 -29
- arelle/plugin/validate/EDINET/PluginValidationDataExtension.py +30 -5
- arelle/plugin/validate/EDINET/{InstanceType.py → ReportFolderType.py} +11 -15
- arelle/plugin/validate/EDINET/UploadContents.py +20 -5
- arelle/plugin/validate/EDINET/rules/contexts.py +4 -2
- arelle/plugin/validate/EDINET/rules/edinet.py +22 -0
- arelle/plugin/validate/EDINET/rules/gfm.py +20 -29
- arelle/plugin/validate/EDINET/rules/upload.py +230 -57
- arelle/plugin/validate/ESEF/ESEF_2021/ValidateXbrlFinally.py +2 -2
- arelle/plugin/validate/ESEF/ESEF_Current/ValidateXbrlFinally.py +2 -2
- arelle/plugin/validate/NL/PluginValidationDataExtension.py +1 -1
- arelle/plugin/validate/NL/rules/fr_nl.py +6 -7
- arelle/plugin/validate/UK/ValidateUK.py +31 -66
- {arelle_release-2.37.49.dist-info → arelle_release-2.37.51.dist-info}/METADATA +20 -18
- {arelle_release-2.37.49.dist-info → arelle_release-2.37.51.dist-info}/RECORD +27 -27
- {arelle_release-2.37.49.dist-info → arelle_release-2.37.51.dist-info}/WHEEL +0 -0
- {arelle_release-2.37.49.dist-info → arelle_release-2.37.51.dist-info}/entry_points.txt +0 -0
- {arelle_release-2.37.49.dist-info → arelle_release-2.37.51.dist-info}/licenses/LICENSE.md +0 -0
- {arelle_release-2.37.49.dist-info → arelle_release-2.37.51.dist-info}/top_level.txt +0 -0
|
@@ -30,7 +30,7 @@ from arelle.utils.validate.Validation import Validation
|
|
|
30
30
|
from arelle.utils.validate.ValidationUtil import etreeIterWithDepth
|
|
31
31
|
from ..DisclosureSystems import (DISCLOSURE_SYSTEM_EDINET)
|
|
32
32
|
from ..PluginValidationDataExtension import PluginValidationDataExtension
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
|
|
35
35
|
_: TypeGetText
|
|
36
36
|
|
|
@@ -257,13 +257,14 @@ def rule_gfm_1_2_8(
|
|
|
257
257
|
EDINET.EC5700W: [GFM 1.2.8] Every xbrli:context element must appear in at least one
|
|
258
258
|
contextRef attribute in the same instance.
|
|
259
259
|
"""
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
260
|
+
unusedContexts = list(set(val.modelXbrl.contexts.values()) - set(val.modelXbrl.contextsInUse))
|
|
261
|
+
unusedContexts.extend(val.modelXbrl.ixdsUnmappedContexts.values())
|
|
262
|
+
unusedContexts.sort(key=lambda x: x.id if x.id is not None else "")
|
|
263
|
+
for context in unusedContexts:
|
|
263
264
|
yield Validation.warning(
|
|
264
265
|
codes='EDINET.EC5700W.GFM.1.2.8',
|
|
265
266
|
msg=_('If you are not using a context, delete it if it is not needed.'),
|
|
266
|
-
modelObject
|
|
267
|
+
modelObject=context
|
|
267
268
|
)
|
|
268
269
|
|
|
269
270
|
|
|
@@ -367,28 +368,15 @@ def rule_gfm_1_2_14(
|
|
|
367
368
|
(a format that conforms to XML grammar, such as all start and end tags being paired, and the end tag of a nested tag not coming after the end tag of its parent tag, etc.).
|
|
368
369
|
Please modify it so that it is well-formed.
|
|
369
370
|
"""
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
concept = fact.concept
|
|
380
|
-
if not fact.isNil and concept is not None and concept.isTextBlock and XMLpattern.match(fact.value):
|
|
381
|
-
for xmlText in [fact.value] + CDATApattern.findall(fact.value):
|
|
382
|
-
xmlBodyWithoutEntities = htmlBodyTemplate.format(namedEntityPattern.sub("", xmlText).replace('&','&'))
|
|
383
|
-
textblockXml = XML(xmlBodyWithoutEntities)
|
|
384
|
-
if not dtd.validate(textblockXml):
|
|
385
|
-
yield Validation.warning(
|
|
386
|
-
codes='EDINET.EC5700W.GFM.1.2.14',
|
|
387
|
-
msg=_('The content of an element with a data type of nonnum:textBlockItemType is not well-formed XML (a format that conforms to XML grammar, '
|
|
388
|
-
'such as all start and end tags being in pairs, and the end tag of a nested tag not coming after the end tag of its parent tag). '
|
|
389
|
-
'Correct the content so that it is well-formed.'),
|
|
390
|
-
modelObject = fact
|
|
391
|
-
)
|
|
371
|
+
problematicFacts = pluginData.getProblematicTextBlocks(val.modelXbrl)
|
|
372
|
+
if len(problematicFacts) > 0:
|
|
373
|
+
yield Validation.warning(
|
|
374
|
+
codes='EDINET.EC5700W.GFM.1.2.14',
|
|
375
|
+
msg=_('The content of an element with a data type of nonnum:textBlockItemType is not well-formed XML (a format that conforms to XML grammar, '
|
|
376
|
+
'such as all start and end tags being in pairs, and the end tag of a nested tag not coming after the end tag of its parent tag). '
|
|
377
|
+
'Correct the content so that it is well-formed.'),
|
|
378
|
+
modelObject = problematicFacts
|
|
379
|
+
)
|
|
392
380
|
|
|
393
381
|
|
|
394
382
|
@validation(
|
|
@@ -484,6 +472,7 @@ def rule_gfm_1_2_25(
|
|
|
484
472
|
XbrlConst.qnXbrliEndDate.clarkNotation,
|
|
485
473
|
XbrlConst.qnXbrliInstant.clarkNotation
|
|
486
474
|
):
|
|
475
|
+
elt = cast(ModelObject, elt)
|
|
487
476
|
dateText = XmlUtil.text(elt)
|
|
488
477
|
if not GFM_CONTEXT_DATE_PATTERN.match(dateText):
|
|
489
478
|
errors.append(elt)
|
|
@@ -558,12 +547,14 @@ def rule_gfm_1_2_27(
|
|
|
558
547
|
EDINET.EC5700W: [GFM 1.2.27] An instance must not contain unused units.
|
|
559
548
|
"""
|
|
560
549
|
# TODO: Consolidate validations involving unused units
|
|
561
|
-
unusedUnits = set(val.modelXbrl.units.values()) -
|
|
550
|
+
unusedUnits = list(set(val.modelXbrl.units.values()) - set(val.modelXbrl.unitsInUse))
|
|
551
|
+
unusedUnits.extend(val.modelXbrl.ixdsUnmappedUnits.values())
|
|
552
|
+
unusedUnits.sort(key=lambda x: x.hash)
|
|
562
553
|
if len(unusedUnits) > 0:
|
|
563
554
|
yield Validation.warning(
|
|
564
555
|
codes='EDINET.EC5700W.GFM.1.2.27',
|
|
565
556
|
msg=_("Delete unused units from the instance."),
|
|
566
|
-
modelObject=
|
|
557
|
+
modelObject=unusedUnits
|
|
567
558
|
)
|
|
568
559
|
|
|
569
560
|
|
|
@@ -16,7 +16,7 @@ from arelle.utils.PluginHooks import ValidationHook
|
|
|
16
16
|
from arelle.utils.validate.Decorator import validation
|
|
17
17
|
from arelle.utils.validate.Validation import Validation
|
|
18
18
|
from ..DisclosureSystems import (DISCLOSURE_SYSTEM_EDINET)
|
|
19
|
-
from ..
|
|
19
|
+
from ..ReportFolderType import ReportFolderType, HTML_EXTENSIONS, IMAGE_EXTENSIONS
|
|
20
20
|
from ..PluginValidationDataExtension import PluginValidationDataExtension
|
|
21
21
|
|
|
22
22
|
if TYPE_CHECKING:
|
|
@@ -24,6 +24,16 @@ if TYPE_CHECKING:
|
|
|
24
24
|
|
|
25
25
|
_: TypeGetText
|
|
26
26
|
|
|
27
|
+
ALLOWED_ROOT_FOLDERS = {
|
|
28
|
+
"AttachDoc",
|
|
29
|
+
"AuditDoc",
|
|
30
|
+
"PrivateAttach",
|
|
31
|
+
"PrivateDoc",
|
|
32
|
+
"PublicAttach",
|
|
33
|
+
"PublicDoc",
|
|
34
|
+
"XBRL",
|
|
35
|
+
}
|
|
36
|
+
|
|
27
37
|
FILE_COUNT_LIMITS = {
|
|
28
38
|
Path("AttachDoc"): 990,
|
|
29
39
|
Path("AuditDoc"): 990,
|
|
@@ -43,7 +53,7 @@ FILENAME_STEM_PATTERN = re.compile(r'[a-zA-Z0-9_-]*')
|
|
|
43
53
|
hook=ValidationHook.FILESOURCE,
|
|
44
54
|
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
45
55
|
)
|
|
46
|
-
def
|
|
56
|
+
def rule_EC0100E(
|
|
47
57
|
pluginData: ControllerPluginData,
|
|
48
58
|
cntlr: Cntlr,
|
|
49
59
|
fileSource: FileSource,
|
|
@@ -51,30 +61,32 @@ def rule_EC0121E(
|
|
|
51
61
|
**kwargs: Any,
|
|
52
62
|
) -> Iterable[Validation]:
|
|
53
63
|
"""
|
|
54
|
-
EDINET.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
64
|
+
EDINET.EC0100E: An illegal directory is found directly under the transferred directory.
|
|
65
|
+
Only the following root folders are allowed:
|
|
66
|
+
AttachDoc
|
|
67
|
+
AuditDoc*
|
|
68
|
+
PrivateAttach
|
|
69
|
+
PrivateDoc*
|
|
70
|
+
PublicAttach
|
|
71
|
+
PublicDoc*
|
|
72
|
+
XBRL
|
|
73
|
+
* Only when reporting corrections
|
|
74
|
+
|
|
75
|
+
NOTE: since we do not have access to the submission type, we can't determine if the submission is a correction or not.
|
|
76
|
+
For this implementation, we will allow all directories that may be valid for at least one submission type.
|
|
77
|
+
This allows for a false-negative outcome when a non-correction submission has a correction-only root directory.
|
|
61
78
|
"""
|
|
62
79
|
uploadContents = pluginData.getUploadContents(fileSource)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
paths.update(amendmentPaths)
|
|
66
|
-
for path in paths:
|
|
67
|
-
if len(str(path.name)) > 31 or not FILENAME_STEM_PATTERN.match(path.stem):
|
|
80
|
+
for path, pathInfo in uploadContents.uploadPaths.items():
|
|
81
|
+
if pathInfo.isRoot and path.name not in ALLOWED_ROOT_FOLDERS:
|
|
68
82
|
yield Validation.error(
|
|
69
|
-
codes='EDINET.
|
|
70
|
-
msg=_("
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
"
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
basename=path.name,
|
|
77
|
-
file=str(path)
|
|
83
|
+
codes='EDINET.EC0100E',
|
|
84
|
+
msg=_("An illegal directory is found directly under the transferred directory. "
|
|
85
|
+
"Directory name or file name: '%(rootDirectory)s'. "
|
|
86
|
+
"Delete all folders except the following folders that exist directly "
|
|
87
|
+
"under the root folder, and then upload again: %(allowedDirectories)s."),
|
|
88
|
+
rootDirectory=path.name,
|
|
89
|
+
allowedDirectories=', '.join(f"'{d}'" for d in ALLOWED_ROOT_FOLDERS)
|
|
78
90
|
)
|
|
79
91
|
|
|
80
92
|
|
|
@@ -82,7 +94,7 @@ def rule_EC0121E(
|
|
|
82
94
|
hook=ValidationHook.FILESOURCE,
|
|
83
95
|
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
84
96
|
)
|
|
85
|
-
def
|
|
97
|
+
def rule_EC0124E_EC0187E(
|
|
86
98
|
pluginData: ControllerPluginData,
|
|
87
99
|
cntlr: Cntlr,
|
|
88
100
|
fileSource: FileSource,
|
|
@@ -90,7 +102,8 @@ def rule_EC0124E(
|
|
|
90
102
|
**kwargs: Any,
|
|
91
103
|
) -> Iterable[Validation]:
|
|
92
104
|
"""
|
|
93
|
-
EDINET.EC0124E: There are no empty directories.
|
|
105
|
+
EDINET.EC0124E: There are no empty root directories.
|
|
106
|
+
EDINET.EC0187E: There are no empty subdirectories.
|
|
94
107
|
"""
|
|
95
108
|
uploadFilepaths = pluginData.getUploadFilepaths(fileSource)
|
|
96
109
|
emptyDirectories = []
|
|
@@ -98,15 +111,24 @@ def rule_EC0124E(
|
|
|
98
111
|
if path.suffix:
|
|
99
112
|
continue
|
|
100
113
|
if not any(path in p.parents for p in uploadFilepaths):
|
|
101
|
-
emptyDirectories.append(
|
|
114
|
+
emptyDirectories.append(path)
|
|
102
115
|
for emptyDirectory in emptyDirectories:
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
116
|
+
if len(emptyDirectory.parts) <= 1:
|
|
117
|
+
yield Validation.error(
|
|
118
|
+
codes='EDINET.EC0124E',
|
|
119
|
+
msg=_("There is no file directly under '%(emptyDirectory)s'. "
|
|
120
|
+
"No empty root folders. "
|
|
121
|
+
"Please store the file in the appropriate folder or delete the folder and upload again."),
|
|
122
|
+
emptyDirectory=str(emptyDirectory),
|
|
123
|
+
)
|
|
124
|
+
else:
|
|
125
|
+
yield Validation.error(
|
|
126
|
+
codes='EDINET.EC0187E',
|
|
127
|
+
msg=_("'%(parentDirectory)s' contains a subordinate directory ('%(emptyDirectory)s') with no files. "
|
|
128
|
+
"Please store the file in the corresponding subfolder or delete the subfolder and upload again."),
|
|
129
|
+
parentDirectory=str(emptyDirectory.parent),
|
|
130
|
+
emptyDirectory=str(emptyDirectory),
|
|
131
|
+
)
|
|
110
132
|
|
|
111
133
|
|
|
112
134
|
@validation(
|
|
@@ -160,22 +182,13 @@ def rule_EC0130E(
|
|
|
160
182
|
EDINET.EC0130E: File extensions must match the file extensions allowed in Figure 2-1-3 and Figure 2-1-5.
|
|
161
183
|
"""
|
|
162
184
|
uploadContents = pluginData.getUploadContents(fileSource)
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
for amendmentPath in amendmentPaths:
|
|
166
|
-
isSubdirectory = amendmentPath.parent.name != instanceType.value
|
|
167
|
-
checks.append((amendmentPath, True, instanceType, isSubdirectory))
|
|
168
|
-
for instanceType, formPaths in uploadContents.instances.items():
|
|
169
|
-
for amendmentPath in formPaths:
|
|
170
|
-
isSubdirectory = amendmentPath.parent.name != instanceType.value
|
|
171
|
-
checks.append((amendmentPath, False, instanceType, isSubdirectory))
|
|
172
|
-
for path, isAmendment, instanceType, isSubdirectory in checks:
|
|
173
|
-
ext = path.suffix
|
|
174
|
-
if len(ext) == 0:
|
|
185
|
+
for path, pathInfo in uploadContents.uploadPaths.items():
|
|
186
|
+
if pathInfo.reportFolderType is None or pathInfo.isDirectory:
|
|
175
187
|
continue
|
|
176
|
-
validExtensions =
|
|
188
|
+
validExtensions = pathInfo.reportFolderType.getValidExtensions(pathInfo.isCorrection, pathInfo.isSubdirectory)
|
|
177
189
|
if validExtensions is None:
|
|
178
190
|
continue
|
|
191
|
+
ext = path.suffix
|
|
179
192
|
if ext not in validExtensions:
|
|
180
193
|
yield Validation.error(
|
|
181
194
|
codes='EDINET.EC0130E',
|
|
@@ -207,18 +220,17 @@ def rule_EC0132E(
|
|
|
207
220
|
EDINET.EC0132E: Store the manifest file directly under the relevant folder.
|
|
208
221
|
"""
|
|
209
222
|
uploadContents = pluginData.getUploadContents(fileSource)
|
|
210
|
-
for
|
|
211
|
-
if
|
|
223
|
+
for reportFolderType, paths in uploadContents.reports.items():
|
|
224
|
+
if reportFolderType.isAttachment:
|
|
212
225
|
continue
|
|
213
|
-
if
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
)
|
|
226
|
+
if reportFolderType.manifestPath not in paths:
|
|
227
|
+
yield Validation.error(
|
|
228
|
+
codes='EDINET.EC0132E',
|
|
229
|
+
msg=_("'%(expectedManifestName)s' does not exist in '%(expectedManifestDirectory)s'. "
|
|
230
|
+
"Please store the manifest file (or cover file) directly under the relevant folder and upload it again. "),
|
|
231
|
+
expectedManifestName=reportFolderType.manifestPath.name,
|
|
232
|
+
expectedManifestDirectory=str(reportFolderType.manifestPath.parent),
|
|
233
|
+
)
|
|
222
234
|
|
|
223
235
|
|
|
224
236
|
@validation(
|
|
@@ -279,6 +291,36 @@ def rule_EC0188E(
|
|
|
279
291
|
)
|
|
280
292
|
|
|
281
293
|
|
|
294
|
+
@validation(
|
|
295
|
+
hook=ValidationHook.FILESOURCE,
|
|
296
|
+
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
297
|
+
)
|
|
298
|
+
def rule_EC0192E(
|
|
299
|
+
pluginData: ControllerPluginData,
|
|
300
|
+
cntlr: Cntlr,
|
|
301
|
+
fileSource: FileSource,
|
|
302
|
+
*args: Any,
|
|
303
|
+
**kwargs: Any,
|
|
304
|
+
) -> Iterable[Validation]:
|
|
305
|
+
"""
|
|
306
|
+
EDINET.EC0192E: The cover file for PrivateDoc cannot be set because it uses a
|
|
307
|
+
PublicDoc cover file. Please delete the cover file from PrivateDoc and upload
|
|
308
|
+
it again.
|
|
309
|
+
"""
|
|
310
|
+
uploadContents = pluginData.getUploadContents(fileSource)
|
|
311
|
+
for path, pathInfo in uploadContents.uploadPaths.items():
|
|
312
|
+
if not pathInfo.isCoverPage:
|
|
313
|
+
continue
|
|
314
|
+
# Only applies to PrivateDoc correction reports
|
|
315
|
+
if pathInfo.isCorrection and pathInfo.reportFolderType == ReportFolderType.PRIVATE_DOC:
|
|
316
|
+
yield Validation.error(
|
|
317
|
+
codes='EDINET.EC0192E',
|
|
318
|
+
msg=_("The cover file for PrivateDoc ('%(file)s') cannot be set because it uses a PublicDoc cover file. "
|
|
319
|
+
"Please delete the cover file from PrivateDoc and upload it again."),
|
|
320
|
+
file=str(path),
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
|
|
282
324
|
@validation(
|
|
283
325
|
hook=ValidationHook.FILESOURCE,
|
|
284
326
|
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
@@ -315,6 +357,82 @@ def rule_EC0198E(
|
|
|
315
357
|
)
|
|
316
358
|
|
|
317
359
|
|
|
360
|
+
@validation(
|
|
361
|
+
hook=ValidationHook.FILESOURCE,
|
|
362
|
+
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
363
|
+
)
|
|
364
|
+
def rule_EC0233E(
|
|
365
|
+
pluginData: ControllerPluginData,
|
|
366
|
+
cntlr: Cntlr,
|
|
367
|
+
fileSource: FileSource,
|
|
368
|
+
*args: Any,
|
|
369
|
+
**kwargs: Any,
|
|
370
|
+
) -> Iterable[Validation]:
|
|
371
|
+
"""
|
|
372
|
+
EDINET.EC0233E: There is a file in the report directory that comes before the cover file
|
|
373
|
+
in file name sort order.
|
|
374
|
+
|
|
375
|
+
NOTE: This includes files in subdirectories. For example, PublicDoc/00000000_images/image.png
|
|
376
|
+
comes before PublicDoc/0000000_header_*.htm
|
|
377
|
+
"""
|
|
378
|
+
uploadContents = pluginData.getUploadContents(fileSource)
|
|
379
|
+
directories = defaultdict(list)
|
|
380
|
+
for path in uploadContents.sortedPaths:
|
|
381
|
+
pathInfo = uploadContents.uploadPaths[path]
|
|
382
|
+
if pathInfo.isDirectory:
|
|
383
|
+
continue
|
|
384
|
+
if pathInfo.reportFolderType in (ReportFolderType.PRIVATE_DOC, ReportFolderType.PUBLIC_DOC):
|
|
385
|
+
directories[pathInfo.reportPath].append(pathInfo)
|
|
386
|
+
for reportPath, pathInfos in directories.items():
|
|
387
|
+
coverPagePath = next(iter(p for p in pathInfos if p.isCoverPage), None)
|
|
388
|
+
if coverPagePath is None:
|
|
389
|
+
continue
|
|
390
|
+
errorPathInfos = pathInfos[:pathInfos.index(coverPagePath)]
|
|
391
|
+
for pathInfo in errorPathInfos:
|
|
392
|
+
yield Validation.error(
|
|
393
|
+
codes='EDINET.EC0233E',
|
|
394
|
+
msg=_("There is a file in the report directory in '%(reportPath)s' that comes before the cover "
|
|
395
|
+
"file ('%(coverPage)s') in file name sort order. "
|
|
396
|
+
"Directory name or file name: '%(path)s'. "
|
|
397
|
+
"Please make sure that there are no files that come before the cover file in the file "
|
|
398
|
+
"name sort order, and then upload again."),
|
|
399
|
+
reportPath=str(reportPath),
|
|
400
|
+
coverPage=str(coverPagePath.path.name),
|
|
401
|
+
path=str(pathInfo.path),
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
@validation(
|
|
406
|
+
hook=ValidationHook.FILESOURCE,
|
|
407
|
+
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
408
|
+
)
|
|
409
|
+
def rule_EC0234E(
|
|
410
|
+
pluginData: ControllerPluginData,
|
|
411
|
+
cntlr: Cntlr,
|
|
412
|
+
fileSource: FileSource,
|
|
413
|
+
*args: Any,
|
|
414
|
+
**kwargs: Any,
|
|
415
|
+
) -> Iterable[Validation]:
|
|
416
|
+
"""
|
|
417
|
+
EDINET.EC0234E: A cover file exists in an unsupported subdirectory.
|
|
418
|
+
"""
|
|
419
|
+
uploadContents = pluginData.getUploadContents(fileSource)
|
|
420
|
+
for path, pathInfo in uploadContents.uploadPaths.items():
|
|
421
|
+
if pathInfo.isDirectory:
|
|
422
|
+
continue
|
|
423
|
+
if pathInfo.reportFolderType not in (ReportFolderType.PRIVATE_DOC, ReportFolderType.PUBLIC_DOC):
|
|
424
|
+
continue
|
|
425
|
+
if pathInfo.isSubdirectory and pathInfo.isCoverPage:
|
|
426
|
+
yield Validation.error(
|
|
427
|
+
codes='EDINET.EC0234E',
|
|
428
|
+
msg=_("A cover file ('%(coverPage)s') exists in an unsupported subdirectory. "
|
|
429
|
+
"Directory: '%(directory)s'. "
|
|
430
|
+
"Please make sure there is no cover file in the subfolder and upload again."),
|
|
431
|
+
coverPage=str(path.name),
|
|
432
|
+
directory=str(path.parent),
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
|
|
318
436
|
@validation(
|
|
319
437
|
hook=ValidationHook.FILESOURCE,
|
|
320
438
|
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
@@ -444,6 +562,61 @@ def rule_EC1020E(
|
|
|
444
562
|
)
|
|
445
563
|
|
|
446
564
|
|
|
565
|
+
@validation(
|
|
566
|
+
hook=ValidationHook.FILESOURCE,
|
|
567
|
+
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
568
|
+
)
|
|
569
|
+
def rule_filenames(
|
|
570
|
+
pluginData: ControllerPluginData,
|
|
571
|
+
cntlr: Cntlr,
|
|
572
|
+
fileSource: FileSource,
|
|
573
|
+
*args: Any,
|
|
574
|
+
**kwargs: Any,
|
|
575
|
+
) -> Iterable[Validation]:
|
|
576
|
+
"""
|
|
577
|
+
EDINET.EC0121E: There is a directory or file that contains
|
|
578
|
+
more than 31 characters or uses characters other than those allowed (alphanumeric characters,
|
|
579
|
+
'-' and '_').
|
|
580
|
+
Note: Applies to everything EXCEPT files directly beneath non-correction report folders.
|
|
581
|
+
|
|
582
|
+
EDINET.EC0200E: There is a file that uses characters other
|
|
583
|
+
than those allowed (alphanumeric characters, '-' and '_').
|
|
584
|
+
Note: Applies ONLY to files directly beneath non-correction report folders.
|
|
585
|
+
"""
|
|
586
|
+
for path, pathInfo in pluginData.getUploadContents(fileSource).uploadPaths.items():
|
|
587
|
+
isReportFile = (
|
|
588
|
+
not pathInfo.isAttachment and
|
|
589
|
+
not pathInfo.isCorrection and
|
|
590
|
+
not pathInfo.isDirectory and
|
|
591
|
+
not pathInfo.isSubdirectory
|
|
592
|
+
)
|
|
593
|
+
charactersAreValid = FILENAME_STEM_PATTERN.fullmatch(path.stem)
|
|
594
|
+
lengthIsValid = isReportFile or (len(path.name) <= 31)
|
|
595
|
+
if charactersAreValid and lengthIsValid:
|
|
596
|
+
continue
|
|
597
|
+
if isReportFile:
|
|
598
|
+
yield Validation.error(
|
|
599
|
+
codes='EDINET.EC0200E',
|
|
600
|
+
msg=_("There is a file inside the XBRL directory that uses characters "
|
|
601
|
+
"other than those allowed (alphanumeric characters, '-' and '_'). "
|
|
602
|
+
"File: '%(path)s'. "
|
|
603
|
+
"Please change the filename to usable characters, and upload again."),
|
|
604
|
+
path=str(path)
|
|
605
|
+
)
|
|
606
|
+
else:
|
|
607
|
+
yield Validation.error(
|
|
608
|
+
codes='EDINET.EC0121E',
|
|
609
|
+
msg=_("There is a directory or file in '%(directory)s' that contains more "
|
|
610
|
+
"than 31 characters or uses characters other than those allowed "
|
|
611
|
+
"(alphanumeric characters, '-' and '_'). "
|
|
612
|
+
"Directory or filename: '%(basename)s'. "
|
|
613
|
+
"Please change the file name (or folder name) to within 31 characters and to usable "
|
|
614
|
+
"characters, and upload again."),
|
|
615
|
+
directory=str(path.parent),
|
|
616
|
+
basename=path.name,
|
|
617
|
+
)
|
|
618
|
+
|
|
619
|
+
|
|
447
620
|
@validation(
|
|
448
621
|
hook=ValidationHook.FILESOURCE,
|
|
449
622
|
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
@@ -529,11 +529,11 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
|
|
|
529
529
|
if contextsWithDisallowedOCEs:
|
|
530
530
|
modelXbrl.error("ESEF.2.1.3.segmentUsed",
|
|
531
531
|
_("xbrli:segment container MUST NOT be used in contexts: %(contextIds)s"),
|
|
532
|
-
modelObject=contextsWithDisallowedOCEs, contextIds=", ".join(c.id for c in contextsWithDisallowedOCEs))
|
|
532
|
+
modelObject=contextsWithDisallowedOCEs, contextIds=", ".join(c.id for c in contextsWithDisallowedOCEs if c.id is not None))
|
|
533
533
|
if contextsWithDisallowedOCEcontent:
|
|
534
534
|
modelXbrl.error("ESEF.2.1.3.scenarioContainsNonDimensionalContent",
|
|
535
535
|
_("xbrli:scenario in contexts MUST NOT contain any other content than defined in XBRL Dimensions specification: %(contextIds)s"),
|
|
536
|
-
modelObject=contextsWithDisallowedOCEcontent, contextIds=", ".join(c.id for c in contextsWithDisallowedOCEcontent))
|
|
536
|
+
modelObject=contextsWithDisallowedOCEcontent, contextIds=", ".join(c.id for c in contextsWithDisallowedOCEcontent if c.id is not None))
|
|
537
537
|
if len(contextIdentifiers) > 1:
|
|
538
538
|
modelXbrl.error("ESEF.2.1.4.multipleIdentifiers",
|
|
539
539
|
_("All entity identifiers in contexts MUST have identical content: %(contextIds)s"),
|
|
@@ -589,11 +589,11 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
|
|
|
589
589
|
if contextsWithDisallowedOCEs:
|
|
590
590
|
modelXbrl.error("ESEF.2.1.3.segmentUsed",
|
|
591
591
|
_("xbrli:segment container MUST NOT be used in contexts: %(contextIds)s"),
|
|
592
|
-
modelObject=contextsWithDisallowedOCEs, contextIds=", ".join(c.id for c in contextsWithDisallowedOCEs))
|
|
592
|
+
modelObject=contextsWithDisallowedOCEs, contextIds=", ".join(c.id for c in contextsWithDisallowedOCEs if c.id is not None))
|
|
593
593
|
if contextsWithDisallowedOCEcontent:
|
|
594
594
|
modelXbrl.error("ESEF.2.1.3.scenarioContainsNonDimensionalContent",
|
|
595
595
|
_("xbrli:scenario in contexts MUST NOT contain any other content than defined in XBRL Dimensions specification: %(contextIds)s"),
|
|
596
|
-
modelObject=contextsWithDisallowedOCEcontent, contextIds=", ".join(c.id for c in contextsWithDisallowedOCEcontent))
|
|
596
|
+
modelObject=contextsWithDisallowedOCEcontent, contextIds=", ".join(c.id for c in contextsWithDisallowedOCEcontent if c.id is not None))
|
|
597
597
|
if len(contextIdentifiers) > 1:
|
|
598
598
|
modelXbrl.error("ESEF.2.1.4.multipleIdentifiers",
|
|
599
599
|
_("All entity identifiers in contexts MUST have identical content: %(contextIds)s"),
|
|
@@ -265,7 +265,7 @@ class PluginValidationDataExtension(PluginData):
|
|
|
265
265
|
contextsWithPeriodTimeZone.append(context)
|
|
266
266
|
if context.hasSegment:
|
|
267
267
|
contextsWithSegments.append(context)
|
|
268
|
-
if context.nonDimValues("scenario"):
|
|
268
|
+
if context.nonDimValues("scenario"):
|
|
269
269
|
contextsWithImproperContent.append(context)
|
|
270
270
|
return ContextData(
|
|
271
271
|
contextsWithImproperContent=contextsWithImproperContent,
|
|
@@ -552,9 +552,9 @@ def rule_fr_nl_3_03(
|
|
|
552
552
|
"""
|
|
553
553
|
FR-NL-3.03: An XBRL instance document MUST NOT contain unused contexts
|
|
554
554
|
"""
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
for context in
|
|
555
|
+
unusedContexts = list(set(val.modelXbrl.contexts.values()) - set(val.modelXbrl.contextsInUse))
|
|
556
|
+
unusedContexts.sort(key=lambda x: x.id if x.id is not None else "")
|
|
557
|
+
for context in unusedContexts:
|
|
558
558
|
yield Validation.error(
|
|
559
559
|
codes='NL.FR-NL-3.03',
|
|
560
560
|
msg=_('Unused context must not exist in XBRL instance document'),
|
|
@@ -637,10 +637,9 @@ def rule_fr_nl_4_02(
|
|
|
637
637
|
"""
|
|
638
638
|
FR-NL-4.02: An XBRL instance document MUST NOT contain unused 'xbrli:unit' elements
|
|
639
639
|
"""
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
for unit in unused_units:
|
|
640
|
+
unusedUnits = list(set(val.modelXbrl.units.values()) - set(val.modelXbrl.unitsInUse))
|
|
641
|
+
unusedUnits.sort(key=lambda x: x.hash)
|
|
642
|
+
for unit in unusedUnits:
|
|
644
643
|
yield Validation.error(
|
|
645
644
|
codes='NL.FR-NL-4.02',
|
|
646
645
|
msg=_('Unused unit must not exist in the XBRL instance document'),
|