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.
- arelle/DisclosureSystem.py +5 -0
- 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/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.62.dist-info}/METADATA +2 -1
- {arelle_release-2.37.61.dist-info → arelle_release-2.37.62.dist-info}/RECORD +36 -33
- /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.62.dist-info}/WHEEL +0 -0
- {arelle_release-2.37.61.dist-info → arelle_release-2.37.62.dist-info}/entry_points.txt +0 -0
- {arelle_release-2.37.61.dist-info → arelle_release-2.37.62.dist-info}/licenses/LICENSE.md +0 -0
- {arelle_release-2.37.61.dist-info → arelle_release-2.37.62.dist-info}/top_level.txt +0 -0
|
@@ -16,8 +16,9 @@ from typing import Callable, Hashable, Iterable, cast
|
|
|
16
16
|
import os
|
|
17
17
|
import regex
|
|
18
18
|
|
|
19
|
+
from arelle import XbrlConst
|
|
19
20
|
from arelle.LinkbaseType import LinkbaseType
|
|
20
|
-
from arelle.ModelDocument import Type as ModelDocumentType, ModelDocument
|
|
21
|
+
from arelle.ModelDocument import Type as ModelDocumentType, ModelDocument, load as modelDocumentLoad
|
|
21
22
|
from arelle.ModelDtsObject import ModelConcept
|
|
22
23
|
from arelle.ModelInstanceObject import ModelFact, ModelUnit, ModelContext, ModelInlineFact
|
|
23
24
|
from arelle.ModelObject import ModelObject
|
|
@@ -32,12 +33,14 @@ from arelle.typing import TypeGetText
|
|
|
32
33
|
from arelle.utils.PluginData import PluginData
|
|
33
34
|
from .Constants import xhtmlDtdExtension, PROHIBITED_HTML_TAGS, PROHIBITED_HTML_ATTRIBUTES
|
|
34
35
|
from .ControllerPluginData import ControllerPluginData
|
|
35
|
-
from .
|
|
36
|
+
from .CoverItemRequirements import CoverItemRequirements
|
|
37
|
+
from .DeiRequirements import DeiRequirements, DEI_LOCAL_NAMES
|
|
36
38
|
from .FilingFormat import FilingFormat, FILING_FORMATS
|
|
37
39
|
from .FormType import FormType
|
|
38
40
|
from .ManifestInstance import ManifestInstance
|
|
41
|
+
from .ReportFolderType import HTML_EXTENSIONS
|
|
39
42
|
from .Statement import Statement, STATEMENTS, BalanceSheet, StatementInstance, StatementType
|
|
40
|
-
from .UploadContents import UploadContents
|
|
43
|
+
from .UploadContents import UploadContents, UploadPathInfo
|
|
41
44
|
|
|
42
45
|
_: TypeGetText
|
|
43
46
|
|
|
@@ -57,6 +60,7 @@ STANDARD_TAXONOMY_URL_PREFIXES = frozenset((
|
|
|
57
60
|
'https://www.w3.org/1999/xlink'
|
|
58
61
|
))
|
|
59
62
|
|
|
63
|
+
|
|
60
64
|
@dataclass(frozen=True)
|
|
61
65
|
class UriReference:
|
|
62
66
|
attributeName: str
|
|
@@ -67,27 +71,41 @@ class UriReference:
|
|
|
67
71
|
|
|
68
72
|
@dataclass
|
|
69
73
|
class PluginValidationDataExtension(PluginData):
|
|
74
|
+
# Namespaces
|
|
75
|
+
jpcrpEsrNamespace: str
|
|
76
|
+
jpcrpNamespace: str
|
|
77
|
+
jpctlNamespace: str
|
|
78
|
+
jpcrpSbrNamespace: str
|
|
79
|
+
jpdeiNamespace: str
|
|
80
|
+
jpigpNamespace: str
|
|
81
|
+
jplvhNamespace: str
|
|
82
|
+
jppfsNamespace: str
|
|
83
|
+
jpspsEsrNamespace: str
|
|
84
|
+
jpspsSbrNamespace: str
|
|
85
|
+
jpspsNamespace: str
|
|
86
|
+
jptoiNamespace: str
|
|
87
|
+
jptooPstNamespace: str
|
|
88
|
+
jptooToaNamespace: str
|
|
89
|
+
jptooTonNamespace: str
|
|
90
|
+
jptooTorNamespace: str
|
|
91
|
+
jptooWtoNamespace: str
|
|
92
|
+
|
|
70
93
|
accountingStandardsDeiQn: QName
|
|
71
94
|
assetsIfrsQn: QName
|
|
72
95
|
consolidatedOrNonConsolidatedAxisQn: QName
|
|
73
96
|
documentTypeDeiQn: QName
|
|
74
97
|
jpcrpEsrFilingDateCoverPageQn: QName
|
|
75
|
-
jpcrpEsrNamespace: str
|
|
76
98
|
jpcrpFilingDateCoverPageQn: QName
|
|
77
|
-
jpcrpNamespace: str
|
|
78
|
-
jpdeiNamespace: str
|
|
79
|
-
jpigpNamespace: str
|
|
80
|
-
jppfsNamespace: str
|
|
81
99
|
jpspsFilingDateCoverPageQn: QName
|
|
82
|
-
jpspsNamespace: str
|
|
83
100
|
nonConsolidatedMemberQn: QName
|
|
84
101
|
ratioOfFemaleDirectorsAndOtherOfficersQn: QName
|
|
85
102
|
|
|
86
|
-
|
|
87
|
-
coverPageItems: tuple[QName, ...]
|
|
88
|
-
coverPageRequirementsPath: Path
|
|
103
|
+
coverItemRequirementsPath: Path
|
|
89
104
|
coverPageTitleQns: tuple[QName, ...]
|
|
105
|
+
deiItems: tuple[QName, ...]
|
|
106
|
+
deiRequirementsPath: Path
|
|
90
107
|
|
|
108
|
+
_namespaceMap: dict[str, str]
|
|
91
109
|
_uriReferences: list[UriReference]
|
|
92
110
|
|
|
93
111
|
def __init__(self, name: str, validateXbrl: ValidateXbrl):
|
|
@@ -96,10 +114,40 @@ class PluginValidationDataExtension(PluginData):
|
|
|
96
114
|
# Namespaces
|
|
97
115
|
self.jpcrpEsrNamespace = "http://disclosure.edinet-fsa.go.jp/taxonomy/jpcrp-esr/2024-11-01/jpcrp-esr_cor"
|
|
98
116
|
self.jpcrpNamespace = 'http://disclosure.edinet-fsa.go.jp/taxonomy/jpcrp/2024-11-01/jpcrp_cor'
|
|
117
|
+
self.jpcrpSbrNamespace = "http://disclosure.edinet-fsa.go.jp/taxonomy/jpcrp-sbr/2024-11-01/jpcrp-sbr_cor"
|
|
118
|
+
self.jpctlNamespace = 'http://disclosure.edinet-fsa.go.jp/taxonomy/jpctl/2024-11-01/jpctl_cor'
|
|
99
119
|
self.jpdeiNamespace = 'http://disclosure.edinet-fsa.go.jp/taxonomy/jpdei/2013-08-31/jpdei_cor'
|
|
100
120
|
self.jpigpNamespace = "http://disclosure.edinet-fsa.go.jp/taxonomy/jpigp/2024-11-01/jpigp_cor"
|
|
121
|
+
self.jplvhNamespace = "http://disclosure.edinet-fsa.go.jp/taxonomy/jplvh/2024-11-01/jplvh_cor"
|
|
101
122
|
self.jppfsNamespace = "http://disclosure.edinet-fsa.go.jp/taxonomy/jppfs/2024-11-01/jppfs_cor"
|
|
123
|
+
self.jpspsEsrNamespace = 'http://disclosure.edinet-fsa.go.jp/taxonomy/jpsps-esr/2024-11-01/jpsps-esr_cor'
|
|
124
|
+
self.jpspsSbrNamespace = 'http://disclosure.edinet-fsa.go.jp/taxonomy/jpsps-sbr/2024-11-01/jpsps-sbr_cor'
|
|
102
125
|
self.jpspsNamespace = 'http://disclosure.edinet-fsa.go.jp/taxonomy/jpsps/2024-11-01/jpsps_cor'
|
|
126
|
+
self.jptoiNamespace = 'http://disclosure.edinet-fsa.go.jp/taxonomy/jptoi/2024-11-01/jptoi_cor'
|
|
127
|
+
self.jptooPstNamespace = 'http://disclosure.edinet-fsa.go.jp/taxonomy/jptoo-pst/2024-11-01/jptoo-pst_cor'
|
|
128
|
+
self.jptooToaNamespace = 'http://disclosure.edinet-fsa.go.jp/taxonomy/jptoo-toa/2024-11-01/jptoo-toa_cor'
|
|
129
|
+
self.jptooTonNamespace = 'http://disclosure.edinet-fsa.go.jp/taxonomy/jptoo-ton/2024-11-01/jptoo-ton_cor'
|
|
130
|
+
self.jptooTorNamespace = 'http://disclosure.edinet-fsa.go.jp/taxonomy/jptoo-tor/2024-11-01/jptoo-tor_cor'
|
|
131
|
+
self.jptooWtoNamespace = 'http://disclosure.edinet-fsa.go.jp/taxonomy/jptoo-wto/2024-11-01/jptoo-wto_cor'
|
|
132
|
+
self._namespaceMap = {
|
|
133
|
+
"jpcrp-esr_cor": self.jpcrpEsrNamespace,
|
|
134
|
+
"jpcrp-sbr_cor": self.jpcrpSbrNamespace,
|
|
135
|
+
"jpcrp_cor": self.jpcrpNamespace,
|
|
136
|
+
"jpctl_cor": self.jpctlNamespace,
|
|
137
|
+
"jpdei_cor": self.jpdeiNamespace,
|
|
138
|
+
"jpigp_cor": self.jpigpNamespace,
|
|
139
|
+
"jplvh_cor": self.jplvhNamespace,
|
|
140
|
+
"jppfs_cor": self.jppfsNamespace,
|
|
141
|
+
"jpsps_cor": self.jpspsNamespace,
|
|
142
|
+
"jpsps-esr_cor": self.jpspsEsrNamespace,
|
|
143
|
+
"jpsps-sbr_cor": self.jpspsSbrNamespace,
|
|
144
|
+
"jptoi_cor": self.jptoiNamespace,
|
|
145
|
+
"jptoo-pst_cor": self.jptooPstNamespace,
|
|
146
|
+
"jptoo-toa_cor": self.jptooToaNamespace,
|
|
147
|
+
"jptoo-ton_cor": self.jptooTonNamespace,
|
|
148
|
+
"jptoo-tor_cor": self.jptooPstNamespace,
|
|
149
|
+
"jptoo-wto_cor": self.jptooWtoNamespace,
|
|
150
|
+
}
|
|
103
151
|
|
|
104
152
|
# QNames
|
|
105
153
|
self.accountingStandardsDeiQn = qname(self.jpdeiNamespace, 'AccountingStandardsDEI')
|
|
@@ -113,18 +161,18 @@ class PluginValidationDataExtension(PluginData):
|
|
|
113
161
|
self.nonConsolidatedMemberQn = qname(self.jppfsNamespace, "NonConsolidatedMember")
|
|
114
162
|
self.ratioOfFemaleDirectorsAndOtherOfficersQn = qname(self.jpcrpNamespace, "RatioOfFemaleDirectorsAndOtherOfficers")
|
|
115
163
|
|
|
116
|
-
self.
|
|
117
|
-
self.coverPageItems = tuple(
|
|
118
|
-
qname(self.jpdeiNamespace, localName)
|
|
119
|
-
for localName in COVER_PAGE_ITEM_LOCAL_NAMES
|
|
120
|
-
)
|
|
121
|
-
self.coverPageRequirementsPath = Path(__file__).parent / "resources" / "cover-page-requirements.csv"
|
|
164
|
+
self.coverItemRequirementsPath = Path(__file__).parent / "resources" / "cover-item-requirements.json"
|
|
122
165
|
self.coverPageTitleQns = (
|
|
123
166
|
qname(self.jpspsNamespace, "DocumentTitleAnnualSecuritiesReportCoverPage"),
|
|
124
167
|
qname(self.jpcrpNamespace, "DocumentTitleCoverPage"),
|
|
125
168
|
qname(self.jpcrpEsrNamespace, "DocumentTitleCoverPage"),
|
|
126
169
|
qname(self.jpspsNamespace, "DocumentTitleCoverPage"),
|
|
127
170
|
)
|
|
171
|
+
self.deiItems = tuple(
|
|
172
|
+
qname(self.jpdeiNamespace, localName)
|
|
173
|
+
for localName in DEI_LOCAL_NAMES
|
|
174
|
+
)
|
|
175
|
+
self.deiRequirementsPath = Path(__file__).parent / "resources" / "dei-requirements.csv"
|
|
128
176
|
|
|
129
177
|
self._uriReferences = []
|
|
130
178
|
self._initialize(validateXbrl.modelXbrl)
|
|
@@ -148,27 +196,62 @@ class PluginValidationDataExtension(PluginData):
|
|
|
148
196
|
contextIsConsolidated = memberValue != self.nonConsolidatedMemberQn
|
|
149
197
|
return bool(statement.isConsolidated == contextIsConsolidated)
|
|
150
198
|
|
|
199
|
+
def _initializeDocument(self, uri: str, modelDocument: ModelDocument, modelXbrl: ModelXbrl) -> None:
|
|
200
|
+
docPath = Path(uri)
|
|
201
|
+
basePath = Path(str(modelXbrl.fileSource.basefile))
|
|
202
|
+
if not docPath.is_relative_to(basePath):
|
|
203
|
+
return
|
|
204
|
+
controllerPluginData = ControllerPluginData.get(modelXbrl.modelManager.cntlr, self.name)
|
|
205
|
+
controllerPluginData.addUsedFilepath(docPath.relative_to(basePath))
|
|
206
|
+
for elt, name, value in self.getUriAttributeValues(modelDocument):
|
|
207
|
+
self._uriReferences.append(UriReference(
|
|
208
|
+
attributeName=name,
|
|
209
|
+
attributeValue=value,
|
|
210
|
+
document=modelDocument,
|
|
211
|
+
element=elt,
|
|
212
|
+
))
|
|
213
|
+
fullPath = (Path(modelDocument.uri).parent / value).resolve()
|
|
214
|
+
if fullPath.is_relative_to(basePath):
|
|
215
|
+
fileSourcePath = fullPath.relative_to(basePath)
|
|
216
|
+
controllerPluginData.addUsedFilepath(fileSourcePath)
|
|
217
|
+
referenceUri = str(fullPath)
|
|
218
|
+
if (
|
|
219
|
+
fullPath.suffix in HTML_EXTENSIONS and
|
|
220
|
+
referenceUri not in modelXbrl.urlDocs and
|
|
221
|
+
modelXbrl.fileSource.exists(referenceUri)
|
|
222
|
+
):
|
|
223
|
+
referenceModelDocument = modelDocumentLoad(
|
|
224
|
+
modelXbrl,
|
|
225
|
+
referenceUri,
|
|
226
|
+
referringElement=elt
|
|
227
|
+
)
|
|
228
|
+
if referenceModelDocument is not None:
|
|
229
|
+
self._initializeDocument(referenceUri, referenceModelDocument, modelXbrl)
|
|
230
|
+
|
|
151
231
|
def _initialize(self, modelXbrl: ModelXbrl) -> None:
|
|
152
232
|
if not isinstance(modelXbrl.fileSource.basefile, str):
|
|
153
233
|
return
|
|
234
|
+
# Additional documents may be loaded, so make a copy to iterate over.
|
|
235
|
+
urlDocs = list(modelXbrl.urlDocs.items())
|
|
236
|
+
for uri, modelDocument in urlDocs:
|
|
237
|
+
self._initializeDocument(uri, modelDocument, modelXbrl)
|
|
238
|
+
|
|
239
|
+
def addToTableOfContents(self, modelXbrl: ModelXbrl) -> None:
|
|
240
|
+
uploadContents = self.getUploadContents(modelXbrl)
|
|
241
|
+
if uploadContents is None:
|
|
242
|
+
return
|
|
154
243
|
controllerPluginData = ControllerPluginData.get(modelXbrl.modelManager.cntlr, self.name)
|
|
155
|
-
|
|
156
|
-
for
|
|
157
|
-
|
|
158
|
-
if
|
|
244
|
+
tocBuilder = controllerPluginData.getTableOfContentsBuilder()
|
|
245
|
+
for modelDocument in modelXbrl.urlDocs.values():
|
|
246
|
+
path = Path(modelDocument.uri)
|
|
247
|
+
if modelDocument.type != ModelDocumentType.INLINEXBRL:
|
|
159
248
|
continue
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
element=elt,
|
|
167
|
-
))
|
|
168
|
-
fullPath = Path(doc.uri).parent / value
|
|
169
|
-
if fullPath.is_relative_to(basePath):
|
|
170
|
-
fileSourcePath = fullPath.relative_to(basePath)
|
|
171
|
-
controllerPluginData.addUsedFilepath(fileSourcePath)
|
|
249
|
+
pathInfo = uploadContents.uploadPathsByFullPath.get(path)
|
|
250
|
+
if pathInfo is not None and not pathInfo.isCoverPage:
|
|
251
|
+
tocBuilder.addDocument(modelDocument)
|
|
252
|
+
|
|
253
|
+
def qname(self, prefix: str, localName: str) -> QName:
|
|
254
|
+
return qname(self._namespaceMap[prefix], localName)
|
|
172
255
|
|
|
173
256
|
@lru_cache(1)
|
|
174
257
|
def isCorporateForm(self, modelXbrl: ModelXbrl) -> bool:
|
|
@@ -202,10 +285,22 @@ class PluginValidationDataExtension(PluginData):
|
|
|
202
285
|
extensionConcepts = []
|
|
203
286
|
for concepts in modelXbrl.nameConcepts.values():
|
|
204
287
|
for concept in concepts:
|
|
205
|
-
if self.isExtensionUri(concept.
|
|
288
|
+
if self.isExtensionUri(concept.document.uri, modelXbrl):
|
|
206
289
|
extensionConcepts.append(concept)
|
|
207
290
|
return extensionConcepts
|
|
208
291
|
|
|
292
|
+
@lru_cache(1)
|
|
293
|
+
def getUsedConcepts(self, modelXbrl: ModelXbrl) -> set[ModelConcept]:
|
|
294
|
+
"""
|
|
295
|
+
Returns a set of concepts used on facts and in explicit dimensions
|
|
296
|
+
"""
|
|
297
|
+
usedConcepts = {fact.concept for fact in modelXbrl.facts if fact.concept is not None}
|
|
298
|
+
for context in modelXbrl.contextsInUse:
|
|
299
|
+
for dim in context.scenDimValues.values():
|
|
300
|
+
if dim.isExplicit:
|
|
301
|
+
usedConcepts.update([dim.dimension, dim.member])
|
|
302
|
+
return usedConcepts
|
|
303
|
+
|
|
209
304
|
def getBalanceSheets(self, modelXbrl: ModelXbrl, statement: Statement) -> list[BalanceSheet]:
|
|
210
305
|
"""
|
|
211
306
|
:return: Balance sheet data for each context/unit pairing the given statement.
|
|
@@ -261,9 +356,60 @@ class PluginValidationDataExtension(PluginData):
|
|
|
261
356
|
)
|
|
262
357
|
return balanceSheets
|
|
263
358
|
|
|
264
|
-
|
|
359
|
+
@lru_cache(1)
|
|
360
|
+
def getCoverItemRequirements(self, modelXbrl: ModelXbrl) -> list[QName] | None:
|
|
361
|
+
manifestInstance = self.getManifestInstance(modelXbrl)
|
|
362
|
+
if manifestInstance is None:
|
|
363
|
+
return None
|
|
364
|
+
if any(e is not None and e.startswith('EDINET.EC5800E') for e in modelXbrl.errors):
|
|
365
|
+
# Manifest TOC parsing failed, so cannot determine cover items.
|
|
366
|
+
return None
|
|
367
|
+
assert len(manifestInstance.tocItems) == 1, _("Only one TOC item should be associated with this instance.")
|
|
368
|
+
roleUri = manifestInstance.tocItems[0].extrole
|
|
369
|
+
roleUri = roleUri.replace("_std_", "_")
|
|
370
|
+
controllerPluginData = ControllerPluginData.get(modelXbrl.modelManager.cntlr, self.name)
|
|
371
|
+
coverItemRequirements = controllerPluginData.getCoverItemRequirements(self.coverItemRequirementsPath)
|
|
372
|
+
coverItems = coverItemRequirements.get(roleUri)
|
|
373
|
+
return [
|
|
374
|
+
self.qname(prefix, localName)
|
|
375
|
+
for prefix, localName in
|
|
376
|
+
[name.split(':') for name in coverItems]
|
|
377
|
+
]
|
|
378
|
+
|
|
379
|
+
@lru_cache(1)
|
|
380
|
+
def getCoverItems(self, modelXbrl: ModelXbrl) -> frozenset[QName]:
|
|
381
|
+
controllerPluginData = ControllerPluginData.get(modelXbrl.modelManager.cntlr, self.name)
|
|
382
|
+
coverItemRequirements = controllerPluginData.getCoverItemRequirements(self.coverItemRequirementsPath)
|
|
383
|
+
coverItems = coverItemRequirements.all()
|
|
384
|
+
return frozenset(
|
|
385
|
+
self.qname(prefix, localName)
|
|
386
|
+
for prefix, localName in
|
|
387
|
+
[name.split(':') for name in coverItems]
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
def getDeiRequirements(self, modelXbrl: ModelXbrl) -> DeiRequirements:
|
|
265
391
|
controllerPluginData = ControllerPluginData.get(modelXbrl.modelManager.cntlr, self.name)
|
|
266
|
-
return controllerPluginData.
|
|
392
|
+
return controllerPluginData.getDeiRequirements(self.deiRequirementsPath, self.deiItems, FILING_FORMATS)
|
|
393
|
+
|
|
394
|
+
@lru_cache(1)
|
|
395
|
+
def getExtensionSchemas(self, modelXbrl: ModelXbrl) -> dict[str, UploadPathInfo]:
|
|
396
|
+
namespacePathInfos: dict[str, UploadPathInfo] = {}
|
|
397
|
+
uploadContents = self.getUploadContents(modelXbrl)
|
|
398
|
+
if uploadContents is None:
|
|
399
|
+
return namespacePathInfos
|
|
400
|
+
for modelDocument in modelXbrl.urlDocs.values():
|
|
401
|
+
if modelDocument.type != ModelDocumentType.SCHEMA:
|
|
402
|
+
continue # Not a schema
|
|
403
|
+
if modelDocument.targetNamespace is None:
|
|
404
|
+
continue # No target namespace
|
|
405
|
+
if not self.isExtensionUri(modelDocument.uri, modelXbrl):
|
|
406
|
+
continue # Not an extension schema
|
|
407
|
+
path = Path(modelDocument.uri)
|
|
408
|
+
pathInfo = uploadContents.uploadPathsByFullPath.get(path)
|
|
409
|
+
if pathInfo is None or pathInfo.reportFolderType is None:
|
|
410
|
+
continue # Not part of the filing, error will be caught elsewhere
|
|
411
|
+
namespacePathInfos[modelDocument.targetNamespace] = pathInfo
|
|
412
|
+
return namespacePathInfos
|
|
267
413
|
|
|
268
414
|
def getProblematicTextBlocks(self, modelXbrl: ModelXbrl) -> list[ModelInlineFact]:
|
|
269
415
|
problematicTextBlocks: list[ModelInlineFact] = []
|
|
@@ -356,10 +502,9 @@ class PluginValidationDataExtension(PluginData):
|
|
|
356
502
|
# values assigned to the various FilingFormats. This may only be by coincidence or convention.
|
|
357
503
|
# If it doesn't end up being reliable, we may need to find another way to identify the form.
|
|
358
504
|
# For example, by disclosure system selection or CLI argument.
|
|
359
|
-
documentTitleFacts = []
|
|
505
|
+
documentTitleFacts: list[ModelFact] = []
|
|
360
506
|
for qname in self.coverPageTitleQns:
|
|
361
|
-
|
|
362
|
-
documentTitleFacts.append(fact)
|
|
507
|
+
documentTitleFacts.extend(self.iterValidNonNilFacts(modelXbrl, qname))
|
|
363
508
|
formTypes = self.getFormTypes(modelXbrl)
|
|
364
509
|
filingFormats = []
|
|
365
510
|
for filingFormatIndex, filingFormat in enumerate(FILING_FORMATS):
|
|
@@ -367,7 +512,7 @@ class PluginValidationDataExtension(PluginData):
|
|
|
367
512
|
continue
|
|
368
513
|
prefixes = {taxonomy.value for taxonomy in filingFormat.taxonomies}
|
|
369
514
|
if not any(
|
|
370
|
-
str(fact.xValue).startswith(filingFormat.documentType.value) and
|
|
515
|
+
str(fact.xValue).strip().startswith(filingFormat.documentType.value) and
|
|
371
516
|
fact.concept.qname.prefix.split('_')[0] in prefixes
|
|
372
517
|
for fact in documentTitleFacts
|
|
373
518
|
):
|
|
@@ -378,7 +523,7 @@ class PluginValidationDataExtension(PluginData):
|
|
|
378
523
|
"arelle:NoMatchingEdinetFormat",
|
|
379
524
|
_("No matching EDINET filing formats could be identified based on form "
|
|
380
525
|
"type (%(formTypes)s) and title."),
|
|
381
|
-
formTypes=formTypes,
|
|
526
|
+
formTypes=", ".join(t.value for t in formTypes),
|
|
382
527
|
modelObject=documentTitleFacts,
|
|
383
528
|
)
|
|
384
529
|
return None
|
|
@@ -451,6 +596,20 @@ class PluginValidationDataExtension(PluginData):
|
|
|
451
596
|
elts.append(elt)
|
|
452
597
|
return elts
|
|
453
598
|
|
|
599
|
+
def getStandardTaxonomyExtensionLinks(self, linkbaseType: LinkbaseType, modelXbrl: ModelXbrl) -> list[ModelObject]:
|
|
600
|
+
elts: list[ModelObject] = []
|
|
601
|
+
for modelDocument in modelXbrl.urlDocs.values():
|
|
602
|
+
if self.isStandardTaxonomyUrl(modelDocument.uri, modelXbrl) or not modelDocument.type == ModelDocumentType.SCHEMA:
|
|
603
|
+
continue
|
|
604
|
+
rootElt = modelDocument.xmlRootElement
|
|
605
|
+
for elt in rootElt.iterdescendants(XbrlConst.qnLinkLinkbaseRef.clarkNotation):
|
|
606
|
+
uri = elt.attrib.get(XbrlConst.qnXlinkHref.clarkNotation)
|
|
607
|
+
role = elt.attrib.get(XbrlConst.qnXlinkRole.clarkNotation)
|
|
608
|
+
if not role == linkbaseType.getRefUri() or self.isExtensionUri(uri, modelXbrl):
|
|
609
|
+
continue
|
|
610
|
+
elts.append(elt)
|
|
611
|
+
return elts
|
|
612
|
+
|
|
454
613
|
def getUploadContents(self, modelXbrl: ModelXbrl) -> UploadContents | None:
|
|
455
614
|
controllerPluginData = ControllerPluginData.get(modelXbrl.modelManager.cntlr, self.name)
|
|
456
615
|
return controllerPluginData.getUploadContents()
|
|
@@ -458,9 +617,16 @@ class PluginValidationDataExtension(PluginData):
|
|
|
458
617
|
@lru_cache(1)
|
|
459
618
|
def getUriAttributeValues(self, modelDocument: ModelDocument) -> list[tuple[ModelObject, str, str]]:
|
|
460
619
|
results: list[tuple[ModelObject, str, str]] = []
|
|
461
|
-
|
|
620
|
+
modelDocumentType = modelDocument.type
|
|
621
|
+
# Normal document parsing does not assign the HTML type to HTML files.
|
|
622
|
+
# Use ModelDocumentType.identify to check for HTML files.
|
|
623
|
+
if modelDocumentType == ModelDocumentType.UnknownXML:
|
|
624
|
+
modelDocumentType = ModelDocumentType.identify(modelDocument.modelXbrl.fileSource, modelDocument.uri)
|
|
625
|
+
if modelDocumentType not in (ModelDocumentType.INLINEXBRL, ModelDocumentType.HTML):
|
|
462
626
|
return results
|
|
463
627
|
for elt in modelDocument.xmlRootElement.iter():
|
|
628
|
+
if not isinstance(elt, ModelObject):
|
|
629
|
+
continue
|
|
464
630
|
for name in htmlEltUriAttrs.get(elt.localName, ()):
|
|
465
631
|
value = elt.get(name)
|
|
466
632
|
if value is not None:
|
|
@@ -7,6 +7,10 @@ from enum import Enum
|
|
|
7
7
|
from functools import cached_property, lru_cache
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
|
|
10
|
+
from regex import Pattern
|
|
11
|
+
|
|
12
|
+
from . import Constants
|
|
13
|
+
|
|
10
14
|
|
|
11
15
|
class ReportFolderType(Enum):
|
|
12
16
|
ATTACH_DOC = "AttachDoc"
|
|
@@ -30,6 +34,10 @@ class ReportFolderType(Enum):
|
|
|
30
34
|
def isAttachment(self) -> bool:
|
|
31
35
|
return "Attach" in self.value
|
|
32
36
|
|
|
37
|
+
@cached_property
|
|
38
|
+
def ixbrlFilenamePatterns(self) -> list[Pattern[str]]:
|
|
39
|
+
return IXBRL_FILENAME_PATTERNS.get(self, [])
|
|
40
|
+
|
|
33
41
|
@cached_property
|
|
34
42
|
def manifestName(self) -> str:
|
|
35
43
|
return f'manifest_{self.value}.xml'
|
|
@@ -38,10 +46,22 @@ class ReportFolderType(Enum):
|
|
|
38
46
|
def manifestPath(self) -> Path:
|
|
39
47
|
return self.xbrlDirectory / self.manifestName
|
|
40
48
|
|
|
49
|
+
@cached_property
|
|
50
|
+
def namespaceUriPatterns(self) -> list[Pattern[str]]:
|
|
51
|
+
return NAMESPACE_URI_PATTERNS.get(self, [])
|
|
52
|
+
|
|
53
|
+
@cached_property
|
|
54
|
+
def prefixPatterns(self) -> list[Pattern[str]]:
|
|
55
|
+
return PREFIX_PATTERNS.get(self, [])
|
|
56
|
+
|
|
41
57
|
@cached_property
|
|
42
58
|
def xbrlDirectory(self) -> Path:
|
|
43
59
|
return Path('XBRL') / str(self.value)
|
|
44
60
|
|
|
61
|
+
@cached_property
|
|
62
|
+
def xbrlFilenamePatterns(self) -> list[Pattern[str]]:
|
|
63
|
+
return XBRL_FILENAME_PATTERNS.get(self, [])
|
|
64
|
+
|
|
45
65
|
@lru_cache(1)
|
|
46
66
|
def getValidExtensions(self, isAmendment: bool, isSubdirectory: bool) -> frozenset[str] | None:
|
|
47
67
|
if self.extensionCategory is None:
|
|
@@ -99,3 +119,44 @@ VALID_EXTENSIONS = {
|
|
|
99
119
|
},
|
|
100
120
|
},
|
|
101
121
|
}
|
|
122
|
+
|
|
123
|
+
IXBRL_FILENAME_PATTERNS = {
|
|
124
|
+
ReportFolderType.AUDIT_DOC: [
|
|
125
|
+
Constants.AUDIT_IXBRL_FILENAME_PATTERN,
|
|
126
|
+
Constants.AUDIT_LINKBASE_FILENAME_PATTERN,
|
|
127
|
+
Constants.AUDIT_SCHEMA_FILENAME_PATTERN,
|
|
128
|
+
],
|
|
129
|
+
ReportFolderType.PUBLIC_DOC: [
|
|
130
|
+
Constants.REPORT_COVER_FILENAME_PATTERN,
|
|
131
|
+
Constants.REPORT_IXBRL_FILENAME_PATTERN,
|
|
132
|
+
Constants.REPORT_LINKBASE_FILENAME_PATTERN,
|
|
133
|
+
Constants.REPORT_SCHEMA_FILENAME_PATTERN,
|
|
134
|
+
]
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
NAMESPACE_URI_PATTERNS = {
|
|
138
|
+
ReportFolderType.AUDIT_DOC: [
|
|
139
|
+
Constants.AUDIT_NAMESPACE_URI_PATTERN,
|
|
140
|
+
],
|
|
141
|
+
ReportFolderType.PUBLIC_DOC: [
|
|
142
|
+
Constants.REPORT_NAMESPACE_URI_PATTERN,
|
|
143
|
+
],
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
PREFIX_PATTERNS = {
|
|
147
|
+
ReportFolderType.AUDIT_DOC: [
|
|
148
|
+
Constants.AUDIT_PREFIX_PATTERN,
|
|
149
|
+
],
|
|
150
|
+
ReportFolderType.PUBLIC_DOC: [
|
|
151
|
+
Constants.REPORT_PREFIX_PATTERN,
|
|
152
|
+
],
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
XBRL_FILENAME_PATTERNS = {
|
|
156
|
+
ReportFolderType.AUDIT_DOC: [
|
|
157
|
+
Constants.AUDIT_XBRL_FILENAME_PATTERN,
|
|
158
|
+
],
|
|
159
|
+
ReportFolderType.PUBLIC_DOC: [
|
|
160
|
+
Constants.REPORT_XBRL_FILENAME_PATTERN,
|
|
161
|
+
],
|
|
162
|
+
}
|