arelle-release 2.37.61__py3-none-any.whl → 2.37.63__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of arelle-release might be problematic. Click here for more details.
- arelle/DisclosureSystem.py +5 -0
- arelle/FileSource.py +4 -1
- arelle/HtmlUtil.py +5 -4
- arelle/ValidateDuplicateFacts.py +2 -0
- arelle/ValidateXbrl.py +3 -1
- arelle/ValidateXbrlDTS.py +1 -1
- arelle/XbrlConst.py +18 -0
- arelle/_version.py +2 -2
- arelle/api/Session.py +6 -0
- arelle/config/disclosuresystems.xsd +1 -0
- arelle/plugin/validate/EDINET/Constants.py +95 -0
- arelle/plugin/validate/EDINET/ControllerPluginData.py +14 -3
- arelle/plugin/validate/EDINET/CoverItemRequirements.py +42 -0
- arelle/plugin/validate/EDINET/{CoverPageRequirements.py → DeiRequirements.py} +24 -24
- arelle/plugin/validate/EDINET/PluginValidationDataExtension.py +209 -43
- arelle/plugin/validate/EDINET/ReportFolderType.py +61 -0
- arelle/plugin/validate/EDINET/TableOfContentsBuilder.py +493 -0
- arelle/plugin/validate/EDINET/__init__.py +13 -2
- arelle/plugin/validate/EDINET/resources/config.xml +6 -0
- arelle/plugin/validate/EDINET/resources/cover-item-requirements.json +793 -0
- arelle/plugin/validate/EDINET/resources/edinet-taxonomies.xml +2 -0
- arelle/plugin/validate/EDINET/rules/contexts.py +61 -1
- arelle/plugin/validate/EDINET/rules/edinet.py +278 -4
- arelle/plugin/validate/EDINET/rules/frta.py +122 -3
- arelle/plugin/validate/EDINET/rules/gfm.py +681 -5
- arelle/plugin/validate/EDINET/rules/upload.py +231 -192
- arelle/plugin/validate/NL/PluginValidationDataExtension.py +6 -8
- arelle/plugin/validate/NL/ValidationPluginExtension.py +0 -3
- arelle/plugin/validate/NL/rules/nl_kvk.py +1 -2
- arelle/utils/validate/ValidationPlugin.py +1 -1
- arelle/utils/validate/ValidationUtil.py +1 -2
- {arelle_release-2.37.61.dist-info → arelle_release-2.37.63.dist-info}/METADATA +2 -1
- {arelle_release-2.37.61.dist-info → arelle_release-2.37.63.dist-info}/RECORD +38 -35
- /arelle/plugin/validate/EDINET/resources/{cover-page-requirements.csv → dei-requirements.csv} +0 -0
- {arelle_release-2.37.61.dist-info → arelle_release-2.37.63.dist-info}/WHEEL +0 -0
- {arelle_release-2.37.61.dist-info → arelle_release-2.37.63.dist-info}/entry_points.txt +0 -0
- {arelle_release-2.37.61.dist-info → arelle_release-2.37.63.dist-info}/licenses/LICENSE.md +0 -0
- {arelle_release-2.37.61.dist-info → arelle_release-2.37.63.dist-info}/top_level.txt +0 -0
arelle/DisclosureSystem.py
CHANGED
|
@@ -62,6 +62,7 @@ class DisclosureSystem:
|
|
|
62
62
|
self.validateEntryText = False
|
|
63
63
|
self.allowedExternalHrefPattern = None
|
|
64
64
|
self.allowedImageTypes = None
|
|
65
|
+
self.arcroleCyclesAllowed = {}
|
|
65
66
|
self.schemaValidateSchema = None
|
|
66
67
|
self.blockDisallowedReferences = False
|
|
67
68
|
self.maxSubmissionSubdirectoryEntryNesting = 0
|
|
@@ -182,6 +183,10 @@ class DisclosureSystem:
|
|
|
182
183
|
self.allowedExternalHrefPattern = re.compile(dsElt.get("allowedExternalHrefPattern"))
|
|
183
184
|
if dsElt.get("allowedImageTypes"):
|
|
184
185
|
self.allowedImageTypes = json.loads(dsElt.get("allowedImageTypes"))
|
|
186
|
+
if dsElt.get("arcroleCyclesAllowed"):
|
|
187
|
+
self.arcroleCyclesAllowed = json.loads(dsElt.get("arcroleCyclesAllowed"))
|
|
188
|
+
for arcrole, specSet in self.arcroleCyclesAllowed.items():
|
|
189
|
+
self.arcroleCyclesAllowed[arcrole] = tuple(specSet)
|
|
185
190
|
self.blockDisallowedReferences = dsElt.get("blockDisallowedReferences") == "true"
|
|
186
191
|
try:
|
|
187
192
|
self.maxSubmissionSubdirectoryEntryNesting = int(dsElt.get("maxSubmissionSubdirectoryEntryNesting"))
|
arelle/FileSource.py
CHANGED
|
@@ -51,7 +51,10 @@ def openFileSource(
|
|
|
51
51
|
sourceFileSource: FileSource | None = None,
|
|
52
52
|
) -> FileSource:
|
|
53
53
|
if sourceZipStream:
|
|
54
|
-
if
|
|
54
|
+
if name := getattr(sourceZipStream, "name", None):
|
|
55
|
+
# Python IO convention is to use the name attribute
|
|
56
|
+
sourceZipStreamFileName: str = os.sep + str(name)
|
|
57
|
+
elif isinstance(sourceZipStream, FileNamedBytesIO) and sourceZipStream.fileName:
|
|
55
58
|
sourceZipStreamFileName = os.sep + sourceZipStream.fileName
|
|
56
59
|
else:
|
|
57
60
|
sourceZipStreamFileName = os.sep + "POSTupload.zip"
|
arelle/HtmlUtil.py
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
'''
|
|
2
2
|
See COPYRIGHT.md for copyright information.
|
|
3
3
|
'''
|
|
4
|
-
|
|
4
|
+
from __future__ import annotations
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
|
|
7
|
+
def attrValue(attr: str) -> str:
|
|
7
8
|
# retrieves attribute in a string, such as xyz="abc" or xyz='abc' or xyz=abc;
|
|
8
|
-
prestuff, matchedName, valuePart =
|
|
9
|
-
value = []
|
|
9
|
+
prestuff, matchedName, valuePart = attr.lower().partition("charset")
|
|
10
|
+
value: list[str] = []
|
|
10
11
|
endSep = None
|
|
11
12
|
beforeEquals = True
|
|
12
13
|
for c in valuePart:
|
arelle/ValidateDuplicateFacts.py
CHANGED
|
@@ -207,6 +207,8 @@ class DuplicateFactSet:
|
|
|
207
207
|
:return: A subset of the facts where the fact of lower precision in every consistent pair has been removed.
|
|
208
208
|
"""
|
|
209
209
|
facts = self.deduplicateCompleteSubsets()
|
|
210
|
+
if len(facts) < 2:
|
|
211
|
+
return facts
|
|
210
212
|
if not self.areNumeric:
|
|
211
213
|
# Consistency is equivalent to completeness for non-numeric facts
|
|
212
214
|
return facts
|
arelle/ValidateXbrl.py
CHANGED
|
@@ -147,6 +147,8 @@ class ValidateXbrl:
|
|
|
147
147
|
if arcrole.startswith("XBRL-") or ELR is None or \
|
|
148
148
|
linkqname is None or arcqname is None:
|
|
149
149
|
continue
|
|
150
|
+
elif arcrole in self.modelXbrl.modelManager.disclosureSystem.arcroleCyclesAllowed:
|
|
151
|
+
cyclesAllowed, specSect = self.modelXbrl.modelManager.disclosureSystem.arcroleCyclesAllowed[arcrole]
|
|
150
152
|
elif arcrole in XbrlConst.standardArcroleCyclesAllowed:
|
|
151
153
|
# TODO: table should be in this module, where it is used
|
|
152
154
|
cyclesAllowed, specSect = XbrlConst.standardArcroleCyclesAllowed[arcrole]
|
|
@@ -163,7 +165,7 @@ class ValidateXbrl:
|
|
|
163
165
|
or arcrole in self.genericArcArcroles \
|
|
164
166
|
or arcrole.startswith(XbrlConst.formulaStartsWith) \
|
|
165
167
|
or (modelXbrl.hasXDT and arcrole.startswith(XbrlConst.dimStartsWith)):
|
|
166
|
-
relsSet = modelXbrl.relationshipSet(arcrole,ELR,linkqname,arcqname)
|
|
168
|
+
relsSet = modelXbrl.relationshipSet(arcrole, ELR, linkqname, arcqname)
|
|
167
169
|
if cyclesAllowed != "any" and \
|
|
168
170
|
((XbrlConst.isStandardExtLinkQname(linkqname) and XbrlConst.isStandardArcQname(arcqname)) \
|
|
169
171
|
or arcrole in self.genericArcArcroles):
|
arelle/ValidateXbrlDTS.py
CHANGED
|
@@ -372,7 +372,7 @@ def checkElements(val, modelDocument, parent):
|
|
|
372
372
|
if isinstance(parent,ModelObject): # element
|
|
373
373
|
if (parent.localName == "meta" and parent.namespaceURI == XbrlConst.xhtml and
|
|
374
374
|
(parent.get("http-equiv") or "").lower() == "content-type"):
|
|
375
|
-
val.metaContentTypeEncoding = HtmlUtil.attrValue(parent.get("content")
|
|
375
|
+
val.metaContentTypeEncoding = HtmlUtil.attrValue(parent.get("content"))
|
|
376
376
|
elif isinstance(parent,etree._ElementTree): # documentNode
|
|
377
377
|
val.documentTypeEncoding = modelDocument.documentEncoding # parent.docinfo.encoding
|
|
378
378
|
val.metaContentTypeEncoding = ""
|
arelle/XbrlConst.py
CHANGED
|
@@ -27,6 +27,8 @@ qnXsdImport = qname("{http://www.w3.org/2001/XMLSchema}xsd:import")
|
|
|
27
27
|
qnXsdSchema = qname("{http://www.w3.org/2001/XMLSchema}xsd:schema")
|
|
28
28
|
qnXsdAppinfo = qname("{http://www.w3.org/2001/XMLSchema}xsd:appinfo")
|
|
29
29
|
qnXsdDefaultType = qname("{http://www.w3.org/2001/XMLSchema}xsd:anyType")
|
|
30
|
+
qnXsdElement = qname("{http://www.w3.org/2001/XMLSchema}xsd:element")
|
|
31
|
+
qnXsdAttribute = qname("{http://www.w3.org/2001/XMLSchema}xsd:attribute")
|
|
30
32
|
xsi = "http://www.w3.org/2001/XMLSchema-instance"
|
|
31
33
|
qnXsiNil = qname(xsi, "xsi:nil") # need default prefix in qname
|
|
32
34
|
qnXsiType = qname(xsi, "xsi:type")
|
|
@@ -128,6 +130,7 @@ qnXlinkArcRole = qname("{http://www.w3.org/1999/xlink}xlink:arcrole")
|
|
|
128
130
|
qnXlinkFrom = qname("{http://www.w3.org/1999/xlink}xlink:from")
|
|
129
131
|
qnXlinkHref = qname("{http://www.w3.org/1999/xlink}xlink:href")
|
|
130
132
|
qnXlinkLabel = qname("{http://www.w3.org/1999/xlink}xlink:label")
|
|
133
|
+
qnXlinkRole = qname("{http://www.w3.org/1999/xlink}xlink:role")
|
|
131
134
|
qnXlinkTo = qname("{http://www.w3.org/1999/xlink}xlink:to")
|
|
132
135
|
qnXlinkType = qname("{http://www.w3.org/1999/xlink}xlink:type")
|
|
133
136
|
xl = "http://www.xbrl.org/2003/XLink"
|
|
@@ -180,6 +183,21 @@ ixAttributes = set(
|
|
|
180
183
|
"tupleID",
|
|
181
184
|
)
|
|
182
185
|
)
|
|
186
|
+
ixbrlTargetElements = frozenset({
|
|
187
|
+
qnIXbrlFraction,
|
|
188
|
+
qnIXbrlNonFraction,
|
|
189
|
+
qnIXbrlNonNumeric,
|
|
190
|
+
qnIXbrlResources,
|
|
191
|
+
qnIXbrlTuple,
|
|
192
|
+
})
|
|
193
|
+
ixbrl11TargetElements = frozenset({
|
|
194
|
+
qnIXbrl11Fraction,
|
|
195
|
+
qnIXbrl11NonFraction,
|
|
196
|
+
qnIXbrl11NonNumeric,
|
|
197
|
+
qnIXbrl11Resources,
|
|
198
|
+
qnIXbrl11Tuple,
|
|
199
|
+
})
|
|
200
|
+
ixbrlAllTargetElements = ixbrlTargetElements | ixbrl11TargetElements
|
|
183
201
|
conceptLabel = "http://www.xbrl.org/2003/arcrole/concept-label"
|
|
184
202
|
conceptReference = "http://www.xbrl.org/2003/arcrole/concept-reference"
|
|
185
203
|
footnote = "http://www.xbrl.org/2003/role/footnote"
|
arelle/_version.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '2.37.
|
|
32
|
-
__version_tuple__ = version_tuple = (2, 37,
|
|
31
|
+
__version__ = version = '2.37.63'
|
|
32
|
+
__version_tuple__ = version_tuple = (2, 37, 63)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
arelle/api/Session.py
CHANGED
|
@@ -113,6 +113,12 @@ class Session:
|
|
|
113
113
|
) -> bool:
|
|
114
114
|
"""
|
|
115
115
|
Perform a run using the given options.
|
|
116
|
+
When using a sourceZipStream, the name attribute on it will be checked
|
|
117
|
+
and, if present, assumed to be the effective name of the zip file the
|
|
118
|
+
stream represents. When validating '.xbri' files, via a sourceZipStream,
|
|
119
|
+
it is important to set this name attribute to avoid
|
|
120
|
+
rpe:documentTypeFileExtensionMismatch errors.
|
|
121
|
+
|
|
116
122
|
:param options: Options to use for the run.
|
|
117
123
|
:param sourceZipStream: Optional stream to read source data from.
|
|
118
124
|
:param responseZipStream: Options stream to write response data to.
|
|
@@ -3,6 +3,8 @@ See COPYRIGHT.md for copyright information.
|
|
|
3
3
|
"""
|
|
4
4
|
from enum import Enum
|
|
5
5
|
|
|
6
|
+
import regex
|
|
7
|
+
|
|
6
8
|
from arelle.ModelValue import qname
|
|
7
9
|
|
|
8
10
|
class AccountingStandard(Enum):
|
|
@@ -79,3 +81,96 @@ NUMERIC_LABEL_ROLES = frozenset({
|
|
|
79
81
|
'http://www.xbrl.org/2009/role/negatedNetLabel',
|
|
80
82
|
'http://www.xbrl.org/2009/role/negatedTerseLabel',
|
|
81
83
|
})
|
|
84
|
+
|
|
85
|
+
PATTERN_CODE = r'(?P<code>[A-Za-z\d]*)'
|
|
86
|
+
PATTERN_CONSOLIDATED = r'(?P<consolidated>c|n)'
|
|
87
|
+
PATTERN_COUNT = r'(?P<count>\d{2})'
|
|
88
|
+
PATTERN_DATE1 = r'(?P<year1>\d{4})-(?P<month1>\d{2})-(?P<day1>\d{2})'
|
|
89
|
+
PATTERN_DATE2 = r'(?P<year2>\d{4})-(?P<month2>\d{2})-(?P<day2>\d{2})'
|
|
90
|
+
PATTERN_FORM = r'(?P<form>\d{6})'
|
|
91
|
+
PATTERN_LINKBASE = r'(?P<linkbase>lab|lab-en|gla|pre|def|cal)'
|
|
92
|
+
PATTERN_MAIN = r'(?P<main>\d{7})'
|
|
93
|
+
PATTERN_NAME = r'(?P<name>[a-z]{6})'
|
|
94
|
+
PATTERN_ORDINANCE = r'(?P<ordinance>[a-z]*)'
|
|
95
|
+
PATTERN_PERIOD = r'(?P<period>c|p)' # TODO: Have only seen "c" in sample/public filings, assuming "p" for previous.
|
|
96
|
+
PATTERN_REPORT = r'(?P<report>[a-z0-9]*)'
|
|
97
|
+
PATTERN_REPORT_SERIAL = r'(?P<report_serial>\d{3})'
|
|
98
|
+
PATTERN_SERIAL = r'(?P<serial>\d{3})'
|
|
99
|
+
|
|
100
|
+
PATTERN_AUDIT_REPORT_PREFIX = rf'jpaud-{PATTERN_REPORT}-{PATTERN_PERIOD}{PATTERN_CONSOLIDATED}'
|
|
101
|
+
PATTERN_REPORT_PREFIX = rf'jp{PATTERN_ORDINANCE}{PATTERN_FORM}-{PATTERN_REPORT}'
|
|
102
|
+
PATTERN_SUFFIX = rf'{PATTERN_REPORT_SERIAL}_{PATTERN_CODE}-{PATTERN_SERIAL}_{PATTERN_DATE1}_{PATTERN_COUNT}_{PATTERN_DATE2}'
|
|
103
|
+
|
|
104
|
+
PATTERN_URI_HOST = r'http:\/\/disclosure\.edinet-fsa\.go\.jp'
|
|
105
|
+
PATTERN_AUDIT_URI_PREFIX = rf'jpaud\/{PATTERN_REPORT}\/{PATTERN_PERIOD}{PATTERN_CONSOLIDATED}'
|
|
106
|
+
PATTERN_REPORT_URI_PREFIX = rf'jp{PATTERN_ORDINANCE}{PATTERN_FORM}\/{PATTERN_REPORT}'
|
|
107
|
+
PATTERN_URI_SUFFIX = rf'{PATTERN_REPORT_SERIAL}\/{PATTERN_CODE}-{PATTERN_SERIAL}\/{PATTERN_DATE1}\/{PATTERN_COUNT}\/{PATTERN_DATE2}'
|
|
108
|
+
|
|
109
|
+
# Extension namespace URI for report
|
|
110
|
+
# Example: http://disclosure.edinet-fsa.go.jp/jpcrp030000/asr/001/X99002-000/2025-03-31/01/2025-06-28
|
|
111
|
+
REPORT_NAMESPACE_URI_PATTERN = regex.compile(rf'{PATTERN_URI_HOST}\/{PATTERN_REPORT_URI_PREFIX}\/{PATTERN_URI_SUFFIX}')
|
|
112
|
+
|
|
113
|
+
# Extension namespace URI for audit report
|
|
114
|
+
# Example: http://disclosure.edinet-fsa.go.jp/jpaud/aar/cn/001/X99002-000/2025-03-31/01/2025-06-28
|
|
115
|
+
AUDIT_NAMESPACE_URI_PATTERN = regex.compile(rf'{PATTERN_URI_HOST}\/{PATTERN_AUDIT_URI_PREFIX}\/{PATTERN_URI_SUFFIX}')
|
|
116
|
+
|
|
117
|
+
# Extension namespace prefix for report
|
|
118
|
+
# Example: jpcrp040300-ssr_X99005-000
|
|
119
|
+
REPORT_PREFIX_PATTERN = regex.compile(rf'{PATTERN_REPORT_PREFIX}_{PATTERN_CODE}-{PATTERN_SERIAL}')
|
|
120
|
+
|
|
121
|
+
# Extension namespace prefix for audit report
|
|
122
|
+
# Example: jpaud-qrr-cn_X99005-000
|
|
123
|
+
AUDIT_PREFIX_PATTERN = regex.compile(rf'{PATTERN_AUDIT_REPORT_PREFIX}_{PATTERN_CODE}-{PATTERN_SERIAL}')
|
|
124
|
+
|
|
125
|
+
# Instance file for report
|
|
126
|
+
# Example: jpcrp050300-esr-001_X99007-000_2025-04-10_01_2025-04-10.xsd
|
|
127
|
+
REPORT_XBRL_FILENAME_PATTERN = regex.compile(rf'{PATTERN_REPORT_PREFIX}-{PATTERN_SUFFIX}\.xbrl')
|
|
128
|
+
|
|
129
|
+
# Instance file for audit report
|
|
130
|
+
# Example: jpaud-aar-cn-001_X99001-000_2025-03-31_01_2025-06-28.xsd
|
|
131
|
+
AUDIT_XBRL_FILENAME_PATTERN = regex.compile(rf'{PATTERN_AUDIT_REPORT_PREFIX}-{PATTERN_SUFFIX}\.xbrl')
|
|
132
|
+
|
|
133
|
+
# Schema file for report
|
|
134
|
+
# Example: jpcrp050300-esr-001_X99007-000_2025-04-10_01_2025-04-10.xsd
|
|
135
|
+
REPORT_SCHEMA_FILENAME_PATTERN = regex.compile(rf'{PATTERN_REPORT_PREFIX}-{PATTERN_SUFFIX}\.xsd')
|
|
136
|
+
|
|
137
|
+
# Schema file for audit report
|
|
138
|
+
# Example: jpaud-aar-cn-001_X99001-000_2025-03-31_01_2025-06-28.xsd
|
|
139
|
+
AUDIT_SCHEMA_FILENAME_PATTERN = regex.compile(rf'{PATTERN_AUDIT_REPORT_PREFIX}-{PATTERN_SUFFIX}\.xsd')
|
|
140
|
+
|
|
141
|
+
# Linkbase file for report
|
|
142
|
+
# Example: jpcrp020000-srs-001_X99001-000_2025-03-31_01_2025-11-20_cal.xml
|
|
143
|
+
REPORT_LINKBASE_FILENAME_PATTERN = regex.compile(rf'{PATTERN_REPORT_PREFIX}-{PATTERN_SUFFIX}_{PATTERN_LINKBASE}\.xml')
|
|
144
|
+
|
|
145
|
+
# Linkbase file for audit report
|
|
146
|
+
# Example: jpaud-qrr-cc-001_X99001-000_2025-03-31_01_2025-11-20_pre.xml
|
|
147
|
+
AUDIT_LINKBASE_FILENAME_PATTERN = regex.compile(rf'{PATTERN_AUDIT_REPORT_PREFIX}-{PATTERN_SUFFIX}_{PATTERN_LINKBASE}\.xml')
|
|
148
|
+
|
|
149
|
+
# Cover page file for report
|
|
150
|
+
# Example: 0000000_header_jpcrp020000-srs-001_X99001-000_2025-03-31_01_2025-11-20_ixbrl.htm
|
|
151
|
+
REPORT_COVER_FILENAME_PATTERN = regex.compile(rf'{COVER_PAGE_FILENAME_PREFIX}{PATTERN_REPORT_PREFIX}-{PATTERN_SUFFIX}_ixbrl\.htm')
|
|
152
|
+
|
|
153
|
+
# Main file for report
|
|
154
|
+
# Example: 0205020_honbun_jpcrp020000-srs-001_X99001-000_2025-03-31_01_2025-11-20_ixbrl.htm
|
|
155
|
+
REPORT_IXBRL_FILENAME_PATTERN = regex.compile(rf'{PATTERN_MAIN}_{PATTERN_NAME}_{PATTERN_REPORT_PREFIX}-{PATTERN_SUFFIX}_ixbrl\.htm')
|
|
156
|
+
|
|
157
|
+
# Main file for audit report
|
|
158
|
+
# Example: jpaud-qrr-cc-001_X99001-000_2025-03-31_01_2025-11-20_pre.xml
|
|
159
|
+
AUDIT_IXBRL_FILENAME_PATTERN = regex.compile(rf'{PATTERN_AUDIT_REPORT_PREFIX}-{PATTERN_SUFFIX}_ixbrl\.htm')
|
|
160
|
+
|
|
161
|
+
PATTERN_CONTEXT_RELATIVE_PERIOD = r'(?P<relative_period>Prior[1-9]Year|CurrentYear|Prior[1-9]Interim|Interim)'
|
|
162
|
+
PATTERN_CONTEXT_PERIOD = r'(?P<relative_period>Prior[1-9]Year|CurrentYear|Prior[1-9]Interim|Interim|FilingDate|RecordDate)'
|
|
163
|
+
PATTERN_CONTEXT_DURATION = r'(?P<duration>Duration|Instant)'
|
|
164
|
+
PATTERN_CONTEXT_MEMBERS = r'(?P<context_members>(?:_[a-zA-Z][a-zA-Z0-9-]+)+)*'
|
|
165
|
+
PATTERN_CONTEXT_NUMBER = r'(?P<context_number>[0-9]{3})'
|
|
166
|
+
|
|
167
|
+
# All context IDs
|
|
168
|
+
# Example: Prior2YearDuration_jpcrp020000-srs_X99001-000IndustrialMachineryReportableSegmentMember
|
|
169
|
+
CONTEXT_ID_PATTERN = regex.compile(rf'{PATTERN_CONTEXT_PERIOD}{PATTERN_CONTEXT_DURATION}{PATTERN_CONTEXT_MEMBERS}(?:_{PATTERN_CONTEXT_NUMBER})?')
|
|
170
|
+
|
|
171
|
+
# Context IDs for facts associated with Financial Statements
|
|
172
|
+
# Example: Prior2YearDuration
|
|
173
|
+
FINANCIAL_STATEMENT_CONTEXT_ID_PATTERN = regex.compile(rf'^{PATTERN_CONTEXT_RELATIVE_PERIOD}{PATTERN_CONTEXT_DURATION}.*')
|
|
174
|
+
|
|
175
|
+
# Accepted language codes for Japan
|
|
176
|
+
JAPAN_LANGUAGE_CODES = frozenset({'ja', 'jp', 'ja-jp', 'JA', 'JP', 'JA-JP'})
|
|
@@ -16,9 +16,11 @@ from arelle.ModelValue import QName
|
|
|
16
16
|
from arelle.typing import TypeGetText
|
|
17
17
|
from arelle.utils.PluginData import PluginData
|
|
18
18
|
from . import Constants
|
|
19
|
-
from .
|
|
19
|
+
from .CoverItemRequirements import CoverItemRequirements
|
|
20
|
+
from .DeiRequirements import DeiRequirements
|
|
20
21
|
from .FilingFormat import FilingFormat
|
|
21
22
|
from .ReportFolderType import ReportFolderType
|
|
23
|
+
from .TableOfContentsBuilder import TableOfContentsBuilder
|
|
22
24
|
from .UploadContents import UploadContents, UploadPathInfo
|
|
23
25
|
|
|
24
26
|
if TYPE_CHECKING:
|
|
@@ -30,12 +32,14 @@ _: TypeGetText
|
|
|
30
32
|
@dataclass
|
|
31
33
|
class ControllerPluginData(PluginData):
|
|
32
34
|
_manifestInstancesById: dict[str, ManifestInstance]
|
|
35
|
+
_tocBuilder: TableOfContentsBuilder
|
|
33
36
|
_uploadContents: UploadContents | None
|
|
34
37
|
_usedFilepaths: set[Path]
|
|
35
38
|
|
|
36
39
|
def __init__(self, name: str):
|
|
37
40
|
super().__init__(name)
|
|
38
41
|
self._manifestInstancesById = {}
|
|
42
|
+
self._tocBuilder = TableOfContentsBuilder()
|
|
39
43
|
self._usedFilepaths = set()
|
|
40
44
|
self._uploadContents = None
|
|
41
45
|
|
|
@@ -49,8 +53,12 @@ class ControllerPluginData(PluginData):
|
|
|
49
53
|
self._manifestInstancesById[manifestInstance.id] = manifestInstance
|
|
50
54
|
|
|
51
55
|
@lru_cache(1)
|
|
52
|
-
def
|
|
53
|
-
return
|
|
56
|
+
def getCoverItemRequirements(self, jsonPath: Path) -> CoverItemRequirements:
|
|
57
|
+
return CoverItemRequirements(jsonPath)
|
|
58
|
+
|
|
59
|
+
@lru_cache(1)
|
|
60
|
+
def getDeiRequirements(self, csvPath: Path, deiItems: tuple[QName, ...], filingFormats: tuple[FilingFormat, ...]) -> DeiRequirements:
|
|
61
|
+
return DeiRequirements(csvPath, deiItems, filingFormats)
|
|
54
62
|
|
|
55
63
|
def getManifestInstances(self) -> list[ManifestInstance]:
|
|
56
64
|
"""
|
|
@@ -58,6 +66,9 @@ class ControllerPluginData(PluginData):
|
|
|
58
66
|
"""
|
|
59
67
|
return list(self._manifestInstancesById.values())
|
|
60
68
|
|
|
69
|
+
def getTableOfContentsBuilder(self) -> TableOfContentsBuilder:
|
|
70
|
+
return self._tocBuilder
|
|
71
|
+
|
|
61
72
|
def getUploadContents(self) -> UploadContents | None:
|
|
62
73
|
return self._uploadContents
|
|
63
74
|
|
|
@@ -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, [])
|
|
@@ -10,55 +10,55 @@ from arelle.ModelValue import QName
|
|
|
10
10
|
from .FilingFormat import FilingFormat
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
#
|
|
13
|
+
# DEI requirements parsing is designed so that the contents of Attachment #5
|
|
14
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
|
|
16
|
-
#
|
|
17
|
-
class
|
|
18
|
-
_coverPageItems: tuple[QName, ...]
|
|
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:
|
|
19
18
|
_csvPath: Path
|
|
20
|
-
_data: dict[QName, dict[FilingFormat,
|
|
19
|
+
_data: dict[QName, dict[FilingFormat, DeiItemStatus | None]] | None
|
|
20
|
+
_deiItems: tuple[QName, ...]
|
|
21
21
|
_filingFormats: tuple[FilingFormat, ...]
|
|
22
22
|
|
|
23
|
-
def __init__(self, csvPath: Path,
|
|
24
|
-
self._coverPageItems = coverPageItems
|
|
23
|
+
def __init__(self, csvPath: Path, deiItems: tuple[QName, ...], filingFormats: tuple[FilingFormat, ...]):
|
|
25
24
|
self._csvPath = csvPath
|
|
26
25
|
self._data = None
|
|
26
|
+
self._deiItems = deiItems
|
|
27
27
|
self._filingFormats = filingFormats
|
|
28
28
|
|
|
29
|
-
def _load(self) -> dict[QName, dict[FilingFormat,
|
|
29
|
+
def _load(self) -> dict[QName, dict[FilingFormat, DeiItemStatus | None]]:
|
|
30
30
|
if self._data is None:
|
|
31
31
|
with open(self._csvPath, encoding='utf-8') as f:
|
|
32
32
|
data = [
|
|
33
33
|
[
|
|
34
|
-
|
|
34
|
+
DeiItemStatus.parse(cell) for cell in line.strip().split(',')
|
|
35
35
|
]
|
|
36
36
|
for line in f.readlines()
|
|
37
37
|
]
|
|
38
38
|
self._data = {}
|
|
39
|
-
assert len(data) == len(self.
|
|
40
|
-
"Unexpected number of rows in
|
|
39
|
+
assert len(data) == len(self._deiItems), \
|
|
40
|
+
"Unexpected number of rows in DEI requirements CSV."
|
|
41
41
|
for rowIndex, row in enumerate(data):
|
|
42
42
|
assert len(row) == len(self._filingFormats), \
|
|
43
|
-
f"Unexpected number of columns in
|
|
44
|
-
|
|
45
|
-
self._data[
|
|
43
|
+
f"Unexpected number of columns in DEI requirements CSV at row {rowIndex}."
|
|
44
|
+
deiItem = self._deiItems[rowIndex]
|
|
45
|
+
self._data[deiItem] = {}
|
|
46
46
|
for colIndex, cell in enumerate(row):
|
|
47
47
|
filingFormat = self._filingFormats[colIndex]
|
|
48
|
-
self._data[
|
|
48
|
+
self._data[deiItem][filingFormat] = cell
|
|
49
49
|
return self._data
|
|
50
50
|
|
|
51
51
|
|
|
52
|
-
def get(self,
|
|
52
|
+
def get(self, deiItem: QName, filingFormat: FilingFormat) -> DeiItemStatus | None:
|
|
53
53
|
data = self._load()
|
|
54
|
-
if
|
|
54
|
+
if deiItem not in data:
|
|
55
55
|
return None
|
|
56
|
-
if filingFormat not in data[
|
|
56
|
+
if filingFormat not in data[deiItem]:
|
|
57
57
|
return None
|
|
58
|
-
return data[
|
|
58
|
+
return data[deiItem][filingFormat]
|
|
59
59
|
|
|
60
60
|
|
|
61
|
-
class
|
|
61
|
+
class DeiItemStatus(Enum):
|
|
62
62
|
# The values of the enum correspond to the symbols used in the spreadsheet.
|
|
63
63
|
PROHIBITED = '×'
|
|
64
64
|
OPTIONAL = '△'
|
|
@@ -66,16 +66,16 @@ class CoverPageItemStatus(Enum):
|
|
|
66
66
|
REQUIRED = '◎'
|
|
67
67
|
|
|
68
68
|
@classmethod
|
|
69
|
-
def parse(cls, value: str) ->
|
|
69
|
+
def parse(cls, value: str) -> DeiItemStatus | None:
|
|
70
70
|
try:
|
|
71
71
|
return cls(value)
|
|
72
72
|
except ValueError:
|
|
73
73
|
return None
|
|
74
74
|
|
|
75
75
|
# The below values are based on Attachment #5 in "(Appendix) Taxonomy Extension Guideline" (ESE140111.zip).
|
|
76
|
-
# Column D lists the
|
|
76
|
+
# Column D lists the DEI concepts. Rows purely for grouping are omitted.
|
|
77
77
|
# The order is preserved. The index is used to map to other data structures.
|
|
78
|
-
|
|
78
|
+
DEI_LOCAL_NAMES = (
|
|
79
79
|
# Submitter Information (提出者情報)
|
|
80
80
|
'EDINETCodeDEI',
|
|
81
81
|
'FundCodeDEI',
|