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.

@@ -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 Manifest:
22
- instances: list[ManifestInstance]
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
- @dataclass(frozen=True)
39
- class ManifestInstance:
40
- id: str
41
- ixbrlFiles: list[Path]
42
- preferredFilename: str
43
- type: str
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) -> Manifest:
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 Manifest(
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[Manifest]:
101
- manifests: list[Manifest] = []
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 manifests
124
+ return instances
105
125
  if filesource.reportPackage is not None:
106
- return manifests
107
- for _archiveFile in (filesource.dir or ()):
108
- if not Path(_archiveFile).stem.startswith('manifest'):
109
- continue
110
- assert isinstance(filesource.fs, zipfile.ZipFile), \
111
- "The EDINET plugin only supports archives in .zip format."
112
- with filesource.fs.open(_archiveFile) as manifestDoc:
113
- xmlRootElement = etree.fromstring(manifestDoc.read())
114
- manifests.append(_parseManifestDoc(xmlRootElement, Path(_archiveFile)))
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
- with open(file, 'rb') as manifestDoc:
122
- xmlRootElement = etree.fromstring(manifestDoc.read())
123
- manifests.append(_parseManifestDoc(xmlRootElement, file))
124
- return manifests
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 .FormType import FormType
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[FormType, frozenset[Path]]
35
+ amendmentPaths: dict[InstanceType, frozenset[Path]]
33
36
  directories: frozenset[Path]
34
- forms: dict[FormType, frozenset[Path]]
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
- formType = FormType.parse(formName)
166
- if formType is not None:
167
- forms[formType].append(path)
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
- formType = FormType.parse(formName)
171
- if formType is not None:
172
- amendmentPaths[formType].append(path)
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
- forms={k: frozenset(v) for k, v in forms.items() if len(v) > 0},
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(filesource: FileSource, inlineOnly: bool, *args: Any, **kwargs: Any) -> list[dict[str, Any]] | None:
55
- manifests = parseManifests(filesource)
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
- jpcrpEsrFacts = val.modelXbrl.factsByQname.get(pluginData.jpcrpEsrFilingDateCoverPageQn, set())
43
- jpcrpFacts = val.modelXbrl.factsByQname.get(pluginData.jpcrpFilingDateCoverPageQn, set())
44
- jpspsFacts = val.modelXbrl.factsByQname.get(pluginData.jpspsFilingDateCoverPageQn, set())
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.loadModelRelationshipsFrom()
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)