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
@@ -10,7 +10,6 @@ from arelle.FunctionIxt import ixtNamespaces
10
10
  from arelle.ModelValue import QName, qname
11
11
  from arelle.XmlValidate import lexicalPatterns
12
12
 
13
- styleIxHiddenPattern = re.compile(r"(.*[^\w]|^)-esef-ix-hidden\s*:\s*([\w.-]+).*")
14
13
  styleCssHiddenPattern = re.compile(r"(.*[^\w]|^)display\s*:\s*none([^\w].*|$)")
15
14
  datetimePattern = lexicalPatterns["XBRLI_DATEUNION"]
16
15
  docTypeXhtmlPattern = re.compile(r"^<!(?:DOCTYPE\s+)\s*html(?:PUBLIC\s+)?(?:.*-//W3C//DTD\s+(X?HTML)\s)?.*>$", re.IGNORECASE)
@@ -119,6 +118,9 @@ filenameRegexes = {
119
118
  "ref": r"(.{1,})-[0-9]{4}-[0-9]{2}-[0-9]{2}_ref[.]xml$",
120
119
  }
121
120
 
121
+ reportBasenamePattern = "{base}-{date}-{version}-{lang}"
122
+ reportBasenameRegex = re.compile(r"(.{1,})-[0-9]{4}-[0-9]{2}-[0-9]{2}-[1-9]-[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$")
123
+
122
124
  mandatory: set[QName] = set() # mandatory element qnames
123
125
 
124
126
  # hidden references
@@ -348,6 +348,11 @@ def checkFilingDTS(
348
348
  val.modelXbrl.error("ESEF.3.1.1.linkbasesNotSeparateFiles",
349
349
  _("Each linkbase type MUST be provided in a separate linkbase file, found: %(linkbasesFound)s."),
350
350
  modelObject=modelDocument.xmlRootElement, linkbasesFound=", ".join(sorted(linkbasesFound)))
351
+ # As per ESEF conformance test case TC2_invalid, also output an extensionTaxonomyWrongFilesStructure error
352
+ val.modelXbrl.error("ESEF.3.1.1.extensionTaxonomyWrongFilesStructure",
353
+ _("Each linkbase type MUST be provided in a separate linkbase file, please check file %(documentName)s."),
354
+ modelObject=modelDocument.xmlRootElement,
355
+ documentName=modelDocument.basename)
351
356
 
352
357
  # check for any prohibiting dimensionArc's
353
358
  for prohibitingArcElt in modelDocument.xmlRootElement.iterdescendants(tag="{http://www.xbrl.org/2003/linkbase}definitionArc"):
@@ -10,7 +10,7 @@ from lxml.etree import XML, XMLSyntaxError
10
10
 
11
11
  from arelle.ModelObject import ModelObject
12
12
  from arelle.ModelXbrl import ModelXbrl
13
- from arelle.UrlUtil import scheme
13
+ from arelle.UrlUtil import isExternalUrl
14
14
  from arelle.ValidateFilingText import validateGraphicHeaderType
15
15
  from arelle.typing import TypeGetText
16
16
  from ..Const import supportedImgTypes
@@ -89,7 +89,7 @@ def checkSVGContent(
89
89
  modelXbrl.error(f"{guidance}.executableCodePresent",
90
90
  _("Inline XBRL images MUST NOT contain executable code: %(element)s"),
91
91
  modelObject=imgElt, element=eltTag)
92
- elif scheme(href) in ("http", "https", "ftp"):
92
+ elif isExternalUrl(href):
93
93
  modelXbrl.error(f"{guidance}.referencesPointingOutsideOfTheReportingPackagePresent",
94
94
  _("Inline XBRL instance document [image] MUST NOT contain any reference pointing to resources outside the reporting package: %(element)s"),
95
95
  modelObject=imgElt, element=eltTag)
@@ -27,7 +27,7 @@ from arelle.PythonUtil import isLegacyAbs, strTruncate
27
27
  from arelle.utils.Contexts import partitionModelXbrlContexts
28
28
  from arelle.utils.Units import partitionModelXbrlUnits
29
29
  from arelle.utils.validate.DetectScriptsInXhtml import containsScriptMarkers
30
- from arelle.UrlUtil import decodeBase64DataImage, isHttpUrl, scheme
30
+ from arelle.UrlUtil import decodeBase64DataImage, isHttpUrl, isExternalUrl
31
31
  from arelle.ValidateFilingText import parseImageDataURL
32
32
  from arelle.ValidateUtr import ValidateUtr
33
33
  from arelle.ValidateXbrl import ValidateXbrl
@@ -60,7 +60,6 @@ from ..Const import (
60
60
  esefStatementsOfMonetaryDeclarationNames,
61
61
  mandatory,
62
62
  styleCssHiddenPattern,
63
- styleIxHiddenPattern,
64
63
  untransformableTypes,
65
64
  )
66
65
  from ..Dimensions import checkFilingDimensions
@@ -269,6 +268,9 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
269
268
  "%(documentSets)s (Document files appear to be in multiple document sets)"),
270
269
  modelObject=doc, documentSets=", ".join(sorted(ixdsDocDirs)))
271
270
  ixTargetUsage = val.authParam["ixTargetUsage"]
271
+ styleIxHiddenProperty = val.authParam.get("styleIxHiddenProperty")
272
+ if styleIxHiddenProperty:
273
+ styleIxHiddenPattern = re.compile(rf"(.*[^\w]|^){styleIxHiddenProperty}\s*:\s*([\w.-]+).*")
272
274
  if modelDocument.type in (ModelDocument.Type.INLINEXBRL, ModelDocument.Type.INLINEXBRLDOCUMENTSET, ModelDocument.Type.UnknownXML):
273
275
  hiddenEltIds = {}
274
276
  presentedHiddenEltIds = defaultdict(list)
@@ -307,7 +309,7 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
307
309
  modelObject=elt, element=eltTag)
308
310
  elif eltTag == "img":
309
311
  src = elt.get("src","").strip()
310
- if scheme(src) in ("http", "https", "ftp"):
312
+ if isExternalUrl(src):
311
313
  modelXbrl.error("ESEF.4.1.6.xHTMLDocumentContainsExternalReferences" if val.unconsolidated
312
314
  else "ESEF.3.5.1.inlineXbrlDocumentContainsExternalReferences",
313
315
  _("Inline XBRL instance documents MUST NOT contain any reference pointing to resources outside the reporting package: %(element)s"),
@@ -341,7 +343,7 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
341
343
  _("Images included in the XHTML document SHOULD be base64 encoded: %(src)s."),
342
344
  modelObject=elt, src=src[:128])
343
345
  if dataURLParts and dataURLParts.mimeSubtype and dataURLParts.data:
344
- checkImageContents(modelXbrl, elt, dataURLParts.mimeSubtype, False, dataURLParts.data, val.consolidated)
346
+ checkImageContents(modelXbrl, elt, dataURLParts.mimeSubtype, False, dataURLParts.data, val.consolidated) # type: ignore[arg-type]
345
347
  else:
346
348
  if not dataURLParts.mimeSubtype:
347
349
  modelXbrl.error(f"{contentOtherThanXHTMLGuidance}.MIMETypeNotSpecified",
@@ -353,7 +355,7 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
353
355
  modelObject=elt, src=src[:128])
354
356
  # check for malicious image contents
355
357
  try: # allow embedded newlines
356
- checkImageContents(modelXbrl, elt, dataURLParts.mimeSubtype, False, decodeBase64DataImage(dataURLParts.data), val.consolidated)
358
+ checkImageContents(modelXbrl, elt, dataURLParts.mimeSubtype, False, decodeBase64DataImage(dataURLParts.data), val.consolidated) # type: ignore[arg-type]
357
359
  imgContents = None # deref, may be very large
358
360
  except binascii.Error as err:
359
361
  modelXbrl.error(f"{contentOtherThanXHTMLGuidance}.embeddedImageNotUsingBase64Encoding",
@@ -365,7 +367,7 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
365
367
  # or to other sections of the annual financial report.'''
366
368
  #elif eltTag == "a":
367
369
  # href = elt.get("href","").strip()
368
- # if scheme(href) in ("http", "https", "ftp"):
370
+ # if isExternalUrl(href):
369
371
  # modelXbrl.error("ESEF.4.1.6.xHTMLDocumentContainsExternalReferences" if val.unconsolidated
370
372
  # else "ESEF.3.5.1.inlineXbrlDocumentContainsExternalReferences",
371
373
  # _("Inline XBRL instance documents MUST NOT contain any reference pointing to resources outside the reporting package: %(element)s"),
@@ -420,7 +422,7 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
420
422
  if isinstance(elt, ModelInlineFootnote):
421
423
  checkFootnote(elt, elt.value)
422
424
  elif isinstance(elt, ModelResource) and elt.qname == XbrlConst.qnLinkFootnote:
423
- checkFootnote(elt, elt.value)
425
+ checkFootnote(elt, elt.value) # type: ignore[attr-defined]
424
426
  elif isinstance(elt, ModelInlineFact):
425
427
  if elt.format is not None and elt.format.namespaceURI not in IXT_NAMESPACES:
426
428
  transformRegistryErrors.add(elt)
@@ -461,17 +463,18 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
461
463
  modelObject=eligibleForTransformHiddenFacts,
462
464
  countEligible=len(eligibleForTransformHiddenFacts),
463
465
  elements=", ".join(sorted(set(str(f.qname) for f in eligibleForTransformHiddenFacts))))
464
- for ixdsHtmlRootElt in modelXbrl.ixdsHtmlElements:
465
- for ixElt in ixdsHtmlRootElt.getroottree().iterfind(".//{http://www.w3.org/1999/xhtml}*[@style]"):
466
- hiddenFactRefMatch = styleIxHiddenPattern.match(ixElt.get("style",""))
467
- if hiddenFactRefMatch:
468
- hiddenFactRef = hiddenFactRefMatch.group(2)
469
- if hiddenFactRef not in hiddenEltIds:
470
- modelXbrl.error("ESEF.2.4.1.esefIxHiddenStyleNotLinkingFactInHiddenSection",
471
- _("\"-esef-ix-hidden\" style identifies id attribute of a fact that is not in ix:hidden section: %(factId)s"),
472
- modelObject=ixElt, factId=hiddenFactRef)
473
- else:
474
- presentedHiddenEltIds[hiddenFactRef].append(ixElt)
466
+ if styleIxHiddenProperty:
467
+ for ixdsHtmlRootElt in modelXbrl.ixdsHtmlElements:
468
+ for ixElt in ixdsHtmlRootElt.getroottree().iterfind(".//{http://www.w3.org/1999/xhtml}*[@style]"):
469
+ hiddenFactRefMatch = styleIxHiddenPattern.match(ixElt.get("style",""))
470
+ if hiddenFactRefMatch:
471
+ hiddenFactRef = hiddenFactRefMatch.group(2)
472
+ if hiddenFactRef not in hiddenEltIds:
473
+ modelXbrl.error("ESEF.2.4.1.esefIxHiddenStyleNotLinkingFactInHiddenSection",
474
+ _("\"%(styleIxHiddenProperty)s\" style identifies id attribute of a fact that is not in ix:hidden section: %(factId)s"),
475
+ modelObject=ixElt, styleIxHiddenProperty=styleIxHiddenProperty, factId=hiddenFactRef)
476
+ else:
477
+ presentedHiddenEltIds[hiddenFactRef].append(ixElt)
475
478
  for hiddenEltId, ixElt in hiddenEltIds.items():
476
479
  if (hiddenEltId not in presentedHiddenEltIds and
477
480
  getattr(ixElt, "xValid", 0) >= VALID and # may not be validated
@@ -529,11 +532,11 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
529
532
  if contextsWithDisallowedOCEs:
530
533
  modelXbrl.error("ESEF.2.1.3.segmentUsed",
531
534
  _("xbrli:segment container MUST NOT be used in contexts: %(contextIds)s"),
532
- modelObject=contextsWithDisallowedOCEs, contextIds=", ".join(c.id for c in contextsWithDisallowedOCEs))
535
+ modelObject=contextsWithDisallowedOCEs, contextIds=", ".join(c.id for c in contextsWithDisallowedOCEs if c.id is not None))
533
536
  if contextsWithDisallowedOCEcontent:
534
537
  modelXbrl.error("ESEF.2.1.3.scenarioContainsNonDimensionalContent",
535
538
  _("xbrli:scenario in contexts MUST NOT contain any other content than defined in XBRL Dimensions specification: %(contextIds)s"),
536
- modelObject=contextsWithDisallowedOCEcontent, contextIds=", ".join(c.id for c in contextsWithDisallowedOCEcontent))
539
+ modelObject=contextsWithDisallowedOCEcontent, contextIds=", ".join(c.id for c in contextsWithDisallowedOCEcontent if c.id is not None))
537
540
  if len(contextIdentifiers) > 1:
538
541
  modelXbrl.error("ESEF.2.1.4.multipleIdentifiers",
539
542
  _("All entity identifiers in contexts MUST have identical content: %(contextIds)s"),
@@ -164,21 +164,49 @@ def checkFilingDTS(val: ValidateXbrl, modelDocument: ModelDocument, esefNotesCon
164
164
  if modelConcept.isMonetary and not modelConcept.balance:
165
165
  extMonetaryConceptsWithoutBalance.append(modelConcept)
166
166
  if esefDisclosureSystemYear >= 2024:
167
- widerConcept = widerNarrowerRelSet.fromModelObject(modelConcept)
168
- narrowerConcept = widerNarrowerRelSet.toModelObject(modelConcept)
169
-
170
- # Transform the qname to str for the later join()
171
- widerTypes = set(r.toModelObject.typeQname for r in widerConcept if r.toModelObject is not None)
172
- narrowerTypes = set(r.fromModelObject.typeQname for r in narrowerConcept if r.fromModelObject is not None)
173
-
174
- if (narrowerTypes and narrowerTypes != {modelConcept.typeQname}) or (widerTypes and widerTypes != {modelConcept.typeQname}):
175
- widerNarrowerType = "{} {}".format(
176
- "Wider: {}".format(", ".join(t.clarkNotation for t in widerTypes)) if widerTypes else "",
177
- "Narrower: {}".format(", ".join(t.clarkNotation for t in narrowerTypes)) if narrowerTypes else ""
167
+ widerRels = widerNarrowerRelSet.fromModelObject(modelConcept)
168
+ narrowerRels = widerNarrowerRelSet.toModelObject(modelConcept)
169
+
170
+ widerTypes = set(
171
+ r.toModelObject.typeQname
172
+ for r in widerRels
173
+ if r.toModelObject is not None
174
+ if r.toModelObject.typeQname is not None
175
+ )
176
+ narrowerTypes = set(
177
+ r.fromModelObject.typeQname
178
+ for r in narrowerRels
179
+ if r.fromModelObject is not None
180
+ if r.fromModelObject.typeQname is not None
181
+ )
182
+
183
+ allAnchorTypes = widerTypes | narrowerTypes
184
+ if esefDisclosureSystemYear == 2024:
185
+ invalidAnchorTypes = any(t != modelConcept.typeQname for t in allAnchorTypes)
186
+ msg = _("Issuers should anchor their extension elements to ESEF core taxonomy "
187
+ "elements sharing the same data type.")
188
+ else:
189
+ invalidAnchorTypes = any(not modelConcept.instanceOfType(t) for t in allAnchorTypes)
190
+ msg = _("Issuers should anchor their extension elements to ESEF core taxonomy "
191
+ "elements with the same data type or a data type derived from the "
192
+ "anchor's data type.")
193
+ if invalidAnchorTypes:
194
+ anchorTypeParts = []
195
+ if widerTypes:
196
+ widerStr = ", ".join(sorted(t.clarkNotation for t in widerTypes))
197
+ anchorTypeParts.append(f"Wider: {widerStr}")
198
+ if narrowerTypes:
199
+ narrowerStr = ", ".join(sorted(t.clarkNotation for t in narrowerTypes))
200
+ anchorTypeParts.append(f"Narrower: {narrowerStr}")
201
+ anchorTypeStr = " ".join(anchorTypeParts)
202
+ val.modelXbrl.warning(
203
+ "ESEF.1.4.1.differentExtensionDataType",
204
+ msg + _(" Concept: %(qname)s type: %(type)s %(anchorTypes)s"),
205
+ modelObject=modelConcept,
206
+ qname=modelConcept.qname,
207
+ type=modelConcept.typeQname.clarkNotation,
208
+ anchorTypes=anchorTypeStr
178
209
  )
179
- val.modelXbrl.warning("ESEF.1.4.1.differentExtensionDataType",
180
- _("Issuers should anchor their extension elements to ESEF core taxonomy elements sharing the same data type. Concept: %(qname)s type: %(type)s %(widerNarrowerType)s"),
181
- modelObject=modelConcept, qname=modelConcept.qname, type=modelConcept.typeQname, widerNarrowerType=widerNarrowerType)
182
210
 
183
211
  # check all lang's of standard label
184
212
  hasStandardLabel = False
@@ -386,6 +414,11 @@ def checkFilingDTS(val: ValidateXbrl, modelDocument: ModelDocument, esefNotesCon
386
414
  val.modelXbrl.error("ESEF.3.1.1.linkbasesNotSeparateFiles",
387
415
  _("Each linkbase type MUST be provided in a separate linkbase file, found: %(linkbasesFound)s."),
388
416
  modelObject=modelDocument.xmlRootElement, linkbasesFound=", ".join(sorted(linkbasesFound)))
417
+ # As per ESEF conformance test case TC2_invalid, also output an extensionTaxonomyWrongFilesStructure error
418
+ val.modelXbrl.error("ESEF.3.1.1.extensionTaxonomyWrongFilesStructure",
419
+ _("Each linkbase type MUST be provided in a separate linkbase file, please check file %(documentName)s."),
420
+ modelObject=modelDocument.xmlRootElement,
421
+ documentName=modelDocument.basename)
389
422
 
390
423
  # check for any prohibiting dimensionArc's
391
424
  for prohibitingArcElt in modelDocument.xmlRootElement.iterdescendants(tag="{http://www.xbrl.org/2003/linkbase}definitionArc"):
@@ -15,6 +15,7 @@ import tinycss2.ast # type: ignore[import-untyped]
15
15
  from lxml.etree import _Comment, _Element, _ElementTree, _Entity, _ProcessingInstruction
16
16
 
17
17
  from arelle import LeiUtil, ModelDocument, XbrlConst
18
+ from arelle.FunctionIxt import ixtNamespacesByVersion
18
19
  from arelle.ModelDtsObject import ModelConcept
19
20
  from arelle.ModelDtsObject import ModelResource
20
21
  from arelle.ModelInstanceObject import ModelContext
@@ -33,7 +34,7 @@ from arelle.PythonUtil import strTruncate
33
34
  from arelle.utils.Contexts import partitionModelXbrlContexts
34
35
  from arelle.utils.Units import partitionModelXbrlUnits
35
36
  from arelle.utils.validate.DetectScriptsInXhtml import containsScriptMarkers
36
- from arelle.UrlUtil import isHttpUrl
37
+ from arelle.UrlUtil import isHttpUrl, isExternalUrl
37
38
  from arelle.ValidateUtr import ValidateUtr
38
39
  from arelle.ValidateXbrl import ValidateXbrl
39
40
  from arelle.ValidateXbrlCalcs import inferredDecimals, rangeValue
@@ -56,7 +57,6 @@ from .DTS import checkFilingDTS
56
57
  from ..Const import (
57
58
  DefaultDimensionLinkroles,
58
59
  FOOTNOTE_LINK_CHILDREN,
59
- IXT_NAMESPACES,
60
60
  LineItemsNotQualifiedLinkroles,
61
61
  PERCENT_TYPES, datetimePattern,
62
62
  docTypeXhtmlPattern,
@@ -64,9 +64,10 @@ from ..Const import (
64
64
  esefPrimaryStatementPlaceholderNames,
65
65
  mandatory,
66
66
  styleCssHiddenPattern,
67
- styleIxHiddenPattern,
68
67
  supportedImgTypes,
69
68
  untransformableTypes,
69
+ reportBasenamePattern,
70
+ reportBasenameRegex
70
71
  )
71
72
  from ..Dimensions import checkFilingDimensions
72
73
  from ..Util import checkForMultiLangDuplicates, getEsefNotesStatementConcepts, isExtension, getDisclosureSystemYear
@@ -74,6 +75,7 @@ from ..Util import checkForMultiLangDuplicates, getEsefNotesStatementConcepts, i
74
75
  _: TypeGetText # Handle gettext
75
76
 
76
77
  ifrsNsPattern = re.compile(r"https?://xbrl.ifrs.org/taxonomy/[0-9-]{10}/ifrs-full")
78
+ escapeWorthyStr = re.compile(r".*[<&]")
77
79
 
78
80
 
79
81
  def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
@@ -294,6 +296,12 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
294
296
  "http://www.xbrl.org/WGN/report-packages/WGN-2018-08-14/report-packages-WGN-2018-08-14"
295
297
  ".html: %(fileName)s (Document file not in correct place in package)"),
296
298
  modelObject=doc, fileName=doc.basename)
299
+ elif esefDisclosureSystemYear >= 2025:
300
+ m = reportBasenameRegex.match(_baseName)
301
+ if not m:
302
+ modelXbrl.error("ESEF.2.6.3.incorrectNamingConventionReportPackageReportFile",
303
+ _("Inline XBRL document filename SHOULD match %(pattern)s"),
304
+ modelObject=doc, fileName=doc.basename, pattern=reportBasenamePattern)
297
305
  else: # non-consolidated
298
306
  if docTypeMatch:
299
307
  if not docTypeMatch.group(1) or docTypeMatch.group(1).lower() == "html":
@@ -311,6 +319,17 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
311
319
  "%(url)s: %(documentSets)s (Document files appear to be in multiple document sets)"),
312
320
  modelObject=doc, documentSets=", ".join(sorted(ixdsDocDirs)), url=reportIncorrectlyPlacedInPackageRef)
313
321
  ixTargetUsage = val.authParam["ixTargetUsage"]
322
+ earliestTRVersion = val.authParam.get("earliestTransformationRegistryVersion", min(ixtNamespacesByVersion.keys()))
323
+ if earliestTRVersion not in ixtNamespacesByVersion:
324
+ raise ValueError(f'Unsupported "earliestTransformationRegistryVersion" disclosure system config setting: {earliestTRVersion}')
325
+ allowedTRnamespaces = frozenset(
326
+ ns for version, ns in ixtNamespacesByVersion.items()
327
+ if version >= earliestTRVersion
328
+ )
329
+ if styleIxHiddenProperty := val.authParam.get("styleIxHiddenProperty"):
330
+ styleIxHiddenPattern = re.compile(rf"(.*[^\w]|^){styleIxHiddenProperty}\s*:\s*([\w.-]+).*")
331
+ else:
332
+ styleIxHiddenPattern = None
314
333
  if modelDocument.type in (ModelDocument.Type.INLINEXBRL, ModelDocument.Type.INLINEXBRLDOCUMENTSET, ModelDocument.Type.UnknownXML):
315
334
  hiddenEltIds = {}
316
335
  presentedHiddenEltIds = defaultdict(list)
@@ -355,6 +374,27 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
355
374
  modelXbrl.error(f"{contentOtherThanXHTMLGuidance}.executableCodePresent",
356
375
  _("Inline XBRL documents MUST NOT contain executable code: %(element)s"),
357
376
  modelObject=elt, element=eltTag)
377
+ externalReferenceFound = False
378
+ if eltTag == "object":
379
+ # Check if the attribute 'archive' contains an external reference!
380
+ archiveAttr = elt.get("archive", "").strip()
381
+ for possibleURI in archiveAttr.split(" "):
382
+ if isExternalUrl(possibleURI.strip()):
383
+ externalReferenceFound = True
384
+ break
385
+ elif eltTag == "script":
386
+ # Check if the attribute 'src' contains an external reference!
387
+ srcAttr = elt.get("src", "").strip()
388
+ if isExternalUrl(srcAttr):
389
+ externalReferenceFound = True
390
+ if externalReferenceFound:
391
+ modelXbrl.error(
392
+ "ESEF.4.1.6.xHTMLDocumentContainsExternalReferences" if val.unconsolidated
393
+ else "ESEF.3.5.1.inlineXbrlDocumentContainsExternalReferences",
394
+ _("Inline XBRL instance documents MUST NOT contain any reference pointing to resources outside the reporting package: %(element)s"),
395
+ modelObject=elt, element=eltTag,
396
+ messageCodes=("ESEF.3.5.1.inlineXbrlDocumentContainsExternalReferences",
397
+ "ESEF.4.1.6.xHTMLDocumentContainsExternalReferences"))
358
398
  elif eltTag == "a" and "mailto" in elt.get("href", ""):
359
399
  modelXbrl.warning(f"{contentOtherThanXHTMLGuidance}.executableCodePresent.mailto",
360
400
  _("Inline XBRL documents SHOULD NOT contain any 'mailto' URI: %(element)s"),
@@ -371,7 +411,7 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
371
411
  # or to other sections of the annual financial report.'''
372
412
  #elif eltTag == "a":
373
413
  # href = elt.get("href","").strip()
374
- # if scheme(href) in ("http", "https", "ftp"):
414
+ # if isExternalUrl(href):
375
415
  # modelXbrl.error("ESEF.4.1.6.xHTMLDocumentContainsExternalReferences" if val.unconsolidated
376
416
  # else "ESEF.3.5.1.inlineXbrlDocumentContainsExternalReferences",
377
417
  # _("Inline XBRL instance documents MUST NOT contain any reference pointing to resources outside the reporting package: %(element)s"),
@@ -465,9 +505,9 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
465
505
  if isinstance(elt, ModelInlineFootnote):
466
506
  checkFootnote(elt, elt.value)
467
507
  elif isinstance(elt, ModelResource) and elt.qname == XbrlConst.qnLinkFootnote:
468
- checkFootnote(elt, elt.value)
508
+ checkFootnote(elt, elt.value) # type: ignore[attr-defined]
469
509
  elif isinstance(elt, ModelInlineFact):
470
- if elt.format is not None and elt.format.namespaceURI not in IXT_NAMESPACES:
510
+ if elt.format is not None and elt.format.namespaceURI not in allowedTRnamespaces:
471
511
  transformRegistryErrors.add(elt)
472
512
  ixHiddenFacts = set()
473
513
  for ixHiddenElt in ixdsHtmlRootElt.iterdescendants(tag=ixNStag + "hidden"):
@@ -509,11 +549,15 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
509
549
  for ixdsHtmlRootElt in modelXbrl.ixdsHtmlElements:
510
550
  for ixElt in ixdsHtmlRootElt.getroottree().iterfind(".//{http://www.w3.org/1999/xhtml}*[@style]"):
511
551
  styleValue = ixElt.get("style","")
512
- hiddenFactRefMatch = styleIxHiddenPattern.match(styleValue)
513
552
 
514
553
  if styleValue:
515
554
  for declaration in tinycss2.parse_blocks_contents(styleValue):
516
555
  if isinstance(declaration, tinycss2.ast.Declaration):
556
+ disallowedHiddenStyle = val.authParam.get("disallowedHiddenStyle")
557
+ if declaration.lower_name == disallowedHiddenStyle:
558
+ modelXbrl.error("ESEF.2.4.1.IxHiddenStyleDisallowed",
559
+ _("Usage of disallowed hidden style: %(styleName)s"),
560
+ modelObject=ixElt, styleName=disallowedHiddenStyle)
517
561
  validateCssUrlContent(declaration.value, ixElt.modelDocument.baseForElement(ixElt),
518
562
  modelXbrl, val, ixElt, imageValidationParameters)
519
563
  elif isinstance(declaration, tinycss2.ast.ParseError):
@@ -521,14 +565,16 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
521
565
  _("The style attribute contains erroneous CSS declaration \"%(styleContent)s\": %(parseError)s"),
522
566
  modelObject=ixElt, parseError=declaration.message,
523
567
  styleContent=styleValue)
524
- if hiddenFactRefMatch:
525
- hiddenFactRef = hiddenFactRefMatch.group(2)
526
- if hiddenFactRef not in hiddenEltIds:
527
- modelXbrl.error("ESEF.2.4.1.esefIxHiddenStyleNotLinkingFactInHiddenSection",
528
- _("\"-esef-ix-hidden\" style identifies id attribute of a fact that is not in ix:hidden section: %(factId)s"),
529
- modelObject=ixElt, factId=hiddenFactRef)
530
- else:
531
- presentedHiddenEltIds[hiddenFactRef].append(ixElt)
568
+ if styleIxHiddenPattern is not None:
569
+ hiddenFactRefMatch = styleIxHiddenPattern.match(styleValue)
570
+ if hiddenFactRefMatch:
571
+ hiddenFactRef = hiddenFactRefMatch.group(2)
572
+ if hiddenFactRef not in hiddenEltIds:
573
+ modelXbrl.error("ESEF.2.4.1.esefIxHiddenStyleNotLinkingFactInHiddenSection",
574
+ _("\"%(styleIxHiddenProperty)s\" style identifies id attribute of a fact that is not in ix:hidden section: %(factId)s"),
575
+ modelObject=ixElt, styleIxHiddenProperty=styleIxHiddenProperty, factId=hiddenFactRef)
576
+ else:
577
+ presentedHiddenEltIds[hiddenFactRef].append(ixElt)
532
578
  for hiddenEltId, ixElt in hiddenEltIds.items():
533
579
  if (hiddenEltId not in presentedHiddenEltIds and
534
580
  getattr(ixElt, "xValid", 0) >= VALID and # may not be validated
@@ -536,9 +582,10 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
536
582
  requiredToDisplayFacts.append(ixElt)
537
583
  if requiredToDisplayFacts:
538
584
  modelXbrl.error("ESEF.2.4.1.factInHiddenSectionNotInReport",
539
- _("The ix:hidden section contains %(countUnreferenced)s fact(s) whose @id is not applied on any \"-esef-ix- hidden\" style: %(elements)s"),
585
+ _("The ix:hidden section contains %(countUnreferenced)s fact(s) whose @id is not applied on any \"%(styleIxHiddenProperty)s\" style: %(elements)s"),
540
586
  modelObject=requiredToDisplayFacts,
541
587
  countUnreferenced=len(requiredToDisplayFacts),
588
+ styleIxHiddenProperty=styleIxHiddenProperty,
542
589
  elements=", ".join(sorted(set(str(f.qname) for f in requiredToDisplayFacts))))
543
590
  del eligibleForTransformHiddenFacts, hiddenEltIds, presentedHiddenEltIds, requiredToDisplayFacts
544
591
  elif modelDocument.type == ModelDocument.Type.INSTANCE:
@@ -589,11 +636,11 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
589
636
  if contextsWithDisallowedOCEs:
590
637
  modelXbrl.error("ESEF.2.1.3.segmentUsed",
591
638
  _("xbrli:segment container MUST NOT be used in contexts: %(contextIds)s"),
592
- modelObject=contextsWithDisallowedOCEs, contextIds=", ".join(c.id for c in contextsWithDisallowedOCEs))
639
+ modelObject=contextsWithDisallowedOCEs, contextIds=", ".join(c.id for c in contextsWithDisallowedOCEs if c.id is not None))
593
640
  if contextsWithDisallowedOCEcontent:
594
641
  modelXbrl.error("ESEF.2.1.3.scenarioContainsNonDimensionalContent",
595
642
  _("xbrli:scenario in contexts MUST NOT contain any other content than defined in XBRL Dimensions specification: %(contextIds)s"),
596
- modelObject=contextsWithDisallowedOCEcontent, contextIds=", ".join(c.id for c in contextsWithDisallowedOCEcontent))
643
+ modelObject=contextsWithDisallowedOCEcontent, contextIds=", ".join(c.id for c in contextsWithDisallowedOCEcontent if c.id is not None))
597
644
  if len(contextIdentifiers) > 1:
598
645
  modelXbrl.error("ESEF.2.1.4.multipleIdentifiers",
599
646
  _("All entity identifiers in contexts MUST have identical content: %(contextIds)s"),
@@ -669,10 +716,22 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
669
716
  if esefDisclosureSystemYear >= 2024:
670
717
  if not f.id:
671
718
  factsMissingId.append(f)
672
- if isinstance(f, ModelInlineFact) and f.concept is not None and f.isEscaped != f.concept.isTextBlock:
673
- modelXbrl.error("ESEF.2.2.7.improperApplicationOfEscapeAttribute",
674
- _("Facts with datatype 'dtr-types:textBlockItemType' MUST use the 'escape' attribute set to 'true'. Facts with any other datatype MUST use the 'escape' attribute set to 'false' - fact %(conceptName)s"),
675
- modelObject=f, conceptName=f.concept.qname)
719
+ errorMsg = _("Facts with datatype 'dtr-types:textBlockItemType' MUST use the 'escape' attribute set to 'true'.")
720
+ if isinstance(f, ModelInlineFact) and f.concept is not None and esefDisclosureSystemYear >= 2024:
721
+ if esefDisclosureSystemYear == 2024:
722
+ errorMsg += _(" Facts with any other datatype MUST use the 'escape' attribute set to 'false'")
723
+ hasEscapeIssue = f.isEscaped != f.concept.isTextBlock
724
+ elif esefDisclosureSystemYear >= 2025:
725
+ hasEscapeIssue = not f.isEscaped and (
726
+ f.concept.isTextBlock or
727
+ (f.value and escapeWorthyStr.match(f.value)))
728
+ if f.value and escapeWorthyStr.match(f.value):
729
+ errorMsg = _("Facts containing special characters like '&' or '<' must be escaped")
730
+ if hasEscapeIssue:
731
+ errorMsg += _(" - fact %(conceptName)s")
732
+ modelXbrl.error("ESEF.2.2.7.improperApplicationOfEscapeAttribute",
733
+ errorMsg,
734
+ modelObject=f, conceptName=f.concept.qname)
676
735
  if f.effectiveValue in ["0", "-0"] and f.xValue != 0:
677
736
  modelXbrl.warning("ESEF.2.2.5.roundedValueBelowScaleNotNull",
678
737
  _("A value that has been rounded and is below the scale should show a value of zero. It has been found to have the value %(value)s - fact %(conceptName)s"),
@@ -707,6 +766,17 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
707
766
  #elif dim.isTyped:
708
767
  # conceptsUsed.add(dim.typedMember)
709
768
  '''
769
+ # identify report date
770
+ reportDate = None
771
+ for f in modelXbrl.factsByLocalName.get("DateOfEndOfReportingPeriod2013", ()):
772
+ if getattr(f, "xValid", 0) >= VALID:
773
+ reportDate = f.xValue
774
+ break
775
+ if not reportDate: # no report period, take name's end context
776
+ for f in modelXbrl.factsByLocalName.get("NameOfReportingEntityOrOtherMeansOfIdentification", ()):
777
+ if getattr(f, "xValid", 0) >= VALID:
778
+ reportDate = f.context.endDate
779
+ break
710
780
 
711
781
  if esefDisclosureSystemYear >= 2024 and factsMissingId:
712
782
  modelXbrl.warning("ESEF.2.2.8.missingFactID",
@@ -776,8 +846,8 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
776
846
 
777
847
  if transformRegistryErrors:
778
848
  modelXbrl.error("ESEF.2.2.3.incorrectTransformationRuleApplied",
779
- _("ESMA recommends applying the Transformation Rules Registry 4, as published by XBRL International or any more recent versions of the Transformation Rules Registry provided with a 'Recommendation' status, for these elements: %(elements)s."),
780
- modelObject=transformRegistryErrors,
849
+ _("ESMA recommends applying the Transformation Rules Registry %(earliestTrNum)s, as published by XBRL International or any more recent versions of the Transformation Rules Registry provided with a 'Recommendation' status, for these elements: %(elements)s."),
850
+ modelObject=transformRegistryErrors, earliestTrNum=earliestTRVersion,
781
851
  elements=", ".join(sorted(str(fact.qname) for fact in transformRegistryErrors)))
782
852
 
783
853
  if orphanedFootnotes:
@@ -833,7 +903,12 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
833
903
 
834
904
  # 3.1.1 test
835
905
  hasOutdatedUrl = False
836
- for e in val.authParam["outdatedTaxonomyURLs"]:
906
+ outdatedTaxonomyURLs = val.authParam["outdatedTaxonomyURLs"].copy()
907
+ if reportDate:
908
+ for expiringTaxonomyURLs in val.authParam.get("expiringTaxonomyURLs", ()):
909
+ if expiringTaxonomyURLs["lastReportableDate"] < str(reportDate):
910
+ outdatedTaxonomyURLs.update(expiringTaxonomyURLs["URLs"])
911
+ for e in outdatedTaxonomyURLs:
837
912
  if e in val.extensionImportedUrls:
838
913
  val.modelXbrl.error("ESEF.3.1.2.incorrectEsefTaxonomyVersionUsed",
839
914
  _("The issuer's extension taxonomies MUST import the applicable version of the taxonomy files prepared by ESMA. Outdated entry point: %(url)s"),
@@ -64,6 +64,7 @@ _: TypeGetText
64
64
  ESEF_DISCLOSURE_SYSTEM_TEST_PROPERTY = "ESEFplugin"
65
65
 
66
66
  ixErrorPattern = re.compile(r"ix11[.]|xmlSchema[:]|(?!xbrl.5.2.5.2|xbrl.5.2.6.2)xbrl[.]|xbrld[ti]e[:]|utre[:]")
67
+ dupIdErrorPattern = re.compile(r"xml.3.3.1:idMustBeUnique|ix11.14.1.2:uniqueIxId")
67
68
 
68
69
 
69
70
  def validateEntity(modelXbrl: ModelXbrl, filename:str, filesource: FileSource) -> None:
@@ -75,7 +76,7 @@ def validateEntity(modelXbrl: ModelXbrl, filename:str, filesource: FileSource) -
75
76
  parser = XMLParser(load_dtd=True, resolve_entities=False)
76
77
  root = parse(file[0], parser=parser)
77
78
  if root.docinfo.internalDTD:
78
- for entity in root.docinfo.internalDTD.iterentities(): # type: ignore[union-attr]
79
+ for entity in root.docinfo.internalDTD.iterentities():
79
80
  modelXbrl.error(f"{contentOtherThanXHTMLGuidance}.maliciousCodePresent",
80
81
  _("Documents MUST NOT contain any malicious content. Dangerous XML entity found: %(element)s."),
81
82
  modelObject=filename, element=entity.name)
@@ -88,6 +89,11 @@ def esefDisclosureSystemSelected(modelXbrl: ModelXbrl) -> bool:
88
89
  return getattr(modelXbrl.modelManager.disclosureSystem, ESEF_DISCLOSURE_SYSTEM_TEST_PROPERTY, False)
89
90
 
90
91
 
92
+ def shouldRunEsefValidationRules(val: ValidateXbrl) -> bool:
93
+ if not val.validateDisclosureSystem:
94
+ return False
95
+ return esefDisclosureSystemSelected(val.modelXbrl)
96
+
91
97
  class ESEFPlugin(PluginHooks):
92
98
  @staticmethod
93
99
  def disclosureSystemTypes(
@@ -216,7 +222,7 @@ class ESEFPlugin(PluginHooks):
216
222
  *args: Any,
217
223
  **kwargs: Any,
218
224
  ) -> None:
219
- if not esefDisclosureSystemSelected(val.modelXbrl) and val.validateDisclosureSystem:
225
+ if not shouldRunEsefValidationRules(val):
220
226
  return None
221
227
  modelXbrl = val.modelXbrl
222
228
  val.extensionImportedUrls = set()
@@ -281,7 +287,7 @@ class ESEFPlugin(PluginHooks):
281
287
  *args: Any,
282
288
  **kwargs: Any,
283
289
  ) -> None:
284
- if not esefDisclosureSystemSelected(val.modelXbrl) and val.validateDisclosureSystem:
290
+ if not shouldRunEsefValidationRules(val):
285
291
  return None
286
292
  disclosureSystemYear = getDisclosureSystemYear(val.modelXbrl)
287
293
  if disclosureSystemYear == 2021:
@@ -294,7 +300,7 @@ class ESEFPlugin(PluginHooks):
294
300
  *args: Any,
295
301
  **kwargs: Any,
296
302
  ) -> None:
297
- if not esefDisclosureSystemSelected(val.modelXbrl) and val.validateDisclosureSystem:
303
+ if not shouldRunEsefValidationRules(val):
298
304
  return None
299
305
  if val.unconsolidated:
300
306
  return None
@@ -306,6 +312,14 @@ class ESEFPlugin(PluginHooks):
306
312
  modelObject=modelXbrl)
307
313
  return # never loaded properly
308
314
 
315
+ disclosureSystemYear = getDisclosureSystemYear(modelXbrl)
316
+ if disclosureSystemYear >= 2025:
317
+ numDupIdErrors = sum(dupIdErrorPattern.match(e) is not None for e in modelXbrl.errors if isinstance(e,str))
318
+ if numDupIdErrors:
319
+ modelXbrl.warning("ESEF.2.2.8.duplicatedIdAttribute",
320
+ _("ID attributes should be unique, %(numDupIdErrors)s such errors were reported."),
321
+ modelObject=modelXbrl, numDupIdErrors=numDupIdErrors)
322
+
309
323
  numXbrlErrors = sum(ixErrorPattern.match(e) is not None for e in modelXbrl.errors if isinstance(e,str))
310
324
  if numXbrlErrors:
311
325
  modelXbrl.error("ESEF.RTS.Annex.III.Par.1.invalidInlineXBRL",
@@ -329,7 +343,7 @@ class ESEFPlugin(PluginHooks):
329
343
  *args: Any,
330
344
  **kwargs: Any,
331
345
  ) -> None:
332
- if not esefDisclosureSystemSelected(val.modelXbrl) and val.validateDisclosureSystem:
346
+ if not shouldRunEsefValidationRules(val):
333
347
  return None
334
348
  modelXbrl = val.modelXbrl
335
349
  if hasattr(val, 'priorFormulaOptionsRunIDs'): # reset environment formula run IDs if they were saved
@@ -370,7 +384,7 @@ __pluginInfo__ = {
370
384
  "Validate ESMA ESEF-2022",
371
385
  "validate/ESEF_2022",
372
386
  ],
373
- "version": "1.2024.00",
387
+ "version": "1.2025.00",
374
388
  "description": """ESMA ESEF Filer Manual and RTS Validations.""",
375
389
  "license": "Apache-2",
376
390
  "author": authorLabel,