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/BetaFeatures.py CHANGED
@@ -3,27 +3,6 @@ See COPYRIGHT.md for copyright information.
3
3
  """
4
4
  from __future__ import annotations
5
5
 
6
- BETA_OBJECT_MODEL_FEATURE = "betaObjectModel"
7
6
  # Add camelCaseOptionName
8
7
  BETA_FEATURES_AND_DESCRIPTIONS: dict[str, str] = {
9
- BETA_OBJECT_MODEL_FEATURE: "Replace lxml based object model with a pure Python class hierarchy.",
10
8
  }
11
-
12
-
13
- _NEW_OBJECT_MODEL_STATUS_ACCESSED = False
14
- _USE_NEW_OBJECT_MODEL = False
15
-
16
-
17
- def enableNewObjectModel() -> None:
18
- global _USE_NEW_OBJECT_MODEL
19
- if _USE_NEW_OBJECT_MODEL:
20
- return
21
- if _NEW_OBJECT_MODEL_STATUS_ACCESSED:
22
- raise RuntimeError("Can't change object model transition setting after classes have already been defined.")
23
- _USE_NEW_OBJECT_MODEL = True
24
-
25
-
26
- def newObjectModelEnabled() -> bool:
27
- global _NEW_OBJECT_MODEL_STATUS_ACCESSED
28
- _NEW_OBJECT_MODEL_STATUS_ACCESSED = True
29
- return _USE_NEW_OBJECT_MODEL
arelle/Cntlr.py CHANGED
@@ -18,24 +18,25 @@ import os
18
18
  import subprocess
19
19
  import sys
20
20
  import tempfile
21
- from typing import Any, TYPE_CHECKING, cast
21
+ from typing import TYPE_CHECKING, Any, cast
22
22
 
23
- import regex as re
23
+ import regex
24
24
 
25
25
  from arelle import Locale, ModelManager, PackageManager, PluginManager, XbrlConst
26
26
  from arelle.BetaFeatures import BETA_FEATURES_AND_DESCRIPTIONS
27
27
  from arelle.ErrorManager import ErrorManager
28
28
  from arelle.FileSource import FileSource
29
- from arelle.SystemInfo import PlatformOS, getSystemWordSize, hasFileSystem, hasWebServer, isCGI, isGAE
30
- from arelle.WebCache import WebCache
31
29
  from arelle.logging.formatters.LogFormatter import LogFormatter, logRefsFileLines # noqa: F401 - for reimport
32
30
  from arelle.logging.handlers.LogHandlerWithXml import LogHandlerWithXml # noqa: F401 - for reimport
33
31
  from arelle.logging.handlers.LogToBufferHandler import LogToBufferHandler
34
32
  from arelle.logging.handlers.LogToPrintHandler import LogToPrintHandler
35
33
  from arelle.logging.handlers.LogToXmlHandler import LogToXmlHandler
36
34
  from arelle.logging.handlers.StructuredMessageLogHandler import StructuredMessageLogHandler
35
+ from arelle.SystemInfo import PlatformOS, getSystemWordSize, hasFileSystem, hasWebServer, isCGI, isGAE
37
36
  from arelle.typing import TypeGetText
38
37
  from arelle.utils.PluginData import PluginData
38
+ from arelle.utils.validate.Validation import Validation
39
+ from arelle.WebCache import WebCache
39
40
 
40
41
  _: TypeGetText
41
42
 
@@ -60,7 +61,7 @@ def resourcesDir() -> str:
60
61
  # for python 3.2 remove __pycache__
61
62
  if _moduleDir.endswith("__pycache__"):
62
63
  _moduleDir = os.path.dirname(_moduleDir)
63
- if (re.match(r".*[\\/](library|python{0.major}{0.minor}).zip[\\/]arelle$".format(sys.version_info),
64
+ if (regex.match(r".*[\\/](library|python{0.major}{0.minor}).zip[\\/]arelle$".format(sys.version_info),
64
65
  _moduleDir)): # cx_Freexe uses library up to 3.4 and python35 after 3.5
65
66
  _resourcesDir = os.path.dirname(os.path.dirname(_moduleDir))
66
67
  else:
@@ -161,11 +162,12 @@ class Cntlr:
161
162
  disable_persistent_config: bool = False,
162
163
  betaFeatures: dict[str, bool] | None = None
163
164
  ) -> None:
165
+ self.logger = None
164
166
  if betaFeatures is None:
165
167
  betaFeatures = {}
166
168
  self.betaFeatures = {
167
169
  b: betaFeatures.get(b, False)
168
- for b in BETA_FEATURES_AND_DESCRIPTIONS.keys()
170
+ for b in BETA_FEATURES_AND_DESCRIPTIONS
169
171
  }
170
172
  self.errorManager = None
171
173
  self.hasWin32gui = False
@@ -297,6 +299,11 @@ class Cntlr:
297
299
  self.startLogging(logFileName, logFileMode, logFileEncoding, logFormat)
298
300
  self.errorManager = ErrorManager(self.modelManager, logging._checkLevel("INCONSISTENCY")) # type: ignore[attr-defined]
299
301
 
302
+ def validation(self, val: Validation, fileSource: FileSource | None = None) -> None:
303
+ """Same as `error`, but parameters passed in from Validation object
304
+ """
305
+ self.error(codes=val.codes, msg=val.msg, level=val.level.name, fileSource=fileSource, **val.args)
306
+
300
307
  def error(self, codes: Any, msg: str, level: str = "ERROR", fileSource: FileSource | None = None, **args: Any) -> None:
301
308
  if self.logger is None or self.errorManager is None:
302
309
  self.addToLog(
@@ -434,11 +441,11 @@ class Cntlr:
434
441
 
435
442
  def setLogLevelFilter(self, logLevelFilter: str) -> None:
436
443
  if self.logger:
437
- setattr(self.logger, "messageLevelFilter", re.compile(logLevelFilter) if logLevelFilter else None)
444
+ setattr(self.logger, "messageLevelFilter", regex.compile(logLevelFilter) if logLevelFilter else None)
438
445
 
439
446
  def setLogCodeFilter(self, logCodeFilter: str) -> None:
440
447
  if self.logger:
441
- setattr(self.logger, "messageCodeFilter", re.compile(logCodeFilter) if logCodeFilter else None)
448
+ setattr(self.logger, "messageCodeFilter", regex.compile(logCodeFilter) if logCodeFilter else None)
442
449
 
443
450
  def addToLog(
444
451
  self,
arelle/CntlrCmdLine.py CHANGED
@@ -51,14 +51,14 @@ from arelle.BetaFeatures import BETA_FEATURES_AND_DESCRIPTIONS
51
51
  from arelle.Locale import format_string, setApplicationLocale, setDisableRTL
52
52
  from arelle.ModelFormulaObject import FormulaOptions
53
53
  from arelle.ModelValue import qname
54
+ from arelle.ValidateFileSource import ValidateFileSource
54
55
  from arelle.oim.xml.Save import saveOimReportToXmlInstance
55
56
  from arelle.rendering import RenderingEvaluator
56
57
  from arelle.RuntimeOptions import RuntimeOptions, RuntimeOptionsException
57
58
  from arelle.SocketUtils import INTERNET_CONNECTIVITY, OFFLINE
58
59
  from arelle.SystemInfo import PlatformOS, getSystemInfo, getSystemWordSize, hasWebServer, isCGI, isGAE
59
60
  from arelle.typing import TypeGetText
60
- from arelle.UrlUtil import isHttpUrl
61
- from arelle.utils.EntryPointDetection import filesourceEntrypointFiles
61
+ from arelle.utils.EntryPointDetection import parseEntrypointFileInput
62
62
  from arelle.ValidateXbrlDTS import ValidateBaseTaxonomiesMode
63
63
  from arelle.WebCache import proxyTuple
64
64
 
@@ -98,6 +98,38 @@ def parseAndRun(args):
98
98
  return cntlr
99
99
 
100
100
 
101
+ PREPARSE_ARG_CONFIGS = frozenset([
102
+ (re.compile(r'^--plugins?.*$'), 'plugins'),
103
+ (re.compile(r'^--options(F|f)ile.*$'), 'optionsFile'),
104
+ ])
105
+
106
+
107
+ def preparseArgs(args: list[str], parser: OptionParser) -> dict[str, str]:
108
+ """
109
+ Some command line arguments influence the actual parsing of other arguments.
110
+ This function pre-parses those arguments to allow for processing before full
111
+ argument parseing occurs.
112
+ :param args: Command line arguments
113
+ :param parser: OptionParser to report errors
114
+ :return: Dictionary of pre-parsed options
115
+ """
116
+ preparsedArgs = {}
117
+ for i, arg in enumerate(args):
118
+ for pattern, preparsedArg in PREPARSE_ARG_CONFIGS:
119
+ if pattern.fullmatch(arg):
120
+ __, sep, value = arg.partition('=')
121
+ if sep: # --arg=value
122
+ preparsedValue = value
123
+ elif i < len(args) - 1: # --arg value
124
+ preparsedValue = args[i+1]
125
+ else: # --arg
126
+ preparsedValue = ""
127
+ if preparsedArg in preparsedArgs:
128
+ parser.error(_("Multiple '{}' values found during argument preparsing.").format(preparsedArg))
129
+ preparsedArgs[preparsedArg] = preparsedValue
130
+ return preparsedArgs
131
+
132
+
101
133
  def parseArgs(args):
102
134
  """
103
135
  Parses the command line arguments and generates runtimeOptions and arellePluginModules
@@ -412,24 +444,27 @@ def parseArgs(args):
412
444
  pluginOptionsIndex = len(parser.option_list)
413
445
  pluginOptionsGroupIndex = len(parser.option_groups)
414
446
 
447
+ preparsedArgs = preparseArgs(args, parser)
448
+
449
+ preloadPlugins = []
450
+ optionsFile = preparsedArgs.get('optionsFile')
451
+ optionsFileOptions = {}
452
+ if optionsFile:
453
+ optionsFileOptions = _parseOptionsFile(optionsFile, parser)
454
+ preloadPlugins.extend(optionsFileOptions.get('plugins', '').split('|'))
455
+
456
+ preloadPlugins.extend(preparsedArgs.get('plugins', '').split('|'))
457
+
415
458
  # install any dynamic plugins so their command line options can be parsed if present
416
459
  arellePluginModules = {}
417
- for i, arg in enumerate(args):
418
- if arg.startswith('--plugin'): # allow singular or plural (option must simply be non-ambiguous
419
- if len(arg) > 9 and arg[9] == '=':
420
- preloadPlugins = arg[10:]
421
- elif i < len(args) - 1:
422
- preloadPlugins = args[i+1]
423
- else:
424
- preloadPlugins = ""
425
- for pluginCmd in preloadPlugins.split('|'):
426
- cmd = pluginCmd.strip()
427
- if cmd not in ("show", "temp") and len(cmd) > 0 and cmd[0] not in ('-', '~', '+'):
428
- moduleInfo = PluginManager.addPluginModule(cmd)
429
- if moduleInfo:
430
- arellePluginModules[cmd] = moduleInfo
431
- PluginManager.reset()
432
- break
460
+ for pluginCmd in preloadPlugins:
461
+ cmd = pluginCmd.strip()
462
+ if cmd not in ("show", "temp") and len(cmd) > 0 and cmd[0] not in ('-', '~', '+'):
463
+ moduleInfo = PluginManager.addPluginModule(cmd)
464
+ if moduleInfo:
465
+ arellePluginModules[cmd] = moduleInfo
466
+ PluginManager.reset()
467
+
433
468
  # add plug-in options
434
469
  for optionsExtender in PluginManager.pluginClassMethods("CntlrCmdLine.Options"):
435
470
  optionsExtender(parser)
@@ -440,6 +475,10 @@ def parseArgs(args):
440
475
  help=_("Show product version, copyright, and license."))
441
476
  parser.add_option("--diagnostics", action="store_true", dest="diagnostics",
442
477
  help=_("output system diagnostics information"))
478
+ parser.add_option("--optionsFile", "--optionsfile",
479
+ action="store", dest="optionsFile",
480
+ help=_("Provide a path to a JSON file containing runtime options. "
481
+ "These options will be overridden by any command line options provided."))
443
482
 
444
483
  if not args and isGAE():
445
484
  args = ["--webserver=::gae"]
@@ -518,9 +557,22 @@ def parseArgs(args):
518
557
  for optGroup in parser.option_groups[pluginOptionsGroupIndex:pluginLastOptionsGroupIndex]:
519
558
  for groupOption in optGroup.option_list:
520
559
  pluginOptionDestinations.add(groupOption.dest)
560
+
521
561
  baseOptions = {}
522
- pluginOptions = {}
562
+ # Collect options from options file
563
+ for optionName, optionValue in optionsFileOptions.items():
564
+ if not hasattr(RuntimeOptions, optionName) and optionName not in pluginOptionDestinations:
565
+ parser.error(_("Unexpected name '{}' found in options file.").format(optionName))
566
+ continue
567
+ baseOptions[optionName] = optionValue
568
+ # Collect options from command line
523
569
  for optionName, optionValue in vars(options).items():
570
+ if optionName not in baseOptions or optionValue is not None:
571
+ baseOptions[optionName] = optionValue
572
+
573
+ pluginOptions = {}
574
+ finalOptions = {} # Validated options for RuntimeOptions
575
+ for optionName, optionValue in baseOptions.items():
524
576
  if optionName in pluginOptionDestinations:
525
577
  pluginOptions[optionName] = optionValue
526
578
  else:
@@ -532,9 +584,10 @@ def parseArgs(args):
532
584
  parser.error(_("--testcaseExpectedErrors must be in the format '--testcaseExpectedErrors=testcase-index.xml:v-1|errorCode1,errorCode2,...'"))
533
585
  expectedErrors[expectedErrorSplit[0]] = expectedErrorSplit[1].split(',')
534
586
  optionValue = expectedErrors
535
- baseOptions[optionName] = optionValue
587
+ if optionValue is not None or optionName not in finalOptions:
588
+ finalOptions[optionName] = optionValue
536
589
  try:
537
- runtimeOptions = RuntimeOptions(pluginOptions=pluginOptions, **baseOptions)
590
+ runtimeOptions = RuntimeOptions(pluginOptions=pluginOptions, **finalOptions)
538
591
  except RuntimeOptionsException as e:
539
592
  parser.error(f"{e}, please try\n python CntlrCmdLine.py --help")
540
593
  if (
@@ -620,7 +673,33 @@ class ParserForDynamicPlugins:
620
673
 
621
674
 
622
675
  def _pluginHasCliOptions(moduleInfo):
623
- return "CntlrCmdLine.Options" in moduleInfo["classMethods"]
676
+ if "CntlrCmdLine.Options" in moduleInfo["classMethods"]:
677
+ return True
678
+ if imports := moduleInfo.get("imports"):
679
+ return any(_pluginHasCliOptions(importedModule) for importedModule in imports)
680
+ return False
681
+
682
+
683
+ def _parseOptionsFile(optionsFile: str, parser: OptionParser) -> dict:
684
+ """
685
+ Parse the JSON options within the provided filepath.
686
+ :param optionsFile: The path to the JSON options file.
687
+ :param parser: The parser to log an error to if needed.
688
+ :return: The parsed options as a dictionary.
689
+ """
690
+ try:
691
+ with open(optionsFile) as f:
692
+ jsonOptions = json.load(f)
693
+ except OSError:
694
+ parser.error(_("Options file path does not exist: {}").format(optionsFile))
695
+ return {}
696
+ except Exception as e:
697
+ parser.error(_("Unable to parse options JSON file: {}").format(e))
698
+ return {}
699
+ if not isinstance(jsonOptions, dict):
700
+ parser.error(_("Options JSON file must contain a JSON object at its root."))
701
+ return {}
702
+ return jsonOptions
624
703
 
625
704
 
626
705
  class CntlrCmdLine(Cntlr.Cntlr):
@@ -823,6 +902,9 @@ class CntlrCmdLine(Cntlr.Cntlr):
823
902
  else:
824
903
  self.modelManager.disclosureSystem.select(None) # just load ordinary mappings
825
904
  self.modelManager.validateDisclosureSystem = False
905
+ if self.modelManager.disclosureSystem.keepOpen:
906
+ # Force keepOpen if specified by disclosure system.
907
+ options.keepOpen = True
826
908
  if options.baseTaxonomyValidationMode is not None:
827
909
  self.modelManager.baseTaxonomyValidationMode = ValidateBaseTaxonomiesMode.fromName(options.baseTaxonomyValidationMode)
828
910
  self.modelManager.validateXmlOim = bool(options.validateXmlOim)
@@ -947,6 +1029,7 @@ class CntlrCmdLine(Cntlr.Cntlr):
947
1029
  if options.testcaseFilters:
948
1030
  fo.testcaseFilters = options.testcaseFilters
949
1031
  if options.testcaseResultsCaptureWarnings:
1032
+ self.errorManager.setErrorCaptureLevel(logging._checkLevel("WARNING"))
950
1033
  fo.testcaseResultsCaptureWarnings = True
951
1034
  if options.testcaseResultOptions:
952
1035
  fo.testcaseResultOptions = options.testcaseResultOptions
@@ -974,44 +1057,19 @@ class CntlrCmdLine(Cntlr.Cntlr):
974
1057
  if not (options.entrypointFile or sourceZipStream):
975
1058
  return True # success
976
1059
 
1060
+ entrypointParseResult = parseEntrypointFileInput(self, options.entrypointFile, sourceZipStream)
1061
+ if not entrypointParseResult.success:
1062
+ return False
1063
+ filesource = entrypointParseResult.filesource
1064
+ _entrypointFiles = entrypointParseResult.entrypointFiles
977
1065
  success = True
978
- # entrypointFile may be absent (if input is a POSTED zip or file name ending in .zip)
979
- # or may be a | separated set of file names
980
- _entryPoints = []
981
- _checkIfXmlIsEis = self.modelManager.disclosureSystem and self.modelManager.disclosureSystem.validationType == "EFM"
982
- if options.entrypointFile:
983
- _f = options.entrypointFile
984
- try: # may be a json list
985
- _entryPoints = json.loads(_f)
986
- _checkIfXmlIsEis = False # json entry objects never specify an xml EIS archive
987
- except ValueError as e:
988
- # is it malformed json?
989
- if _f.startswith("[{") or _f.endswith("]}") or '"file:"' in _f:
990
- self.addToLog(_("File name parameter appears to be malformed JSON: {}\n{}").format(e, _f),
991
- messageCode="FileNameFormatError",
992
- level=logging.ERROR)
993
- success = False
994
- else: # try as file names separated by '|'
995
- for f in (_f or '').split('|'):
996
- if not sourceZipStream and not isHttpUrl(f) and not os.path.isabs(f):
997
- f = os.path.normpath(os.path.join(os.getcwd(), f)) # make absolute normed path
998
- _entryPoints.append({"file":f})
999
- filesource = None # file source for all instances if not None
1000
- if sourceZipStream:
1001
- filesource = FileSource.openFileSource(None, self, sourceZipStream)
1002
- elif len(_entryPoints) == 1 and "file" in _entryPoints[0]: # check if an archive and need to discover entry points (and not IXDS)
1003
- entryPath = PackageManager.mappedUrl(_entryPoints[0]["file"])
1004
- filesource = FileSource.openFileSource(entryPath, self, checkIfXmlIsEis=_checkIfXmlIsEis)
1005
- _entrypointFiles = _entryPoints
1006
- if filesource and not filesource.selection and not (sourceZipStream and len(_entrypointFiles) > 0):
1007
- try:
1008
- filesourceEntrypointFiles(filesource, _entrypointFiles)
1009
- except Exception as err:
1010
- self.addToLog(str(err), messageCode="error", level=logging.ERROR)
1011
- return False
1012
1066
 
1013
1067
  for pluginXbrlMethod in PluginManager.pluginClassMethods("CntlrCmdLine.Filing.Start"):
1014
1068
  pluginXbrlMethod(self, options, filesource, _entrypointFiles, sourceZipStream=sourceZipStream, responseZipStream=responseZipStream)
1069
+
1070
+ if options.validate and filesource is not None:
1071
+ ValidateFileSource(self, filesource).validate(options.reportPackage)
1072
+
1015
1073
  if len(_entrypointFiles) == 0 and not options.packages:
1016
1074
  if options.entrypointFile:
1017
1075
  msg = _("No XBRL entry points could be loaded from provided file: {}").format(options.entrypointFile)
@@ -1027,6 +1085,8 @@ class CntlrCmdLine(Cntlr.Cntlr):
1027
1085
  else:
1028
1086
  _entrypointFile = PackageManager.mappedUrl(_entrypointFile)
1029
1087
  filesource = FileSource.openFileSource(_entrypointFile, self, sourceZipStream)
1088
+ if options.validate:
1089
+ ValidateFileSource(self, filesource).validate(options.reportPackage)
1030
1090
  self.entrypointFile = _entrypointFile
1031
1091
  timeNow = XmlUtil.dateunionValue(datetime.datetime.now())
1032
1092
  firstStartedAt = startedAt = time.time()
@@ -1239,6 +1299,11 @@ class CntlrCmdLine(Cntlr.Cntlr):
1239
1299
  self.modelManager.close(modelDiffReport)
1240
1300
  elif modelXbrl:
1241
1301
  self.modelManager.close(modelXbrl)
1302
+
1303
+ if options.validate:
1304
+ for pluginXbrlMethod in PluginManager.pluginClassMethods("Validate.Complete"):
1305
+ pluginXbrlMethod(self, filesource)
1306
+
1242
1307
  if filesource is not None and not options.keepOpen:
1243
1308
  # Archive filesource potentially used by multiple reports may still be open.
1244
1309
  filesource.close()
arelle/CntlrWinMain.py CHANGED
@@ -5,65 +5,110 @@ See COPYRIGHT.md for copyright information.
5
5
  '''
6
6
  from __future__ import annotations
7
7
 
8
- from arelle import ValidateDuplicateFacts
9
- import os, sys, subprocess, pickle, time, locale, fnmatch, platform, webbrowser
8
+ import fnmatch
9
+ import os
10
+ import pickle
11
+ import platform
12
+ import subprocess
13
+ import sys
14
+ import time
15
+ import webbrowser
16
+ from typing import Any
17
+
10
18
  import regex as re
11
19
 
20
+ from arelle import UrlUtil, ValidateDuplicateFacts
21
+ from arelle.ValidateFileSource import ValidateFileSource
12
22
  from arelle.logging.formatters.LogFormatter import logRefsFileLines
23
+ from arelle.utils.EntryPointDetection import parseEntrypointFileInput
13
24
 
14
25
  if sys.platform == 'win32' and getattr(sys, 'frozen', False):
15
26
  # need the .dll directory in path to be able to access Tk and Tcl DLLs efore importinng Tk, etc.
16
27
  os.environ['PATH'] = os.path.dirname(sys.executable) + ";" + os.environ['PATH']
17
28
 
18
- from tkinter import (Tk, Tcl, TclError, Toplevel, Menu, PhotoImage, StringVar, BooleanVar, IntVar, N, S, E, W, EW,
19
- HORIZONTAL, VERTICAL, END, font as tkFont)
29
+ from tkinter import (
30
+ END,
31
+ EW,
32
+ HORIZONTAL,
33
+ VERTICAL,
34
+ BooleanVar,
35
+ E,
36
+ IntVar,
37
+ Menu,
38
+ N,
39
+ PhotoImage,
40
+ S,
41
+ StringVar,
42
+ Tcl,
43
+ TclError,
44
+ Tk,
45
+ W,
46
+ )
47
+ from tkinter import font as tkFont
48
+
20
49
  try:
21
- from tkinter.ttk import Frame, Button, Label, Combobox, Separator, PanedWindow, Notebook
50
+ from tkinter.ttk import Button, Combobox, Frame, Label, Notebook, PanedWindow, Separator
22
51
  except ImportError: # 3.0 versions of tkinter
23
- from ttk import Frame, Button, Label, Combobox, Separator, PanedWindow, Notebook
52
+ from ttk import Button, Combobox, Frame, Label, Notebook, PanedWindow, Separator
24
53
  try:
25
54
  import syslog
26
55
  except ImportError:
27
56
  syslog = None
57
+
58
+ import logging
59
+ import multiprocessing
60
+ import queue
61
+ import threading
28
62
  import tkinter.filedialog
29
- import tkinter.messagebox, traceback
63
+ import tkinter.messagebox
30
64
  import tkinter.simpledialog
31
- from arelle.Locale import format_string, setApplicationLocale
65
+ import traceback
66
+
67
+ from arelle import (
68
+ Cntlr,
69
+ DialogLanguage,
70
+ DialogPackageManager,
71
+ DialogPluginManager,
72
+ DialogURL,
73
+ ModelDocument,
74
+ PackageManager,
75
+ TableStructure,
76
+ Updater,
77
+ ViewFileConcepts,
78
+ ViewFileDTS,
79
+ ViewFileFactList,
80
+ ViewFileFactTable,
81
+ ViewFileFormulae,
82
+ ViewFileRelationshipSet,
83
+ ViewFileRoleTypes,
84
+ ViewFileTests,
85
+ ViewWinConcepts,
86
+ ViewWinDTS,
87
+ ViewWinFactList,
88
+ ViewWinFactTable,
89
+ ViewWinFormulae,
90
+ ViewWinProperties,
91
+ ViewWinRelationshipSet,
92
+ ViewWinRenderedGrid,
93
+ ViewWinRoleTypes,
94
+ ViewWinRssFeed,
95
+ ViewWinTests,
96
+ ViewWinTree,
97
+ ViewWinVersReport,
98
+ ViewWinXml,
99
+ XbrlConst,
100
+ )
32
101
  from arelle.CntlrWinTooltip import ToolTip
33
- from arelle import XbrlConst
102
+ from arelle.FileSource import openFileSource
103
+ from arelle.Locale import format_string, setApplicationLocale
104
+ from arelle.ModelFormulaObject import FormulaOptions
105
+ from arelle.oim.xml.Save import saveOimReportToXmlInstance
34
106
  from arelle.PluginManager import pluginClassMethods
107
+ from arelle.rendering import RenderingEvaluator
35
108
  from arelle.UrlUtil import isHttpUrl
36
109
  from arelle.ValidateXbrlCalcs import ValidateCalcsMode as CalcsMode
37
110
  from arelle.ValidateXbrlDTS import ValidateBaseTaxonomiesMode
38
111
  from arelle.Version import copyrightLabel
39
- from arelle.oim.xml.Save import saveOimReportToXmlInstance
40
- import logging
41
- import multiprocessing
42
-
43
- import threading, queue
44
-
45
- from arelle import Cntlr
46
- from arelle import (DialogURL, DialogLanguage,
47
- DialogPluginManager, DialogPackageManager,
48
- ModelDocument,
49
- ModelManager,
50
- PackageManager,
51
- TableStructure,
52
- ViewWinDTS,
53
- ViewWinProperties, ViewWinConcepts, ViewWinRelationshipSet, ViewWinFormulae,
54
- ViewWinFactList, ViewFileFactList, ViewWinFactTable, ViewWinRenderedGrid, ViewWinXml,
55
- ViewWinRoleTypes, ViewFileRoleTypes, ViewFileConcepts,
56
- ViewWinTests, ViewWinTree, ViewWinVersReport, ViewWinRssFeed,
57
- ViewFileDTS,
58
- ViewFileFactTable,
59
- ViewFileFormulae,
60
- ViewFileTests,
61
- ViewFileRelationshipSet,
62
- Updater
63
- )
64
- from arelle.ModelFormulaObject import FormulaOptions
65
- from arelle.FileSource import openFileSource
66
- from arelle.rendering import RenderingEvaluator
67
112
 
68
113
  restartMain = True
69
114
 
@@ -816,15 +861,25 @@ class CntlrWinMain (Cntlr.Cntlr):
816
861
  if filename:
817
862
  for xbrlLoadedMethod in pluginClassMethods("CntlrWinMain.Xbrl.Open"):
818
863
  filename = xbrlLoadedMethod(self, filename) # runs in GUI thread, allows mapping filename, mult return filename
819
- filesource = None
864
+ entrypointParseResult = parseEntrypointFileInput(self, filename, fallbackSelect=False)
865
+ if not entrypointParseResult.success or entrypointParseResult.filesource is None:
866
+ return
867
+ filesource = entrypointParseResult.filesource
868
+ entrypointFiles = entrypointParseResult.entrypointFiles
820
869
  # check for archive files
821
- filesource = openFileSource(filename, self,
822
- checkIfXmlIsEis=self.modelManager.disclosureSystem and
823
- self.modelManager.disclosureSystem.validationType == "EFM")
824
870
  if filesource.isArchive:
825
- if not filesource.selection and not filesource.isReportPackage: # or filesource.isRss:
871
+ filenameWithoutFakeIxdsPrefix = UrlUtil.stripIxdsSurrogatePrefix(filename)
872
+ if all(e.get("file") == filenameWithoutFakeIxdsPrefix for e in entrypointFiles):
873
+ entrypointFiles = []
874
+ if (
875
+ len(entrypointFiles) == 0 and
876
+ not filesource.selection and
877
+ not filesource.isReportPackage
878
+ ):
826
879
  from arelle import DialogOpenArchive
827
880
  filename = DialogOpenArchive.askArchiveFile(self, filesource)
881
+ if filename is not None:
882
+ entrypointFiles.append({"file": filename})
828
883
  if filename and filesource.basefile and not isHttpUrl(filesource.basefile):
829
884
  self.config["fileOpenDir"] = os.path.dirname(filesource.baseurl)
830
885
  filesource.loadTaxonomyPackageMappings() # if a package, load mappings if not loaded yet
@@ -837,7 +892,7 @@ class CntlrWinMain (Cntlr.Cntlr):
837
892
  self.updateFileHistory(filename, importToDTS)
838
893
  elif len(filename) == 1:
839
894
  self.updateFileHistory(filename[0], importToDTS)
840
- thread = threading.Thread(target=self.backgroundLoadXbrl, args=(filesource,importToDTS,selectTopView), daemon=True).start()
895
+ thread = threading.Thread(target=self.backgroundLoadXbrl, args=(filesource,entrypointFiles,importToDTS,selectTopView), daemon=True).start()
841
896
 
842
897
  def webOpen(self, *ignore):
843
898
  if not self.okayToContinue():
@@ -848,12 +903,24 @@ class CntlrWinMain (Cntlr.Cntlr):
848
903
  self.updateFileHistory(url, False)
849
904
  for xbrlLoadedMethod in pluginClassMethods("CntlrWinMain.Xbrl.Open"):
850
905
  url = xbrlLoadedMethod(self, url) # runs in GUI thread, allows mapping url, mult return url
851
- filesource = openFileSource(url,self)
852
- if filesource.isArchive and not filesource.isReportPackage and not filesource.selection: # or filesource.isRss:
853
- from arelle import DialogOpenArchive
854
- url = DialogOpenArchive.askArchiveFile(self, filesource)
855
- self.updateFileHistory(url, False)
856
- thread = threading.Thread(target=self.backgroundLoadXbrl, args=(filesource,False,False), daemon=True).start()
906
+ entrypointParseResult = parseEntrypointFileInput(self, url, fallbackSelect=False)
907
+ if not entrypointParseResult.success or entrypointParseResult.filesource is None:
908
+ return
909
+ filesource = entrypointParseResult.filesource
910
+ entrypointFiles = entrypointParseResult.entrypointFiles
911
+ if filesource.isArchive:
912
+ if (
913
+ len(entrypointFiles) == 0 and
914
+ not filesource.selection and
915
+ not filesource.isReportPackage
916
+ ):
917
+ from arelle import DialogOpenArchive
918
+ url = DialogOpenArchive.askArchiveFile(self, filesource)
919
+ if url is not None:
920
+ entrypointFiles.append({"file": url})
921
+ self.updateFileHistory(url, False)
922
+ filesource.loadTaxonomyPackageMappings()
923
+ thread = threading.Thread(target=self.backgroundLoadXbrl, args=(filesource,entrypointFiles,False,False), daemon=True).start()
857
924
 
858
925
  def importWebOpen(self, *ignore):
859
926
  if not self.modelManager.modelXbrl or self.modelManager.modelXbrl.modelDocument.type not in (
@@ -867,36 +934,37 @@ class CntlrWinMain (Cntlr.Cntlr):
867
934
  self.fileOpenFile(url, importToDTS=True)
868
935
 
869
936
 
870
- def backgroundLoadXbrl(self, filesource, importToDTS, selectTopView):
937
+ def backgroundLoadXbrl(self, filesource, entrypointFiles: list[dict[str, Any]], importToDTS, selectTopView):
871
938
  startedAt = time.time()
872
939
  loadedModels = []
873
940
  try:
874
- if importToDTS:
875
- action = _("imported")
876
- profileStat = "import"
877
- modelXbrl = self.modelManager.modelXbrl
878
- if modelXbrl:
879
- ModelDocument.load(modelXbrl, filesource.url, isSupplemental=importToDTS)
880
- modelXbrl.relationshipSets.clear() # relationships have to be re-cached
881
- loadedModels.append(modelXbrl)
882
- else:
883
- action = _("loaded")
884
- profileStat = "load"
885
- if (reportPackage := filesource.reportPackage) and "_IXDS#?#" not in filesource.url:
886
- for report in reportPackage.reports or []:
887
- if len(report.fullPathFiles) > 1:
888
- self.addToLog(_("Loading error. Inline document set encountered. Enable 'Inline XBRL Document Set' plug-in and use the Open Inline Doc Set dialog from the file menu to open this filing: {0}").format(filesource.url))
889
- continue
890
- filesource.select(report.fullPathPrimary)
891
- modelXbrl = self.modelManager.load(filesource, _("views loading"))
892
- if modelXbrl:
893
- loadedModels.append(modelXbrl)
941
+ action = _("preparing entrypoints")
942
+ for pluginXbrlMethod in pluginClassMethods("CntlrWinMain.Filing.Start"):
943
+ pluginXbrlMethod(self, filesource, entrypointFiles)
944
+ for entrypoint in entrypointFiles:
945
+ entrypointFile = entrypoint.get("file", None) if isinstance(entrypoint,dict) else entrypoint
946
+ if filesource and filesource.isArchive:
947
+ filesource.select(entrypointFile)
894
948
  else:
949
+ entrypointFile = PackageManager.mappedUrl(entrypointFile)
950
+ filesource = openFileSource(entrypointFile, self)
951
+ if importToDTS:
952
+ action = _("imported")
953
+ profileStat = "import"
954
+ modelXbrl = self.modelManager.modelXbrl
955
+ if modelXbrl:
956
+ ModelDocument.load(modelXbrl, filesource.url, isSupplemental=importToDTS)
957
+ modelXbrl.relationshipSets.clear() # relationships have to be re-cached
958
+ loadedModels.append(modelXbrl)
959
+ else:
960
+ action = _("loaded")
961
+ profileStat = "load"
895
962
  modelXbrl = self.modelManager.load(
896
963
  filesource,
897
964
  _("views loading"),
898
965
  # check modified time if GUI-loading from web
899
966
  checkModifiedTime=isHttpUrl(filesource.url),
967
+ entrypoint=entrypoint,
900
968
  )
901
969
  if modelXbrl:
902
970
  loadedModels.append(modelXbrl)
@@ -1135,12 +1203,17 @@ class CntlrWinMain (Cntlr.Cntlr):
1135
1203
 
1136
1204
  def backgroundValidate(self):
1137
1205
  from arelle import Validate
1206
+ validatedFileSources = set()
1138
1207
  for loadedModelXbrl in self.modelManager.loadedModelXbrls:
1139
1208
  if loadedModelXbrl.modelDocument:
1140
1209
  startedAt = time.time()
1141
1210
  if loadedModelXbrl.modelDocument.type in ModelDocument.Type.TESTCASETYPES:
1142
1211
  for pluginXbrlMethod in pluginClassMethods("Testcases.Start"):
1143
1212
  pluginXbrlMethod(self, None, loadedModelXbrl)
1213
+ if loadedModelXbrl.fileSource not in validatedFileSources:
1214
+ validatedFileSources.add(loadedModelXbrl.fileSource)
1215
+ ValidateFileSource(self, loadedModelXbrl.fileSource).validate(self.modelManager.validateAllFilesAsReportPackages)
1216
+
1144
1217
  for modelXbrl in [loadedModelXbrl] + getattr(loadedModelXbrl, "supplementalModelXbrls", []):
1145
1218
  priorOutputInstance = modelXbrl.formulaOutputInstance
1146
1219
  modelXbrl.formulaOutputInstance = None # prevent closing on background thread by validateFormula