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
arelle/DialogFind.py CHANGED
@@ -186,7 +186,7 @@ class DialogFind(Toplevel):
186
186
  else:
187
187
  if not self.modelManager.modelXbrl or not docType in (
188
188
  ModelDocument.Type.SCHEMA, ModelDocument.Type.LINKBASE, ModelDocument.Type.INSTANCE, ModelDocument.Type.INLINEXBRL,
189
- ModelDocument.Type.RSSFEED):
189
+ ModelDocument.Type.RSSFEED, ModelDocument.Type.INLINEXBRLDOCUMENTSET):
190
190
  messagebox.showerror(_("Find cannot be completed"),
191
191
  _("Find requires an opened DTS or RSS Feed"), parent=self.parent)
192
192
  return
@@ -386,15 +386,17 @@ class DialogPluginManager(Toplevel):
386
386
  group = {
387
387
  "ixbrl-viewer": "1", # pip installed Arelle viewer
388
388
  "iXBRLViewerPlugin": "2", # git clone installed Arelle viewer
389
- "Edgar Renderer": "3",
389
+ "EDGAR": "3",
390
+ "Inline XBRL Document Set": "4",
391
+ "XBRL rule processor (xule)": "5",
390
392
  }.get(key)
391
393
  if not group:
392
394
  if key.startswith("Validate"):
393
- group = "4"
395
+ group = "6"
394
396
  elif key.startswith("xbrlDB"):
395
- group = "5"
397
+ group = "7"
396
398
  else:
397
- group = "6"
399
+ group = "8"
398
400
  return group + key.lower()
399
401
 
400
402
  @staticmethod
@@ -62,6 +62,7 @@ class DisclosureSystem:
62
62
  self.validateEntryText = False
63
63
  self.allowedExternalHrefPattern = None
64
64
  self.allowedImageTypes = None
65
+ self.arcroleCyclesAllowed = {}
65
66
  self.schemaValidateSchema = None
66
67
  self.blockDisallowedReferences = False
67
68
  self.maxSubmissionSubdirectoryEntryNesting = 0
@@ -79,6 +80,7 @@ class DisclosureSystem:
79
80
  self.utrUrl = ["http://www.xbrl.org/utr/utr.xml"]
80
81
  self.utrStatusFilters = None
81
82
  self.utrTypeEntries = None
83
+ self.keepOpen: bool = False
82
84
  self.identifierSchemePattern = None
83
85
  self.identifierValuePattern = None
84
86
  self.identifierValueName = None
@@ -182,6 +184,10 @@ class DisclosureSystem:
182
184
  self.allowedExternalHrefPattern = re.compile(dsElt.get("allowedExternalHrefPattern"))
183
185
  if dsElt.get("allowedImageTypes"):
184
186
  self.allowedImageTypes = json.loads(dsElt.get("allowedImageTypes"))
187
+ if dsElt.get("arcroleCyclesAllowed"):
188
+ self.arcroleCyclesAllowed = json.loads(dsElt.get("arcroleCyclesAllowed"))
189
+ for arcrole, specSet in self.arcroleCyclesAllowed.items():
190
+ self.arcroleCyclesAllowed[arcrole] = tuple(specSet)
185
191
  self.blockDisallowedReferences = dsElt.get("blockDisallowedReferences") == "true"
186
192
  try:
187
193
  self.maxSubmissionSubdirectoryEntryNesting = int(dsElt.get("maxSubmissionSubdirectoryEntryNesting"))
@@ -209,6 +215,7 @@ class DisclosureSystem:
209
215
  self.utrUrl = [self.modelManager.cntlr.webCache.normalizeUrl(u, url)
210
216
  for u in dsElt.get("utrUrl").split()]
211
217
  self.utrStatusFilters = dsElt.get("utrStatusFilters")
218
+ self.keepOpen = dsElt.get("keepOpen") == "true"
212
219
  self.identifierSchemePattern = compileAttrPattern(dsElt,"identifierSchemePattern")
213
220
  self.identifierValuePattern = compileAttrPattern(dsElt,"identifierValuePattern")
214
221
  self.identifierValueName = dsElt.get("identifierValueName")
arelle/ErrorManager.py CHANGED
@@ -180,7 +180,10 @@ class ErrorManager:
180
180
  objectUrl = arg
181
181
  else:
182
182
  try:
183
- objectUrl = arg.modelDocument.displayUri
183
+ if isinstance(arg, ObjectPropertyViewWrapper):
184
+ objectUrl = arg.modelObject.modelDocument.displayUri
185
+ else:
186
+ objectUrl = arg.modelDocument.displayUri
184
187
  except AttributeError:
185
188
  try:
186
189
  objectUrl = arg.displayUri
@@ -204,13 +207,22 @@ class ErrorManager:
204
207
  _arg:ModelObject = arg.modelObject if isinstance(arg, ObjectPropertyViewWrapper) else arg
205
208
  if len(modelObjectArgs) > 1 and getattr(arg,"tag",None) == "instance":
206
209
  continue # skip IXDS top level element
207
- fragmentIdentifier = cast(str, XmlUtil.elementFragmentIdentifier(_arg))
208
- if not hasattr(_arg, 'modelDocument') and _arg.namespaceURI == XbrlConst.svg:
210
+ fragmentIdentifier = "#" + cast(str, XmlUtil.elementFragmentIdentifier(_arg))
211
+ if not hasattr(_arg, 'modelDocument') and _arg.namespaceURI == XbrlConst.svg and len(refs) > 0:
209
212
  # This is an embedded SVG document without its own file.
210
- ref["href"] = "#" + fragmentIdentifier
213
+ # Set the href to the containing document element that defined the encoded SVG.
214
+ # and define a nestedHrefs attribute with the fragment identifier.
215
+ priorRef = refs[-1]
216
+ ref["href"] = priorRef["href"]
217
+ priorNestedHrefs = priorRef.get("customAttributes", {}).get("nestedHrefs", [])
218
+ ref["customAttributes"] = {
219
+ "nestedHrefs": [*priorNestedHrefs, fragmentIdentifier]
220
+ }
221
+ if priorArgSourceline := priorRef.get("sourceLine"):
222
+ ref["sourceLine"] = priorArgSourceline
211
223
  else:
212
- ref["href"] = file + "#" + fragmentIdentifier
213
- ref["sourceLine"] = _arg.sourceline
224
+ ref["href"] = file + fragmentIdentifier
225
+ ref["sourceLine"] = _arg.sourceline
214
226
  ref["objectId"] = _arg.objectId()
215
227
  if logRefObjectProperties:
216
228
  try:
@@ -304,3 +316,6 @@ class ErrorManager:
304
316
  if isinstance(argValue, dict):
305
317
  return dict((self._loggableValue(k), self._loggableValue(v)) for k, v in argValue.items())
306
318
  return str(argValue)
319
+
320
+ def setErrorCaptureLevel(self, errorCaptureLevel: int) -> None:
321
+ self._errorCaptureLevel = errorCaptureLevel
arelle/FileSource.py CHANGED
@@ -51,7 +51,10 @@ def openFileSource(
51
51
  sourceFileSource: FileSource | None = None,
52
52
  ) -> FileSource:
53
53
  if sourceZipStream:
54
- if isinstance(sourceZipStream, FileNamedBytesIO) and sourceZipStream.fileName:
54
+ if name := getattr(sourceZipStream, "name", None):
55
+ # Python IO convention is to use the name attribute
56
+ sourceZipStreamFileName: str = os.sep + str(name)
57
+ elif isinstance(sourceZipStream, FileNamedBytesIO) and sourceZipStream.fileName:
55
58
  sourceZipStreamFileName = os.sep + sourceZipStream.fileName
56
59
  else:
57
60
  sourceZipStreamFileName = os.sep + "POSTupload.zip"
@@ -564,7 +567,7 @@ class FileSource:
564
567
  if encoding is None:
565
568
  encoding = XmlUtil.encoding(b)
566
569
  if stripDeclaration:
567
- b = stripDeclarationBytes(b)
570
+ b = stripDeclarationBytes(b, encoding)
568
571
  return (FileNamedTextIOWrapper(filepath, io.BytesIO(b), encoding=encoding),
569
572
  encoding)
570
573
  except KeyError as err:
@@ -581,7 +584,7 @@ class FileSource:
581
584
  if encoding is None:
582
585
  encoding = XmlUtil.encoding(b)
583
586
  if stripDeclaration:
584
- b = stripDeclarationBytes(b)
587
+ b = stripDeclarationBytes(b, encoding)
585
588
  return (FileNamedTextIOWrapper(filepath, io.BytesIO(b), encoding=encoding),
586
589
  encoding)
587
590
  except KeyError as err:
@@ -914,7 +917,11 @@ def openXmlFileStream(
914
917
  text = stripDeclarationText(text)
915
918
  return (FileNamedStringIO(filepath, initial_value=text), encoding)
916
919
 
917
- def stripDeclarationBytes(xml: bytes) -> bytes:
920
+ def stripDeclarationBytes(xml: bytes, encoding: str | None) -> bytes:
921
+ if encoding is not None and not encoding.lower().startswith('utf-8'):
922
+ text = xml.decode(encoding)
923
+ text = stripDeclarationText(text)
924
+ return text.encode(encoding)
918
925
  xmlStart = xml[0:120]
919
926
  indexOfDeclaration = xmlStart.find(b"<?xml")
920
927
  if indexOfDeclaration >= 0:
arelle/FunctionIxt.py CHANGED
@@ -1254,20 +1254,25 @@ tr5Functions.update ({
1254
1254
  })
1255
1255
  deprecatedNamespaceURI = 'http://www.xbrl.org/2008/inlineXBRL/transformation' # the CR/PR pre-REC namespace
1256
1256
 
1257
+ ixtNamespacesByVersion = {
1258
+ 1: 'http://www.xbrl.org/inlineXBRL/transformation/2010-04-20',
1259
+ 2: 'http://www.xbrl.org/inlineXBRL/transformation/2011-07-31',
1260
+ 3: 'http://www.xbrl.org/inlineXBRL/transformation/2015-02-26',
1261
+ 4: 'http://www.xbrl.org/inlineXBRL/transformation/2020-02-12',
1262
+ 5: 'http://www.xbrl.org/inlineXBRL/transformation/2022-02-16',
1263
+ }
1264
+
1257
1265
  ixtNamespaces = {
1258
- "ixt v1": 'http://www.xbrl.org/inlineXBRL/transformation/2010-04-20',
1259
- "ixt v2": 'http://www.xbrl.org/inlineXBRL/transformation/2011-07-31',
1260
- "ixt v3": 'http://www.xbrl.org/inlineXBRL/transformation/2015-02-26',
1261
- "ixt v4": 'http://www.xbrl.org/inlineXBRL/transformation/2020-02-12',
1262
- "ixt v5": 'http://www.xbrl.org/inlineXBRL/transformation/2022-02-16',
1263
- }
1266
+ f"ixt v{key}": val
1267
+ for key, val in ixtNamespacesByVersion.items()
1268
+ }
1264
1269
 
1265
1270
  ixtNamespaceFunctions = {
1266
- ixtNamespaces["ixt v1"]: tr1Functions, # transformation registry v1
1267
- ixtNamespaces["ixt v2"]: tr2Functions, # transformation registry v2
1268
- ixtNamespaces["ixt v3"]: tr3Functions, # transformation registry v3
1269
- ixtNamespaces["ixt v4"]: tr4Functions, # transformation registry v4
1270
- ixtNamespaces["ixt v5"]: tr5Functions, # transformation registry v5
1271
+ ixtNamespacesByVersion[1]: tr1Functions, # transformation registry v1
1272
+ ixtNamespacesByVersion[2]: tr2Functions, # transformation registry v2
1273
+ ixtNamespacesByVersion[3]: tr3Functions, # transformation registry v3
1274
+ ixtNamespacesByVersion[4]: tr4Functions, # transformation registry v4
1275
+ ixtNamespacesByVersion[5]: tr5Functions, # transformation registry v5
1271
1276
  "http://www.xbrl.org/inlineXBRL/transformation/WGWD/YYYY-MM-DD": tr5Functions, # transformation registry v4 draft
1272
1277
  'http://www.xbrl.org/2008/inlineXBRL/transformation': tr1Functions # the CR/PR pre-REC namespace
1273
1278
  }
arelle/HtmlUtil.py CHANGED
@@ -1,12 +1,13 @@
1
1
  '''
2
2
  See COPYRIGHT.md for copyright information.
3
3
  '''
4
- import regex as re
4
+ from __future__ import annotations
5
5
 
6
- def attrValue(str, name):
6
+
7
+ def attrValue(attr: str) -> str:
7
8
  # retrieves attribute in a string, such as xyz="abc" or xyz='abc' or xyz=abc;
8
- prestuff, matchedName, valuePart = str.lower().partition("charset")
9
- value = []
9
+ prestuff, matchedName, valuePart = attr.lower().partition("charset")
10
+ value: list[str] = []
10
11
  endSep = None
11
12
  beforeEquals = True
12
13
  for c in valuePart:
arelle/LeiUtil.py CHANGED
@@ -1,55 +1,75 @@
1
- '''
1
+ """
2
2
  See COPYRIGHT.md for copyright information.
3
3
 
4
4
  Implementation of ISO 17442:2012(E) Appendix A
5
5
 
6
- '''
6
+ """
7
+
8
+ import string
9
+ from enum import Enum, auto
10
+
7
11
  import regex as re
8
12
 
9
- LEI_VALID = 0
10
- LEI_INVALID_LEXICAL = 1
11
- LEI_INVALID_CHECKSUM = 2
12
13
 
13
- LEI_RESULTS = ("valid", "invalid lexical", "invalid checksum")
14
+ class LEIValidationResult(Enum):
15
+ VALID = auto()
16
+ INVALID_LEXICAL = auto()
17
+ INVALID_CHECKSUM = auto()
18
+
19
+ def description(self) -> str:
20
+ return self.name.lower().replace("_", " ")
21
+
22
+
23
+ LEI_VALID = LEIValidationResult.VALID
24
+ LEI_INVALID_LEXICAL = LEIValidationResult.INVALID_LEXICAL
25
+ LEI_INVALID_CHECKSUM = LEIValidationResult.INVALID_CHECKSUM
26
+
27
+ _leiLexicalPattern = re.compile(r"[0-9A-Z]{18}[0-9]{2}", re.ASCII)
28
+ _requiredLEILength = 20
29
+
30
+ _leiToDigitTable = str.maketrans(
31
+ dict(
32
+ (ord(char), str(index))
33
+ for index, char in enumerate(string.ascii_uppercase, start=len(string.digits))
34
+ )
35
+ )
36
+
37
+ # The pattern comes from Solvency II documentation here https://dev.eiopa.europa.eu/Taxonomy/Full/2.3.0/PF/EIOPA_PensionFunds_Validations_2.3.0_PWD2.xlsx
38
+ _validLeiDespiteChecksumFailPatternString = r"^(029200720E3M3A4D6D01|029200758D5M0AI3F601|315700X8JQ3IU0NGK501|3157007SCCESQAUH5Z01|315700TCC9NTEP7J8Z01|13250000000000000301|315700ADOXDR5PCY5400|315700A0UB9Q7DOQIZ00|315700BM9Z39TNTGQW00|315700BZ5F7DRYG2UM00|315700B8401GTFFY6X01|315700DJ07P6OX10FK01|315700DKCD4QSKLAMO01|315700D23JL5C1DZNT00|315700EIYO2TLSEGQ700|315700ET7M7VQ4C84R00|315700EZSEA51937KX01|315700GXBGM8DBYKHF01|315700GZA843JXKTJ400|315700G5G24XYL1TXH00|315700HS7WJ1B0SUWM01|315700HXOEOK58E58P01|315700HZU4SMI8LZTU00|315700I3W2AFHP8MNQ01|315700JICZ3SY5SAXX00|315700JKZH6I0067ND01|315700JO5E28SRE00Q01|315700JXUHL9H2C3P700|315700LDDN3RM7Y2MP00|315700LWYOZNQ7V1T100|315700MBYPT6PGKO7M01|315700MW2F0KFR45QW01|315700M5843O6DU83901|315700NNMGS8F3P2CN00|315700N0VEIBHP0NPQ01|315700OASRCM664PAW01|315700OFW4YCOBNX4U01|315700OVW93X0T3HP200|315700O666JVNCQU9X00|315700PLI0I7W8IOV400|315700PN3J57ZUNF1V00|315700PXKOSX7WQV4N00|315700P4N9VSLK5QZV01|315700P40OV6BT045900|315700P6TZOLP92KN801|315700P89WR82VNB8Z00|315700QGM4XWZE1I5N01|315700Q1S8O1UORF9700|315700RK8M4FAHMYAP01|315700RTEHY362KXWJ00|315700SNUXK41WMW5J00|315700S22RGYRIEEOT00|315700S3TF79ALV82F01|315700TF5Z7T28HZJK01|315700TWGZ89LLSRS000|315700TXNX10N8XH4K00|315700T2EEQAPBO0C301|315700T6T49EDM16YO01|315700UJ6N4LGKLNPB00|315700UKZXWXEO126601|315700UYFD5GF9R13F01|315700VG7PTE9EJJRX01|315700VITYR7AL4M9S01|315700VMAJZ9JZTXNQ00|315700WH3YMKHCVYW201|315700WKCDF4QGRRO200|315700WR4IHOO1M5LP00|315700WYOZ6994UATN00|315700WZPEIS41QDKE00|315700XEFYMA5EZ0P500|315700XE21UYOA3GAC01|315700XI4Z8GF5BDUJ01|315700XSCP1S8WOD8E01|315700X40GNCOUWJYR00|315700YS6RQ5TF3VBP01|315700Y1W7W1JHAUBW00|315700Y5JNQMMUF5ID01|3157000VAJWZ3P8ZED00|3157001KM8GOU7PXZY01|3157001K2LAL04D87901|3157001MLDD3SDFQA901|3157001TPR6K4GBTLN00|31570010000000006400|31570010000000009601|31570010000000019301|31570010000000025800|31570010000000029001|31570010000000035500|31570010000000038701|31570010000000045200|31570010000000048401|31570010000000054900|31570010000000064600|31570010000000067801|31570010000000077501|31570010000000084000|31570010000000087201|31570010000000096901|31570010000000103400|31570010000000106601|31570010000000116301|31570010000000122800|31570010000000126001|3157002CKDQOCIHE5H01|31570020000000005900|31570029808HJVCNFA01|3157003FQSSGS9OZ9E01|3157004PTVTDOKB46401|3157004R6CH6C1P4KX00|3157005RUI28M8FANK00|3157005VJE7A3MBUS201|3157005WT1SENAE17R00|31570058O0Z320C4GZ00|3157006B6JVZ5DFMSN00|3157006DE3SPNIUY9K01|3157006FR3JBBOLOMX01|3157006I3B6RSTPQLI00|3157006KT1EZ15OIXW00|315700659AALVVLVIO01|31570067WSCDST0S3F01|3157008VKYORMUNC5O01|3157008ZY7CW6LVU5J00|3157009FTHFDDK7FHW01|3157009OVCV07O4HXM00|315700T8U7IU4W8J3A01|315700BBRQHDWX6SHZ00|3157008KD17KROO7UT01)$"
14
39
 
15
- leiLexicalPattern = re.compile(r"^[0-9A-Z]{18}[0-9]{2}$")
40
+ # Turn the _validLeiDespiteChecksumFailPattern in to an immutable set as that is much faster to check
41
+ _validLeiDespiteChecksumFailSet = frozenset(
42
+ _validLeiDespiteChecksumFailPatternString[2:-2].split("|")
43
+ )
16
44
 
17
- validInvalidLeiPattern = re.compile("^(029200720E3M3A4D6D01|029200758D5M0AI3F601|315700X8JQ3IU0NGK501|3157007SCCESQAUH5Z01|315700TCC9NTEP7J8Z01|13250000000000000301|315700ADOXDR5PCY5400|315700A0UB9Q7DOQIZ00|315700BM9Z39TNTGQW00|315700BZ5F7DRYG2UM00|315700B8401GTFFY6X01|315700DJ07P6OX10FK01|315700DKCD4QSKLAMO01|315700D23JL5C1DZNT00|315700EIYO2TLSEGQ700|315700ET7M7VQ4C84R00|315700EZSEA51937KX01|315700GXBGM8DBYKHF01|315700GZA843JXKTJ400|315700G5G24XYL1TXH00|315700HS7WJ1B0SUWM01|315700HXOEOK58E58P01|315700HZU4SMI8LZTU00|315700I3W2AFHP8MNQ01|315700JICZ3SY5SAXX00|315700JKZH6I0067ND01|315700JO5E28SRE00Q01|315700JXUHL9H2C3P700|315700LDDN3RM7Y2MP00|315700LWYOZNQ7V1T100|315700MBYPT6PGKO7M01|315700MW2F0KFR45QW01|315700M5843O6DU83901|315700NNMGS8F3P2CN00|315700N0VEIBHP0NPQ01|315700OASRCM664PAW01|315700OFW4YCOBNX4U01|315700OVW93X0T3HP200|315700O666JVNCQU9X00|315700PLI0I7W8IOV400|315700PN3J57ZUNF1V00|315700PXKOSX7WQV4N00|315700P4N9VSLK5QZV01|315700P40OV6BT045900|315700P6TZOLP92KN801|315700P89WR82VNB8Z00|315700QGM4XWZE1I5N01|315700Q1S8O1UORF9700|315700RK8M4FAHMYAP01|315700RTEHY362KXWJ00|315700SNUXK41WMW5J00|315700S22RGYRIEEOT00|315700S3TF79ALV82F01|315700TF5Z7T28HZJK01|315700TWGZ89LLSRS000|315700TXNX10N8XH4K00|315700T2EEQAPBO0C301|315700T6T49EDM16YO01|315700UJ6N4LGKLNPB00|315700UKZXWXEO126601|315700UYFD5GF9R13F01|315700VG7PTE9EJJRX01|315700VITYR7AL4M9S01|315700VMAJZ9JZTXNQ00|315700WH3YMKHCVYW201|315700WKCDF4QGRRO200|315700WR4IHOO1M5LP00|315700WYOZ6994UATN00|315700WZPEIS41QDKE00|315700XEFYMA5EZ0P500|315700XE21UYOA3GAC01|315700XI4Z8GF5BDUJ01|315700XSCP1S8WOD8E01|315700X40GNCOUWJYR00|315700YS6RQ5TF3VBP01|315700Y1W7W1JHAUBW00|315700Y5JNQMMUF5ID01|3157000VAJWZ3P8ZED00|3157001KM8GOU7PXZY01|3157001K2LAL04D87901|3157001MLDD3SDFQA901|3157001TPR6K4GBTLN00|31570010000000006400|31570010000000009601|31570010000000019301|31570010000000025800|31570010000000029001|31570010000000035500|31570010000000038701|31570010000000045200|31570010000000048401|31570010000000054900|31570010000000064600|31570010000000067801|31570010000000077501|31570010000000084000|31570010000000087201|31570010000000096901|31570010000000103400|31570010000000106601|31570010000000116301|31570010000000122800|31570010000000126001|3157002CKDQOCIHE5H01|31570020000000005900|31570029808HJVCNFA01|3157003FQSSGS9OZ9E01|3157004PTVTDOKB46401|3157004R6CH6C1P4KX00|3157005RUI28M8FANK00|3157005VJE7A3MBUS201|3157005WT1SENAE17R00|31570058O0Z320C4GZ00|3157006B6JVZ5DFMSN00|3157006DE3SPNIUY9K01|3157006FR3JBBOLOMX01|3157006I3B6RSTPQLI00|3157006KT1EZ15OIXW00|315700659AALVVLVIO01|31570067WSCDST0S3F01|3157008VKYORMUNC5O01|3157008ZY7CW6LVU5J00|3157009FTHFDDK7FHW01|3157009OVCV07O4HXM00|315700T8U7IU4W8J3A01|315700BBRQHDWX6SHZ00|3157008KD17KROO7UT01)$")
18
45
 
19
- def checkLei(lei: str) -> int:
20
- if validInvalidLeiPattern.match(lei):
21
- return LEI_VALID
22
- if not leiLexicalPattern.match(lei):
23
- return LEI_INVALID_LEXICAL
24
- if not int(
25
- "".join({"0":"0", "1":"1", "2":"2", "3":"3", "4":"4", "5":"5", "6":"6", "7":"7", "8":"8", "9":"9",
26
- "A":"10", "B":"11", "C":"12", "D":"13", "E":"14", "F":"15", "G":"16", "H":"17", "I":"18",
27
- "J":"19", "K":"20", "L":"21", "M":"22", "N":"23", "O":"24", "P":"25", "Q":"26", "R":"27",
28
- "S":"28", "T":"29", "U":"30", "V":"31", "W":"32", "X":"33", "Y":"34", "Z":"35"
29
- }[c] for c in lei)
30
- ) % 97 == 1:
31
- return LEI_INVALID_CHECKSUM
32
- return LEI_VALID
46
+ def checkLei(lei: str) -> LEIValidationResult:
47
+ if len(lei) != _requiredLEILength:
48
+ return LEIValidationResult.INVALID_LEXICAL
49
+
50
+ if not _leiLexicalPattern.fullmatch(lei):
51
+ return LEIValidationResult.INVALID_LEXICAL
52
+
53
+ result = LEIValidationResult.INVALID_CHECKSUM
54
+
55
+ if int(lei.translate(_leiToDigitTable)) % 97 == 1:
56
+ result = LEIValidationResult.VALID
57
+
58
+ if (
59
+ result is LEIValidationResult.INVALID_CHECKSUM
60
+ and lei in _validLeiDespiteChecksumFailSet
61
+ ):
62
+ result = LEIValidationResult.VALID
63
+
64
+ return result
65
+
33
66
 
34
67
  if __name__ == "__main__":
35
- # test cases
36
- for lei, name in (
37
- ("001GPB6A9XPE8XJICC14", "Fidelity Advisor Series I"),
38
- ("004L5FPTUREIWK9T2N63", "Hutchin Hill Capital, LP"),
39
- ("00EHHQ2ZHDCFXJCPCL46", "Vanguard Russell 1000 Growth Index Trust"),
40
- ("00GBW0Z2GYIER7DHDS71", "Aristeia Capital, L.L.C."),
41
- ("1S619D6B3ZQIH6MS6B47", "Barclays Vie SA"),
42
- ("21380014JAZAUFJRHC43", "BRE/OPERA HOLDINGS"),
43
- ("21380016W7GAG26FIJ74", "SOCIETE FRANCAISE ET SUISSE"),
44
- ("21380058ERUIT9H53T71", "TOTAN ICAP CO., LTD"),
45
- ("213800A9GT65GAES2V60", "BARCLAYS SECURITIES JAPAN LIMITED"),
46
- ("213800DELL1MWFDHVN53", "PIRELLI JAPAN"),
47
- ("213800A9GT65GAES2V60", "BARCLAYS SECURITIES JAPAN LIMITED"),
48
- ("214800A9GT65GAES2V60", "Error 1"),
49
- ("213800A9GT65GAE%2V60", "Error 2"),
50
- ("213800A9GT65GAES2V62", "Error 3"),
51
- ("1234", "Error 4"),
52
- ("""
53
- 5299003M8JKHEFX58Y02""", "Error 4")
54
- ):
55
- print ("LEI {} result {} name {}".format(lei, LEI_RESULTS[checkLei(lei)], name) )
68
+ import sys
69
+
70
+ leis = sys.argv[1:]
71
+ if not leis:
72
+ raise SystemExit("Specify the LEI(s) you want to check as arguments.")
73
+
74
+ for arg in sys.argv[1:]:
75
+ print(f"{arg:20s} : {checkLei(arg).description()}")
arelle/ModelDocument.py CHANGED
@@ -446,7 +446,7 @@ def loadSchemalocatedSchema(modelXbrl, element, relativeUrl, namespace, baseUrl)
446
446
  doc.inDTS = False
447
447
  return doc
448
448
 
449
- def create(modelXbrl, type, uri, schemaRefs=None, isEntry=False, initialXml=None, initialComment=None, base=None, discover=True, documentEncoding="utf-8", xbrliNamespacePrefix=None) -> ModelDocument:
449
+ def create(modelXbrl, type, uri, schemaRefs=None, isEntry=False, initialXml=None, initialComment=None, base=None, discover=True, documentEncoding="utf-8", xbrliNamespacePrefix=None, entrypoint=None) -> ModelDocument:
450
450
  """Returns a new modelDocument, created from scratch, with any necessary header elements
451
451
 
452
452
  (such as the schema, instance, or RSS feed top level elements)
@@ -538,6 +538,7 @@ def create(modelXbrl, type, uri, schemaRefs=None, isEntry=False, initialXml=None
538
538
  modelDocument.isQualifiedElementFormDefault = False
539
539
  modelDocument.isQualifiedAttributeFormDefault = False
540
540
  modelDocument.definesUTR = False
541
+ modelDocument.entrypoint = entrypoint
541
542
  return modelDocument
542
543
 
543
544
  class Type:
@@ -727,10 +728,12 @@ class ModelDocument:
727
728
  # The document encoding. The XML declaration is stripped from the document
728
729
  # before lxml parses the document making the lxml DocInfo encoding unreliable.
729
730
  documentEncoding: str
731
+ entrypoint: dict[str, Any] | None
730
732
  xmlRootElement: Any
731
733
  targetXbrlRootElement: ModelObject
732
734
 
733
735
  def __init__(self, modelXbrl, type, uri, filepath, xmlDocument):
736
+ self.entrypoint = None
734
737
  self.modelXbrl = modelXbrl
735
738
  self.skipDTS = modelXbrl.skipDTS
736
739
  self.type = type
@@ -1579,12 +1582,6 @@ def inlineIxdsDiscover(modelXbrl, modelIxdsDocument, setTargetModelXbrl=False, *
1579
1582
  modelXbrl.targetRoleRefs = {} # roleRefs used by selected target
1580
1583
  modelXbrl.targetArcroleRefs = {} # arcroleRefs used by selected target
1581
1584
  modelXbrl.targetRelationships = set() # relationship elements used by selected target
1582
- targetModelXbrl = modelXbrl if setTargetModelXbrl else None # modelXbrl of target for contexts/units in multi-target/multi-instance situation
1583
- assignUnusedContextsUnits = (not setTargetModelXbrl and not ixdsTarget and
1584
- not getattr(modelXbrl, "supplementalModelXbrls", ()) and (
1585
- not getattr(modelXbrl, "targetIXDSesToLoad", ()) or
1586
- set(e.modelDocument for e in modelXbrl.ixdsHtmlElements) ==
1587
- set(x.modelDocument for e in getattr(modelXbrl, "targetIXDSesToLoad", ()) for x in e[1])))
1588
1585
  hasResources = hasHeader = False
1589
1586
  for htmlElement in modelXbrl.ixdsHtmlElements:
1590
1587
  mdlDoc = htmlElement.modelDocument
@@ -1611,8 +1608,12 @@ def inlineIxdsDiscover(modelXbrl, modelIxdsDocument, setTargetModelXbrl=False, *
1611
1608
  for modelInlineFact in htmlElement.iterdescendants(ixNStag + "nonNumeric", ixNStag + "nonFraction", ixNStag + "fraction"):
1612
1609
  if isinstance(modelInlineFact,ModelObject):
1613
1610
  _target = modelInlineFact.get("target")
1614
- factTargetContextRefs[_target].add(modelInlineFact.get("contextRef"))
1615
- factTargetUnitRefs[_target].add(modelInlineFact.get("unitRef"))
1611
+ contextRef = modelInlineFact.get("contextRef")
1612
+ if contextRef is not None:
1613
+ factTargetContextRefs[_target].add(contextRef.strip())
1614
+ unitRef = modelInlineFact.get("unitRef")
1615
+ if unitRef is not None:
1616
+ factTargetUnitRefs[_target].add(unitRef.strip())
1616
1617
  if modelInlineFact.id:
1617
1618
  factsByFactID[modelInlineFact.id] = modelInlineFact
1618
1619
  for elt in htmlElement.iterdescendants(tag=ixNStag + "continuation"):
@@ -1734,9 +1735,9 @@ def inlineIxdsDiscover(modelXbrl, modelIxdsDocument, setTargetModelXbrl=False, *
1734
1735
  targetRoleUris[_target].add(footnoteRole)
1735
1736
 
1736
1737
  contextRefs = factTargetContextRefs[ixdsTarget]
1738
+ contextRefsForAllTargets = {ref for refs in factTargetContextRefs.values() for ref in refs}
1737
1739
  unitRefs = factTargetUnitRefs[ixdsTarget]
1738
- allContextRefs = set.union(*factTargetContextRefs.values())
1739
- allUnitRefs = set.union(*factTargetUnitRefs.values())
1740
+ unitRefsForAllTargets = {ref for refs in factTargetUnitRefs.values() for ref in refs}
1740
1741
 
1741
1742
  # discovery of contexts, units and roles which are used by target document
1742
1743
  for htmlElement in modelXbrl.ixdsHtmlElements:
@@ -1745,13 +1746,17 @@ def inlineIxdsDiscover(modelXbrl, modelIxdsDocument, setTargetModelXbrl=False, *
1745
1746
 
1746
1747
  for inlineElement in htmlElement.iterdescendants(tag=ixNStag + "resources"):
1747
1748
  for elt in inlineElement.iterchildren("{http://www.xbrl.org/2003/instance}context"):
1748
- id = elt.get("id")
1749
- if id in contextRefs or (assignUnusedContextsUnits and id not in allContextRefs):
1749
+ contextId = elt.get("id")
1750
+ if contextId in contextRefs:
1750
1751
  modelIxdsDocument.contextDiscover(elt, setTargetModelXbrl)
1752
+ elif contextId not in contextRefsForAllTargets:
1753
+ modelXbrl.ixdsUnmappedContexts[contextId] = elt
1751
1754
  for elt in inlineElement.iterchildren("{http://www.xbrl.org/2003/instance}unit"):
1752
- id = elt.get("id")
1753
- if id in unitRefs or (assignUnusedContextsUnits and id not in allUnitRefs):
1755
+ unitId = elt.get("id")
1756
+ if unitId in unitRefs:
1754
1757
  modelIxdsDocument.unitDiscover(elt, setTargetModelXbrl)
1758
+ elif unitId not in unitRefsForAllTargets:
1759
+ modelXbrl.ixdsUnmappedUnits[unitId] = elt
1755
1760
  for refElement in inlineElement.iterchildren("{http://www.xbrl.org/2003/linkbase}roleRef"):
1756
1761
  r = refElement.get("roleURI")
1757
1762
  if r in targetRoleUris[ixdsTarget]:
arelle/ModelDtsObject.py CHANGED
@@ -1690,6 +1690,7 @@ class RelationStatus:
1690
1690
  INEFFECTIVE = 4
1691
1691
 
1692
1692
  arcCustAttrsExclusions = {XbrlConst.xlink, "use","priority","order","weight","preferredLabel"}
1693
+ modelObjectAttrs = frozenset(dir(ModelObject))
1693
1694
 
1694
1695
  class ModelRelationship(ModelObject):
1695
1696
  """
@@ -2014,6 +2015,7 @@ class ModelRelationship(ModelObject):
2014
2015
  @property
2015
2016
  def equivalenceHash(self): # not exact, use equivalenceKey if hashes are the same
2016
2017
  return hash((self.qname,
2018
+ self.arcrole,
2017
2019
  self.linkQname,
2018
2020
  self.linkrole, # needed when linkrole=None merges multiple links
2019
2021
  self.fromModelObject.objectIndex if isinstance(self.fromModelObject, ModelObject) else -1,
@@ -2027,6 +2029,7 @@ class ModelRelationship(ModelObject):
2027
2029
  """(tuple) -- Key to determine relationship equivalence per 2.1 spec"""
2028
2030
  # cannot be cached because this is unique per relationship
2029
2031
  return (self.qname,
2032
+ self.arcrole,
2030
2033
  self.linkQname,
2031
2034
  self.linkrole, # needed when linkrole=None merges multiple links
2032
2035
  self.fromModelObject.objectIndex if isinstance(self.fromModelObject, ModelObject) else -1,
@@ -2086,6 +2089,11 @@ class ModelRelationship(ModelObject):
2086
2089
  self.toModelObject.qname if isinstance(self.toModelObject, ModelObject) else "??",
2087
2090
  self.modelDocument.basename, self.sourceline))
2088
2091
 
2092
+ def __dir__(self):
2093
+ # Ignore ModelObject attributes because most relate to the underlying lxml element,
2094
+ # which ModelRelationship does not have.
2095
+ return [a for a in object.__dir__(self) if a.startswith('__') or a not in modelObjectAttrs]
2096
+
2089
2097
  @property
2090
2098
  def viewConcept(self):
2091
2099
  if isinstance(self.toModelObject, ModelConcept):
@@ -1159,7 +1159,7 @@ class ModelContext(ModelObject):
1159
1159
  self._dimsHash = hash( frozenset(self.qnameDims.values()) )
1160
1160
  return self._dimsHash
1161
1161
 
1162
- def nonDimValues(self, contextElement):
1162
+ def nonDimValues(self, contextElement: str | int) -> list[ModelObject]:
1163
1163
  """([ModelObject]) -- ContextElement is either string or Aspect code for segment or scenario, returns nonXDT ModelObject children of context element.
1164
1164
 
1165
1165
  :param contextElement: one of 'segment', 'scenario', Aspect.NON_XDT_SEGMENT, Aspect.NON_XDT_SCENARIO, Aspect.COMPLETE_SEGMENT, Aspect.COMPLETE_SCENARIO
arelle/ModelObject.py CHANGED
@@ -8,7 +8,6 @@ from lxml import etree
8
8
  from arelle import Locale
9
9
  from arelle import ModelValue
10
10
  from arelle.XmlValidateConst import VALID_NO_CONTENT
11
- from arelle.model import CommentBase, ElementBase, PIBase
12
11
 
13
12
  if TYPE_CHECKING:
14
13
  from arelle.ModelDocument import ModelDocument
@@ -21,7 +20,7 @@ if TYPE_CHECKING:
21
20
  from arelle.ModelInstanceObject import ModelInlineFootnote
22
21
  from arelle.ModelInstanceObject import ModelInlineFact
23
22
  from arelle.ModelInstanceObject import ModelDimensionValue
24
- from arelle.ModelValue import qname, qnameEltPfxName, QName, TypeSValue, TypeXValue
23
+ from arelle.ModelValue import QName, TypeSValue, TypeXValue
25
24
 
26
25
  XmlUtil: Any = None
27
26
 
@@ -32,7 +31,7 @@ def init() -> None: # init globals
32
31
  if XmlUtil is None:
33
32
  from arelle import XmlUtil
34
33
 
35
- class ModelObject(ElementBase):
34
+ class ModelObject(etree.ElementBase):
36
35
  """ModelObjects represent the XML elements within a document, and are implemented as custom
37
36
  lxml proxy objects. Each modelDocument has a parser with the parser objects in ModelObjectFactory.py,
38
37
  to determine the type of model object to correspond to a proxied lxml XML element.
@@ -117,6 +116,7 @@ class ModelObject(ElementBase):
117
116
  xValueError: Exception | None
118
117
  xValid: int
119
118
  xlinkLabel: str
119
+ tag: str
120
120
  targetModelXbrl: ModelXbrl
121
121
 
122
122
  def _init(self) -> None:
@@ -125,9 +125,9 @@ class ModelObject(ElementBase):
125
125
  if parent is not None and hasattr(parent, "modelDocument"):
126
126
  self.init(parent.modelDocument)
127
127
 
128
- def clear(self) -> None:
128
+ def clear(self, keep_tail: bool = False) -> None:
129
129
  self.__dict__.clear() # delete local attributes
130
- super(ModelObject, self).clear() # delete children
130
+ super().clear(keep_tail) # delete children
131
131
 
132
132
  def init(self, modelDocument: ModelDocument) -> None:
133
133
  self.modelDocument = modelDocument
@@ -160,9 +160,10 @@ class ModelObject(ElementBase):
160
160
  return emptySet
161
161
 
162
162
  def setNamespaceLocalName(self) -> None:
163
- ns, sep, self._localName = self.tag.rpartition("}")
163
+ tag = self.tag
164
+ ns, sep, self._localName = tag.rpartition("}")
164
165
  if sep:
165
- self._namespaceURI = ns[1:]
166
+ self._namespaceURI: str | None = ns[1:]
166
167
  else:
167
168
  self._namespaceURI = None
168
169
  if self.prefix:
@@ -257,7 +258,7 @@ class ModelObject(ElementBase):
257
258
  return self._parentQname
258
259
  except AttributeError:
259
260
  parentObj = self.getparent()
260
- self._parentQname = parentObj.elementQname if parentObj is not None else None # type: ignore[attr-defined]
261
+ self._parentQname = parentObj.elementQname if parentObj is not None else None
261
262
  return self._parentQname
262
263
 
263
264
 
@@ -267,18 +268,18 @@ class ModelObject(ElementBase):
267
268
 
268
269
  @property
269
270
  def stringValue(self) -> str: # "string value" of node, text of all Element descendants
270
- return ''.join(self._textNodes(recurse=True)) # return text of Element descendants
271
+ return ''.join(self.textNodes(recurse=True)) # return text of Element descendants
271
272
 
272
273
  @property
273
274
  def textValue(self) -> str: # xml axis text() differs from string value, no descendant element text
274
- return ''.join(self._textNodes()) # no text nodes returns ''
275
+ return ''.join(self.textNodes()) # no text nodes returns ''
275
276
 
276
- def _textNodes(self, recurse:bool = False) -> Generator[str | Any, None, None]:
277
+ def textNodes(self, recurse:bool = False) -> Generator[str | Any, None, None]:
277
278
  if self.text and getattr(self,"xValid", 0) != VALID_NO_CONTENT: # skip tuple whitespaces
278
279
  yield self.text
279
280
  for c in self.iterchildren():
280
281
  if recurse and isinstance(c, ModelObject):
281
- for nestedText in c._textNodes(recurse):
282
+ for nestedText in c.textNodes(recurse):
282
283
  yield nestedText
283
284
  if c.tail and getattr(self,"xValid", 0) != VALID_NO_CONTENT: # skip tuple whitespaces
284
285
  yield c.tail # get tail of nested element, comment or processor nodes
@@ -305,10 +306,7 @@ class ModelObject(ElementBase):
305
306
 
306
307
  @property
307
308
  def elementAttributesStr(self) -> str:
308
- # Note 2022-09-09:
309
- # Mypy raises the following error. Not sure why this is the case, this returns a str not binary data?
310
- # On Python 3 formatting "b'abc'" with "{}" produces "b'abc'", not "abc"; use "{!r}" if this is desired behavior
311
- return ', '.join(["{0}='{1}'".format(name, value) for name, value in self.items()]) # type: ignore[str-bytes-safe]
309
+ return ', '.join(["{0}='{1}'".format(name, value) for name, value in self.items()])
312
310
 
313
311
  def resolveUri(
314
312
  self,
@@ -398,7 +396,7 @@ class ModelObject(ElementBase):
398
396
  def __repr__(self) -> str:
399
397
  return ("{0}[{1}, {2} line {3})".format(type(self).__name__, self.objectIndex, self.modelDocument.basename, self.sourceline))
400
398
 
401
- class ModelComment(CommentBase): # type: ignore[misc]
399
+ class ModelComment(etree.CommentBase):
402
400
  """ModelConcept is a custom proxy objects for etree.
403
401
  """
404
402
  def _init(self) -> None:
@@ -410,7 +408,7 @@ class ModelComment(CommentBase): # type: ignore[misc]
410
408
  def init(self, modelDocument: ModelDocument) -> None:
411
409
  self.modelDocument = modelDocument
412
410
 
413
- class ModelProcessingInstruction(PIBase): # type: ignore[misc]
411
+ class ModelProcessingInstruction(etree.PIBase):
414
412
  """ModelProcessingInstruction is a custom proxy object for etree.
415
413
  """
416
414
  def _init(self) -> None: