arelle-release 2.37.56__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.

@@ -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,8 +30,11 @@ 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
38
40
  from .UploadContents import UploadContents
@@ -58,35 +60,59 @@ class PluginValidationDataExtension(PluginData):
58
60
  consolidatedOrNonConsolidatedAxisQn: QName
59
61
  documentTypeDeiQn: QName
60
62
  jpcrpEsrFilingDateCoverPageQn: QName
63
+ jpcrpEsrNamespace: str
61
64
  jpcrpFilingDateCoverPageQn: QName
65
+ jpcrpNamespace: str
66
+ jpdeiNamespace: str
67
+ jpigpNamespace: str
68
+ jppfsNamespace: str
62
69
  jpspsFilingDateCoverPageQn: QName
70
+ jpspsNamespace: str
63
71
  nonConsolidatedMemberQn: QName
64
72
  ratioOfFemaleDirectorsAndOtherOfficersQn: QName
65
73
 
66
74
  contextIdPattern: regex.Pattern[str]
75
+ coverPageItems: tuple[QName, ...]
76
+ coverPageRequirementsPath: Path
77
+ coverPageTitleQns: tuple[QName, ...]
67
78
 
68
79
  _uriReferences: list[UriReference]
69
80
 
70
81
  def __init__(self, name: str, validateXbrl: ValidateXbrl):
71
82
  super().__init__(name)
72
- 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"
73
86
  self.jpcrpNamespace = 'http://disclosure.edinet-fsa.go.jp/taxonomy/jpcrp/2024-11-01/jpcrp_cor'
74
- jpdeiNamespace = 'http://disclosure.edinet-fsa.go.jp/taxonomy/jpdei/2013-08-31/jpdei_cor'
75
- jpigpNamespace = "http://disclosure.edinet-fsa.go.jp/taxonomy/jpigp/2024-11-01/jpigp_cor"
76
- jppfsNamespace = "http://disclosure.edinet-fsa.go.jp/taxonomy/jppfs/2024-11-01/jppfs_cor"
77
- jpspsNamespace = 'http://disclosure.edinet-fsa.go.jp/taxonomy/jpsps/2024-11-01/jpsps_cor'
78
- self.accountingStandardsDeiQn = qname(jpdeiNamespace, 'AccountingStandardsDEI')
79
- self.assetsIfrsQn = qname(jpigpNamespace, 'AssetsIFRS')
80
- self.consolidatedOrNonConsolidatedAxisQn = qname(jppfsNamespace, 'ConsolidatedOrNonConsolidatedAxis')
81
- 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')
82
97
  self.issuedSharesTotalNumberOfSharesEtcQn = qname(self.jpcrpNamespace, 'IssuedSharesTotalNumberOfSharesEtcTextBlock')
83
- self.jpcrpEsrFilingDateCoverPageQn = qname(jpcrpEsrNamespace, 'FilingDateCoverPage')
98
+ self.jpcrpEsrFilingDateCoverPageQn = qname(self.jpcrpEsrNamespace, 'FilingDateCoverPage')
84
99
  self.jpcrpFilingDateCoverPageQn = qname(self.jpcrpNamespace, 'FilingDateCoverPage')
85
- self.jpspsFilingDateCoverPageQn = qname(jpspsNamespace, 'FilingDateCoverPage')
86
- self.nonConsolidatedMemberQn = qname(jppfsNamespace, "NonConsolidatedMember")
100
+ self.jpspsFilingDateCoverPageQn = qname(self.jpspsNamespace, 'FilingDateCoverPage')
101
+ self.nonConsolidatedMemberQn = qname(self.jppfsNamespace, "NonConsolidatedMember")
87
102
  self.ratioOfFemaleDirectorsAndOtherOfficersQn = qname(self.jpcrpNamespace, "RatioOfFemaleDirectorsAndOtherOfficers")
88
103
 
89
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
+ )
90
116
 
91
117
  self._uriReferences = []
92
118
  self._initialize(validateXbrl.modelXbrl)
@@ -140,17 +166,22 @@ class PluginValidationDataExtension(PluginData):
140
166
 
141
167
  @lru_cache(1)
142
168
  def isCorporateForm(self, modelXbrl: ModelXbrl) -> bool:
143
- documentTypes = self.getDocumentTypes(modelXbrl)
144
- if any(documentType == form.value for form in CORPORATE_FORMS for documentType in documentTypes):
145
- return True
146
- return False
169
+ formTypes = self.getFormTypes(modelXbrl)
170
+ return any(
171
+ formType.isCorporateForm
172
+ for formType in formTypes
173
+ )
147
174
 
148
175
  def isCorporateReport(self, modelXbrl: ModelXbrl) -> bool:
149
176
  return self.jpcrpNamespace in modelXbrl.namespaceDocs
150
177
 
178
+ @lru_cache(1)
151
179
  def isStockForm(self, modelXbrl: ModelXbrl) -> bool:
152
- documentTypes = self.getDocumentTypes(modelXbrl)
153
- 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
+ )
154
185
 
155
186
  def getBalanceSheets(self, modelXbrl: ModelXbrl, statement: Statement) -> list[BalanceSheet]:
156
187
  """
@@ -207,6 +238,10 @@ class PluginValidationDataExtension(PluginData):
207
238
  )
208
239
  return balanceSheets
209
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
+
210
245
  def getProblematicTextBlocks(self, modelXbrl: ModelXbrl) -> list[ModelInlineFact]:
211
246
  problematicTextBlocks: list[ModelInlineFact] = []
212
247
  dtd = DTD(os.path.join(modelXbrl.modelManager.cntlr.configDir, xhtmlDtdExtension))
@@ -248,15 +283,6 @@ class PluginValidationDataExtension(PluginData):
248
283
  def getDeduplicatedFacts(self, modelXbrl: ModelXbrl) -> list[ModelFact]:
249
284
  return getDeduplicatedFacts(modelXbrl, DeduplicationType.CONSISTENT_PAIRS)
250
285
 
251
- @lru_cache(1)
252
- def getDocumentTypes(self, modelXbrl: ModelXbrl) -> set[str]:
253
- documentFacts = modelXbrl.factsByQname.get(self.documentTypeDeiQn, set())
254
- documentTypes = set()
255
- for fact in documentFacts:
256
- if fact.xValid >= VALID:
257
- documentTypes.add(fact.textValue)
258
- return documentTypes
259
-
260
286
  def getFactsByContextAndUnit(
261
287
  self, modelXbrl: ModelXbrl,
262
288
  getContextKey: Callable[[ModelContext], Hashable],
@@ -297,6 +323,80 @@ class PluginValidationDataExtension(PluginData):
297
323
  if isinstance(elt, (ModelObject, LinkPrototype))
298
324
  ]
299
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
+
300
400
  @lru_cache(1)
301
401
  def getManifestInstance(self, modelXbrl: ModelXbrl) -> ManifestInstance | None:
302
402
  controllerPluginData = ControllerPluginData.get(modelXbrl.modelManager.cntlr, self.name)
@@ -350,10 +450,17 @@ class PluginValidationDataExtension(PluginData):
350
450
  def isStandardTaxonomyUrl(self, uri: str, modelXbrl: ModelXbrl) -> bool:
351
451
  return modelXbrl.modelManager.disclosureSystem.hrefValidForDisclosureSystem(uri)
352
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
+
353
461
  def iterValidNonNilFacts(self, modelXbrl: ModelXbrl, qname: QName) -> Iterable[ModelFact]:
354
- facts = modelXbrl.factsByQname.get(qname, set())
355
- for fact in facts:
356
- if fact.xValid >= VALID and not fact.isNil:
462
+ for fact in self.iterValidFacts(modelXbrl, qname):
463
+ if not fact.isNil:
357
464
  yield fact
358
465
 
359
466
  def addUsedFilepath(self, modelXbrl: ModelXbrl, path: Path) -> None:
@@ -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",),
@@ -672,6 +674,117 @@ def rule_gfm_1_3_8(
672
674
  )
673
675
 
674
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
+
675
788
  @validation(
676
789
  hook=ValidationHook.XBRL_FINALLY,
677
790
  disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
@@ -683,7 +796,7 @@ def rule_gfm_1_5_6(
683
796
  **kwargs: Any,
684
797
  ) -> Iterable[Validation]:
685
798
  """
686
- EDINET.EC5700W: [GFM 1.5.6] The length of a label must be less than 511 characters unless it its role is documentation.
799
+ EDINET.EC5700W: [GFM 1.5.6] The length of a label must be less than 511 characters unless its role is documentation.
687
800
  """
688
801
  labelRelationshipSet = val.modelXbrl.relationshipSet(XbrlConst.conceptLabel)
689
802
  if labelRelationshipSet is None:
@@ -692,7 +805,7 @@ def rule_gfm_1_5_6(
692
805
  labelRels = labelRelationshipSet.fromModelObject(concept)
693
806
  for rel in labelRels:
694
807
  label = rel.toModelObject
695
- if label.role != XbrlConst.documentationLabel and label.viewText() is not None and len(label.viewText()) > 511:
808
+ if label.role != XbrlConst.documentationLabel and label.viewText() is not None and len(label.viewText()) >= 511:
696
809
  yield Validation.warning(
697
810
  codes='EDINET.EC5700W.GFM.1.5.6',
698
811
  msg=_("The concept of '%(concept)s' has a label classified as '%(role)s' that is longer than 511 characters: %(label)s"),
@@ -701,3 +814,75 @@ def rule_gfm_1_5_6(
701
814
  label=label.viewText(),
702
815
  modelObject=label
703
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
+ )