arelle-release 2.37.61__py3-none-any.whl → 2.37.62__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.

Files changed (36) hide show
  1. arelle/DisclosureSystem.py +5 -0
  2. arelle/HtmlUtil.py +5 -4
  3. arelle/ValidateDuplicateFacts.py +2 -0
  4. arelle/ValidateXbrl.py +3 -1
  5. arelle/ValidateXbrlDTS.py +1 -1
  6. arelle/XbrlConst.py +18 -0
  7. arelle/_version.py +2 -2
  8. arelle/config/disclosuresystems.xsd +1 -0
  9. arelle/plugin/validate/EDINET/Constants.py +95 -0
  10. arelle/plugin/validate/EDINET/ControllerPluginData.py +14 -3
  11. arelle/plugin/validate/EDINET/CoverItemRequirements.py +42 -0
  12. arelle/plugin/validate/EDINET/{CoverPageRequirements.py → DeiRequirements.py} +24 -24
  13. arelle/plugin/validate/EDINET/PluginValidationDataExtension.py +209 -43
  14. arelle/plugin/validate/EDINET/ReportFolderType.py +61 -0
  15. arelle/plugin/validate/EDINET/TableOfContentsBuilder.py +493 -0
  16. arelle/plugin/validate/EDINET/__init__.py +13 -2
  17. arelle/plugin/validate/EDINET/resources/config.xml +6 -0
  18. arelle/plugin/validate/EDINET/resources/cover-item-requirements.json +793 -0
  19. arelle/plugin/validate/EDINET/resources/edinet-taxonomies.xml +2 -0
  20. arelle/plugin/validate/EDINET/rules/contexts.py +61 -1
  21. arelle/plugin/validate/EDINET/rules/edinet.py +278 -4
  22. arelle/plugin/validate/EDINET/rules/frta.py +122 -3
  23. arelle/plugin/validate/EDINET/rules/gfm.py +681 -5
  24. arelle/plugin/validate/EDINET/rules/upload.py +231 -192
  25. arelle/plugin/validate/NL/PluginValidationDataExtension.py +6 -8
  26. arelle/plugin/validate/NL/ValidationPluginExtension.py +0 -3
  27. arelle/plugin/validate/NL/rules/nl_kvk.py +1 -2
  28. arelle/utils/validate/ValidationPlugin.py +1 -1
  29. arelle/utils/validate/ValidationUtil.py +1 -2
  30. {arelle_release-2.37.61.dist-info → arelle_release-2.37.62.dist-info}/METADATA +2 -1
  31. {arelle_release-2.37.61.dist-info → arelle_release-2.37.62.dist-info}/RECORD +36 -33
  32. /arelle/plugin/validate/EDINET/resources/{cover-page-requirements.csv → dei-requirements.csv} +0 -0
  33. {arelle_release-2.37.61.dist-info → arelle_release-2.37.62.dist-info}/WHEEL +0 -0
  34. {arelle_release-2.37.61.dist-info → arelle_release-2.37.62.dist-info}/entry_points.txt +0 -0
  35. {arelle_release-2.37.61.dist-info → arelle_release-2.37.62.dist-info}/licenses/LICENSE.md +0 -0
  36. {arelle_release-2.37.61.dist-info → arelle_release-2.37.62.dist-info}/top_level.txt +0 -0
@@ -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/HtmlUtil.py CHANGED
@@ -1,12 +1,13 @@
1
1
  '''
2
2
  See COPYRIGHT.md for copyright information.
3
3
  '''
4
- import regex as re
4
+ from __future__ import annotations
5
5
 
6
- def attrValue(str, name):
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 = str.lower().partition("charset")
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:
@@ -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"), "charset")
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.61'
32
- __version_tuple__ = version_tuple = (2, 37, 61)
31
+ __version__ = version = '2.37.62'
32
+ __version_tuple__ = version_tuple = (2, 37, 62)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -74,6 +74,7 @@
74
74
  <xs:attribute name="standardTaxonomyDatabase" />
75
75
  <xs:attribute name="standardTaxonomyUrlPattern" />
76
76
  <xs:attribute name="options" />
77
+ <xs:attribute name="arcroleCyclesAllowed" type="xs:string"/>
77
78
  </xs:complexType>
78
79
  </xs:element>
79
80
  </xs:schema>
@@ -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 .CoverPageRequirements import CoverPageRequirements
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 getCoverPageRequirements(self, csvPath: Path, coverPageItems: tuple[QName, ...], filingFormats: tuple[FilingFormat, ...]) -> CoverPageRequirements:
53
- return CoverPageRequirements(csvPath, coverPageItems, filingFormats)
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
- # Cover page requirements parsing is designed so that the contents of Attachment #5
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 Cover Page items
16
- # andt the columns correspond to different formats.
17
- class CoverPageRequirements:
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, CoverPageItemStatus | None]] | None
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, coverPageItems: tuple[QName, ...], filingFormats: tuple[FilingFormat, ...]):
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, CoverPageItemStatus | None]]:
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
- CoverPageItemStatus.parse(cell) for cell in line.strip().split(',')
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._coverPageItems), \
40
- "Unexpected number of rows in cover page requirements CSV."
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 cover page requirements CSV at row {rowIndex}."
44
- coverPageItem = self._coverPageItems[rowIndex]
45
- self._data[coverPageItem] = {}
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[coverPageItem][filingFormat] = cell
48
+ self._data[deiItem][filingFormat] = cell
49
49
  return self._data
50
50
 
51
51
 
52
- def get(self, coverPageItem: QName, filingFormat: FilingFormat) -> CoverPageItemStatus | None:
52
+ def get(self, deiItem: QName, filingFormat: FilingFormat) -> DeiItemStatus | None:
53
53
  data = self._load()
54
- if coverPageItem not in data:
54
+ if deiItem not in data:
55
55
  return None
56
- if filingFormat not in data[coverPageItem]:
56
+ if filingFormat not in data[deiItem]:
57
57
  return None
58
- return data[coverPageItem][filingFormat]
58
+ return data[deiItem][filingFormat]
59
59
 
60
60
 
61
- class CoverPageItemStatus(Enum):
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) -> CoverPageItemStatus | None:
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 elements of the cover page. Rows purely for grouping are omitted.
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
- COVER_PAGE_ITEM_LOCAL_NAMES = (
78
+ DEI_LOCAL_NAMES = (
79
79
  # Submitter Information (提出者情報)
80
80
  'EDINETCodeDEI',
81
81
  'FundCodeDEI',