arelle-release 2.37.71__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 (71) hide show
  1. arelle/BetaFeatures.py +0 -21
  2. arelle/Cntlr.py +7 -1
  3. arelle/CntlrCmdLine.py +95 -19
  4. arelle/CntlrWinMain.py +4 -1
  5. arelle/DialogFind.py +1 -1
  6. arelle/ModelDtsObject.py +2 -0
  7. arelle/ModelObject.py +16 -18
  8. arelle/ModelObjectFactory.py +17 -15
  9. arelle/ModelXbrl.py +7 -1
  10. arelle/PluginManager.py +1 -5
  11. arelle/RuntimeOptions.py +1 -0
  12. arelle/UrlUtil.py +11 -0
  13. arelle/Validate.py +3 -3
  14. arelle/ValidateXbrl.py +2 -1
  15. arelle/ValidateXbrlCalcs.py +210 -186
  16. arelle/WebCache.py +2 -8
  17. arelle/XbrlConst.py +2 -0
  18. arelle/XmlUtil.py +16 -21
  19. arelle/XmlValidate.py +4 -6
  20. arelle/_version.py +2 -2
  21. arelle/config/rosettaEntitlements.plist +8 -0
  22. arelle/conformance/CSVTestcaseLoader.py +1 -1
  23. arelle/formula/XPathContext.py +3 -3
  24. arelle/logging/formatters/LogFormatter.py +3 -1
  25. arelle/packages/report/ReportPackage.py +9 -1
  26. arelle/plugin/inlineXbrlDocumentSet.py +1 -3
  27. arelle/plugin/validate/DBA/DisclosureSystems.py +19 -1
  28. arelle/plugin/validate/DBA/resources/config.xml +5 -0
  29. arelle/plugin/validate/DBA/rules/fr.py +19 -2
  30. arelle/plugin/validate/DBA/rules/tc.py +2 -0
  31. arelle/plugin/validate/DBA/rules/th.py +6 -0
  32. arelle/plugin/validate/DBA/rules/tm.py +18 -5
  33. arelle/plugin/validate/DBA/rules/tr.py +11 -5
  34. arelle/plugin/validate/EDINET/ControllerPluginData.py +2 -1
  35. arelle/plugin/validate/EDINET/NamespaceConfig.py +50 -0
  36. arelle/plugin/validate/EDINET/PluginValidationDataExtension.py +33 -78
  37. arelle/plugin/validate/EDINET/TableOfContentsBuilder.py +153 -51
  38. arelle/plugin/validate/EDINET/rules/contexts.py +1 -1
  39. arelle/plugin/validate/EDINET/rules/edinet.py +163 -20
  40. arelle/plugin/validate/EDINET/rules/gfm.py +88 -1
  41. arelle/plugin/validate/EDINET/rules/upload.py +1 -1
  42. arelle/plugin/validate/ESEF/ESEF_2021/ValidateXbrlFinally.py +3 -3
  43. arelle/plugin/validate/ESEF/ESEF_Current/DTS.py +42 -14
  44. arelle/plugin/validate/ESEF/ESEF_Current/ValidateXbrlFinally.py +14 -3
  45. arelle/plugin/validate/ESEF/__init__.py +10 -5
  46. arelle/plugin/validate/ESEF/resources/authority-validations.json +10 -5
  47. arelle/plugin/validate/NL/DisclosureSystems.py +22 -0
  48. arelle/plugin/validate/NL/PluginValidationDataExtension.py +20 -0
  49. arelle/plugin/validate/NL/ValidationPluginExtension.py +48 -3
  50. arelle/plugin/validate/NL/resources/config.xml +18 -0
  51. arelle/plugin/validate/NL/rules/br_kvk.py +9 -54
  52. arelle/plugin/validate/NL/rules/fg_nl.py +7 -38
  53. arelle/plugin/validate/NL/rules/fr_kvk.py +7 -42
  54. arelle/plugin/validate/NL/rules/fr_nl.py +25 -140
  55. arelle/plugin/validate/NL/rules/nl_kvk.py +125 -12
  56. arelle/plugin/validate/ROS/rules/ros.py +3 -1
  57. arelle/plugin/validate/UK/__init__.py +70 -14
  58. arelle/utils/EntryPointDetection.py +17 -11
  59. arelle/utils/validate/ESEFImage.py +3 -3
  60. arelle/utils/validate/Validation.py +9 -0
  61. arelle/utils/validate/ValidationPlugin.py +14 -12
  62. {arelle_release-2.37.71.dist-info → arelle_release-2.38.0.dist-info}/METADATA +10 -5
  63. {arelle_release-2.37.71.dist-info → arelle_release-2.38.0.dist-info}/RECORD +67 -69
  64. {arelle_release-2.37.71.dist-info → arelle_release-2.38.0.dist-info}/licenses/LICENSE.md +0 -3
  65. arelle/model/CommentBase.py +0 -9
  66. arelle/model/ElementBase.py +0 -11
  67. arelle/model/PIBase.py +0 -10
  68. arelle/model/__init__.py +0 -15
  69. {arelle_release-2.37.71.dist-info → arelle_release-2.38.0.dist-info}/WHEEL +0 -0
  70. {arelle_release-2.37.71.dist-info → arelle_release-2.38.0.dist-info}/entry_points.txt +0 -0
  71. {arelle_release-2.37.71.dist-info → arelle_release-2.38.0.dist-info}/top_level.txt +0 -0
@@ -12,7 +12,7 @@ import regex
12
12
  import unicodedata
13
13
  from jaconv import jaconv
14
14
 
15
- from arelle import XbrlConst, ValidateDuplicateFacts, XmlUtil
15
+ from arelle import ValidateDuplicateFacts, ValidateXbrlCalcs, XbrlConst, XmlUtil
16
16
  from arelle.Cntlr import Cntlr
17
17
  from arelle.FileSource import FileSource
18
18
  from arelle.LinkbaseType import LinkbaseType
@@ -22,6 +22,7 @@ from arelle.ModelValue import QName, qname
22
22
  from arelle.ModelXbrl import ModelXbrl
23
23
  from arelle.ValidateDuplicateFacts import DuplicateType
24
24
  from arelle.ValidateXbrl import ValidateXbrl
25
+ from arelle.XmlValidateConst import VALID
25
26
  from arelle.typing import TypeGetText
26
27
  from arelle.utils.PluginHooks import ValidationHook
27
28
  from arelle.utils.validate.Decorator import validation
@@ -178,6 +179,53 @@ def rule_EC5002E(
178
179
  )
179
180
 
180
181
 
182
+ @validation(
183
+ hook=ValidationHook.XBRL_FINALLY,
184
+ disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
185
+ )
186
+ def rule_calculations(
187
+ pluginData: PluginValidationDataExtension,
188
+ val: ValidateXbrl,
189
+ *args: Any,
190
+ **kwargs: Any,
191
+ ) -> Iterable[Validation]:
192
+ """
193
+ EDINET.EC5611W: The calculated totals for the accounts in the main financial statements, when
194
+ using the appropriate combination of context and extended link roles, should be within the
195
+ range of differences due to rounding.
196
+ """
197
+ for _validation in ValidateXbrlCalcs.validate(
198
+ val.modelXbrl,
199
+ ValidateXbrlCalcs.ValidateCalcsMode.XBRL_v2_1
200
+ ):
201
+ if _validation.codes != "xbrl.5.2.5.2:calcInconsistency":
202
+ continue
203
+ contextId = _validation.args.get("contextID")
204
+ assert contextId is not None
205
+ linkrole = _validation.args.get("linkrole")
206
+ assert linkrole is not None
207
+ isSemiAnnualContext = 'Interim' in contextId
208
+ isSemiAnnualLinkRole = "SemiAnnual" in linkrole
209
+ if isSemiAnnualLinkRole != isSemiAnnualContext:
210
+ # Period of context and linkrole do not match.
211
+ continue
212
+ context = val.modelXbrl.contexts[contextId]
213
+ member = context.dimMemberQname(pluginData.consolidatedOrNonConsolidatedAxisQn, includeDefaults=True)
214
+ isConsolidatedContext = member != pluginData.nonConsolidatedMemberQn
215
+ isConsolidatedLinkRole = "Consolidated" in linkrole
216
+ if isConsolidatedLinkRole != isConsolidatedContext:
217
+ # Consolidated status of context and linkrole do not match.
218
+ continue
219
+ yield Validation.warning(
220
+ codes='EDINET.EC5611W',
221
+ msg=_("The value \"%(reportedSum)s\" of the total subject \"%(concept)s\" does not match the calculated value \"%(computedSum)s\" (number of items: %(count)s) of the calculation link. "
222
+ "Please correct the settings of the relevant elements and calculation links "
223
+ "<roleUri=%(linkrole)s> <contextID=%(contextID)s> <unit=%(unitID)s>."),
224
+ count=len(_validation.args.get("modelObject", [])) - 1,
225
+ **_validation.args,
226
+ )
227
+
228
+
181
229
  @validation(
182
230
  hook=ValidationHook.XBRL_FINALLY,
183
231
  disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
@@ -357,7 +405,7 @@ def rule_EC5623W(
357
405
  """
358
406
  EDINET.EC5623W: Instances using IFRS taxonomies must set the DEI "Accounting Standard" value to "IFRS".
359
407
  """
360
- if pluginData.jpigpNamespace not in val.modelXbrl.prefixedNamespaces.values():
408
+ if pluginData.namespaces.jpigp not in val.modelXbrl.prefixedNamespaces.values():
361
409
  return
362
410
  errorFacts = [
363
411
  fact for fact in pluginData.iterValidNonNilFacts(val.modelXbrl, pluginData.accountingStandardsDeiQn)
@@ -715,7 +763,7 @@ def rule_EC8030W(
715
763
  usedConcepts = pluginData.getUsedConcepts(val.modelXbrl)
716
764
  relSet = val.modelXbrl.relationshipSet(tuple(LinkbaseType.PRESENTATION.getArcroles()))
717
765
  for concept in usedConcepts:
718
- if concept.qname.namespaceURI == pluginData.jpdeiNamespace:
766
+ if concept.qname.namespaceURI == pluginData.namespaces.jpdei:
719
767
  continue
720
768
  if concept.qname.localName.endswith('DEI'):
721
769
  # Example: jpsps_cor:SecuritiesRegistrationStatementAmendmentFlagDeemedRegistrationStatementDEI
@@ -864,6 +912,61 @@ def rule_EC8034W(
864
912
  )
865
913
 
866
914
 
915
+ @validation(
916
+ hook=ValidationHook.XBRL_FINALLY,
917
+ disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
918
+ )
919
+ def rule_EC8069W(
920
+ pluginData: PluginValidationDataExtension,
921
+ val: ValidateXbrl,
922
+ *args: Any,
923
+ **kwargs: Any,
924
+ ) -> Iterable[Validation]:
925
+ """
926
+ EDINET.EC8069W: If tagging IssuedSharesTotalNumberOfSharesEtcTextBlock, also tag using at least one of the following three elements:"
927
+ "Overview of the corporate governance system (company with auditors) [text block]" (CorporateGovernanceCompanyWithCorporateAuditorsTextBlock) ・
928
+ "Overview of the corporate governance system (company with audit and supervisory committee) [text block]" (CorporateGovernanceCompanyWithAuditAndSupervisoryCommitteeTextBlock)
929
+ "Overview of the corporate governance system (company with nominating committee, etc.) [text block]" (CorporateGovernanceCompanyWithNominatingAndOtherCommitteesTextBlock)
930
+
931
+ If a role indicating a disclosure of securities information and if IssuedSharesTotalNumberOfSharesEtcTextBlock is tagged and non-nil then the one or more of the above three elements must also be tagged and non-nil.
932
+ """
933
+ roleUris = (
934
+ 'http://disclosure.edinet-fsa.go.jp/role/jpcrp/rol_std_CabinetOfficeOrdinanceOnDisclosureOfCorporateInformationEtcFormNo24SecuritiesRegistrationStatement',
935
+ 'http://disclosure.edinet-fsa.go.jp/role/jpcrp/rol_std_CabinetOfficeOrdinanceOnDisclosureOfCorporateInformationEtcFormNo27SecuritiesRegistrationStatement',
936
+ 'http://disclosure.edinet-fsa.go.jp/role/jpcrp/rol_std_CabinetOfficeOrdinanceOnDisclosureOfCorporateInformationEtcFormNo3AnnualSecuritiesReport',
937
+ 'http://disclosure.edinet-fsa.go.jp/role/jpcrp/rol_std_CabinetOfficeOrdinanceOnDisclosureOfCorporateInformationEtcFormNo32AnnualSecuritiesReport',
938
+ 'http://disclosure.edinet-fsa.go.jp/role/jpcrp/rol_std_CabinetOfficeOrdinanceOnDisclosureOfCorporateInformationEtcFormNo4AnnualSecuritiesReport',
939
+ 'http://disclosure.edinet-fsa.go.jp/role/jpcrp/rol_std_CabinetOfficeOrdinanceOnDisclosureOfCorporateInformationEtcFormNo43QuarterlySecuritiesReport',
940
+ 'http://disclosure.edinet-fsa.go.jp/role/jpcrp/rol_std_CabinetOfficeOrdinanceOnDisclosureOfCorporateInformationEtcFormNo43SemiAnnualSecuritiesReport',
941
+ 'http://disclosure.edinet-fsa.go.jp/role/jpcrp/rol_CabinetOfficeOrdinanceOnDisclosureOfCorporateInformationEtcFormNo24SecuritiesRegistrationStatement',
942
+ 'http://disclosure.edinet-fsa.go.jp/role/jpcrp/rol_CabinetOfficeOrdinanceOnDisclosureOfCorporateInformationEtcFormNo27SecuritiesRegistrationStatement',
943
+ 'http://disclosure.edinet-fsa.go.jp/role/jpcrp/rol_CabinetOfficeOrdinanceOnDisclosureOfCorporateInformationEtcFormNo3AnnualSecuritiesReport',
944
+ 'http://disclosure.edinet-fsa.go.jp/role/jpcrp/rol_CabinetOfficeOrdinanceOnDisclosureOfCorporateInformationEtcFormNo32AnnualSecuritiesReport',
945
+ 'http://disclosure.edinet-fsa.go.jp/role/jpcrp/rol_CabinetOfficeOrdinanceOnDisclosureOfCorporateInformationEtcFormNo4AnnualSecuritiesReport',
946
+ 'http://disclosure.edinet-fsa.go.jp/role/jpcrp/rol_CabinetOfficeOrdinanceOnDisclosureOfCorporateInformationEtcFormNo43QuarterlySecuritiesReport',
947
+ 'http://disclosure.edinet-fsa.go.jp/role/jpcrp/rol_CabinetOfficeOrdinanceOnDisclosureOfCorporateInformationEtcFormNo43SemiAnnualSecuritiesReport'
948
+ )
949
+ if not hasPresentationalConceptsWithFacts(val.modelXbrl, roleUris):
950
+ return
951
+ if not pluginData.hasValidNonNilFact(val.modelXbrl, pluginData.issuedSharesTotalNumberOfSharesEtcQn):
952
+ return
953
+ if (
954
+ pluginData.hasValidNonNilFact(val.modelXbrl, pluginData.corporateGovernanceCompanyWithAuditAndSupervisoryCommitteeTextBlockQn) or
955
+ pluginData.hasValidNonNilFact(val.modelXbrl, pluginData.corporateGovernanceCompanyWithCorporateAuditorsTextBlockQn) or
956
+ pluginData.hasValidNonNilFact(val.modelXbrl, pluginData.corporateGovernanceCompanyWithNominatingAndOtherCommitteesTextBlockQn)
957
+ ):
958
+ return
959
+ totalStockShares = val.modelXbrl.factsByQname.get(pluginData.issuedSharesTotalNumberOfSharesEtcQn)
960
+ yield Validation.error(
961
+ codes='EDINET.EC8069W',
962
+ msg=_("If tagging IssuedSharesTotalNumberOfSharesEtcTextBlock, also tag using at least one of the following three elements: \n"
963
+ "'Overview of corporate governance system (company with auditors) [Text Block]' (CorporateGovernanceCompanyWithCorporateAuditorsTextBlock) \n"
964
+ "'Overview of corporate governance system (company with audit and supervisory committee) [Text Block]' (CorporateGovernanceCompanyWithAuditAndSupervisoryCommitteeTextBlock)\n"
965
+ "'Overview of corporate governance system (company with nominating committee, etc.) [Text Block]' (CorporateGovernanceCompanyWithNominatingAndOtherCommitteesTextBlock)\n"),
966
+ modelObject=totalStockShares
967
+ )
968
+
969
+
867
970
  @validation(
868
971
  hook=ValidationHook.XBRL_FINALLY,
869
972
  disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
@@ -906,7 +1009,7 @@ def rule_EC8073E(
906
1009
  hook=ValidationHook.XBRL_FINALLY,
907
1010
  disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
908
1011
  )
909
- def rule_EC8073W(
1012
+ def rule_EC8073W_EC8074W(
910
1013
  pluginData: PluginValidationDataExtension,
911
1014
  val: ValidateXbrl,
912
1015
  *args: Any,
@@ -914,7 +1017,31 @@ def rule_EC8073W(
914
1017
  ) -> Iterable[Validation]:
915
1018
  """
916
1019
  EDINET.EC8073W: Prohibited characters are used in Japanese labels for descendents of ExecutiveOfficersMember
917
- """
1020
+ EDINET.EC8074W: Prohibited characters are used in English labels for descendents of ExecutiveOfficersMember
1021
+ """
1022
+ def getIllegalCharsJapanese(textValue: str) -> set[str]:
1023
+ """Check for prohibited characters in Japanese labels."""
1024
+ return set(HALF_KANA.intersection(set(textValue)))
1025
+
1026
+ def getIllegalCharsEnglish(textValue: str) -> set[str]:
1027
+ """Check for prohibited characters in English labels."""
1028
+ illegalChars = set()
1029
+ for char in textValue:
1030
+ if char in HALF_KANA:
1031
+ illegalChars.add(char)
1032
+ continue
1033
+ codePoint = ord(char)
1034
+ if 0xA1 <= codePoint <= 0xBF:
1035
+ # Exclude symbols
1036
+ illegalChars.add(char)
1037
+ elif codePoint in (0xD7, 0xF7):
1038
+ # Exclude multiplication and division symbols
1039
+ illegalChars.add(char)
1040
+ elif codePoint > 0xFF:
1041
+ # Characters beyond Latin-1
1042
+ illegalChars.add(char)
1043
+ return illegalChars
1044
+
918
1045
  memberConcept = val.modelXbrl.qnameConcepts.get(pluginData.executiveOfficersMemberQn)
919
1046
  if memberConcept is None:
920
1047
  return
@@ -925,21 +1052,37 @@ def rule_EC8073W(
925
1052
  )
926
1053
  for concept in conceptsLabelsToCheck:
927
1054
  for labelRel in labelRelSet.fromModelObject(concept):
928
- if (labelRel.toModelObject is None or
929
- labelRel.toModelObject.xmlLang not in JAPAN_LANGUAGE_CODES or
930
- labelRel.toModelObject.textValue is None):
1055
+ label = labelRel.toModelObject
1056
+ if label is None or label.textValue is None:
931
1057
  continue
932
- illegalChars = HALF_KANA.intersection(set(labelRel.toModelObject.textValue))
933
- if any(illegalChars):
934
- yield Validation.warning(
935
- codes='EDINET.EC8073W',
936
- msg=_("The concept: %(concept)s has a %(role)s label which contains characters that are not "
937
- "allowed. Label: %(label)s, disallowed characters: %(characters)s"),
938
- concept=concept.qname.localName,
939
- role=labelRel.toModelObject.role,
940
- label=labelRel.toModelObject.textValue,
941
- characters=list(illegalChars)
942
- )
1058
+
1059
+ # Check Japanese labels
1060
+ if label.xmlLang in JAPAN_LANGUAGE_CODES:
1061
+ illegalChars = getIllegalCharsJapanese(label.textValue)
1062
+ if illegalChars:
1063
+ yield Validation.warning(
1064
+ codes='EDINET.EC8073W',
1065
+ msg=_("The concept: %(concept)s has a %(role)s label which contains characters that are not "
1066
+ "allowed. Label: %(label)s, disallowed characters: %(characters)s"),
1067
+ concept=concept.qname.localName,
1068
+ role=label.role,
1069
+ label=label.textValue,
1070
+ characters=list(illegalChars)
1071
+ )
1072
+
1073
+ # Check English labels
1074
+ elif label.xmlLang == 'en':
1075
+ illegalChars = getIllegalCharsEnglish(label.textValue)
1076
+ if illegalChars:
1077
+ yield Validation.warning(
1078
+ codes='EDINET.EC8074W',
1079
+ msg=_("The concept: %(concept)s has a %(role)s label which contains characters that are not "
1080
+ "allowed. Label: %(label)s, disallowed characters: %(characters)s"),
1081
+ concept=concept.qname.localName,
1082
+ role=label.role,
1083
+ label=label.textValue,
1084
+ characters=sorted(illegalChars)
1085
+ )
943
1086
 
944
1087
 
945
1088
  @validation(
@@ -1697,7 +1840,7 @@ def rule_EC8050W(
1697
1840
  else:
1698
1841
  return
1699
1842
 
1700
- reportableSegmentsMemberQn = qname(pluginData.jpcrpNamespace, "ReportableSegmentsMember")
1843
+ reportableSegmentsMemberQn = qname(pluginData.namespaces.jpcrp, "ReportableSegmentsMember")
1701
1844
 
1702
1845
  def _getConceptAndDescendantQNames(
1703
1846
  modelXbrl: ModelXbrl,
@@ -556,7 +556,6 @@ def rule_gfm_1_2_25(
556
556
  XbrlConst.qnXbrliEndDate.clarkNotation,
557
557
  XbrlConst.qnXbrliInstant.clarkNotation
558
558
  ):
559
- elt = cast(ModelObject, elt)
560
559
  dateText = XmlUtil.text(elt)
561
560
  if not GFM_CONTEXT_DATE_PATTERN.match(dateText):
562
561
  errors.append(elt)
@@ -1994,6 +1993,94 @@ def rule_gfm_1_8_5(
1994
1993
  )
1995
1994
 
1996
1995
 
1996
+ @validation(
1997
+ hook=ValidationHook.XBRL_FINALLY,
1998
+ disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
1999
+ )
2000
+ def rule_gfm_1_8_9(
2001
+ pluginData: PluginValidationDataExtension,
2002
+ val: ValidateXbrl,
2003
+ *args: Any,
2004
+ **kwargs: Any,
2005
+ ) -> Iterable[Validation]:
2006
+ """
2007
+ EDINET.EC5700W: [GFM 1.8.9] Each ELR can have at most one effective arc with the role of http://xbrl.org/int/dim/arcrole/all.
2008
+
2009
+ GFM 1.8.9: If the value of attribute xbrldt:targetRole on an effective definition relationship is
2010
+ not empty, then that relationship must have at least one effective consecutive
2011
+ relationship (as defined by the XBRL Dimensions 1.0 specification).
2012
+ """
2013
+ drsELRs = set()
2014
+ for baseSetKey, baseSetModelLinks in val.modelXbrl.baseSets.items():
2015
+ arcrole, elr, linkqname, arcqname = baseSetKey
2016
+ if elr and linkqname and arcqname and not arcrole.startswith("XBRL-"):
2017
+ if arcrole == XbrlConst.all or arcrole == XbrlConst.notAll:
2018
+ drsELRs.add(elr)
2019
+ for ELR in drsELRs:
2020
+ domainMemberRelationshipSet = val.modelXbrl.relationshipSet( XbrlConst.domainMember, ELR)
2021
+ primaryItems = set()
2022
+ for hypercubeArcrole in (XbrlConst.all, XbrlConst.notAll):
2023
+ hypercubeRelationships = val.modelXbrl.relationshipSet(
2024
+ hypercubeArcrole, ELR).fromModelObjects()
2025
+ for hypercubeRels in hypercubeRelationships.values():
2026
+ for hypercubeRel in hypercubeRels:
2027
+ hypercube = hypercubeRel.toModelObject
2028
+ fromConcept = hypercubeRel.fromModelObject
2029
+ primaryItems.add(fromConcept)
2030
+ toConcept = hypercubeRel.toModelObject
2031
+ dimELR = hypercubeRel.targetRole
2032
+ dimTargetRequired = (dimELR is not None)
2033
+ if not dimELR:
2034
+ dimELR = elr
2035
+ hypercubeDimRels = val.modelXbrl.relationshipSet(XbrlConst.hypercubeDimension, dimELR).fromModelObject(toConcept)
2036
+ if dimTargetRequired and len(hypercubeDimRels) == 0:
2037
+ yield Validation.warning(
2038
+ codes='EDINET.EC5700W.GFM.1.8.9',
2039
+ msg=_("Add the arc related to the xbrldt:targetRole attribute or remove the attribute. "
2040
+ "Arcrole %(arcroleURI)s from %(fromConcept)s to %(toConcept)s."),
2041
+ modelObject=hypercubeRel,
2042
+ arcroleURI=hypercubeRel.arcrole,
2043
+ fromConcept=fromConcept.qname,
2044
+ toConcept=toConcept.qname,
2045
+ )
2046
+ for hypercubeDimRel in hypercubeDimRels:
2047
+ dim = hypercubeDimRel.toModelObject
2048
+ if not isinstance(dim, ModelConcept):
2049
+ continue
2050
+ domELR = hypercubeDimRel.targetRole
2051
+ domTargetRequired = (domELR is not None)
2052
+ if not domELR and dim.isExplicitDimension:
2053
+ domELR = dimELR
2054
+ dimDomRels = val.modelXbrl.relationshipSet(XbrlConst.dimensionDomain, domELR).fromModelObject(dim)
2055
+ if domTargetRequired and len(dimDomRels) == 0:
2056
+ yield Validation.warning(
2057
+ codes='EDINET.EC5700W.GFM.1.8.9',
2058
+ msg=_("Add the arc related to the xbrldt:targetRole attribute or remove the attribute. "
2059
+ "Arcrole %(arcroleURI)s from %(fromConcept)s to %(toConcept)s."),
2060
+ modelObject=hypercubeDimRel,
2061
+ arcroleURI=hypercubeRel.arcrole,
2062
+ fromConcept=hypercube.qname,
2063
+ toConcept=dim.qname,
2064
+ )
2065
+ fromRelationships = domainMemberRelationshipSet.fromModelObjects()
2066
+ for relFrom, rels in fromRelationships.items():
2067
+ for rel in rels:
2068
+ fromMbr = rel.fromModelObject
2069
+ toMbr = rel.toModelObject
2070
+ toELR = rel.targetRole
2071
+ if isinstance(toMbr, ModelConcept) and toELR and len(
2072
+ val.modelXbrl.relationshipSet(XbrlConst.domainMember, toELR).fromModelObject(toMbr)) == 0:
2073
+ yield Validation.warning(
2074
+ codes='EDINET.EC5700W.GFM.1.8.9',
2075
+ msg=_("Add the arc related to the xbrldt:targetRole attribute or remove the attribute. "
2076
+ "Arcrole %(arcroleURI)s from %(fromConcept)s to %(toConcept)s."),
2077
+ modelObject=rel,
2078
+ arcroleURI=hypercubeRel.arcrole,
2079
+ fromConcept=toMbr.qname,
2080
+ toConcept=fromMbr.qname,
2081
+ )
2082
+
2083
+
1997
2084
  @validation(
1998
2085
  hook=ValidationHook.XBRL_FINALLY,
1999
2086
  disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
@@ -1169,7 +1169,7 @@ def rule_EC8023W(
1169
1169
  continue
1170
1170
  if fact.localName != 'nonFraction':
1171
1171
  continue
1172
- if fact.qname.namespaceURI == pluginData.jpigpNamespace:
1172
+ if fact.qname.namespaceURI == pluginData.namespaces.jpigp:
1173
1173
  continue
1174
1174
 
1175
1175
  precedingChar = None
@@ -343,7 +343,7 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
343
343
  _("Images included in the XHTML document SHOULD be base64 encoded: %(src)s."),
344
344
  modelObject=elt, src=src[:128])
345
345
  if dataURLParts and dataURLParts.mimeSubtype and dataURLParts.data:
346
- 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]
347
347
  else:
348
348
  if not dataURLParts.mimeSubtype:
349
349
  modelXbrl.error(f"{contentOtherThanXHTMLGuidance}.MIMETypeNotSpecified",
@@ -355,7 +355,7 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
355
355
  modelObject=elt, src=src[:128])
356
356
  # check for malicious image contents
357
357
  try: # allow embedded newlines
358
- 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]
359
359
  imgContents = None # deref, may be very large
360
360
  except binascii.Error as err:
361
361
  modelXbrl.error(f"{contentOtherThanXHTMLGuidance}.embeddedImageNotUsingBase64Encoding",
@@ -422,7 +422,7 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
422
422
  if isinstance(elt, ModelInlineFootnote):
423
423
  checkFootnote(elt, elt.value)
424
424
  elif isinstance(elt, ModelResource) and elt.qname == XbrlConst.qnLinkFootnote:
425
- checkFootnote(elt, elt.value)
425
+ checkFootnote(elt, elt.value) # type: ignore[attr-defined]
426
426
  elif isinstance(elt, ModelInlineFact):
427
427
  if elt.format is not None and elt.format.namespaceURI not in IXT_NAMESPACES:
428
428
  transformRegistryErrors.add(elt)
@@ -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
@@ -505,7 +505,7 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
505
505
  if isinstance(elt, ModelInlineFootnote):
506
506
  checkFootnote(elt, elt.value)
507
507
  elif isinstance(elt, ModelResource) and elt.qname == XbrlConst.qnLinkFootnote:
508
- checkFootnote(elt, elt.value)
508
+ checkFootnote(elt, elt.value) # type: ignore[attr-defined]
509
509
  elif isinstance(elt, ModelInlineFact):
510
510
  if elt.format is not None and elt.format.namespaceURI not in allowedTRnamespaces:
511
511
  transformRegistryErrors.add(elt)
@@ -553,6 +553,11 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
553
553
  if styleValue:
554
554
  for declaration in tinycss2.parse_blocks_contents(styleValue):
555
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)
556
561
  validateCssUrlContent(declaration.value, ixElt.modelDocument.baseForElement(ixElt),
557
562
  modelXbrl, val, ixElt, imageValidationParameters)
558
563
  elif isinstance(declaration, tinycss2.ast.ParseError):
@@ -577,9 +582,10 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
577
582
  requiredToDisplayFacts.append(ixElt)
578
583
  if requiredToDisplayFacts:
579
584
  modelXbrl.error("ESEF.2.4.1.factInHiddenSectionNotInReport",
580
- _("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"),
581
586
  modelObject=requiredToDisplayFacts,
582
587
  countUnreferenced=len(requiredToDisplayFacts),
588
+ styleIxHiddenProperty=styleIxHiddenProperty,
583
589
  elements=", ".join(sorted(set(str(f.qname) for f in requiredToDisplayFacts))))
584
590
  del eligibleForTransformHiddenFacts, hiddenEltIds, presentedHiddenEltIds, requiredToDisplayFacts
585
591
  elif modelDocument.type == ModelDocument.Type.INSTANCE:
@@ -710,16 +716,21 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
710
716
  if esefDisclosureSystemYear >= 2024:
711
717
  if not f.id:
712
718
  factsMissingId.append(f)
719
+ errorMsg = _("Facts with datatype 'dtr-types:textBlockItemType' MUST use the 'escape' attribute set to 'true'.")
713
720
  if isinstance(f, ModelInlineFact) and f.concept is not None and esefDisclosureSystemYear >= 2024:
714
721
  if esefDisclosureSystemYear == 2024:
722
+ errorMsg += _(" Facts with any other datatype MUST use the 'escape' attribute set to 'false'")
715
723
  hasEscapeIssue = f.isEscaped != f.concept.isTextBlock
716
724
  elif esefDisclosureSystemYear >= 2025:
717
725
  hasEscapeIssue = not f.isEscaped and (
718
726
  f.concept.isTextBlock or
719
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")
720
730
  if hasEscapeIssue:
731
+ errorMsg += _(" - fact %(conceptName)s")
721
732
  modelXbrl.error("ESEF.2.2.7.improperApplicationOfEscapeAttribute",
722
- _("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"),
733
+ errorMsg,
723
734
  modelObject=f, conceptName=f.concept.qname)
724
735
  if f.effectiveValue in ["0", "-0"] and f.xValue != 0:
725
736
  modelXbrl.warning("ESEF.2.2.5.roundedValueBelowScaleNotNull",
@@ -76,7 +76,7 @@ def validateEntity(modelXbrl: ModelXbrl, filename:str, filesource: FileSource) -
76
76
  parser = XMLParser(load_dtd=True, resolve_entities=False)
77
77
  root = parse(file[0], parser=parser)
78
78
  if root.docinfo.internalDTD:
79
- for entity in root.docinfo.internalDTD.iterentities(): # type: ignore[union-attr]
79
+ for entity in root.docinfo.internalDTD.iterentities():
80
80
  modelXbrl.error(f"{contentOtherThanXHTMLGuidance}.maliciousCodePresent",
81
81
  _("Documents MUST NOT contain any malicious content. Dangerous XML entity found: %(element)s."),
82
82
  modelObject=filename, element=entity.name)
@@ -89,6 +89,11 @@ def esefDisclosureSystemSelected(modelXbrl: ModelXbrl) -> bool:
89
89
  return getattr(modelXbrl.modelManager.disclosureSystem, ESEF_DISCLOSURE_SYSTEM_TEST_PROPERTY, False)
90
90
 
91
91
 
92
+ def shouldRunEsefValidationRules(val: ValidateXbrl) -> bool:
93
+ if not val.validateDisclosureSystem:
94
+ return False
95
+ return esefDisclosureSystemSelected(val.modelXbrl)
96
+
92
97
  class ESEFPlugin(PluginHooks):
93
98
  @staticmethod
94
99
  def disclosureSystemTypes(
@@ -217,7 +222,7 @@ class ESEFPlugin(PluginHooks):
217
222
  *args: Any,
218
223
  **kwargs: Any,
219
224
  ) -> None:
220
- if not esefDisclosureSystemSelected(val.modelXbrl) and val.validateDisclosureSystem:
225
+ if not shouldRunEsefValidationRules(val):
221
226
  return None
222
227
  modelXbrl = val.modelXbrl
223
228
  val.extensionImportedUrls = set()
@@ -282,7 +287,7 @@ class ESEFPlugin(PluginHooks):
282
287
  *args: Any,
283
288
  **kwargs: Any,
284
289
  ) -> None:
285
- if not esefDisclosureSystemSelected(val.modelXbrl) and val.validateDisclosureSystem:
290
+ if not shouldRunEsefValidationRules(val):
286
291
  return None
287
292
  disclosureSystemYear = getDisclosureSystemYear(val.modelXbrl)
288
293
  if disclosureSystemYear == 2021:
@@ -295,7 +300,7 @@ class ESEFPlugin(PluginHooks):
295
300
  *args: Any,
296
301
  **kwargs: Any,
297
302
  ) -> None:
298
- if not esefDisclosureSystemSelected(val.modelXbrl) and val.validateDisclosureSystem:
303
+ if not shouldRunEsefValidationRules(val):
299
304
  return None
300
305
  if val.unconsolidated:
301
306
  return None
@@ -338,7 +343,7 @@ class ESEFPlugin(PluginHooks):
338
343
  *args: Any,
339
344
  **kwargs: Any,
340
345
  ) -> None:
341
- if not esefDisclosureSystemSelected(val.modelXbrl) and val.validateDisclosureSystem:
346
+ if not shouldRunEsefValidationRules(val):
342
347
  return None
343
348
  modelXbrl = val.modelXbrl
344
349
  if hasattr(val, 'priorFormulaOptionsRunIDs'): # reset environment formula run IDs if they were saved
@@ -105,7 +105,8 @@
105
105
  "https://www.esma.europa.eu/taxonomy/2021-03-24/esef_cor.xsd"
106
106
  ],
107
107
  "earliestTransformationRegistryVersion": 4,
108
- "styleIxHiddenProperty": "-esef-ix-hidden"
108
+ "styleIxHiddenProperty": "-esef-ix-hidden",
109
+ "disallowedHiddenStyle": "-ix-hidden"
109
110
  },
110
111
  "ESEF-2022": {
111
112
  "outdatedTaxonomyURLs": [
@@ -129,7 +130,8 @@
129
130
  "https://www.esma.europa.eu/taxonomy/2022-03-24/esef_cor.xsd"
130
131
  ],
131
132
  "earliestTransformationRegistryVersion": 4,
132
- "styleIxHiddenProperty": "-esef-ix-hidden"
133
+ "styleIxHiddenProperty": "-esef-ix-hidden",
134
+ "disallowedHiddenStyle": "-ix-hidden"
133
135
  },
134
136
  "ESEF-2023": {
135
137
  "outdatedTaxonomyURLs": [
@@ -153,7 +155,8 @@
153
155
  "https://www.esma.europa.eu/taxonomy/2022-03-24/esef_cor.xsd"
154
156
  ],
155
157
  "earliestTransformationRegistryVersion": 4,
156
- "styleIxHiddenProperty": "-esef-ix-hidden"
158
+ "styleIxHiddenProperty": "-esef-ix-hidden",
159
+ "disallowedHiddenStyle": "-ix-hidden"
157
160
  },
158
161
  "ESEF-2024": {
159
162
  "outdatedTaxonomyURLs": [
@@ -181,7 +184,8 @@
181
184
  "https://www.esma.europa.eu/taxonomy/2024-03-27/esef_cor.xsd"
182
185
  ],
183
186
  "earliestTransformationRegistryVersion": 4,
184
- "styleIxHiddenProperty": "-esef-ix-hidden"
187
+ "styleIxHiddenProperty": "-esef-ix-hidden",
188
+ "disallowedHiddenStyle": "-ix-hidden"
185
189
  },
186
190
  "ESEF-2025-DRAFT": {
187
191
  "outdatedTaxonomyURLs": [
@@ -211,7 +215,8 @@
211
215
  }
212
216
  ],
213
217
  "earliestTransformationRegistryVersion": 5,
214
- "styleIxHiddenProperty": "-ix-hidden"
218
+ "styleIxHiddenProperty": "-ix-hidden",
219
+ "disallowedHiddenStyle": "-esef-ix-hidden"
215
220
  },
216
221
  "AT": {
217
222
  "name": "Austria",