arelle-release 2.37.46__py3-none-any.whl → 2.38.0__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.
Files changed (204) hide show
  1. arelle/BetaFeatures.py +0 -21
  2. arelle/Cntlr.py +15 -8
  3. arelle/CntlrCmdLine.py +121 -56
  4. arelle/CntlrWinMain.py +143 -70
  5. arelle/DialogFind.py +1 -1
  6. arelle/DialogPluginManager.py +6 -4
  7. arelle/DisclosureSystem.py +7 -0
  8. arelle/ErrorManager.py +21 -6
  9. arelle/FileSource.py +11 -4
  10. arelle/FunctionIxt.py +16 -11
  11. arelle/HtmlUtil.py +5 -4
  12. arelle/LeiUtil.py +63 -43
  13. arelle/ModelDocument.py +20 -15
  14. arelle/ModelDtsObject.py +8 -0
  15. arelle/ModelInstanceObject.py +1 -1
  16. arelle/ModelObject.py +16 -18
  17. arelle/ModelObjectFactory.py +35 -17
  18. arelle/ModelXbrl.py +28 -11
  19. arelle/PluginManager.py +130 -105
  20. arelle/RuntimeOptions.py +1 -0
  21. arelle/UrlUtil.py +14 -0
  22. arelle/Validate.py +17 -12
  23. arelle/ValidateDuplicateFacts.py +3 -1
  24. arelle/ValidateFileSource.py +38 -0
  25. arelle/ValidateFilingText.py +3 -3
  26. arelle/ValidateXbrl.py +5 -2
  27. arelle/ValidateXbrlCalcs.py +210 -186
  28. arelle/ValidateXbrlDTS.py +1 -1
  29. arelle/ViewFile.py +1 -0
  30. arelle/ViewFileFactTable.py +2 -2
  31. arelle/ViewWinDTS.py +4 -1
  32. arelle/WebCache.py +28 -24
  33. arelle/XbrlConst.py +22 -0
  34. arelle/XmlUtil.py +16 -21
  35. arelle/XmlValidate.py +6 -9
  36. arelle/_version.py +16 -3
  37. arelle/api/Session.py +11 -2
  38. arelle/config/disclosuresystems.xsd +2 -0
  39. arelle/config/rosettaEntitlements.plist +8 -0
  40. arelle/conformance/CSVTestcaseLoader.py +1 -1
  41. arelle/formula/XPathContext.py +3 -3
  42. arelle/logging/formatters/LogFormatter.py +3 -1
  43. arelle/packages/report/ReportPackage.py +26 -13
  44. arelle/packages/report/ReportPackageConst.py +0 -1
  45. arelle/plugin/inlineXbrlDocumentSet.py +19 -5
  46. arelle/plugin/validate/DBA/DisclosureSystems.py +19 -1
  47. arelle/plugin/validate/DBA/PluginValidationDataExtension.py +2 -4
  48. arelle/plugin/validate/DBA/ValidationPluginExtension.py +2 -1
  49. arelle/plugin/validate/DBA/resources/config.xml +5 -0
  50. arelle/plugin/validate/DBA/rules/__init__.py +2 -2
  51. arelle/plugin/validate/DBA/rules/fr.py +19 -2
  52. arelle/plugin/validate/DBA/rules/tc.py +2 -0
  53. arelle/plugin/validate/DBA/rules/th.py +6 -0
  54. arelle/plugin/validate/DBA/rules/tm.py +18 -5
  55. arelle/plugin/validate/DBA/rules/tr.py +11 -5
  56. arelle/plugin/validate/EDINET/Constants.py +193 -9
  57. arelle/plugin/validate/EDINET/ContextRequirement.py +58 -0
  58. arelle/plugin/validate/EDINET/ControllerPluginData.py +220 -1
  59. arelle/plugin/validate/EDINET/CoverItemRequirements.py +42 -0
  60. arelle/plugin/validate/EDINET/DeiRequirements.py +118 -0
  61. arelle/plugin/validate/EDINET/FilingFormat.py +275 -0
  62. arelle/plugin/validate/EDINET/FormType.py +134 -0
  63. arelle/plugin/validate/EDINET/ManifestInstance.py +72 -5
  64. arelle/plugin/validate/EDINET/NamespaceConfig.py +50 -0
  65. arelle/plugin/validate/EDINET/PluginValidationDataExtension.py +493 -132
  66. arelle/plugin/validate/EDINET/{InstanceType.py → ReportFolderType.py} +72 -15
  67. arelle/plugin/validate/EDINET/Statement.py +139 -0
  68. arelle/plugin/validate/EDINET/TableOfContentsBuilder.py +595 -0
  69. arelle/plugin/validate/EDINET/UploadContents.py +48 -0
  70. arelle/plugin/validate/EDINET/ValidationPluginExtension.py +20 -2
  71. arelle/plugin/validate/EDINET/__init__.py +31 -6
  72. arelle/plugin/validate/EDINET/resources/config.xml +8 -1
  73. arelle/plugin/validate/EDINET/resources/cover-item-requirements.json +793 -0
  74. arelle/plugin/validate/EDINET/resources/dei-requirements.csv +27 -0
  75. arelle/plugin/validate/EDINET/resources/edinet-taxonomies.xml +2 -0
  76. arelle/plugin/validate/EDINET/rules/contexts.py +375 -14
  77. arelle/plugin/validate/EDINET/rules/edinet.py +1934 -45
  78. arelle/plugin/validate/EDINET/rules/frta.py +122 -3
  79. arelle/plugin/validate/EDINET/rules/gfm.py +1907 -11
  80. arelle/plugin/validate/EDINET/rules/upload.py +989 -141
  81. arelle/plugin/validate/ESEF/Const.py +3 -1
  82. arelle/plugin/validate/ESEF/ESEF_2021/DTS.py +5 -0
  83. arelle/plugin/validate/ESEF/ESEF_2021/Image.py +2 -2
  84. arelle/plugin/validate/ESEF/ESEF_2021/ValidateXbrlFinally.py +23 -20
  85. arelle/plugin/validate/ESEF/ESEF_Current/DTS.py +47 -14
  86. arelle/plugin/validate/ESEF/ESEF_Current/ValidateXbrlFinally.py +100 -25
  87. arelle/plugin/validate/ESEF/__init__.py +20 -6
  88. arelle/plugin/validate/ESEF/resources/authority-validations.json +76 -9
  89. arelle/plugin/validate/ESEF/resources/config.xml +20 -0
  90. arelle/plugin/validate/NL/DisclosureSystems.py +22 -0
  91. arelle/plugin/validate/NL/PluginValidationDataExtension.py +27 -9
  92. arelle/plugin/validate/NL/ValidationPluginExtension.py +51 -7
  93. arelle/plugin/validate/NL/resources/config.xml +18 -0
  94. arelle/plugin/validate/NL/rules/br_kvk.py +17 -61
  95. arelle/plugin/validate/NL/rules/fg_nl.py +7 -38
  96. arelle/plugin/validate/NL/rules/fr_kvk.py +7 -42
  97. arelle/plugin/validate/NL/rules/fr_nl.py +31 -147
  98. arelle/plugin/validate/NL/rules/nl_kvk.py +142 -28
  99. arelle/plugin/validate/ROS/PluginValidationDataExtension.py +2 -0
  100. arelle/plugin/validate/ROS/ValidationPluginExtension.py +4 -1
  101. arelle/plugin/validate/ROS/rules/ros.py +41 -9
  102. arelle/plugin/validate/UK/ValidateUK.py +130 -66
  103. arelle/plugin/validate/UK/__init__.py +89 -103
  104. arelle/utils/EntryPointDetection.py +79 -13
  105. arelle/utils/PluginHooks.py +125 -0
  106. arelle/utils/validate/ESEFImage.py +6 -6
  107. arelle/utils/validate/Validation.py +18 -0
  108. arelle/utils/validate/ValidationPlugin.py +76 -11
  109. arelle/utils/validate/ValidationUtil.py +35 -3
  110. {arelle_release-2.37.46.dist-info → arelle_release-2.38.0.dist-info}/METADATA +30 -20
  111. {arelle_release-2.37.46.dist-info → arelle_release-2.38.0.dist-info}/RECORD +115 -191
  112. {arelle_release-2.37.46.dist-info → arelle_release-2.38.0.dist-info}/licenses/LICENSE.md +0 -3
  113. arelle/archive/CustomLogger.py +0 -43
  114. arelle/archive/LoadEFMvalidate.py +0 -32
  115. arelle/archive/LoadSavePreLbCsv.py +0 -26
  116. arelle/archive/LoadValidate.cs +0 -31
  117. arelle/archive/LoadValidate.py +0 -36
  118. arelle/archive/LoadValidateCmdLine.java +0 -69
  119. arelle/archive/LoadValidatePostedZip.java +0 -57
  120. arelle/archive/LoadValidateWebService.java +0 -34
  121. arelle/archive/SaveTableToExelle.py +0 -140
  122. arelle/archive/TR3toTR4.py +0 -88
  123. arelle/archive/plugin/ESEF_2022/__init__.py +0 -47
  124. arelle/archive/plugin/bigInstance.py +0 -394
  125. arelle/archive/plugin/cmdWebServerExtension.py +0 -43
  126. arelle/archive/plugin/crashTest.py +0 -38
  127. arelle/archive/plugin/functionsXmlCreation.py +0 -106
  128. arelle/archive/plugin/hello_i18n.pot +0 -26
  129. arelle/archive/plugin/hello_i18n.py +0 -32
  130. arelle/archive/plugin/importTestChild1.py +0 -21
  131. arelle/archive/plugin/importTestChild2.py +0 -22
  132. arelle/archive/plugin/importTestGrandchild1.py +0 -21
  133. arelle/archive/plugin/importTestGrandchild2.py +0 -21
  134. arelle/archive/plugin/importTestImported1.py +0 -23
  135. arelle/archive/plugin/importTestImported11.py +0 -22
  136. arelle/archive/plugin/importTestParent.py +0 -48
  137. arelle/archive/plugin/instanceInfo.py +0 -306
  138. arelle/archive/plugin/loadFromOIM-2018.py +0 -1282
  139. arelle/archive/plugin/locale/fr/LC_MESSAGES/hello_i18n.po +0 -25
  140. arelle/archive/plugin/objectmaker.py +0 -285
  141. arelle/archive/plugin/packagedImportTest/__init__.py +0 -47
  142. arelle/archive/plugin/packagedImportTest/importTestChild1.py +0 -21
  143. arelle/archive/plugin/packagedImportTest/importTestChild2.py +0 -22
  144. arelle/archive/plugin/packagedImportTest/importTestGrandchild1.py +0 -21
  145. arelle/archive/plugin/packagedImportTest/importTestGrandchild2.py +0 -21
  146. arelle/archive/plugin/packagedImportTest/importTestImported1.py +0 -24
  147. arelle/archive/plugin/packagedImportTest/importTestImported11.py +0 -21
  148. arelle/archive/plugin/packagedImportTest/subdir/importTestImported111.py +0 -21
  149. arelle/archive/plugin/packagedImportTest/subdir/subsubdir/importTestImported1111.py +0 -21
  150. arelle/archive/plugin/sakaCalendar.py +0 -215
  151. arelle/archive/plugin/saveInstanceInfoset.py +0 -121
  152. arelle/archive/plugin/sphinx/FormulaGenerator.py +0 -823
  153. arelle/archive/plugin/sphinx/SphinxContext.py +0 -404
  154. arelle/archive/plugin/sphinx/SphinxEvaluator.py +0 -783
  155. arelle/archive/plugin/sphinx/SphinxMethods.py +0 -1287
  156. arelle/archive/plugin/sphinx/SphinxParser.py +0 -1093
  157. arelle/archive/plugin/sphinx/SphinxValidator.py +0 -163
  158. arelle/archive/plugin/sphinx/US-GAAP Ratios Example.xsr +0 -52
  159. arelle/archive/plugin/sphinx/__init__.py +0 -285
  160. arelle/archive/plugin/streamingExtensions.py +0 -335
  161. arelle/archive/plugin/updateTableLB.py +0 -242
  162. arelle/archive/plugin/validate/SBRnl/CustomLoader.py +0 -19
  163. arelle/archive/plugin/validate/SBRnl/DTS.py +0 -305
  164. arelle/archive/plugin/validate/SBRnl/Dimensions.py +0 -357
  165. arelle/archive/plugin/validate/SBRnl/Document.py +0 -799
  166. arelle/archive/plugin/validate/SBRnl/Filing.py +0 -467
  167. arelle/archive/plugin/validate/SBRnl/__init__.py +0 -75
  168. arelle/archive/plugin/validate/SBRnl/config.xml +0 -26
  169. arelle/archive/plugin/validate/SBRnl/sbr-nl-taxonomies.xml +0 -754
  170. arelle/archive/plugin/validate/USBestPractices.py +0 -570
  171. arelle/archive/plugin/validate/USCorpAction.py +0 -557
  172. arelle/archive/plugin/validate/USSecTagging.py +0 -337
  173. arelle/archive/plugin/validate/XDC/__init__.py +0 -77
  174. arelle/archive/plugin/validate/XDC/config.xml +0 -20
  175. arelle/archive/plugin/validate/XFsyntax/__init__.py +0 -64
  176. arelle/archive/plugin/validate/XFsyntax/xf.py +0 -2227
  177. arelle/archive/plugin/validate/calc2.py +0 -536
  178. arelle/archive/plugin/validateSchemaLxml.py +0 -156
  179. arelle/archive/plugin/validateTableInfoset.py +0 -52
  180. arelle/archive/us-gaap-dei-docType-extraction-frm.xml +0 -90
  181. arelle/archive/us-gaap-dei-ratio-cash-frm.xml +0 -150
  182. arelle/examples/plugin/formulaSuiteConverter.py +0 -212
  183. arelle/examples/plugin/functionsCustom.py +0 -59
  184. arelle/examples/plugin/hello_dolly.py +0 -64
  185. arelle/examples/plugin/multi.py +0 -58
  186. arelle/examples/plugin/rssSaveOim.py +0 -96
  187. arelle/examples/plugin/validate/XYZ/DisclosureSystems.py +0 -2
  188. arelle/examples/plugin/validate/XYZ/PluginValidationDataExtension.py +0 -10
  189. arelle/examples/plugin/validate/XYZ/ValidationPluginExtension.py +0 -49
  190. arelle/examples/plugin/validate/XYZ/__init__.py +0 -75
  191. arelle/examples/plugin/validate/XYZ/resources/config.xml +0 -16
  192. arelle/examples/plugin/validate/XYZ/rules/__init__.py +0 -0
  193. arelle/examples/plugin/validate/XYZ/rules/rules01.py +0 -110
  194. arelle/examples/plugin/validate/XYZ/rules/rules02.py +0 -59
  195. arelle/model/CommentBase.py +0 -9
  196. arelle/model/ElementBase.py +0 -11
  197. arelle/model/PIBase.py +0 -10
  198. arelle/model/__init__.py +0 -15
  199. arelle/scripts-macOS/startWebServer.command +0 -3
  200. arelle/scripts-unix/startWebServer.sh +0 -1
  201. arelle/scripts-windows/startWebServer.bat +0 -5
  202. {arelle_release-2.37.46.dist-info → arelle_release-2.38.0.dist-info}/WHEEL +0 -0
  203. {arelle_release-2.37.46.dist-info → arelle_release-2.38.0.dist-info}/entry_points.txt +0 -0
  204. {arelle_release-2.37.46.dist-info → arelle_release-2.38.0.dist-info}/top_level.txt +0 -0
@@ -3,124 +3,441 @@ See COPYRIGHT.md for copyright information.
3
3
  """
4
4
  from __future__ import annotations
5
5
 
6
- import zipfile
7
6
  from collections import defaultdict
7
+ from collections.abc import Callable, Hashable, Iterable
8
8
  from dataclasses import dataclass
9
+ from decimal import Decimal
9
10
  from functools import lru_cache
10
11
  from pathlib import Path
11
12
 
12
- import regex
13
+ from lxml.etree import DTD, XML
14
+ from operator import attrgetter
15
+ from typing import cast
13
16
 
14
- from arelle.ModelDocument import Type as ModelDocumentType
15
- from arelle.ModelInstanceObject import ModelFact
17
+ import os
18
+
19
+ from arelle import XbrlConst
20
+ from arelle.LinkbaseType import LinkbaseType
21
+ from arelle.ModelDocument import Type as ModelDocumentType, ModelDocument, load as modelDocumentLoad
22
+ from arelle.ModelDtsObject import ModelConcept
23
+ from arelle.ModelInstanceObject import ModelFact, ModelUnit, ModelContext, ModelInlineFact
16
24
  from arelle.ModelObject import ModelObject
17
25
  from arelle.ModelValue import QName, qname
18
26
  from arelle.ModelXbrl import ModelXbrl
19
27
  from arelle.PrototypeDtsObject import LinkPrototype
20
28
  from arelle.ValidateDuplicateFacts import getDeduplicatedFacts, DeduplicationType
21
29
  from arelle.ValidateXbrl import ValidateXbrl
30
+ from arelle.XhtmlValidate import htmlEltUriAttrs
22
31
  from arelle.XmlValidate import VALID
23
32
  from arelle.typing import TypeGetText
24
33
  from arelle.utils.PluginData import PluginData
25
- from .Constants import CORPORATE_FORMS
34
+ from .Constants import xhtmlDtdExtension, PROHIBITED_HTML_TAGS, PROHIBITED_HTML_ATTRIBUTES
26
35
  from .ControllerPluginData import ControllerPluginData
27
- from .InstanceType import InstanceType
36
+ from .DeiRequirements import DeiRequirements, DEI_LOCAL_NAMES
37
+ from .FilingFormat import FilingFormat, FILING_FORMATS, DocumentType, Ordinance
38
+ from .FormType import FormType
28
39
  from .ManifestInstance import ManifestInstance
40
+ from .NamespaceConfig import NamespaceConfig
41
+ from .ReportFolderType import HTML_EXTENSIONS
42
+ from .Statement import Statement, STATEMENTS, BalanceSheet, StatementInstance, StatementType
43
+ from .UploadContents import UploadContents, UploadPathInfo
29
44
 
30
45
  _: TypeGetText
31
46
 
32
47
 
48
+ STANDARD_TAXONOMY_URL_PREFIXES = frozenset((
49
+ 'http://disclosure.edinet-fsa.go.jp/taxonomy/',
50
+ 'https://disclosure.edinet-fsa.go.jp/taxonomy/',
51
+ 'http://www.xbrl.org/20',
52
+ 'https://www.xbrl.org/20',
53
+ 'http://www.xbrl.org/lrr/',
54
+ 'https://www.xbrl.org/lrr/',
55
+ 'http://xbrl.org/20',
56
+ 'https://xbrl.org/20',
57
+ 'http://www.xbrl.org/dtr/',
58
+ 'https://www.xbrl.org/dtr/',
59
+ 'http://www.w3.org/1999/xlink',
60
+ 'https://www.w3.org/1999/xlink'
61
+ ))
62
+
63
+
33
64
  @dataclass(frozen=True)
34
- class UploadContents:
35
- amendmentPaths: dict[InstanceType, frozenset[Path]]
36
- directories: frozenset[Path]
37
- instances: dict[InstanceType, frozenset[Path]]
38
- unknownPaths: frozenset[Path]
65
+ class UriReference:
66
+ attributeName: str
67
+ attributeValue: str
68
+ document: ModelDocument
69
+ element: ModelObject
39
70
 
40
71
 
41
72
  @dataclass
42
73
  class PluginValidationDataExtension(PluginData):
74
+ namespaces: NamespaceConfig
75
+
76
+ accountingStandardsDeiQn: QName
43
77
  assetsIfrsQn: QName
78
+ categoriesOfDirectorsAndOtherOfficersAxisQn: QName
79
+ consolidatedOrNonConsolidatedAxisQn: QName
80
+ corporateGovernanceCompanyWithAuditAndSupervisoryCommitteeTextBlockQn: QName
81
+ corporateGovernanceCompanyWithCorporateAuditorsTextBlockQn: QName
82
+ corporateGovernanceCompanyWithNominatingAndOtherCommitteesTextBlockQn: QName
44
83
  documentTypeDeiQn: QName
84
+ executiveOfficersMemberQn: QName
45
85
  jpcrpEsrFilingDateCoverPageQn: QName
46
86
  jpcrpFilingDateCoverPageQn: QName
47
87
  jpspsFilingDateCoverPageQn: QName
48
- liabilitiesAndEquityIfrsQn: QName
88
+ issuedSharesTotalNumberOfSharesEtcQn: QName
49
89
  nonConsolidatedMemberQn: QName
50
90
  ratioOfFemaleDirectorsAndOtherOfficersQn: QName
51
91
 
52
- contextIdPattern: regex.Pattern[str]
92
+ coverItemRequirementsPath: Path
93
+ coverPageTitleQns: tuple[QName, ...]
94
+ deiItems: tuple[QName, ...]
95
+ deiRequirementsPath: Path
53
96
 
54
- _primaryModelXbrl: ModelXbrl | None = None
97
+ _uriReferences: list[UriReference]
55
98
 
56
- def __init__(self, name: str):
99
+ def __init__(self, name: str, validateXbrl: ValidateXbrl):
57
100
  super().__init__(name)
58
- jpcrpEsrNamespace = "http://disclosure.edinet-fsa.go.jp/taxonomy/jpcrp-esr/2024-11-01/jpcrp-esr_cor"
59
- jpcrpNamespace = 'http://disclosure.edinet-fsa.go.jp/taxonomy/jpcrp/2024-11-01/jpcrp_cor'
60
- jpdeiNamespace = 'http://disclosure.edinet-fsa.go.jp/taxonomy/jpdei/2013-08-31/jpdei_cor'
61
- jpigpNamespace = "http://disclosure.edinet-fsa.go.jp/taxonomy/jpigp/2024-11-01/jpigp_cor"
62
- jppfsNamespace = "http://disclosure.edinet-fsa.go.jp/taxonomy/jppfs/2024-11-01/jppfs_cor"
63
- jpspsNamespace = 'http://disclosure.edinet-fsa.go.jp/taxonomy/jpsps/2024-11-01/jpsps_cor'
64
- self.assetsIfrsQn = qname(jpigpNamespace, 'AssetsIFRS')
65
- self.documentTypeDeiQn = qname(jpdeiNamespace, 'DocumentTypeDEI')
66
- self.jpcrpEsrFilingDateCoverPageQn = qname(jpcrpEsrNamespace, 'FilingDateCoverPage')
67
- self.jpcrpFilingDateCoverPageQn = qname(jpcrpNamespace, 'FilingDateCoverPage')
68
- self.jpspsFilingDateCoverPageQn = qname(jpspsNamespace, 'FilingDateCoverPage')
69
- self.liabilitiesAndEquityIfrsQn = qname(jpigpNamespace, "LiabilitiesAndEquityIFRS")
70
- self.nonConsolidatedMemberQn = qname(jppfsNamespace, "NonConsolidatedMember")
71
- self.ratioOfFemaleDirectorsAndOtherOfficersQn = qname(jpcrpNamespace, "RatioOfFemaleDirectorsAndOtherOfficers")
72
-
73
- self.contextIdPattern = regex.compile(r'(Prior[1-9]Year|CurrentYear|Prior[1-9]Interim|Interim)(Duration|Instant)')
101
+
102
+ self.namespaces = NamespaceConfig()
103
+
104
+ # QNames
105
+ self.accountingStandardsDeiQn = qname(self.namespaces.jpdei, 'AccountingStandardsDEI')
106
+ self.assetsIfrsQn = qname(self.namespaces.jpigp, 'AssetsIFRS')
107
+ self.categoriesOfDirectorsAndOtherOfficersAxisQn = qname(self.namespaces.jpcrp, 'CategoriesOfDirectorsAndOtherOfficersAxis')
108
+ self.consolidatedOrNonConsolidatedAxisQn = qname(self.namespaces.jppfs, 'ConsolidatedOrNonConsolidatedAxis')
109
+ self.corporateGovernanceCompanyWithAuditAndSupervisoryCommitteeTextBlockQn = qname(self.namespaces.jpcrp, 'CorporateGovernanceCompanyWithAuditAndSupervisoryCommitteeTextBlock')
110
+ self.corporateGovernanceCompanyWithCorporateAuditorsTextBlockQn = qname(self.namespaces.jpcrp, 'CorporateGovernanceCompanyWithCorporateAuditorsTextBlock')
111
+ self.corporateGovernanceCompanyWithNominatingAndOtherCommitteesTextBlockQn = qname(self.namespaces.jpcrp, 'CorporateGovernanceCompanyWithNominatingAndOtherCommitteesTextBlock')
112
+ self.documentTypeDeiQn = qname(self.namespaces.jpdei, 'DocumentTypeDEI')
113
+ self.executiveOfficersMemberQn = qname(self.namespaces.jpcrp, 'ExecutiveOfficersMember')
114
+ self.issuedSharesTotalNumberOfSharesEtcQn = qname(self.namespaces.jpcrp, 'IssuedSharesTotalNumberOfSharesEtcTextBlock')
115
+ self.jpcrpEsrFilingDateCoverPageQn = qname(self.namespaces.jpcrpEsr, 'FilingDateCoverPage')
116
+ self.jpcrpFilingDateCoverPageQn = qname(self.namespaces.jpcrp, 'FilingDateCoverPage')
117
+ self.jpspsFilingDateCoverPageQn = qname(self.namespaces.jpsps, 'FilingDateCoverPage')
118
+ self.nonConsolidatedMemberQn = qname(self.namespaces.jppfs, "NonConsolidatedMember")
119
+ self.ratioOfFemaleDirectorsAndOtherOfficersQn = qname(self.namespaces.jpcrp, "RatioOfFemaleDirectorsAndOtherOfficers")
120
+
121
+ self.coverItemRequirementsPath = Path(__file__).parent / "resources" / "cover-item-requirements.json"
122
+ self.coverPageTitleQns = (
123
+ qname(self.namespaces.jpsps, "DocumentTitleAnnualSecuritiesReportCoverPage"),
124
+ qname(self.namespaces.jpcrp, "DocumentTitleCoverPage"),
125
+ qname(self.namespaces.jpcrpEsr, "DocumentTitleCoverPage"),
126
+ qname(self.namespaces.jpsps, "DocumentTitleCoverPage"),
127
+ )
128
+ self.deiItems = tuple(
129
+ qname(self.namespaces.jpdei, localName)
130
+ for localName in DEI_LOCAL_NAMES
131
+ )
132
+ self.deiRequirementsPath = Path(__file__).parent / "resources" / "dei-requirements.csv"
133
+
134
+ self._uriReferences = []
135
+ self._initialize(validateXbrl.modelXbrl)
74
136
 
75
137
  # Identity hash for caching.
76
138
  def __hash__(self) -> int:
77
139
  return id(self)
78
140
 
141
+ @lru_cache(1)
142
+ def _contextMatchesStatement(self, modelXbrl: ModelXbrl, contextId: str, statement: Statement) -> bool:
143
+ """
144
+ :return: Whether the context's facts are applicable to the given statement.
145
+ """
146
+ if 'Interim' in contextId:
147
+ # valid06.zip suggests "interim"" contexts are not considered for balance sheets.
148
+ return False
149
+ context = modelXbrl.contexts[contextId]
150
+ if not all(dimQn == self.consolidatedOrNonConsolidatedAxisQn for dimQn in context.qnameDims):
151
+ return False
152
+ memberValue = context.dimMemberQname(self.consolidatedOrNonConsolidatedAxisQn, includeDefaults=True)
153
+ contextIsConsolidated = memberValue != self.nonConsolidatedMemberQn
154
+ return bool(statement.isConsolidated == contextIsConsolidated)
155
+
156
+ def _initializeDocument(self, uri: str, modelDocument: ModelDocument, modelXbrl: ModelXbrl) -> None:
157
+ docPath = Path(uri)
158
+ basePath = Path(str(modelXbrl.fileSource.basefile))
159
+ if not docPath.is_relative_to(basePath):
160
+ return
161
+ controllerPluginData = ControllerPluginData.get(modelXbrl.modelManager.cntlr, self.name)
162
+ controllerPluginData.addUsedFilepath(docPath.relative_to(basePath))
163
+ for elt, name, value in self.getUriAttributeValues(modelDocument):
164
+ self._uriReferences.append(UriReference(
165
+ attributeName=name,
166
+ attributeValue=value,
167
+ document=modelDocument,
168
+ element=elt,
169
+ ))
170
+ fullPath = (Path(modelDocument.uri).parent / value).resolve()
171
+ if fullPath.is_relative_to(basePath):
172
+ fileSourcePath = fullPath.relative_to(basePath)
173
+ controllerPluginData.addUsedFilepath(fileSourcePath)
174
+ referenceUri = str(fullPath)
175
+ if (
176
+ fullPath.suffix in HTML_EXTENSIONS and
177
+ referenceUri not in modelXbrl.urlDocs and
178
+ modelXbrl.fileSource.exists(referenceUri)
179
+ ):
180
+ referenceModelDocument = modelDocumentLoad(
181
+ modelXbrl,
182
+ referenceUri,
183
+ referringElement=elt
184
+ )
185
+ if referenceModelDocument is not None:
186
+ self._initializeDocument(referenceUri, referenceModelDocument, modelXbrl)
187
+
188
+ def _initialize(self, modelXbrl: ModelXbrl) -> None:
189
+ if not isinstance(modelXbrl.fileSource.basefile, str):
190
+ return
191
+ # Additional documents may be loaded, so make a copy to iterate over.
192
+ urlDocs = list(modelXbrl.urlDocs.items())
193
+ for uri, modelDocument in urlDocs:
194
+ self._initializeDocument(uri, modelDocument, modelXbrl)
195
+
196
+ def addToTableOfContents(self, modelXbrl: ModelXbrl) -> None:
197
+ uploadContents = self.getUploadContents(modelXbrl)
198
+ if uploadContents is None:
199
+ return
200
+ controllerPluginData = ControllerPluginData.get(modelXbrl.modelManager.cntlr, self.name)
201
+ tocBuilder = controllerPluginData.getTableOfContentsBuilder()
202
+ for modelDocument in modelXbrl.urlDocs.values():
203
+ path = Path(modelDocument.uri)
204
+ if modelDocument.type != ModelDocumentType.INLINEXBRL:
205
+ continue
206
+ pathInfo = uploadContents.uploadPathsByFullPath.get(path)
207
+ if pathInfo is not None and not pathInfo.isCoverPage:
208
+ tocBuilder.addDocument(modelDocument)
209
+
210
+ def qname(self, prefix: str, localName: str) -> QName:
211
+ ns = self.namespaces.get(prefix)
212
+ assert ns is not None, f"Unknown namespace prefix: {prefix}"
213
+ return qname(ns, localName)
214
+
79
215
  @lru_cache(1)
80
216
  def isCorporateForm(self, modelXbrl: ModelXbrl) -> bool:
81
- documentTypes = self.getDocumentTypes(modelXbrl)
82
- if any(documentType == form.value for form in CORPORATE_FORMS for documentType in documentTypes):
217
+ formType = self.getFormType(modelXbrl)
218
+ if formType is None:
219
+ return False
220
+ return formType.isCorporateForm
221
+
222
+ def isCorporateReport(self, modelXbrl: ModelXbrl) -> bool:
223
+ return self.namespaces.jpcrp in modelXbrl.namespaceDocs
224
+
225
+ def isExtensionUri(self, uri: str, modelXbrl: ModelXbrl) -> bool:
226
+ if uri.startswith(modelXbrl.uriDir):
83
227
  return True
84
- return False
228
+ return not any(uri.startswith(taxonomyUri) for taxonomyUri in STANDARD_TAXONOMY_URL_PREFIXES)
229
+
230
+ @lru_cache(1)
231
+ def isStockForm(self, modelXbrl: ModelXbrl) -> bool:
232
+ formType = self.getFormType(modelXbrl)
233
+ if formType is None:
234
+ return False
235
+ return formType.isStockReport
85
236
 
86
237
  @lru_cache(1)
87
- def shouldValidateUpload(self, val: ValidateXbrl) -> bool:
238
+ def getExtensionConcepts(self, modelXbrl: ModelXbrl) -> list[ModelConcept]:
88
239
  """
89
- Determine if the upload validation should be performed on this model.
240
+ Returns a list of extension concepts in the DTS.
241
+ """
242
+ extensionConcepts = []
243
+ for concepts in modelXbrl.nameConcepts.values():
244
+ for concept in concepts:
245
+ if self.isExtensionUri(concept.document.uri, modelXbrl):
246
+ extensionConcepts.append(concept)
247
+ return extensionConcepts
90
248
 
91
- Upload validation should not be performed if the target document is
92
- not a zipfile.
249
+ @lru_cache(1)
250
+ def getUsedConcepts(self, modelXbrl: ModelXbrl) -> set[ModelConcept]:
251
+ """
252
+ Returns a set of concepts used on facts and in explicit dimensions
253
+ """
254
+ usedConcepts = {fact.concept for fact in modelXbrl.facts if fact.concept is not None}
255
+ for context in modelXbrl.contextsInUse:
256
+ for dim in context.scenDimValues.values():
257
+ if dim.isExplicit:
258
+ usedConcepts.update([dim.dimension, dim.member])
259
+ return usedConcepts
93
260
 
94
- Upload validation should only be performed once for the entire package,
95
- not duplicated for each model. To facilitate this with Arelle's validation
96
- system which largely prevents referencing other models, we can use `--keepOpen`
97
- and check if the given model is the first to be loaded.
98
- :param val: The ValidateXbrl instance with a model to check.
99
- :return: True if upload validation should be performed, False otherwise.
261
+ def getBalanceSheets(self, modelXbrl: ModelXbrl, statement: Statement) -> list[BalanceSheet]:
262
+ """
263
+ :return: Balance sheet data for each context/unit pairing the given statement.
100
264
  """
101
- modelXbrl = val.modelXbrl
102
- if modelXbrl == val.testModelXbrl:
103
- # Not running within a testcase
104
- if modelXbrl != modelXbrl.modelManager.loadedModelXbrls[0]:
105
- return False
106
- if not modelXbrl.fileSource.fs:
107
- return False # No stream
108
- if not isinstance(modelXbrl.fileSource.fs, zipfile.ZipFile):
109
- return False # Not a zipfile
110
- return True
265
+ balanceSheets: list[BalanceSheet] = []
266
+ if statement.roleUri not in modelXbrl.roleTypes:
267
+ return balanceSheets
268
+ if statement.statementType not in (
269
+ StatementType.BALANCE_SHEET,
270
+ StatementType.STATEMENT_OF_FINANCIAL_POSITION
271
+ ):
272
+ return balanceSheets
273
+
274
+ relSet = modelXbrl.relationshipSet(
275
+ tuple(LinkbaseType.CALCULATION.getArcroles()),
276
+ linkrole=statement.roleUri
277
+ )
278
+ rootConcepts = relSet.rootConcepts
279
+ if len(rootConcepts) == 0:
280
+ return balanceSheets
281
+
282
+ # GFM 1.2.7 and 1.2.10 asserts no duplicate contexts and units, respectively,
283
+ # so context and unit IDs can be used as a key.
284
+ factsByContextIdAndUnitId = self.getFactsByContextAndUnit(
285
+ modelXbrl,
286
+ attrgetter("id"),
287
+ attrgetter("id"),
288
+ tuple(concept.qname for concept in rootConcepts)
289
+ )
290
+
291
+ for (contextId, unitId), facts in factsByContextIdAndUnitId.items():
292
+ if not self._contextMatchesStatement(modelXbrl, contextId, statement):
293
+ continue
294
+ creditSum = Decimal(0)
295
+ debitSum = Decimal(0)
296
+ for fact in facts:
297
+ if isinstance(fact.xValue, float):
298
+ value = Decimal(fact.xValue)
299
+ else:
300
+ value = cast(Decimal, fact.xValue)
301
+ if fact.concept.balance == "debit":
302
+ debitSum += value
303
+ elif fact.concept.balance == "credit":
304
+ creditSum += value
305
+ balanceSheets.append(
306
+ BalanceSheet(
307
+ creditSum=creditSum,
308
+ contextId=str(contextId),
309
+ facts=facts,
310
+ debitSum=debitSum,
311
+ unitId=str(unitId),
312
+ )
313
+ )
314
+ return balanceSheets
315
+
316
+ @lru_cache(1)
317
+ def getCoverItemRequirements(self, modelXbrl: ModelXbrl) -> list[QName] | None:
318
+ manifestInstance = self.getManifestInstance(modelXbrl)
319
+ if manifestInstance is None:
320
+ return None
321
+ if any(e is not None and e.startswith('EDINET.EC5800E') for e in modelXbrl.errors):
322
+ # Manifest TOC parsing failed, so cannot determine cover items.
323
+ return None
324
+ assert len(manifestInstance.tocItems) == 1, _("Only one TOC item should be associated with this instance.")
325
+ roleUri = manifestInstance.tocItems[0].extrole
326
+ roleUri = roleUri.replace("_std_", "_")
327
+ controllerPluginData = ControllerPluginData.get(modelXbrl.modelManager.cntlr, self.name)
328
+ coverItemRequirements = controllerPluginData.getCoverItemRequirements(self.coverItemRequirementsPath)
329
+ coverItems = coverItemRequirements.get(roleUri)
330
+ return [
331
+ self.qname(prefix, localName)
332
+ for prefix, localName in
333
+ [name.split(':') for name in coverItems]
334
+ ]
335
+
336
+ @lru_cache(1)
337
+ def getCoverItems(self, modelXbrl: ModelXbrl) -> frozenset[QName]:
338
+ controllerPluginData = ControllerPluginData.get(modelXbrl.modelManager.cntlr, self.name)
339
+ coverItemRequirements = controllerPluginData.getCoverItemRequirements(self.coverItemRequirementsPath)
340
+ coverItems = coverItemRequirements.all()
341
+ return frozenset(
342
+ self.qname(prefix, localName)
343
+ for prefix, localName in
344
+ [name.split(':') for name in coverItems]
345
+ )
346
+
347
+ def getDeiRequirements(self, modelXbrl: ModelXbrl) -> DeiRequirements:
348
+ controllerPluginData = ControllerPluginData.get(modelXbrl.modelManager.cntlr, self.name)
349
+ return controllerPluginData.getDeiRequirements(self.deiRequirementsPath, self.deiItems, FILING_FORMATS)
350
+
351
+ @lru_cache(1)
352
+ def getExtensionSchemas(self, modelXbrl: ModelXbrl) -> dict[str, UploadPathInfo]:
353
+ namespacePathInfos: dict[str, UploadPathInfo] = {}
354
+ uploadContents = self.getUploadContents(modelXbrl)
355
+ if uploadContents is None:
356
+ return namespacePathInfos
357
+ for modelDocument in modelXbrl.urlDocs.values():
358
+ if modelDocument.type != ModelDocumentType.SCHEMA:
359
+ continue # Not a schema
360
+ if modelDocument.targetNamespace is None:
361
+ continue # No target namespace
362
+ if not self.isExtensionUri(modelDocument.uri, modelXbrl):
363
+ continue # Not an extension schema
364
+ path = Path(modelDocument.uri)
365
+ pathInfo = uploadContents.uploadPathsByFullPath.get(path)
366
+ if pathInfo is None or pathInfo.reportFolderType is None:
367
+ continue # Not part of the filing, error will be caught elsewhere
368
+ namespacePathInfos[modelDocument.targetNamespace] = pathInfo
369
+ return namespacePathInfos
370
+
371
+ def getProblematicTextBlocks(self, modelXbrl: ModelXbrl) -> list[ModelInlineFact]:
372
+ problematicTextBlocks: list[ModelInlineFact] = []
373
+ dtd = DTD(os.path.join(modelXbrl.modelManager.cntlr.configDir, xhtmlDtdExtension))
374
+ htmlBodyTemplate = "<body><div>\n{0}\n</div></body>\n"
375
+ for fact in modelXbrl.facts:
376
+ concept = fact.concept
377
+ if isinstance(fact, ModelInlineFact) and not fact.isNil and concept is not None and concept.isTextBlock and not fact.isEscaped:
378
+ xmlBody = htmlBodyTemplate.format(fact.value)
379
+ try:
380
+ textblockXml = XML(xmlBody)
381
+ if not dtd.validate(textblockXml):
382
+ problematicTextBlocks.append(fact)
383
+ except Exception:
384
+ problematicTextBlocks.append(fact)
385
+ return problematicTextBlocks
386
+
387
+ @lru_cache(1)
388
+ def getStatementInstance(self, modelXbrl: ModelXbrl, statement: Statement) -> StatementInstance | None:
389
+ if statement.roleUri not in modelXbrl.roleTypes:
390
+ return None
391
+ return StatementInstance(
392
+ balanceSheets=self.getBalanceSheets(modelXbrl, statement),
393
+ statement=statement,
394
+ )
395
+
396
+ @lru_cache(1)
397
+ def getStatementInstances(self, modelXbrl: ModelXbrl) -> list[StatementInstance]:
398
+ return [
399
+ statementInstance
400
+ for statement in STATEMENTS
401
+ if (statementInstance := self.getStatementInstance(modelXbrl, statement)) is not None
402
+ ]
403
+
404
+ @property
405
+ def uriReferences(self) -> list[UriReference]:
406
+ return self._uriReferences
111
407
 
112
408
  @lru_cache(1)
113
409
  def getDeduplicatedFacts(self, modelXbrl: ModelXbrl) -> list[ModelFact]:
114
410
  return getDeduplicatedFacts(modelXbrl, DeduplicationType.CONSISTENT_PAIRS)
115
411
 
116
412
  @lru_cache(1)
117
- def getDocumentTypes(self, modelXbrl: ModelXbrl) -> set[str]:
118
- documentFacts = modelXbrl.factsByQname.get(self.documentTypeDeiQn, set())
119
- documentTypes = set()
120
- for fact in documentFacts:
121
- if fact.xValid >= VALID:
122
- documentTypes.add(fact.textValue)
123
- return documentTypes
413
+ def getDocumentType(self, modelXbrl: ModelXbrl) -> DocumentType | None:
414
+ """
415
+ Retrieves document type value from the instance.
416
+ :param modelXbrl: Instance to get document type from.
417
+ :return: document type parsed from filename.
418
+ """
419
+ filingFormat = self.getFilingFormat(modelXbrl)
420
+ if filingFormat is None:
421
+ return None
422
+ return filingFormat.documentType
423
+
424
+ def getFactsByContextAndUnit(
425
+ self, modelXbrl: ModelXbrl,
426
+ getContextKey: Callable[[ModelContext], Hashable],
427
+ getUnitKey: Callable[[ModelUnit], Hashable],
428
+ qnames: tuple[QName, ...] | None = None,
429
+ ) -> dict[tuple[Hashable, Hashable], list[ModelFact]]:
430
+ deduplicatedFacts = self.getDeduplicatedFacts(modelXbrl)
431
+ getFactsByContextAndUnit = defaultdict(list)
432
+ for fact in deduplicatedFacts:
433
+ if qnames is not None and fact.qname not in qnames:
434
+ continue
435
+ if fact.context is None or fact.unit is None:
436
+ continue
437
+ contextKey = getContextKey(fact.context)
438
+ unitKey = getUnitKey(fact.unit)
439
+ getFactsByContextAndUnit[(contextKey, unitKey)].append(fact)
440
+ return dict(getFactsByContextAndUnit)
124
441
 
125
442
  @lru_cache(1)
126
443
  def getFootnoteLinkElements(self, modelXbrl: ModelXbrl) -> list[ModelObject | LinkPrototype]:
@@ -145,90 +462,134 @@ class PluginValidationDataExtension(PluginData):
145
462
  ]
146
463
 
147
464
  @lru_cache(1)
148
- def getManifestInstance(self, modelXbrl: ModelXbrl) -> ManifestInstance | None:
149
- controllerPluginData = ControllerPluginData.get(modelXbrl.modelManager.cntlr, self.name)
150
- return controllerPluginData.matchManifestInstance(modelXbrl.ixdsDocUrls)
465
+ def getFilingFormat(self, modelXbrl: ModelXbrl) -> FilingFormat | None:
466
+ manifestInstance = self.getManifestInstance(modelXbrl)
467
+ if manifestInstance is None:
468
+ return None
469
+ return manifestInstance.filingFormat
470
+
471
+ @lru_cache(1)
472
+ def getFormType(self, modelXbrl: ModelXbrl) -> FormType | None:
473
+ """
474
+ Retrieves form type value from the instance.
475
+ :param modelXbrl: Instance to get form type from.
476
+ :return: Form type parsed from filename.
477
+ """
478
+ filingFormat = self.getFilingFormat(modelXbrl)
479
+ if filingFormat is None:
480
+ return None
481
+ return filingFormat.formType
151
482
 
152
483
  @lru_cache(1)
153
- def getManifestInstances(self, modelXbrl: ModelXbrl) -> list[ManifestInstance]:
484
+ def getManifestInstance(self, modelXbrl: ModelXbrl) -> ManifestInstance | None:
154
485
  controllerPluginData = ControllerPluginData.get(modelXbrl.modelManager.cntlr, self.name)
155
- return controllerPluginData.getManifestInstances()
486
+ return controllerPluginData.getManifestInstance(modelXbrl)
156
487
 
157
- def getUploadFileSizes(self, modelXbrl: ModelXbrl) -> dict[Path, int]:
488
+ @lru_cache(1)
489
+ def getOrdinance(self, modelXbrl: ModelXbrl) -> Ordinance | None:
158
490
  """
159
- Get the sizes of files in the upload directory.
160
- :param modelXbrl: The ModelXbrl instance to get file sizes for.
161
- :return: A dictionary mapping file paths to their sizes.
491
+ Retrieves ordinance value from the instance.
492
+ :param modelXbrl: Instance to get ordinance from.
493
+ :return: Ordinance parsed from filename.
162
494
  """
163
- if not self.isUpload(modelXbrl):
164
- return {}
165
- assert isinstance(modelXbrl.fileSource.fs, zipfile.ZipFile)
166
- return {
167
- Path(i.filename): i.file_size
168
- for i in modelXbrl.fileSource.fs.infolist()
169
- if not i.is_dir()
170
- }
171
-
172
- @lru_cache(1)
173
- def getUploadContents(self, modelXbrl: ModelXbrl) -> UploadContents:
174
- uploadFilepaths = self.getUploadFilepaths(modelXbrl)
175
- amendmentPaths = defaultdict(list)
176
- unknownPaths = []
177
- directories = []
178
- forms = defaultdict(list)
179
- for path in uploadFilepaths:
180
- parents = list(reversed([p.name for p in path.parents if len(p.name) > 0]))
181
- if len(parents) == 0:
495
+ filingFormat = self.getFilingFormat(modelXbrl)
496
+ if filingFormat is None:
497
+ return None
498
+ return filingFormat.ordinance
499
+
500
+ @lru_cache(1)
501
+ def getProhibitedAttributeElements(self, modelDocument: ModelDocument) -> list[tuple[ModelObject, str]]:
502
+ results: list[tuple[ModelObject, str]] = []
503
+ if modelDocument.type not in (ModelDocumentType.INLINEXBRL, ModelDocumentType.HTML):
504
+ return results
505
+ for elt in modelDocument.xmlRootElement.iter():
506
+ if not isinstance(elt, ModelObject):
182
507
  continue
183
- if parents[0] == 'XBRL':
184
- if len(parents) > 1:
185
- formName = parents[1]
186
- instanceType = InstanceType.parse(formName)
187
- if instanceType is not None:
188
- forms[instanceType].append(path)
189
- continue
190
- formName = parents[0]
191
- instanceType = InstanceType.parse(formName)
192
- if instanceType is not None:
193
- amendmentPaths[instanceType].append(path)
508
+ for attributeName in elt.attrib:
509
+ if attributeName in PROHIBITED_HTML_ATTRIBUTES:
510
+ results.append((elt, str(attributeName)))
511
+ return results
512
+
513
+ @lru_cache(1)
514
+ def getProhibitedTagElements(self, modelDocument: ModelDocument) -> list[ModelObject]:
515
+ elts: list[ModelObject] = []
516
+ if modelDocument.type not in (ModelDocumentType.INLINEXBRL, ModelDocumentType.HTML):
517
+ return elts
518
+ for elt in modelDocument.xmlRootElement.iter():
519
+ if not isinstance(elt, ModelObject):
194
520
  continue
195
- if len(path.suffix) == 0:
196
- directories.append(path)
521
+ tag = elt.qname.localName
522
+ if tag in PROHIBITED_HTML_TAGS:
523
+ elts.append(elt)
524
+ return elts
525
+
526
+ def getStandardTaxonomyExtensionLinks(self, linkbaseType: LinkbaseType, modelXbrl: ModelXbrl) -> list[ModelObject]:
527
+ elts: list[ModelObject] = []
528
+ for modelDocument in modelXbrl.urlDocs.values():
529
+ if self.isStandardTaxonomyUrl(modelDocument.uri, modelXbrl) or not modelDocument.type == ModelDocumentType.SCHEMA:
197
530
  continue
198
- unknownPaths.append(path)
199
- return UploadContents(
200
- amendmentPaths={k: frozenset(v) for k, v in amendmentPaths.items() if len(v) > 0},
201
- directories=frozenset(directories),
202
- instances={k: frozenset(v) for k, v in forms.items() if len(v) > 0},
203
- unknownPaths=frozenset(unknownPaths)
204
- )
531
+ rootElt = modelDocument.xmlRootElement
532
+ for elt in rootElt.iterdescendants(XbrlConst.qnLinkLinkbaseRef.clarkNotation):
533
+ uri = elt.attrib.get(XbrlConst.qnXlinkHref.clarkNotation)
534
+ role = elt.attrib.get(XbrlConst.qnXlinkRole.clarkNotation)
535
+ if not role == linkbaseType.getRefUri() or self.isExtensionUri(uri, modelXbrl):
536
+ continue
537
+ elts.append(elt)
538
+ return elts
539
+
540
+ def getUploadContents(self, modelXbrl: ModelXbrl) -> UploadContents | None:
541
+ controllerPluginData = ControllerPluginData.get(modelXbrl.modelManager.cntlr, self.name)
542
+ return controllerPluginData.getUploadContents()
205
543
 
206
544
  @lru_cache(1)
207
- def getUploadFilepaths(self, modelXbrl: ModelXbrl) -> list[Path]:
208
- if not self.isUpload(modelXbrl):
209
- return []
210
- paths = set()
211
- assert isinstance(modelXbrl.fileSource.fs, zipfile.ZipFile)
212
- for name in modelXbrl.fileSource.fs.namelist():
213
- path = Path(name)
214
- paths.add(path)
215
- paths.update(path.parents)
216
- return sorted(paths)
545
+ def getUriAttributeValues(self, modelDocument: ModelDocument) -> list[tuple[ModelObject, str, str]]:
546
+ results: list[tuple[ModelObject, str, str]] = []
547
+ modelDocumentType = modelDocument.type
548
+ # Normal document parsing does not assign the HTML type to HTML files.
549
+ # Use ModelDocumentType.identify to check for HTML files.
550
+ if modelDocumentType == ModelDocumentType.UnknownXML:
551
+ modelDocumentType = ModelDocumentType.identify(modelDocument.modelXbrl.fileSource, modelDocument.uri)
552
+ if modelDocumentType not in (ModelDocumentType.INLINEXBRL, ModelDocumentType.HTML):
553
+ return results
554
+ for elt in modelDocument.xmlRootElement.iter():
555
+ if not isinstance(elt, ModelObject):
556
+ continue
557
+ for name in htmlEltUriAttrs.get(elt.localName, ()):
558
+ value = elt.get(name)
559
+ if value is not None:
560
+ results.append((elt, name, value))
561
+ return results
217
562
 
218
563
  def hasValidNonNilFact(self, modelXbrl: ModelXbrl, qname: QName) -> bool:
219
- requiredFacts = modelXbrl.factsByQname.get(qname, set())
220
- return any(fact.xValid >= VALID and not fact.isNil for fact in requiredFacts)
221
-
222
- @lru_cache(1)
223
- def isUpload(self, modelXbrl: ModelXbrl) -> bool:
224
- if not modelXbrl.fileSource.fs or \
225
- not isinstance(modelXbrl.fileSource.fs, zipfile.ZipFile):
226
- modelXbrl.warning(
227
- codes="EDINET.uploadNotValidated",
228
- msg=_("The target file is not a zip file, so upload validation could not be performed.")
229
- )
230
- return False
231
- return True
564
+ return any(True for fact in self.iterValidNonNilFacts(modelXbrl, qname))
232
565
 
233
566
  def isStandardTaxonomyUrl(self, uri: str, modelXbrl: ModelXbrl) -> bool:
234
567
  return modelXbrl.modelManager.disclosureSystem.hrefValidForDisclosureSystem(uri)
568
+
569
+ def iterCoverPages(self, modelXbrl: ModelXbrl) -> Iterable[ModelDocument]:
570
+ uploadContents = self.getUploadContents(modelXbrl)
571
+ if uploadContents is None:
572
+ return
573
+ for url, doc in modelXbrl.urlDocs.items():
574
+ path = Path(url)
575
+ pathInfo = uploadContents.uploadPathsByFullPath.get(path)
576
+ if pathInfo is None or not pathInfo.isCoverPage:
577
+ continue
578
+ yield doc
579
+
580
+ def iterFacts(self, modelXbrl: ModelXbrl, qname: QName) -> Iterable[ModelFact]:
581
+ yield from modelXbrl.factsByQname.get(qname, set())
582
+
583
+ def iterValidFacts(self, modelXbrl: ModelXbrl, qname: QName) -> Iterable[ModelFact]:
584
+ for fact in self.iterFacts(modelXbrl, qname):
585
+ if fact.xValid >= VALID:
586
+ yield fact
587
+
588
+ def iterValidNonNilFacts(self, modelXbrl: ModelXbrl, qname: QName) -> Iterable[ModelFact]:
589
+ for fact in self.iterValidFacts(modelXbrl, qname):
590
+ if not fact.isNil:
591
+ yield fact
592
+
593
+ def addUsedFilepath(self, modelXbrl: ModelXbrl, path: Path) -> None:
594
+ controllerPluginData = ControllerPluginData.get(modelXbrl.modelManager.cntlr, self.name)
595
+ controllerPluginData.addUsedFilepath(path)