arelle-release 2.37.55__py3-none-any.whl → 2.37.57__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/CntlrCmdLine.py +1 -0
- arelle/ErrorManager.py +3 -0
- arelle/ValidateDuplicateFacts.py +1 -1
- arelle/XbrlConst.py +1 -0
- arelle/_version.py +2 -2
- arelle/plugin/validate/EDINET/Constants.py +0 -18
- arelle/plugin/validate/EDINET/ControllerPluginData.py +43 -20
- arelle/plugin/validate/EDINET/CoverPageRequirements.py +118 -0
- arelle/plugin/validate/EDINET/FilingFormat.py +253 -0
- arelle/plugin/validate/EDINET/FormType.py +81 -0
- arelle/plugin/validate/EDINET/PluginValidationDataExtension.py +146 -34
- arelle/plugin/validate/EDINET/UploadContents.py +18 -2
- arelle/plugin/validate/EDINET/ValidationPluginExtension.py +1 -0
- arelle/plugin/validate/EDINET/resources/cover-page-requirements.csv +27 -0
- arelle/plugin/validate/EDINET/rules/edinet.py +1 -1
- arelle/plugin/validate/EDINET/rules/gfm.py +216 -0
- arelle/plugin/validate/EDINET/rules/upload.py +295 -35
- arelle/plugin/validate/ROS/PluginValidationDataExtension.py +2 -0
- arelle/plugin/validate/ROS/ValidationPluginExtension.py +1 -0
- arelle/plugin/validate/ROS/rules/ros.py +39 -9
- {arelle_release-2.37.55.dist-info → arelle_release-2.37.57.dist-info}/METADATA +1 -1
- {arelle_release-2.37.55.dist-info → arelle_release-2.37.57.dist-info}/RECORD +26 -22
- {arelle_release-2.37.55.dist-info → arelle_release-2.37.57.dist-info}/WHEEL +0 -0
- {arelle_release-2.37.55.dist-info → arelle_release-2.37.57.dist-info}/entry_points.txt +0 -0
- {arelle_release-2.37.55.dist-info → arelle_release-2.37.57.dist-info}/licenses/LICENSE.md +0 -0
- {arelle_release-2.37.55.dist-info → arelle_release-2.37.57.dist-info}/top_level.txt +0 -0
|
@@ -9,14 +9,13 @@ from decimal import Decimal
|
|
|
9
9
|
from functools import lru_cache
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
|
|
12
|
-
from lxml.etree import DTD, XML
|
|
12
|
+
from lxml.etree import DTD, XML
|
|
13
13
|
from operator import attrgetter
|
|
14
14
|
from typing import Callable, Hashable, Iterable, cast
|
|
15
15
|
|
|
16
16
|
import os
|
|
17
17
|
import regex
|
|
18
18
|
|
|
19
|
-
from arelle import UrlUtil
|
|
20
19
|
from arelle.LinkbaseType import LinkbaseType
|
|
21
20
|
from arelle.ModelDocument import Type as ModelDocumentType, ModelDocument
|
|
22
21
|
from arelle.ModelDtsObject import ModelConcept
|
|
@@ -31,10 +30,14 @@ from arelle.XhtmlValidate import htmlEltUriAttrs
|
|
|
31
30
|
from arelle.XmlValidate import VALID
|
|
32
31
|
from arelle.typing import TypeGetText
|
|
33
32
|
from arelle.utils.PluginData import PluginData
|
|
34
|
-
from .Constants import
|
|
33
|
+
from .Constants import xhtmlDtdExtension, PROHIBITED_HTML_TAGS, PROHIBITED_HTML_ATTRIBUTES
|
|
35
34
|
from .ControllerPluginData import ControllerPluginData
|
|
35
|
+
from .CoverPageRequirements import CoverPageRequirements, COVER_PAGE_ITEM_LOCAL_NAMES
|
|
36
|
+
from .FilingFormat import FilingFormat, FILING_FORMATS
|
|
37
|
+
from .FormType import FormType
|
|
36
38
|
from .ManifestInstance import ManifestInstance
|
|
37
39
|
from .Statement import Statement, STATEMENTS, BalanceSheet, StatementInstance, StatementType
|
|
40
|
+
from .UploadContents import UploadContents
|
|
38
41
|
|
|
39
42
|
_: TypeGetText
|
|
40
43
|
|
|
@@ -57,35 +60,59 @@ class PluginValidationDataExtension(PluginData):
|
|
|
57
60
|
consolidatedOrNonConsolidatedAxisQn: QName
|
|
58
61
|
documentTypeDeiQn: QName
|
|
59
62
|
jpcrpEsrFilingDateCoverPageQn: QName
|
|
63
|
+
jpcrpEsrNamespace: str
|
|
60
64
|
jpcrpFilingDateCoverPageQn: QName
|
|
65
|
+
jpcrpNamespace: str
|
|
66
|
+
jpdeiNamespace: str
|
|
67
|
+
jpigpNamespace: str
|
|
68
|
+
jppfsNamespace: str
|
|
61
69
|
jpspsFilingDateCoverPageQn: QName
|
|
70
|
+
jpspsNamespace: str
|
|
62
71
|
nonConsolidatedMemberQn: QName
|
|
63
72
|
ratioOfFemaleDirectorsAndOtherOfficersQn: QName
|
|
64
73
|
|
|
65
74
|
contextIdPattern: regex.Pattern[str]
|
|
75
|
+
coverPageItems: tuple[QName, ...]
|
|
76
|
+
coverPageRequirementsPath: Path
|
|
77
|
+
coverPageTitleQns: tuple[QName, ...]
|
|
66
78
|
|
|
67
79
|
_uriReferences: list[UriReference]
|
|
68
80
|
|
|
69
81
|
def __init__(self, name: str, validateXbrl: ValidateXbrl):
|
|
70
82
|
super().__init__(name)
|
|
71
|
-
|
|
83
|
+
|
|
84
|
+
# Namespaces
|
|
85
|
+
self.jpcrpEsrNamespace = "http://disclosure.edinet-fsa.go.jp/taxonomy/jpcrp-esr/2024-11-01/jpcrp-esr_cor"
|
|
72
86
|
self.jpcrpNamespace = 'http://disclosure.edinet-fsa.go.jp/taxonomy/jpcrp/2024-11-01/jpcrp_cor'
|
|
73
|
-
jpdeiNamespace = 'http://disclosure.edinet-fsa.go.jp/taxonomy/jpdei/2013-08-31/jpdei_cor'
|
|
74
|
-
jpigpNamespace = "http://disclosure.edinet-fsa.go.jp/taxonomy/jpigp/2024-11-01/jpigp_cor"
|
|
75
|
-
jppfsNamespace = "http://disclosure.edinet-fsa.go.jp/taxonomy/jppfs/2024-11-01/jppfs_cor"
|
|
76
|
-
jpspsNamespace = 'http://disclosure.edinet-fsa.go.jp/taxonomy/jpsps/2024-11-01/jpsps_cor'
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
self.
|
|
80
|
-
self.
|
|
87
|
+
self.jpdeiNamespace = 'http://disclosure.edinet-fsa.go.jp/taxonomy/jpdei/2013-08-31/jpdei_cor'
|
|
88
|
+
self.jpigpNamespace = "http://disclosure.edinet-fsa.go.jp/taxonomy/jpigp/2024-11-01/jpigp_cor"
|
|
89
|
+
self.jppfsNamespace = "http://disclosure.edinet-fsa.go.jp/taxonomy/jppfs/2024-11-01/jppfs_cor"
|
|
90
|
+
self.jpspsNamespace = 'http://disclosure.edinet-fsa.go.jp/taxonomy/jpsps/2024-11-01/jpsps_cor'
|
|
91
|
+
|
|
92
|
+
# QNames
|
|
93
|
+
self.accountingStandardsDeiQn = qname(self.jpdeiNamespace, 'AccountingStandardsDEI')
|
|
94
|
+
self.assetsIfrsQn = qname(self.jpigpNamespace, 'AssetsIFRS')
|
|
95
|
+
self.consolidatedOrNonConsolidatedAxisQn = qname(self.jppfsNamespace, 'ConsolidatedOrNonConsolidatedAxis')
|
|
96
|
+
self.documentTypeDeiQn = qname(self.jpdeiNamespace, 'DocumentTypeDEI')
|
|
81
97
|
self.issuedSharesTotalNumberOfSharesEtcQn = qname(self.jpcrpNamespace, 'IssuedSharesTotalNumberOfSharesEtcTextBlock')
|
|
82
|
-
self.jpcrpEsrFilingDateCoverPageQn = qname(jpcrpEsrNamespace, 'FilingDateCoverPage')
|
|
98
|
+
self.jpcrpEsrFilingDateCoverPageQn = qname(self.jpcrpEsrNamespace, 'FilingDateCoverPage')
|
|
83
99
|
self.jpcrpFilingDateCoverPageQn = qname(self.jpcrpNamespace, 'FilingDateCoverPage')
|
|
84
|
-
self.jpspsFilingDateCoverPageQn = qname(jpspsNamespace, 'FilingDateCoverPage')
|
|
85
|
-
self.nonConsolidatedMemberQn = qname(jppfsNamespace, "NonConsolidatedMember")
|
|
100
|
+
self.jpspsFilingDateCoverPageQn = qname(self.jpspsNamespace, 'FilingDateCoverPage')
|
|
101
|
+
self.nonConsolidatedMemberQn = qname(self.jppfsNamespace, "NonConsolidatedMember")
|
|
86
102
|
self.ratioOfFemaleDirectorsAndOtherOfficersQn = qname(self.jpcrpNamespace, "RatioOfFemaleDirectorsAndOtherOfficers")
|
|
87
103
|
|
|
88
104
|
self.contextIdPattern = regex.compile(r'(Prior[1-9]Year|CurrentYear|Prior[1-9]Interim|Interim)(Duration|Instant)')
|
|
105
|
+
self.coverPageItems = tuple(
|
|
106
|
+
qname(self.jpdeiNamespace, localName)
|
|
107
|
+
for localName in COVER_PAGE_ITEM_LOCAL_NAMES
|
|
108
|
+
)
|
|
109
|
+
self.coverPageRequirementsPath = Path(__file__).parent / "resources" / "cover-page-requirements.csv"
|
|
110
|
+
self.coverPageTitleQns = (
|
|
111
|
+
qname(self.jpspsNamespace, "DocumentTitleAnnualSecuritiesReportCoverPage"),
|
|
112
|
+
qname(self.jpcrpNamespace, "DocumentTitleCoverPage"),
|
|
113
|
+
qname(self.jpcrpEsrNamespace, "DocumentTitleCoverPage"),
|
|
114
|
+
qname(self.jpspsNamespace, "DocumentTitleCoverPage"),
|
|
115
|
+
)
|
|
89
116
|
|
|
90
117
|
self._uriReferences = []
|
|
91
118
|
self._initialize(validateXbrl.modelXbrl)
|
|
@@ -139,17 +166,22 @@ class PluginValidationDataExtension(PluginData):
|
|
|
139
166
|
|
|
140
167
|
@lru_cache(1)
|
|
141
168
|
def isCorporateForm(self, modelXbrl: ModelXbrl) -> bool:
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
169
|
+
formTypes = self.getFormTypes(modelXbrl)
|
|
170
|
+
return any(
|
|
171
|
+
formType.isCorporateForm
|
|
172
|
+
for formType in formTypes
|
|
173
|
+
)
|
|
146
174
|
|
|
147
175
|
def isCorporateReport(self, modelXbrl: ModelXbrl) -> bool:
|
|
148
176
|
return self.jpcrpNamespace in modelXbrl.namespaceDocs
|
|
149
177
|
|
|
178
|
+
@lru_cache(1)
|
|
150
179
|
def isStockForm(self, modelXbrl: ModelXbrl) -> bool:
|
|
151
|
-
|
|
152
|
-
return any(
|
|
180
|
+
formTypes = self.getFormTypes(modelXbrl)
|
|
181
|
+
return any(
|
|
182
|
+
formType.isStockReport
|
|
183
|
+
for formType in formTypes
|
|
184
|
+
)
|
|
153
185
|
|
|
154
186
|
def getBalanceSheets(self, modelXbrl: ModelXbrl, statement: Statement) -> list[BalanceSheet]:
|
|
155
187
|
"""
|
|
@@ -206,6 +238,10 @@ class PluginValidationDataExtension(PluginData):
|
|
|
206
238
|
)
|
|
207
239
|
return balanceSheets
|
|
208
240
|
|
|
241
|
+
def getCoverPageRequirements(self, modelXbrl: ModelXbrl) -> CoverPageRequirements:
|
|
242
|
+
controllerPluginData = ControllerPluginData.get(modelXbrl.modelManager.cntlr, self.name)
|
|
243
|
+
return controllerPluginData.getCoverPageRequirements(self.coverPageRequirementsPath, self.coverPageItems, FILING_FORMATS)
|
|
244
|
+
|
|
209
245
|
def getProblematicTextBlocks(self, modelXbrl: ModelXbrl) -> list[ModelInlineFact]:
|
|
210
246
|
problematicTextBlocks: list[ModelInlineFact] = []
|
|
211
247
|
dtd = DTD(os.path.join(modelXbrl.modelManager.cntlr.configDir, xhtmlDtdExtension))
|
|
@@ -247,15 +283,6 @@ class PluginValidationDataExtension(PluginData):
|
|
|
247
283
|
def getDeduplicatedFacts(self, modelXbrl: ModelXbrl) -> list[ModelFact]:
|
|
248
284
|
return getDeduplicatedFacts(modelXbrl, DeduplicationType.CONSISTENT_PAIRS)
|
|
249
285
|
|
|
250
|
-
@lru_cache(1)
|
|
251
|
-
def getDocumentTypes(self, modelXbrl: ModelXbrl) -> set[str]:
|
|
252
|
-
documentFacts = modelXbrl.factsByQname.get(self.documentTypeDeiQn, set())
|
|
253
|
-
documentTypes = set()
|
|
254
|
-
for fact in documentFacts:
|
|
255
|
-
if fact.xValid >= VALID:
|
|
256
|
-
documentTypes.add(fact.textValue)
|
|
257
|
-
return documentTypes
|
|
258
|
-
|
|
259
286
|
def getFactsByContextAndUnit(
|
|
260
287
|
self, modelXbrl: ModelXbrl,
|
|
261
288
|
getContextKey: Callable[[ModelContext], Hashable],
|
|
@@ -296,6 +323,80 @@ class PluginValidationDataExtension(PluginData):
|
|
|
296
323
|
if isinstance(elt, (ModelObject, LinkPrototype))
|
|
297
324
|
]
|
|
298
325
|
|
|
326
|
+
@lru_cache(1)
|
|
327
|
+
def getFilingFormat(self, modelXbrl: ModelXbrl) -> FilingFormat | None:
|
|
328
|
+
# This function attempts to identify the filing format based on form number and title concepts.
|
|
329
|
+
# The provided form number value directly informs the format.
|
|
330
|
+
# However, the document title is not necessarily an explicit setting of the format's
|
|
331
|
+
# document type. In the samples available to us and in a handful of public filings,
|
|
332
|
+
# it is effective to match the first segment of the title value against document type
|
|
333
|
+
# values assigned to the various FilingFormats. This may only be by coincidence or convention.
|
|
334
|
+
# If it doesn't end up being reliable, we may need to find another way to identify the form.
|
|
335
|
+
# For example, by disclosure system selection or CLI argument.
|
|
336
|
+
documentTitleFacts = []
|
|
337
|
+
for qname in self.coverPageTitleQns:
|
|
338
|
+
for fact in self.iterValidNonNilFacts(modelXbrl, qname):
|
|
339
|
+
documentTitleFacts.append(fact)
|
|
340
|
+
formTypes = self.getFormTypes(modelXbrl)
|
|
341
|
+
filingFormats = []
|
|
342
|
+
for filingFormatIndex, filingFormat in enumerate(FILING_FORMATS):
|
|
343
|
+
if filingFormat.formType not in formTypes:
|
|
344
|
+
continue
|
|
345
|
+
prefixes = {taxonomy.value for taxonomy in filingFormat.taxonomies}
|
|
346
|
+
if not any(
|
|
347
|
+
str(fact.xValue).startswith(filingFormat.documentType.value) and
|
|
348
|
+
fact.concept.qname.prefix.split('_')[0] in prefixes
|
|
349
|
+
for fact in documentTitleFacts
|
|
350
|
+
):
|
|
351
|
+
continue
|
|
352
|
+
filingFormats.append((filingFormat, filingFormatIndex))
|
|
353
|
+
if len(filingFormats) == 0:
|
|
354
|
+
modelXbrl.error(
|
|
355
|
+
"arelle:NoMatchingEdinetFormat",
|
|
356
|
+
_("No matching EDINET filing formats could be identified based on form "
|
|
357
|
+
"type (%(formTypes)s) and title."),
|
|
358
|
+
formTypes=formTypes,
|
|
359
|
+
modelObject=documentTitleFacts,
|
|
360
|
+
)
|
|
361
|
+
return None
|
|
362
|
+
if len(filingFormats) > 1:
|
|
363
|
+
formatIndexes = [str(idx + 1) for _, idx in filingFormats]
|
|
364
|
+
modelXbrl.error(
|
|
365
|
+
"arelle:MultipleMatchingEdinetFormats",
|
|
366
|
+
_("Multiple EDINET filing formats (%(formatIndexes)s) matched based on form "
|
|
367
|
+
"type %(formTypes)s and title."),
|
|
368
|
+
formatIndexes=formatIndexes,
|
|
369
|
+
formTypes=formTypes,
|
|
370
|
+
modelObject=documentTitleFacts,
|
|
371
|
+
)
|
|
372
|
+
return None
|
|
373
|
+
filingFormat, filingFormatIndex = filingFormats[0]
|
|
374
|
+
modelXbrl.modelManager.cntlr.addToLog("Identified filing format: #{}, {}, {}, {}, {}".format(
|
|
375
|
+
filingFormatIndex + 1,
|
|
376
|
+
filingFormat.ordinance.value,
|
|
377
|
+
filingFormat.documentType.value,
|
|
378
|
+
filingFormat.formType.value,
|
|
379
|
+
', '.join(taxonomy.value for taxonomy in filingFormat.taxonomies)
|
|
380
|
+
), messageCode="info")
|
|
381
|
+
return filingFormat
|
|
382
|
+
|
|
383
|
+
@lru_cache(1)
|
|
384
|
+
def getFormTypes(self, modelXbrl: ModelXbrl) -> set[FormType]:
|
|
385
|
+
"""
|
|
386
|
+
Retrieves form type values from the instance.
|
|
387
|
+
Note that the underlying concept is labeled "DocumentTypeDEI",
|
|
388
|
+
but "Document Type" refers to something else in EDINET documentation.
|
|
389
|
+
In practice, the value of this field is the form number / form type.
|
|
390
|
+
:param modelXbrl: Instance to get form types from.
|
|
391
|
+
:return: Set of discovered form types.
|
|
392
|
+
"""
|
|
393
|
+
formTypes = set()
|
|
394
|
+
for fact in self.iterValidNonNilFacts(modelXbrl, self.documentTypeDeiQn):
|
|
395
|
+
formType = FormType.parse(fact.textValue)
|
|
396
|
+
if formType is not None:
|
|
397
|
+
formTypes.add(formType)
|
|
398
|
+
return formTypes
|
|
399
|
+
|
|
299
400
|
@lru_cache(1)
|
|
300
401
|
def getManifestInstance(self, modelXbrl: ModelXbrl) -> ManifestInstance | None:
|
|
301
402
|
controllerPluginData = ControllerPluginData.get(modelXbrl.modelManager.cntlr, self.name)
|
|
@@ -309,7 +410,7 @@ class PluginValidationDataExtension(PluginData):
|
|
|
309
410
|
for elt in modelDocument.xmlRootElement.iter():
|
|
310
411
|
if not isinstance(elt, ModelObject):
|
|
311
412
|
continue
|
|
312
|
-
for attributeName in elt.attrib
|
|
413
|
+
for attributeName in elt.attrib:
|
|
313
414
|
if attributeName in PROHIBITED_HTML_ATTRIBUTES:
|
|
314
415
|
results.append((elt, str(attributeName)))
|
|
315
416
|
return results
|
|
@@ -327,6 +428,10 @@ class PluginValidationDataExtension(PluginData):
|
|
|
327
428
|
elts.append(elt)
|
|
328
429
|
return elts
|
|
329
430
|
|
|
431
|
+
def getUploadContents(self, modelXbrl: ModelXbrl) -> UploadContents | None:
|
|
432
|
+
controllerPluginData = ControllerPluginData.get(modelXbrl.modelManager.cntlr, self.name)
|
|
433
|
+
return controllerPluginData.getUploadContents()
|
|
434
|
+
|
|
330
435
|
@lru_cache(1)
|
|
331
436
|
def getUriAttributeValues(self, modelDocument: ModelDocument) -> list[tuple[ModelObject, str, str]]:
|
|
332
437
|
results: list[tuple[ModelObject, str, str]] = []
|
|
@@ -345,10 +450,17 @@ class PluginValidationDataExtension(PluginData):
|
|
|
345
450
|
def isStandardTaxonomyUrl(self, uri: str, modelXbrl: ModelXbrl) -> bool:
|
|
346
451
|
return modelXbrl.modelManager.disclosureSystem.hrefValidForDisclosureSystem(uri)
|
|
347
452
|
|
|
453
|
+
def iterFacts(self, modelXbrl: ModelXbrl, qname: QName) -> Iterable[ModelFact]:
|
|
454
|
+
yield from modelXbrl.factsByQname.get(qname, set())
|
|
455
|
+
|
|
456
|
+
def iterValidFacts(self, modelXbrl: ModelXbrl, qname: QName) -> Iterable[ModelFact]:
|
|
457
|
+
for fact in self.iterFacts(modelXbrl, qname):
|
|
458
|
+
if fact.xValid >= VALID:
|
|
459
|
+
yield fact
|
|
460
|
+
|
|
348
461
|
def iterValidNonNilFacts(self, modelXbrl: ModelXbrl, qname: QName) -> Iterable[ModelFact]:
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
if fact.xValid >= VALID and not fact.isNil:
|
|
462
|
+
for fact in self.iterValidFacts(modelXbrl, qname):
|
|
463
|
+
if not fact.isNil:
|
|
352
464
|
yield fact
|
|
353
465
|
|
|
354
466
|
def addUsedFilepath(self, modelXbrl: ModelXbrl, path: Path) -> None:
|
|
@@ -4,6 +4,7 @@ See COPYRIGHT.md for copyright information.
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
6
|
from dataclasses import dataclass
|
|
7
|
+
from functools import cached_property
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
|
|
9
10
|
from .ReportFolderType import ReportFolderType
|
|
@@ -12,15 +13,30 @@ from .ReportFolderType import ReportFolderType
|
|
|
12
13
|
@dataclass(frozen=True)
|
|
13
14
|
class UploadContents:
|
|
14
15
|
reports: dict[ReportFolderType, frozenset[Path]]
|
|
15
|
-
uploadPaths:
|
|
16
|
+
uploadPaths: list[UploadPathInfo]
|
|
16
17
|
|
|
17
18
|
@property
|
|
18
19
|
def sortedPaths(self) -> list[Path]:
|
|
19
|
-
return sorted(self.uploadPaths
|
|
20
|
+
return sorted(uploadPath.path for uploadPath in self.uploadPaths)
|
|
21
|
+
|
|
22
|
+
@cached_property
|
|
23
|
+
def uploadPathsByFullPath(self) -> dict[Path, UploadPathInfo]:
|
|
24
|
+
return {
|
|
25
|
+
uploadPath.fullPath: uploadPath
|
|
26
|
+
for uploadPath in self.uploadPaths
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@cached_property
|
|
30
|
+
def uploadPathsByPath(self) -> dict[Path, UploadPathInfo]:
|
|
31
|
+
return {
|
|
32
|
+
uploadPath.path: uploadPath
|
|
33
|
+
for uploadPath in self.uploadPaths
|
|
34
|
+
}
|
|
20
35
|
|
|
21
36
|
|
|
22
37
|
@dataclass(frozen=True)
|
|
23
38
|
class UploadPathInfo:
|
|
39
|
+
fullPath: Path
|
|
24
40
|
isAttachment: bool
|
|
25
41
|
isCorrection: bool
|
|
26
42
|
isCoverPage: bool
|
|
@@ -37,6 +37,7 @@ class ValidationPluginExtension(ValidationPlugin):
|
|
|
37
37
|
}, level=logging.INFO
|
|
38
38
|
)
|
|
39
39
|
pluginData = ControllerPluginData.get(filesource.cntlr, self.name)
|
|
40
|
+
pluginData.setUploadContents(filesource)
|
|
40
41
|
entrypointFiles = []
|
|
41
42
|
for instance in instances:
|
|
42
43
|
pluginData.addManifestInstance(instance)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎
|
|
2
|
+
×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,×,×,×,×,×,×,×,×,×,×,×,×
|
|
3
|
+
○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○
|
|
4
|
+
◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎
|
|
5
|
+
◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,△,△,△,△,△,△,△,△,△,△,△,△,△,△,△,△,△,△,△,△,△,△,△,△,△,△,△,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎
|
|
6
|
+
×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,×,×,×,×,×,×,×,×,×,×,×,×
|
|
7
|
+
×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,×,×,×,×,×,×,×,×,×,×,×,×
|
|
8
|
+
◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎
|
|
9
|
+
◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,×,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎
|
|
10
|
+
○,×,×,○,○,○,○,◎,◎,◎,◎,◎,◎,×,◎,◎,◎,◎,◎,◎,×,×,×,×,×,×,○,○,×,×,○,○,○,○,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×
|
|
11
|
+
○,×,×,○,○,○,○,◎,◎,◎,◎,◎,◎,×,◎,◎,◎,◎,◎,◎,×,×,×,×,×,×,○,○,×,×,○,○,○,○,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×
|
|
12
|
+
○,×,×,○,○,○,○,○,○,○,○,○,○,×,○,○,○,○,○,○,×,×,×,×,×,×,○,○,×,×,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×
|
|
13
|
+
○,×,×,○,○,○,○,◎,◎,◎,○,◎,◎,×,◎,◎,◎,◎,○,◎,×,×,×,×,×,×,○,○,×,×,○,○,○,○,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×
|
|
14
|
+
○,×,×,○,○,○,○,◎,◎,◎,◎,◎,◎,×,◎,◎,◎,◎,◎,◎,×,×,×,×,×,×,○,○,×,×,○,○,○,○,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×
|
|
15
|
+
○,×,×,○,○,○,○,◎,◎,◎,◎,◎,◎,×,◎,◎,◎,◎,◎,◎,×,×,×,×,×,×,○,○,×,×,○,○,○,○,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×
|
|
16
|
+
○,×,×,○,○,○,○,◎,◎,◎,◎,◎,◎,×,◎,◎,◎,◎,◎,◎,×,×,×,×,×,×,○,○,×,×,○,○,○,○,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×
|
|
17
|
+
○,×,×,○,○,○,○,◎,◎,◎,◎,◎,◎,×,◎,◎,◎,◎,◎,◎,×,×,×,×,×,×,○,○,×,×,○,○,○,○,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×
|
|
18
|
+
○,×,×,○,○,○,○,○,○,○,○,○,○,×,○,○,○,○,○,○,×,×,×,×,×,×,○,○,×,×,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×
|
|
19
|
+
○,×,×,○,○,○,○,○,○,○,○,○,○,×,○,○,○,○,○,○,×,×,×,×,×,×,○,○,×,×,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×
|
|
20
|
+
○,×,×,○,○,○,○,○,○,○,○,○,○,×,○,○,○,○,○,○,×,×,×,×,×,×,○,○,×,×,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×
|
|
21
|
+
○,×,×,○,○,○,○,×,×,×,×,×,×,×,○,○,×,×,×,×,×,×,×,×,×,×,○,○,×,×,○,○,○,○,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×
|
|
22
|
+
○,×,×,○,○,○,○,×,×,×,×,×,×,×,○,○,×,×,×,×,×,×,×,×,×,×,○,○,×,×,○,○,○,○,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×,×
|
|
23
|
+
◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎
|
|
24
|
+
◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎
|
|
25
|
+
○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○
|
|
26
|
+
◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎
|
|
27
|
+
◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎,◎
|
|
@@ -93,7 +93,7 @@ def rule_EC1057E(
|
|
|
93
93
|
Ensure that there is a nonnil value disclosed for FilingDateCoverPage
|
|
94
94
|
Note: This rule is only applicable to the public documents.
|
|
95
95
|
"""
|
|
96
|
-
dei = pluginData.
|
|
96
|
+
dei = pluginData.getFormTypes(val.modelXbrl)
|
|
97
97
|
if len(dei) > 0:
|
|
98
98
|
if not (pluginData.hasValidNonNilFact(val.modelXbrl, pluginData.jpcrpEsrFilingDateCoverPageQn)
|
|
99
99
|
or pluginData.hasValidNonNilFact(val.modelXbrl, pluginData.jpcrpFilingDateCoverPageQn)
|
|
@@ -10,6 +10,7 @@ from typing import Any, cast, Iterable
|
|
|
10
10
|
import regex
|
|
11
11
|
|
|
12
12
|
from arelle import XbrlConst, XmlUtil
|
|
13
|
+
from arelle.ModelDtsObject import ModelConcept
|
|
13
14
|
from arelle.ModelInstanceObject import ModelFact
|
|
14
15
|
from arelle.ModelObject import ModelObject
|
|
15
16
|
from arelle.ModelValue import QName
|
|
@@ -32,6 +33,7 @@ from ..PluginValidationDataExtension import PluginValidationDataExtension
|
|
|
32
33
|
|
|
33
34
|
_: TypeGetText
|
|
34
35
|
|
|
36
|
+
DISALLOWED_LABEL_WHITE_SPACE_CHARACTERS = regex.compile(r'\s{2,}')
|
|
35
37
|
GFM_CONTEXT_DATE_PATTERN = regex.compile(r"^[12][0-9]{3}-[01][0-9]-[0-3][0-9]$")
|
|
36
38
|
GFM_RECOMMENDED_NAMESPACE_PREFIXES = {
|
|
37
39
|
XbrlConst.xbrli: ("xbrli",),
|
|
@@ -670,3 +672,217 @@ def rule_gfm_1_3_8(
|
|
|
670
672
|
msg=_("The submitter-specific taxonomy has an embedded linkbase."),
|
|
671
673
|
modelObject=embeddedElements
|
|
672
674
|
)
|
|
675
|
+
|
|
676
|
+
|
|
677
|
+
@validation(
|
|
678
|
+
hook=ValidationHook.XBRL_FINALLY,
|
|
679
|
+
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
680
|
+
)
|
|
681
|
+
def rule_gfm_1_3_20(
|
|
682
|
+
pluginData: PluginValidationDataExtension,
|
|
683
|
+
val: ValidateXbrl,
|
|
684
|
+
*args: Any,
|
|
685
|
+
**kwargs: Any,
|
|
686
|
+
) -> Iterable[Validation]:
|
|
687
|
+
"""
|
|
688
|
+
EDINET.EC5700W: [GFM 1.3.20] Set the nillable attribute value to "true".
|
|
689
|
+
|
|
690
|
+
GFM 1.3.20 The nillable attribute value of an xsd:element must equal "true".
|
|
691
|
+
"""
|
|
692
|
+
nonNillableElements = set()
|
|
693
|
+
for concept in val.modelXbrl.qnameConcepts.values():
|
|
694
|
+
if concept.namespaceURI == XbrlConst.xsd:
|
|
695
|
+
if concept.get("nillable") == "false":
|
|
696
|
+
nonNillableElements.add(concept)
|
|
697
|
+
if len(nonNillableElements) > 0:
|
|
698
|
+
yield Validation.warning(
|
|
699
|
+
codes='EDINET.EC5700W.GFM.1.3.20',
|
|
700
|
+
msg=_("Set the nillable attribute value to 'true'."),
|
|
701
|
+
modelObject=nonNillableElements
|
|
702
|
+
)
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
@validation(
|
|
706
|
+
hook=ValidationHook.XBRL_FINALLY,
|
|
707
|
+
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
708
|
+
)
|
|
709
|
+
def rule_gfm_1_3_23(
|
|
710
|
+
pluginData: PluginValidationDataExtension,
|
|
711
|
+
val: ValidateXbrl,
|
|
712
|
+
*args: Any,
|
|
713
|
+
**kwargs: Any,
|
|
714
|
+
) -> Iterable[Validation]:
|
|
715
|
+
"""
|
|
716
|
+
EDINET.EC5700W: [GFM 1.3.23] Set the periodType attribute to "duration".
|
|
717
|
+
|
|
718
|
+
GFM 1.3.23 If the abstract attribute of xsd:element is "true", then the
|
|
719
|
+
xbrli:periodType attribute must be "duration".
|
|
720
|
+
"""
|
|
721
|
+
instantAbstractElements = set()
|
|
722
|
+
for concept in val.modelXbrl.qnameConcepts.values():
|
|
723
|
+
if concept.abstract == "true" and concept.periodType == "instant":
|
|
724
|
+
instantAbstractElements.add(concept)
|
|
725
|
+
if len(instantAbstractElements) > 0:
|
|
726
|
+
yield Validation.warning(
|
|
727
|
+
codes='EDINET.EC5700W.GFM.1.3.23',
|
|
728
|
+
msg=_("Set the periodType attribute to 'duration'."),
|
|
729
|
+
modelObject=instantAbstractElements
|
|
730
|
+
)
|
|
731
|
+
|
|
732
|
+
|
|
733
|
+
@validation(
|
|
734
|
+
hook=ValidationHook.XBRL_FINALLY,
|
|
735
|
+
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
736
|
+
)
|
|
737
|
+
def rule_gfm_1_3_30(
|
|
738
|
+
pluginData: PluginValidationDataExtension,
|
|
739
|
+
val: ValidateXbrl,
|
|
740
|
+
*args: Any,
|
|
741
|
+
**kwargs: Any,
|
|
742
|
+
) -> Iterable[Validation]:
|
|
743
|
+
"""
|
|
744
|
+
EDINET.EC5700W: [GFM 1.3.30] Set the periodType attribute to "duration".
|
|
745
|
+
|
|
746
|
+
GFM 1.3.30 If xsd:element type attribute equals "nonnum:domainItemType" then
|
|
747
|
+
the xbrli:periodType attribute must equal "duration".
|
|
748
|
+
"""
|
|
749
|
+
instantDomainElements = set()
|
|
750
|
+
for concept in val.modelXbrl.qnameConcepts.values():
|
|
751
|
+
if concept.type is not None and concept.type.isDomainItemType and concept.periodType == "instant":
|
|
752
|
+
instantDomainElements.add(concept)
|
|
753
|
+
if len(instantDomainElements) > 0:
|
|
754
|
+
yield Validation.warning(
|
|
755
|
+
codes='EDINET.EC5700W.GFM.1.3.30',
|
|
756
|
+
msg=_("Set the periodType attribute to 'duration'."),
|
|
757
|
+
modelObject=instantDomainElements
|
|
758
|
+
)
|
|
759
|
+
|
|
760
|
+
@validation(
|
|
761
|
+
hook=ValidationHook.XBRL_FINALLY,
|
|
762
|
+
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
763
|
+
)
|
|
764
|
+
def rule_gfm_1_3_31(
|
|
765
|
+
pluginData: PluginValidationDataExtension,
|
|
766
|
+
val: ValidateXbrl,
|
|
767
|
+
*args: Any,
|
|
768
|
+
**kwargs: Any,
|
|
769
|
+
) -> Iterable[Validation]:
|
|
770
|
+
"""
|
|
771
|
+
EDINET.EC5700W: [GFM 1.3.31] Set the abstract attribute to "true".
|
|
772
|
+
|
|
773
|
+
GFM 1.3.31: If xsd:element type attribute equals "nonnum:domainItemType" then
|
|
774
|
+
the abstract attribute must equal to "true".
|
|
775
|
+
"""
|
|
776
|
+
nonAbstractDomainElements = set()
|
|
777
|
+
for concept in val.modelXbrl.qnameConcepts.values():
|
|
778
|
+
if concept.type is not None and concept.type.isDomainItemType and concept.abstract != "true":
|
|
779
|
+
nonAbstractDomainElements.add(concept)
|
|
780
|
+
if len(nonAbstractDomainElements) > 0:
|
|
781
|
+
yield Validation.warning(
|
|
782
|
+
codes='EDINET.EC5700W.GFM.1.3.31',
|
|
783
|
+
msg=_("Set the abstract attribute to 'true'."),
|
|
784
|
+
modelObject=nonAbstractDomainElements
|
|
785
|
+
)
|
|
786
|
+
|
|
787
|
+
|
|
788
|
+
@validation(
|
|
789
|
+
hook=ValidationHook.XBRL_FINALLY,
|
|
790
|
+
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
791
|
+
)
|
|
792
|
+
def rule_gfm_1_5_6(
|
|
793
|
+
pluginData: PluginValidationDataExtension,
|
|
794
|
+
val: ValidateXbrl,
|
|
795
|
+
*args: Any,
|
|
796
|
+
**kwargs: Any,
|
|
797
|
+
) -> Iterable[Validation]:
|
|
798
|
+
"""
|
|
799
|
+
EDINET.EC5700W: [GFM 1.5.6] The length of a label must be less than 511 characters unless its role is documentation.
|
|
800
|
+
"""
|
|
801
|
+
labelRelationshipSet = val.modelXbrl.relationshipSet(XbrlConst.conceptLabel)
|
|
802
|
+
if labelRelationshipSet is None:
|
|
803
|
+
return
|
|
804
|
+
for concept in val.modelXbrl.qnameConcepts.values():
|
|
805
|
+
labelRels = labelRelationshipSet.fromModelObject(concept)
|
|
806
|
+
for rel in labelRels:
|
|
807
|
+
label = rel.toModelObject
|
|
808
|
+
if label.role != XbrlConst.documentationLabel and label.viewText() is not None and len(label.viewText()) >= 511:
|
|
809
|
+
yield Validation.warning(
|
|
810
|
+
codes='EDINET.EC5700W.GFM.1.5.6',
|
|
811
|
+
msg=_("The concept of '%(concept)s' has a label classified as '%(role)s' that is longer than 511 characters: %(label)s"),
|
|
812
|
+
concept=concept.qname,
|
|
813
|
+
role=label.role,
|
|
814
|
+
label=label.viewText(),
|
|
815
|
+
modelObject=label
|
|
816
|
+
)
|
|
817
|
+
|
|
818
|
+
|
|
819
|
+
@validation(
|
|
820
|
+
hook=ValidationHook.XBRL_FINALLY,
|
|
821
|
+
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
822
|
+
)
|
|
823
|
+
def rule_gfm_1_5_7(
|
|
824
|
+
pluginData: PluginValidationDataExtension,
|
|
825
|
+
val: ValidateXbrl,
|
|
826
|
+
*args: Any,
|
|
827
|
+
**kwargs: Any,
|
|
828
|
+
) -> Iterable[Validation]:
|
|
829
|
+
"""
|
|
830
|
+
EDINET.EC5700W: [GFM 1.5.7] A label cannot contain the "<" character or consecutive white space characters including
|
|
831
|
+
but not limited to: space, carriage return, line feed or tab.
|
|
832
|
+
"""
|
|
833
|
+
labelRelationshipSet = val.modelXbrl.relationshipSet(XbrlConst.conceptLabel)
|
|
834
|
+
if labelRelationshipSet is None:
|
|
835
|
+
return
|
|
836
|
+
for concept in val.modelXbrl.qnameConcepts.values():
|
|
837
|
+
labelRels = labelRelationshipSet.fromModelObject(concept)
|
|
838
|
+
for rel in labelRels:
|
|
839
|
+
label = rel.toModelObject
|
|
840
|
+
if label.role != XbrlConst.documentationLabel and label.textValue is not None:
|
|
841
|
+
if '<' in label.textValue:
|
|
842
|
+
yield Validation.warning(
|
|
843
|
+
codes='EDINET.EC5700W.GFM.1.5.7',
|
|
844
|
+
msg=_("The concept of '%(concept)s' has a label classified as '%(role)s that contains the '<' character: %(label)s"),
|
|
845
|
+
concept=concept.qname,
|
|
846
|
+
role=label.role,
|
|
847
|
+
label=label.textValue,
|
|
848
|
+
modelObject=label
|
|
849
|
+
)
|
|
850
|
+
elif DISALLOWED_LABEL_WHITE_SPACE_CHARACTERS.search(label.textValue):
|
|
851
|
+
yield Validation.warning(
|
|
852
|
+
codes='EDINET.EC5700W.GFM.1.5.7',
|
|
853
|
+
msg=_("The concept of '%(concept)s' has a label classified as '%(role)s' that contains consecutive white space characters: %(label)s"),
|
|
854
|
+
concept=concept.qname,
|
|
855
|
+
role=label.role,
|
|
856
|
+
label=label.textValue,
|
|
857
|
+
modelObject=label
|
|
858
|
+
)
|
|
859
|
+
|
|
860
|
+
|
|
861
|
+
@validation(
|
|
862
|
+
hook=ValidationHook.XBRL_FINALLY,
|
|
863
|
+
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
864
|
+
)
|
|
865
|
+
def rule_gfm_1_5_8(
|
|
866
|
+
pluginData: PluginValidationDataExtension,
|
|
867
|
+
val: ValidateXbrl,
|
|
868
|
+
*args: Any,
|
|
869
|
+
**kwargs: Any,
|
|
870
|
+
) -> Iterable[Validation]:
|
|
871
|
+
"""
|
|
872
|
+
EDINET.EC5700W: [GFM 1.5.8] A label should not begin or end with a white space character
|
|
873
|
+
"""
|
|
874
|
+
labelRelationshipSet = val.modelXbrl.relationshipSet(XbrlConst.conceptLabel)
|
|
875
|
+
if labelRelationshipSet is None:
|
|
876
|
+
return
|
|
877
|
+
for concept in val.modelXbrl.qnameConcepts.values():
|
|
878
|
+
labelRels = labelRelationshipSet.fromModelObject(concept)
|
|
879
|
+
for rel in labelRels:
|
|
880
|
+
label = rel.toModelObject
|
|
881
|
+
if label.textValue is not None and label.textValue != label.textValue.strip():
|
|
882
|
+
yield Validation.warning(
|
|
883
|
+
codes='EDINET.EC5700W.GFM.1.5.8',
|
|
884
|
+
msg=_("The concept of '%(concept)s' has a label that contains disallowed white space either at the begining or the end: '%(label)s'"),
|
|
885
|
+
concept=concept.qname,
|
|
886
|
+
label=label.textValue,
|
|
887
|
+
modelObject=label
|
|
888
|
+
)
|