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.
- arelle/BetaFeatures.py +0 -21
- arelle/Cntlr.py +15 -8
- arelle/CntlrCmdLine.py +121 -56
- arelle/CntlrWinMain.py +143 -70
- arelle/DialogFind.py +1 -1
- arelle/DialogPluginManager.py +6 -4
- arelle/DisclosureSystem.py +7 -0
- arelle/ErrorManager.py +21 -6
- arelle/FileSource.py +11 -4
- arelle/FunctionIxt.py +16 -11
- arelle/HtmlUtil.py +5 -4
- arelle/LeiUtil.py +63 -43
- arelle/ModelDocument.py +20 -15
- arelle/ModelDtsObject.py +8 -0
- arelle/ModelInstanceObject.py +1 -1
- arelle/ModelObject.py +16 -18
- arelle/ModelObjectFactory.py +35 -17
- arelle/ModelXbrl.py +28 -11
- arelle/PluginManager.py +130 -105
- arelle/RuntimeOptions.py +1 -0
- arelle/UrlUtil.py +14 -0
- arelle/Validate.py +17 -12
- arelle/ValidateDuplicateFacts.py +3 -1
- arelle/ValidateFileSource.py +38 -0
- arelle/ValidateFilingText.py +3 -3
- arelle/ValidateXbrl.py +5 -2
- arelle/ValidateXbrlCalcs.py +210 -186
- arelle/ValidateXbrlDTS.py +1 -1
- arelle/ViewFile.py +1 -0
- arelle/ViewFileFactTable.py +2 -2
- arelle/ViewWinDTS.py +4 -1
- arelle/WebCache.py +28 -24
- arelle/XbrlConst.py +22 -0
- arelle/XmlUtil.py +16 -21
- arelle/XmlValidate.py +6 -9
- arelle/_version.py +16 -3
- arelle/api/Session.py +11 -2
- arelle/config/disclosuresystems.xsd +2 -0
- arelle/config/rosettaEntitlements.plist +8 -0
- arelle/conformance/CSVTestcaseLoader.py +1 -1
- arelle/formula/XPathContext.py +3 -3
- arelle/logging/formatters/LogFormatter.py +3 -1
- arelle/packages/report/ReportPackage.py +26 -13
- arelle/packages/report/ReportPackageConst.py +0 -1
- arelle/plugin/inlineXbrlDocumentSet.py +19 -5
- arelle/plugin/validate/DBA/DisclosureSystems.py +19 -1
- arelle/plugin/validate/DBA/PluginValidationDataExtension.py +2 -4
- arelle/plugin/validate/DBA/ValidationPluginExtension.py +2 -1
- arelle/plugin/validate/DBA/resources/config.xml +5 -0
- arelle/plugin/validate/DBA/rules/__init__.py +2 -2
- arelle/plugin/validate/DBA/rules/fr.py +19 -2
- arelle/plugin/validate/DBA/rules/tc.py +2 -0
- arelle/plugin/validate/DBA/rules/th.py +6 -0
- arelle/plugin/validate/DBA/rules/tm.py +18 -5
- arelle/plugin/validate/DBA/rules/tr.py +11 -5
- arelle/plugin/validate/EDINET/Constants.py +193 -9
- arelle/plugin/validate/EDINET/ContextRequirement.py +58 -0
- arelle/plugin/validate/EDINET/ControllerPluginData.py +220 -1
- arelle/plugin/validate/EDINET/CoverItemRequirements.py +42 -0
- arelle/plugin/validate/EDINET/DeiRequirements.py +118 -0
- arelle/plugin/validate/EDINET/FilingFormat.py +275 -0
- arelle/plugin/validate/EDINET/FormType.py +134 -0
- arelle/plugin/validate/EDINET/ManifestInstance.py +72 -5
- arelle/plugin/validate/EDINET/NamespaceConfig.py +50 -0
- arelle/plugin/validate/EDINET/PluginValidationDataExtension.py +493 -132
- arelle/plugin/validate/EDINET/{InstanceType.py → ReportFolderType.py} +72 -15
- arelle/plugin/validate/EDINET/Statement.py +139 -0
- arelle/plugin/validate/EDINET/TableOfContentsBuilder.py +595 -0
- arelle/plugin/validate/EDINET/UploadContents.py +48 -0
- arelle/plugin/validate/EDINET/ValidationPluginExtension.py +20 -2
- arelle/plugin/validate/EDINET/__init__.py +31 -6
- arelle/plugin/validate/EDINET/resources/config.xml +8 -1
- arelle/plugin/validate/EDINET/resources/cover-item-requirements.json +793 -0
- arelle/plugin/validate/EDINET/resources/dei-requirements.csv +27 -0
- arelle/plugin/validate/EDINET/resources/edinet-taxonomies.xml +2 -0
- arelle/plugin/validate/EDINET/rules/contexts.py +375 -14
- arelle/plugin/validate/EDINET/rules/edinet.py +1934 -45
- arelle/plugin/validate/EDINET/rules/frta.py +122 -3
- arelle/plugin/validate/EDINET/rules/gfm.py +1907 -11
- arelle/plugin/validate/EDINET/rules/upload.py +989 -141
- arelle/plugin/validate/ESEF/Const.py +3 -1
- arelle/plugin/validate/ESEF/ESEF_2021/DTS.py +5 -0
- arelle/plugin/validate/ESEF/ESEF_2021/Image.py +2 -2
- arelle/plugin/validate/ESEF/ESEF_2021/ValidateXbrlFinally.py +23 -20
- arelle/plugin/validate/ESEF/ESEF_Current/DTS.py +47 -14
- arelle/plugin/validate/ESEF/ESEF_Current/ValidateXbrlFinally.py +100 -25
- arelle/plugin/validate/ESEF/__init__.py +20 -6
- arelle/plugin/validate/ESEF/resources/authority-validations.json +76 -9
- arelle/plugin/validate/ESEF/resources/config.xml +20 -0
- arelle/plugin/validate/NL/DisclosureSystems.py +22 -0
- arelle/plugin/validate/NL/PluginValidationDataExtension.py +27 -9
- arelle/plugin/validate/NL/ValidationPluginExtension.py +51 -7
- arelle/plugin/validate/NL/resources/config.xml +18 -0
- arelle/plugin/validate/NL/rules/br_kvk.py +17 -61
- arelle/plugin/validate/NL/rules/fg_nl.py +7 -38
- arelle/plugin/validate/NL/rules/fr_kvk.py +7 -42
- arelle/plugin/validate/NL/rules/fr_nl.py +31 -147
- arelle/plugin/validate/NL/rules/nl_kvk.py +142 -28
- arelle/plugin/validate/ROS/PluginValidationDataExtension.py +2 -0
- arelle/plugin/validate/ROS/ValidationPluginExtension.py +4 -1
- arelle/plugin/validate/ROS/rules/ros.py +41 -9
- arelle/plugin/validate/UK/ValidateUK.py +130 -66
- arelle/plugin/validate/UK/__init__.py +89 -103
- arelle/utils/EntryPointDetection.py +79 -13
- arelle/utils/PluginHooks.py +125 -0
- arelle/utils/validate/ESEFImage.py +6 -6
- arelle/utils/validate/Validation.py +18 -0
- arelle/utils/validate/ValidationPlugin.py +76 -11
- arelle/utils/validate/ValidationUtil.py +35 -3
- {arelle_release-2.37.46.dist-info → arelle_release-2.38.0.dist-info}/METADATA +30 -20
- {arelle_release-2.37.46.dist-info → arelle_release-2.38.0.dist-info}/RECORD +115 -191
- {arelle_release-2.37.46.dist-info → arelle_release-2.38.0.dist-info}/licenses/LICENSE.md +0 -3
- arelle/archive/CustomLogger.py +0 -43
- arelle/archive/LoadEFMvalidate.py +0 -32
- arelle/archive/LoadSavePreLbCsv.py +0 -26
- arelle/archive/LoadValidate.cs +0 -31
- arelle/archive/LoadValidate.py +0 -36
- arelle/archive/LoadValidateCmdLine.java +0 -69
- arelle/archive/LoadValidatePostedZip.java +0 -57
- arelle/archive/LoadValidateWebService.java +0 -34
- arelle/archive/SaveTableToExelle.py +0 -140
- arelle/archive/TR3toTR4.py +0 -88
- arelle/archive/plugin/ESEF_2022/__init__.py +0 -47
- arelle/archive/plugin/bigInstance.py +0 -394
- arelle/archive/plugin/cmdWebServerExtension.py +0 -43
- arelle/archive/plugin/crashTest.py +0 -38
- arelle/archive/plugin/functionsXmlCreation.py +0 -106
- arelle/archive/plugin/hello_i18n.pot +0 -26
- arelle/archive/plugin/hello_i18n.py +0 -32
- arelle/archive/plugin/importTestChild1.py +0 -21
- arelle/archive/plugin/importTestChild2.py +0 -22
- arelle/archive/plugin/importTestGrandchild1.py +0 -21
- arelle/archive/plugin/importTestGrandchild2.py +0 -21
- arelle/archive/plugin/importTestImported1.py +0 -23
- arelle/archive/plugin/importTestImported11.py +0 -22
- arelle/archive/plugin/importTestParent.py +0 -48
- arelle/archive/plugin/instanceInfo.py +0 -306
- arelle/archive/plugin/loadFromOIM-2018.py +0 -1282
- arelle/archive/plugin/locale/fr/LC_MESSAGES/hello_i18n.po +0 -25
- arelle/archive/plugin/objectmaker.py +0 -285
- arelle/archive/plugin/packagedImportTest/__init__.py +0 -47
- arelle/archive/plugin/packagedImportTest/importTestChild1.py +0 -21
- arelle/archive/plugin/packagedImportTest/importTestChild2.py +0 -22
- arelle/archive/plugin/packagedImportTest/importTestGrandchild1.py +0 -21
- arelle/archive/plugin/packagedImportTest/importTestGrandchild2.py +0 -21
- arelle/archive/plugin/packagedImportTest/importTestImported1.py +0 -24
- arelle/archive/plugin/packagedImportTest/importTestImported11.py +0 -21
- arelle/archive/plugin/packagedImportTest/subdir/importTestImported111.py +0 -21
- arelle/archive/plugin/packagedImportTest/subdir/subsubdir/importTestImported1111.py +0 -21
- arelle/archive/plugin/sakaCalendar.py +0 -215
- arelle/archive/plugin/saveInstanceInfoset.py +0 -121
- arelle/archive/plugin/sphinx/FormulaGenerator.py +0 -823
- arelle/archive/plugin/sphinx/SphinxContext.py +0 -404
- arelle/archive/plugin/sphinx/SphinxEvaluator.py +0 -783
- arelle/archive/plugin/sphinx/SphinxMethods.py +0 -1287
- arelle/archive/plugin/sphinx/SphinxParser.py +0 -1093
- arelle/archive/plugin/sphinx/SphinxValidator.py +0 -163
- arelle/archive/plugin/sphinx/US-GAAP Ratios Example.xsr +0 -52
- arelle/archive/plugin/sphinx/__init__.py +0 -285
- arelle/archive/plugin/streamingExtensions.py +0 -335
- arelle/archive/plugin/updateTableLB.py +0 -242
- arelle/archive/plugin/validate/SBRnl/CustomLoader.py +0 -19
- arelle/archive/plugin/validate/SBRnl/DTS.py +0 -305
- arelle/archive/plugin/validate/SBRnl/Dimensions.py +0 -357
- arelle/archive/plugin/validate/SBRnl/Document.py +0 -799
- arelle/archive/plugin/validate/SBRnl/Filing.py +0 -467
- arelle/archive/plugin/validate/SBRnl/__init__.py +0 -75
- arelle/archive/plugin/validate/SBRnl/config.xml +0 -26
- arelle/archive/plugin/validate/SBRnl/sbr-nl-taxonomies.xml +0 -754
- arelle/archive/plugin/validate/USBestPractices.py +0 -570
- arelle/archive/plugin/validate/USCorpAction.py +0 -557
- arelle/archive/plugin/validate/USSecTagging.py +0 -337
- arelle/archive/plugin/validate/XDC/__init__.py +0 -77
- arelle/archive/plugin/validate/XDC/config.xml +0 -20
- arelle/archive/plugin/validate/XFsyntax/__init__.py +0 -64
- arelle/archive/plugin/validate/XFsyntax/xf.py +0 -2227
- arelle/archive/plugin/validate/calc2.py +0 -536
- arelle/archive/plugin/validateSchemaLxml.py +0 -156
- arelle/archive/plugin/validateTableInfoset.py +0 -52
- arelle/archive/us-gaap-dei-docType-extraction-frm.xml +0 -90
- arelle/archive/us-gaap-dei-ratio-cash-frm.xml +0 -150
- arelle/examples/plugin/formulaSuiteConverter.py +0 -212
- arelle/examples/plugin/functionsCustom.py +0 -59
- arelle/examples/plugin/hello_dolly.py +0 -64
- arelle/examples/plugin/multi.py +0 -58
- arelle/examples/plugin/rssSaveOim.py +0 -96
- arelle/examples/plugin/validate/XYZ/DisclosureSystems.py +0 -2
- arelle/examples/plugin/validate/XYZ/PluginValidationDataExtension.py +0 -10
- arelle/examples/plugin/validate/XYZ/ValidationPluginExtension.py +0 -49
- arelle/examples/plugin/validate/XYZ/__init__.py +0 -75
- arelle/examples/plugin/validate/XYZ/resources/config.xml +0 -16
- arelle/examples/plugin/validate/XYZ/rules/__init__.py +0 -0
- arelle/examples/plugin/validate/XYZ/rules/rules01.py +0 -110
- arelle/examples/plugin/validate/XYZ/rules/rules02.py +0 -59
- arelle/model/CommentBase.py +0 -9
- arelle/model/ElementBase.py +0 -11
- arelle/model/PIBase.py +0 -10
- arelle/model/__init__.py +0 -15
- arelle/scripts-macOS/startWebServer.command +0 -3
- arelle/scripts-unix/startWebServer.sh +0 -1
- arelle/scripts-windows/startWebServer.bat +0 -5
- {arelle_release-2.37.46.dist-info → arelle_release-2.38.0.dist-info}/WHEEL +0 -0
- {arelle_release-2.37.46.dist-info → arelle_release-2.38.0.dist-info}/entry_points.txt +0 -0
- {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
|
|
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
|
+
)
|