arelle-release 2.37.46__py3-none-any.whl → 2.38.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. arelle/BetaFeatures.py +0 -21
  2. arelle/Cntlr.py +15 -8
  3. arelle/CntlrCmdLine.py +121 -56
  4. arelle/CntlrWinMain.py +143 -70
  5. arelle/DialogFind.py +1 -1
  6. arelle/DialogPluginManager.py +6 -4
  7. arelle/DisclosureSystem.py +7 -0
  8. arelle/ErrorManager.py +21 -6
  9. arelle/FileSource.py +11 -4
  10. arelle/FunctionIxt.py +16 -11
  11. arelle/HtmlUtil.py +5 -4
  12. arelle/LeiUtil.py +63 -43
  13. arelle/ModelDocument.py +20 -15
  14. arelle/ModelDtsObject.py +8 -0
  15. arelle/ModelInstanceObject.py +1 -1
  16. arelle/ModelObject.py +16 -18
  17. arelle/ModelObjectFactory.py +35 -17
  18. arelle/ModelXbrl.py +28 -11
  19. arelle/PluginManager.py +130 -105
  20. arelle/RuntimeOptions.py +1 -0
  21. arelle/UrlUtil.py +14 -0
  22. arelle/Validate.py +17 -12
  23. arelle/ValidateDuplicateFacts.py +3 -1
  24. arelle/ValidateFileSource.py +38 -0
  25. arelle/ValidateFilingText.py +3 -3
  26. arelle/ValidateXbrl.py +5 -2
  27. arelle/ValidateXbrlCalcs.py +210 -186
  28. arelle/ValidateXbrlDTS.py +1 -1
  29. arelle/ViewFile.py +1 -0
  30. arelle/ViewFileFactTable.py +2 -2
  31. arelle/ViewWinDTS.py +4 -1
  32. arelle/WebCache.py +28 -24
  33. arelle/XbrlConst.py +22 -0
  34. arelle/XmlUtil.py +16 -21
  35. arelle/XmlValidate.py +6 -9
  36. arelle/_version.py +16 -3
  37. arelle/api/Session.py +11 -2
  38. arelle/config/disclosuresystems.xsd +2 -0
  39. arelle/config/rosettaEntitlements.plist +8 -0
  40. arelle/conformance/CSVTestcaseLoader.py +1 -1
  41. arelle/formula/XPathContext.py +3 -3
  42. arelle/logging/formatters/LogFormatter.py +3 -1
  43. arelle/packages/report/ReportPackage.py +26 -13
  44. arelle/packages/report/ReportPackageConst.py +0 -1
  45. arelle/plugin/inlineXbrlDocumentSet.py +19 -5
  46. arelle/plugin/validate/DBA/DisclosureSystems.py +19 -1
  47. arelle/plugin/validate/DBA/PluginValidationDataExtension.py +2 -4
  48. arelle/plugin/validate/DBA/ValidationPluginExtension.py +2 -1
  49. arelle/plugin/validate/DBA/resources/config.xml +5 -0
  50. arelle/plugin/validate/DBA/rules/__init__.py +2 -2
  51. arelle/plugin/validate/DBA/rules/fr.py +19 -2
  52. arelle/plugin/validate/DBA/rules/tc.py +2 -0
  53. arelle/plugin/validate/DBA/rules/th.py +6 -0
  54. arelle/plugin/validate/DBA/rules/tm.py +18 -5
  55. arelle/plugin/validate/DBA/rules/tr.py +11 -5
  56. arelle/plugin/validate/EDINET/Constants.py +193 -9
  57. arelle/plugin/validate/EDINET/ContextRequirement.py +58 -0
  58. arelle/plugin/validate/EDINET/ControllerPluginData.py +220 -1
  59. arelle/plugin/validate/EDINET/CoverItemRequirements.py +42 -0
  60. arelle/plugin/validate/EDINET/DeiRequirements.py +118 -0
  61. arelle/plugin/validate/EDINET/FilingFormat.py +275 -0
  62. arelle/plugin/validate/EDINET/FormType.py +134 -0
  63. arelle/plugin/validate/EDINET/ManifestInstance.py +72 -5
  64. arelle/plugin/validate/EDINET/NamespaceConfig.py +50 -0
  65. arelle/plugin/validate/EDINET/PluginValidationDataExtension.py +493 -132
  66. arelle/plugin/validate/EDINET/{InstanceType.py → ReportFolderType.py} +72 -15
  67. arelle/plugin/validate/EDINET/Statement.py +139 -0
  68. arelle/plugin/validate/EDINET/TableOfContentsBuilder.py +595 -0
  69. arelle/plugin/validate/EDINET/UploadContents.py +48 -0
  70. arelle/plugin/validate/EDINET/ValidationPluginExtension.py +20 -2
  71. arelle/plugin/validate/EDINET/__init__.py +31 -6
  72. arelle/plugin/validate/EDINET/resources/config.xml +8 -1
  73. arelle/plugin/validate/EDINET/resources/cover-item-requirements.json +793 -0
  74. arelle/plugin/validate/EDINET/resources/dei-requirements.csv +27 -0
  75. arelle/plugin/validate/EDINET/resources/edinet-taxonomies.xml +2 -0
  76. arelle/plugin/validate/EDINET/rules/contexts.py +375 -14
  77. arelle/plugin/validate/EDINET/rules/edinet.py +1934 -45
  78. arelle/plugin/validate/EDINET/rules/frta.py +122 -3
  79. arelle/plugin/validate/EDINET/rules/gfm.py +1907 -11
  80. arelle/plugin/validate/EDINET/rules/upload.py +989 -141
  81. arelle/plugin/validate/ESEF/Const.py +3 -1
  82. arelle/plugin/validate/ESEF/ESEF_2021/DTS.py +5 -0
  83. arelle/plugin/validate/ESEF/ESEF_2021/Image.py +2 -2
  84. arelle/plugin/validate/ESEF/ESEF_2021/ValidateXbrlFinally.py +23 -20
  85. arelle/plugin/validate/ESEF/ESEF_Current/DTS.py +47 -14
  86. arelle/plugin/validate/ESEF/ESEF_Current/ValidateXbrlFinally.py +100 -25
  87. arelle/plugin/validate/ESEF/__init__.py +20 -6
  88. arelle/plugin/validate/ESEF/resources/authority-validations.json +76 -9
  89. arelle/plugin/validate/ESEF/resources/config.xml +20 -0
  90. arelle/plugin/validate/NL/DisclosureSystems.py +22 -0
  91. arelle/plugin/validate/NL/PluginValidationDataExtension.py +27 -9
  92. arelle/plugin/validate/NL/ValidationPluginExtension.py +51 -7
  93. arelle/plugin/validate/NL/resources/config.xml +18 -0
  94. arelle/plugin/validate/NL/rules/br_kvk.py +17 -61
  95. arelle/plugin/validate/NL/rules/fg_nl.py +7 -38
  96. arelle/plugin/validate/NL/rules/fr_kvk.py +7 -42
  97. arelle/plugin/validate/NL/rules/fr_nl.py +31 -147
  98. arelle/plugin/validate/NL/rules/nl_kvk.py +142 -28
  99. arelle/plugin/validate/ROS/PluginValidationDataExtension.py +2 -0
  100. arelle/plugin/validate/ROS/ValidationPluginExtension.py +4 -1
  101. arelle/plugin/validate/ROS/rules/ros.py +41 -9
  102. arelle/plugin/validate/UK/ValidateUK.py +130 -66
  103. arelle/plugin/validate/UK/__init__.py +89 -103
  104. arelle/utils/EntryPointDetection.py +79 -13
  105. arelle/utils/PluginHooks.py +125 -0
  106. arelle/utils/validate/ESEFImage.py +6 -6
  107. arelle/utils/validate/Validation.py +18 -0
  108. arelle/utils/validate/ValidationPlugin.py +76 -11
  109. arelle/utils/validate/ValidationUtil.py +35 -3
  110. {arelle_release-2.37.46.dist-info → arelle_release-2.38.0.dist-info}/METADATA +30 -20
  111. {arelle_release-2.37.46.dist-info → arelle_release-2.38.0.dist-info}/RECORD +115 -191
  112. {arelle_release-2.37.46.dist-info → arelle_release-2.38.0.dist-info}/licenses/LICENSE.md +0 -3
  113. arelle/archive/CustomLogger.py +0 -43
  114. arelle/archive/LoadEFMvalidate.py +0 -32
  115. arelle/archive/LoadSavePreLbCsv.py +0 -26
  116. arelle/archive/LoadValidate.cs +0 -31
  117. arelle/archive/LoadValidate.py +0 -36
  118. arelle/archive/LoadValidateCmdLine.java +0 -69
  119. arelle/archive/LoadValidatePostedZip.java +0 -57
  120. arelle/archive/LoadValidateWebService.java +0 -34
  121. arelle/archive/SaveTableToExelle.py +0 -140
  122. arelle/archive/TR3toTR4.py +0 -88
  123. arelle/archive/plugin/ESEF_2022/__init__.py +0 -47
  124. arelle/archive/plugin/bigInstance.py +0 -394
  125. arelle/archive/plugin/cmdWebServerExtension.py +0 -43
  126. arelle/archive/plugin/crashTest.py +0 -38
  127. arelle/archive/plugin/functionsXmlCreation.py +0 -106
  128. arelle/archive/plugin/hello_i18n.pot +0 -26
  129. arelle/archive/plugin/hello_i18n.py +0 -32
  130. arelle/archive/plugin/importTestChild1.py +0 -21
  131. arelle/archive/plugin/importTestChild2.py +0 -22
  132. arelle/archive/plugin/importTestGrandchild1.py +0 -21
  133. arelle/archive/plugin/importTestGrandchild2.py +0 -21
  134. arelle/archive/plugin/importTestImported1.py +0 -23
  135. arelle/archive/plugin/importTestImported11.py +0 -22
  136. arelle/archive/plugin/importTestParent.py +0 -48
  137. arelle/archive/plugin/instanceInfo.py +0 -306
  138. arelle/archive/plugin/loadFromOIM-2018.py +0 -1282
  139. arelle/archive/plugin/locale/fr/LC_MESSAGES/hello_i18n.po +0 -25
  140. arelle/archive/plugin/objectmaker.py +0 -285
  141. arelle/archive/plugin/packagedImportTest/__init__.py +0 -47
  142. arelle/archive/plugin/packagedImportTest/importTestChild1.py +0 -21
  143. arelle/archive/plugin/packagedImportTest/importTestChild2.py +0 -22
  144. arelle/archive/plugin/packagedImportTest/importTestGrandchild1.py +0 -21
  145. arelle/archive/plugin/packagedImportTest/importTestGrandchild2.py +0 -21
  146. arelle/archive/plugin/packagedImportTest/importTestImported1.py +0 -24
  147. arelle/archive/plugin/packagedImportTest/importTestImported11.py +0 -21
  148. arelle/archive/plugin/packagedImportTest/subdir/importTestImported111.py +0 -21
  149. arelle/archive/plugin/packagedImportTest/subdir/subsubdir/importTestImported1111.py +0 -21
  150. arelle/archive/plugin/sakaCalendar.py +0 -215
  151. arelle/archive/plugin/saveInstanceInfoset.py +0 -121
  152. arelle/archive/plugin/sphinx/FormulaGenerator.py +0 -823
  153. arelle/archive/plugin/sphinx/SphinxContext.py +0 -404
  154. arelle/archive/plugin/sphinx/SphinxEvaluator.py +0 -783
  155. arelle/archive/plugin/sphinx/SphinxMethods.py +0 -1287
  156. arelle/archive/plugin/sphinx/SphinxParser.py +0 -1093
  157. arelle/archive/plugin/sphinx/SphinxValidator.py +0 -163
  158. arelle/archive/plugin/sphinx/US-GAAP Ratios Example.xsr +0 -52
  159. arelle/archive/plugin/sphinx/__init__.py +0 -285
  160. arelle/archive/plugin/streamingExtensions.py +0 -335
  161. arelle/archive/plugin/updateTableLB.py +0 -242
  162. arelle/archive/plugin/validate/SBRnl/CustomLoader.py +0 -19
  163. arelle/archive/plugin/validate/SBRnl/DTS.py +0 -305
  164. arelle/archive/plugin/validate/SBRnl/Dimensions.py +0 -357
  165. arelle/archive/plugin/validate/SBRnl/Document.py +0 -799
  166. arelle/archive/plugin/validate/SBRnl/Filing.py +0 -467
  167. arelle/archive/plugin/validate/SBRnl/__init__.py +0 -75
  168. arelle/archive/plugin/validate/SBRnl/config.xml +0 -26
  169. arelle/archive/plugin/validate/SBRnl/sbr-nl-taxonomies.xml +0 -754
  170. arelle/archive/plugin/validate/USBestPractices.py +0 -570
  171. arelle/archive/plugin/validate/USCorpAction.py +0 -557
  172. arelle/archive/plugin/validate/USSecTagging.py +0 -337
  173. arelle/archive/plugin/validate/XDC/__init__.py +0 -77
  174. arelle/archive/plugin/validate/XDC/config.xml +0 -20
  175. arelle/archive/plugin/validate/XFsyntax/__init__.py +0 -64
  176. arelle/archive/plugin/validate/XFsyntax/xf.py +0 -2227
  177. arelle/archive/plugin/validate/calc2.py +0 -536
  178. arelle/archive/plugin/validateSchemaLxml.py +0 -156
  179. arelle/archive/plugin/validateTableInfoset.py +0 -52
  180. arelle/archive/us-gaap-dei-docType-extraction-frm.xml +0 -90
  181. arelle/archive/us-gaap-dei-ratio-cash-frm.xml +0 -150
  182. arelle/examples/plugin/formulaSuiteConverter.py +0 -212
  183. arelle/examples/plugin/functionsCustom.py +0 -59
  184. arelle/examples/plugin/hello_dolly.py +0 -64
  185. arelle/examples/plugin/multi.py +0 -58
  186. arelle/examples/plugin/rssSaveOim.py +0 -96
  187. arelle/examples/plugin/validate/XYZ/DisclosureSystems.py +0 -2
  188. arelle/examples/plugin/validate/XYZ/PluginValidationDataExtension.py +0 -10
  189. arelle/examples/plugin/validate/XYZ/ValidationPluginExtension.py +0 -49
  190. arelle/examples/plugin/validate/XYZ/__init__.py +0 -75
  191. arelle/examples/plugin/validate/XYZ/resources/config.xml +0 -16
  192. arelle/examples/plugin/validate/XYZ/rules/__init__.py +0 -0
  193. arelle/examples/plugin/validate/XYZ/rules/rules01.py +0 -110
  194. arelle/examples/plugin/validate/XYZ/rules/rules02.py +0 -59
  195. arelle/model/CommentBase.py +0 -9
  196. arelle/model/ElementBase.py +0 -11
  197. arelle/model/PIBase.py +0 -10
  198. arelle/model/__init__.py +0 -15
  199. arelle/scripts-macOS/startWebServer.command +0 -3
  200. arelle/scripts-unix/startWebServer.sh +0 -1
  201. arelle/scripts-windows/startWebServer.bat +0 -5
  202. {arelle_release-2.37.46.dist-info → arelle_release-2.38.0.dist-info}/WHEEL +0 -0
  203. {arelle_release-2.37.46.dist-info → arelle_release-2.38.0.dist-info}/entry_points.txt +0 -0
  204. {arelle_release-2.37.46.dist-info → arelle_release-2.38.0.dist-info}/top_level.txt +0 -0
@@ -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.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"):
@@ -19,8 +79,9 @@ def filesourceEntrypointFiles(filesource, entrypointFiles=None, inlineOnly=False
19
79
  if filesource.isTaxonomyPackage: # if archive is also a taxonomy package, activate mappings
20
80
  filesource.loadTaxonomyPackageMappings()
21
81
  # HF note: a web api request to load a specific file from archive is ignored, is this right?
22
- del entrypointFiles[:] # clear out archive from entrypointFiles
82
+ discoveredEntrypointFiles = []
23
83
  if reportPackage := filesource.reportPackage:
84
+ del entrypointFiles[:]
24
85
  assert isinstance(filesource.basefile, str)
25
86
  for report in reportPackage.reports or []:
26
87
  if report.isInline:
@@ -31,10 +92,10 @@ def filesourceEntrypointFiles(filesource, entrypointFiles=None, inlineOnly=False
31
92
  ixdsDiscovered = True
32
93
  if not ixdsDiscovered and len(reportEntries) > 1:
33
94
  raise RuntimeError(_("Loading error. Inline document set encountered. Enable 'InlineXbrlDocumentSet' plug-in to load this filing: {0}").format(filesource.url))
34
- entrypointFiles.extend(reportEntries)
95
+ discoveredEntrypointFiles.extend(reportEntries)
35
96
  elif not inlineOnly:
36
- entrypointFiles.append({"file": report.fullPathPrimary})
37
- else:
97
+ discoveredEntrypointFiles.append({"file": report.fullPathPrimary})
98
+ elif fallbackSelect:
38
99
  # attempt to find inline XBRL files before instance files, .xhtml before probing others (ESMA)
39
100
  urlsByType = {}
40
101
  for _archiveFile in (filesource.dir or ()): # .dir might be none if IOerror
@@ -45,20 +106,25 @@ def filesourceEntrypointFiles(filesource, entrypointFiles=None, inlineOnly=False
45
106
  # use inline instances, if any, else non-inline instances
46
107
  for identifiedType in ((ModelDocument.Type.INLINEXBRL,) if inlineOnly else (ModelDocument.Type.INLINEXBRL, ModelDocument.Type.INSTANCE)):
47
108
  for url in urlsByType.get(identifiedType, []):
48
- entrypointFiles.append({"file":url})
49
- if entrypointFiles:
109
+ discoveredEntrypointFiles.append({"file":url})
110
+ if discoveredEntrypointFiles:
50
111
  if identifiedType == ModelDocument.Type.INLINEXBRL:
51
112
  for pluginXbrlMethod in PluginManager.pluginClassMethods("InlineDocumentSet.Discovery"):
52
- pluginXbrlMethod(filesource, entrypointFiles) # group into IXDS if plugin feature is available
113
+ pluginXbrlMethod(filesource, discoveredEntrypointFiles) # group into IXDS if plugin feature is available
53
114
  break # found inline (or non-inline) entrypoint files, don't look for any other type
54
115
  # for ESEF non-consolidated xhtml documents accept an xhtml entry point
55
- if not entrypointFiles and not inlineOnly:
116
+ if not discoveredEntrypointFiles and not inlineOnly:
56
117
  for url in urlsByType.get(ModelDocument.Type.HTML, []):
57
- entrypointFiles.append({"file":url})
58
- if not entrypointFiles and filesource.taxonomyPackage is not None:
118
+ discoveredEntrypointFiles.append({"file":url})
119
+ if not discoveredEntrypointFiles and filesource.taxonomyPackage is not None:
59
120
  for packageEntry in filesource.taxonomyPackage.get('entryPoints', {}).values():
60
121
  for _resolvedUrl, remappedUrl, _closest in packageEntry:
61
- entrypointFiles.append({"file": remappedUrl})
122
+ discoveredEntrypointFiles.append({"file": remappedUrl})
123
+ if discoveredEntrypointFiles:
124
+ # Only clear archive entry points if we discovered new ones.
125
+ # This could be a plugin loaded archive, such as an Excel file.
126
+ del entrypointFiles[:]
127
+ entrypointFiles.extend(discoveredEntrypointFiles)
62
128
 
63
129
 
64
130
  elif os.path.isdir(filesource.url):
@@ -34,6 +34,8 @@ 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"
38
+ FILESOURCE = "Validate.FileSource"
37
39
  XBRL_START = "Validate.XBRL.Start"
38
40
  XBRL_FINALLY = "Validate.XBRL.Finally"
39
41
  XBRL_DTS_DOCUMENT = "Validate.XBRL.DTS.document"
@@ -41,6 +43,42 @@ class ValidationHook(Enum):
41
43
 
42
44
 
43
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
+
44
82
  @staticmethod
45
83
  def cntlrCmdLineOptions(
46
84
  parser: OptionParser,
@@ -217,6 +255,34 @@ class PluginHooks(ABC):
217
255
  """
218
256
  raise NotImplementedError
219
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
+
220
286
  @staticmethod
221
287
  def cntlrWinMainMenuHelp(
222
288
  cntlr: CntlrWinMain,
@@ -568,6 +634,65 @@ class PluginHooks(ABC):
568
634
  """
569
635
  raise NotImplementedError
570
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
+
666
+ @staticmethod
667
+ def validateFileSource(
668
+ cntlr: Cntlr,
669
+ fileSource: FileSource,
670
+ *args: Any,
671
+ **kwargs: Any,
672
+ ) -> None:
673
+ """
674
+ Plugin hook: `Validate.FileSource`
675
+
676
+ Hook for validating FileSource. This is useful for validations that apply to the filesource and not the
677
+ XBRL models loaded from it.
678
+
679
+ Example:
680
+ ```python
681
+ size = fileSource.getBytesSize()
682
+ if size is None:
683
+ return
684
+ if size > 100_000_000:
685
+ yield Validation.error(codes="0.0.0", msg="File size exceeds 100MB.")
686
+ ```
687
+
688
+ :param cntlr: The [Cntlr](#arelle.Cntlr.Cntlr) instance.
689
+ :param fileSource: The [FileSource](#arelle.FileSource.FileSource) to validate.
690
+ :param args: Argument capture to ensure new parameters don't break plugin hook.
691
+ :param kwargs: Argument capture to ensure new named parameters don't break plugin hook.
692
+ :return: None
693
+ """
694
+ raise NotImplementedError
695
+
571
696
  @staticmethod
572
697
  def validateFinally(
573
698
  val: ValidateXbrl,
@@ -17,7 +17,7 @@ from arelle import ModelDocument
17
17
  from arelle.ModelObjectFactory import parser
18
18
  from arelle.ModelXbrl import ModelXbrl
19
19
  from arelle.typing import TypeGetText
20
- from arelle.UrlUtil import decodeBase64DataImage, scheme
20
+ from arelle.UrlUtil import decodeBase64DataImage, isExternalUrl
21
21
  from arelle.utils.validate.Validation import Validation
22
22
  from arelle.ValidateFilingText import parseImageDataURL, validateGraphicHeaderType
23
23
  from arelle.ValidateXbrl import ValidateXbrl
@@ -105,15 +105,15 @@ def validateImage(
105
105
  if minExternalRessourceSize != -1:
106
106
  # transform kb to b
107
107
  minExternalRessourceSize = minExternalRessourceSize * 1024
108
- if scheme(image) in ("http", "https", "ftp"):
108
+ if isExternalUrl(image):
109
109
  yield Validation.error(("ESEF.4.1.6.xHTMLDocumentContainsExternalReferences" if not params.consolidated
110
110
  else "ESEF.3.5.1.inlineXbrlDocumentContainsExternalReferences",
111
- "NL.NL-KVK.3.6.2.1.inlineXbrlDocumentContainsExternalReferences"),
111
+ "NL.NL-KVK.3.6.2.1.inlineXbrlDocumentSetContainsExternalReferences"),
112
112
  _("Inline XBRL instance documents MUST NOT contain any reference pointing to resources outside the reporting package: %(element)s"),
113
113
  modelObject=elts, element=elts[0].tag, evaluatedMsg=evaluatedMsg,
114
114
  messageCodes=("ESEF.3.5.1.inlineXbrlDocumentContainsExternalReferences",
115
115
  "ESEF.4.1.6.xHTMLDocumentContainsExternalReferences",
116
- "NL.NL-KVK.3.6.2.1.inlineXbrlDocumentContainsExternalReferences"))
116
+ "NL.NL-KVK.3.6.2.1.inlineXbrlDocumentSetContainsExternalReferences"))
117
117
  elif image.startswith("data:image"):
118
118
  dataURLParts = parseImageDataURL(image)
119
119
  if not dataURLParts or not dataURLParts.isBase64:
@@ -277,7 +277,7 @@ def checkSVGContentElt(
277
277
  yield Validation.error((f"{guidance}.executableCodePresent", "NL.NL-KVK.3.5.1.1.executableCodePresent"),
278
278
  _("Inline XBRL images MUST NOT contain executable code: %(element)s"),
279
279
  modelObject=imgElts, element=eltTag)
280
- elif scheme(href) in ("http", "https", "ftp"):
281
- yield Validation.error((f"{guidance}.referencesPointingOutsideOfTheReportingPackagePresent", "NL.NL-KVK.3.6.2.1.inlineXbrlDocumentContainsExternalReferences"),
280
+ elif isExternalUrl(href):
281
+ yield Validation.error((f"{guidance}.referencesPointingOutsideOfTheReportingPackagePresent", "NL.NL-KVK.3.6.2.1.inlineXbrlDocumentSetContainsExternalReferences"),
282
282
  _("Inline XBRL instance document [image] MUST NOT contain any reference pointing to resources outside the reporting package: %(element)s"),
283
283
  modelObject=imgElts, element=eltTag)
@@ -10,6 +10,7 @@ from typing import Any
10
10
 
11
11
  class Level(Enum):
12
12
  ERROR = "ERROR"
13
+ INCONSISTENCY = "INCONSISTENCY"
13
14
  WARNING = "WARNING"
14
15
 
15
16
  @dataclass(frozen=True)
@@ -19,6 +20,15 @@ class Validation:
19
20
  msg: str
20
21
  args: dict[str, Any] = field(default_factory=dict)
21
22
 
23
+ @staticmethod
24
+ def build(
25
+ level: Level,
26
+ codes: str | tuple[str, ...],
27
+ msg: str,
28
+ **kwargs: Any,
29
+ ) -> Validation:
30
+ return Validation(level=level, codes=codes, msg=msg, args=kwargs)
31
+
22
32
  @staticmethod
23
33
  def error(
24
34
  codes: str | tuple[str, ...],
@@ -27,6 +37,14 @@ class Validation:
27
37
  ) -> Validation:
28
38
  return Validation(level=Level.ERROR, codes=codes, msg=msg, args=kwargs)
29
39
 
40
+ @staticmethod
41
+ def inconsistency(
42
+ codes: str | tuple[str, ...],
43
+ msg: str,
44
+ **kwargs: Any,
45
+ ) -> Validation:
46
+ return Validation(level=Level.INCONSISTENCY, codes=codes, msg=msg, args=kwargs)
47
+
30
48
  @staticmethod
31
49
  def warning(
32
50
  codes: str | tuple[str, ...],
@@ -8,17 +8,20 @@ from pathlib import Path
8
8
  from types import ModuleType
9
9
  from typing import Any
10
10
 
11
+ from arelle.Cntlr import Cntlr
11
12
  from arelle.DisclosureSystem import DisclosureSystem
13
+ from arelle.FileSource import FileSource
12
14
  from arelle.ModelDocument import LoadingException, ModelDocument
15
+ from arelle.ModelManager import ModelManager
13
16
  from arelle.ModelXbrl import ModelXbrl
14
17
  from arelle.ValidateXbrl import ValidateXbrl
18
+ from arelle.utils.PluginData import PluginData
15
19
  from arelle.utils.PluginHooks import ValidationHook
16
20
  from arelle.utils.validate.Decorator import (
17
21
  ValidationAttributes,
18
22
  ValidationFunction,
19
23
  getValidationAttributes,
20
24
  )
21
- from arelle.utils.PluginData import PluginData
22
25
 
23
26
 
24
27
  class ValidationPlugin:
@@ -55,7 +58,7 @@ class ValidationPlugin:
55
58
  ValidationHook, dict[ValidationFunction, set[str]]
56
59
  ] = {}
57
60
 
58
- def newPluginData(self, validateXbrl: ValidateXbrl) -> PluginData:
61
+ def newPluginData(self, cntlr: Cntlr, validateXbrl: ValidateXbrl | None) -> PluginData:
59
62
  """
60
63
  Returns a dataclass intended to be overriden by plugins to facilitate caching and passing data between rule functions.
61
64
  The default implementation doesn't provide any fields other than the plugin name.
@@ -99,6 +102,46 @@ class ValidationPlugin:
99
102
  ) -> ModelDocument | LoadingException | None:
100
103
  raise NotImplementedError
101
104
 
105
+ def validateComplete(
106
+ self,
107
+ cntlr: Cntlr,
108
+ fileSource: FileSource,
109
+ *args: Any,
110
+ **kwargs: Any,
111
+ ) -> None:
112
+ """
113
+ Executes validation functions in the rules module that was provided to the constructor of this class.
114
+ Each function decorated with [@validation](#arelle.utils.validate.Decorator.validation) will be run if:
115
+ 1. the decorator was used with the validation complete hook: `@validation(hook=ValidationHook.COMPLETE)`
116
+
117
+ :param cntlr: The [Cntlr](#arelle.Cntlr.Cntlr) instance.
118
+ :param fileSource: The [FileSource](#arelle.FileSource.FileSource) involved in loading the entrypoint files.
119
+ :param args: Argument capture to ensure new parameters don't break plugin hook.
120
+ :param kwargs: Argument capture to ensure new named parameters don't break plugin hook.
121
+ :return: None
122
+ """
123
+ self._executeCntlrValidations(ValidationHook.COMPLETE, cntlr, fileSource, *args, **kwargs)
124
+
125
+ def validateFileSource(
126
+ self,
127
+ cntlr: Cntlr,
128
+ fileSource: FileSource,
129
+ *args: Any,
130
+ **kwargs: Any,
131
+ ) -> None:
132
+ """
133
+ Executes validation functions in the rules module that was provided to the constructor of this class.
134
+ Each function decorated with [@validation](#arelle.utils.validate.Decorator.validation) will be run if:
135
+ 1. the decorator was used with the FileSource validation hook: `@validation(hook=ValidationHook.FILESOURCE)`
136
+
137
+ :param cntlr: The [Cntlr](#arelle.Cntlr.Cntlr) instance.
138
+ :param fileSource: The [FileSource](#arelle.FileSource.FileSource) to validate.
139
+ :param args: Argument capture to ensure new parameters don't break plugin hook.
140
+ :param kwargs: Argument capture to ensure new named parameters don't break plugin hook.
141
+ :return: None
142
+ """
143
+ self._executeCntlrValidations(ValidationHook.FILESOURCE, cntlr, fileSource, *args, **kwargs)
144
+
102
145
  def validateXbrlStart(
103
146
  self,
104
147
  val: ValidateXbrl,
@@ -118,7 +161,7 @@ class ValidationPlugin:
118
161
  :param kwargs: Argument capture to ensure new named parameters don't break plugin hook.
119
162
  :return: None
120
163
  """
121
- self._executeValidations(ValidationHook.XBRL_START, val, parameters, *args, **kwargs)
164
+ self._executeModelValidations(ValidationHook.XBRL_START, val, parameters, *args, **kwargs)
122
165
 
123
166
  def validateXbrlFinally(
124
167
  self,
@@ -137,7 +180,7 @@ class ValidationPlugin:
137
180
  :param kwargs: Argument capture to ensure new named parameters don't break plugin hook.
138
181
  :return: None
139
182
  """
140
- self._executeValidations(ValidationHook.XBRL_FINALLY, val, *args, **kwargs)
183
+ self._executeModelValidations(ValidationHook.XBRL_FINALLY, val, *args, **kwargs)
141
184
 
142
185
  def validateXbrlDtsDocument(
143
186
  self,
@@ -158,7 +201,7 @@ class ValidationPlugin:
158
201
  :param kwargs: Argument capture to ensure new named parameters don't break plugin hook.
159
202
  :return: None
160
203
  """
161
- self._executeValidations(ValidationHook.XBRL_DTS_DOCUMENT, val, modelDocument, isFilingDocument, *args, **kwargs)
204
+ self._executeModelValidations(ValidationHook.XBRL_DTS_DOCUMENT, val, modelDocument, isFilingDocument, *args, **kwargs)
162
205
 
163
206
  def validateFinally(
164
207
  self,
@@ -177,9 +220,28 @@ class ValidationPlugin:
177
220
  :param kwargs: Argument capture to ensure new named parameters don't break plugin hook.
178
221
  :return: None
179
222
  """
180
- self._executeValidations(ValidationHook.FINALLY, val, *args, **kwargs)
223
+ self._executeModelValidations(ValidationHook.FINALLY, val, *args, **kwargs)
224
+
225
+ def _executeCntlrValidations(
226
+ self,
227
+ pluginHook: ValidationHook,
228
+ cntlr: Cntlr,
229
+ fileSource: FileSource | None = None,
230
+ *args: Any,
231
+ **kwargs: Any,
232
+ ) -> None:
233
+ if self.disclosureSystemFromPluginSelected(cntlr.modelManager):
234
+ pluginData = self.newPluginData(
235
+ cntlr=cntlr,
236
+ validateXbrl=None
237
+ )
238
+ for rule in self._getValidations(cntlr.modelManager.disclosureSystem, pluginHook):
239
+ validations = rule(pluginData, cntlr, fileSource, *args, **kwargs)
240
+ if validations is not None:
241
+ for val in validations:
242
+ cntlr.validation(val, fileSource=fileSource)
181
243
 
182
- def _executeValidations(
244
+ def _executeModelValidations(
183
245
  self,
184
246
  pluginHook: ValidationHook,
185
247
  validateXbrl: ValidateXbrl,
@@ -189,20 +251,23 @@ class ValidationPlugin:
189
251
  if self.disclosureSystemFromPluginSelected(validateXbrl):
190
252
  pluginData = validateXbrl.getPluginData(self.name)
191
253
  if pluginData is None:
192
- pluginData = self.newPluginData(validateXbrl)
254
+ pluginData = self.newPluginData(
255
+ cntlr=validateXbrl.modelXbrl.modelManager.cntlr,
256
+ validateXbrl=validateXbrl
257
+ )
193
258
  validateXbrl.setPluginData(pluginData)
194
259
  for rule in self._getValidations(validateXbrl.disclosureSystem, pluginHook):
195
260
  validations = rule(pluginData, validateXbrl, *args, **kwargs)
196
261
  if validations is not None:
197
262
  modelXbrl = validateXbrl.modelXbrl
198
263
  for val in validations:
199
- modelXbrl.log(level=val.level.name, codes=val.codes, msg=val.msg, **val.args)
264
+ modelXbrl.validation(val)
200
265
 
201
266
  def disclosureSystemFromPluginSelected(
202
267
  self,
203
- model: ValidateXbrl | ModelXbrl,
268
+ model: ValidateXbrl | ModelManager | ModelXbrl,
204
269
  ) -> bool:
205
- if isinstance(model, ValidateXbrl):
270
+ if isinstance(model, (ModelManager, ValidateXbrl)):
206
271
  disclosureSystem = model.disclosureSystem
207
272
  elif isinstance(model, ModelXbrl):
208
273
  disclosureSystem = model.modelManager.disclosureSystem
@@ -1,13 +1,18 @@
1
1
  """
2
2
  See COPYRIGHT.md for copyright information.
3
3
  """
4
+
4
5
  from __future__ import annotations
5
6
 
6
- from collections.abc import Generator
7
+ from collections.abc import Generator, Iterable
7
8
 
8
9
  from lxml.etree import _Element
9
10
 
11
+ from arelle import XbrlConst
12
+ from arelle.ModelDtsObject import ModelConcept
10
13
  from arelle.ModelObject import ModelObject
14
+ from arelle.ModelValue import QName
15
+ from arelle.ModelXbrl import ModelXbrl
11
16
  from arelle.typing import TypeGetText
12
17
 
13
18
  _: TypeGetText
@@ -19,5 +24,32 @@ def etreeIterWithDepth(
19
24
  ) -> Generator[tuple[ModelObject | _Element, int], None, None]:
20
25
  yield node, depth
21
26
  for child in node.iterchildren():
22
- for n_d in etreeIterWithDepth(child, depth + 1):
23
- yield n_d
27
+ yield from etreeIterWithDepth(child, depth + 1)
28
+
29
+
30
+ def hasPresentationalConceptsWithFacts(
31
+ modelXbrl: ModelXbrl,
32
+ roleUris: Iterable[str],
33
+ memberQnameFilter: set[QName] | None = None,
34
+ ) -> bool:
35
+ """
36
+ Returns True if any concepts used in the presentation network of the role URIs have been tagged with facts.
37
+ This DOES NOT check if the facts are dimensionally valid against hypercubes defined in the roles.
38
+ """
39
+ roleRelSet = modelXbrl.relationshipSet(XbrlConst.parentChild, tuple(roleUris))
40
+ concepts = set(roleRelSet.fromModelObjects().keys()) | set(roleRelSet.toModelObjects().keys())
41
+ for concept in concepts:
42
+ if not isinstance(concept, ModelConcept):
43
+ continue
44
+ if concept.qname is None:
45
+ continue
46
+ if concept.isAbstract:
47
+ continue
48
+ for fact in modelXbrl.factsByQname.get(concept.qname, set()):
49
+ if memberQnameFilter is None:
50
+ return True
51
+ if fact.context is not None:
52
+ for dimValue in fact.context.qnameDims.values():
53
+ if dimValue.memberQname in memberQnameFilter:
54
+ return True
55
+ return False
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arelle-release
3
- Version: 2.37.46
3
+ Version: 2.38.0
4
4
  Summary: An open source XBRL platform.
5
5
  Author-email: "arelle.org" <support@arelle.org>
6
6
  License-Expression: Apache-2.0
@@ -18,47 +18,57 @@ Classifier: Intended Audience :: End Users/Desktop
18
18
  Classifier: Intended Audience :: Developers
19
19
  Classifier: Natural Language :: English
20
20
  Classifier: Programming Language :: Python :: 3
21
- Classifier: Programming Language :: Python :: 3.9
22
21
  Classifier: Programming Language :: Python :: 3.10
23
22
  Classifier: Programming Language :: Python :: 3.11
24
23
  Classifier: Programming Language :: Python :: 3.12
25
24
  Classifier: Programming Language :: Python :: 3.13
25
+ Classifier: Programming Language :: Python :: 3.14
26
26
  Classifier: Operating System :: OS Independent
27
27
  Classifier: Topic :: Text Processing :: Markup :: XML
28
- Requires-Python: <3.14,>=3.9
28
+ Requires-Python: >=3.10
29
29
  Description-Content-Type: text/markdown
30
30
  License-File: LICENSE.md
31
31
  Requires-Dist: bottle<0.14,>=0.13
32
32
  Requires-Dist: certifi
33
33
  Requires-Dist: filelock
34
- Requires-Dist: jsonschema==4.*
35
- Requires-Dist: isodate==0.*
36
- Requires-Dist: lxml<6,>=4
34
+ Requires-Dist: isodate<1,>=0
35
+ Requires-Dist: jaconv<1,>=0
36
+ Requires-Dist: jsonschema<5,>=4
37
+ Requires-Dist: lxml!=6.0.0,<7,>=4
37
38
  Requires-Dist: numpy<3,>=1
38
- Requires-Dist: openpyxl==3.*
39
- Requires-Dist: pillow<12,>=10
40
- Requires-Dist: pyparsing==3.*
41
- Requires-Dist: python-dateutil==2.*
39
+ Requires-Dist: openpyxl<4,>=3
40
+ Requires-Dist: pillow<13,>=10
41
+ Requires-Dist: pyparsing<4,>=3
42
+ Requires-Dist: python-dateutil<3,>=2
42
43
  Requires-Dist: regex
43
- Requires-Dist: truststore==0.*; python_version > "3.9"
44
- Requires-Dist: typing-extensions==4.*
44
+ Requires-Dist: truststore<1,>=0
45
+ Requires-Dist: typing-extensions<5,>=4
45
46
  Provides-Extra: crypto
46
- Requires-Dist: pycryptodome==3.*; extra == "crypto"
47
+ Requires-Dist: pycryptodome<4,>=3; extra == "crypto"
47
48
  Provides-Extra: db
48
- Requires-Dist: pg8000==1.*; extra == "db"
49
- Requires-Dist: PyMySQL==1.*; extra == "db"
49
+ Requires-Dist: cx_Oracle<9,>=8; extra == "db"
50
+ Requires-Dist: pg8000<2,>=1; extra == "db"
51
+ Requires-Dist: PyMySQL<2,>=1; extra == "db"
50
52
  Requires-Dist: pyodbc<6,>=4; extra == "db"
51
53
  Requires-Dist: rdflib<8,>=5; extra == "db"
52
54
  Provides-Extra: efm
53
- Requires-Dist: holidays==0.*; extra == "efm"
55
+ Requires-Dist: aniso8601<11,>=10; extra == "efm"
56
+ Requires-Dist: holidays<1,>=0; extra == "efm"
57
+ Requires-Dist: matplotlib<4,>=3; extra == "efm"
54
58
  Requires-Dist: pytz; extra == "efm"
55
59
  Provides-Extra: esef
56
- Requires-Dist: tinycss2==1.*; extra == "esef"
60
+ Requires-Dist: tinycss2<2,>=1; extra == "esef"
61
+ Provides-Extra: gui
62
+ Requires-Dist: pywin32<400,>=300; sys_platform == "win32" and extra == "gui"
57
63
  Provides-Extra: objectmaker
58
- Requires-Dist: graphviz==0.*; extra == "objectmaker"
64
+ Requires-Dist: graphviz<1,>=0; extra == "objectmaker"
65
+ Provides-Extra: viewer
66
+ Requires-Dist: ixbrl-viewer<2,>=1; extra == "viewer"
59
67
  Provides-Extra: webserver
60
- Requires-Dist: cheroot<11,>=8; extra == "webserver"
61
- Requires-Dist: tornado==6.*; extra == "webserver"
68
+ Requires-Dist: cheroot<12,>=8; extra == "webserver"
69
+ Requires-Dist: tornado<7,>=6; extra == "webserver"
70
+ Provides-Extra: xule
71
+ Requires-Dist: aniso8601<11,>=10; extra == "xule"
62
72
  Dynamic: license-file
63
73
 
64
74
  # Arelle