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.

Files changed (26) hide show
  1. arelle/CntlrCmdLine.py +1 -0
  2. arelle/ErrorManager.py +3 -0
  3. arelle/ValidateDuplicateFacts.py +1 -1
  4. arelle/XbrlConst.py +1 -0
  5. arelle/_version.py +2 -2
  6. arelle/plugin/validate/EDINET/Constants.py +0 -18
  7. arelle/plugin/validate/EDINET/ControllerPluginData.py +43 -20
  8. arelle/plugin/validate/EDINET/CoverPageRequirements.py +118 -0
  9. arelle/plugin/validate/EDINET/FilingFormat.py +253 -0
  10. arelle/plugin/validate/EDINET/FormType.py +81 -0
  11. arelle/plugin/validate/EDINET/PluginValidationDataExtension.py +146 -34
  12. arelle/plugin/validate/EDINET/UploadContents.py +18 -2
  13. arelle/plugin/validate/EDINET/ValidationPluginExtension.py +1 -0
  14. arelle/plugin/validate/EDINET/resources/cover-page-requirements.csv +27 -0
  15. arelle/plugin/validate/EDINET/rules/edinet.py +1 -1
  16. arelle/plugin/validate/EDINET/rules/gfm.py +216 -0
  17. arelle/plugin/validate/EDINET/rules/upload.py +295 -35
  18. arelle/plugin/validate/ROS/PluginValidationDataExtension.py +2 -0
  19. arelle/plugin/validate/ROS/ValidationPluginExtension.py +1 -0
  20. arelle/plugin/validate/ROS/rules/ros.py +39 -9
  21. {arelle_release-2.37.55.dist-info → arelle_release-2.37.57.dist-info}/METADATA +1 -1
  22. {arelle_release-2.37.55.dist-info → arelle_release-2.37.57.dist-info}/RECORD +26 -22
  23. {arelle_release-2.37.55.dist-info → arelle_release-2.37.57.dist-info}/WHEEL +0 -0
  24. {arelle_release-2.37.55.dist-info → arelle_release-2.37.57.dist-info}/entry_points.txt +0 -0
  25. {arelle_release-2.37.55.dist-info → arelle_release-2.37.57.dist-info}/licenses/LICENSE.md +0 -0
  26. {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, _ElementTree, _Comment, _ProcessingInstruction
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 CORPORATE_FORMS, FormType, xhtmlDtdExtension, PROHIBITED_HTML_TAGS, PROHIBITED_HTML_ATTRIBUTES
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
- jpcrpEsrNamespace = "http://disclosure.edinet-fsa.go.jp/taxonomy/jpcrp-esr/2024-11-01/jpcrp-esr_cor"
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
- self.accountingStandardsDeiQn = qname(jpdeiNamespace, 'AccountingStandardsDEI')
78
- self.assetsIfrsQn = qname(jpigpNamespace, 'AssetsIFRS')
79
- self.consolidatedOrNonConsolidatedAxisQn = qname(jppfsNamespace, 'ConsolidatedOrNonConsolidatedAxis')
80
- self.documentTypeDeiQn = qname(jpdeiNamespace, 'DocumentTypeDEI')
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
- documentTypes = self.getDocumentTypes(modelXbrl)
143
- if any(documentType == form.value for form in CORPORATE_FORMS for documentType in documentTypes):
144
- return True
145
- return False
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
- documentTypes = self.getDocumentTypes(modelXbrl)
152
- return any(documentType == form.value for form in FormType if form.isStockReport for documentType in documentTypes)
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.keys():
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
- facts = modelXbrl.factsByQname.get(qname, set())
350
- for fact in facts:
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: dict[Path, UploadPathInfo]
16
+ uploadPaths: list[UploadPathInfo]
16
17
 
17
18
  @property
18
19
  def sortedPaths(self) -> list[Path]:
19
- return sorted(self.uploadPaths.keys())
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.getDocumentTypes(val.modelXbrl)
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
+ )