arelle-release 2.37.43__py3-none-any.whl → 2.37.45__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of arelle-release might be problematic. Click here for more details.
- arelle/Cntlr.py +35 -0
- arelle/ErrorManager.py +306 -0
- arelle/ModelRelationshipSet.py +1 -1
- arelle/ModelXbrl.py +45 -233
- arelle/Validate.py +2 -0
- arelle/XbrlConst.py +1 -0
- arelle/_version.py +2 -2
- arelle/plugin/validate/EDINET/Constants.py +12 -0
- arelle/plugin/validate/EDINET/ControllerPluginData.py +80 -0
- arelle/plugin/validate/EDINET/{FormType.py → InstanceType.py} +8 -8
- arelle/plugin/validate/EDINET/{Manifest.py → ManifestInstance.py} +78 -33
- arelle/plugin/validate/EDINET/PluginValidationDataExtension.py +35 -10
- arelle/plugin/validate/EDINET/ValidationPluginExtension.py +20 -3
- arelle/plugin/validate/EDINET/__init__.py +4 -22
- arelle/plugin/validate/EDINET/rules/edinet.py +26 -7
- arelle/plugin/validate/EDINET/rules/manifests.py +88 -0
- arelle/plugin/validate/EDINET/rules/upload.py +51 -53
- arelle/plugin/validate/ROS/PluginValidationDataExtension.py +6 -0
- arelle/utils/validate/ESEFImage.py +7 -7
- {arelle_release-2.37.43.dist-info → arelle_release-2.37.45.dist-info}/METADATA +1 -1
- {arelle_release-2.37.43.dist-info → arelle_release-2.37.45.dist-info}/RECORD +25 -22
- {arelle_release-2.37.43.dist-info → arelle_release-2.37.45.dist-info}/WHEEL +0 -0
- {arelle_release-2.37.43.dist-info → arelle_release-2.37.45.dist-info}/entry_points.txt +0 -0
- {arelle_release-2.37.43.dist-info → arelle_release-2.37.45.dist-info}/licenses/LICENSE.md +0 -0
- {arelle_release-2.37.43.dist-info → arelle_release-2.37.45.dist-info}/top_level.txt +0 -0
|
@@ -3,44 +3,59 @@ See COPYRIGHT.md for copyright information.
|
|
|
3
3
|
"""
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
|
+
import traceback
|
|
6
7
|
import zipfile
|
|
8
|
+
from collections import defaultdict
|
|
7
9
|
from dataclasses import dataclass
|
|
8
10
|
from functools import lru_cache
|
|
9
11
|
from pathlib import Path
|
|
12
|
+
from typing import cast
|
|
10
13
|
|
|
11
14
|
from lxml import etree
|
|
12
15
|
from lxml.etree import _Element
|
|
13
16
|
|
|
14
17
|
from arelle import XbrlConst
|
|
18
|
+
from arelle.Cntlr import Cntlr
|
|
15
19
|
from arelle.FileSource import FileSource
|
|
16
20
|
from arelle.ModelValue import QName, qname
|
|
21
|
+
from arelle.typing import TypeGetText
|
|
17
22
|
from . import Constants
|
|
18
23
|
|
|
24
|
+
_: TypeGetText
|
|
25
|
+
|
|
19
26
|
|
|
20
27
|
@dataclass(frozen=True)
|
|
21
|
-
class
|
|
22
|
-
|
|
28
|
+
class ManifestInstance:
|
|
29
|
+
id: str
|
|
30
|
+
ixbrlFiles: list[Path]
|
|
23
31
|
path: Path
|
|
32
|
+
preferredFilename: str
|
|
24
33
|
titlesByLang: dict[str, str]
|
|
25
34
|
tocItems: list[ManifestTocItem]
|
|
35
|
+
type: str
|
|
26
36
|
|
|
27
37
|
|
|
28
38
|
@dataclass(frozen=True)
|
|
29
39
|
class ManifestTocItem:
|
|
40
|
+
element: _Element
|
|
30
41
|
extrole: str
|
|
31
42
|
childItems: list[ManifestTocItem]
|
|
32
43
|
itemIn: str
|
|
33
44
|
parent: QName | None
|
|
34
45
|
ref: str
|
|
35
46
|
start: QName | None
|
|
47
|
+
end: QName | None
|
|
36
48
|
|
|
37
49
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
50
|
+
def _invalidManifestError(cntlr: Cntlr, file: str, message: str | None = None) -> None:
|
|
51
|
+
if message is None:
|
|
52
|
+
message = _("Please correct the content.")
|
|
53
|
+
cntlr.error(
|
|
54
|
+
"EDINET.EC5800E.FATAL_ERROR_INVALID_MANIFEST",
|
|
55
|
+
_("The manifest file definition is incorrect <file=%(file)s>. %(message)s"),
|
|
56
|
+
file=file,
|
|
57
|
+
message=message,
|
|
58
|
+
)
|
|
44
59
|
|
|
45
60
|
|
|
46
61
|
def _parseManifestTocItems(parentElt: _Element, parentQName: QName | None) -> list[ManifestTocItem]:
|
|
@@ -51,28 +66,33 @@ def _parseManifestTocItems(parentElt: _Element, parentQName: QName | None) -> li
|
|
|
51
66
|
childParentQName = qname(insertElt.attrib.get("parent"), insertElt.nsmap) if insertElt.attrib.get("parent") else None
|
|
52
67
|
childTocItems.extend(_parseManifestTocItems(insertElt, childParentQName))
|
|
53
68
|
tocItems.append(ManifestTocItem(
|
|
69
|
+
element=itemElt,
|
|
54
70
|
extrole=itemElt.attrib.get("extrole", ""),
|
|
55
71
|
childItems=childTocItems,
|
|
56
72
|
parent=parentQName,
|
|
57
73
|
itemIn=itemElt.attrib.get("in", ""),
|
|
58
74
|
ref=itemElt.attrib.get("ref", ""),
|
|
59
75
|
start=qname(itemElt.attrib.get("start"), itemElt.nsmap) if itemElt.attrib.get("start") else None,
|
|
76
|
+
end=qname(itemElt.attrib.get("end"), itemElt.nsmap) if itemElt.attrib.get("end") else None,
|
|
60
77
|
))
|
|
61
78
|
return tocItems
|
|
62
79
|
|
|
63
80
|
|
|
64
|
-
def _parseManifestDoc(xmlRootElement: _Element, path: Path) ->
|
|
81
|
+
def _parseManifestDoc(xmlRootElement: _Element, path: Path) -> list[ManifestInstance]:
|
|
65
82
|
instances = []
|
|
66
83
|
titlesByLang = {}
|
|
67
84
|
base = path.parent
|
|
68
85
|
tocElts = list(xmlRootElement.iterchildren(tag=Constants.qnEdinetManifestTocComposition.clarkNotation))
|
|
69
|
-
assert len(tocElts) == 1, 'There should be exactly one tocComposition element in the manifest.'
|
|
86
|
+
assert len(tocElts) == 1, _('There should be exactly one tocComposition element in the manifest.')
|
|
70
87
|
for titleElt in tocElts[0].iterchildren(tag=Constants.qnEdinetManifestTitle.clarkNotation):
|
|
71
88
|
lang = titleElt.attrib.get(XbrlConst.qnXmlLang.clarkNotation, "")
|
|
72
89
|
titlesByLang[lang] = titleElt.text.strip() if titleElt.text else ""
|
|
73
90
|
tocItems = _parseManifestTocItems(tocElts[0], None)
|
|
91
|
+
tocItemsByRef = defaultdict(list)
|
|
92
|
+
for tocItem in tocItems:
|
|
93
|
+
tocItemsByRef[tocItem.ref].append(tocItem)
|
|
74
94
|
listElts = list(xmlRootElement.iterchildren(tag=Constants.qnEdinetManifestList.clarkNotation))
|
|
75
|
-
assert len(listElts) == 1, 'There should be exactly one list element in the manifest.'
|
|
95
|
+
assert len(listElts) == 1, _('There should be exactly one list element in the manifest.')
|
|
76
96
|
for instanceElt in listElts[0].iterchildren(tag=Constants.qnEdinetManifestInstance.clarkNotation):
|
|
77
97
|
instanceId = str(instanceElt.attrib.get("id", ""))
|
|
78
98
|
instanceType = str(instanceElt.attrib.get("type", ""))
|
|
@@ -85,40 +105,65 @@ def _parseManifestDoc(xmlRootElement: _Element, path: Path) -> Manifest:
|
|
|
85
105
|
instances.append(ManifestInstance(
|
|
86
106
|
id=instanceId,
|
|
87
107
|
ixbrlFiles=ixbrlFiles,
|
|
108
|
+
path=path,
|
|
88
109
|
preferredFilename=preferredFilename,
|
|
110
|
+
titlesByLang=titlesByLang,
|
|
111
|
+
tocItems=tocItemsByRef.get(instanceId, []),
|
|
89
112
|
type=instanceType,
|
|
90
113
|
))
|
|
91
|
-
return
|
|
92
|
-
instances=instances,
|
|
93
|
-
path=path,
|
|
94
|
-
titlesByLang=titlesByLang,
|
|
95
|
-
tocItems=tocItems,
|
|
96
|
-
)
|
|
114
|
+
return instances
|
|
97
115
|
|
|
98
116
|
|
|
99
117
|
@lru_cache(1)
|
|
100
|
-
def parseManifests(filesource: FileSource) -> list[
|
|
101
|
-
|
|
118
|
+
def parseManifests(filesource: FileSource) -> list[ManifestInstance]:
|
|
119
|
+
assert filesource.cntlr is not None, "FileSource controller must be set before parsing manifests."
|
|
120
|
+
cntlr = filesource.cntlr
|
|
121
|
+
instances: list[ManifestInstance] = []
|
|
102
122
|
if filesource.isArchive:
|
|
103
123
|
if filesource.isTaxonomyPackage:
|
|
104
|
-
return
|
|
124
|
+
return instances
|
|
105
125
|
if filesource.reportPackage is not None:
|
|
106
|
-
return
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
"The EDINET plugin only supports archives in .zip format."
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
126
|
+
return instances
|
|
127
|
+
if not isinstance(filesource.fs, zipfile.ZipFile):
|
|
128
|
+
_invalidManifestError(
|
|
129
|
+
cntlr,
|
|
130
|
+
cast(str, filesource.url),
|
|
131
|
+
_("The EDINET plugin only supports archives in .zip format.")
|
|
132
|
+
)
|
|
133
|
+
else:
|
|
134
|
+
for _archiveFile in (filesource.dir or ()):
|
|
135
|
+
if not Path(_archiveFile).stem.startswith('manifest'):
|
|
136
|
+
continue
|
|
137
|
+
try:
|
|
138
|
+
with filesource.fs.open(_archiveFile) as manifestDoc:
|
|
139
|
+
xmlRootElement = etree.fromstring(manifestDoc.read())
|
|
140
|
+
instances.extend(_parseManifestDoc(xmlRootElement, Path(_archiveFile)))
|
|
141
|
+
except AssertionError as err:
|
|
142
|
+
_invalidManifestError(cntlr, str(_archiveFile), str(err))
|
|
143
|
+
except Exception as err:
|
|
144
|
+
_invalidManifestError(cntlr, str(_archiveFile))
|
|
145
|
+
cntlr.addToLog(_("[Exception] Failed to parse manifest \"{0}\": \n{1} \n{2}").format(
|
|
146
|
+
str(_archiveFile),
|
|
147
|
+
err,
|
|
148
|
+
traceback.format_exc()
|
|
149
|
+
))
|
|
115
150
|
elif (dirpath := Path(str(filesource.url))).is_dir():
|
|
116
151
|
for file in dirpath.rglob("*"):
|
|
117
152
|
if not file.is_file():
|
|
118
153
|
continue
|
|
119
154
|
if not file.stem.startswith('manifest'):
|
|
120
155
|
continue
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
156
|
+
try:
|
|
157
|
+
with open(file, 'rb') as manifestDoc:
|
|
158
|
+
xmlRootElement = etree.fromstring(manifestDoc.read())
|
|
159
|
+
instances.extend(_parseManifestDoc(xmlRootElement, file))
|
|
160
|
+
except AssertionError as err:
|
|
161
|
+
_invalidManifestError(cntlr, str(file), str(err))
|
|
162
|
+
except Exception as err:
|
|
163
|
+
_invalidManifestError(cntlr, str(file))
|
|
164
|
+
cntlr.addToLog(_("[Exception] Failed to parse manifest \"{0}\": \n{1} \n{2}").format(
|
|
165
|
+
str(file),
|
|
166
|
+
err,
|
|
167
|
+
traceback.format_exc()
|
|
168
|
+
))
|
|
169
|
+
return instances
|
|
@@ -22,16 +22,19 @@ from arelle.ValidateXbrl import ValidateXbrl
|
|
|
22
22
|
from arelle.XmlValidate import VALID
|
|
23
23
|
from arelle.typing import TypeGetText
|
|
24
24
|
from arelle.utils.PluginData import PluginData
|
|
25
|
-
from .
|
|
25
|
+
from .Constants import CORPORATE_FORMS
|
|
26
|
+
from .ControllerPluginData import ControllerPluginData
|
|
27
|
+
from .InstanceType import InstanceType
|
|
28
|
+
from .ManifestInstance import ManifestInstance
|
|
26
29
|
|
|
27
30
|
_: TypeGetText
|
|
28
31
|
|
|
29
32
|
|
|
30
33
|
@dataclass(frozen=True)
|
|
31
34
|
class UploadContents:
|
|
32
|
-
amendmentPaths: dict[
|
|
35
|
+
amendmentPaths: dict[InstanceType, frozenset[Path]]
|
|
33
36
|
directories: frozenset[Path]
|
|
34
|
-
|
|
37
|
+
instances: dict[InstanceType, frozenset[Path]]
|
|
35
38
|
unknownPaths: frozenset[Path]
|
|
36
39
|
|
|
37
40
|
|
|
@@ -44,6 +47,7 @@ class PluginValidationDataExtension(PluginData):
|
|
|
44
47
|
jpspsFilingDateCoverPageQn: QName
|
|
45
48
|
liabilitiesAndEquityIfrsQn: QName
|
|
46
49
|
nonConsolidatedMemberQn: QName
|
|
50
|
+
ratioOfFemaleDirectorsAndOtherOfficersQn: QName
|
|
47
51
|
|
|
48
52
|
contextIdPattern: regex.Pattern[str]
|
|
49
53
|
|
|
@@ -64,6 +68,7 @@ class PluginValidationDataExtension(PluginData):
|
|
|
64
68
|
self.jpspsFilingDateCoverPageQn = qname(jpspsNamespace, 'FilingDateCoverPage')
|
|
65
69
|
self.liabilitiesAndEquityIfrsQn = qname(jpigpNamespace, "LiabilitiesAndEquityIFRS")
|
|
66
70
|
self.nonConsolidatedMemberQn = qname(jppfsNamespace, "NonConsolidatedMember")
|
|
71
|
+
self.ratioOfFemaleDirectorsAndOtherOfficersQn = qname(jpcrpNamespace, "RatioOfFemaleDirectorsAndOtherOfficers")
|
|
67
72
|
|
|
68
73
|
self.contextIdPattern = regex.compile(r'(Prior[1-9]Year|CurrentYear|Prior[1-9]Interim|Interim)(Duration|Instant)')
|
|
69
74
|
|
|
@@ -71,6 +76,13 @@ class PluginValidationDataExtension(PluginData):
|
|
|
71
76
|
def __hash__(self) -> int:
|
|
72
77
|
return id(self)
|
|
73
78
|
|
|
79
|
+
@lru_cache(1)
|
|
80
|
+
def isCorporateForm(self, modelXbrl: ModelXbrl) -> bool:
|
|
81
|
+
documentTypes = self.getDocumentTypes(modelXbrl)
|
|
82
|
+
if any(documentType == form.value for form in CORPORATE_FORMS for documentType in documentTypes):
|
|
83
|
+
return True
|
|
84
|
+
return False
|
|
85
|
+
|
|
74
86
|
@lru_cache(1)
|
|
75
87
|
def shouldValidateUpload(self, val: ValidateXbrl) -> bool:
|
|
76
88
|
"""
|
|
@@ -132,6 +144,15 @@ class PluginValidationDataExtension(PluginData):
|
|
|
132
144
|
if isinstance(elt, (ModelObject, LinkPrototype))
|
|
133
145
|
]
|
|
134
146
|
|
|
147
|
+
@lru_cache(1)
|
|
148
|
+
def getManifestInstance(self, modelXbrl: ModelXbrl) -> ManifestInstance | None:
|
|
149
|
+
controllerPluginData = ControllerPluginData.get(modelXbrl.modelManager.cntlr, self.name)
|
|
150
|
+
return controllerPluginData.matchManifestInstance(modelXbrl.ixdsDocUrls)
|
|
151
|
+
|
|
152
|
+
@lru_cache(1)
|
|
153
|
+
def getManifestInstances(self, modelXbrl: ModelXbrl) -> list[ManifestInstance]:
|
|
154
|
+
controllerPluginData = ControllerPluginData.get(modelXbrl.modelManager.cntlr, self.name)
|
|
155
|
+
return controllerPluginData.getManifestInstances()
|
|
135
156
|
|
|
136
157
|
def getUploadFileSizes(self, modelXbrl: ModelXbrl) -> dict[Path, int]:
|
|
137
158
|
"""
|
|
@@ -162,14 +183,14 @@ class PluginValidationDataExtension(PluginData):
|
|
|
162
183
|
if parents[0] == 'XBRL':
|
|
163
184
|
if len(parents) > 1:
|
|
164
185
|
formName = parents[1]
|
|
165
|
-
|
|
166
|
-
if
|
|
167
|
-
forms[
|
|
186
|
+
instanceType = InstanceType.parse(formName)
|
|
187
|
+
if instanceType is not None:
|
|
188
|
+
forms[instanceType].append(path)
|
|
168
189
|
continue
|
|
169
190
|
formName = parents[0]
|
|
170
|
-
|
|
171
|
-
if
|
|
172
|
-
amendmentPaths[
|
|
191
|
+
instanceType = InstanceType.parse(formName)
|
|
192
|
+
if instanceType is not None:
|
|
193
|
+
amendmentPaths[instanceType].append(path)
|
|
173
194
|
continue
|
|
174
195
|
if len(path.suffix) == 0:
|
|
175
196
|
directories.append(path)
|
|
@@ -178,7 +199,7 @@ class PluginValidationDataExtension(PluginData):
|
|
|
178
199
|
return UploadContents(
|
|
179
200
|
amendmentPaths={k: frozenset(v) for k, v in amendmentPaths.items() if len(v) > 0},
|
|
180
201
|
directories=frozenset(directories),
|
|
181
|
-
|
|
202
|
+
instances={k: frozenset(v) for k, v in forms.items() if len(v) > 0},
|
|
182
203
|
unknownPaths=frozenset(unknownPaths)
|
|
183
204
|
)
|
|
184
205
|
|
|
@@ -194,6 +215,10 @@ class PluginValidationDataExtension(PluginData):
|
|
|
194
215
|
paths.update(path.parents)
|
|
195
216
|
return sorted(paths)
|
|
196
217
|
|
|
218
|
+
def hasValidNonNilFact(self, modelXbrl: ModelXbrl, qname: QName) -> bool:
|
|
219
|
+
requiredFacts = modelXbrl.factsByQname.get(qname, set())
|
|
220
|
+
return any(fact.xValid >= VALID and not fact.isNil for fact in requiredFacts)
|
|
221
|
+
|
|
197
222
|
@lru_cache(1)
|
|
198
223
|
def isUpload(self, modelXbrl: ModelXbrl) -> bool:
|
|
199
224
|
if not modelXbrl.fileSource.fs or \
|
|
@@ -5,16 +5,36 @@ from __future__ import annotations
|
|
|
5
5
|
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
|
+
from arelle.FileSource import FileSource
|
|
8
9
|
from arelle.ValidateXbrl import ValidateXbrl
|
|
9
10
|
from arelle.typing import TypeGetText
|
|
10
11
|
from arelle.utils.validate.ValidationPlugin import ValidationPlugin
|
|
12
|
+
from .ControllerPluginData import ControllerPluginData
|
|
11
13
|
from .DisclosureSystems import DISCLOSURE_SYSTEM_EDINET
|
|
14
|
+
from .ManifestInstance import parseManifests
|
|
12
15
|
from .PluginValidationDataExtension import PluginValidationDataExtension
|
|
13
16
|
|
|
14
17
|
_: TypeGetText
|
|
15
18
|
|
|
16
19
|
|
|
17
20
|
class ValidationPluginExtension(ValidationPlugin):
|
|
21
|
+
|
|
22
|
+
def fileSourceEntrypointFiles(self, filesource: FileSource, *args: Any, **kwargs: Any) -> list[dict[str, Any]] | None:
|
|
23
|
+
instances = parseManifests(filesource)
|
|
24
|
+
if len(instances) == 0:
|
|
25
|
+
return None
|
|
26
|
+
assert filesource.cntlr is not None
|
|
27
|
+
pluginData = ControllerPluginData.get(filesource.cntlr, self.name)
|
|
28
|
+
entrypointFiles = []
|
|
29
|
+
for instance in instances:
|
|
30
|
+
pluginData.addManifestInstance(instance)
|
|
31
|
+
entrypoints = []
|
|
32
|
+
for ixbrlFile in instance.ixbrlFiles:
|
|
33
|
+
filesource.select(str(ixbrlFile))
|
|
34
|
+
entrypoints.append({"file": filesource.url})
|
|
35
|
+
entrypointFiles.append({'ixds': entrypoints, 'id': instance.id})
|
|
36
|
+
return entrypointFiles
|
|
37
|
+
|
|
18
38
|
def newPluginData(self, validateXbrl: ValidateXbrl) -> PluginValidationDataExtension:
|
|
19
39
|
disclosureSystem = validateXbrl.disclosureSystem.name
|
|
20
40
|
if disclosureSystem == DISCLOSURE_SYSTEM_EDINET:
|
|
@@ -24,6 +44,3 @@ class ValidationPluginExtension(ValidationPlugin):
|
|
|
24
44
|
return PluginValidationDataExtension(
|
|
25
45
|
self.name,
|
|
26
46
|
)
|
|
27
|
-
|
|
28
|
-
def modelXbrlLoadComplete(self, *args: Any, **kwargs: Any) -> None:
|
|
29
|
-
return None
|
|
@@ -5,17 +5,14 @@ See COPYRIGHT.md for copyright information.
|
|
|
5
5
|
"""
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
-
from collections import defaultdict
|
|
9
8
|
from pathlib import Path
|
|
10
9
|
from typing import Any
|
|
11
10
|
|
|
12
|
-
from arelle.FileSource import FileSource
|
|
13
11
|
from arelle.ModelXbrl import ModelXbrl
|
|
14
12
|
from arelle.Version import authorLabel, copyrightLabel
|
|
15
13
|
from . import Constants
|
|
16
|
-
from .Manifest import Manifest, ManifestInstance, parseManifests
|
|
17
14
|
from .ValidationPluginExtension import ValidationPluginExtension
|
|
18
|
-
from .rules import contexts, edinet, frta, gfm, upload
|
|
15
|
+
from .rules import contexts, edinet, frta, gfm, manifests, upload
|
|
19
16
|
|
|
20
17
|
PLUGIN_NAME = "Validate EDINET"
|
|
21
18
|
DISCLOSURE_SYSTEM_VALIDATION_TYPE = "EDINET"
|
|
@@ -38,6 +35,7 @@ validationPlugin = ValidationPluginExtension(
|
|
|
38
35
|
edinet,
|
|
39
36
|
frta,
|
|
40
37
|
gfm,
|
|
38
|
+
manifests,
|
|
41
39
|
upload,
|
|
42
40
|
],
|
|
43
41
|
)
|
|
@@ -51,19 +49,8 @@ def disclosureSystemConfigURL(*args: Any, **kwargs: Any) -> str:
|
|
|
51
49
|
return validationPlugin.disclosureSystemConfigURL
|
|
52
50
|
|
|
53
51
|
|
|
54
|
-
def fileSourceEntrypointFiles(
|
|
55
|
-
|
|
56
|
-
if len(manifests) == 0:
|
|
57
|
-
return None
|
|
58
|
-
entrypointFiles = []
|
|
59
|
-
for manifest in manifests:
|
|
60
|
-
for instance in manifest.instances:
|
|
61
|
-
entrypoints = []
|
|
62
|
-
for ixbrlFile in instance.ixbrlFiles:
|
|
63
|
-
filesource.select(str(ixbrlFile))
|
|
64
|
-
entrypoints.append({"file": filesource.url})
|
|
65
|
-
entrypointFiles.append({'ixds': entrypoints})
|
|
66
|
-
return entrypointFiles
|
|
52
|
+
def fileSourceEntrypointFiles(*args: Any, **kwargs: Any) -> list[dict[str, Any]] | None:
|
|
53
|
+
return validationPlugin.fileSourceEntrypointFiles(*args, **kwargs)
|
|
67
54
|
|
|
68
55
|
|
|
69
56
|
def loggingSeverityReleveler(modelXbrl: ModelXbrl, level: str, messageCode: str, args: Any, **kwargs: Any) -> tuple[str | None, str | None]:
|
|
@@ -72,10 +59,6 @@ def loggingSeverityReleveler(modelXbrl: ModelXbrl, level: str, messageCode: str,
|
|
|
72
59
|
return level, messageCode
|
|
73
60
|
|
|
74
61
|
|
|
75
|
-
def modelXbrlLoadComplete(*args: Any, **kwargs: Any) -> None:
|
|
76
|
-
return validationPlugin.modelXbrlLoadComplete(*args, **kwargs)
|
|
77
|
-
|
|
78
|
-
|
|
79
62
|
def validateFinally(*args: Any, **kwargs: Any) -> None:
|
|
80
63
|
return validationPlugin.validateFinally(*args, **kwargs)
|
|
81
64
|
|
|
@@ -96,7 +79,6 @@ __pluginInfo__ = {
|
|
|
96
79
|
"DisclosureSystem.ConfigURL": disclosureSystemConfigURL,
|
|
97
80
|
"FileSource.EntrypointFiles": fileSourceEntrypointFiles,
|
|
98
81
|
"Logging.Severity.Releveler": loggingSeverityReleveler,
|
|
99
|
-
"ModelXbrl.LoadComplete": modelXbrlLoadComplete,
|
|
100
82
|
"Validate.XBRL.Finally": validateXbrlFinally,
|
|
101
83
|
"ValidateFormula.Finished": validateFinally,
|
|
102
84
|
}
|
|
@@ -11,7 +11,6 @@ from arelle import XbrlConst, ValidateDuplicateFacts
|
|
|
11
11
|
from arelle.LinkbaseType import LinkbaseType
|
|
12
12
|
from arelle.ValidateDuplicateFacts import DuplicateType
|
|
13
13
|
from arelle.ValidateXbrl import ValidateXbrl
|
|
14
|
-
from arelle.XmlValidateConst import VALID
|
|
15
14
|
from arelle.typing import TypeGetText
|
|
16
15
|
from arelle.utils.PluginHooks import ValidationHook
|
|
17
16
|
from arelle.utils.validate.Decorator import validation
|
|
@@ -39,11 +38,9 @@ def rule_EC1057E(
|
|
|
39
38
|
"""
|
|
40
39
|
dei = pluginData.getDocumentTypes(val.modelXbrl)
|
|
41
40
|
if len(dei) > 0:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
requiredFacts = jpcrpFacts.union(jpspsFacts, jpcrpEsrFacts)
|
|
46
|
-
if not any(fact.xValid >= VALID and not fact.isNil for fact in requiredFacts):
|
|
41
|
+
if not (pluginData.hasValidNonNilFact(val.modelXbrl, pluginData.jpcrpEsrFilingDateCoverPageQn)
|
|
42
|
+
or pluginData.hasValidNonNilFact(val.modelXbrl, pluginData.jpcrpFilingDateCoverPageQn)
|
|
43
|
+
or pluginData.hasValidNonNilFact(val.modelXbrl, pluginData.jpspsFilingDateCoverPageQn)):
|
|
47
44
|
yield Validation.error(
|
|
48
45
|
codes='EDINET.EC1057E',
|
|
49
46
|
msg=_("The [Submission Date] on the cover page has not been filled in."),
|
|
@@ -157,7 +154,7 @@ def rule_EC8027W(
|
|
|
157
154
|
continue
|
|
158
155
|
arcroles = linkbaseType.getArcroles()
|
|
159
156
|
relSet = val.modelXbrl.relationshipSet(tuple(arcroles), roleType.roleURI)
|
|
160
|
-
relSetFrom = relSet.
|
|
157
|
+
relSetFrom = relSet.fromModelObjects()
|
|
161
158
|
rootConcepts = relSet.rootConcepts
|
|
162
159
|
if len(rootConcepts) < 2:
|
|
163
160
|
continue
|
|
@@ -224,3 +221,25 @@ def rule_EC8062W(
|
|
|
224
221
|
assetSum=f"{assetSum:,}",
|
|
225
222
|
modelObject=facts,
|
|
226
223
|
)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
@validation(
|
|
227
|
+
hook=ValidationHook.XBRL_FINALLY,
|
|
228
|
+
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
229
|
+
)
|
|
230
|
+
def rule_EC8075W(
|
|
231
|
+
pluginData: PluginValidationDataExtension,
|
|
232
|
+
val: ValidateXbrl,
|
|
233
|
+
*args: Any,
|
|
234
|
+
**kwargs: Any,
|
|
235
|
+
) -> Iterable[Validation]:
|
|
236
|
+
"""
|
|
237
|
+
EDINET.EC8075W: The percentage of female executives has not been tagged in detail. Ensure that there is
|
|
238
|
+
a nonnil value disclosed for jpcrp_cor:RatioOfFemaleDirectorsAndOtherOfficers.
|
|
239
|
+
"""
|
|
240
|
+
if pluginData.isCorporateForm(val.modelXbrl):
|
|
241
|
+
if not pluginData.hasValidNonNilFact(val.modelXbrl, pluginData.ratioOfFemaleDirectorsAndOtherOfficersQn):
|
|
242
|
+
yield Validation.warning(
|
|
243
|
+
codes='EDINET.EC8075W',
|
|
244
|
+
msg=_("The percentage of female executives has not been tagged in detail."),
|
|
245
|
+
)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""
|
|
2
|
+
See COPYRIGHT.md for copyright information.
|
|
3
|
+
"""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import Any, Iterable
|
|
7
|
+
|
|
8
|
+
from arelle.LinkbaseType import LinkbaseType
|
|
9
|
+
from arelle.ModelXbrl import ModelXbrl
|
|
10
|
+
from arelle.ValidateXbrl import ValidateXbrl
|
|
11
|
+
from arelle.typing import TypeGetText
|
|
12
|
+
from arelle.utils.PluginHooks import ValidationHook
|
|
13
|
+
from arelle.utils.validate.Decorator import validation
|
|
14
|
+
from arelle.utils.validate.Validation import Validation
|
|
15
|
+
from ..DisclosureSystems import (DISCLOSURE_SYSTEM_EDINET)
|
|
16
|
+
from ..ManifestInstance import ManifestTocItem
|
|
17
|
+
from ..PluginValidationDataExtension import PluginValidationDataExtension
|
|
18
|
+
|
|
19
|
+
_: TypeGetText
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _validateTocItem(modelXbrl: ModelXbrl, tocItem: ManifestTocItem, instanceId: str) -> Iterable[Validation]:
|
|
23
|
+
for childTocItem in tocItem.childItems:
|
|
24
|
+
yield from _validateTocItem(modelXbrl, childTocItem, instanceId)
|
|
25
|
+
relSet = modelXbrl.relationshipSet(tuple(LinkbaseType.PRESENTATION.getArcroles()), tocItem.extrole)
|
|
26
|
+
if len(relSet.fromModelObjects()) == 0:
|
|
27
|
+
yield Validation.error(
|
|
28
|
+
codes="EDINET.EC5800E.FATAL_ERROR_TOC_TREE_NOT_DEFINED",
|
|
29
|
+
msg=_("A table of contents item specified an extended link role "
|
|
30
|
+
"that does not exist in the presentation linkbase. "),
|
|
31
|
+
modelObject=tocItem.element,
|
|
32
|
+
)
|
|
33
|
+
for conceptQname in {tocItem.parent, tocItem.start, tocItem.end}:
|
|
34
|
+
if conceptQname is None:
|
|
35
|
+
continue
|
|
36
|
+
concept = modelXbrl.qnameConcepts.get(conceptQname)
|
|
37
|
+
if concept is None or not relSet.contains(concept):
|
|
38
|
+
yield Validation.error(
|
|
39
|
+
codes="EDINET.EC5800E.ERROR_ELEMENT_NOT_DEFINED_IN_EXTENDED_LINK_ROLE",
|
|
40
|
+
msg=_("A table of contents item specified an extended link role "
|
|
41
|
+
"that does not contain the element specified by the parent "
|
|
42
|
+
"attribute of the insert element or the start or end "
|
|
43
|
+
"attribute of the item element. Check the extended link "
|
|
44
|
+
"role and correct it."),
|
|
45
|
+
modelObject=tocItem.element,
|
|
46
|
+
)
|
|
47
|
+
if tocItem.end is not None:
|
|
48
|
+
if tocItem.start is None or \
|
|
49
|
+
not relSet.isRelated(tocItem.start, "child", tocItem.end):
|
|
50
|
+
yield Validation.error(
|
|
51
|
+
codes="EDINET.EC5800E.ERROR_ENDING_ELEMENT_NOT_DEFINED_UNDER_STARTING_ELEMENT",
|
|
52
|
+
msg=_("A table of contents item specified an end element that "
|
|
53
|
+
"is not a descendant of the start element within the "
|
|
54
|
+
"specified extended link role. Please check and correct "
|
|
55
|
+
"the table of contents items."),
|
|
56
|
+
modelObject=tocItem.element,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@validation(
|
|
61
|
+
hook=ValidationHook.XBRL_FINALLY,
|
|
62
|
+
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
63
|
+
)
|
|
64
|
+
def rule_EC5800E(
|
|
65
|
+
pluginData: PluginValidationDataExtension,
|
|
66
|
+
val: ValidateXbrl,
|
|
67
|
+
*args: Any,
|
|
68
|
+
**kwargs: Any,
|
|
69
|
+
) -> Iterable[Validation]:
|
|
70
|
+
"""
|
|
71
|
+
EDINET.EC5800E.FATAL_ERROR_TOC_TREE_NOT_DEFINED:
|
|
72
|
+
A table of contents item specified an extended link role that does not
|
|
73
|
+
exist in the presentation linkbase.
|
|
74
|
+
EDINET.EC5800E.ERROR_ELEMENT_NOT_DEFINED_IN_EXTENDED_LINK_ROLE:
|
|
75
|
+
A table of contents item specified an extended link role that does not
|
|
76
|
+
contain the element specified by the parent attribute of the insert
|
|
77
|
+
element or the start or end attribute of the item element. Check the
|
|
78
|
+
extended link role and correct it.
|
|
79
|
+
EDINET.EC5800E.ERROR_ENDING_ELEMENT_NOT_DEFINED_UNDER_STARTING_ELEMENT:
|
|
80
|
+
A table of contents item specified an end element that is not a descendant
|
|
81
|
+
of the start element within the specified extended link role. Please check
|
|
82
|
+
and correct the table of contents items.
|
|
83
|
+
"""
|
|
84
|
+
instance = pluginData.getManifestInstance(val.modelXbrl)
|
|
85
|
+
if instance is None:
|
|
86
|
+
return
|
|
87
|
+
for tocItem in instance.tocItems:
|
|
88
|
+
yield from _validateTocItem(val.modelXbrl, tocItem, instance.id)
|