arelle-release 2.36.30__py3-none-any.whl → 2.36.32__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of arelle-release might be problematic. Click here for more details.

Files changed (45) hide show
  1. arelle/CntlrCmdLine.py +90 -52
  2. arelle/CntlrWinMain.py +29 -2
  3. arelle/ModelManager.py +2 -0
  4. arelle/ModelValue.py +5 -4
  5. arelle/RuntimeOptions.py +1 -0
  6. arelle/ValidateXbrlDTS.py +83 -27
  7. arelle/ViewFileRelationshipSet.py +4 -2
  8. arelle/ViewFileRenderedGrid.py +1 -1
  9. arelle/ViewFileRenderedLayout.py +0 -1
  10. arelle/ViewWinRelationshipSet.py +4 -3
  11. arelle/ViewWinRenderedGrid.py +1 -1
  12. arelle/XmlUtil.py +1 -1
  13. arelle/_version.py +2 -2
  14. arelle/formula/XPathParser.py +1 -1
  15. arelle/plugin/saveLoadableOIM.py +38 -5
  16. arelle/rendering/RenderingLayout.py +1 -1
  17. arelle/rendering/RenderingResolution.py +2 -2
  18. {arelle_release-2.36.30.dist-info → arelle_release-2.36.32.dist-info}/METADATA +3 -2
  19. {arelle_release-2.36.30.dist-info → arelle_release-2.36.32.dist-info}/RECORD +45 -45
  20. {arelle_release-2.36.30.dist-info → arelle_release-2.36.32.dist-info}/WHEEL +1 -1
  21. tests/integration_tests/ui_tests/ArelleGUITest/ArelleGUITest/ArelleGUITest.csproj +2 -2
  22. tests/integration_tests/validation/conformance_suite_configurations/cipc_current.py +1 -0
  23. tests/integration_tests/validation/conformance_suite_configurations/dba_current.py +1 -0
  24. tests/integration_tests/validation/conformance_suite_configurations/dba_multi_current.py +1 -0
  25. tests/integration_tests/validation/conformance_suite_configurations/esef_ixbrl_2021.py +1 -0
  26. tests/integration_tests/validation/conformance_suite_configurations/esef_ixbrl_2022.py +1 -0
  27. tests/integration_tests/validation/conformance_suite_configurations/esef_ixbrl_2023.py +1 -0
  28. tests/integration_tests/validation/conformance_suite_configurations/esef_ixbrl_2024.py +1 -0
  29. tests/integration_tests/validation/conformance_suite_configurations/esef_xhtml_2021.py +1 -0
  30. tests/integration_tests/validation/conformance_suite_configurations/esef_xhtml_2022.py +1 -0
  31. tests/integration_tests/validation/conformance_suite_configurations/esef_xhtml_2023.py +1 -0
  32. tests/integration_tests/validation/conformance_suite_configurations/esef_xhtml_2024.py +1 -0
  33. tests/integration_tests/validation/conformance_suite_configurations/hmrc_current.py +1 -0
  34. tests/integration_tests/validation/conformance_suite_configurations/kvk_nt16.py +1 -0
  35. tests/integration_tests/validation/conformance_suite_configurations/kvk_nt17.py +1 -0
  36. tests/integration_tests/validation/conformance_suite_configurations/kvk_nt18.py +1 -0
  37. tests/integration_tests/validation/conformance_suite_configurations/kvk_nt19.py +1 -0
  38. tests/integration_tests/validation/conformance_suite_configurations/nl_nt16.py +1 -0
  39. tests/integration_tests/validation/conformance_suite_configurations/nl_nt17.py +1 -0
  40. tests/integration_tests/validation/conformance_suite_configurations/nl_nt18.py +1 -0
  41. tests/integration_tests/validation/conformance_suite_configurations/nl_nt19.py +1 -0
  42. tests/integration_tests/validation/conformance_suite_configurations/ros_current.py +1 -0
  43. {arelle_release-2.36.30.dist-info → arelle_release-2.36.32.dist-info}/entry_points.txt +0 -0
  44. {arelle_release-2.36.30.dist-info → arelle_release-2.36.32.dist-info/licenses}/LICENSE.md +0 -0
  45. {arelle_release-2.36.30.dist-info → arelle_release-2.36.32.dist-info}/top_level.txt +0 -0
arelle/CntlrCmdLine.py CHANGED
@@ -6,33 +6,59 @@ This module is Arelle's controller in command line non-interactive mode
6
6
  See COPYRIGHT.md for copyright information.
7
7
  '''
8
8
  from __future__ import annotations
9
- from arelle import PackageManager, ValidateDuplicateFacts
10
- import gettext, time, datetime, os, shlex, sys, traceback, fnmatch, threading, json, logging, platform
11
- from optparse import OptionGroup, OptionParser, SUPPRESS_HELP, Option
9
+
10
+ import datetime
11
+ import fnmatch
12
+ import gettext
13
+ import glob
14
+ import json
15
+ import logging
16
+ import os
17
+ import platform
18
+ import shlex
19
+ import sys
20
+ import threading
21
+ import time
22
+ import traceback
23
+ from optparse import SUPPRESS_HELP, Option, OptionGroup, OptionParser
24
+ from pprint import pprint
25
+
12
26
  import regex as re
13
- from arelle import (Cntlr, FileSource, ModelDocument, XmlUtil, XbrlConst, Version,
14
- ViewFileDTS, ViewFileFactList, ViewFileFactTable, ViewFileConcepts,
15
- ViewFileFormulae, ViewFileRelationshipSet, ViewFileTests, ViewFileRssFeed,
16
- ViewFileRoleTypes)
17
- from arelle.oim.xml.Save import saveOimReportToXmlInstance
18
- from arelle.rendering import RenderingEvaluator
19
- from arelle.RuntimeOptions import RuntimeOptions, RuntimeOptionsException
27
+ from lxml import etree
28
+
29
+ from arelle import (
30
+ Cntlr,
31
+ FileSource,
32
+ ModelDocument,
33
+ PackageManager,
34
+ PluginManager,
35
+ ValidateDuplicateFacts,
36
+ Version,
37
+ ViewFileConcepts,
38
+ ViewFileDTS,
39
+ ViewFileFactList,
40
+ ViewFileFactTable,
41
+ ViewFileFormulae,
42
+ ViewFileRelationshipSet,
43
+ ViewFileRoleTypes,
44
+ ViewFileRssFeed,
45
+ ViewFileTests,
46
+ XbrlConst,
47
+ XmlUtil,
48
+ )
20
49
  from arelle.BetaFeatures import BETA_FEATURES_AND_DESCRIPTIONS
21
- from arelle.ModelValue import qname
22
50
  from arelle.Locale import format_string, setApplicationLocale, setDisableRTL
23
51
  from arelle.ModelFormulaObject import FormulaOptions
24
- from arelle import PluginManager
25
- from arelle.PluginManager import pluginClassMethods
52
+ from arelle.ModelValue import qname
53
+ from arelle.oim.xml.Save import saveOimReportToXmlInstance
54
+ from arelle.rendering import RenderingEvaluator
55
+ from arelle.RuntimeOptions import RuntimeOptions, RuntimeOptionsException
26
56
  from arelle.SocketUtils import INTERNET_CONNECTIVITY, OFFLINE
57
+ from arelle.SystemInfo import PlatformOS, getSystemInfo, getSystemWordSize, hasWebServer, isCGI, isGAE
27
58
  from arelle.typing import TypeGetText
28
59
  from arelle.UrlUtil import isHttpUrl
29
- from arelle.Version import copyrightLabel
60
+ from arelle.ValidateXbrlDTS import ValidateBaseTaxonomiesMode
30
61
  from arelle.WebCache import proxyTuple
31
- from arelle.SystemInfo import getSystemInfo, getSystemWordSize, hasWebServer, isCGI, isGAE, PlatformOS
32
- from pprint import pprint
33
- import logging
34
- from lxml import etree
35
- import glob
36
62
 
37
63
  win32file = win32api = win32process = pywintypes = None
38
64
  STILL_ACTIVE = 259 # MS Windows process status constants
@@ -51,10 +77,7 @@ def main():
51
77
  :type message: [str]
52
78
  """
53
79
  envArgs = os.getenv("ARELLE_ARGS")
54
- if envArgs:
55
- args = shlex.split(envArgs)
56
- else:
57
- args = sys.argv[1:]
80
+ args = shlex.split(envArgs) if envArgs else sys.argv[1:]
58
81
  setApplicationLocale()
59
82
  gettext.install("arelle")
60
83
  parseAndRun(args)
@@ -96,7 +119,7 @@ def parseArgs(args):
96
119
  cntlr = CntlrCmdLine(uiLang=uiLang, disable_persistent_config=disable_persistent_config) # This Cntlr is needed for translations and to enable the web cache. The cntlr is not used outside the parse function
97
120
  usage = "usage: %prog [options]"
98
121
  parser = OptionParser(usage,
99
- version="Arelle(r) {0} ({1}bit)".format(Version.__version__, getSystemWordSize()),
122
+ version=f"Arelle(r) {Version.__version__} ({getSystemWordSize()}bit)",
100
123
  conflict_handler="resolve") # allow reloading plug-in options without errors
101
124
  parser.add_option("-f", "--file", dest="entrypointFile",
102
125
  help=_("FILENAME is an entry point, which may be "
@@ -137,6 +160,15 @@ def parseArgs(args):
137
160
  choices=[a.value for a in ValidateDuplicateFacts.DUPLICATE_TYPE_ARG_MAP],
138
161
  dest="validateDuplicateFacts",
139
162
  help=_("Select which types of duplicates should trigger warnings."))
163
+ parser.add_option("--baseTaxonomyValidation", "--basetaxonomyvalidation",
164
+ choices=("disclosureSystem", "none", "all"),
165
+ dest="baseTaxonomyValidationMode",
166
+ default="disclosureSystem",
167
+ help=_("""Specify if base taxonomies should be validated.
168
+ Skipping validation of base taxonomy files which are known to be valid can significantly reduce validation time.
169
+ disclosureSystem - (default) skip validation of base taxonomy files which are known to be valid by the disclosure system
170
+ none - skip validation of all base taxonomies
171
+ all - validate all base taxonomies"""))
140
172
  parser.add_option("--saveOIMToXMLReport", "--saveoimtoxmlreport", "--saveOIMinstance", "--saveoiminstance",
141
173
  action="store",
142
174
  dest="saveOIMToXMLReport",
@@ -397,7 +429,7 @@ def parseArgs(args):
397
429
  PluginManager.reset()
398
430
  break
399
431
  # add plug-in options
400
- for optionsExtender in pluginClassMethods("CntlrCmdLine.Options"):
432
+ for optionsExtender in PluginManager.pluginClassMethods("CntlrCmdLine.Options"):
401
433
  optionsExtender(parser)
402
434
  pluginLastOptionIndex = len(parser.option_list)
403
435
  pluginLastOptionsGroupIndex = len(parser.option_groups)
@@ -459,7 +491,7 @@ def parseArgs(args):
459
491
  version=Version.__version__,
460
492
  wordSize=getSystemWordSize(),
461
493
  platform=platform.machine(),
462
- copyrightLabel=copyrightLabel,
494
+ copyrightLabel=Version.copyrightLabel,
463
495
  pythonVersion=f'{sys.version_info[0]}.{sys.version_info[1]}.{sys.version_info[2]}',
464
496
  lxmlVersion=f'{etree.LXML_VERSION[0]}.{etree.LXML_VERSION[1]}.{etree.LXML_VERSION[2]}',
465
497
  bottleCopyright="\n Bottle (c) 2011-2013 Marcel Hellkamp" if hasWebServer() else ""
@@ -568,7 +600,7 @@ def filesourceEntrypointFiles(filesource, entrypointFiles=None, inlineOnly=False
568
600
  if report.isInline:
569
601
  reportEntries = [{"file": f} for f in report.fullPathFiles]
570
602
  ixdsDiscovered = False
571
- for pluginXbrlMethod in pluginClassMethods("InlineDocumentSet.Discovery"):
603
+ for pluginXbrlMethod in PluginManager.pluginClassMethods("InlineDocumentSet.Discovery"):
572
604
  pluginXbrlMethod(filesource, reportEntries)
573
605
  ixdsDiscovered = True
574
606
  if not ixdsDiscovered and len(reportEntries) > 1:
@@ -590,13 +622,17 @@ def filesourceEntrypointFiles(filesource, entrypointFiles=None, inlineOnly=False
590
622
  entrypointFiles.append({"file":url})
591
623
  if entrypointFiles:
592
624
  if identifiedType == ModelDocument.Type.INLINEXBRL:
593
- for pluginXbrlMethod in pluginClassMethods("InlineDocumentSet.Discovery"):
625
+ for pluginXbrlMethod in PluginManager.pluginClassMethods("InlineDocumentSet.Discovery"):
594
626
  pluginXbrlMethod(filesource, entrypointFiles) # group into IXDS if plugin feature is available
595
627
  break # found inline (or non-inline) entrypoint files, don't look for any other type
596
628
  # for ESEF non-consolidated xhtml documents accept an xhtml entry point
597
629
  if not entrypointFiles and not inlineOnly:
598
630
  for url in urlsByType.get(ModelDocument.Type.HTML, []):
599
631
  entrypointFiles.append({"file":url})
632
+ if not entrypointFiles and filesource.taxonomyPackage is not None:
633
+ for packageEntry in filesource.taxonomyPackage.get('entryPoints', {}).values():
634
+ for _resolvedUrl, remappedUrl, _closest in packageEntry:
635
+ entrypointFiles.append({"file": remappedUrl})
600
636
 
601
637
 
602
638
  elif os.path.isdir(filesource.url):
@@ -611,7 +647,7 @@ def filesourceEntrypointFiles(filesource, entrypointFiles=None, inlineOnly=False
611
647
  if identifiedType in (ModelDocument.Type.INSTANCE, ModelDocument.Type.INLINEXBRL):
612
648
  entrypointFiles.append({"file":_path})
613
649
  if hasInline: # group into IXDS if plugin feature is available
614
- for pluginXbrlMethod in pluginClassMethods("InlineDocumentSet.Discovery"):
650
+ for pluginXbrlMethod in PluginManager.pluginClassMethods("InlineDocumentSet.Discovery"):
615
651
  pluginXbrlMethod(filesource, entrypointFiles)
616
652
 
617
653
  return entrypointFiles
@@ -652,7 +688,7 @@ class CntlrCmdLine(Cntlr.Cntlr):
652
688
  """
653
689
 
654
690
  def __init__(self, logFileName=None, uiLang=None, disable_persistent_config=False):
655
- super(CntlrCmdLine, self).__init__(hasGui=False, uiLang=uiLang, disable_persistent_config=disable_persistent_config)
691
+ super().__init__(hasGui=False, uiLang=uiLang, disable_persistent_config=disable_persistent_config)
656
692
  self.preloadedPlugins = {}
657
693
 
658
694
  def run(self, options: RuntimeOptions, sourceZipStream=None, responseZipStream=None, sourceZipStreamFileName=None) -> bool:
@@ -664,24 +700,24 @@ class CntlrCmdLine(Cntlr.Cntlr):
664
700
  :param options: OptionParser options from parse_args of main argv arguments (when called from command line) or corresponding arguments from web service (REST) request.
665
701
  :type options: optparse.Values
666
702
  """
667
- for b in BETA_FEATURES_AND_DESCRIPTIONS.keys():
703
+ for b in BETA_FEATURES_AND_DESCRIPTIONS:
668
704
  self.betaFeatures[b] = getattr(options, b)
669
705
  if options.statusPipe or options.monitorParentProcess:
670
706
  try:
671
707
  global win32file, win32api, win32process, pywintypes
672
708
  import win32file, win32api, win32process, pywintypes
673
709
  except ImportError: # win32 not installed
674
- self.addToLog("--statusPipe {} cannot be installed, packages for win32 missing".format(options.statusPipe))
710
+ self.addToLog(f"--statusPipe {options.statusPipe} cannot be installed, packages for win32 missing")
675
711
  options.statusPipe = options.monitorParentProcess = None
676
712
  if options.statusPipe:
677
713
  try:
678
- self.statusPipe = win32file.CreateFile("\\\\.\\pipe\\{}".format(options.statusPipe),
714
+ self.statusPipe = win32file.CreateFile(f"\\\\.\\pipe\\{options.statusPipe}",
679
715
  win32file.GENERIC_READ | win32file.GENERIC_WRITE, 0, None, win32file.OPEN_EXISTING, win32file.FILE_FLAG_NO_BUFFERING, None)
680
716
  self.showStatus = self.showStatusOnPipe
681
717
  self.lastStatusTime = 0.0
682
718
  self.parentProcessHandle = None
683
719
  except pywintypes.error: # named pipe doesn't exist
684
- self.addToLog("--statusPipe {} has not been created by calling program".format(options.statusPipe))
720
+ self.addToLog(f"--statusPipe {options.statusPipe} has not been created by calling program")
685
721
  if options.monitorParentProcess:
686
722
  try:
687
723
  self.parentProcessHandle = win32api.OpenProcess(PROCESS_QUERY_INFORMATION, False, int(options.monitorParentProcess))
@@ -693,14 +729,14 @@ class CntlrCmdLine(Cntlr.Cntlr):
693
729
  _t.start()
694
730
  monitorParentProcess()
695
731
  except ImportError: # win32 not installed
696
- self.addToLog("--monitorParentProcess {} cannot be installed, packages for win32api and win32process missing".format(options.monitorParentProcess))
732
+ self.addToLog(f"--monitorParentProcess {options.monitorParentProcess} cannot be installed, packages for win32api and win32process missing")
697
733
  except (ValueError, pywintypes.error): # parent process doesn't exist
698
- self.addToLog("--monitorParentProcess Process {} Id is invalid".format(options.monitorParentProcess))
734
+ self.addToLog(f"--monitorParentProcess Process {options.monitorParentProcess} Id is invalid")
699
735
  sys.exit()
700
736
  if options.showOptions: # debug options
701
737
  for optName, optValue in sorted(options.__dict__.items(), key=lambda optItem: optItem[0]):
702
- self.addToLog("Option {0}={1}".format(optName, optValue), messageCode="info")
703
- self.addToLog("sys.argv {0}".format(sys.argv), messageCode="info")
738
+ self.addToLog(f"Option {optName}={optValue}", messageCode="info")
739
+ self.addToLog(f"sys.argv {sys.argv}", messageCode="info")
704
740
 
705
741
  setDisableRTL(options.disableRtl) # not saved to config
706
742
 
@@ -730,7 +766,7 @@ class CntlrCmdLine(Cntlr.Cntlr):
730
766
  ":****" if password else "",
731
767
  "@" if (user or password) else "",
732
768
  urlAddr,
733
- ":{0}".format(urlPort) if urlPort else ""), messageCode="info")
769
+ f":{urlPort}" if urlPort else ""), messageCode="info")
734
770
  else:
735
771
  self.addToLog(_("Proxy is disabled."), messageCode="info")
736
772
  if options.noCertificateCheck:
@@ -801,7 +837,7 @@ class CntlrCmdLine(Cntlr.Cntlr):
801
837
  if loadPluginOptions:
802
838
  _optionsParser = ParserForDynamicPlugins(options)
803
839
  # add plug-in options
804
- for optionsExtender in pluginClassMethods("CntlrCmdLine.Options"):
840
+ for optionsExtender in PluginManager.pluginClassMethods("CntlrCmdLine.Options"):
805
841
  optionsExtender(_optionsParser)
806
842
 
807
843
  if showPluginModules:
@@ -844,6 +880,8 @@ class CntlrCmdLine(Cntlr.Cntlr):
844
880
  else:
845
881
  self.modelManager.disclosureSystem.select(None) # just load ordinary mappings
846
882
  self.modelManager.validateDisclosureSystem = False
883
+ if options.baseTaxonomyValidationMode is not None:
884
+ self.modelManager.baseTaxonomyValidationMode = ValidateBaseTaxonomiesMode.fromName(options.baseTaxonomyValidationMode)
847
885
  self.modelManager.validateXmlOim = bool(options.validateXmlOim)
848
886
  if options.validateDuplicateFacts:
849
887
  duplicateTypeArg = ValidateDuplicateFacts.DuplicateTypeArg(options.validateDuplicateFacts)
@@ -981,7 +1019,7 @@ class CntlrCmdLine(Cntlr.Cntlr):
981
1019
 
982
1020
  # run utility command line options that don't depend on entrypoint Files
983
1021
  hasUtilityPlugin = False
984
- for pluginXbrlMethod in pluginClassMethods("CntlrCmdLine.Utility.Run"):
1022
+ for pluginXbrlMethod in PluginManager.pluginClassMethods("CntlrCmdLine.Utility.Run"):
985
1023
  hasUtilityPlugin = True
986
1024
  try:
987
1025
  pluginXbrlMethod(self, options, sourceZipStream=sourceZipStream, responseZipStream=responseZipStream)
@@ -1006,7 +1044,7 @@ class CntlrCmdLine(Cntlr.Cntlr):
1006
1044
  except ValueError as e:
1007
1045
  # is it malformed json?
1008
1046
  if _f.startswith("[{") or _f.endswith("]}") or '"file:"' in _f:
1009
- self.addToLog(_("File name parameter appears to be malformed JSON: {0}\n{1}".format(e, _f)),
1047
+ self.addToLog(_("File name parameter appears to be malformed JSON: {}\n{}").format(e, _f),
1010
1048
  messageCode="FileNameFormatError",
1011
1049
  level=logging.ERROR)
1012
1050
  success = False
@@ -1029,9 +1067,9 @@ class CntlrCmdLine(Cntlr.Cntlr):
1029
1067
  self.addToLog(str(err), messageCode="error", level=logging.ERROR)
1030
1068
  return False
1031
1069
 
1032
- for pluginXbrlMethod in pluginClassMethods("CntlrCmdLine.Filing.Start"):
1070
+ for pluginXbrlMethod in PluginManager.pluginClassMethods("CntlrCmdLine.Filing.Start"):
1033
1071
  pluginXbrlMethod(self, options, filesource, _entrypointFiles, sourceZipStream=sourceZipStream, responseZipStream=responseZipStream)
1034
- if len(_entrypointFiles) == 0:
1072
+ if len(_entrypointFiles) == 0 and not options.packages:
1035
1073
  if options.entrypointFile:
1036
1074
  msg = _("No XBRL entry points could be loaded from provided file: {}").format(options.entrypointFile)
1037
1075
  else:
@@ -1090,10 +1128,10 @@ class CntlrCmdLine(Cntlr.Cntlr):
1090
1128
  if modelXbrl.errors:
1091
1129
  success = False # loading errors, don't attempt to utilize loaded DTS
1092
1130
  if modelXbrl.modelDocument.type in ModelDocument.Type.TESTCASETYPES:
1093
- for pluginXbrlMethod in pluginClassMethods("Testcases.Start"):
1131
+ for pluginXbrlMethod in PluginManager.pluginClassMethods("Testcases.Start"):
1094
1132
  pluginXbrlMethod(self, options, modelXbrl)
1095
1133
  else: # not a test case, probably instance or DTS
1096
- for pluginXbrlMethod in pluginClassMethods("CntlrCmdLine.Xbrl.Loaded"):
1134
+ for pluginXbrlMethod in PluginManager.pluginClassMethods("CntlrCmdLine.Xbrl.Loaded"):
1097
1135
  pluginXbrlMethod(self, options, modelXbrl, _entrypoint, responseZipStream=responseZipStream)
1098
1136
  if options.saveOIMToXMLReport:
1099
1137
  if modelXbrl.loadedFromOIM and modelXbrl.modelDocument is not None:
@@ -1142,7 +1180,7 @@ class CntlrCmdLine(Cntlr.Cntlr):
1142
1180
  for modelXbrl in [self.modelManager.modelXbrl] + getattr(self.modelManager.modelXbrl, "supplementalModelXbrls", []):
1143
1181
  hasFormulae = modelXbrl.hasFormulae
1144
1182
  isAlreadyValidated = False
1145
- for pluginXbrlMethod in pluginClassMethods("ModelDocument.IsValidated"):
1183
+ for pluginXbrlMethod in PluginManager.pluginClassMethods("ModelDocument.IsValidated"):
1146
1184
  if pluginXbrlMethod(modelXbrl): # e.g., streaming extensions already has validated
1147
1185
  isAlreadyValidated = True
1148
1186
  if options.validate and not isAlreadyValidated:
@@ -1208,10 +1246,10 @@ class CntlrCmdLine(Cntlr.Cntlr):
1208
1246
  if options.arcroleTypesFile:
1209
1247
  ViewFileRoleTypes.viewRoleTypes(modelXbrl, options.arcroleTypesFile, "Arcrole Types", isArcrole=True, lang=options.labelLang)
1210
1248
 
1211
- for pluginXbrlMethod in pluginClassMethods("CntlrCmdLine.Xbrl.Run"):
1249
+ for pluginXbrlMethod in PluginManager.pluginClassMethods("CntlrCmdLine.Xbrl.Run"):
1212
1250
  pluginXbrlMethod(self, options, modelXbrl, _entrypoint, sourceZipStream=sourceZipStream, responseZipStream=responseZipStream)
1213
1251
 
1214
- except (IOError, EnvironmentError) as err:
1252
+ except OSError as err:
1215
1253
  self.addToLog(_("[IOError] Failed to save output:\n {0}").format(err),
1216
1254
  messageCode="IOError",
1217
1255
  file=options.entrypointFile,
@@ -1258,15 +1296,15 @@ class CntlrCmdLine(Cntlr.Cntlr):
1258
1296
  self.modelManager.close(modelDiffReport)
1259
1297
  elif modelXbrl:
1260
1298
  self.modelManager.close(modelXbrl)
1261
- if not options.keepOpen:
1299
+ if filesource is not None and not options.keepOpen:
1262
1300
  # Archive filesource potentially used by multiple reports may still be open.
1263
1301
  filesource.close()
1264
1302
 
1265
1303
  if success:
1266
1304
  if options.validate:
1267
- for pluginXbrlMethod in pluginClassMethods("CntlrCmdLine.Filing.Validate"):
1305
+ for pluginXbrlMethod in PluginManager.pluginClassMethods("CntlrCmdLine.Filing.Validate"):
1268
1306
  pluginXbrlMethod(self, options, filesource, _entrypointFiles, sourceZipStream=sourceZipStream, responseZipStream=responseZipStream)
1269
- for pluginXbrlMethod in pluginClassMethods("CntlrCmdLine.Filing.End"):
1307
+ for pluginXbrlMethod in PluginManager.pluginClassMethods("CntlrCmdLine.Filing.End"):
1270
1308
  pluginXbrlMethod(self, options, filesource, _entrypointFiles, sourceZipStream=sourceZipStream, responseZipStream=responseZipStream)
1271
1309
  self.username = self.password = None #dereference password
1272
1310
  self._clearPluginData()
arelle/CntlrWinMain.py CHANGED
@@ -34,6 +34,7 @@ from arelle import XbrlConst
34
34
  from arelle.PluginManager import pluginClassMethods
35
35
  from arelle.UrlUtil import isHttpUrl
36
36
  from arelle.ValidateXbrlCalcs import ValidateCalcsMode as CalcsMode
37
+ from arelle.ValidateXbrlDTS import ValidateBaseTaxonomiesMode
37
38
  from arelle.Version import copyrightLabel
38
39
  from arelle.oim.xml.Save import saveOimReportToXmlInstance
39
40
  import logging
@@ -164,6 +165,16 @@ class CntlrWinMain (Cntlr.Cntlr):
164
165
  for calcChoiceMenuLabel, calcChoiceEnumValue in CalcsMode.menu().items():
165
166
  calcMenu.add_radiobutton(label=calcChoiceMenuLabel, underline=0, var=self.calcChoiceEnumVar, value=calcChoiceEnumValue)
166
167
  toolsMenu.add_cascade(label=_("Calc linkbase"), menu=calcMenu, underline=0)
168
+
169
+ baseValidateModeMenu = Menu(self.menubar, tearoff=0)
170
+ baseValidationModeName = self.config.setdefault("baseTaxonomyValidationMode", ValidateBaseTaxonomiesMode.DISCLOSURE_SYSTEM.value)
171
+ self.modelManager.baseTaxonomyValidationMode = ValidateBaseTaxonomiesMode.fromName(baseValidationModeName)
172
+ self.baseTaxonomyValidationModeEnumVar = StringVar(self.parent, value=baseValidationModeName)
173
+ self.baseTaxonomyValidationModeEnumVar.trace("w", self.setBaseTaxonomyValidationModeEnumVar)
174
+ for modeLabel, modeValue in ValidateBaseTaxonomiesMode.menu().items():
175
+ baseValidateModeMenu.add_radiobutton(label=modeLabel, underline=0, var=self.baseTaxonomyValidationModeEnumVar, value=modeValue)
176
+ validateMenu.add_cascade(label=_("Base taxonomy validation"), menu=baseValidateModeMenu, underline=0)
177
+
167
178
  self.modelManager.validateUtr = self.config.setdefault("validateUtr",True)
168
179
  self.validateUtr = BooleanVar(value=self.modelManager.validateUtr)
169
180
  self.validateUtr.trace("w", self.setValidateUtr)
@@ -670,7 +681,16 @@ class CntlrWinMain (Cntlr.Cntlr):
670
681
  elif isinstance(view, ViewWinDTS.ViewDTS):
671
682
  ViewFileDTS.viewDTS(modelXbrl, filename)
672
683
  else:
673
- ViewFileRelationshipSet.viewRelationshipSet(modelXbrl, filename, view.tabTitle, view.arcrole, labelrole=view.labelrole, lang=view.lang)
684
+ if isinstance(view.arcrole, tuple) and len(view.arcrole) > 0 and view.arcrole[0] == "Calculation":
685
+ # "arcrole" is overloaded with special strings that are sometimes used magically to query
686
+ # the model and other times just to provide a header value. In the case of Calculation, it's
687
+ # only the header used in the GUI view and including it here when going to save will throw
688
+ # an exception.
689
+ arcrole = XbrlConst.summationItems
690
+ else:
691
+ arcrole = view.arcrole
692
+
693
+ ViewFileRelationshipSet.viewRelationshipSet(modelXbrl, filename, view.tabTitle, arcrole, labelrole=view.labelrole, lang=view.lang)
674
694
  except (IOError, EnvironmentError) as err:
675
695
  tkinter.messagebox.showwarning(_("arelle - Error"),
676
696
  _("Failed to save {0}:\n{1}").format(
@@ -977,7 +997,7 @@ class CntlrWinMain (Cntlr.Cntlr):
977
997
  hasView = ViewWinRelationshipSet.viewRelationshipSet(modelXbrl, self.tabWinTopRt, XbrlConst.parentChild, lang=self.labelLang)
978
998
  if hasView and topView is None: topView = modelXbrl.views[-1]
979
999
  currentAction = "calculation linkbase view"
980
- hasView = ViewWinRelationshipSet.viewRelationshipSet(modelXbrl, self.tabWinTopRt, ("Calculation",(XbrlConst.summationItem, XbrlConst.summationItem11)), lang=self.labelLang)
1000
+ hasView = ViewWinRelationshipSet.viewRelationshipSet(modelXbrl, self.tabWinTopRt, ("Calculation", XbrlConst.summationItems), lang=self.labelLang)
981
1001
  if hasView and topView is None: topView = modelXbrl.views[-1]
982
1002
  currentAction = "dimensions relationships view"
983
1003
  hasView = ViewWinRelationshipSet.viewRelationshipSet(modelXbrl, self.tabWinTopRt, "XBRL-dimensions", lang=self.labelLang)
@@ -1401,6 +1421,13 @@ class CntlrWinMain (Cntlr.Cntlr):
1401
1421
  self.saveConfig()
1402
1422
  self.setValidateTooltipText()
1403
1423
 
1424
+ def setBaseTaxonomyValidationModeEnumVar(self, *args):
1425
+ modeName = self.baseTaxonomyValidationModeEnumVar.get()
1426
+ self.modelManager.baseTaxonomyValidationMode = ValidateBaseTaxonomiesMode.fromName(modeName)
1427
+ self.config["baseTaxonomyValidationMode"] = modeName
1428
+ self.saveConfig()
1429
+ self.setValidateTooltipText()
1430
+
1404
1431
  def setValidateUtr(self, *args):
1405
1432
  self.modelManager.validateUtr = self.validateUtr.get()
1406
1433
  self.config["validateUtr"] = self.modelManager.validateUtr
arelle/ModelManager.py CHANGED
@@ -9,6 +9,7 @@ import gc, sys, traceback, logging
9
9
  from arelle import ModelXbrl, Validate, DisclosureSystem, PackageManager, ValidateXbrlCalcs, ValidateDuplicateFacts
10
10
  from arelle.ModelFormulaObject import FormulaOptions
11
11
  from arelle.PluginManager import pluginClassMethods
12
+ from arelle.ValidateXbrlDTS import ValidateBaseTaxonomiesMode
12
13
  from arelle.typing import LocaleDict
13
14
 
14
15
  if TYPE_CHECKING:
@@ -67,6 +68,7 @@ class ModelManager:
67
68
  self.loadedModelXbrls = []
68
69
  self.customTransforms: dict[QName, Callable[[str], str]] | None = None
69
70
  self.isLocaleSet = False
71
+ self.baseTaxonomyValidationMode = ValidateBaseTaxonomiesMode.DISCLOSURE_SYSTEM
70
72
  self.validateAllFilesAsReportPackages = False
71
73
  self.validateDuplicateFacts = ValidateDuplicateFacts.DuplicateType.NONE
72
74
  self.validateXmlOim = False
arelle/ModelValue.py CHANGED
@@ -277,10 +277,11 @@ def tzinfo(tz: str | None) -> datetime.timezone | None:
277
277
  else:
278
278
  return datetime.timezone(datetime.timedelta(hours=int(tz[0:3]), minutes=int(tz[0]+tz[4:6])))
279
279
 
280
- def tzinfoStr(dt: datetime.datetime) -> str:
281
- tz = str(dt.tzinfo or "")
282
- if tz.startswith("UTC"):
283
- return tz[3:] or "Z"
280
+ def tzinfoStr(dt: datetime.datetime | datetime.date) -> str:
281
+ if isinstance(dt, datetime.datetime):
282
+ tz = str(dt.tzinfo or "")
283
+ if tz.startswith("UTC"):
284
+ return tz[3:] or "Z"
284
285
  return ""
285
286
 
286
287
  def dateTime(
arelle/RuntimeOptions.py CHANGED
@@ -33,6 +33,7 @@ class RuntimeOptions:
33
33
  about: Optional[str] = None
34
34
  anchFile: Optional[str] = None
35
35
  arcroleTypesFile: Optional[str] = None
36
+ baseTaxonomyValidationMode: Optional[str] = None
36
37
  betaObjectModel: Optional[bool] = False
37
38
  cacheDirectory: Optional[str] = None
38
39
  calFile: Optional[str] = None
arelle/ValidateXbrlDTS.py CHANGED
@@ -2,6 +2,7 @@
2
2
  See COPYRIGHT.md for copyright information.
3
3
  '''
4
4
  from __future__ import annotations
5
+ from enum import Enum, auto
5
6
  from typing import TYPE_CHECKING
6
7
  from arelle import (ModelDocument, ModelDtsObject, HtmlUtil, UrlUtil, XmlUtil, XbrlUtil, XbrlConst,
7
8
  XmlValidate)
@@ -49,6 +50,37 @@ inlineDisplayNonePattern = re.compile(r"display\s*:\s*none")
49
50
  # lookbehind below is to ignore even numbers of \ before illegal escape character
50
51
  illegalXsdPatternEscapeChar = re.compile(r"(?:(?:^|[^\\])(?:\\\\)*)(\\[^nrt\\|.^?*+{}()[\]pPsSiIcCdDwW-])")
51
52
 
53
+ class ValidateBaseTaxonomiesMode(Enum):
54
+ DISCLOSURE_SYSTEM = "disclosureSystem"
55
+ NONE = "none"
56
+ ALL = "all"
57
+
58
+ @staticmethod
59
+ def fromName(modeName: str) -> ValidateBaseTaxonomiesMode:
60
+ for mode in ValidateBaseTaxonomiesMode:
61
+ if mode.value == modeName:
62
+ return mode
63
+ raise ValueError(f"Unknown ValidateBaseTaxonomiesMode: {modeName}")
64
+
65
+ @staticmethod
66
+ def tooltip(enum: ValidateBaseTaxonomiesMode):
67
+ if enum == ValidateBaseTaxonomiesMode.DISCLOSURE_SYSTEM:
68
+ return _("Skip validation of base taxonomy files which are known to be valid by the disclosure system")
69
+ if enum == ValidateBaseTaxonomiesMode.NONE:
70
+ return _("Skip validation of all base taxonomy files")
71
+ if enum == ValidateBaseTaxonomiesMode.ALL:
72
+ return _("Validate all base taxonomy files")
73
+ raise ValueError(f"Unknown ValidateBaseTaxonomiesMode: {enum}")
74
+
75
+ @staticmethod
76
+ def menu():
77
+ return {
78
+ _("Use disclosure system settings"): ValidateBaseTaxonomiesMode.DISCLOSURE_SYSTEM.value,
79
+ _("Don't validate any base files"): ValidateBaseTaxonomiesMode.NONE.value,
80
+ _("Validate all base files"): ValidateBaseTaxonomiesMode.ALL.value,
81
+ }
82
+
83
+
52
84
  def arcFromConceptQname(arcElement):
53
85
  modelRelationship = baseSetRelationship(arcElement)
54
86
  if modelRelationship is None:
@@ -261,36 +293,42 @@ def checkDTS(val: ValidateXbrl, modelDocument: ModelDocument.ModelDocument, chec
261
293
  # XML validation checks (remove if using validating XML)
262
294
  val.extendedElementName = None
263
295
  isFilingDocument = False
264
- # validate contents of entry point document or its sibling/descendant documents or in report package of entry point
265
- if ((modelDocument.uri.startswith(val.modelXbrl.uriDir) or # document uri in same subtree as entry doocument
266
- (val.modelXbrl.fileSource.isOpen and modelDocument.filepath.startswith(val.modelXbrl.fileSource.baseurl))) and # document in entry submission's package
267
- modelDocument.targetNamespace not in val.disclosureSystem.baseTaxonomyNamespaces and
268
- modelDocument.xmlDocument):
269
- isFilingDocument = True
270
- val.valUsedPrefixes = set()
271
- val.schemaRoleTypes = {}
272
- val.schemaArcroleTypes = {}
273
- val.referencedNamespaces = set()
274
296
 
275
- val.containsRelationship = False
297
+ if modelDocument.xmlDocument is not None:
298
+ isExtensionTaxonomyDoc = _isExtensionTaxonomyDocument(val, modelDocument)
299
+ if isExtensionTaxonomyDoc or _shouldValidateBaseTaxonomyDoc(val, modelDocument):
300
+ isFilingDocument = True
301
+ val.valUsedPrefixes = set()
302
+ val.schemaRoleTypes = {}
303
+ val.schemaArcroleTypes = {}
304
+ val.referencedNamespaces = set()
276
305
 
277
- checkElements(val, modelDocument, modelDocument.xmlDocument)
306
+ val.containsRelationship = False
278
307
 
279
- if (modelDocument.type == ModelDocument.Type.INLINEXBRL and
280
- val.validateGFM and
281
- (val.documentTypeEncoding.lower() != 'utf-8' or val.metaContentTypeEncoding.lower() != 'utf-8')):
282
- val.modelXbrl.error("GFM.1.10.4",
283
- _("XML declaration encoding %(encoding)s and meta content type encoding %(metaContentTypeEncoding)s must both be utf-8"),
284
- modelXbrl=modelDocument, encoding=val.documentTypeEncoding,
285
- metaContentTypeEncoding=val.metaContentTypeEncoding)
286
- if val.validateSBRNL:
287
- for pluginXbrlMethod in pluginClassMethods("Validate.SBRNL.DTS.document"):
288
- pluginXbrlMethod(val, modelDocument)
289
- del val.valUsedPrefixes
290
- del val.schemaRoleTypes
291
- del val.schemaArcroleTypes
292
- for pluginXbrlMethod in pluginClassMethods("Validate.XBRL.DTS.document"):
293
- pluginXbrlMethod(val, modelDocument, isFilingDocument)
308
+ checkElements(val, modelDocument, modelDocument.xmlDocument)
309
+
310
+ if (modelDocument.type == ModelDocument.Type.INLINEXBRL and
311
+ val.validateGFM and
312
+ (val.documentTypeEncoding.lower() != 'utf-8' or val.metaContentTypeEncoding.lower() != 'utf-8')):
313
+ val.modelXbrl.error("GFM.1.10.4",
314
+ _("XML declaration encoding %(encoding)s and meta content type encoding %(metaContentTypeEncoding)s must both be utf-8"),
315
+ modelXbrl=modelDocument, encoding=val.documentTypeEncoding,
316
+ metaContentTypeEncoding=val.metaContentTypeEncoding)
317
+ if val.validateSBRNL:
318
+ for pluginXbrlMethod in pluginClassMethods("Validate.SBRNL.DTS.document"):
319
+ pluginXbrlMethod(val, modelDocument)
320
+ del val.valUsedPrefixes
321
+ del val.schemaRoleTypes
322
+ del val.schemaArcroleTypes
323
+
324
+ if isExtensionTaxonomyDoc:
325
+ # While not captured in the hook name, the Validate.XBRL.DTS.document hook has been historically used by
326
+ # plugins (see EDGAR plugin) to validate extension taxonomies. This worked because Arelle didn't fully
327
+ # validate base taxonomy documents. Although Arelle now validates all documents, it retains this logic for
328
+ # the plugin hook to prevent running validation rules intended solely for extension taxonomy documents
329
+ # against base taxonomy documents.
330
+ for pluginXbrlMethod in pluginClassMethods("Validate.XBRL.DTS.document"):
331
+ pluginXbrlMethod(val, modelDocument, isFilingDocument)
294
332
 
295
333
  val.roleRefURIs = None
296
334
  val.arcroleRefURIs = None
@@ -1388,3 +1426,21 @@ def checkIxContinuationChain(val, elt, chain=None):
1388
1426
  if contAt is not None:
1389
1427
  chain.append(elt)
1390
1428
  checkIxContinuationChain(val, contAt, chain)
1429
+
1430
+ def _isExtensionTaxonomyDocument(val: ValidateXbrl, modelDocument: ModelDocument.ModelDocument) -> bool:
1431
+ if modelDocument.uri.startswith(val.modelXbrl.uriDir):
1432
+ # document uri in same subtree as entry doocument.
1433
+ return True
1434
+
1435
+ # check if document in entry submission's package.
1436
+ return val.modelXbrl.fileSource.isOpen and modelDocument.filepath.startswith(val.modelXbrl.fileSource.baseurl)
1437
+
1438
+ def _shouldValidateBaseTaxonomyDoc(val: ValidateXbrl, modelDocument: ModelDocument.ModelDocument) -> bool:
1439
+ baseTaxonomyValidationMode = val.modelXbrl.modelManager.baseTaxonomyValidationMode
1440
+ if baseTaxonomyValidationMode == ValidateBaseTaxonomiesMode.NONE:
1441
+ return False
1442
+ if baseTaxonomyValidationMode == ValidateBaseTaxonomiesMode.ALL:
1443
+ return True
1444
+ if baseTaxonomyValidationMode == ValidateBaseTaxonomiesMode.DISCLOSURE_SYSTEM:
1445
+ return modelDocument.uri not in getattr(val.disclosureSystem, "standardTaxonomiesDict", {})
1446
+ raise ValueError(f"Invalid base taxonomy validation mode: {baseTaxonomyValidationMode}")
@@ -31,8 +31,10 @@ COL_WIDTHS = {
31
31
  "Name": 40, "Namespace": 60, "LocalName": 40, "Documentation": 80
32
32
  }
33
33
 
34
- def hasCalcArcrole(arcroles: tuple[str] | str) -> bool:
35
- return any(arcrole in XbrlConst.summationItems for arcrole in (arcroles if isinstance(arcroles, (tuple,list)) else (arcroles,)))
34
+ def hasCalcArcrole(arcroles: tuple[str | tuple[str, ...], ...] | str) -> bool:
35
+ if isinstance(arcroles, (tuple, list)):
36
+ return any(hasCalcArcrole(arcrole) for arcrole in arcroles)
37
+ return arcroles in XbrlConst.summationItems
36
38
 
37
39
  class ViewRelationshipSet(ViewFile.View):
38
40
  def __init__(self, modelXbrl, outfile, header, labelrole, lang, cols):
@@ -16,7 +16,7 @@ from arelle.ModelRenderingObject import (StrctMdlBreakdown, StrctMdlStructuralNo
16
16
  OPEN_ASPECT_ENTRY_SURROGATE, ROLLUP_SPECIFIES_MEMBER, ROLLUP_FOR_DIMENSION_RELATIONSHIP_NODE,
17
17
  aspectStrctNodes)
18
18
  from arelle.rendering.RenderingLayout import layoutTable
19
- from arelle.rendering.RenderingResolution import resolveTableStructure, RENDER_UNITS_PER_CHAR
19
+ from arelle.rendering.RenderingResolution import RENDER_UNITS_PER_CHAR
20
20
  from arelle.ModelValue import QName
21
21
  from arelle.ModelXbrl import DEFAULT
22
22
  from arelle.ViewFile import HTML, XML
@@ -10,7 +10,6 @@ from arelle import ViewFile
10
10
  from arelle.FunctionXs import xsString
11
11
  from arelle.ModelObject import ModelObject
12
12
  from arelle.Aspect import Aspect, aspectModels, aspectRuleAspects, aspectModelAspect, aspectStr
13
- from arelle.rendering.RenderingResolution import resolveTableStructure
14
13
  from arelle.rendering.RenderingLayout import layoutTable
15
14
  from arelle import XbrlConst
16
15
  from arelle.XmlUtil import elementFragmentIdentifier, addQnameValue