arelle-release 2.37.61__py3-none-any.whl → 2.37.63__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of arelle-release might be problematic. Click here for more details.
- arelle/DisclosureSystem.py +5 -0
- arelle/FileSource.py +4 -1
- arelle/HtmlUtil.py +5 -4
- arelle/ValidateDuplicateFacts.py +2 -0
- arelle/ValidateXbrl.py +3 -1
- arelle/ValidateXbrlDTS.py +1 -1
- arelle/XbrlConst.py +18 -0
- arelle/_version.py +2 -2
- arelle/api/Session.py +6 -0
- arelle/config/disclosuresystems.xsd +1 -0
- arelle/plugin/validate/EDINET/Constants.py +95 -0
- arelle/plugin/validate/EDINET/ControllerPluginData.py +14 -3
- arelle/plugin/validate/EDINET/CoverItemRequirements.py +42 -0
- arelle/plugin/validate/EDINET/{CoverPageRequirements.py → DeiRequirements.py} +24 -24
- arelle/plugin/validate/EDINET/PluginValidationDataExtension.py +209 -43
- arelle/plugin/validate/EDINET/ReportFolderType.py +61 -0
- arelle/plugin/validate/EDINET/TableOfContentsBuilder.py +493 -0
- arelle/plugin/validate/EDINET/__init__.py +13 -2
- arelle/plugin/validate/EDINET/resources/config.xml +6 -0
- arelle/plugin/validate/EDINET/resources/cover-item-requirements.json +793 -0
- arelle/plugin/validate/EDINET/resources/edinet-taxonomies.xml +2 -0
- arelle/plugin/validate/EDINET/rules/contexts.py +61 -1
- arelle/plugin/validate/EDINET/rules/edinet.py +278 -4
- arelle/plugin/validate/EDINET/rules/frta.py +122 -3
- arelle/plugin/validate/EDINET/rules/gfm.py +681 -5
- arelle/plugin/validate/EDINET/rules/upload.py +231 -192
- arelle/plugin/validate/NL/PluginValidationDataExtension.py +6 -8
- arelle/plugin/validate/NL/ValidationPluginExtension.py +0 -3
- arelle/plugin/validate/NL/rules/nl_kvk.py +1 -2
- arelle/utils/validate/ValidationPlugin.py +1 -1
- arelle/utils/validate/ValidationUtil.py +1 -2
- {arelle_release-2.37.61.dist-info → arelle_release-2.37.63.dist-info}/METADATA +2 -1
- {arelle_release-2.37.61.dist-info → arelle_release-2.37.63.dist-info}/RECORD +38 -35
- /arelle/plugin/validate/EDINET/resources/{cover-page-requirements.csv → dei-requirements.csv} +0 -0
- {arelle_release-2.37.61.dist-info → arelle_release-2.37.63.dist-info}/WHEEL +0 -0
- {arelle_release-2.37.61.dist-info → arelle_release-2.37.63.dist-info}/entry_points.txt +0 -0
- {arelle_release-2.37.61.dist-info → arelle_release-2.37.63.dist-info}/licenses/LICENSE.md +0 -0
- {arelle_release-2.37.61.dist-info → arelle_release-2.37.63.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
|
|
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
|
|
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
|
|
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
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
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
|
-
|
|
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:
|
|
805
|
-
EDINET.EC1013E:
|
|
806
|
-
|
|
807
|
-
EDINET.
|
|
808
|
-
EDINET.
|
|
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
|
-
|
|
826
|
-
|
|
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.
|
|
829
|
-
msg=_("The URI in the HTML specifies a path
|
|
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
|
-
"
|
|
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
|
-
|
|
838
|
-
|
|
839
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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(
|
|
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
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
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
|
|
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[
|
|
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 =
|
|
704
|
-
for elt
|
|
705
|
-
if elt.
|
|
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
|
-
|
|
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
|
|
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:
|