arelle-release 2.37.46__py3-none-any.whl → 2.38.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. arelle/BetaFeatures.py +0 -21
  2. arelle/Cntlr.py +15 -8
  3. arelle/CntlrCmdLine.py +121 -56
  4. arelle/CntlrWinMain.py +143 -70
  5. arelle/DialogFind.py +1 -1
  6. arelle/DialogPluginManager.py +6 -4
  7. arelle/DisclosureSystem.py +7 -0
  8. arelle/ErrorManager.py +21 -6
  9. arelle/FileSource.py +11 -4
  10. arelle/FunctionIxt.py +16 -11
  11. arelle/HtmlUtil.py +5 -4
  12. arelle/LeiUtil.py +63 -43
  13. arelle/ModelDocument.py +20 -15
  14. arelle/ModelDtsObject.py +8 -0
  15. arelle/ModelInstanceObject.py +1 -1
  16. arelle/ModelObject.py +16 -18
  17. arelle/ModelObjectFactory.py +35 -17
  18. arelle/ModelXbrl.py +28 -11
  19. arelle/PluginManager.py +130 -105
  20. arelle/RuntimeOptions.py +1 -0
  21. arelle/UrlUtil.py +14 -0
  22. arelle/Validate.py +17 -12
  23. arelle/ValidateDuplicateFacts.py +3 -1
  24. arelle/ValidateFileSource.py +38 -0
  25. arelle/ValidateFilingText.py +3 -3
  26. arelle/ValidateXbrl.py +5 -2
  27. arelle/ValidateXbrlCalcs.py +210 -186
  28. arelle/ValidateXbrlDTS.py +1 -1
  29. arelle/ViewFile.py +1 -0
  30. arelle/ViewFileFactTable.py +2 -2
  31. arelle/ViewWinDTS.py +4 -1
  32. arelle/WebCache.py +28 -24
  33. arelle/XbrlConst.py +22 -0
  34. arelle/XmlUtil.py +16 -21
  35. arelle/XmlValidate.py +6 -9
  36. arelle/_version.py +16 -3
  37. arelle/api/Session.py +11 -2
  38. arelle/config/disclosuresystems.xsd +2 -0
  39. arelle/config/rosettaEntitlements.plist +8 -0
  40. arelle/conformance/CSVTestcaseLoader.py +1 -1
  41. arelle/formula/XPathContext.py +3 -3
  42. arelle/logging/formatters/LogFormatter.py +3 -1
  43. arelle/packages/report/ReportPackage.py +26 -13
  44. arelle/packages/report/ReportPackageConst.py +0 -1
  45. arelle/plugin/inlineXbrlDocumentSet.py +19 -5
  46. arelle/plugin/validate/DBA/DisclosureSystems.py +19 -1
  47. arelle/plugin/validate/DBA/PluginValidationDataExtension.py +2 -4
  48. arelle/plugin/validate/DBA/ValidationPluginExtension.py +2 -1
  49. arelle/plugin/validate/DBA/resources/config.xml +5 -0
  50. arelle/plugin/validate/DBA/rules/__init__.py +2 -2
  51. arelle/plugin/validate/DBA/rules/fr.py +19 -2
  52. arelle/plugin/validate/DBA/rules/tc.py +2 -0
  53. arelle/plugin/validate/DBA/rules/th.py +6 -0
  54. arelle/plugin/validate/DBA/rules/tm.py +18 -5
  55. arelle/plugin/validate/DBA/rules/tr.py +11 -5
  56. arelle/plugin/validate/EDINET/Constants.py +193 -9
  57. arelle/plugin/validate/EDINET/ContextRequirement.py +58 -0
  58. arelle/plugin/validate/EDINET/ControllerPluginData.py +220 -1
  59. arelle/plugin/validate/EDINET/CoverItemRequirements.py +42 -0
  60. arelle/plugin/validate/EDINET/DeiRequirements.py +118 -0
  61. arelle/plugin/validate/EDINET/FilingFormat.py +275 -0
  62. arelle/plugin/validate/EDINET/FormType.py +134 -0
  63. arelle/plugin/validate/EDINET/ManifestInstance.py +72 -5
  64. arelle/plugin/validate/EDINET/NamespaceConfig.py +50 -0
  65. arelle/plugin/validate/EDINET/PluginValidationDataExtension.py +493 -132
  66. arelle/plugin/validate/EDINET/{InstanceType.py → ReportFolderType.py} +72 -15
  67. arelle/plugin/validate/EDINET/Statement.py +139 -0
  68. arelle/plugin/validate/EDINET/TableOfContentsBuilder.py +595 -0
  69. arelle/plugin/validate/EDINET/UploadContents.py +48 -0
  70. arelle/plugin/validate/EDINET/ValidationPluginExtension.py +20 -2
  71. arelle/plugin/validate/EDINET/__init__.py +31 -6
  72. arelle/plugin/validate/EDINET/resources/config.xml +8 -1
  73. arelle/plugin/validate/EDINET/resources/cover-item-requirements.json +793 -0
  74. arelle/plugin/validate/EDINET/resources/dei-requirements.csv +27 -0
  75. arelle/plugin/validate/EDINET/resources/edinet-taxonomies.xml +2 -0
  76. arelle/plugin/validate/EDINET/rules/contexts.py +375 -14
  77. arelle/plugin/validate/EDINET/rules/edinet.py +1934 -45
  78. arelle/plugin/validate/EDINET/rules/frta.py +122 -3
  79. arelle/plugin/validate/EDINET/rules/gfm.py +1907 -11
  80. arelle/plugin/validate/EDINET/rules/upload.py +989 -141
  81. arelle/plugin/validate/ESEF/Const.py +3 -1
  82. arelle/plugin/validate/ESEF/ESEF_2021/DTS.py +5 -0
  83. arelle/plugin/validate/ESEF/ESEF_2021/Image.py +2 -2
  84. arelle/plugin/validate/ESEF/ESEF_2021/ValidateXbrlFinally.py +23 -20
  85. arelle/plugin/validate/ESEF/ESEF_Current/DTS.py +47 -14
  86. arelle/plugin/validate/ESEF/ESEF_Current/ValidateXbrlFinally.py +100 -25
  87. arelle/plugin/validate/ESEF/__init__.py +20 -6
  88. arelle/plugin/validate/ESEF/resources/authority-validations.json +76 -9
  89. arelle/plugin/validate/ESEF/resources/config.xml +20 -0
  90. arelle/plugin/validate/NL/DisclosureSystems.py +22 -0
  91. arelle/plugin/validate/NL/PluginValidationDataExtension.py +27 -9
  92. arelle/plugin/validate/NL/ValidationPluginExtension.py +51 -7
  93. arelle/plugin/validate/NL/resources/config.xml +18 -0
  94. arelle/plugin/validate/NL/rules/br_kvk.py +17 -61
  95. arelle/plugin/validate/NL/rules/fg_nl.py +7 -38
  96. arelle/plugin/validate/NL/rules/fr_kvk.py +7 -42
  97. arelle/plugin/validate/NL/rules/fr_nl.py +31 -147
  98. arelle/plugin/validate/NL/rules/nl_kvk.py +142 -28
  99. arelle/plugin/validate/ROS/PluginValidationDataExtension.py +2 -0
  100. arelle/plugin/validate/ROS/ValidationPluginExtension.py +4 -1
  101. arelle/plugin/validate/ROS/rules/ros.py +41 -9
  102. arelle/plugin/validate/UK/ValidateUK.py +130 -66
  103. arelle/plugin/validate/UK/__init__.py +89 -103
  104. arelle/utils/EntryPointDetection.py +79 -13
  105. arelle/utils/PluginHooks.py +125 -0
  106. arelle/utils/validate/ESEFImage.py +6 -6
  107. arelle/utils/validate/Validation.py +18 -0
  108. arelle/utils/validate/ValidationPlugin.py +76 -11
  109. arelle/utils/validate/ValidationUtil.py +35 -3
  110. {arelle_release-2.37.46.dist-info → arelle_release-2.38.0.dist-info}/METADATA +30 -20
  111. {arelle_release-2.37.46.dist-info → arelle_release-2.38.0.dist-info}/RECORD +115 -191
  112. {arelle_release-2.37.46.dist-info → arelle_release-2.38.0.dist-info}/licenses/LICENSE.md +0 -3
  113. arelle/archive/CustomLogger.py +0 -43
  114. arelle/archive/LoadEFMvalidate.py +0 -32
  115. arelle/archive/LoadSavePreLbCsv.py +0 -26
  116. arelle/archive/LoadValidate.cs +0 -31
  117. arelle/archive/LoadValidate.py +0 -36
  118. arelle/archive/LoadValidateCmdLine.java +0 -69
  119. arelle/archive/LoadValidatePostedZip.java +0 -57
  120. arelle/archive/LoadValidateWebService.java +0 -34
  121. arelle/archive/SaveTableToExelle.py +0 -140
  122. arelle/archive/TR3toTR4.py +0 -88
  123. arelle/archive/plugin/ESEF_2022/__init__.py +0 -47
  124. arelle/archive/plugin/bigInstance.py +0 -394
  125. arelle/archive/plugin/cmdWebServerExtension.py +0 -43
  126. arelle/archive/plugin/crashTest.py +0 -38
  127. arelle/archive/plugin/functionsXmlCreation.py +0 -106
  128. arelle/archive/plugin/hello_i18n.pot +0 -26
  129. arelle/archive/plugin/hello_i18n.py +0 -32
  130. arelle/archive/plugin/importTestChild1.py +0 -21
  131. arelle/archive/plugin/importTestChild2.py +0 -22
  132. arelle/archive/plugin/importTestGrandchild1.py +0 -21
  133. arelle/archive/plugin/importTestGrandchild2.py +0 -21
  134. arelle/archive/plugin/importTestImported1.py +0 -23
  135. arelle/archive/plugin/importTestImported11.py +0 -22
  136. arelle/archive/plugin/importTestParent.py +0 -48
  137. arelle/archive/plugin/instanceInfo.py +0 -306
  138. arelle/archive/plugin/loadFromOIM-2018.py +0 -1282
  139. arelle/archive/plugin/locale/fr/LC_MESSAGES/hello_i18n.po +0 -25
  140. arelle/archive/plugin/objectmaker.py +0 -285
  141. arelle/archive/plugin/packagedImportTest/__init__.py +0 -47
  142. arelle/archive/plugin/packagedImportTest/importTestChild1.py +0 -21
  143. arelle/archive/plugin/packagedImportTest/importTestChild2.py +0 -22
  144. arelle/archive/plugin/packagedImportTest/importTestGrandchild1.py +0 -21
  145. arelle/archive/plugin/packagedImportTest/importTestGrandchild2.py +0 -21
  146. arelle/archive/plugin/packagedImportTest/importTestImported1.py +0 -24
  147. arelle/archive/plugin/packagedImportTest/importTestImported11.py +0 -21
  148. arelle/archive/plugin/packagedImportTest/subdir/importTestImported111.py +0 -21
  149. arelle/archive/plugin/packagedImportTest/subdir/subsubdir/importTestImported1111.py +0 -21
  150. arelle/archive/plugin/sakaCalendar.py +0 -215
  151. arelle/archive/plugin/saveInstanceInfoset.py +0 -121
  152. arelle/archive/plugin/sphinx/FormulaGenerator.py +0 -823
  153. arelle/archive/plugin/sphinx/SphinxContext.py +0 -404
  154. arelle/archive/plugin/sphinx/SphinxEvaluator.py +0 -783
  155. arelle/archive/plugin/sphinx/SphinxMethods.py +0 -1287
  156. arelle/archive/plugin/sphinx/SphinxParser.py +0 -1093
  157. arelle/archive/plugin/sphinx/SphinxValidator.py +0 -163
  158. arelle/archive/plugin/sphinx/US-GAAP Ratios Example.xsr +0 -52
  159. arelle/archive/plugin/sphinx/__init__.py +0 -285
  160. arelle/archive/plugin/streamingExtensions.py +0 -335
  161. arelle/archive/plugin/updateTableLB.py +0 -242
  162. arelle/archive/plugin/validate/SBRnl/CustomLoader.py +0 -19
  163. arelle/archive/plugin/validate/SBRnl/DTS.py +0 -305
  164. arelle/archive/plugin/validate/SBRnl/Dimensions.py +0 -357
  165. arelle/archive/plugin/validate/SBRnl/Document.py +0 -799
  166. arelle/archive/plugin/validate/SBRnl/Filing.py +0 -467
  167. arelle/archive/plugin/validate/SBRnl/__init__.py +0 -75
  168. arelle/archive/plugin/validate/SBRnl/config.xml +0 -26
  169. arelle/archive/plugin/validate/SBRnl/sbr-nl-taxonomies.xml +0 -754
  170. arelle/archive/plugin/validate/USBestPractices.py +0 -570
  171. arelle/archive/plugin/validate/USCorpAction.py +0 -557
  172. arelle/archive/plugin/validate/USSecTagging.py +0 -337
  173. arelle/archive/plugin/validate/XDC/__init__.py +0 -77
  174. arelle/archive/plugin/validate/XDC/config.xml +0 -20
  175. arelle/archive/plugin/validate/XFsyntax/__init__.py +0 -64
  176. arelle/archive/plugin/validate/XFsyntax/xf.py +0 -2227
  177. arelle/archive/plugin/validate/calc2.py +0 -536
  178. arelle/archive/plugin/validateSchemaLxml.py +0 -156
  179. arelle/archive/plugin/validateTableInfoset.py +0 -52
  180. arelle/archive/us-gaap-dei-docType-extraction-frm.xml +0 -90
  181. arelle/archive/us-gaap-dei-ratio-cash-frm.xml +0 -150
  182. arelle/examples/plugin/formulaSuiteConverter.py +0 -212
  183. arelle/examples/plugin/functionsCustom.py +0 -59
  184. arelle/examples/plugin/hello_dolly.py +0 -64
  185. arelle/examples/plugin/multi.py +0 -58
  186. arelle/examples/plugin/rssSaveOim.py +0 -96
  187. arelle/examples/plugin/validate/XYZ/DisclosureSystems.py +0 -2
  188. arelle/examples/plugin/validate/XYZ/PluginValidationDataExtension.py +0 -10
  189. arelle/examples/plugin/validate/XYZ/ValidationPluginExtension.py +0 -49
  190. arelle/examples/plugin/validate/XYZ/__init__.py +0 -75
  191. arelle/examples/plugin/validate/XYZ/resources/config.xml +0 -16
  192. arelle/examples/plugin/validate/XYZ/rules/__init__.py +0 -0
  193. arelle/examples/plugin/validate/XYZ/rules/rules01.py +0 -110
  194. arelle/examples/plugin/validate/XYZ/rules/rules02.py +0 -59
  195. arelle/model/CommentBase.py +0 -9
  196. arelle/model/ElementBase.py +0 -11
  197. arelle/model/PIBase.py +0 -10
  198. arelle/model/__init__.py +0 -15
  199. arelle/scripts-macOS/startWebServer.command +0 -3
  200. arelle/scripts-unix/startWebServer.sh +0 -1
  201. arelle/scripts-windows/startWebServer.bat +0 -5
  202. {arelle_release-2.37.46.dist-info → arelle_release-2.38.0.dist-info}/WHEEL +0 -0
  203. {arelle_release-2.37.46.dist-info → arelle_release-2.38.0.dist-info}/entry_points.txt +0 -0
  204. {arelle_release-2.37.46.dist-info → arelle_release-2.38.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,58 @@
1
+ """
2
+ See COPYRIGHT.md for copyright information.
3
+ """
4
+ from __future__ import annotations
5
+
6
+ from dataclasses import dataclass
7
+ from typing import Literal
8
+
9
+
10
+ @dataclass(frozen=True)
11
+ class ContextRequirement:
12
+ """
13
+ If {elementExists} is set, and {elementDoesNotExist} is not set,
14
+ any context with ID starting with {contextId} must have {element} value
15
+ matching the {elementMatch} DEI value (adjusted by {dayAdjustment} days).
16
+ """
17
+ contextId: str
18
+ element: Literal['endDate', 'instant', 'startDate']
19
+ elementExists: str | None
20
+ elementDoesNotExist: str | None
21
+ elementMatch: str
22
+ dayAdjustment: int = 0 # days to adjust by (e.g. -1 for "day before")
23
+
24
+
25
+ # See section 3-4-2 in the Validation Guidelines.
26
+ CONTEXT_REQUIREMENTS = [
27
+ ContextRequirement('CurrentYearInstant', 'instant', None, None, 'CurrentFiscalYearEndDateDEI'),
28
+ ContextRequirement('Prior1YearInstant', 'instant', 'EndDateOfQuarterlyOrSemiAnnualPeriodOfNextFiscalYearDEI', None, 'CurrentFiscalYearEndDateDEI'),
29
+ ContextRequirement('Prior1YearInstant', 'instant', None, 'EndDateOfQuarterlyOrSemiAnnualPeriodOfNextFiscalYearDEI', 'PreviousFiscalYearEndDateDEI'),
30
+ ContextRequirement('Prior2YearInstant', 'instant', 'EndDateOfQuarterlyOrSemiAnnualPeriodOfNextFiscalYearDEI', None, 'PreviousFiscalYearEndDateDEI'),
31
+ ContextRequirement('Prior2YearInstant', 'instant', None, 'EndDateOfQuarterlyOrSemiAnnualPeriodOfNextFiscalYearDEI', 'PreviousFiscalYearStartDateDEI', -1),
32
+ ContextRequirement('CurrentQuarterInstant', 'instant', 'EndDateOfQuarterlyOrSemiAnnualPeriodOfNextFiscalYearDEI', None, 'EndDateOfQuarterlyOrSemiAnnualPeriodOfNextFiscalYearDEI'),
33
+ ContextRequirement('CurrentQuarterInstant', 'instant', None, 'EndDateOfQuarterlyOrSemiAnnualPeriodOfNextFiscalYearDEI', 'CurrentPeriodEndDateDEI'),
34
+ ContextRequirement('Prior1QuarterInstant', 'instant', None, 'EndDateOfQuarterlyOrSemiAnnualPeriodOfNextFiscalYearDEI', 'ComparativePeriodEndDateDEI'),
35
+ ContextRequirement('InterimInstant', 'instant', 'EndDateOfQuarterlyOrSemiAnnualPeriodOfNextFiscalYearDEI', None, 'EndDateOfQuarterlyOrSemiAnnualPeriodOfNextFiscalYearDEI'),
36
+ ContextRequirement('InterimInstant', 'instant', None, 'EndDateOfQuarterlyOrSemiAnnualPeriodOfNextFiscalYearDEI', 'CurrentPeriodEndDateDEI'),
37
+ ContextRequirement('Prior1InterimInstant', 'instant', None, 'EndDateOfQuarterlyOrSemiAnnualPeriodOfNextFiscalYearDEI', 'ComparativePeriodEndDateDEI'),
38
+ ContextRequirement('CurrentYearDuration', 'startDate', None, None, 'CurrentFiscalYearStartDateDEI'),
39
+ ContextRequirement('Prior1YearDuration', 'startDate', 'NextFiscalYearStartDateDEI', None, 'CurrentFiscalYearStartDateDEI'),
40
+ ContextRequirement('Prior1YearDuration', 'startDate', None, 'NextFiscalYearStartDateDEI', 'PreviousFiscalYearStartDateDEI'),
41
+ ContextRequirement('Prior2YearDuration', 'startDate', 'NextFiscalYearStartDateDEI', None, 'PreviousFiscalYearStartDateDEI'),
42
+ ContextRequirement('CurrentYTDDuration', 'startDate', 'NextFiscalYearStartDateDEI', None, 'NextFiscalYearStartDateDEI'),
43
+ ContextRequirement('CurrentYTDDuration', 'startDate', None, 'NextFiscalYearStartDateDEI', 'CurrentFiscalYearStartDateDEI'),
44
+ ContextRequirement('Prior1YTDDuration', 'startDate', None, 'NextFiscalYearStartDateDEI', 'PreviousFiscalYearStartDateDEI'),
45
+ ContextRequirement('InterimDuration', 'startDate', 'NextFiscalYearStartDateDEI', None, 'NextFiscalYearStartDateDEI'),
46
+ ContextRequirement('InterimDuration', 'startDate', None, 'NextFiscalYearStartDateDEI', 'CurrentFiscalYearStartDateDEI'),
47
+ ContextRequirement('Prior1InterimDuration', 'startDate', None, 'NextFiscalYearStartDateDEI', 'PreviousFiscalYearStartDateDEI'),
48
+ ContextRequirement('CurrentYearDuration', 'endDate', None, None, 'CurrentFiscalYearEndDateDEI'),
49
+ ContextRequirement('Prior1YearDuration', 'endDate', 'EndDateOfQuarterlyOrSemiAnnualPeriodOfNextFiscalYearDEI', None, 'CurrentFiscalYearEndDateDEI'),
50
+ ContextRequirement('Prior1YearDuration', 'endDate', None, 'EndDateOfQuarterlyOrSemiAnnualPeriodOfNextFiscalYearDEI', 'PreviousFiscalYearEndDateDEI'),
51
+ ContextRequirement('Prior2YearDuration', 'endDate', 'EndDateOfQuarterlyOrSemiAnnualPeriodOfNextFiscalYearDEI', None, 'PreviousFiscalYearEndDateDEI'),
52
+ ContextRequirement('CurrentYTDDuration', 'endDate', 'EndDateOfQuarterlyOrSemiAnnualPeriodOfNextFiscalYearDEI', None, 'EndDateOfQuarterlyOrSemiAnnualPeriodOfNextFiscalYearDEI'),
53
+ ContextRequirement('CurrentYTDDuration', 'endDate', None, 'EndDateOfQuarterlyOrSemiAnnualPeriodOfNextFiscalYearDEI', 'CurrentPeriodEndDateDEI'),
54
+ ContextRequirement('Prior1YTDDuration', 'endDate', None, 'EndDateOfQuarterlyOrSemiAnnualPeriodOfNextFiscalYearDEI', 'ComparativePeriodEndDateDEI'),
55
+ ContextRequirement('InterimDuration', 'endDate', 'EndDateOfQuarterlyOrSemiAnnualPeriodOfNextFiscalYearDEI', None, 'EndDateOfQuarterlyOrSemiAnnualPeriodOfNextFiscalYearDEI'),
56
+ ContextRequirement('InterimDuration', 'endDate', None, 'EndDateOfQuarterlyOrSemiAnnualPeriodOfNextFiscalYearDEI', 'CurrentPeriodEndDateDEI'),
57
+ ContextRequirement('Prior1InterimDuration', 'endDate', None, 'EndDateOfQuarterlyOrSemiAnnualPeriodOfNextFiscalYearDEI', 'ComparativePeriodEndDateDEI'),
58
+ ]
@@ -3,12 +3,28 @@ See COPYRIGHT.md for copyright information.
3
3
  """
4
4
  from __future__ import annotations
5
5
 
6
+ import zipfile
7
+ from collections import defaultdict
6
8
  from dataclasses import dataclass
9
+ from functools import lru_cache
10
+ from pathlib import Path
7
11
  from typing import TYPE_CHECKING
8
12
 
9
13
  from arelle.Cntlr import Cntlr
14
+ from arelle.FileSource import FileSource
15
+ from arelle.ModelValue import QName, TypeXValue
16
+ from arelle.ModelXbrl import ModelXbrl
17
+ from arelle.XmlValidateConst import VALID
10
18
  from arelle.typing import TypeGetText
11
19
  from arelle.utils.PluginData import PluginData
20
+ from . import Constants
21
+ from .CoverItemRequirements import CoverItemRequirements
22
+ from .DeiRequirements import DeiRequirements, DEI_LOCAL_NAMES
23
+ from .FilingFormat import DocumentType, FilingFormat
24
+ from .NamespaceConfig import NamespaceConfig
25
+ from .ReportFolderType import ReportFolderType
26
+ from .TableOfContentsBuilder import TableOfContentsBuilder
27
+ from .UploadContents import UploadContents, UploadPathInfo
12
28
 
13
29
  if TYPE_CHECKING:
14
30
  from .ManifestInstance import ManifestInstance
@@ -18,11 +34,22 @@ _: TypeGetText
18
34
 
19
35
  @dataclass
20
36
  class ControllerPluginData(PluginData):
37
+ _deiValues: dict[str, TypeXValue]
38
+ _loadedModelXbrls: list[ModelXbrl]
21
39
  _manifestInstancesById: dict[str, ManifestInstance]
40
+ _tocBuilder: TableOfContentsBuilder
41
+ _uploadContents: UploadContents | None
42
+ _usedFilepaths: set[Path]
22
43
 
23
44
  def __init__(self, name: str):
24
45
  super().__init__(name)
46
+ self.namespaces = NamespaceConfig()
47
+ self._deiValues = {}
48
+ self._loadedModelXbrls = []
25
49
  self._manifestInstancesById = {}
50
+ self._tocBuilder = TableOfContentsBuilder()
51
+ self._usedFilepaths = set()
52
+ self._uploadContents = None
26
53
 
27
54
  def __hash__(self) -> int:
28
55
  return id(self)
@@ -33,19 +60,173 @@ class ControllerPluginData(PluginData):
33
60
  """
34
61
  self._manifestInstancesById[manifestInstance.id] = manifestInstance
35
62
 
63
+ def addModelXbrl(self, modelXbrl: ModelXbrl) -> None:
64
+ self._loadedModelXbrls.append(modelXbrl)
65
+ self.setDeiValues(modelXbrl)
66
+
67
+ @lru_cache(1)
68
+ def getCoverItemRequirements(self, jsonPath: Path) -> CoverItemRequirements:
69
+ return CoverItemRequirements(jsonPath)
70
+
71
+ @lru_cache(1)
72
+ def getDeiRequirements(self, csvPath: Path, deiItems: tuple[QName, ...], filingFormats: tuple[FilingFormat, ...]) -> DeiRequirements:
73
+ return DeiRequirements(csvPath, deiItems, filingFormats)
74
+
75
+ def getDeiValue(self, localName: str) -> TypeXValue:
76
+ return self._deiValues.get(localName)
77
+
36
78
  def getManifestInstances(self) -> list[ManifestInstance]:
37
79
  """
38
80
  Retrieve all loaded manifest instances.
39
81
  """
40
82
  return list(self._manifestInstancesById.values())
41
83
 
42
- def matchManifestInstance(self, ixdsDocUrls: list[str]) -> ManifestInstance | None:
84
+ def getTableOfContentsBuilder(self) -> TableOfContentsBuilder:
85
+ return self._tocBuilder
86
+
87
+ def getUploadContents(self) -> UploadContents | None:
88
+ return self._uploadContents
89
+
90
+ def setUploadContents(self, fileSource: FileSource) -> UploadContents:
91
+ reports = defaultdict(list)
92
+ uploadPaths = {}
93
+ for path, zipPath in self.getUploadFilepaths(fileSource).items():
94
+ if len(path.parts) == 0:
95
+ continue
96
+ assert isinstance(fileSource.basefile, str)
97
+ fullPath = Path(fileSource.basefile) / path
98
+ parents = list(reversed([p.name for p in path.parents if len(p.name) > 0]))
99
+ reportFolderType = None
100
+ isCorrection = True
101
+ isDirectory = zipPath.is_dir()
102
+ isInSubdirectory = False
103
+ reportPath = None
104
+ if len(parents) > 0:
105
+ isCorrection = parents[0] != 'XBRL'
106
+ if not isCorrection:
107
+ if len(parents) > 1:
108
+ formName = parents[1]
109
+ isInSubdirectory = len(parents) > 2
110
+ reportFolderType = ReportFolderType.parse(formName)
111
+ if reportFolderType is None:
112
+ formName = parents[0]
113
+ isInSubdirectory = len(parents) > 1
114
+ reportFolderType = ReportFolderType.parse(formName)
115
+ if reportFolderType is not None:
116
+ reportPath = Path(reportFolderType.value) if isCorrection else Path("XBRL") / reportFolderType.value
117
+ if not isCorrection:
118
+ reports[reportFolderType].append(path)
119
+ uploadPaths[path] = UploadPathInfo(
120
+ fullPath=fullPath,
121
+ isAttachment=reportFolderType is not None and reportFolderType.isAttachment,
122
+ isCorrection=isCorrection,
123
+ isCoverPage=not isDirectory and path.stem.startswith(Constants.COVER_PAGE_FILENAME_PREFIX),
124
+ isDirectory=isDirectory,
125
+ isRoot=len(path.parts) == 1,
126
+ isSubdirectory=isInSubdirectory or (isDirectory and reportFolderType is not None),
127
+ path=path,
128
+ reportFolderType=reportFolderType,
129
+ reportPath=reportPath,
130
+ )
131
+ self._uploadContents = UploadContents(
132
+ reports={k: frozenset(v) for k, v in reports.items() if len(v) > 0},
133
+ uploadPaths=list(uploadPaths.values())
134
+ )
135
+ return self._uploadContents
136
+
137
+ @lru_cache(1)
138
+ def getUploadFilepaths(self, fileSource: FileSource) -> dict[Path, zipfile.Path]:
139
+ if not self.isUpload(fileSource):
140
+ return {}
141
+ paths = {}
142
+ assert isinstance(fileSource.fs, zipfile.ZipFile)
143
+ # First, fill in paths from zip file list
144
+ for file in fileSource.fs.filelist:
145
+ zipPath = zipfile.Path(fileSource.fs, file.filename)
146
+ paths[Path(file.filename)] = zipPath
147
+ # Then, fill in any parent directories that weren't in file list
148
+ for path in list(paths):
149
+ for parent in path.parents:
150
+ if parent in paths:
151
+ continue
152
+ paths[parent] = zipfile.Path(fileSource.fs, parent.as_posix() + '/')
153
+ return {
154
+ path: paths[path]
155
+ for path in sorted(paths)
156
+ }
157
+
158
+ @lru_cache(1)
159
+ def getUploadFileSizes(self, fileSource: FileSource) -> dict[Path, int]:
160
+ """
161
+ Get the sizes of files in the upload directory.
162
+ :param fileSource: The FileSource instance to get file sizes for.
163
+ :return: A dictionary mapping file paths to their sizes.
164
+ """
165
+ if not self.isUpload(fileSource):
166
+ return {}
167
+ assert isinstance(fileSource.fs, zipfile.ZipFile)
168
+ return {
169
+ Path(i.filename): i.file_size
170
+ for i in fileSource.fs.infolist()
171
+ if not i.is_dir()
172
+ }
173
+
174
+ def getUsedFilepaths(self) -> frozenset[Path]:
175
+ return frozenset(self._usedFilepaths)
176
+
177
+ def isConsolidated(self) -> bool | None:
178
+ """
179
+ Is this a consolidated (not individual) filing?
180
+ Looks for the DEI fact 'WhetherConsolidatedFinancialStatementsArePreparedDEI'
181
+ within PublicDoc instances. If an explicit True/False value is found, it is returned.
182
+ If no non-nil value exists, None is returned, which indicates a not applicable state.
183
+ :return:
184
+ """
185
+ for modelXbrl in self.loadedModelXbrls:
186
+ manifestInstance = self.getManifestInstance(modelXbrl)
187
+ if manifestInstance is None:
188
+ continue
189
+ if manifestInstance.type != ReportFolderType.PUBLIC_DOC.value:
190
+ continue
191
+ facts = modelXbrl.factsByLocalName.get('WhetherConsolidatedFinancialStatementsArePreparedDEI', set())
192
+ for fact in facts:
193
+ if fact.xValue == True:
194
+ return True
195
+ if fact.xValue == False:
196
+ return False
197
+ return None
198
+
199
+ @lru_cache(1)
200
+ def isUpload(self, fileSource: FileSource) -> bool:
201
+ fileSource.open() # Make sure file source is open
202
+ if (fileSource.fs is None or
203
+ not isinstance(fileSource.fs, zipfile.ZipFile)):
204
+ if fileSource.cntlr is not None:
205
+ fileSource.cntlr.addToLog(
206
+ _("The target file is not a zip file, so upload validation was not performed."),
207
+ messageCode="EDINET.uploadNotValidated",
208
+ file=str(fileSource.url)
209
+ )
210
+ return False
211
+ return True
212
+
213
+ @property
214
+ def loadedModelXbrls(self) -> list[ModelXbrl]:
215
+ """
216
+ TODO: Only necessary because cntlr.modelManager.loadedModelXbrls is not reliable
217
+ in the current conformance suite runner. Remove when that is fixed/replaced.
218
+ """
219
+ return self._loadedModelXbrls
220
+
221
+ @lru_cache(1)
222
+ def getManifestInstance(self, modelXbrl: ModelXbrl) -> ManifestInstance | None:
43
223
  """
44
224
  Match a manifest instance based on the provided ixdsDocUrls.
45
225
  A one-to-one mapping must exist between the model's IXDS document URLs and the manifest instance's IXBRL files.
46
226
  :param ixdsDocUrls: A model's list of IXDS document URLs.
47
227
  :return: A matching ManifestInstance if found, otherwise None.
48
228
  """
229
+ ixdsDocUrls = modelXbrl.ixdsDocUrls
49
230
  modelUrls = set(ixdsDocUrls)
50
231
  matchedInstance = None
51
232
  for instance in self._manifestInstancesById.values():
@@ -70,6 +251,44 @@ class ControllerPluginData(PluginData):
70
251
  break
71
252
  return matchedInstance
72
253
 
254
+ def setDeiValue(self, localName: str, value: TypeXValue) -> None:
255
+ if localName in self._deiValues:
256
+ # Duplicate DEI values will be caught by validations.
257
+ return
258
+ self._deiValues[localName] = value
259
+
260
+ def setDeiValues(self, modelXbrl: ModelXbrl) -> None:
261
+ """
262
+ Set DEI values from the provided modelXbrl.
263
+ Some EDINET validations rely on both DEI values defined in one instance
264
+ and other values in a separate instance, so we collect DEI values from
265
+ all instances and collect them at a controller level.
266
+ :param modelXbrl:
267
+ :return:
268
+ """
269
+ for localName in DEI_LOCAL_NAMES:
270
+ for fact in modelXbrl.factsByLocalName.get(localName, ()):
271
+ if fact.isNil or fact.xValid < VALID:
272
+ continue
273
+ self.setDeiValue(localName, fact.xValue)
274
+
275
+ def addUsedFilepath(self, path: Path) -> None:
276
+ self._usedFilepaths.add(path)
277
+
278
+ def hasDocumentType(self, documentTypes: set[DocumentType]) -> bool:
279
+ """
280
+ Check if any of the loaded instances are one of the specfied document types.
281
+ """
282
+ for modelXbrl in self.loadedModelXbrls:
283
+ manifestInstance = self.getManifestInstance(modelXbrl)
284
+ if manifestInstance is None:
285
+ continue
286
+ if manifestInstance.filingFormat is None:
287
+ continue
288
+ if manifestInstance.filingFormat.documentType in documentTypes:
289
+ return True
290
+ return False
291
+
73
292
  @staticmethod
74
293
  def get(cntlr: Cntlr, name: str) -> ControllerPluginData:
75
294
  controllerPluginData = cntlr.getPluginData(name)
@@ -0,0 +1,42 @@
1
+ """
2
+ See COPYRIGHT.md for copyright information.
3
+ """
4
+ from __future__ import annotations
5
+
6
+ import json
7
+ from pathlib import Path
8
+
9
+
10
+ # Cover item requirements do not appear to be specifically documented anywhere,
11
+ # so we have inferred them from the documentation and sample filings available to us:
12
+ # Cover item requirements loaded by starting with "Taxonomy Element List" (ESE140114.xlsx),
13
+ # which appears to give a mapping of ELR URIs to required cover items (Concepts with
14
+ # "*CoverPage" local names). However, the sequence of the cover items (validated by EC1004E)
15
+ # does not match the order in samples #10, #11, #21 and #22. In those cases, the sequences
16
+ # have been updated to match the samples.
17
+ # A note at the bottom of "3-4-2 Cover Page" within "File Specification for EDINET Filing"
18
+ # (ESE140104.pdf) indicates that cover pages are either generated within EDINET's submission
19
+ # UI or manually. The manual process involves downloading a template cover page HTML file, editing it,
20
+ # and then re-uploading it. This suggests that there are cover page template files that may be the most
21
+ # reliable source of truth for cover item requirements, but we have not been able to access these templates.
22
+ class CoverItemRequirements:
23
+ _jsonPath: Path
24
+ _data: dict[str, list[str]] | None
25
+
26
+ def __init__(self, jsonPath: Path):
27
+ self._jsonPath = jsonPath
28
+ self._data = None
29
+
30
+ def _load(self) -> dict[str, list[str]]:
31
+ if self._data is None:
32
+ with open(self._jsonPath, encoding='utf-8') as f:
33
+ self._data = json.load(f)
34
+ return self._data
35
+
36
+ def all(self) -> frozenset[str]:
37
+ data = self._load()
38
+ return frozenset(v for values in data.values() for v in values)
39
+
40
+ def get(self, roleUri: str) -> list[str]:
41
+ data = self._load()
42
+ return data.get(roleUri, [])
@@ -0,0 +1,118 @@
1
+ """
2
+ See COPYRIGHT.md for copyright information.
3
+ """
4
+ from __future__ import annotations
5
+
6
+ from enum import Enum
7
+ from pathlib import Path
8
+
9
+ from arelle.ModelValue import QName
10
+ from .FilingFormat import FilingFormat
11
+
12
+
13
+ # DEI requirements parsing is designed so that the contents of Attachment #5
14
+ # in "(Appendix) Taxonomy Extension Guideline" (ESE140111.zip), or future versions,
15
+ # can be easily exported to a CSV file where the rows correspond to DEI concepts
16
+ # and the columns correspond to different formats.
17
+ class DeiRequirements:
18
+ _csvPath: Path
19
+ _data: dict[QName, dict[FilingFormat, DeiItemStatus | None]] | None
20
+ _deiItems: tuple[QName, ...]
21
+ _filingFormats: tuple[FilingFormat, ...]
22
+
23
+ def __init__(self, csvPath: Path, deiItems: tuple[QName, ...], filingFormats: tuple[FilingFormat, ...]):
24
+ self._csvPath = csvPath
25
+ self._data = None
26
+ self._deiItems = deiItems
27
+ self._filingFormats = filingFormats
28
+
29
+ def _load(self) -> dict[QName, dict[FilingFormat, DeiItemStatus | None]]:
30
+ if self._data is None:
31
+ with open(self._csvPath, encoding='utf-8') as f:
32
+ data = [
33
+ [
34
+ DeiItemStatus.parse(cell) for cell in line.strip().split(',')
35
+ ]
36
+ for line in f.readlines()
37
+ ]
38
+ self._data = {}
39
+ assert len(data) == len(self._deiItems), \
40
+ "Unexpected number of rows in DEI requirements CSV."
41
+ for rowIndex, row in enumerate(data):
42
+ assert len(row) == len(self._filingFormats), \
43
+ f"Unexpected number of columns in DEI requirements CSV at row {rowIndex}."
44
+ deiItem = self._deiItems[rowIndex]
45
+ self._data[deiItem] = {}
46
+ for colIndex, cell in enumerate(row):
47
+ filingFormat = self._filingFormats[colIndex]
48
+ self._data[deiItem][filingFormat] = cell
49
+ return self._data
50
+
51
+
52
+ def get(self, deiItem: QName, filingFormat: FilingFormat) -> DeiItemStatus | None:
53
+ data = self._load()
54
+ if deiItem not in data:
55
+ return None
56
+ if filingFormat not in data[deiItem]:
57
+ return None
58
+ return data[deiItem][filingFormat]
59
+
60
+
61
+ class DeiItemStatus(Enum):
62
+ # The values of the enum correspond to the symbols used in the spreadsheet.
63
+ PROHIBITED = '×'
64
+ OPTIONAL = '△'
65
+ CONDITIONAL = '○'
66
+ REQUIRED = '◎'
67
+
68
+ @classmethod
69
+ def parse(cls, value: str) -> DeiItemStatus | None:
70
+ try:
71
+ return cls(value)
72
+ except ValueError:
73
+ return None
74
+
75
+ # The below values are based on Attachment #5 in "(Appendix) Taxonomy Extension Guideline" (ESE140111.zip).
76
+ # Column D lists the DEI concepts. Rows purely for grouping are omitted.
77
+ # The order is preserved. The index is used to map to other data structures.
78
+ DEI_LOCAL_NAMES = (
79
+ # Submitter Information (提出者情報)
80
+ 'EDINETCodeDEI',
81
+ 'FundCodeDEI',
82
+ 'SecurityCodeDEI',
83
+ 'FilerNameInJapaneseDEI',
84
+ 'FilerNameInEnglishDEI',
85
+ 'FundNameInJapaneseDEI',
86
+ 'FundNameInEnglishDEI',
87
+
88
+ # Document Submission Information (提出書類情報)
89
+ 'CabinetOfficeOrdinanceDEI',
90
+ 'DocumentTypeDEI',
91
+ 'AccountingStandardsDEI',
92
+ 'WhetherConsolidatedFinancialStatementsArePreparedDEI',
93
+ 'IndustryCodeWhenConsolidatedFinancialStatementsArePreparedInAccordanceWithIndustrySpecificRegulationsDEI',
94
+ 'IndustryCodeWhenFinancialStatementsArePreparedInAccordanceWithIndustrySpecificRegulationsDEI',
95
+
96
+ # Current Fiscal Year (当会計期間)
97
+ 'CurrentFiscalYearStartDateDEI',
98
+ 'CurrentPeriodEndDateDEI',
99
+ 'TypeOfCurrentPeriodDEI',
100
+ 'CurrentFiscalYearEndDateDEI',
101
+
102
+ # Previous Fiscal Year (比較対象会計期間)
103
+ 'PreviousFiscalYearStartDateDEI',
104
+ 'ComparativePeriodEndDateDEI',
105
+ 'PreviousFiscalYearEndDateDEI',
106
+
107
+ # Next Fiscal Year (次の中間期の会計期間)
108
+ 'NextFiscalYearStartDateDEI',
109
+ 'EndDateOfQuarterlyOrSemiAnnualPeriodOfNextFiscalYearDEI',
110
+
111
+ 'NumberOfSubmissionDEI',
112
+ 'AmendmentFlagDEI',
113
+ 'IdentificationOfDocumentSubjectToAmendmentDEI',
114
+
115
+ # Type of Correction (訂正の種類)
116
+ 'ReportAmendmentFlagDEI',
117
+ 'XBRLAmendmentFlagDEI',
118
+ )