arelle-release 2.37.60__py3-none-any.whl → 2.37.62__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (37) hide show
  1. arelle/DisclosureSystem.py +5 -0
  2. arelle/HtmlUtil.py +5 -4
  3. arelle/ModelDtsObject.py +6 -0
  4. arelle/ValidateDuplicateFacts.py +2 -0
  5. arelle/ValidateXbrl.py +3 -1
  6. arelle/ValidateXbrlDTS.py +1 -1
  7. arelle/XbrlConst.py +18 -0
  8. arelle/_version.py +2 -2
  9. arelle/config/disclosuresystems.xsd +1 -0
  10. arelle/plugin/validate/EDINET/Constants.py +95 -0
  11. arelle/plugin/validate/EDINET/ControllerPluginData.py +14 -3
  12. arelle/plugin/validate/EDINET/CoverItemRequirements.py +42 -0
  13. arelle/plugin/validate/EDINET/{CoverPageRequirements.py → DeiRequirements.py} +24 -24
  14. arelle/plugin/validate/EDINET/PluginValidationDataExtension.py +209 -43
  15. arelle/plugin/validate/EDINET/ReportFolderType.py +61 -0
  16. arelle/plugin/validate/EDINET/TableOfContentsBuilder.py +493 -0
  17. arelle/plugin/validate/EDINET/__init__.py +13 -2
  18. arelle/plugin/validate/EDINET/resources/config.xml +6 -0
  19. arelle/plugin/validate/EDINET/resources/cover-item-requirements.json +793 -0
  20. arelle/plugin/validate/EDINET/resources/edinet-taxonomies.xml +2 -0
  21. arelle/plugin/validate/EDINET/rules/contexts.py +61 -1
  22. arelle/plugin/validate/EDINET/rules/edinet.py +278 -4
  23. arelle/plugin/validate/EDINET/rules/frta.py +122 -3
  24. arelle/plugin/validate/EDINET/rules/gfm.py +849 -5
  25. arelle/plugin/validate/EDINET/rules/upload.py +231 -192
  26. arelle/plugin/validate/NL/PluginValidationDataExtension.py +6 -8
  27. arelle/plugin/validate/NL/ValidationPluginExtension.py +0 -3
  28. arelle/plugin/validate/NL/rules/nl_kvk.py +1 -2
  29. arelle/utils/validate/ValidationPlugin.py +1 -1
  30. arelle/utils/validate/ValidationUtil.py +1 -2
  31. {arelle_release-2.37.60.dist-info → arelle_release-2.37.62.dist-info}/METADATA +2 -1
  32. {arelle_release-2.37.60.dist-info → arelle_release-2.37.62.dist-info}/RECORD +37 -34
  33. /arelle/plugin/validate/EDINET/resources/{cover-page-requirements.csv → dei-requirements.csv} +0 -0
  34. {arelle_release-2.37.60.dist-info → arelle_release-2.37.62.dist-info}/WHEEL +0 -0
  35. {arelle_release-2.37.60.dist-info → arelle_release-2.37.62.dist-info}/entry_points.txt +0 -0
  36. {arelle_release-2.37.60.dist-info → arelle_release-2.37.62.dist-info}/licenses/LICENSE.md +0 -0
  37. {arelle_release-2.37.60.dist-info → arelle_release-2.37.62.dist-info}/top_level.txt +0 -0
@@ -6,26 +6,23 @@ from __future__ import annotations
6
6
  import re
7
7
  from collections import defaultdict
8
8
  from pathlib import Path
9
- from typing import Any, Iterable, TYPE_CHECKING, cast
10
-
11
- import regex
9
+ from typing import Any, Iterable, TYPE_CHECKING
12
10
 
13
11
  from arelle import UrlUtil, XbrlConst
14
12
  from arelle.Cntlr import Cntlr
15
13
  from arelle.FileSource import FileSource
16
14
  from arelle.ModelInstanceObject import ModelFact
15
+ from arelle.ModelObject import ModelObject
17
16
  from arelle.ValidateXbrl import ValidateXbrl
18
17
  from arelle.XmlValidateConst import VALID
19
18
  from arelle.typing import TypeGetText
20
19
  from arelle.utils.PluginHooks import ValidationHook
21
20
  from arelle.utils.validate.Decorator import validation
22
21
  from arelle.utils.validate.Validation import Validation
23
- from .. import Constants
24
- from ..CoverPageRequirements import CoverPageItemStatus
22
+ from ..Constants import JAPAN_LANGUAGE_CODES
25
23
  from ..DisclosureSystems import (DISCLOSURE_SYSTEM_EDINET)
26
- from ..FilingFormat import FILING_FORMATS
27
- from ..ReportFolderType import ReportFolderType, HTML_EXTENSIONS, IMAGE_EXTENSIONS
28
24
  from ..PluginValidationDataExtension import PluginValidationDataExtension
25
+ from ..ReportFolderType import ReportFolderType, HTML_EXTENSIONS, IMAGE_EXTENSIONS
29
26
 
30
27
  if TYPE_CHECKING:
31
28
  from ..ControllerPluginData import ControllerPluginData
@@ -56,48 +53,6 @@ FILE_COUNT_LIMITS = {
56
53
 
57
54
  FILENAME_STEM_PATTERN = re.compile(r'[a-zA-Z0-9_-]*')
58
55
 
59
- PATTERN_CODE = r'(?P<code>[A-Za-z\d]*)'
60
- PATTERN_CONSOLIDATED = r'(?P<consolidated>c|n)'
61
- PATTERN_COUNT = r'(?P<count>\d{2})'
62
- PATTERN_DATE1 = r'(?P<year1>\d{4})-(?P<month1>\d{2})-(?P<day1>\d{2})'
63
- PATTERN_DATE2 = r'(?P<year2>\d{4})-(?P<month2>\d{2})-(?P<day2>\d{2})'
64
- PATTERN_FORM = r'(?P<form>\d{6})'
65
- PATTERN_LINKBASE = r'(?P<linkbase>lab|lab-en|gla|pre|def|cal)'
66
- PATTERN_MAIN = r'(?P<main>\d{7})'
67
- PATTERN_NAME = r'(?P<name>[a-z]{6})'
68
- PATTERN_ORDINANCE = r'(?P<ordinance>[a-z]*)'
69
- PATTERN_PERIOD = r'(?P<period>c|p)' # TODO: Have only seen "c" in sample/public filings, assuming "p" for previous.
70
- PATTERN_REPORT = r'(?P<report>[a-z]*)'
71
- PATTERN_REPORT_SERIAL = r'(?P<report_serial>\d{3})'
72
- PATTERN_SERIAL = r'(?P<serial>\d{3})'
73
-
74
- PATTERN_AUDIT_REPORT_PREFIX = rf'jpaud-{PATTERN_REPORT}-{PATTERN_PERIOD}{PATTERN_CONSOLIDATED}'
75
- PATTERN_REPORT_PREFIX = rf'jp{PATTERN_ORDINANCE}{PATTERN_FORM}-{PATTERN_REPORT}'
76
- PATTERN_SUFFIX = rf'{PATTERN_REPORT_SERIAL}_{PATTERN_CODE}-{PATTERN_SERIAL}_{PATTERN_DATE1}_{PATTERN_COUNT}_{PATTERN_DATE2}'
77
-
78
- PATTERNS = list(regex.compile(p) for p in (
79
- # Schema file for report
80
- # Example: jpcrp050300-esr-001_X99007-000_2025-04-10_01_2025-04-10.xsd
81
- rf'{PATTERN_REPORT_PREFIX}-{PATTERN_SUFFIX}.xsd',
82
- # Schema file for audit report
83
- # Example: jpaud-aar-cn-001_X99001-000_2025-03-31_01_2025-06-28.xsd
84
- rf'{PATTERN_AUDIT_REPORT_PREFIX}-{PATTERN_SUFFIX}.xsd',
85
- # Linkbase file for report
86
- # Example: jpcrp020000-srs-001_X99001-000_2025-03-31_01_2025-11-20_cal.xml
87
- rf'{PATTERN_REPORT_PREFIX}-{PATTERN_SUFFIX}_{PATTERN_LINKBASE}.xml',
88
- # Linkbase file for audit report
89
- # Example: jpaud-qrr-cc-001_X99001-000_2025-03-31_01_2025-11-20_pre.xml
90
- rf'{PATTERN_AUDIT_REPORT_PREFIX}-{PATTERN_SUFFIX}_{PATTERN_LINKBASE}.xml',
91
- # Cover page file for report
92
- # Example: 0000000_header_jpcrp020000-srs-001_X99001-000_2025-03-31_01_2025-11-20_ixbrl.htm
93
- rf'{Constants.COVER_PAGE_FILENAME_PREFIX}{PATTERN_REPORT_PREFIX}-{PATTERN_SUFFIX}_ixbrl.htm',
94
- # Main file for report
95
- # Example: 0205020_honbun_jpcrp020000-srs-001_X99001-000_2025-03-31_01_2025-11-20_ixbrl.htm
96
- rf'{PATTERN_MAIN}_{PATTERN_NAME}_{PATTERN_REPORT_PREFIX}-{PATTERN_SUFFIX}_ixbrl.htm',
97
- # Main file for audit report
98
- # Example: jpaud-qrr-cc-001_X99001-000_2025-03-31_01_2025-11-20_pre.xml
99
- rf'{PATTERN_AUDIT_REPORT_PREFIX}-{PATTERN_SUFFIX}_ixbrl.htm',
100
- ))
101
56
 
102
57
  @validation(
103
58
  hook=ValidationHook.FILESOURCE,
@@ -580,8 +535,8 @@ def rule_EC0349E(
580
535
  **kwargs: Any,
581
536
  ) -> Iterable[Validation]:
582
537
  """
583
- EDINET.EC0349E: An unexpected directory or file exists in the XBRL directory.
584
- Only PublicDoc, PrivateDoc, or AuditDoc directories may exist beneath the XBRL directory.
538
+ EDINET.EC0349E: An unexpected directory or file exists directly beneath the XBRL directory.
539
+ Only PublicDoc, PrivateDoc, or AuditDoc directories may exist directly beneath the XBRL directory.
585
540
  """
586
541
  uploadContents = pluginData.getUploadContents()
587
542
  if uploadContents is None:
@@ -596,13 +551,12 @@ def rule_EC0349E(
596
551
  if path.parent != xbrlDirectoryPath:
597
552
  continue
598
553
  if path not in allowedPaths:
599
- if not any(pattern.fullmatch(path.name) for pattern in PATTERNS):
600
- yield Validation.error(
601
- codes='EDINET.EC0349E',
602
- msg=_("An unexpected directory or file exists in the XBRL directory. "
603
- "Directory or file name: '%(file)s'."),
604
- file=path.name,
605
- )
554
+ yield Validation.error(
555
+ codes='EDINET.EC0349E',
556
+ msg=_("An unexpected directory or file exists directly beneath the XBRL directory. "
557
+ "Directory or file name: '%(file)s'."),
558
+ file=path.name,
559
+ )
606
560
 
607
561
 
608
562
  @validation(
@@ -632,7 +586,8 @@ def rule_EC0352E(
632
586
  any(path == t.manifestPath for t in ReportFolderType)
633
587
  ):
634
588
  continue
635
- if not any(pattern.fullmatch(path.name) for pattern in PATTERNS):
589
+ patterns = pathInfo.reportFolderType.ixbrlFilenamePatterns
590
+ if not any(pattern.fullmatch(path.name) for pattern in patterns):
636
591
  yield Validation.error(
637
592
  codes='EDINET.EC0352E',
638
593
  msg=_("A file with an invalid name exists. "
@@ -645,7 +600,7 @@ def rule_EC0352E(
645
600
  hook=ValidationHook.XBRL_FINALLY,
646
601
  disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
647
602
  )
648
- def rules_cover_page(
603
+ def rule_cover_items(
649
604
  pluginData: PluginValidationDataExtension,
650
605
  val: ValidateXbrl,
651
606
  *args: Any,
@@ -684,83 +639,89 @@ def rules_cover_page(
684
639
  filingFormat = pluginData.getFilingFormat(val.modelXbrl)
685
640
  if filingFormat is None:
686
641
  return
687
- coverPageRequirements = pluginData.getCoverPageRequirements(val.modelXbrl)
688
- currentLineNumber = 0
689
- for qname in pluginData.coverPageItems:
690
- foundFacts = []
691
- validNonNilFacts = []
692
- for fact in pluginData.iterFacts(val.modelXbrl, qname):
693
- if fact.modelDocument != doc:
694
- continue
695
- if fact.qname.prefix is not None and filingFormat.includesTaxonomyPrefix(fact.qname.prefix):
696
- foundFacts.append(fact)
697
- if fact.xValid >= VALID and not fact.isNil:
698
- validNonNilFacts.append(fact)
699
-
700
- for fact in sorted(foundFacts, key=lambda f: cast(int, f.sourceline)):
701
- if (sourceLine := cast(int, fact.sourceline)) <= currentLineNumber:
702
- yield Validation.error(
703
- codes='EDINET.EC1004E',
704
- msg=_("Cover item %(localName)s is not in the correct order. "
705
- "File name: '%(file)s'. "
706
- "Please correct the order of cover items in the appropriate file."),
707
- localName=qname.localName,
708
- file=doc.basename,
709
- modelObject=fact,
710
- )
711
- else:
712
- currentLineNumber = sourceLine
642
+ allCoverItems = pluginData.getCoverItems(val.modelXbrl)
643
+ requiredCoverItems = pluginData.getCoverItemRequirements(val.modelXbrl)
644
+ if requiredCoverItems is None:
645
+ return
646
+ prohibitedCoverItems = allCoverItems - set(requiredCoverItems)
647
+ sequenceQueue = list(requiredCoverItems)
713
648
 
714
- if len(foundFacts) > 1:
649
+ ixNStag = doc.ixNStag
650
+ rootElt = doc.xmlRootElement
651
+ foundFactsByQname = defaultdict(list)
652
+ outOfSequence = False
653
+ seenInSequence = set()
654
+ for elt in rootElt.iterdescendants(ixNStag + "nonNumeric", ixNStag + "nonFraction", ixNStag + "fraction"):
655
+ if not isinstance(elt, ModelFact):
656
+ continue
657
+ if elt.qname in prohibitedCoverItems:
658
+ yield Validation.error(
659
+ codes='EDINET.EC1003E',
660
+ msg=_("Cover item %(localName)s is not necessary. "
661
+ "File name: '%(file)s' (line %(line)s). "
662
+ "Please add the cover item %(localName)s to the relevant file."),
663
+ localName=elt.qname.localName,
664
+ file=doc.basename,
665
+ line=elt.sourceline,
666
+ modelObject=elt,
667
+ )
668
+ continue
669
+ foundFactsByQname[elt.qname].append(elt)
670
+ if elt.qname in seenInSequence:
715
671
  yield Validation.error(
716
672
  codes='EDINET.EC1002E',
717
673
  msg=_("Cover item %(localName)s is duplicated. "
718
674
  "File name: '%(file)s'. "
719
675
  "Please check the cover item %(localName)s of the relevant file "
720
676
  "and make sure there are no duplicates."),
677
+ localName=elt.qname.localName,
678
+ file=doc.basename,
679
+ modelObject=elt,
680
+ )
681
+ continue
682
+ seenInSequence.add(elt.qname)
683
+ if len(sequenceQueue) == 0:
684
+ continue
685
+ if outOfSequence:
686
+ continue
687
+ if not sequenceQueue[0] == elt.qname:
688
+ outOfSequence = True
689
+ yield Validation.error(
690
+ codes='EDINET.EC1004E',
691
+ msg=_("Cover item %(localName)s is not in the correct order. "
692
+ "File name: '%(file)s'. "
693
+ "Please correct the order of cover items in the appropriate file."),
694
+ localName=elt.qname.localName,
695
+ file=doc.basename,
696
+ modelObject=elt,
697
+ )
698
+ if elt.qname in sequenceQueue:
699
+ sequenceQueue.remove(elt.qname)
700
+
701
+ for qname in requiredCoverItems:
702
+ foundFacts = foundFactsByQname.get(qname, [])
703
+ # No facts found.
704
+ if len(foundFacts) == 0:
705
+ yield Validation.error(
706
+ codes='EDINET.EC1001E',
707
+ msg=_("Cover item %(localName)s is missing. "
708
+ "File name: '%(file)s'. "
709
+ "Please add the cover item %(localName)s to the relevant file."),
710
+ localName=qname.localName,
711
+ file=doc.basename,
712
+ )
713
+ # Fact(s) found, but no valid, non-nil value.
714
+ elif not any(f.xValid >= VALID and not f.isNil for f in foundFacts):
715
+ yield Validation.error(
716
+ codes='EDINET.EC1005E',
717
+ msg=_("Cover item %(localName)s is missing a valid value. "
718
+ "File name: '%(file)s'. "
719
+ "Please enter a valid value for %(localName)s in the relevant file."),
721
720
  localName=qname.localName,
722
721
  file=doc.basename,
723
722
  modelObject=foundFacts,
724
723
  )
725
724
 
726
- status = coverPageRequirements.get(qname, filingFormat)
727
- if status is None:
728
- continue
729
- if status == CoverPageItemStatus.REQUIRED:
730
- if len(foundFacts) == 0:
731
- yield Validation.error(
732
- codes='EDINET.EC1001E',
733
- msg=_("Cover item %(localName)s is missing. "
734
- "File name: '%(file)s'. "
735
- "Please add the cover item %(localName)s to the relevant file."),
736
- localName=qname.localName,
737
- file=doc.basename,
738
- )
739
- elif len(validNonNilFacts) == 0:
740
- yield Validation.error(
741
- codes='EDINET.EC1005E',
742
- msg=_("Cover item %(localName)s is missing a valid value. "
743
- "File name: '%(file)s'. "
744
- "Please enter a valid value for %(localName)s in the relevant file."),
745
- localName=qname.localName,
746
- file=doc.basename,
747
- modelObject=foundFacts,
748
- )
749
- elif status == CoverPageItemStatus.PROHIBITED:
750
- for fact in foundFacts:
751
- if fact.isNil:
752
- continue # Prohibited cover pages facts are allowed, only if nil.
753
- yield Validation.error(
754
- codes='EDINET.EC1003E',
755
- msg=_("Cover item %(localName)s is not necessary. "
756
- "File name: '%(file)s' (line %(line)s). "
757
- "Please add the cover item %(localName)s to the relevant file."),
758
- localName=qname.localName,
759
- file=doc.basename,
760
- line=fact.sourceline,
761
- modelObject=fact,
762
- )
763
-
764
725
 
765
726
  @validation(
766
727
  hook=ValidationHook.XBRL_FINALLY,
@@ -801,11 +762,17 @@ def rule_uri_references(
801
762
  **kwargs: Any,
802
763
  ) -> Iterable[Validation]:
803
764
  """
804
- EDINET.EC1007E: The URI in the HTML specifies a URL or absolute path.
805
- EDINET.EC1013E: The URI in the HTML specifies a path not under a subdirectory.
806
- EDINET.EC1014E: The URI in the HTML specifies a path to a directory.
807
- EDINET.EC1021E: The URI in the HTML specifies a path to a file that doesn't exist.
808
- EDINET.EC1023E: The URI in the HTML specifies a path to a PDF file.
765
+ EDINET.EC1007E: A URI in an HTML file must not be a URL or absolute path.
766
+ EDINET.EC1013E: A URI in an HTML file directly beneath a report folder
767
+ must specify a path under a subdirectory.
768
+ EDINET.EC1014E: A URI in an HTML file must not specify a path to a directory.
769
+ EDINET.EC1015E: A URI in an HTML file within a subdirectory
770
+ must not specify a path directly beneath the report folder.
771
+ EDINET.EC1021E: A URI in an HTML file must not specify a path to a file that doesn't exist.
772
+ EDINET.EC1023E: A URI in an HTML file must not specify a path to a PDF file.
773
+ EDINET.EC1035E: A URI in an HTML file must not specify a path to a location higher than the report path.
774
+
775
+ Note: See "図表 3-4-8 PublicDoc フォルダ全体のイメージ" in "File Specification for EDINET".
809
776
  """
810
777
  uploadContents = pluginData.getUploadContents(val.modelXbrl)
811
778
  if uploadContents is None:
@@ -822,21 +789,62 @@ def rule_uri_references(
822
789
  modelObject=uriReference.element,
823
790
  )
824
791
  continue
825
- path = Path(uriReference.attributeValue)
826
- if len(path.parts) < 2:
792
+
793
+ uriPath = Path(uriReference.attributeValue)
794
+ documentFullPath = Path(uriReference.document.uri)
795
+ referenceFullPath = (documentFullPath.parent / uriPath).resolve()
796
+ documentPathInfo = uploadContents.uploadPathsByFullPath.get(documentFullPath)
797
+ assert documentPathInfo is not None # Should always be present, as it must exist to have a uriReference discovered.
798
+ reportFullPath = Path(str(val.modelXbrl.fileSource.baseurl)) / (documentPathInfo.reportPath or "")
799
+
800
+ if reportFullPath not in referenceFullPath.parents:
827
801
  yield Validation.error(
828
- codes='EDINET.EC1013E',
829
- msg=_("The URI in the HTML specifies a path not under a subdirectory. "
802
+ codes='EDINET.EC1035E',
803
+ msg=_("The URI in the HTML specifies a path that navigates "
804
+ "outside of the report folder '%(reportPath)s'. "
830
805
  "File name: '%(file)s' (line %(line)s). "
831
- "Please move the referenced file into a subfolder, or correct the URI."),
806
+ "You cannot create a link from a subfolder to a parent folder. "
807
+ "Please delete the link."),
808
+ reportPath=str(documentPathInfo.reportPath),
832
809
  file=uriReference.document.basename,
833
810
  line=uriReference.element.sourceline,
834
811
  modelObject=uriReference.element,
835
812
  )
836
813
  continue
837
- fullPath = Path(uriReference.document.uri).parent / path
838
- pathInfo = uploadContents.uploadPathsByFullPath.get(fullPath)
839
- if pathInfo is not None and pathInfo.isDirectory:
814
+
815
+ if not documentPathInfo.isSubdirectory:
816
+ if documentFullPath.parent not in referenceFullPath.parent.parents:
817
+ yield Validation.error(
818
+ codes='EDINET.EC1013E',
819
+ msg=_("The URI in the HTML file directly beneath '%(reportPath)s' "
820
+ "specifies a path not under a subdirectory. "
821
+ "File name: '%(file)s' (line %(line)s). "
822
+ "Please move the referenced file into a subfolder beneath "
823
+ "'%(reportPath)s', or correct the URI."),
824
+ reportPath=str(documentPathInfo.reportPath),
825
+ file=uriReference.document.basename,
826
+ line=uriReference.element.sourceline,
827
+ modelObject=uriReference.element,
828
+ )
829
+ continue
830
+
831
+ elif referenceFullPath.parent == reportFullPath:
832
+ yield Validation.error(
833
+ codes='EDINET.EC1015E',
834
+ msg=_("The URI in the HTML file within a subdirectory specifies a "
835
+ "path to a file located directly beneath '%(reportPath)s'. "
836
+ "File name: '%(file)s' (line %(line)s). "
837
+ "You cannot create a link from a subfolder to this parent folder. "
838
+ "Please correct the relevant link."),
839
+ reportPath=str(documentPathInfo.reportPath),
840
+ file=uriReference.document.basename,
841
+ line=uriReference.element.sourceline,
842
+ modelObject=uriReference.element,
843
+ )
844
+ continue
845
+
846
+ referencePathInfo = uploadContents.uploadPathsByFullPath.get(referenceFullPath)
847
+ if referencePathInfo is not None and referencePathInfo.isDirectory:
840
848
  yield Validation.error(
841
849
  codes='EDINET.EC1014E',
842
850
  msg=_("The URI in the HTML specifies a path to a directory. "
@@ -847,7 +855,8 @@ def rule_uri_references(
847
855
  modelObject=uriReference.element,
848
856
  )
849
857
  continue
850
- if path.suffix.lower() == '.pdf':
858
+
859
+ if referenceFullPath.suffix.lower() == '.pdf':
851
860
  yield Validation.error(
852
861
  codes='EDINET.EC1023E',
853
862
  msg=_("The URI in the HTML specifies a path to a PDF file. "
@@ -858,13 +867,14 @@ def rule_uri_references(
858
867
  modelObject=uriReference.element,
859
868
  )
860
869
  continue
861
- if not val.modelXbrl.fileSource.exists(str(fullPath)):
870
+
871
+ if not val.modelXbrl.fileSource.exists(str(referenceFullPath)):
862
872
  yield Validation.error(
863
873
  codes='EDINET.EC1021E',
864
874
  msg=_("The linked file ('%(path)s') does not exist. "
865
875
  "File name: '%(file)s' (line %(line)s). "
866
876
  "Please update the URI to reference a file."),
867
- path=str(path),
877
+ path=str(uriPath),
868
878
  file=uriReference.document.basename,
869
879
  line=uriReference.element.sourceline,
870
880
  modelObject=uriReference.element,
@@ -899,50 +909,6 @@ def rule_EC1009R(
899
909
  )
900
910
 
901
911
 
902
-
903
- @validation(
904
- hook=ValidationHook.XBRL_FINALLY,
905
- disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
906
- )
907
- def rule_EC1010E(
908
- pluginData: PluginValidationDataExtension,
909
- val: ValidateXbrl,
910
- *args: Any,
911
- **kwargs: Any,
912
- ) -> Iterable[Validation]:
913
- """
914
- EDINET.EC1010E: The charset specification in the content attribute of the HTML <meta> tag must be UTF-8.
915
- """
916
- for modelDocument in val.modelXbrl.urlDocs.values():
917
- path = Path(modelDocument.uri)
918
- if path.suffix not in HTML_EXTENSIONS:
919
- continue
920
- rootElt = modelDocument.xmlRootElement
921
- matchingElt = None
922
- missingElts = []
923
- for metaElt in rootElt.iterdescendants(tag=XbrlConst.qnXhtmlMeta.clarkNotation):
924
- if metaElt.qname.localName != 'meta':
925
- continue
926
- content = metaElt.get('content')
927
- if content is None:
928
- continue
929
- charset = content.split('charset=')[-1].strip().lower()
930
- if charset == 'utf-8':
931
- matchingElt = metaElt
932
- else:
933
- missingElts.append(metaElt)
934
-
935
- if matchingElt is None or len(missingElts) > 0:
936
- yield Validation.error(
937
- codes='EDINET.EC1010E',
938
- msg=_("The charset specification in the content attribute of the HTML <meta> tag is not UTF-8. "
939
- "File name: '%(path)s'. "
940
- "Please change the character code of the file to UTF-8."),
941
- path=str(path),
942
- modelObject=missingElts
943
- )
944
-
945
-
946
912
  @validation(
947
913
  hook=ValidationHook.FILESOURCE,
948
914
  disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
@@ -1006,6 +972,43 @@ def rule_EC1017E(
1006
972
  )
1007
973
 
1008
974
 
975
+ @validation(
976
+ hook=ValidationHook.COMPLETE,
977
+ disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
978
+ )
979
+ def rule_toc(
980
+ pluginData: ControllerPluginData,
981
+ cntlr: Cntlr,
982
+ fileSource: FileSource,
983
+ *args: Any,
984
+ **kwargs: Any,
985
+ ) -> Iterable[Validation]:
986
+ """
987
+ Performs validation via controller-level TableOfContentsBuilder.
988
+ """
989
+ tocBuilder = pluginData.getTableOfContentsBuilder()
990
+ yield from tocBuilder.validate()
991
+
992
+
993
+ @validation(
994
+ hook=ValidationHook.XBRL_FINALLY,
995
+ disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
996
+ )
997
+ def rule_toc_pre(
998
+ pluginData: PluginValidationDataExtension,
999
+ val: ValidateXbrl,
1000
+ *args: Any,
1001
+ **kwargs: Any,
1002
+ ) -> Iterable[Validation]:
1003
+ """
1004
+ Doesn't perform validations, but prepares data for TableOfContentsBuilder.
1005
+ """
1006
+ manifestInstance = pluginData.getManifestInstance(val.modelXbrl)
1007
+ if manifestInstance is not None and manifestInstance.type == ReportFolderType.PUBLIC_DOC.value:
1008
+ pluginData.addToTableOfContents(val.modelXbrl)
1009
+ return iter(())
1010
+
1011
+
1009
1012
  @validation(
1010
1013
  hook=ValidationHook.XBRL_FINALLY,
1011
1014
  disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
@@ -1025,7 +1028,6 @@ def rule_html_elements(
1025
1028
  the XML at all, and thus an XML schema error will be triggered rather than this validation error.
1026
1029
  """
1027
1030
  checkNames = frozenset({'body', 'head', 'html'})
1028
- langAttributeValues = frozenset({'ja', 'jp', 'ja-jp', 'JA', 'JP', 'JA-JP'})
1029
1031
  for modelDocument in val.modelXbrl.urlDocs.values():
1030
1032
  path = Path(modelDocument.uri)
1031
1033
  if path.suffix not in HTML_EXTENSIONS:
@@ -1035,12 +1037,14 @@ def rule_html_elements(
1035
1037
  rootElt.qname.localName: 1
1036
1038
  }
1037
1039
  for elt in rootElt.iterdescendants():
1040
+ if not isinstance(elt, ModelObject):
1041
+ continue
1038
1042
  name = elt.qname.localName
1039
1043
  if name in checkNames:
1040
1044
  eltCounts[name] = eltCounts.get(name, 0) + 1
1041
1045
  if not isinstance(elt, ModelFact):
1042
1046
  lang = elt.get(XbrlConst.qnXmlLang.clarkNotation)
1043
- if lang is not None and lang not in langAttributeValues:
1047
+ if lang is not None and lang not in JAPAN_LANGUAGE_CODES:
1044
1048
  yield Validation.error(
1045
1049
  codes='EDINET.EC1011E',
1046
1050
  msg=_("The language setting is not Japanese. "
@@ -1049,7 +1053,7 @@ def rule_html_elements(
1049
1053
  "relevant file to one of the following: %(langValues)s."),
1050
1054
  file=modelDocument.basename,
1051
1055
  line=elt.sourceline,
1052
- langValues=', '.join(langAttributeValues),
1056
+ langValues=', '.join(JAPAN_LANGUAGE_CODES),
1053
1057
  )
1054
1058
 
1055
1059
  if any(count > 1 for count in eltCounts.values()):
@@ -1118,10 +1122,10 @@ def rule_filenames(
1118
1122
  return
1119
1123
  for path, pathInfo in uploadContents.uploadPathsByPath.items():
1120
1124
  isReportFile = (
1121
- not pathInfo.isAttachment and
1122
- not pathInfo.isCorrection and
1123
- not pathInfo.isDirectory and
1124
- not pathInfo.isSubdirectory
1125
+ not pathInfo.isAttachment and
1126
+ not pathInfo.isCorrection and
1127
+ not pathInfo.isDirectory and
1128
+ not pathInfo.isSubdirectory
1125
1129
  )
1126
1130
  charactersAreValid = FILENAME_STEM_PATTERN.fullmatch(path.stem)
1127
1131
  lengthIsValid = isReportFile or (len(path.name) <= 31)
@@ -1172,6 +1176,12 @@ def rule_manifest_preferredFilename(
1172
1176
  EDINET.EC5806E: The same instance file name is set multiple times. File name: xxx
1173
1177
  The preferredFilename attribute value of the instance element in the manifest
1174
1178
  file must be unique within the same file.
1179
+
1180
+ EDINET.EC8008W: The file name of the report instance set in the manifest file
1181
+ does not conform to the rules.
1182
+
1183
+ EDINET.EC8009W: The file name of the audit report instance set in the manifest file
1184
+ does not conform to the rules.
1175
1185
  """
1176
1186
  instances = pluginData.getManifestInstances()
1177
1187
  preferredFilenames: dict[Path, set[str]] = defaultdict(set)
@@ -1187,6 +1197,7 @@ def rule_manifest_preferredFilename(
1187
1197
  id=instance.id,
1188
1198
  )
1189
1199
  continue
1200
+
1190
1201
  preferredFilename = Path(instance.preferredFilename)
1191
1202
  if preferredFilename.suffix != '.xbrl':
1192
1203
  yield Validation.error(
@@ -1201,6 +1212,34 @@ def rule_manifest_preferredFilename(
1201
1212
  id=instance.id,
1202
1213
  )
1203
1214
  continue
1215
+
1216
+ reportFolderType = ReportFolderType.parse(instance.type)
1217
+ match = True if reportFolderType is None else any(
1218
+ pattern.fullmatch(preferredFilename.name)
1219
+ for pattern in reportFolderType.xbrlFilenamePatterns
1220
+ )
1221
+ if not match:
1222
+ if reportFolderType == ReportFolderType.AUDIT_DOC:
1223
+ yield Validation.warning(
1224
+ codes='EDINET.EC8009W',
1225
+ msg=_("The file name of the audit report instance set in the manifest "
1226
+ "file does not conform to the rules. "
1227
+ "File name: '%(file)s'. "
1228
+ "Please set the file name of the corresponding audit report instance "
1229
+ "according to the rules. Please correct the contents of the manifest file."),
1230
+ file=preferredFilename.name,
1231
+ )
1232
+ else:
1233
+ yield Validation.warning(
1234
+ codes='EDINET.EC8008W',
1235
+ msg=_("The file name of the report instance set in the manifest "
1236
+ "file does not comply with the regulations. "
1237
+ "File name: '%(file)s'. "
1238
+ "Please set the file name of the corresponding report instance "
1239
+ "according to the rules. Please correct the contents of the manifest file."),
1240
+ file=preferredFilename.name,
1241
+ )
1242
+
1204
1243
  if instance.preferredFilename in preferredFilenames[instance.path]:
1205
1244
  duplicateFilenames[instance.path].add(instance.preferredFilename)
1206
1245
  continue
@@ -372,7 +372,7 @@ class PluginValidationDataExtension(PluginData):
372
372
  tupleElements.add(elt)
373
373
  if elt.tag == ixFractionTag:
374
374
  fractionElements.add(elt)
375
- for elt, depth in etreeIterWithDepth(ixdsHtmlRootElt):
375
+ for elt in ixdsHtmlRootElt.iter():
376
376
  if elt.get(xmlBaseIdentifier) is not None:
377
377
  baseElements.add(elt)
378
378
  if elt.tag == xhtmlBaseIdentifier:
@@ -696,22 +696,20 @@ class PluginValidationDataExtension(PluginData):
696
696
  return reportXmlLang
697
697
 
698
698
  @lru_cache(1)
699
- def getTargetElements(self, modelXbrl: ModelXbrl) -> list[Any]:
699
+ def getTargetElements(self, modelXbrl: ModelXbrl) -> list[ModelObject]:
700
700
  targetElements = []
701
701
  for ixdsHtmlRootElt in modelXbrl.ixdsHtmlElements:
702
702
  ixNStag = str(getattr(ixdsHtmlRootElt.modelDocument, "ixNStag", ixbrl11))
703
- ixTags = set(ixNStag + ln for ln in ("nonNumeric", "nonFraction", "references", "relationship"))
704
- for elt, depth in etreeIterWithDepth(ixdsHtmlRootElt):
705
- if elt.tag in ixTags and elt.get("target"):
703
+ ixTags = (ixNStag + ln for ln in ("nonNumeric", "nonFraction", "references", "relationship"))
704
+ for elt in ixdsHtmlRootElt.iter(*ixTags):
705
+ if elt.get("target"):
706
706
  targetElements.append(elt)
707
707
  return targetElements
708
708
 
709
709
  def isExtensionUri(self, uri: str, modelXbrl: ModelXbrl) -> bool:
710
710
  if uri.startswith(modelXbrl.uriDir):
711
711
  return True
712
- if not any(uri.startswith(taxonomyUri) for taxonomyUri in STANDARD_TAXONOMY_URLS):
713
- return True
714
- return False
712
+ return not any(uri.startswith(taxonomyUri) for taxonomyUri in STANDARD_TAXONOMY_URLS)
715
713
 
716
714
  @lru_cache(1)
717
715
  def isFilenameValidCharacters(self, filename: str) -> bool:
@@ -147,9 +147,6 @@ class ValidationPluginExtension(ValidationPlugin):
147
147
  rjNamespace = None
148
148
  entrypointRoot = 'http://www.nltaxonomie.nl/nt19/kvk/20241211/entrypoints/'
149
149
  entrypoints = {entrypointRoot + e for e in [
150
- 'kvk-rpt-jaarverantwoording-2024-ifrs-full.xsd',
151
- 'kvk-rpt-jaarverantwoording-2024-ifrs-geconsolideerd-nlgaap-enkelvoudig.xsd',
152
- 'kvk-rpt-jaarverantwoording-2024-ifrs-smes.xsd',
153
150
  'kvk-rpt-jaarverantwoording-2024-nlgaap-banken.xsd',
154
151
  'kvk-rpt-jaarverantwoording-2024-nlgaap-beleggingsentiteiten.xsd',
155
152
  'kvk-rpt-jaarverantwoording-2024-nlgaap-cooperaties.xsd',
@@ -29,7 +29,6 @@ from arelle.utils.validate.DetectScriptsInXhtml import containsScriptMarkers
29
29
  from arelle.utils.validate.ESEFImage import ImageValidationParameters, validateImage
30
30
  from arelle.utils.validate.Validation import Validation
31
31
  from arelle.ValidateDuplicateFacts import getHashEquivalentFactGroups, getAspectEqualFacts
32
- from arelle.utils.validate.ValidationUtil import etreeIterWithDepth
33
32
  from ..DisclosureSystems import (ALL_NL_INLINE_DISCLOSURE_SYSTEMS, NL_INLINE_GAAP_IFRS_DISCLOSURE_SYSTEMS,
34
33
  NL_INLINE_GAAP_OTHER_DISCLOSURE_SYSTEMS)
35
34
  from ..PluginValidationDataExtension import (
@@ -751,7 +750,7 @@ def rule_nl_kvk_3_5_2_3(
751
750
  """
752
751
  badLangsUsed = set()
753
752
  for ixdsHtmlRootElt in val.modelXbrl.ixdsHtmlElements:
754
- for uncast_elt, depth in etreeIterWithDepth(ixdsHtmlRootElt):
753
+ for uncast_elt in ixdsHtmlRootElt.iter():
755
754
  elt = cast(Any, uncast_elt)
756
755
  xmlLang = elt.get("{http://www.w3.org/XML/1998/namespace}lang")
757
756
  if xmlLang and xmlLang not in ALLOWABLE_LANGUAGES: