arelle-release 2.37.53__py3-none-any.whl → 2.37.55__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.

arelle/CntlrCmdLine.py CHANGED
@@ -57,8 +57,7 @@ from arelle.RuntimeOptions import RuntimeOptions, RuntimeOptionsException
57
57
  from arelle.SocketUtils import INTERNET_CONNECTIVITY, OFFLINE
58
58
  from arelle.SystemInfo import PlatformOS, getSystemInfo, getSystemWordSize, hasWebServer, isCGI, isGAE
59
59
  from arelle.typing import TypeGetText
60
- from arelle.UrlUtil import isHttpUrl
61
- from arelle.utils.EntryPointDetection import filesourceEntrypointFiles
60
+ from arelle.utils.EntryPointDetection import parseEntrypointFileInput
62
61
  from arelle.ValidateXbrlDTS import ValidateBaseTaxonomiesMode
63
62
  from arelle.WebCache import proxyTuple
64
63
 
@@ -978,41 +977,12 @@ class CntlrCmdLine(Cntlr.Cntlr):
978
977
  if not (options.entrypointFile or sourceZipStream):
979
978
  return True # success
980
979
 
980
+ entrypointParseResult = parseEntrypointFileInput(self, options.entrypointFile, sourceZipStream)
981
+ if not entrypointParseResult.success:
982
+ return False
983
+ filesource = entrypointParseResult.filesource
984
+ _entrypointFiles = entrypointParseResult.entrypointFiles
981
985
  success = True
982
- # entrypointFile may be absent (if input is a POSTED zip or file name ending in .zip)
983
- # or may be a | separated set of file names
984
- _entryPoints = []
985
- _checkIfXmlIsEis = self.modelManager.disclosureSystem and self.modelManager.disclosureSystem.validationType == "EFM"
986
- if options.entrypointFile:
987
- _f = options.entrypointFile
988
- try: # may be a json list
989
- _entryPoints = json.loads(_f)
990
- _checkIfXmlIsEis = False # json entry objects never specify an xml EIS archive
991
- except ValueError as e:
992
- # is it malformed json?
993
- if _f.startswith("[{") or _f.endswith("]}") or '"file:"' in _f:
994
- self.addToLog(_("File name parameter appears to be malformed JSON: {}\n{}").format(e, _f),
995
- messageCode="FileNameFormatError",
996
- level=logging.ERROR)
997
- success = False
998
- else: # try as file names separated by '|'
999
- for f in (_f or '').split('|'):
1000
- if not sourceZipStream and not isHttpUrl(f) and not os.path.isabs(f):
1001
- f = os.path.normpath(os.path.join(os.getcwd(), f)) # make absolute normed path
1002
- _entryPoints.append({"file":f})
1003
- filesource = None # file source for all instances if not None
1004
- if sourceZipStream:
1005
- filesource = FileSource.openFileSource(None, self, sourceZipStream)
1006
- elif len(_entryPoints) == 1 and "file" in _entryPoints[0]: # check if an archive and need to discover entry points (and not IXDS)
1007
- entryPath = PackageManager.mappedUrl(_entryPoints[0]["file"])
1008
- filesource = FileSource.openFileSource(entryPath, self, checkIfXmlIsEis=_checkIfXmlIsEis)
1009
- _entrypointFiles = _entryPoints
1010
- if filesource and not filesource.selection and not (sourceZipStream and len(_entrypointFiles) > 0):
1011
- try:
1012
- filesourceEntrypointFiles(filesource, _entrypointFiles)
1013
- except Exception as err:
1014
- self.addToLog(str(err), messageCode="error", level=logging.ERROR)
1015
- return False
1016
986
 
1017
987
  for pluginXbrlMethod in PluginManager.pluginClassMethods("CntlrCmdLine.Filing.Start"):
1018
988
  pluginXbrlMethod(self, options, filesource, _entrypointFiles, sourceZipStream=sourceZipStream, responseZipStream=responseZipStream)
@@ -1248,6 +1218,11 @@ class CntlrCmdLine(Cntlr.Cntlr):
1248
1218
  self.modelManager.close(modelDiffReport)
1249
1219
  elif modelXbrl:
1250
1220
  self.modelManager.close(modelXbrl)
1221
+
1222
+ if options.validate:
1223
+ for pluginXbrlMethod in PluginManager.pluginClassMethods("Validate.Complete"):
1224
+ pluginXbrlMethod(self, filesource)
1225
+
1251
1226
  if filesource is not None and not options.keepOpen:
1252
1227
  # Archive filesource potentially used by multiple reports may still be open.
1253
1228
  filesource.close()
arelle/CntlrWinMain.py CHANGED
@@ -5,11 +5,14 @@ See COPYRIGHT.md for copyright information.
5
5
  '''
6
6
  from __future__ import annotations
7
7
 
8
+ from typing import Any
9
+
8
10
  from arelle import ValidateDuplicateFacts
9
11
  import os, sys, subprocess, pickle, time, locale, fnmatch, platform, webbrowser
10
12
  import regex as re
11
13
 
12
14
  from arelle.logging.formatters.LogFormatter import logRefsFileLines
15
+ from arelle.utils.EntryPointDetection import parseEntrypointFileInput
13
16
 
14
17
  if sys.platform == 'win32' and getattr(sys, 'frozen', False):
15
18
  # need the .dll directory in path to be able to access Tk and Tcl DLLs efore importinng Tk, etc.
@@ -816,15 +819,22 @@ class CntlrWinMain (Cntlr.Cntlr):
816
819
  if filename:
817
820
  for xbrlLoadedMethod in pluginClassMethods("CntlrWinMain.Xbrl.Open"):
818
821
  filename = xbrlLoadedMethod(self, filename) # runs in GUI thread, allows mapping filename, mult return filename
819
- filesource = None
822
+ entrypointParseResult = parseEntrypointFileInput(self, filename, fallbackSelect=False)
823
+ if not entrypointParseResult.success or entrypointParseResult.filesource is None:
824
+ return
825
+ filesource = entrypointParseResult.filesource
826
+ entrypointFiles = entrypointParseResult.entrypointFiles
820
827
  # check for archive files
821
- filesource = openFileSource(filename, self,
822
- checkIfXmlIsEis=self.modelManager.disclosureSystem and
823
- self.modelManager.disclosureSystem.validationType == "EFM")
824
828
  if filesource.isArchive:
825
- if not filesource.selection and not filesource.isReportPackage: # or filesource.isRss:
829
+ if (
830
+ len(entrypointFiles) == 0 and
831
+ not filesource.selection and
832
+ not filesource.isReportPackage
833
+ ):
826
834
  from arelle import DialogOpenArchive
827
835
  filename = DialogOpenArchive.askArchiveFile(self, filesource)
836
+ if filename is not None:
837
+ entrypointFiles.append({"file": filename})
828
838
  if filename and filesource.basefile and not isHttpUrl(filesource.basefile):
829
839
  self.config["fileOpenDir"] = os.path.dirname(filesource.baseurl)
830
840
  filesource.loadTaxonomyPackageMappings() # if a package, load mappings if not loaded yet
@@ -837,7 +847,7 @@ class CntlrWinMain (Cntlr.Cntlr):
837
847
  self.updateFileHistory(filename, importToDTS)
838
848
  elif len(filename) == 1:
839
849
  self.updateFileHistory(filename[0], importToDTS)
840
- thread = threading.Thread(target=self.backgroundLoadXbrl, args=(filesource,importToDTS,selectTopView), daemon=True).start()
850
+ thread = threading.Thread(target=self.backgroundLoadXbrl, args=(filesource,entrypointFiles,importToDTS,selectTopView), daemon=True).start()
841
851
 
842
852
  def webOpen(self, *ignore):
843
853
  if not self.okayToContinue():
@@ -848,12 +858,24 @@ class CntlrWinMain (Cntlr.Cntlr):
848
858
  self.updateFileHistory(url, False)
849
859
  for xbrlLoadedMethod in pluginClassMethods("CntlrWinMain.Xbrl.Open"):
850
860
  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()
861
+ entrypointParseResult = parseEntrypointFileInput(self, url, fallbackSelect=False)
862
+ if not entrypointParseResult.success or entrypointParseResult.filesource is None:
863
+ return
864
+ filesource = entrypointParseResult.filesource
865
+ entrypointFiles = entrypointParseResult.entrypointFiles
866
+ if filesource.isArchive:
867
+ if (
868
+ len(entrypointFiles) == 0 and
869
+ not filesource.selection and
870
+ not filesource.isReportPackage
871
+ ):
872
+ from arelle import DialogOpenArchive
873
+ url = DialogOpenArchive.askArchiveFile(self, filesource)
874
+ if url is not None:
875
+ entrypointFiles.append({"file": url})
876
+ self.updateFileHistory(url, False)
877
+ filesource.loadTaxonomyPackageMappings()
878
+ thread = threading.Thread(target=self.backgroundLoadXbrl, args=(filesource,entrypointFiles,False,False), daemon=True).start()
857
879
 
858
880
  def importWebOpen(self, *ignore):
859
881
  if not self.modelManager.modelXbrl or self.modelManager.modelXbrl.modelDocument.type not in (
@@ -867,36 +889,37 @@ class CntlrWinMain (Cntlr.Cntlr):
867
889
  self.fileOpenFile(url, importToDTS=True)
868
890
 
869
891
 
870
- def backgroundLoadXbrl(self, filesource, importToDTS, selectTopView):
892
+ def backgroundLoadXbrl(self, filesource, entrypointFiles: list[dict[str, Any]], importToDTS, selectTopView):
871
893
  startedAt = time.time()
872
894
  loadedModels = []
873
895
  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)
896
+ action = _("preparing entrypoints")
897
+ for pluginXbrlMethod in pluginClassMethods("CntlrWinMain.Filing.Start"):
898
+ pluginXbrlMethod(self, filesource, entrypointFiles)
899
+ for entrypoint in entrypointFiles:
900
+ entrypointFile = entrypoint.get("file", None) if isinstance(entrypoint,dict) else entrypoint
901
+ if filesource and filesource.isArchive:
902
+ filesource.select(entrypointFile)
903
+ else:
904
+ entrypointFile = PackageManager.mappedUrl(entrypointFile)
905
+ filesource = openFileSource(entrypointFile, self)
906
+ if importToDTS:
907
+ action = _("imported")
908
+ profileStat = "import"
909
+ modelXbrl = self.modelManager.modelXbrl
910
+ if modelXbrl:
911
+ ModelDocument.load(modelXbrl, filesource.url, isSupplemental=importToDTS)
912
+ modelXbrl.relationshipSets.clear() # relationships have to be re-cached
913
+ loadedModels.append(modelXbrl)
894
914
  else:
915
+ action = _("loaded")
916
+ profileStat = "load"
895
917
  modelXbrl = self.modelManager.load(
896
918
  filesource,
897
919
  _("views loading"),
898
920
  # check modified time if GUI-loading from web
899
921
  checkModifiedTime=isHttpUrl(filesource.url),
922
+ entrypoint=entrypoint,
900
923
  )
901
924
  if modelXbrl:
902
925
  loadedModels.append(modelXbrl)
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
arelle/Validate.py CHANGED
@@ -304,6 +304,7 @@ class Validate:
304
304
  def _testcaseLoadReadMeFirstUri(self, testcase, modelTestcaseVariation, index, readMeFirstUri, resultIsVersioningReport, resultIsTaxonomyPackage, inputDTSes, errorCaptureLevel, baseForElement, parameters):
305
305
  preLoadingErrors = [] # accumulate pre-loading errors, such as during taxonomy package loading
306
306
  loadedModels = []
307
+ filesource = None
307
308
  readMeFirstElements = modelTestcaseVariation.readMeFirstElements
308
309
  expectTaxonomyPackage = (index < len(readMeFirstElements) and
309
310
  readMeFirstElements[index] is not None and
@@ -492,6 +493,8 @@ class Validate:
492
493
  _("Testcase variation validation exception: %(error)s, instance: %(instance)s"),
493
494
  modelXbrl=model, instance=model.modelDocument.basename, error=err, exc_info=(type(err) is not AssertionError))
494
495
  model.hasFormulae = _hasFormulae
496
+ for pluginXbrlMethod in pluginClassMethods("Validate.Complete"):
497
+ pluginXbrlMethod(self.modelXbrl.modelManager.cntlr, filesource)
495
498
  errors = [error for model in loadedModels for error in model.errors]
496
499
  for err in preLoadingErrors:
497
500
  if err not in errors:
arelle/ViewWinDTS.py CHANGED
@@ -30,7 +30,10 @@ class ViewDTS(ViewWinTree.ViewTree):
30
30
 
31
31
  def viewDtsElement(self, modelDocument, parentNode, n, parents, siblings):
32
32
  if modelDocument.type == ModelDocument.Type.INLINEXBRLDOCUMENTSET:
33
- text = modelDocument.gettype() # no file name to display
33
+ if modelDocument.entrypoint is not None and "id" in modelDocument.entrypoint:
34
+ text = f"{modelDocument.entrypoint['id']} (IXDS)"
35
+ else:
36
+ text = "Inline XBRL Document Set" # no file name or ID to display
34
37
  else:
35
38
  text = "{0} - {1}".format(os.path.basename(modelDocument.uri), modelDocument.gettype())
36
39
  node = self.treeView.insert(parentNode, "end",
arelle/_version.py CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '2.37.53'
32
- __version_tuple__ = version_tuple = (2, 37, 53)
31
+ __version__ = version = '2.37.55'
32
+ __version_tuple__ = version_tuple = (2, 37, 55)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -19,13 +19,22 @@ if TYPE_CHECKING:
19
19
  from arelle.FileSource import FileSource
20
20
 
21
21
 
22
+ def pathsContainReportsDirectory(paths: set[str], topLevelDir: str) -> bool:
23
+ reportDirectory = f"{topLevelDir}/{Const.REPORTS_DIRECTORY}"
24
+ if reportDirectory in paths:
25
+ return True
26
+ reportDirectory = f"{reportDirectory}/"
27
+ return any(path.startswith(reportDirectory) for path in paths)
28
+
22
29
  def getReportPackageTopLevelDirectory(filesource: FileSource) -> str | None:
23
30
  packageEntries = set(filesource.dir or [])
24
- potentialTopLevelReportDirs = {
25
- topLevelDir
26
- for topLevelDir in PackageUtils.getPackageTopLevelDirectories(filesource)
27
- if any(f"{topLevelDir}/{path}" in packageEntries for path in Const.REPORT_PACKAGE_PATHS)
28
- }
31
+ potentialTopLevelReportDirs = set()
32
+ for topLevelDir in PackageUtils.getPackageTopLevelDirectories(filesource):
33
+ if (
34
+ f"{topLevelDir}/{Const.REPORT_PACKAGE_FILE}" in packageEntries or
35
+ pathsContainReportsDirectory(packageEntries, topLevelDir)
36
+ ):
37
+ potentialTopLevelReportDirs.add(topLevelDir)
29
38
  if len(potentialTopLevelReportDirs) == 1:
30
39
  return next(iter(potentialTopLevelReportDirs))
31
40
  return None
@@ -98,7 +107,7 @@ def getAllReportEntries(filesource: FileSource, stld: str | None) -> list[Report
98
107
  if not isinstance(filesource.fs, zipfile.ZipFile):
99
108
  return None
100
109
  entries = [f.orig_filename for f in filesource.fs.infolist()]
101
- if not any(entry.startswith(f"{stld}/{Const.REPORTS_DIRECTORY}") for entry in entries):
110
+ if not pathsContainReportsDirectory(set(entries), stld):
102
111
  return None
103
112
  topReportEntries = []
104
113
  entriesBySubDir = defaultdict(list)
@@ -36,7 +36,6 @@ REPORT_PACKAGE_EXTENSIONS = frozenset(
36
36
 
37
37
  REPORT_PACKAGE_FILE = f"{META_INF_DIRECTORY}/reportPackage.json"
38
38
  REPORTS_DIRECTORY = "reports"
39
- REPORT_PACKAGE_PATHS = [REPORT_PACKAGE_FILE, f"{REPORTS_DIRECTORY}/"]
40
39
 
41
40
  XBRL_2_1_REPORT_FILE_EXTENSION = ".xbrl"
42
41
  JSON_REPORT_FILE_EXTENSION = ".json"
@@ -248,7 +248,7 @@ def inlineXbrlDocumentSetLoader(modelXbrl, normalizedUri, filepath, isEntry=Fals
248
248
  for url in modelXbrl.ixdsDocUrls:
249
249
  xml.append("<instance>{}</instance>\n".format(url))
250
250
  xml.append("</instances>\n")
251
- ixdocset = create(modelXbrl, Type.INLINEXBRLDOCUMENTSET, docsetUrl, isEntry=True, initialXml="".join(xml))
251
+ ixdocset = create(modelXbrl, Type.INLINEXBRLDOCUMENTSET, docsetUrl, isEntry=True, initialXml="".join(xml), entrypoint=kwargs.get('entrypoint'))
252
252
  ixdocset.type = Type.INLINEXBRLDOCUMENTSET
253
253
  ixdocset.targetDocumentPreferredFilename = None # possibly no inline docs in this doc set
254
254
  for i, elt in enumerate(ixdocset.xmlRootElement.iter(tag="instance")):
@@ -739,7 +739,22 @@ def commandLineOptionExtender(parser, *args, **kwargs):
739
739
  def commandLineFilingStart(cntlr, options, filesource, entrypointFiles, *args, **kwargs):
740
740
  global skipExpectedInstanceComparison
741
741
  skipExpectedInstanceComparison = getattr(options, "skipExpectedInstanceComparison", False)
742
- inlineTarget = getattr(options, "inlineTarget", None)
742
+ filingStart(
743
+ cntlr,
744
+ entrypointFiles,
745
+ getattr(options, "inlineTarget", None),
746
+ )
747
+
748
+ def guiFilingStart(cntlr, filesource, entrypointFiles, *args, **kwargs):
749
+ global skipExpectedInstanceComparison
750
+ skipExpectedInstanceComparison = False
751
+ filingStart(
752
+ cntlr,
753
+ entrypointFiles,
754
+ inlineTarget=None
755
+ )
756
+
757
+ def filingStart(cntlr, entrypointFiles, inlineTarget):
743
758
  if inlineTarget:
744
759
  if isinstance(entrypointFiles, dict):
745
760
  entrypointFiles = [entrypointFiles]
@@ -1018,6 +1033,7 @@ __pluginInfo__ = {
1018
1033
  'InlineDocumentSet.Discovery': inlineDocsetDiscovery,
1019
1034
  'InlineDocumentSet.Url.Separator': inlineDocsetUrlSeparator,
1020
1035
  'InlineDocumentSet.CreateTargetInstance': createTargetInstance,
1036
+ 'CntlrWinMain.Filing.Start': guiFilingStart,
1021
1037
  'CntlrWinMain.Menu.File.Open': fileOpenMenuEntender,
1022
1038
  'CntlrWinMain.Menu.Tools': saveTargetDocumentMenuEntender,
1023
1039
  'CntlrCmdLine.Options': commandLineOptionExtender,
@@ -39,3 +39,40 @@ qnEdinetManifestTocComposition = qname("{http://disclosure.edinet-fsa.go.jp/2013
39
39
  xhtmlDtdExtension = "xhtml1-strict-ix.dtd"
40
40
 
41
41
  COVER_PAGE_FILENAME_PREFIX = "0000000_header_"
42
+
43
+ PROHIBITED_HTML_ATTRIBUTES = frozenset({
44
+ 'onblur',
45
+ 'onchange',
46
+ 'onclick',
47
+ 'ondblclick',
48
+ 'onfocus',
49
+ 'onkeydown',
50
+ 'onkeypress',
51
+ 'onkeyup',
52
+ 'onload',
53
+ 'onmousedown',
54
+ 'onmousemove',
55
+ 'onmouseout',
56
+ 'onmouseover',
57
+ 'onmouseup',
58
+ 'onreset',
59
+ 'onselect',
60
+ 'onsubmit',
61
+ 'onunload',
62
+ })
63
+
64
+ PROHIBITED_HTML_TAGS = frozenset({
65
+ 'applet',
66
+ 'embed',
67
+ 'form',
68
+ 'frame',
69
+ 'frameset',
70
+ 'iframe',
71
+ 'input',
72
+ 'object',
73
+ 'plaintext',
74
+ 'pre',
75
+ 'script',
76
+ 'select',
77
+ 'textarea',
78
+ })
@@ -27,10 +27,12 @@ _: TypeGetText
27
27
  @dataclass
28
28
  class ControllerPluginData(PluginData):
29
29
  _manifestInstancesById: dict[str, ManifestInstance]
30
+ _usedFilepaths: set[Path]
30
31
 
31
32
  def __init__(self, name: str):
32
33
  super().__init__(name)
33
34
  self._manifestInstancesById = {}
35
+ self._usedFilepaths = set()
34
36
 
35
37
  def __hash__(self) -> int:
36
38
  return id(self)
@@ -120,6 +122,9 @@ class ControllerPluginData(PluginData):
120
122
  if not i.is_dir()
121
123
  }
122
124
 
125
+ def getUsedFilepaths(self) -> frozenset[Path]:
126
+ return frozenset(self._usedFilepaths)
127
+
123
128
  @lru_cache(1)
124
129
  def isUpload(self, fileSource: FileSource) -> bool:
125
130
  fileSource.open() # Make sure file source is open
@@ -165,6 +170,9 @@ class ControllerPluginData(PluginData):
165
170
  break
166
171
  return matchedInstance
167
172
 
173
+ def addUsedFilepath(self, path: Path) -> None:
174
+ self._usedFilepaths.add(path)
175
+
168
176
  @staticmethod
169
177
  def get(cntlr: Cntlr, name: str) -> ControllerPluginData:
170
178
  controllerPluginData = cntlr.getPluginData(name)
@@ -166,4 +166,4 @@ def parseManifests(filesource: FileSource) -> list[ManifestInstance]:
166
166
  err,
167
167
  traceback.format_exc()
168
168
  ))
169
- return instances
169
+ return sorted(instances, key=lambda i: i.id)
@@ -7,15 +7,18 @@ from collections import defaultdict
7
7
  from dataclasses import dataclass
8
8
  from decimal import Decimal
9
9
  from functools import lru_cache
10
- from lxml.etree import DTD, XML
10
+ from pathlib import Path
11
+
12
+ from lxml.etree import DTD, XML, _ElementTree, _Comment, _ProcessingInstruction
11
13
  from operator import attrgetter
12
14
  from typing import Callable, Hashable, Iterable, cast
13
15
 
14
16
  import os
15
17
  import regex
16
18
 
19
+ from arelle import UrlUtil
17
20
  from arelle.LinkbaseType import LinkbaseType
18
- from arelle.ModelDocument import Type as ModelDocumentType
21
+ from arelle.ModelDocument import Type as ModelDocumentType, ModelDocument
19
22
  from arelle.ModelDtsObject import ModelConcept
20
23
  from arelle.ModelInstanceObject import ModelFact, ModelUnit, ModelContext, ModelInlineFact
21
24
  from arelle.ModelObject import ModelObject
@@ -23,10 +26,12 @@ from arelle.ModelValue import QName, qname
23
26
  from arelle.ModelXbrl import ModelXbrl
24
27
  from arelle.PrototypeDtsObject import LinkPrototype
25
28
  from arelle.ValidateDuplicateFacts import getDeduplicatedFacts, DeduplicationType
29
+ from arelle.ValidateXbrl import ValidateXbrl
30
+ from arelle.XhtmlValidate import htmlEltUriAttrs
26
31
  from arelle.XmlValidate import VALID
27
32
  from arelle.typing import TypeGetText
28
33
  from arelle.utils.PluginData import PluginData
29
- from .Constants import CORPORATE_FORMS, FormType, xhtmlDtdExtension
34
+ from .Constants import CORPORATE_FORMS, FormType, xhtmlDtdExtension, PROHIBITED_HTML_TAGS, PROHIBITED_HTML_ATTRIBUTES
30
35
  from .ControllerPluginData import ControllerPluginData
31
36
  from .ManifestInstance import ManifestInstance
32
37
  from .Statement import Statement, STATEMENTS, BalanceSheet, StatementInstance, StatementType
@@ -37,6 +42,14 @@ _: TypeGetText
37
42
  _DEBIT_QNAME_PATTERN = regex.compile('.*(Liability|Liabilities|Equity)')
38
43
 
39
44
 
45
+ @dataclass(frozen=True)
46
+ class UriReference:
47
+ attributeName: str
48
+ attributeValue: str
49
+ document: ModelDocument
50
+ element: ModelObject
51
+
52
+
40
53
  @dataclass
41
54
  class PluginValidationDataExtension(PluginData):
42
55
  accountingStandardsDeiQn: QName
@@ -51,9 +64,9 @@ class PluginValidationDataExtension(PluginData):
51
64
 
52
65
  contextIdPattern: regex.Pattern[str]
53
66
 
54
- _primaryModelXbrl: ModelXbrl | None = None
67
+ _uriReferences: list[UriReference]
55
68
 
56
- def __init__(self, name: str):
69
+ def __init__(self, name: str, validateXbrl: ValidateXbrl):
57
70
  super().__init__(name)
58
71
  jpcrpEsrNamespace = "http://disclosure.edinet-fsa.go.jp/taxonomy/jpcrp-esr/2024-11-01/jpcrp-esr_cor"
59
72
  self.jpcrpNamespace = 'http://disclosure.edinet-fsa.go.jp/taxonomy/jpcrp/2024-11-01/jpcrp_cor'
@@ -74,6 +87,9 @@ class PluginValidationDataExtension(PluginData):
74
87
 
75
88
  self.contextIdPattern = regex.compile(r'(Prior[1-9]Year|CurrentYear|Prior[1-9]Interim|Interim)(Duration|Instant)')
76
89
 
90
+ self._uriReferences = []
91
+ self._initialize(validateXbrl.modelXbrl)
92
+
77
93
  # Identity hash for caching.
78
94
  def __hash__(self) -> int:
79
95
  return id(self)
@@ -93,6 +109,28 @@ class PluginValidationDataExtension(PluginData):
93
109
  contextIsConsolidated = memberValue != self.nonConsolidatedMemberQn
94
110
  return bool(statement.isConsolidated == contextIsConsolidated)
95
111
 
112
+ def _initialize(self, modelXbrl: ModelXbrl) -> None:
113
+ if not isinstance(modelXbrl.fileSource.basefile, str):
114
+ return
115
+ controllerPluginData = ControllerPluginData.get(modelXbrl.modelManager.cntlr, self.name)
116
+ basePath = Path(modelXbrl.fileSource.basefile)
117
+ for uri, doc in modelXbrl.urlDocs.items():
118
+ docPath = Path(uri)
119
+ if not docPath.is_relative_to(basePath):
120
+ continue
121
+ controllerPluginData.addUsedFilepath(docPath.relative_to(basePath))
122
+ for elt, name, value in self.getUriAttributeValues(doc):
123
+ self._uriReferences.append(UriReference(
124
+ attributeName=name,
125
+ attributeValue=value,
126
+ document=doc,
127
+ element=elt,
128
+ ))
129
+ fullPath = Path(doc.uri).parent / value
130
+ if fullPath.is_relative_to(basePath):
131
+ fileSourcePath = fullPath.relative_to(basePath)
132
+ controllerPluginData.addUsedFilepath(fileSourcePath)
133
+
96
134
  def _isDebitConcept(self, concept: ModelConcept) -> bool:
97
135
  """
98
136
  :return: Whether the given concept is a debit concept.
@@ -201,6 +239,10 @@ class PluginValidationDataExtension(PluginData):
201
239
  if (statementInstance := self.getStatementInstance(modelXbrl, statement)) is not None
202
240
  ]
203
241
 
242
+ @property
243
+ def uriReferences(self) -> list[UriReference]:
244
+ return self._uriReferences
245
+
204
246
  @lru_cache(1)
205
247
  def getDeduplicatedFacts(self, modelXbrl: ModelXbrl) -> list[ModelFact]:
206
248
  return getDeduplicatedFacts(modelXbrl, DeduplicationType.CONSISTENT_PAIRS)
@@ -259,6 +301,44 @@ class PluginValidationDataExtension(PluginData):
259
301
  controllerPluginData = ControllerPluginData.get(modelXbrl.modelManager.cntlr, self.name)
260
302
  return controllerPluginData.matchManifestInstance(modelXbrl.ixdsDocUrls)
261
303
 
304
+ @lru_cache(1)
305
+ def getProhibitedAttributeElements(self, modelDocument: ModelDocument) -> list[tuple[ModelObject, str]]:
306
+ results: list[tuple[ModelObject, str]] = []
307
+ if modelDocument.type not in (ModelDocumentType.INLINEXBRL, ModelDocumentType.HTML):
308
+ return results
309
+ for elt in modelDocument.xmlRootElement.iter():
310
+ if not isinstance(elt, ModelObject):
311
+ continue
312
+ for attributeName in elt.attrib.keys():
313
+ if attributeName in PROHIBITED_HTML_ATTRIBUTES:
314
+ results.append((elt, str(attributeName)))
315
+ return results
316
+
317
+ @lru_cache(1)
318
+ def getProhibitedTagElements(self, modelDocument: ModelDocument) -> list[ModelObject]:
319
+ elts: list[ModelObject] = []
320
+ if modelDocument.type not in (ModelDocumentType.INLINEXBRL, ModelDocumentType.HTML):
321
+ return elts
322
+ for elt in modelDocument.xmlRootElement.iter():
323
+ if not isinstance(elt, ModelObject):
324
+ continue
325
+ tag = elt.qname.localName
326
+ if tag in PROHIBITED_HTML_TAGS:
327
+ elts.append(elt)
328
+ return elts
329
+
330
+ @lru_cache(1)
331
+ def getUriAttributeValues(self, modelDocument: ModelDocument) -> list[tuple[ModelObject, str, str]]:
332
+ results: list[tuple[ModelObject, str, str]] = []
333
+ if modelDocument.type not in (ModelDocumentType.INLINEXBRL, ModelDocumentType.HTML):
334
+ return results
335
+ for elt in modelDocument.xmlRootElement.iter():
336
+ for name in htmlEltUriAttrs.get(elt.localName, ()):
337
+ value = elt.get(name)
338
+ if value is not None:
339
+ results.append((elt, name, value))
340
+ return results
341
+
262
342
  def hasValidNonNilFact(self, modelXbrl: ModelXbrl, qname: QName) -> bool:
263
343
  return any(True for fact in self.iterValidNonNilFacts(modelXbrl, qname))
264
344
 
@@ -270,3 +350,7 @@ class PluginValidationDataExtension(PluginData):
270
350
  for fact in facts:
271
351
  if fact.xValid >= VALID and not fact.isNil:
272
352
  yield fact
353
+
354
+ def addUsedFilepath(self, modelXbrl: ModelXbrl, path: Path) -> None:
355
+ controllerPluginData = ControllerPluginData.get(modelXbrl.modelManager.cntlr, self.name)
356
+ controllerPluginData.addUsedFilepath(path)
@@ -3,6 +3,7 @@ See COPYRIGHT.md for copyright information.
3
3
  """
4
4
  from __future__ import annotations
5
5
 
6
+ import logging
6
7
  from typing import Any
7
8
 
8
9
  from arelle.Cntlr import Cntlr
@@ -26,6 +27,15 @@ class ValidationPluginExtension(ValidationPlugin):
26
27
  if len(instances) == 0:
27
28
  return None
28
29
  assert filesource.cntlr is not None
30
+ filesource.cntlr.addToLog(
31
+ _("EDINET manifest(s) detected (%(manifests)s). Loading %(count)s instances (%(instances)s)."),
32
+ messageCode="info",
33
+ messageArgs={
34
+ "manifests": ', '.join(instance.type for instance in instances),
35
+ "count": len(instances),
36
+ "instances": ', '.join(instance.id for instance in instances),
37
+ }, level=logging.INFO
38
+ )
29
39
  pluginData = ControllerPluginData.get(filesource.cntlr, self.name)
30
40
  entrypointFiles = []
31
41
  for instance in instances:
@@ -49,4 +59,5 @@ class ValidationPluginExtension(ValidationPlugin):
49
59
  raise ValueError(f'Invalid EDINET disclosure system: {disclosureSystem}')
50
60
  return PluginValidationDataExtension(
51
61
  self.name,
62
+ validateXbrl
52
63
  )
@@ -59,6 +59,10 @@ def loggingSeverityReleveler(modelXbrl: ModelXbrl, level: str, messageCode: str,
59
59
  return level, messageCode
60
60
 
61
61
 
62
+ def validateComplete(*args: Any, **kwargs: Any) -> None:
63
+ return validationPlugin.validateComplete(*args, **kwargs)
64
+
65
+
62
66
  def validateFileSource(*args: Any, **kwargs: Any) -> None:
63
67
  return validationPlugin.validateFileSource(*args, **kwargs)
64
68
 
@@ -83,6 +87,7 @@ __pluginInfo__ = {
83
87
  "DisclosureSystem.ConfigURL": disclosureSystemConfigURL,
84
88
  "FileSource.EntrypointFiles": fileSourceEntrypointFiles,
85
89
  "Logging.Severity.Releveler": loggingSeverityReleveler,
90
+ "Validate.Complete": validateComplete,
86
91
  "Validate.FileSource": validateFileSource,
87
92
  "Validate.XBRL.Finally": validateXbrlFinally,
88
93
  "ValidateFormula.Finished": validateFinally,
@@ -641,3 +641,32 @@ def rule_gfm_1_3_1(
641
641
  msg=_("The submitter-specific taxonomy contains include elements."),
642
642
  modelObject=warnings
643
643
  )
644
+
645
+
646
+ @validation(
647
+ hook=ValidationHook.XBRL_FINALLY,
648
+ disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
649
+ )
650
+ def rule_gfm_1_3_8(
651
+ pluginData: PluginValidationDataExtension,
652
+ val: ValidateXbrl,
653
+ *args: Any,
654
+ **kwargs: Any,
655
+ ) -> Iterable[Validation]:
656
+ """
657
+ EDINET.EC5700W: [GFM 1.3.8] TThe submitter-specific taxonomy has an embedded linkbase.
658
+ """
659
+ embeddedElements = []
660
+ for modelDocument in val.modelXbrl.urlDocs.values():
661
+ if pluginData.isStandardTaxonomyUrl(modelDocument.uri, val.modelXbrl):
662
+ continue
663
+ rootElt = modelDocument.xmlRootElement
664
+ for elt in rootElt.iterdescendants(XbrlConst.qnLinkLinkbaseRef.clarkNotation):
665
+ if elt.attrib.get(XbrlConst.qnXlinkType.clarkNotation) in ('extended', 'arc', 'resource', 'locator'):
666
+ embeddedElements.append(elt)
667
+ if len(embeddedElements) > 0:
668
+ yield Validation.warning(
669
+ codes='EDINET.EC5700W.GFM.1.3.8',
670
+ msg=_("The submitter-specific taxonomy has an embedded linkbase."),
671
+ modelObject=embeddedElements
672
+ )
@@ -10,6 +10,7 @@ from typing import Any, Iterable, TYPE_CHECKING
10
10
 
11
11
  import regex
12
12
 
13
+ from arelle import UrlUtil
13
14
  from arelle.Cntlr import Cntlr
14
15
  from arelle.FileSource import FileSource
15
16
  from arelle.ValidateXbrl import ValidateXbrl
@@ -602,6 +603,87 @@ def rule_EC0352E(
602
603
  )
603
604
 
604
605
 
606
+ @validation(
607
+ hook=ValidationHook.XBRL_FINALLY,
608
+ disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
609
+ )
610
+ def rule_EC1006E(
611
+ pluginData: PluginValidationDataExtension,
612
+ val: ValidateXbrl,
613
+ *args: Any,
614
+ **kwargs: Any,
615
+ ) -> Iterable[Validation]:
616
+ """
617
+ EDINET.EC1006E: Prohibited tag is used in HTML.
618
+ """
619
+ for doc in val.modelXbrl.urlDocs.values():
620
+ for elt in pluginData.getProhibitedTagElements(doc):
621
+ yield Validation.error(
622
+ codes='EDINET.EC1006E',
623
+ msg=_("Prohibited tag (%(tag)s) is used in HTML. File name: %(file)s (line %(line)s). "
624
+ "Please correct the prohibited tags for the relevant files. "
625
+ "For information on prohibited tags, please refer to \"4-1-4 Prohibited Rules\" "
626
+ "in the Validation Guidelines."),
627
+ tag=elt.qname.localName,
628
+ file=doc.basename,
629
+ line=elt.sourceline,
630
+ modelObject=elt,
631
+ )
632
+
633
+
634
+ @validation(
635
+ hook=ValidationHook.XBRL_FINALLY,
636
+ disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
637
+ )
638
+ def rule_uri_references(
639
+ pluginData: PluginValidationDataExtension,
640
+ val: ValidateXbrl,
641
+ *args: Any,
642
+ **kwargs: Any,
643
+ ) -> Iterable[Validation]:
644
+ """
645
+ EDINET.EC1007E: The URI in the HTML specifies a URL or absolute path.
646
+ EDINET.EC1013E: The URI in the HTML specifies a path not under a subdirectory.
647
+ EDINET.EC1014E: The URI in the HTML specifies a path to a file that doesn't exist.
648
+ """
649
+ for uriReference in pluginData.uriReferences:
650
+ if UrlUtil.isAbsolute(uriReference.attributeValue):
651
+ yield Validation.error(
652
+ codes='EDINET.EC1007E',
653
+ msg=_("The URI in the HTML specifies a URL or absolute path. "
654
+ "File name: '%(file)s' (line %(line)s). "
655
+ "Please change the links in the files to relative paths."),
656
+ file=uriReference.document.basename,
657
+ line=uriReference.element.sourceline,
658
+ modelObject=uriReference.element,
659
+ )
660
+ continue
661
+ path = Path(uriReference.attributeValue)
662
+ if len(path.parts) < 2:
663
+ yield Validation.error(
664
+ codes='EDINET.EC1013E',
665
+ msg=_("The URI in the HTML specifies a path not under a subdirectory. "
666
+ "File name: '%(file)s' (line %(line)s). "
667
+ "Please move the referenced file into a subfolder, or correct the URI."),
668
+ file=uriReference.document.basename,
669
+ line=uriReference.element.sourceline,
670
+ modelObject=uriReference.element,
671
+ )
672
+ continue
673
+ fullPath = Path(uriReference.document.uri).parent / path
674
+ if not val.modelXbrl.fileSource.exists(str(fullPath)):
675
+ yield Validation.error(
676
+ codes='EDINET.EC1014E',
677
+ msg=_("The URI in the HTML specifies a path to a file that doesn't exist. "
678
+ "File name: '%(file)s' (line %(line)s). "
679
+ "Please update the URI to reference a file."),
680
+ file=uriReference.document.basename,
681
+ line=uriReference.element.sourceline,
682
+ modelObject=uriReference.element,
683
+ )
684
+ continue
685
+
686
+
605
687
  @validation(
606
688
  hook=ValidationHook.FILESOURCE,
607
689
  disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
@@ -631,6 +713,38 @@ def rule_EC1016E(
631
713
  )
632
714
 
633
715
 
716
+ @validation(
717
+ hook=ValidationHook.COMPLETE,
718
+ disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
719
+ )
720
+ def rule_EC1017E(
721
+ pluginData: ControllerPluginData,
722
+ cntlr: Cntlr,
723
+ fileSource: FileSource,
724
+ *args: Any,
725
+ **kwargs: Any,
726
+ ) -> Iterable[Validation]:
727
+ """
728
+ EDINET.EC1017E: There is an unused file.
729
+ """
730
+ uploadContents = pluginData.getUploadContents(fileSource)
731
+ existingSubdirectoryFilepaths = {
732
+ path
733
+ for path, pathInfo in uploadContents.uploadPaths.items()
734
+ if pathInfo.isSubdirectory and not pathInfo.isDirectory
735
+ }
736
+ usedFilepaths = pluginData.getUsedFilepaths()
737
+ unusedSubdirectoryFilepaths = existingSubdirectoryFilepaths - usedFilepaths
738
+ for path in unusedSubdirectoryFilepaths:
739
+ yield Validation.error(
740
+ codes='EDINET.EC1017E',
741
+ msg=_("There is an unused file. "
742
+ "File name: '%(file)s'. "
743
+ "Please remove the file or reference it in the HTML."),
744
+ file=str(path),
745
+ )
746
+
747
+
634
748
  @validation(
635
749
  hook=ValidationHook.XBRL_FINALLY,
636
750
  disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
@@ -676,6 +790,33 @@ def rule_EC1020E(
676
790
  )
677
791
 
678
792
 
793
+ @validation(
794
+ hook=ValidationHook.XBRL_FINALLY,
795
+ disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
796
+ )
797
+ def rule_EC1031E(
798
+ pluginData: PluginValidationDataExtension,
799
+ val: ValidateXbrl,
800
+ *args: Any,
801
+ **kwargs: Any,
802
+ ) -> Iterable[Validation]:
803
+ """
804
+ EDINET.EC1031E: Prohibited attribute is used in HTML.
805
+ """
806
+ for doc in val.modelXbrl.urlDocs.values():
807
+ for elt, attributeName in pluginData.getProhibitedAttributeElements(doc):
808
+ yield Validation.error(
809
+ codes='EDINET.EC1031E',
810
+ msg=_("Prohibited attribute '%(attributeName)s' is used in HTML. "
811
+ "File name: %(file)s (line %(line)s). "
812
+ "Please correct the tag attributes of the relevant file."),
813
+ attributeName=elt.qname.localName,
814
+ file=doc.basename,
815
+ line=elt.sourceline,
816
+ modelObject=elt,
817
+ )
818
+
819
+
679
820
  @validation(
680
821
  hook=ValidationHook.FILESOURCE,
681
822
  disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
@@ -1,12 +1,72 @@
1
+ """
2
+ See COPYRIGHT.md for copyright information.
3
+ """
4
+ from __future__ import annotations
5
+
6
+ import json
7
+ import logging
1
8
  import os
9
+ from dataclasses import dataclass
10
+ from typing import Any, TYPE_CHECKING
2
11
 
3
12
  from arelle import (
4
13
  ModelDocument,
5
- PluginManager,
14
+ PluginManager, FileSource, PackageManager,
6
15
  )
16
+ from arelle.UrlUtil import isHttpUrl
17
+ from arelle.typing import TypeGetText
18
+
19
+ _: TypeGetText
20
+
21
+ if TYPE_CHECKING:
22
+ from arelle.Cntlr import Cntlr
23
+
24
+
25
+ @dataclass(frozen=True)
26
+ class EntrypointParseResult:
27
+ success: bool
28
+ entrypointFiles: list[dict[str, Any]]
29
+ filesource: FileSource | None
30
+
31
+
32
+ def parseEntrypointFileInput(cntlr: Cntlr, entrypointFile: str | None, sourceZipStream=None, fallbackSelect=True) -> EntrypointParseResult:
33
+ # entrypointFile may be absent (if input is a POSTED zip or file name ending in .zip)
34
+ # or may be a | separated set of file names
35
+ _entryPoints = []
36
+ _checkIfXmlIsEis = cntlr.modelManager.disclosureSystem and cntlr.modelManager.disclosureSystem.validationType == "EFM"
37
+ if entrypointFile:
38
+ _f = entrypointFile
39
+ try: # may be a json list
40
+ _entryPoints = json.loads(_f)
41
+ _checkIfXmlIsEis = False # json entry objects never specify an xml EIS archive
42
+ except ValueError as e:
43
+ # is it malformed json?
44
+ if _f.startswith("[{") or _f.endswith("]}") or '"file:"' in _f:
45
+ cntlr.addToLog(_("File name parameter appears to be malformed JSON: {}\n{}").format(e, _f),
46
+ messageCode="FileNameFormatError",
47
+ level=logging.ERROR)
48
+ else: # try as file names separated by '|'
49
+ for f in (_f or '').split('|'):
50
+ if not sourceZipStream and not isHttpUrl(f) and not os.path.isabs(f):
51
+ f = os.path.normpath(os.path.join(os.getcwd(), f)) # make absolute normed path
52
+ _entryPoints.append({"file":f})
53
+ filesource = None # file source for all instances if not None
54
+ if sourceZipStream:
55
+ filesource = FileSource.openFileSource(None, cntlr, sourceZipStream)
56
+ elif len(_entryPoints) == 1 and "file" in _entryPoints[0]: # check if an archive and need to discover entry points (and not IXDS)
57
+ entryPath = PackageManager.mappedUrl(_entryPoints[0]["file"])
58
+ filesource = FileSource.openFileSource(entryPath, cntlr, checkIfXmlIsEis=_checkIfXmlIsEis)
59
+ _entrypointFiles = _entryPoints
60
+ if filesource and not filesource.selection and not (sourceZipStream and len(_entrypointFiles) > 0):
61
+ try:
62
+ filesourceEntrypointFiles(filesource, _entrypointFiles, fallbackSelect=fallbackSelect)
63
+ except Exception as err:
64
+ cntlr.addToLog(str(err), messageCode="error", level=logging.ERROR)
65
+ return EntrypointParseResult(success=False, entrypointFiles=_entrypointFiles, filesource=filesource)
66
+ return EntrypointParseResult(success=True, entrypointFiles=_entrypointFiles, filesource=filesource)
7
67
 
8
68
 
9
- def filesourceEntrypointFiles(filesource, entrypointFiles=None, inlineOnly=False):
69
+ def filesourceEntrypointFiles(filesource, entrypointFiles=None, inlineOnly=False, fallbackSelect=True):
10
70
  if entrypointFiles is None:
11
71
  entrypointFiles = []
12
72
  for pluginXbrlMethod in PluginManager.pluginClassMethods("FileSource.EntrypointFiles"):
@@ -34,7 +94,7 @@ def filesourceEntrypointFiles(filesource, entrypointFiles=None, inlineOnly=False
34
94
  entrypointFiles.extend(reportEntries)
35
95
  elif not inlineOnly:
36
96
  entrypointFiles.append({"file": report.fullPathPrimary})
37
- else:
97
+ elif fallbackSelect:
38
98
  # attempt to find inline XBRL files before instance files, .xhtml before probing others (ESMA)
39
99
  urlsByType = {}
40
100
  for _archiveFile in (filesource.dir or ()): # .dir might be none if IOerror
@@ -34,6 +34,7 @@ class ValidationHook(Enum):
34
34
  These hooks are called at different stages of validation, but all provide a common interface (ValidateXbrl is the first param).
35
35
  """
36
36
 
37
+ COMPLETE = "Validate.Complete"
37
38
  FILESOURCE = "Validate.FileSource"
38
39
  XBRL_START = "Validate.XBRL.Start"
39
40
  XBRL_FINALLY = "Validate.XBRL.Finally"
@@ -42,6 +43,42 @@ class ValidationHook(Enum):
42
43
 
43
44
 
44
45
  class PluginHooks(ABC):
46
+
47
+ @staticmethod
48
+ def cntlrCmdLineFilingStart(
49
+ cntlr: CntlrCmdLine,
50
+ options: RuntimeOptions,
51
+ filesource: FileSource | None,
52
+ entrypoints: list[dict[str, Any]] | None = None,
53
+ sourceZipStream: BinaryIO | None = None,
54
+ responseZipStream: BinaryIO | None = None,
55
+ *args: Any,
56
+ **kwargs: Any,
57
+ ) -> None:
58
+ """
59
+ Plugin hook: `CntlrCmdLine.Filing.Start`
60
+
61
+ This hook is triggered after entrypoints have been discovered and parsed, but before
62
+ models have been loaded.
63
+
64
+ Example:
65
+ ```python
66
+ timeAtStart = time.time()
67
+ myOptionEnabled = options.myOption
68
+ ```
69
+
70
+ :param cntlr: The [CntlrCmdLine](#arelle.CntlrCmdLine.CntlrCmdLine) that is currently running.
71
+ :param options: Parsed options object.
72
+ :param filesource: FileSource, if available.
73
+ :param entrypoints: A list of entrypoint configurations.
74
+ :param sourceZipStream: The source zip stream if the model was loaded from a zip that was POSTed to the webserver.
75
+ :param responseZipStream: The response zip stream if loaded from the webserver and the user requested a zip response.
76
+ :param args: Argument capture to ensure new parameters don't break plugin hook.
77
+ :param kwargs: Argument capture to ensure new named parameters don't break plugin hook.
78
+ :return: None
79
+ """
80
+ raise NotImplementedError
81
+
45
82
  @staticmethod
46
83
  def cntlrCmdLineOptions(
47
84
  parser: OptionParser,
@@ -218,6 +255,34 @@ class PluginHooks(ABC):
218
255
  """
219
256
  raise NotImplementedError
220
257
 
258
+ @staticmethod
259
+ def cntlrWinMainFilingStart(
260
+ cntlr: CntlrWinMain,
261
+ filesource: FileSource | None,
262
+ entrypoints: list[dict[str, Any]] | None = None,
263
+ *args: Any,
264
+ **kwargs: Any,
265
+ ) -> None:
266
+ """
267
+ Plugin hook: `CntlrWinMain.Filing.Start`
268
+
269
+ This hook is triggered after entrypoints have been discovered and parsed, but before
270
+ models have been loaded.
271
+
272
+ Example:
273
+ ```python
274
+ timeAtStart = time.time()
275
+ ```
276
+
277
+ :param cntlr: The [CntlrWinMain](#arelle.CntlrWinMain.CntlrWinMain) that is currently running.
278
+ :param filesource: FileSource, if available.
279
+ :param entrypoints: A list of entrypoint configurations.
280
+ :param args: Argument capture to ensure new parameters don't break plugin hook.
281
+ :param kwargs: Argument capture to ensure new named parameters don't break plugin hook.
282
+ :return: None
283
+ """
284
+ raise NotImplementedError
285
+
221
286
  @staticmethod
222
287
  def cntlrWinMainMenuHelp(
223
288
  cntlr: CntlrWinMain,
@@ -569,6 +634,35 @@ class PluginHooks(ABC):
569
634
  """
570
635
  raise NotImplementedError
571
636
 
637
+ @staticmethod
638
+ def validateComplete(
639
+ cntlr: Cntlr,
640
+ fileSource: FileSource,
641
+ *args: Any,
642
+ **kwargs: Any,
643
+ ) -> None:
644
+ """
645
+ Plugin hook: `Validate.Complete`
646
+
647
+ Hook for executing controller-level validation rules after model-level validation is complete.
648
+ This can be useful for validating multi-instance filings when a rule requires information that
649
+ is only available after all instances have been parsed and validated.
650
+
651
+ Example:
652
+ ```python
653
+ unusedFilepaths = pluginData.getExistingFilepaths() - pluginData.getUsedFilepaths()
654
+ if len(unusedSubdirectoryFilepaths) > 0:
655
+ yield Validation.error(codes="0.0.0", msg="Unused files exist.")
656
+ ```
657
+
658
+ :param cntlr: The [Cntlr](#arelle.Cntlr.Cntlr) instance.
659
+ :param fileSource: The [FileSource](#arelle.FileSource.FileSource) involved in loading the entrypoint files.
660
+ :param args: Argument capture to ensure new parameters don't break plugin hook.
661
+ :param kwargs: Argument capture to ensure new named parameters don't break plugin hook.
662
+ :return: None
663
+ """
664
+ raise NotImplementedError
665
+
572
666
  @staticmethod
573
667
  def validateFileSource(
574
668
  cntlr: Cntlr,
@@ -101,6 +101,26 @@ class ValidationPlugin:
101
101
  ) -> ModelDocument | LoadingException | None:
102
102
  raise NotImplementedError
103
103
 
104
+ def validateComplete(
105
+ self,
106
+ cntlr: Cntlr,
107
+ fileSource: FileSource,
108
+ *args: Any,
109
+ **kwargs: Any,
110
+ ) -> None:
111
+ """
112
+ Executes validation functions in the rules module that was provided to the constructor of this class.
113
+ Each function decorated with [@validation](#arelle.utils.validate.Decorator.validation) will be run if:
114
+ 1. the decorator was used with the validation complete hook: `@validation(hook=ValidationHook.COMPLETE)`
115
+
116
+ :param cntlr: The [Cntlr](#arelle.Cntlr.Cntlr) instance.
117
+ :param fileSource: The [FileSource](#arelle.FileSource.FileSource) involved in loading the entrypoint files.
118
+ :param args: Argument capture to ensure new parameters don't break plugin hook.
119
+ :param kwargs: Argument capture to ensure new named parameters don't break plugin hook.
120
+ :return: None
121
+ """
122
+ self._executeCntlrValidations(ValidationHook.COMPLETE, cntlr, fileSource, *args, **kwargs)
123
+
104
124
  def validateFileSource(
105
125
  self,
106
126
  cntlr: Cntlr,
@@ -112,7 +132,7 @@ class ValidationPlugin:
112
132
  """
113
133
  Executes validation functions in the rules module that was provided to the constructor of this class.
114
134
  Each function decorated with [@validation](#arelle.utils.validate.Decorator.validation) will be run if:
115
- 1. the decorator was used with the xbrl start hook: `@validation(hook=ValidationHook.FILESOURCE)`
135
+ 1. the decorator was used with the FileSource validation hook: `@validation(hook=ValidationHook.FILESOURCE)`
116
136
 
117
137
  :param cntlr: The [Cntlr](#arelle.Cntlr.Cntlr) instance.
118
138
  :param fileSource: The [FileSource](#arelle.FileSource.FileSource) involved in loading the entrypoint files.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arelle-release
3
- Version: 2.37.53
3
+ Version: 2.37.55
4
4
  Summary: An open source XBRL platform.
5
5
  Author-email: "arelle.org" <support@arelle.org>
6
6
  License-Expression: Apache-2.0
@@ -1,12 +1,12 @@
1
1
  arelle/Aspect.py,sha256=Pn9I91D1os1RTVj6htuxTfRzVMhmVDtrbKvV_zy9xMI,5470
2
2
  arelle/BetaFeatures.py,sha256=T_tPac-FiozHyYLCemt0RoHJ1JahUE71L-0tHmIRKpE,858
3
3
  arelle/Cntlr.py,sha256=nwE_qIQMTa5R3sr_dLP6s3srLhiEA5zDOqfas2pDxoA,31691
4
- arelle/CntlrCmdLine.py,sha256=LrlPhfCvOIrq7gKtAb4CMEWk4ofmjEVLyjHyUCYYPJg,90514
4
+ arelle/CntlrCmdLine.py,sha256=D5Xs847rIAMsI_fmJC27GSB0jIMGjE3VejpKvMTqncU,88717
5
5
  arelle/CntlrComServer.py,sha256=h1KPf31uMbErpxTZn_iklDqUMGFgQnjZkFkFjd8gtLQ,1888
6
6
  arelle/CntlrProfiler.py,sha256=2VQJudiUhxryVypxjODx2ccP1-n60icTiWs5lSEokhQ,972
7
7
  arelle/CntlrQuickBooks.py,sha256=BMqd5nkNQOZyNFPefkTeWUUDCYNS6BQavaG8k1Lepu4,31543
8
8
  arelle/CntlrWebMain.py,sha256=_jhosP0Pg-erutBRzTVsL0cmWVgbc6z0WIIlNO-z1N4,50712
9
- arelle/CntlrWinMain.py,sha256=53lRaGTh8-4vG-hpuNVkxUhFa60u0jIHM9WvFdwdv-o,97273
9
+ arelle/CntlrWinMain.py,sha256=SWMdTK4Vv1I8cFpVBxbPiJwfuGn7yMbNDbNZM9UYn9E,98220
10
10
  arelle/CntlrWinTooltip.py,sha256=6MzoAIfkYnNu_bl_je8n0adhwmKxAIcymkg9Tij9Z4M,7951
11
11
  arelle/DialogAbout.py,sha256=XXzMV0fO4BQ3-l1Puirzmn7EZEdmgJg7JNYdJm1FueM,1987
12
12
  arelle/DialogArcroleGroup.py,sha256=r81OT3LFmMkoROpFenk97oVEyQhibKZ1QgDHvMsyCl0,7547
@@ -36,7 +36,7 @@ arelle/LeiUtil.py,sha256=tSPrbQrXEeH5pXgGA_6MAdgMZp20NaW5izJglIXyEQk,5095
36
36
  arelle/LinkbaseType.py,sha256=BwRQl4XZFFCopufC2FEMLhYENNTk2JUWVQvnIUsaqtI,3108
37
37
  arelle/LocalViewer.py,sha256=WVrfek_bLeFFxgWITi1EQb6xCQN8O9Ks-ZL16vRncSk,3080
38
38
  arelle/Locale.py,sha256=07IDxv8nIfvhyRRllFdEAKRI3yo1D2v781cTrjb_RoQ,33193
39
- arelle/ModelDocument.py,sha256=t8fcz5vIj6mNclzaf2kKIz9IJxYi23QoLaW2Av-CmB0,130523
39
+ arelle/ModelDocument.py,sha256=R4uVfqks2XRDziRPAp_AUFebrAQBf4FgG97nb5h7fAs,130651
40
40
  arelle/ModelDtsObject.py,sha256=nvHQs4BDmPxY6mqLiBuqIGRJXyA1EqOEGB2f3S6A6w4,88656
41
41
  arelle/ModelFormulaObject.py,sha256=-eb0lBYciEeAvvGduIs3AHGNGrxge9_0g1cVT6UUgvc,122560
42
42
  arelle/ModelInstanceObject.py,sha256=77rkxl1FK8OyJhTXkrNv6B_4oEK6IZm9eRQdFDxslGk,74278
@@ -67,7 +67,7 @@ arelle/UITkTable.py,sha256=N83cXi5c0lLZLsDbwSKcPrlYoUoGsNavGN5YRx6d9XY,39810
67
67
  arelle/UiUtil.py,sha256=3G0xPclZI8xW_XQDbiFrmylB7Nd5muqi5n2x2oMkMZU,34218
68
68
  arelle/Updater.py,sha256=IZ8cq44Rq88WbQcB1VOpMA6bxdfZxfYQ8rgu9Ehpbes,7448
69
69
  arelle/UrlUtil.py,sha256=HrxZSG59EUMGMMGmWPuZkPi5-0BGqY3jAMkp7V4IdZo,32400
70
- arelle/Validate.py,sha256=hIp9qD5n6SrQNGzoRio_rjR-9Nsmzv6UG2phxT_Pcww,58589
70
+ arelle/Validate.py,sha256=cX1OA3JPiwmjNmxfecZc24GBW1qXdjcEEynJ9F1K7Zg,58764
71
71
  arelle/ValidateDuplicateFacts.py,sha256=L556J1Dhz4ZmsMlRNoDCMpFgDQYiryd9vuBYDvE0Aq8,21769
72
72
  arelle/ValidateFilingText.py,sha256=xnXc0xgdNiHQk0eyP7VSSpvw7qr-pRFRwqqoUb569is,54051
73
73
  arelle/ValidateInfoset.py,sha256=Rz_XBi5Ha43KpxXYhjLolURcWVx5qmqyjLxw48Yt9Dg,20396
@@ -94,7 +94,7 @@ arelle/ViewFileTests.py,sha256=l5phDVBWYNd7xECgJOy9Tsjpjo0mzwP6AlmGbq8ufOU,7798
94
94
  arelle/ViewUtil.py,sha256=A8SEWiQB9tyCPTMaxrQNMF8AI7Y2CxP5I9RuYeVkytU,2279
95
95
  arelle/ViewUtilFormulae.py,sha256=AQ9sQ3e1giqI54YgNNiR7RAxeoXPvunUraqEoAitYak,1837
96
96
  arelle/ViewWinConcepts.py,sha256=FVKJ607_aZrwjPMUdVEfPlA6s7oGqg3Vtf_MxlEEZts,6812
97
- arelle/ViewWinDTS.py,sha256=iNQbEOqfFzwLKSuCdJ_Xi5hyU8uUAzSV3tOx7pCYnus,1726
97
+ arelle/ViewWinDTS.py,sha256=L2q-Pyn-g61RN7SQxTamqDmgKJIIy5UE4vNb8A4lZfg,1913
98
98
  arelle/ViewWinDiffs.py,sha256=Q8Yd-L3zKhgOab4UK01CPHyXyMd3G37DIaDQ00DAolA,4856
99
99
  arelle/ViewWinFactGrid.py,sha256=tQk9MQ9aiTzJ2SsX8Y16V7qccf-Jl1FcBPEP4_Cxc0E,14349
100
100
  arelle/ViewWinFactList.py,sha256=eoSzG-Kt90kb7Ok4HCIn6NqGe8fp8WoN03f8Wn3H5LI,8858
@@ -125,7 +125,7 @@ arelle/XmlValidateConst.py,sha256=U_wN0Q-nWKwf6dKJtcu_83FXPn9c6P8JjzGA5b0w7P0,33
125
125
  arelle/XmlValidateParticles.py,sha256=Mn6vhFl0ZKC_vag1mBwn1rH_x2jmlusJYqOOuxFPO2k,9231
126
126
  arelle/XmlValidateSchema.py,sha256=6frtZOc1Yrx_5yYF6V6oHbScnglWrVbWr6xW4EHtLQI,7428
127
127
  arelle/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
128
- arelle/_version.py,sha256=o-hS3u4c-OHlibwJ4ipa9wOVJpeWj4nnsUtBZGu9EIY,708
128
+ arelle/_version.py,sha256=Ob4Yjv_aHEp_grPDRfpg4fr4W3Qb5_yliBYKjfPLi7w,708
129
129
  arelle/typing.py,sha256=PRe-Fxwr2SBqYYUVPCJ3E7ddDX0_oOISNdT5Q97EbRM,1246
130
130
  arelle/api/Session.py,sha256=kgSxS7VckA1sQ7xp0pJiK7IK-vRxAdAZKUo8gEx27s8,7549
131
131
  arelle/config/creationSoftwareNames.json,sha256=5MK7XUjfDJ9OpRCCHXeOErJ1SlTBZji4WEcEOdOacx0,3128
@@ -222,8 +222,8 @@ arelle/packages/PackageType.py,sha256=2s29OWZ-XpKW1bPJ1kVBcpKMDBr9gzLe25Cukg-YTE
222
222
  arelle/packages/PackageUtils.py,sha256=uraFfkyYQVlY8YdK4j-HgxhkIAOsEaU5Mk4To0ymKxE,575
223
223
  arelle/packages/PackageValidation.py,sha256=y0J6EAR5O8fF51vIrj-2Ur42TObIMvHVFtPXTR-GGOc,7408
224
224
  arelle/packages/report/DetectReportPackage.py,sha256=AemOF0eDbnCR3n6cNTWDb93FttQ18s3jsNQuA-37zM4,305
225
- arelle/packages/report/ReportPackage.py,sha256=_W9Z3u9WEBFJhQAgMAW4Y06bOTydk9F-Il-YNmvWBvc,9939
226
- arelle/packages/report/ReportPackageConst.py,sha256=nmHRfMZDc1PzxjTlOUi6SVp0QWxI62cywCY2q6CNadQ,2682
225
+ arelle/packages/report/ReportPackage.py,sha256=raRUThRiyJqcYODjr1VaTEzwd86m4Wg8xGVi0daJJTQ,10322
226
+ arelle/packages/report/ReportPackageConst.py,sha256=rTs-fV0BztXo_NleIIyN2Y8mpmC_r3eSzUxeZbvSiB8,2612
227
227
  arelle/packages/report/ReportPackageValidator.py,sha256=3Ur_CXUtCPtXzUgDi9t3VI3vcew0I9pq2B-cn_rIkX4,10322
228
228
  arelle/plugin/CacheBuilder.py,sha256=-aDFD3rx83glOprQYO-8gsaIEXcdwB6PFfIq7jy20lo,4489
229
229
  arelle/plugin/EdgarRendererAllReports.py,sha256=V3AeDj29U1XbnFGUO30kAvZtWzDH5G-teyzoJE0HDU0,5031
@@ -234,7 +234,7 @@ arelle/plugin/formulaLoader.py,sha256=_pPZQPAZeNjGj85rvH7QRl4gEjYD7Yhxl1JhuV9wOo
234
234
  arelle/plugin/formulaSaver.py,sha256=STlKyDA-pVUxZoEW57MSu74RdpyHVTxaHvOZyOt0cyg,31385
235
235
  arelle/plugin/formulaXPathChecker.py,sha256=sEEeLHx17XSj8eOgFdzYLBp9ZFk2UUYLOEKtaF_pq34,18795
236
236
  arelle/plugin/functionsMath.py,sha256=Z8N7ok3w1aOusCQA9QvqYwQ8W1j80bb-_4lVclBnNQM,9037
237
- arelle/plugin/inlineXbrlDocumentSet.py,sha256=mXQkqKhwizuQsXPzXsdHMxKVayQxCA86MF0gITogplI,55269
237
+ arelle/plugin/inlineXbrlDocumentSet.py,sha256=mG7BVPblkm6A81sAKR8NxjlNDTqlpKoErJTHn7lw1tk,55712
238
238
  arelle/plugin/loadFromExcel.py,sha256=galvvaj9jTKMMRUasCSh1SQnCCBzoN_HEpYWv0fmUdM,124407
239
239
  arelle/plugin/loadFromOIM.py,sha256=dJHnX56bmuY40f6vRMsQ7pJmIWcB5N_3jmYWGGnZSdM,903
240
240
  arelle/plugin/profileCmdLine.py,sha256=uLL0fGshpiwtzyLKAtW0WuXAvcRtZgxQG6swM0e4BHA,2370
@@ -312,25 +312,25 @@ arelle/plugin/validate/DBA/rules/tm.py,sha256=ui9oKBqlAForwkQ9kk9KBiUogTJE5pv1Rb
312
312
  arelle/plugin/validate/DBA/rules/tr.py,sha256=4TootFjl0HXsKZk1XNBCyj-vnjRs4lg35hfiz_b_4wU,14684
313
313
  arelle/plugin/validate/EBA/__init__.py,sha256=x3zXNcdSDJ3kHfL7kMs0Ve0Vs9oWbzNFVf1TK4Avmy8,45924
314
314
  arelle/plugin/validate/EBA/config.xml,sha256=37wMVUAObK-XEqakqD8zPNog20emYt4a_yfL1AKubF8,2022
315
- arelle/plugin/validate/EDINET/Constants.py,sha256=H_OX8hq7nns6TbKPpmDlg1_pNOd7P-XMFF03MMZBAuk,1387
316
- arelle/plugin/validate/EDINET/ControllerPluginData.py,sha256=T4m8GFiVg9tNoLcC3HxeegNnKVqAqZdc6kNdxM5Vz1Y,7005
315
+ arelle/plugin/validate/EDINET/Constants.py,sha256=IkJ1R0ZQAs9kUXY8eB7e8azYlC4m500VILrQhniB5Q4,1949
316
+ arelle/plugin/validate/EDINET/ControllerPluginData.py,sha256=2hMPxpL0vZqmzcuvPl9o4_g79ZOtJgLI1p_VRW2YYVk,7259
317
317
  arelle/plugin/validate/EDINET/DisclosureSystems.py,sha256=3rKG42Eg-17Xx_KXU_V5yHW6I3LTwQunvf4a44C9k_4,36
318
- arelle/plugin/validate/EDINET/ManifestInstance.py,sha256=SkQV-aOsYn3CTgCkH4IXNdM3QKoiz8okwb29ftMtV3Q,6882
319
- arelle/plugin/validate/EDINET/PluginValidationDataExtension.py,sha256=EkUJvkjk96ehgfnnoUxBAv9x9aauntvF9oXFOyr81Yo,12447
318
+ arelle/plugin/validate/EDINET/ManifestInstance.py,sha256=o6BGlaQHSsn6D0VKH4zn59UscKnjTKlo99kSGfGdYlU,6910
319
+ arelle/plugin/validate/EDINET/PluginValidationDataExtension.py,sha256=aM3OtB0X6NSHouFDut68pkLfe143rTW6nL6tIcI84D4,16092
320
320
  arelle/plugin/validate/EDINET/ReportFolderType.py,sha256=Q-9a-5tJfhK-cjY8WUB2AT1NI-Nn9cFtARVOIJoLRGU,2979
321
321
  arelle/plugin/validate/EDINET/Statement.py,sha256=0Mw5IB7LMtvUZ-2xKZfxmq67xF_dCgJo3eNLweLFRHU,9350
322
322
  arelle/plugin/validate/EDINET/UploadContents.py,sha256=IKQYl6lXYTfAZKLIDzRRGSRt3FXoL2Eldbx3Dh7T2I4,712
323
- arelle/plugin/validate/EDINET/ValidationPluginExtension.py,sha256=oMY0ntLr1qIh3uMi1W_M-bT5bhXPDx048X3oDFP5zOY,2042
324
- arelle/plugin/validate/EDINET/__init__.py,sha256=OZ7gMknCHd0M-9nt8UOmjEZW50YQzbvSLongr9O7Yi0,3022
323
+ arelle/plugin/validate/EDINET/ValidationPluginExtension.py,sha256=xr4Ft9v-zpwgRuKSjregyHjtUb5KgHqdSx9NKg9UVeg,2528
324
+ arelle/plugin/validate/EDINET/__init__.py,sha256=ECWgqzwHA7MZ3g7SeoFI7ttR9Wq_lywV-TlqeUW_juY,3186
325
325
  arelle/plugin/validate/EDINET/resources/config.xml,sha256=7uT4GcRgk5veMLpFhPPQJxbGKiQvM52P8EMrjn0qd0g,646
326
326
  arelle/plugin/validate/EDINET/resources/edinet-taxonomies.xml,sha256=997I3RGTLg5OY3vn5hQxVFAAxOmDSOYpuyQe6VnWSY0,16285
327
327
  arelle/plugin/validate/EDINET/rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
328
328
  arelle/plugin/validate/EDINET/rules/contexts.py,sha256=KPoyWfRaURvxoGVcWP64mTMTAKPMSmQSX06RClCLddw,7590
329
329
  arelle/plugin/validate/EDINET/rules/edinet.py,sha256=VYrDZaKbsQuQEvOY5F0Pv4Jzk9YZ4iETOkAFOggrhEY,12632
330
330
  arelle/plugin/validate/EDINET/rules/frta.py,sha256=N0YglHYZuLD2IuwE26viR2ViwUYjneBuMFU9vlrS0aQ,7616
331
- arelle/plugin/validate/EDINET/rules/gfm.py,sha256=Elyd0Vqooj_rC0yDWC8NneWCQ_Ckb9IZy1XCn7m1_IE,24389
331
+ arelle/plugin/validate/EDINET/rules/gfm.py,sha256=3kV7jahj_Y8GxwgjebUtnIrnDTbZEYEDfzokDTGcehc,25490
332
332
  arelle/plugin/validate/EDINET/rules/manifests.py,sha256=MoT9R_a4BzuYdQVbF7RC5wz134Ve68svSdJ3NlpO_AU,4026
333
- arelle/plugin/validate/EDINET/rules/upload.py,sha256=jfBt-WjUiJqh8QSkaLMxfsz9dP6fT3z63GT-ws514YY,31415
333
+ arelle/plugin/validate/EDINET/rules/upload.py,sha256=f9MLg7Dz9czsAHKE-SQHRlCe77AXWAPyp4RDeB0G8Rc,36818
334
334
  arelle/plugin/validate/ESEF/Const.py,sha256=JujF_XV-_TNsxjGbF-8SQS4OOZIcJ8zhCMnr-C1O5Ho,22660
335
335
  arelle/plugin/validate/ESEF/Dimensions.py,sha256=MOJM7vwNPEmV5cu-ZzPrhx3347ZvxgD6643OB2HRnIk,10597
336
336
  arelle/plugin/validate/ESEF/Util.py,sha256=QH3btcGqBpr42M7WSKZLSdNXygZaZLfEiEjlxoG21jE,7950
@@ -665,24 +665,24 @@ arelle/resources/libs/Tktable2.11/win-x86_64/Tktable.dll,sha256=R-Xf_J9tYjTe4pZE
665
665
  arelle/resources/libs/Tktable2.11/win-x86_64/pkgIndex.tcl,sha256=tmkATyDWsKAA3NgvlNGhOOdTWdBD91X1BgAHmybd_lY,151
666
666
  arelle/resources/libs/Tktable2.11/win-x86_64/tkTable.tcl,sha256=JrSZRngZFHtw8Svpzl68AVoffCuzzqG0zs3Zxe8UUoM,24094
667
667
  arelle/utils/Contexts.py,sha256=j9uSBAXGkunlJGC9SscCbb1cj3oU_J3b_yjdhj2B4a4,1714
668
- arelle/utils/EntryPointDetection.py,sha256=4RzercL0xE4PJrwoeUYq3S-E7PMSD-IspyS9bwK2RYM,4722
668
+ arelle/utils/EntryPointDetection.py,sha256=TciYbi49dv7fs5inFQR-G6rKCB2XfX_TLgDJ_3FcRK0,7680
669
669
  arelle/utils/Equivalence.py,sha256=Ac6sENvh-WHJlBJneV_-6n_MF2C1filHETkUEiucLJg,525
670
670
  arelle/utils/PluginData.py,sha256=GUnuZaApm1J4Xm9ZA1U2M1aask-AaNGviLtc0fgXbFg,265
671
- arelle/utils/PluginHooks.py,sha256=SX0aH5KnP2E_m5nL_mwn1o0uBTMgG0TeMXfG_HuUe00,32323
671
+ arelle/utils/PluginHooks.py,sha256=kuiNeR7l30-FIxW1lbqm_m4dI5gTY0BHrz60yj9rI_U,36053
672
672
  arelle/utils/Units.py,sha256=c9bwnu9Xnm00gC9Q6qQ1ogsEeTEXGRH7rahlEbrEWnQ,1201
673
673
  arelle/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
674
674
  arelle/utils/validate/Decorator.py,sha256=8LGmA171HZgKrALtsMKyHqMNM-XCdwJOv6KpZz4pC2c,3161
675
675
  arelle/utils/validate/DetectScriptsInXhtml.py,sha256=RFBh_Z24OjR69s71qQzSzbxdU-WCTWuvYlONN-BgpZ0,2098
676
676
  arelle/utils/validate/ESEFImage.py,sha256=M3pR9zxz0Y8oNjrpniEYwztCV2hoBK4fDSi4U095C3k,15520
677
677
  arelle/utils/validate/Validation.py,sha256=n6Ag7VeCj_VO5nqzw_P53hOfXXeT2APK0Enb3UQqBns,832
678
- arelle/utils/validate/ValidationPlugin.py,sha256=LUlH24tSq1W_eFP3DQZfByFPEKpgdlOaTXKiIWQltSc,13658
678
+ arelle/utils/validate/ValidationPlugin.py,sha256=qd08cSng-wzZw2utFSwuNzZwiT2-6JJ_J2rqZXmfbwc,14689
679
679
  arelle/utils/validate/ValidationUtil.py,sha256=9vmSvShn-EdQy56dfesyV8JjSRVPj7txrxRFgh8FxIs,548
680
680
  arelle/utils/validate/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
681
681
  arelle/webserver/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
682
682
  arelle/webserver/bottle.py,sha256=P-JECd9MCTNcxCnKoDUvGcoi03ezYVOgoWgv2_uH-6M,362
683
- arelle_release-2.37.53.dist-info/licenses/LICENSE.md,sha256=Q0tn6q0VUbr-NM8916513NCIG8MNzo24Ev-sxMUBRZc,3959
684
- arelle_release-2.37.53.dist-info/METADATA,sha256=ZpAHR7Eu9CAE3Xrm8GKcsHWisD0SNeVUhTUfiQ8CVx4,9327
685
- arelle_release-2.37.53.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
686
- arelle_release-2.37.53.dist-info/entry_points.txt,sha256=Uj5niwfwVsx3vaQ3fYj8hrZ1xpfCJyTUA09tYKWbzpo,111
687
- arelle_release-2.37.53.dist-info/top_level.txt,sha256=fwU7SYawL4_r-sUMRg7r1nYVGjFMSDvRWx8VGAXEw7w,7
688
- arelle_release-2.37.53.dist-info/RECORD,,
683
+ arelle_release-2.37.55.dist-info/licenses/LICENSE.md,sha256=Q0tn6q0VUbr-NM8916513NCIG8MNzo24Ev-sxMUBRZc,3959
684
+ arelle_release-2.37.55.dist-info/METADATA,sha256=7zIMq7-ff7-wq1Vq8wfHYzjd4A_sMGZsPHuiIz2-nNY,9327
685
+ arelle_release-2.37.55.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
686
+ arelle_release-2.37.55.dist-info/entry_points.txt,sha256=Uj5niwfwVsx3vaQ3fYj8hrZ1xpfCJyTUA09tYKWbzpo,111
687
+ arelle_release-2.37.55.dist-info/top_level.txt,sha256=fwU7SYawL4_r-sUMRg7r1nYVGjFMSDvRWx8VGAXEw7w,7
688
+ arelle_release-2.37.55.dist-info/RECORD,,