arelle-release 2.37.44__py3-none-any.whl → 2.37.46__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.
@@ -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,17 +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
26
25
  from .Constants import CORPORATE_FORMS
26
+ from .ControllerPluginData import ControllerPluginData
27
+ from .InstanceType import InstanceType
28
+ from .ManifestInstance import ManifestInstance
27
29
 
28
30
  _: TypeGetText
29
31
 
30
32
 
31
33
  @dataclass(frozen=True)
32
34
  class UploadContents:
33
- amendmentPaths: dict[FormType, frozenset[Path]]
35
+ amendmentPaths: dict[InstanceType, frozenset[Path]]
34
36
  directories: frozenset[Path]
35
- forms: dict[FormType, frozenset[Path]]
37
+ instances: dict[InstanceType, frozenset[Path]]
36
38
  unknownPaths: frozenset[Path]
37
39
 
38
40
 
@@ -142,6 +144,15 @@ class PluginValidationDataExtension(PluginData):
142
144
  if isinstance(elt, (ModelObject, LinkPrototype))
143
145
  ]
144
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()
145
156
 
146
157
  def getUploadFileSizes(self, modelXbrl: ModelXbrl) -> dict[Path, int]:
147
158
  """
@@ -172,14 +183,14 @@ class PluginValidationDataExtension(PluginData):
172
183
  if parents[0] == 'XBRL':
173
184
  if len(parents) > 1:
174
185
  formName = parents[1]
175
- formType = FormType.parse(formName)
176
- if formType is not None:
177
- forms[formType].append(path)
186
+ instanceType = InstanceType.parse(formName)
187
+ if instanceType is not None:
188
+ forms[instanceType].append(path)
178
189
  continue
179
190
  formName = parents[0]
180
- formType = FormType.parse(formName)
181
- if formType is not None:
182
- amendmentPaths[formType].append(path)
191
+ instanceType = InstanceType.parse(formName)
192
+ if instanceType is not None:
193
+ amendmentPaths[instanceType].append(path)
183
194
  continue
184
195
  if len(path.suffix) == 0:
185
196
  directories.append(path)
@@ -188,7 +199,7 @@ class PluginValidationDataExtension(PluginData):
188
199
  return UploadContents(
189
200
  amendmentPaths={k: frozenset(v) for k, v in amendmentPaths.items() if len(v) > 0},
190
201
  directories=frozenset(directories),
191
- 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},
192
203
  unknownPaths=frozenset(unknownPaths)
193
204
  )
194
205
 
@@ -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
  }
@@ -154,7 +154,7 @@ def rule_EC8027W(
154
154
  continue
155
155
  arcroles = linkbaseType.getArcroles()
156
156
  relSet = val.modelXbrl.relationshipSet(tuple(arcroles), roleType.roleURI)
157
- relSetFrom = relSet.loadModelRelationshipsFrom()
157
+ relSetFrom = relSet.fromModelObjects()
158
158
  rootConcepts = relSet.rootConcepts
159
159
  if len(rootConcepts) < 2:
160
160
  continue
@@ -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)
@@ -5,9 +5,8 @@ from __future__ import annotations
5
5
 
6
6
  import re
7
7
  from collections import defaultdict
8
- from collections.abc import Iterable
9
8
  from pathlib import Path
10
- from typing import Any
9
+ from typing import Any, Iterable
11
10
 
12
11
  from arelle.ValidateXbrl import ValidateXbrl
13
12
  from arelle.typing import TypeGetText
@@ -15,8 +14,7 @@ from arelle.utils.PluginHooks import ValidationHook
15
14
  from arelle.utils.validate.Decorator import validation
16
15
  from arelle.utils.validate.Validation import Validation
17
16
  from ..DisclosureSystems import (DISCLOSURE_SYSTEM_EDINET)
18
- from ..FormType import FormType, HTML_EXTENSIONS, IMAGE_EXTENSIONS
19
- from ..Manifest import parseManifests
17
+ from ..InstanceType import InstanceType, HTML_EXTENSIONS, IMAGE_EXTENSIONS
20
18
  from ..PluginValidationDataExtension import PluginValidationDataExtension
21
19
 
22
20
  _: TypeGetText
@@ -162,19 +160,19 @@ def rule_EC0130E(
162
160
  return
163
161
  uploadContents = pluginData.getUploadContents(val.modelXbrl)
164
162
  checks = []
165
- for formType, amendmentPaths in uploadContents.amendmentPaths.items():
163
+ for instanceType, amendmentPaths in uploadContents.amendmentPaths.items():
166
164
  for amendmentPath in amendmentPaths:
167
- isSubdirectory = amendmentPath.parent.name != formType.value
168
- checks.append((amendmentPath, True, formType, isSubdirectory))
169
- for formType, formPaths in uploadContents.forms.items():
165
+ isSubdirectory = amendmentPath.parent.name != instanceType.value
166
+ checks.append((amendmentPath, True, instanceType, isSubdirectory))
167
+ for instanceType, formPaths in uploadContents.instances.items():
170
168
  for amendmentPath in formPaths:
171
- isSubdirectory = amendmentPath.parent.name != formType.value
172
- checks.append((amendmentPath, False, formType, isSubdirectory))
173
- for path, isAmendment, formType, isSubdirectory in checks:
169
+ isSubdirectory = amendmentPath.parent.name != instanceType.value
170
+ checks.append((amendmentPath, False, instanceType, isSubdirectory))
171
+ for path, isAmendment, instanceType, isSubdirectory in checks:
174
172
  ext = path.suffix
175
173
  if len(ext) == 0:
176
174
  continue
177
- validExtensions = formType.getValidExtensions(isAmendment, isSubdirectory)
175
+ validExtensions = instanceType.getValidExtensions(isAmendment, isSubdirectory)
178
176
  if validExtensions is None:
179
177
  continue
180
178
  if ext not in validExtensions:
@@ -209,17 +207,17 @@ def rule_EC0132E(
209
207
  if not pluginData.shouldValidateUpload(val):
210
208
  return
211
209
  uploadContents = pluginData.getUploadContents(val.modelXbrl)
212
- for formType in (FormType.AUDIT_DOC, FormType.PRIVATE_DOC, FormType.PUBLIC_DOC):
213
- if formType not in uploadContents.forms:
210
+ for instanceType in (InstanceType.AUDIT_DOC, InstanceType.PRIVATE_DOC, InstanceType.PUBLIC_DOC):
211
+ if instanceType not in uploadContents.instances:
214
212
  continue
215
- if formType.manifestPath in uploadContents.forms.get(formType, []):
213
+ if instanceType.manifestPath in uploadContents.instances.get(instanceType, []):
216
214
  continue
217
215
  yield Validation.error(
218
216
  codes='EDINET.EC0132E',
219
217
  msg=_("'%(expectedManifestName)s' does not exist in '%(expectedManifestDirectory)s'. "
220
218
  "Please store the manifest file (or cover file) directly under the relevant folder and upload it again. "),
221
- expectedManifestName=formType.manifestPath.name,
222
- expectedManifestDirectory=str(formType.manifestPath.parent),
219
+ expectedManifestName=instanceType.manifestPath.name,
220
+ expectedManifestDirectory=str(instanceType.manifestPath.parent),
223
221
  )
224
222
 
225
223
 
@@ -478,40 +476,40 @@ def rule_manifest_preferredFilename(
478
476
  """
479
477
  if not pluginData.shouldValidateUpload(val):
480
478
  return
481
- manifests = parseManifests(val.modelXbrl.fileSource)
482
- for manifest in manifests:
483
- preferredFilenames = set()
484
- duplicateFilenames = set()
485
- for instance in manifest.instances:
486
- if len(instance.preferredFilename) == 0:
487
- yield Validation.error(
488
- codes='EDINET.EC5804E',
489
- msg=_("The instance file name is not set. "
490
- "Set the instance name as the preferredFilename attribute value "
491
- "of the instance element in the manifest file. (manifest: '%(manifest)s', id: %(id)s)"),
492
- manifest=str(manifest.path),
493
- id=instance.id,
494
- )
495
- continue
496
- preferredFilename = Path(instance.preferredFilename)
497
- if preferredFilename.suffix != '.xbrl':
498
- yield Validation.error(
499
- codes='EDINET.EC5805E',
500
- msg=_("The instance file extension is not '.xbrl'. "
501
- "File name: '%(preferredFilename)s'. "
502
- "Please change the extension of the instance name set in the "
503
- "preferredFilename attribute value of the instance element in "
504
- "the manifest file to '.xbrl'. (manifest: '%(manifest)s', id: %(id)s)"),
505
- preferredFilename=instance.preferredFilename,
506
- manifest=str(manifest.path),
507
- id=instance.id,
508
- )
509
- continue
510
- if instance.preferredFilename in preferredFilenames:
511
- duplicateFilenames.add(instance.preferredFilename)
512
- continue
513
- preferredFilenames.add(instance.preferredFilename)
514
- for duplicateFilename in duplicateFilenames:
479
+ instances = pluginData.getManifestInstances(val.modelXbrl)
480
+ preferredFilenames: dict[Path, set[str]] = defaultdict(set)
481
+ duplicateFilenames = defaultdict(set)
482
+ for instance in instances:
483
+ if len(instance.preferredFilename) == 0:
484
+ yield Validation.error(
485
+ codes='EDINET.EC5804E',
486
+ msg=_("The instance file name is not set. "
487
+ "Set the instance name as the preferredFilename attribute value "
488
+ "of the instance element in the manifest file. (manifest: '%(manifest)s', id: %(id)s)"),
489
+ manifest=str(instance.path),
490
+ id=instance.id,
491
+ )
492
+ continue
493
+ preferredFilename = Path(instance.preferredFilename)
494
+ if preferredFilename.suffix != '.xbrl':
495
+ yield Validation.error(
496
+ codes='EDINET.EC5805E',
497
+ msg=_("The instance file extension is not '.xbrl'. "
498
+ "File name: '%(preferredFilename)s'. "
499
+ "Please change the extension of the instance name set in the "
500
+ "preferredFilename attribute value of the instance element in "
501
+ "the manifest file to '.xbrl'. (manifest: '%(manifest)s', id: %(id)s)"),
502
+ preferredFilename=instance.preferredFilename,
503
+ manifest=str(instance.path),
504
+ id=instance.id,
505
+ )
506
+ continue
507
+ if instance.preferredFilename in preferredFilenames[instance.path]:
508
+ duplicateFilenames[instance.path].add(instance.preferredFilename)
509
+ continue
510
+ preferredFilenames[instance.path].add(instance.preferredFilename)
511
+ for path, filenames in duplicateFilenames.items():
512
+ for filename in filenames:
515
513
  yield Validation.error(
516
514
  codes='EDINET.EC5806E',
517
515
  msg=_("The same instance file name is set multiple times. "
@@ -519,6 +517,6 @@ def rule_manifest_preferredFilename(
519
517
  "The preferredFilename attribute value of the instance "
520
518
  "element in the manifest file must be unique within the "
521
519
  "same file. (manifest: '%(manifest)s')"),
522
- manifest=str(manifest.path),
523
- preferredFilename=duplicateFilename,
520
+ manifest=str(path),
521
+ preferredFilename=filename,
524
522
  )
@@ -257,22 +257,22 @@ def checkSVGContentElt(
257
257
  ) -> Iterable[Validation]:
258
258
  guidance = params.contentOtherThanXHTMLGuidance
259
259
  rootElement = True
260
- for elt in elt.iter():
260
+ for childElt in elt.iter():
261
261
  if rootElement:
262
- if elt.tag != "{http://www.w3.org/2000/svg}svg":
262
+ if childElt.tag != "{http://www.w3.org/2000/svg}svg":
263
263
  yield Validation.error((f"{guidance}.imageFileCannotBeLoaded", "NL.NL-KVK.3.5.1.imageFileCannotBeLoaded"),
264
264
  _("Image SVG has root element which is not svg"),
265
265
  modelObject=imgElts)
266
266
  rootElement = False
267
267
  # Comments, processing instructions, and maybe other special constructs don't have string tags.
268
- if not isinstance(elt.tag, str):
268
+ if not isinstance(childElt.tag, str):
269
269
  continue
270
- eltTag = elt.tag.rpartition("}")[2] # strip namespace
270
+ eltTag = childElt.tag.rpartition("}")[2] # strip namespace
271
271
  if eltTag == "image":
272
- imgElts = [*imgElts, elt]
273
- yield from validateImage(baseUrl, getHref(elt), modelXbrl, val, imgElts, "", params)
272
+ imgElts = [*imgElts, childElt]
273
+ yield from validateImage(baseUrl, getHref(childElt), modelXbrl, val, imgElts, "", params)
274
274
  if eltTag in ("object", "script", "audio", "foreignObject", "iframe", "image", "use", "video"):
275
- href = elt.get("href","")
275
+ href = childElt.get("href","")
276
276
  if eltTag in ("object", "script") or "javascript:" in href:
277
277
  yield Validation.error((f"{guidance}.executableCodePresent", "NL.NL-KVK.3.5.1.1.executableCodePresent"),
278
278
  _("Inline XBRL images MUST NOT contain executable code: %(element)s"),