arelle-release 2.37.55__py3-none-any.whl → 2.37.57__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 (26) hide show
  1. arelle/CntlrCmdLine.py +1 -0
  2. arelle/ErrorManager.py +3 -0
  3. arelle/ValidateDuplicateFacts.py +1 -1
  4. arelle/XbrlConst.py +1 -0
  5. arelle/_version.py +2 -2
  6. arelle/plugin/validate/EDINET/Constants.py +0 -18
  7. arelle/plugin/validate/EDINET/ControllerPluginData.py +43 -20
  8. arelle/plugin/validate/EDINET/CoverPageRequirements.py +118 -0
  9. arelle/plugin/validate/EDINET/FilingFormat.py +253 -0
  10. arelle/plugin/validate/EDINET/FormType.py +81 -0
  11. arelle/plugin/validate/EDINET/PluginValidationDataExtension.py +146 -34
  12. arelle/plugin/validate/EDINET/UploadContents.py +18 -2
  13. arelle/plugin/validate/EDINET/ValidationPluginExtension.py +1 -0
  14. arelle/plugin/validate/EDINET/resources/cover-page-requirements.csv +27 -0
  15. arelle/plugin/validate/EDINET/rules/edinet.py +1 -1
  16. arelle/plugin/validate/EDINET/rules/gfm.py +216 -0
  17. arelle/plugin/validate/EDINET/rules/upload.py +295 -35
  18. arelle/plugin/validate/ROS/PluginValidationDataExtension.py +2 -0
  19. arelle/plugin/validate/ROS/ValidationPluginExtension.py +1 -0
  20. arelle/plugin/validate/ROS/rules/ros.py +39 -9
  21. {arelle_release-2.37.55.dist-info → arelle_release-2.37.57.dist-info}/METADATA +1 -1
  22. {arelle_release-2.37.55.dist-info → arelle_release-2.37.57.dist-info}/RECORD +26 -22
  23. {arelle_release-2.37.55.dist-info → arelle_release-2.37.57.dist-info}/WHEEL +0 -0
  24. {arelle_release-2.37.55.dist-info → arelle_release-2.37.57.dist-info}/entry_points.txt +0 -0
  25. {arelle_release-2.37.55.dist-info → arelle_release-2.37.57.dist-info}/licenses/LICENSE.md +0 -0
  26. {arelle_release-2.37.55.dist-info → arelle_release-2.37.57.dist-info}/top_level.txt +0 -0
@@ -6,20 +6,24 @@ 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
9
+ from typing import Any, Iterable, TYPE_CHECKING, cast
10
10
 
11
11
  import regex
12
12
 
13
- from arelle import UrlUtil
13
+ from arelle import UrlUtil, XbrlConst
14
14
  from arelle.Cntlr import Cntlr
15
15
  from arelle.FileSource import FileSource
16
+ from arelle.ModelInstanceObject import ModelFact
16
17
  from arelle.ValidateXbrl import ValidateXbrl
18
+ from arelle.XmlValidateConst import VALID
17
19
  from arelle.typing import TypeGetText
18
20
  from arelle.utils.PluginHooks import ValidationHook
19
21
  from arelle.utils.validate.Decorator import validation
20
22
  from arelle.utils.validate.Validation import Validation
21
23
  from .. import Constants
24
+ from ..CoverPageRequirements import CoverPageItemStatus
22
25
  from ..DisclosureSystems import (DISCLOSURE_SYSTEM_EDINET)
26
+ from ..FilingFormat import FILING_FORMATS
23
27
  from ..ReportFolderType import ReportFolderType, HTML_EXTENSIONS, IMAGE_EXTENSIONS
24
28
  from ..PluginValidationDataExtension import PluginValidationDataExtension
25
29
 
@@ -122,8 +126,10 @@ def rule_EC0100E(
122
126
  For this implementation, we will allow all directories that may be valid for at least one submission type.
123
127
  This allows for a false-negative outcome when a non-correction submission has a correction-only root directory.
124
128
  """
125
- uploadContents = pluginData.getUploadContents(fileSource)
126
- for path, pathInfo in uploadContents.uploadPaths.items():
129
+ uploadContents = pluginData.getUploadContents()
130
+ if uploadContents is None:
131
+ return
132
+ for path, pathInfo in uploadContents.uploadPathsByPath.items():
127
133
  if pathInfo.isRoot and path.name not in ALLOWED_ROOT_FOLDERS:
128
134
  yield Validation.error(
129
135
  codes='EDINET.EC0100E',
@@ -153,8 +159,8 @@ def rule_EC0124E_EC0187E(
153
159
  """
154
160
  uploadFilepaths = pluginData.getUploadFilepaths(fileSource)
155
161
  emptyDirectories = []
156
- for path in uploadFilepaths:
157
- if path.suffix:
162
+ for path, zipPath in uploadFilepaths.items():
163
+ if not zipPath.is_dir():
158
164
  continue
159
165
  if not any(path in p.parents for p in uploadFilepaths):
160
166
  emptyDirectories.append(path)
@@ -227,8 +233,10 @@ def rule_EC0130E(
227
233
  """
228
234
  EDINET.EC0130E: File extensions must match the file extensions allowed in Figure 2-1-3 and Figure 2-1-5.
229
235
  """
230
- uploadContents = pluginData.getUploadContents(fileSource)
231
- for path, pathInfo in uploadContents.uploadPaths.items():
236
+ uploadContents = pluginData.getUploadContents()
237
+ if uploadContents is None:
238
+ return
239
+ for path, pathInfo in uploadContents.uploadPathsByPath.items():
232
240
  if pathInfo.reportFolderType is None or pathInfo.isDirectory:
233
241
  continue
234
242
  validExtensions = pathInfo.reportFolderType.getValidExtensions(pathInfo.isCorrection, pathInfo.isSubdirectory)
@@ -265,7 +273,9 @@ def rule_EC0132E(
265
273
  """
266
274
  EDINET.EC0132E: Store the manifest file directly under the relevant folder.
267
275
  """
268
- uploadContents = pluginData.getUploadContents(fileSource)
276
+ uploadContents = pluginData.getUploadContents()
277
+ if uploadContents is None:
278
+ return
269
279
  for reportFolderType, paths in uploadContents.reports.items():
270
280
  if reportFolderType.isAttachment:
271
281
  continue
@@ -353,8 +363,10 @@ def rule_EC0192E(
353
363
  PublicDoc cover file. Please delete the cover file from PrivateDoc and upload
354
364
  it again.
355
365
  """
356
- uploadContents = pluginData.getUploadContents(fileSource)
357
- for path, pathInfo in uploadContents.uploadPaths.items():
366
+ uploadContents = pluginData.getUploadContents()
367
+ if uploadContents is None:
368
+ return
369
+ for path, pathInfo in uploadContents.uploadPathsByPath.items():
358
370
  if not pathInfo.isCoverPage:
359
371
  continue
360
372
  # Only applies to PrivateDoc correction reports
@@ -383,8 +395,8 @@ def rule_EC0198E(
383
395
  """
384
396
  fileCounts: dict[Path, int] = defaultdict(int)
385
397
  uploadFilepaths = pluginData.getUploadFilepaths(fileSource)
386
- for path in uploadFilepaths:
387
- if len(path.suffix) == 0:
398
+ for path, zipPath in uploadFilepaths.items():
399
+ if zipPath.is_dir():
388
400
  continue
389
401
  for directory in FILE_COUNT_LIMITS.keys():
390
402
  if directory in path.parents:
@@ -421,10 +433,12 @@ def rule_EC0233E(
421
433
  NOTE: This includes files in subdirectories. For example, PublicDoc/00000000_images/image.png
422
434
  comes before PublicDoc/0000000_header_*.htm
423
435
  """
424
- uploadContents = pluginData.getUploadContents(fileSource)
436
+ uploadContents = pluginData.getUploadContents()
437
+ if uploadContents is None:
438
+ return
425
439
  directories = defaultdict(list)
426
440
  for path in uploadContents.sortedPaths:
427
- pathInfo = uploadContents.uploadPaths[path]
441
+ pathInfo = uploadContents.uploadPathsByPath[path]
428
442
  if pathInfo.isDirectory:
429
443
  continue
430
444
  if pathInfo.reportFolderType in (ReportFolderType.PRIVATE_DOC, ReportFolderType.PUBLIC_DOC):
@@ -462,8 +476,10 @@ def rule_EC0234E(
462
476
  """
463
477
  EDINET.EC0234E: A cover file exists in an unsupported subdirectory.
464
478
  """
465
- uploadContents = pluginData.getUploadContents(fileSource)
466
- for path, pathInfo in uploadContents.uploadPaths.items():
479
+ uploadContents = pluginData.getUploadContents()
480
+ if uploadContents is None:
481
+ return
482
+ for path, pathInfo in uploadContents.uploadPathsByPath.items():
467
483
  if pathInfo.isDirectory:
468
484
  continue
469
485
  if pathInfo.reportFolderType not in (ReportFolderType.PRIVATE_DOC, ReportFolderType.PUBLIC_DOC):
@@ -549,14 +565,16 @@ def rule_EC0349E(
549
565
  EDINET.EC0349E: An unexpected directory or file exists in the XBRL directory.
550
566
  Only PublicDoc, PrivateDoc, or AuditDoc directories may exist beneath the XBRL directory.
551
567
  """
552
- uploadContent = pluginData.getUploadContents(fileSource)
568
+ uploadContents = pluginData.getUploadContents()
569
+ if uploadContents is None:
570
+ return
553
571
  xbrlDirectoryPath = Path('XBRL')
554
572
  allowedPaths = {p.xbrlDirectory for p in (
555
573
  ReportFolderType.AUDIT_DOC,
556
574
  ReportFolderType.PRIVATE_DOC,
557
575
  ReportFolderType.PUBLIC_DOC,
558
576
  )}
559
- for path, pathInfo in uploadContent.uploadPaths.items():
577
+ for path, pathInfo in uploadContents.uploadPathsByPath.items():
560
578
  if path.parent != xbrlDirectoryPath:
561
579
  continue
562
580
  if path not in allowedPaths:
@@ -583,8 +601,10 @@ def rule_EC0352E(
583
601
  """
584
602
  EDINET.EC0352E: An XBRL file with an invalid name exists.
585
603
  """
586
- uploadContent = pluginData.getUploadContents(fileSource)
587
- for path, pathInfo in uploadContent.uploadPaths.items():
604
+ uploadContents = pluginData.getUploadContents()
605
+ if uploadContents is None:
606
+ return
607
+ for path, pathInfo in uploadContents.uploadPathsByPath.items():
588
608
  if (
589
609
  pathInfo.isDirectory or
590
610
  pathInfo.isCorrection or
@@ -603,6 +623,127 @@ def rule_EC0352E(
603
623
  )
604
624
 
605
625
 
626
+ @validation(
627
+ hook=ValidationHook.XBRL_FINALLY,
628
+ disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
629
+ )
630
+ def rules_cover_page(
631
+ pluginData: PluginValidationDataExtension,
632
+ val: ValidateXbrl,
633
+ *args: Any,
634
+ **kwargs: Any,
635
+ ) -> Iterable[Validation]:
636
+ """
637
+ EDINET.EC1000E: Cover page must contain "【表紙】".
638
+ EDINET.EC1001E: A required item is missing from the cover page.
639
+ EDINET.EC1002E: A duplicate item is included on the cover page.
640
+ EDINET.EC1003E: An unnecessary item is included on the cover page.
641
+ EDINET.EC1004E: An item on the cover page is out of order.
642
+ EDINET.EC1005E: A required item on the cover page is missing a valid value.
643
+ """
644
+ uploadContents = pluginData.getUploadContents(val.modelXbrl)
645
+ if uploadContents is None:
646
+ return
647
+ for url, doc in val.modelXbrl.urlDocs.items():
648
+ path = Path(url)
649
+ pathInfo = uploadContents.uploadPathsByFullPath.get(path)
650
+ if pathInfo is None or not pathInfo.isCoverPage:
651
+ continue
652
+ rootElt = doc.xmlRootElement
653
+ coverPageTextFound = False
654
+ for elt in rootElt.iterdescendants():
655
+ if not coverPageTextFound and elt.text and '【表紙】' in elt.text:
656
+ coverPageTextFound = True
657
+ break
658
+ if not coverPageTextFound:
659
+ yield Validation.error(
660
+ codes='EDINET.EC1000E',
661
+ msg=_("There is no '【表紙】' on the cover page. "
662
+ "File name: '%(file)s'. "
663
+ "Please add '【表紙】' to the relevant file."),
664
+ file=doc.basename,
665
+ )
666
+ filingFormat = pluginData.getFilingFormat(val.modelXbrl)
667
+ if filingFormat is None:
668
+ return
669
+ coverPageRequirements = pluginData.getCoverPageRequirements(val.modelXbrl)
670
+ currentLineNumber = 0
671
+ for qname in pluginData.coverPageItems:
672
+ foundFacts = []
673
+ validNonNilFacts = []
674
+ for fact in pluginData.iterFacts(val.modelXbrl, qname):
675
+ if fact.modelDocument != doc:
676
+ continue
677
+ if fact.qname.prefix is not None and filingFormat.includesTaxonomyPrefix(fact.qname.prefix):
678
+ foundFacts.append(fact)
679
+ if fact.xValid >= VALID and not fact.isNil:
680
+ validNonNilFacts.append(fact)
681
+
682
+ for fact in sorted(foundFacts, key=lambda f: cast(int, f.sourceline)):
683
+ if (sourceLine := cast(int, fact.sourceline)) <= currentLineNumber:
684
+ yield Validation.error(
685
+ codes='EDINET.EC1004E',
686
+ msg=_("Cover item %(localName)s is not in the correct order. "
687
+ "File name: '%(file)s'. "
688
+ "Please correct the order of cover items in the appropriate file."),
689
+ localName=qname.localName,
690
+ file=doc.basename,
691
+ modelObject=fact,
692
+ )
693
+ else:
694
+ currentLineNumber = sourceLine
695
+
696
+ if len(foundFacts) > 1:
697
+ yield Validation.error(
698
+ codes='EDINET.EC1002E',
699
+ msg=_("Cover item %(localName)s is duplicated. "
700
+ "File name: '%(file)s'. "
701
+ "Please check the cover item %(localName)s of the relevant file "
702
+ "and make sure there are no duplicates."),
703
+ localName=qname.localName,
704
+ file=doc.basename,
705
+ modelObject=foundFacts,
706
+ )
707
+
708
+ status = coverPageRequirements.get(qname, filingFormat)
709
+ if status is None:
710
+ continue
711
+ if status == CoverPageItemStatus.REQUIRED:
712
+ if len(foundFacts) == 0:
713
+ yield Validation.error(
714
+ codes='EDINET.EC1001E',
715
+ msg=_("Cover item %(localName)s is missing. "
716
+ "File name: '%(file)s'. "
717
+ "Please add the cover item %(localName)s to the relevant file."),
718
+ localName=qname.localName,
719
+ file=doc.basename,
720
+ )
721
+ elif len(validNonNilFacts) == 0:
722
+ yield Validation.error(
723
+ codes='EDINET.EC1005E',
724
+ msg=_("Cover item %(localName)s is missing a valid value. "
725
+ "File name: '%(file)s'. "
726
+ "Please enter a valid value for %(localName)s in the relevant file."),
727
+ localName=qname.localName,
728
+ file=doc.basename,
729
+ modelObject=foundFacts,
730
+ )
731
+ elif status == CoverPageItemStatus.PROHIBITED:
732
+ for fact in foundFacts:
733
+ if fact.isNil:
734
+ continue # Prohibited cover pages facts are allowed, only if nil.
735
+ yield Validation.error(
736
+ codes='EDINET.EC1003E',
737
+ msg=_("Cover item %(localName)s is not necessary. "
738
+ "File name: '%(file)s' (line %(line)s). "
739
+ "Please add the cover item %(localName)s to the relevant file."),
740
+ localName=qname.localName,
741
+ file=doc.basename,
742
+ line=fact.sourceline,
743
+ modelObject=fact,
744
+ )
745
+
746
+
606
747
  @validation(
607
748
  hook=ValidationHook.XBRL_FINALLY,
608
749
  disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
@@ -644,8 +785,13 @@ def rule_uri_references(
644
785
  """
645
786
  EDINET.EC1007E: The URI in the HTML specifies a URL or absolute path.
646
787
  EDINET.EC1013E: The URI in the HTML specifies a path not under a subdirectory.
647
- EDINET.EC1014E: The URI in the HTML specifies a path to a file that doesn't exist.
788
+ EDINET.EC1014E: The URI in the HTML specifies a path to a directory.
789
+ EDINET.EC1021E: The URI in the HTML specifies a path to a file that doesn't exist.
790
+ EDINET.EC1023E: The URI in the HTML specifies a path to a PDF file.
648
791
  """
792
+ uploadContents = pluginData.getUploadContents(val.modelXbrl)
793
+ if uploadContents is None:
794
+ return
649
795
  for uriReference in pluginData.uriReferences:
650
796
  if UrlUtil.isAbsolute(uriReference.attributeValue):
651
797
  yield Validation.error(
@@ -671,10 +817,11 @@ def rule_uri_references(
671
817
  )
672
818
  continue
673
819
  fullPath = Path(uriReference.document.uri).parent / path
674
- if not val.modelXbrl.fileSource.exists(str(fullPath)):
820
+ pathInfo = uploadContents.uploadPathsByFullPath.get(fullPath)
821
+ if pathInfo is not None and pathInfo.isDirectory:
675
822
  yield Validation.error(
676
823
  codes='EDINET.EC1014E',
677
- msg=_("The URI in the HTML specifies a path to a file that doesn't exist. "
824
+ msg=_("The URI in the HTML specifies a path to a directory. "
678
825
  "File name: '%(file)s' (line %(line)s). "
679
826
  "Please update the URI to reference a file."),
680
827
  file=uriReference.document.basename,
@@ -682,6 +829,100 @@ def rule_uri_references(
682
829
  modelObject=uriReference.element,
683
830
  )
684
831
  continue
832
+ if path.suffix.lower() == '.pdf':
833
+ yield Validation.error(
834
+ codes='EDINET.EC1023E',
835
+ msg=_("The URI in the HTML specifies a path to a PDF file. "
836
+ "File name: '%(file)s' (line %(line)s). "
837
+ "Please remove the link from the relevant file."),
838
+ file=uriReference.document.basename,
839
+ line=uriReference.element.sourceline,
840
+ modelObject=uriReference.element,
841
+ )
842
+ continue
843
+ if not val.modelXbrl.fileSource.exists(str(fullPath)):
844
+ yield Validation.error(
845
+ codes='EDINET.EC1021E',
846
+ msg=_("The linked file ('%(path)s') does not exist. "
847
+ "File name: '%(file)s' (line %(line)s). "
848
+ "Please update the URI to reference a file."),
849
+ path=str(path),
850
+ file=uriReference.document.basename,
851
+ line=uriReference.element.sourceline,
852
+ modelObject=uriReference.element,
853
+ )
854
+ continue
855
+
856
+
857
+ @validation(
858
+ hook=ValidationHook.FILESOURCE,
859
+ disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
860
+ )
861
+ def rule_EC1009R(
862
+ pluginData: ControllerPluginData,
863
+ cntlr: Cntlr,
864
+ fileSource: FileSource,
865
+ *args: Any,
866
+ **kwargs: Any,
867
+ ) -> Iterable[Validation]:
868
+ """
869
+ EDINET.EC1009R: The HTML file size must be 2.5MB (megabytes) or less.
870
+ """
871
+ for path, size in pluginData.getUploadFileSizes(fileSource).items():
872
+ if path.suffix not in HTML_EXTENSIONS:
873
+ continue
874
+ if size > 2_500_000:
875
+ yield Validation.warning(
876
+ codes='EDINET.EC1009R',
877
+ msg=_("The HTML file size exceeds the maximum limit. "
878
+ "File name: '%(path)s'. "
879
+ "Please split the file so that the file size is 2.5MB or less."),
880
+ path=str(path),
881
+ )
882
+
883
+
884
+
885
+ @validation(
886
+ hook=ValidationHook.XBRL_FINALLY,
887
+ disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
888
+ )
889
+ def rule_EC1010E(
890
+ pluginData: PluginValidationDataExtension,
891
+ val: ValidateXbrl,
892
+ *args: Any,
893
+ **kwargs: Any,
894
+ ) -> Iterable[Validation]:
895
+ """
896
+ EDINET.EC1010E: The charset specification in the content attribute of the HTML <meta> tag must be UTF-8.
897
+ """
898
+ for modelDocument in val.modelXbrl.urlDocs.values():
899
+ path = Path(modelDocument.uri)
900
+ if path.suffix not in HTML_EXTENSIONS:
901
+ continue
902
+ rootElt = modelDocument.xmlRootElement
903
+ matchingElt = None
904
+ missingElts = []
905
+ for metaElt in rootElt.iterdescendants(tag=XbrlConst.qnXhtmlMeta.clarkNotation):
906
+ if metaElt.qname.localName != 'meta':
907
+ continue
908
+ content = metaElt.get('content')
909
+ if content is None:
910
+ continue
911
+ charset = content.split('charset=')[-1].strip().lower()
912
+ if charset == 'utf-8':
913
+ matchingElt = metaElt
914
+ else:
915
+ missingElts.append(metaElt)
916
+
917
+ if matchingElt is None or len(missingElts) > 0:
918
+ yield Validation.error(
919
+ codes='EDINET.EC1010E',
920
+ msg=_("The charset specification in the content attribute of the HTML <meta> tag is not UTF-8. "
921
+ "File name: '%(path)s'. "
922
+ "Please change the character code of the file to UTF-8."),
923
+ path=str(path),
924
+ modelObject=missingElts
925
+ )
685
926
 
686
927
 
687
928
  @validation(
@@ -727,10 +968,12 @@ def rule_EC1017E(
727
968
  """
728
969
  EDINET.EC1017E: There is an unused file.
729
970
  """
730
- uploadContents = pluginData.getUploadContents(fileSource)
971
+ uploadContents = pluginData.getUploadContents()
972
+ if uploadContents is None:
973
+ return
731
974
  existingSubdirectoryFilepaths = {
732
975
  path
733
- for path, pathInfo in uploadContents.uploadPaths.items()
976
+ for path, pathInfo in uploadContents.uploadPathsByPath.items()
734
977
  if pathInfo.isSubdirectory and not pathInfo.isDirectory
735
978
  }
736
979
  usedFilepaths = pluginData.getUsedFilepaths()
@@ -749,20 +992,22 @@ def rule_EC1017E(
749
992
  hook=ValidationHook.XBRL_FINALLY,
750
993
  disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
751
994
  )
752
- def rule_EC1020E(
995
+ def rule_html_elements(
753
996
  pluginData: PluginValidationDataExtension,
754
997
  val: ValidateXbrl,
755
998
  *args: Any,
756
999
  **kwargs: Any,
757
1000
  ) -> Iterable[Validation]:
758
1001
  """
1002
+ EDINET.EC1011E: The HTML lang attribute is not Japanese.
759
1003
  EDINET.EC1020E: When writing a DOCTYPE declaration, do not define it multiple times.
760
- Also, please modify the relevant file so that there is only one html tag, one head tag, and one body tag each.
1004
+ Also, please modify the relevant file so that there is only one html tag, one head tag, and one body tag each.
761
1005
 
762
- Note: Some violations of this rule (such as multiple DOCTYPE declarations) prevent Arelle from parsing
1006
+ Note: Some violations of EC1020E (such as multiple DOCTYPE declarations) prevent Arelle from parsing
763
1007
  the XML at all, and thus an XML schema error will be triggered rather than this validation error.
764
1008
  """
765
1009
  checkNames = frozenset({'body', 'head', 'html'})
1010
+ langAttributeValues = frozenset({'ja', 'jp', 'ja-jp', 'JA', 'JP', 'JA-JP'})
766
1011
  for modelDocument in val.modelXbrl.urlDocs.values():
767
1012
  path = Path(modelDocument.uri)
768
1013
  if path.suffix not in HTML_EXTENSIONS:
@@ -773,10 +1018,22 @@ def rule_EC1020E(
773
1018
  }
774
1019
  for elt in rootElt.iterdescendants():
775
1020
  name = elt.qname.localName
776
- if name not in checkNames:
777
- continue
778
- eltCounts[name] = eltCounts.get(name, 0) + 1
779
- pass
1021
+ if name in checkNames:
1022
+ eltCounts[name] = eltCounts.get(name, 0) + 1
1023
+ if not isinstance(elt, ModelFact):
1024
+ lang = elt.get(XbrlConst.qnXmlLang.clarkNotation)
1025
+ if lang is not None and lang not in langAttributeValues:
1026
+ yield Validation.error(
1027
+ codes='EDINET.EC1011E',
1028
+ msg=_("The language setting is not Japanese. "
1029
+ "File name: %(file)s (line %(line)s). "
1030
+ "Please set the lang attribute on the given line of the "
1031
+ "relevant file to one of the following: %(langValues)s."),
1032
+ file=modelDocument.basename,
1033
+ line=elt.sourceline,
1034
+ langValues=', '.join(langAttributeValues),
1035
+ )
1036
+
780
1037
  if any(count > 1 for count in eltCounts.values()):
781
1038
  yield Validation.error(
782
1039
  codes='EDINET.EC1020E',
@@ -810,7 +1067,7 @@ def rule_EC1031E(
810
1067
  msg=_("Prohibited attribute '%(attributeName)s' is used in HTML. "
811
1068
  "File name: %(file)s (line %(line)s). "
812
1069
  "Please correct the tag attributes of the relevant file."),
813
- attributeName=elt.qname.localName,
1070
+ attributeName=attributeName,
814
1071
  file=doc.basename,
815
1072
  line=elt.sourceline,
816
1073
  modelObject=elt,
@@ -838,7 +1095,10 @@ def rule_filenames(
838
1095
  than those allowed (alphanumeric characters, '-' and '_').
839
1096
  Note: Applies ONLY to files directly beneath non-correction report folders.
840
1097
  """
841
- for path, pathInfo in pluginData.getUploadContents(fileSource).uploadPaths.items():
1098
+ uploadContents = pluginData.getUploadContents()
1099
+ if uploadContents is None:
1100
+ return
1101
+ for path, pathInfo in uploadContents.uploadPathsByPath.items():
842
1102
  isReportFile = (
843
1103
  not pathInfo.isAttachment and
844
1104
  not pathInfo.isCorrection and
@@ -35,6 +35,8 @@ SCHEMA_PATTERNS = {
35
35
  "http://www.cro.ie/": re.compile(r"^\d{1,6}$")
36
36
  }
37
37
 
38
+ UK_REF_NS_PATTERN = re.compile(r"^http://xbrl.frc.org.uk/general/\d{4,}-\d{2,}-\d{2,}/ref$")
39
+
38
40
  TR_NAMESPACES = {
39
41
  "http://www.xbrl.org/inlineXBRL/transformation/2010-04-20",
40
42
  "http://www.xbrl.org/inlineXBRL/transformation/2011-07-31",
@@ -12,6 +12,7 @@ from .PluginValidationDataExtension import PluginValidationDataExtension
12
12
 
13
13
  _: TypeGetText
14
14
 
15
+ CURRENCIES_DIMENSION = 'CurrenciesDimension'
15
16
  EQUITY = 'Equity'
16
17
  IE_PROFIT_LOSS = 'ProfitLossBeforeTax'
17
18
  IE_PROFIT_LOSS_ORDINARY = 'ProfitLossOnOrdinaryActivitiesBeforeTax'
@@ -10,6 +10,7 @@ from collections import Counter
10
10
  from collections.abc import Iterable
11
11
  from decimal import Decimal
12
12
 
13
+ from arelle import XbrlConst
13
14
  from arelle.typing import TypeGetText
14
15
  from arelle.ValidateXbrl import ValidateXbrl
15
16
  from collections import defaultdict
@@ -28,8 +29,8 @@ from arelle.utils.validate.Validation import Validation
28
29
  from arelle.ValidateXbrlCalcs import inferredDecimals, rangeValue
29
30
  from arelle.XbrlConst import qnXbrliMonetaryItemType, qnXbrliXbrl, xhtml
30
31
  from arelle.XmlValidateConst import VALID
31
- from ..ValidationPluginExtension import EQUITY, PRINCIPAL_CURRENCY, TURNOVER_REVENUE
32
- from ..PluginValidationDataExtension import MANDATORY_ELEMENTS, SCHEMA_PATTERNS, TR_NAMESPACES, PluginValidationDataExtension
32
+ from ..ValidationPluginExtension import CURRENCIES_DIMENSION, EQUITY, PRINCIPAL_CURRENCY, TURNOVER_REVENUE
33
+ from ..PluginValidationDataExtension import MANDATORY_ELEMENTS, SCHEMA_PATTERNS, TR_NAMESPACES, UK_REF_NS_PATTERN, PluginValidationDataExtension
33
34
 
34
35
 
35
36
  def checkFileEncoding(modelXbrl: ModelXbrl) -> None:
@@ -302,14 +303,14 @@ def rule_ros19(
302
303
  equity_facts = val.modelXbrl.factsByLocalName.get(EQUITY, set())
303
304
  largest_equity_fact = None
304
305
  for e_fact in equity_facts:
305
- if e_fact.xValid >= VALID:
306
+ if e_fact.xValid >= VALID and e_fact.xValue is not None:
306
307
  if largest_equity_fact is None or abs(convertToDecimal(e_fact.xValue)) > abs(convertToDecimal(largest_equity_fact.xValue)):
307
308
  largest_equity_fact = e_fact
308
309
 
309
310
  turnover_facts = val.modelXbrl.factsByLocalName.get(TURNOVER_REVENUE, set())
310
311
  largest_turnover_fact = None
311
312
  for t_fact in turnover_facts:
312
- if t_fact.xValid >= VALID:
313
+ if t_fact.xValid >= VALID and t_fact.xValue is not None:
313
314
  if largest_turnover_fact is None or convertToDecimal(t_fact.xValue) > convertToDecimal(largest_turnover_fact.xValue):
314
315
  largest_turnover_fact = t_fact
315
316
 
@@ -337,27 +338,56 @@ def rule_ros20(
337
338
  used for the majority of monetary facts.
338
339
  """
339
340
  principal_currency_facts = val.modelXbrl.factsByLocalName.get(PRINCIPAL_CURRENCY, set())
340
- principal_currency_values = set(fact.text for fact in principal_currency_facts)
341
+ principal_currency_values = {
342
+ currencyDimensionCode
343
+ for pc_fact in principal_currency_facts
344
+ if (currencyDimensionCode := _getCurrencyDimensionCode(val.modelXbrl, pc_fact))
345
+ }
341
346
  if len(principal_currency_values) != 1:
342
347
  yield Validation.error(
343
348
  "ROS.20",
344
349
  _("'PrincipalCurrencyUsedInBusinessReport' must exist and have a single value. Values found: %(principal_currency_values)s."),
345
350
  modelObject=principal_currency_facts,
346
- principal_currency_values=principal_currency_values,
351
+ principal_currency_values=sorted(principal_currency_values),
347
352
  )
348
353
  else:
349
354
  principal_currency_value = principal_currency_values.pop()
350
355
  monetary_facts = list(val.modelXbrl.factsByDatatype(False, qnXbrliMonetaryItemType))
351
- monetary_units = [list(fact.utrEntries)[0].unitName for fact in monetary_facts if fact.unit is not None]
356
+ monetary_units = [list(fact.utrEntries)[0].unitId for fact in monetary_facts if fact.unit is not None]
352
357
  unit_counts = Counter(monetary_units)
353
358
  principal_currency_value_count = unit_counts[principal_currency_value]
354
- for unit, count in unit_counts.items():
359
+ for count in unit_counts.values():
355
360
  if count > principal_currency_value_count:
356
361
  yield Validation.warning(
357
362
  "ROS.20",
358
- _("'PrincipalCurrencyUsedInBusinessReport' has a value of %(principal_currency_value)s, "
363
+ _("'PrincipalCurrencyUsedInBusinessReport' has a %(currencies_dimension)s value of %(principal_currency_value)s, "
359
364
  "which must match the functional(majority) unit of the financial statement."),
360
365
  modelObject=principal_currency_facts,
366
+ currencies_dimension=CURRENCIES_DIMENSION,
361
367
  principal_currency_value=principal_currency_value,
362
368
  )
363
369
  break
370
+
371
+ def _getCurrencyDimensionCode(modelXbrl: ModelXbrl, fact: ModelInlineFact) -> str | None:
372
+ if fact.context is None:
373
+ return None
374
+ for dim, mem in fact.context.qnameDims.items():
375
+ if dim.localName != CURRENCIES_DIMENSION:
376
+ continue
377
+ if mem.xValid < VALID:
378
+ return None
379
+ mem_concept = modelXbrl.qnameConcepts.get(mem.xValue)
380
+ if mem_concept is None:
381
+ return None
382
+ for ref_rel in modelXbrl.relationshipSet(XbrlConst.conceptReference).fromModelObject(mem_concept):
383
+ concept_ref = ref_rel.toModelObject
384
+ uk_ref_ns = None
385
+ for ns in concept_ref.nsmap.values():
386
+ if UK_REF_NS_PATTERN.match(ns):
387
+ uk_ref_ns = ns
388
+ break
389
+ if uk_ref_ns is None:
390
+ continue
391
+ if code := concept_ref.findtext(f"{{{uk_ref_ns}}}Code"):
392
+ return code.strip()
393
+ return None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arelle-release
3
- Version: 2.37.55
3
+ Version: 2.37.57
4
4
  Summary: An open source XBRL platform.
5
5
  Author-email: "arelle.org" <support@arelle.org>
6
6
  License-Expression: Apache-2.0