arelle-release 2.37.50__py3-none-any.whl → 2.37.52__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/XbrlConst.py +1 -0
- arelle/_version.py +16 -3
- arelle/plugin/validate/EDINET/Constants.py +2 -0
- arelle/plugin/validate/EDINET/ControllerPluginData.py +40 -29
- arelle/plugin/validate/EDINET/PluginValidationDataExtension.py +21 -3
- arelle/plugin/validate/EDINET/{InstanceType.py → ReportFolderType.py} +11 -15
- arelle/plugin/validate/EDINET/UploadContents.py +20 -5
- arelle/plugin/validate/EDINET/rules/gfm.py +38 -25
- arelle/plugin/validate/EDINET/rules/upload.py +344 -57
- arelle/plugin/validate/UK/__init__.py +8 -58
- {arelle_release-2.37.50.dist-info → arelle_release-2.37.52.dist-info}/METADATA +1 -1
- {arelle_release-2.37.50.dist-info → arelle_release-2.37.52.dist-info}/RECORD +16 -16
- {arelle_release-2.37.50.dist-info → arelle_release-2.37.52.dist-info}/WHEEL +0 -0
- {arelle_release-2.37.50.dist-info → arelle_release-2.37.52.dist-info}/entry_points.txt +0 -0
- {arelle_release-2.37.50.dist-info → arelle_release-2.37.52.dist-info}/licenses/LICENSE.md +0 -0
- {arelle_release-2.37.50.dist-info → arelle_release-2.37.52.dist-info}/top_level.txt +0 -0
arelle/XbrlConst.py
CHANGED
|
@@ -22,6 +22,7 @@ _tuple = tuple # type: ignore[type-arg]
|
|
|
22
22
|
xsd = "http://www.w3.org/2001/XMLSchema"
|
|
23
23
|
qnXsdComplexType = qname("{http://www.w3.org/2001/XMLSchema}xsd:complexType")
|
|
24
24
|
qnXsdDocumentation = qname("{http://www.w3.org/2001/XMLSchema}xsd:documentation")
|
|
25
|
+
qnXsdInclude = qname("{http://www.w3.org/2001/XMLSchema}xsd:include")
|
|
25
26
|
qnXsdImport = qname("{http://www.w3.org/2001/XMLSchema}xsd:import")
|
|
26
27
|
qnXsdSchema = qname("{http://www.w3.org/2001/XMLSchema}xsd:schema")
|
|
27
28
|
qnXsdAppinfo = qname("{http://www.w3.org/2001/XMLSchema}xsd:appinfo")
|
arelle/_version.py
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
# file generated by setuptools-scm
|
|
2
2
|
# don't change, don't track in version control
|
|
3
3
|
|
|
4
|
-
__all__ = [
|
|
4
|
+
__all__ = [
|
|
5
|
+
"__version__",
|
|
6
|
+
"__version_tuple__",
|
|
7
|
+
"version",
|
|
8
|
+
"version_tuple",
|
|
9
|
+
"__commit_id__",
|
|
10
|
+
"commit_id",
|
|
11
|
+
]
|
|
5
12
|
|
|
6
13
|
TYPE_CHECKING = False
|
|
7
14
|
if TYPE_CHECKING:
|
|
@@ -9,13 +16,19 @@ if TYPE_CHECKING:
|
|
|
9
16
|
from typing import Union
|
|
10
17
|
|
|
11
18
|
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
19
|
+
COMMIT_ID = Union[str, None]
|
|
12
20
|
else:
|
|
13
21
|
VERSION_TUPLE = object
|
|
22
|
+
COMMIT_ID = object
|
|
14
23
|
|
|
15
24
|
version: str
|
|
16
25
|
__version__: str
|
|
17
26
|
__version_tuple__: VERSION_TUPLE
|
|
18
27
|
version_tuple: VERSION_TUPLE
|
|
28
|
+
commit_id: COMMIT_ID
|
|
29
|
+
__commit_id__: COMMIT_ID
|
|
19
30
|
|
|
20
|
-
__version__ = version = '2.37.
|
|
21
|
-
__version_tuple__ = version_tuple = (2, 37,
|
|
31
|
+
__version__ = version = '2.37.52'
|
|
32
|
+
__version_tuple__ = version_tuple = (2, 37, 52)
|
|
33
|
+
|
|
34
|
+
__commit_id__ = commit_id = None
|
|
@@ -37,3 +37,5 @@ qnEdinetManifestList = qname("{http://disclosure.edinet-fsa.go.jp/2013/manifest}
|
|
|
37
37
|
qnEdinetManifestTitle = qname("{http://disclosure.edinet-fsa.go.jp/2013/manifest}title")
|
|
38
38
|
qnEdinetManifestTocComposition = qname("{http://disclosure.edinet-fsa.go.jp/2013/manifest}tocComposition")
|
|
39
39
|
xhtmlDtdExtension = "xhtml1-strict-ix.dtd"
|
|
40
|
+
|
|
41
|
+
COVER_PAGE_FILENAME_PREFIX = "0000000_header_"
|
|
@@ -12,11 +12,11 @@ from typing import TYPE_CHECKING
|
|
|
12
12
|
|
|
13
13
|
from arelle.Cntlr import Cntlr
|
|
14
14
|
from arelle.FileSource import FileSource
|
|
15
|
-
from arelle.ModelXbrl import ModelXbrl
|
|
16
15
|
from arelle.typing import TypeGetText
|
|
17
16
|
from arelle.utils.PluginData import PluginData
|
|
18
|
-
from .
|
|
19
|
-
from .
|
|
17
|
+
from . import Constants
|
|
18
|
+
from .ReportFolderType import ReportFolderType
|
|
19
|
+
from .UploadContents import UploadContents, UploadPathInfo
|
|
20
20
|
|
|
21
21
|
if TYPE_CHECKING:
|
|
22
22
|
from .ManifestInstance import ManifestInstance
|
|
@@ -50,35 +50,46 @@ class ControllerPluginData(PluginData):
|
|
|
50
50
|
@lru_cache(1)
|
|
51
51
|
def getUploadContents(self, fileSource: FileSource) -> UploadContents:
|
|
52
52
|
uploadFilepaths = self.getUploadFilepaths(fileSource)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
directories = []
|
|
56
|
-
forms = defaultdict(list)
|
|
53
|
+
reports = defaultdict(list)
|
|
54
|
+
uploadPaths = {}
|
|
57
55
|
for path in uploadFilepaths:
|
|
58
|
-
|
|
59
|
-
if len(parents) == 0:
|
|
60
|
-
continue
|
|
61
|
-
if parents[0] == 'XBRL':
|
|
62
|
-
if len(parents) > 1:
|
|
63
|
-
formName = parents[1]
|
|
64
|
-
instanceType = InstanceType.parse(formName)
|
|
65
|
-
if instanceType is not None:
|
|
66
|
-
forms[instanceType].append(path)
|
|
67
|
-
continue
|
|
68
|
-
formName = parents[0]
|
|
69
|
-
instanceType = InstanceType.parse(formName)
|
|
70
|
-
if instanceType is not None:
|
|
71
|
-
amendmentPaths[instanceType].append(path)
|
|
56
|
+
if len(path.parts) == 0:
|
|
72
57
|
continue
|
|
73
|
-
if len(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
58
|
+
parents = list(reversed([p.name for p in path.parents if len(p.name) > 0]))
|
|
59
|
+
reportFolderType = None
|
|
60
|
+
isCorrection = True
|
|
61
|
+
isDirectory = len(path.suffix) == 0
|
|
62
|
+
isInSubdirectory = False
|
|
63
|
+
reportPath = None
|
|
64
|
+
if len(parents) > 0:
|
|
65
|
+
isCorrection = parents[0] != 'XBRL'
|
|
66
|
+
if not isCorrection:
|
|
67
|
+
if len(parents) > 1:
|
|
68
|
+
formName = parents[1]
|
|
69
|
+
isInSubdirectory = len(parents) > 2
|
|
70
|
+
reportFolderType = ReportFolderType.parse(formName)
|
|
71
|
+
if reportFolderType is None:
|
|
72
|
+
formName = parents[0]
|
|
73
|
+
isInSubdirectory = len(parents) > 1
|
|
74
|
+
reportFolderType = ReportFolderType.parse(formName)
|
|
75
|
+
if reportFolderType is not None:
|
|
76
|
+
reportPath = Path(reportFolderType.value) if isCorrection else Path("XBRL") / reportFolderType.value
|
|
77
|
+
if not isCorrection:
|
|
78
|
+
reports[reportFolderType].append(path)
|
|
79
|
+
uploadPaths[path] = UploadPathInfo(
|
|
80
|
+
isAttachment=reportFolderType is not None and reportFolderType.isAttachment,
|
|
81
|
+
isCorrection=isCorrection,
|
|
82
|
+
isCoverPage=not isDirectory and path.stem.startswith(Constants.COVER_PAGE_FILENAME_PREFIX),
|
|
83
|
+
isDirectory=len(path.suffix) == 0,
|
|
84
|
+
isRoot=len(path.parts) == 1,
|
|
85
|
+
isSubdirectory=isInSubdirectory or (isDirectory and reportFolderType is not None),
|
|
86
|
+
path=path,
|
|
87
|
+
reportFolderType=reportFolderType,
|
|
88
|
+
reportPath=reportPath,
|
|
89
|
+
)
|
|
77
90
|
return UploadContents(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
instances={k: frozenset(v) for k, v in forms.items() if len(v) > 0},
|
|
81
|
-
unknownPaths=frozenset(unknownPaths)
|
|
91
|
+
reports={k: frozenset(v) for k, v in reports.items() if len(v) > 0},
|
|
92
|
+
uploadPaths=uploadPaths
|
|
82
93
|
)
|
|
83
94
|
|
|
84
95
|
@lru_cache(1)
|
|
@@ -7,15 +7,17 @@ from collections import defaultdict
|
|
|
7
7
|
from dataclasses import dataclass
|
|
8
8
|
from decimal import Decimal
|
|
9
9
|
from functools import lru_cache
|
|
10
|
+
from lxml.etree import DTD, XML
|
|
10
11
|
from operator import attrgetter
|
|
11
12
|
from typing import Callable, Hashable, Iterable, cast
|
|
12
13
|
|
|
14
|
+
import os
|
|
13
15
|
import regex
|
|
14
16
|
|
|
15
17
|
from arelle.LinkbaseType import LinkbaseType
|
|
16
18
|
from arelle.ModelDocument import Type as ModelDocumentType
|
|
17
19
|
from arelle.ModelDtsObject import ModelConcept
|
|
18
|
-
from arelle.ModelInstanceObject import ModelFact, ModelUnit, ModelContext
|
|
20
|
+
from arelle.ModelInstanceObject import ModelFact, ModelUnit, ModelContext, ModelInlineFact
|
|
19
21
|
from arelle.ModelObject import ModelObject
|
|
20
22
|
from arelle.ModelValue import QName, qname
|
|
21
23
|
from arelle.ModelXbrl import ModelXbrl
|
|
@@ -24,7 +26,7 @@ from arelle.ValidateDuplicateFacts import getDeduplicatedFacts, DeduplicationTyp
|
|
|
24
26
|
from arelle.XmlValidate import VALID
|
|
25
27
|
from arelle.typing import TypeGetText
|
|
26
28
|
from arelle.utils.PluginData import PluginData
|
|
27
|
-
from .Constants import CORPORATE_FORMS, FormType
|
|
29
|
+
from .Constants import CORPORATE_FORMS, FormType, xhtmlDtdExtension
|
|
28
30
|
from .ControllerPluginData import ControllerPluginData
|
|
29
31
|
from .ManifestInstance import ManifestInstance
|
|
30
32
|
from .Statement import Statement, STATEMENTS, BalanceSheet, StatementInstance, StatementType
|
|
@@ -166,6 +168,22 @@ class PluginValidationDataExtension(PluginData):
|
|
|
166
168
|
)
|
|
167
169
|
return balanceSheets
|
|
168
170
|
|
|
171
|
+
def getProblematicTextBlocks(self, modelXbrl: ModelXbrl) -> list[ModelInlineFact]:
|
|
172
|
+
problematicTextBlocks: list[ModelInlineFact] = []
|
|
173
|
+
dtd = DTD(os.path.join(modelXbrl.modelManager.cntlr.configDir, xhtmlDtdExtension))
|
|
174
|
+
htmlBodyTemplate = "<body><div>\n{0}\n</div></body>\n"
|
|
175
|
+
for fact in modelXbrl.facts:
|
|
176
|
+
concept = fact.concept
|
|
177
|
+
if isinstance(fact, ModelInlineFact) and not fact.isNil and concept is not None and concept.isTextBlock and not fact.isEscaped:
|
|
178
|
+
xmlBody = htmlBodyTemplate.format(fact.value)
|
|
179
|
+
try:
|
|
180
|
+
textblockXml = XML(xmlBody)
|
|
181
|
+
if not dtd.validate(textblockXml):
|
|
182
|
+
problematicTextBlocks.append(fact)
|
|
183
|
+
except Exception:
|
|
184
|
+
problematicTextBlocks.append(fact)
|
|
185
|
+
return problematicTextBlocks
|
|
186
|
+
|
|
169
187
|
@lru_cache(1)
|
|
170
188
|
def getStatementInstance(self, modelXbrl: ModelXbrl, statement: Statement) -> StatementInstance | None:
|
|
171
189
|
if statement.roleUri not in modelXbrl.roleTypes:
|
|
@@ -242,7 +260,7 @@ class PluginValidationDataExtension(PluginData):
|
|
|
242
260
|
return controllerPluginData.matchManifestInstance(modelXbrl.ixdsDocUrls)
|
|
243
261
|
|
|
244
262
|
def hasValidNonNilFact(self, modelXbrl: ModelXbrl, qname: QName) -> bool:
|
|
245
|
-
return any(
|
|
263
|
+
return any(True for fact in self.iterValidNonNilFacts(modelXbrl, qname))
|
|
246
264
|
|
|
247
265
|
def isStandardTaxonomyUrl(self, uri: str, modelXbrl: ModelXbrl) -> bool:
|
|
248
266
|
return modelXbrl.modelManager.disclosureSystem.hrefValidForDisclosureSystem(uri)
|
|
@@ -8,16 +8,15 @@ from functools import cached_property, lru_cache
|
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
class
|
|
11
|
+
class ReportFolderType(Enum):
|
|
12
12
|
ATTACH_DOC = "AttachDoc"
|
|
13
13
|
AUDIT_DOC = "AuditDoc"
|
|
14
|
-
ENGLISH_DOC = "EnglishDoc"
|
|
15
14
|
PRIVATE_ATTACH = "PrivateAttach"
|
|
16
15
|
PRIVATE_DOC = "PrivateDoc"
|
|
17
16
|
PUBLIC_DOC = "PublicDoc"
|
|
18
17
|
|
|
19
18
|
@classmethod
|
|
20
|
-
def parse(cls, value: str) ->
|
|
19
|
+
def parse(cls, value: str) -> ReportFolderType | None:
|
|
21
20
|
try:
|
|
22
21
|
return cls(value)
|
|
23
22
|
except ValueError:
|
|
@@ -27,6 +26,10 @@ class InstanceType(Enum):
|
|
|
27
26
|
def extensionCategory(self) -> ExtensionCategory | None:
|
|
28
27
|
return FORM_TYPE_EXTENSION_CATEGORIES.get(self, None)
|
|
29
28
|
|
|
29
|
+
@cached_property
|
|
30
|
+
def isAttachment(self) -> bool:
|
|
31
|
+
return "Attach" in self.value
|
|
32
|
+
|
|
30
33
|
@cached_property
|
|
31
34
|
def manifestName(self) -> str:
|
|
32
35
|
return f'manifest_{self.value}.xml'
|
|
@@ -49,7 +52,6 @@ class InstanceType(Enum):
|
|
|
49
52
|
class ExtensionCategory(Enum):
|
|
50
53
|
ATTACH = 'ATTACH'
|
|
51
54
|
DOC = 'DOC'
|
|
52
|
-
ENGLISH_DOC = 'ENGLISH_DOC'
|
|
53
55
|
|
|
54
56
|
def getValidExtensions(self, isAmendment: bool, isSubdirectory: bool) -> frozenset[str] | None:
|
|
55
57
|
amendmentMap = VALID_EXTENSIONS[isAmendment]
|
|
@@ -60,12 +62,11 @@ class ExtensionCategory(Enum):
|
|
|
60
62
|
|
|
61
63
|
|
|
62
64
|
FORM_TYPE_EXTENSION_CATEGORIES = {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
InstanceType.PUBLIC_DOC: ExtensionCategory.DOC,
|
|
65
|
+
ReportFolderType.ATTACH_DOC: ExtensionCategory.ATTACH,
|
|
66
|
+
ReportFolderType.AUDIT_DOC: ExtensionCategory.DOC,
|
|
67
|
+
ReportFolderType.PRIVATE_ATTACH: ExtensionCategory.ATTACH,
|
|
68
|
+
ReportFolderType.PRIVATE_DOC: ExtensionCategory.DOC,
|
|
69
|
+
ReportFolderType.PUBLIC_DOC: ExtensionCategory.DOC,
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
|
|
@@ -74,7 +75,6 @@ IMAGE_EXTENSIONS = frozenset({'.jpeg', '.jpg', '.gif', '.png'})
|
|
|
74
75
|
ASSET_EXTENSIONS = frozenset(HTML_EXTENSIONS | IMAGE_EXTENSIONS)
|
|
75
76
|
XBRL_EXTENSIONS = frozenset(HTML_EXTENSIONS | {'.xml', '.xsd'})
|
|
76
77
|
ATTACH_EXTENSIONS = frozenset(HTML_EXTENSIONS | {'.pdf', })
|
|
77
|
-
ENGLISH_DOC_EXTENSIONS = frozenset(ASSET_EXTENSIONS | frozenset({'.pdf', '.xml', '.txt'}))
|
|
78
78
|
|
|
79
79
|
# Is Amendment -> Category -> Is Subdirectory
|
|
80
80
|
VALID_EXTENSIONS = {
|
|
@@ -97,9 +97,5 @@ VALID_EXTENSIONS = {
|
|
|
97
97
|
False: HTML_EXTENSIONS,
|
|
98
98
|
True: ASSET_EXTENSIONS,
|
|
99
99
|
},
|
|
100
|
-
ExtensionCategory.ENGLISH_DOC: {
|
|
101
|
-
False: ENGLISH_DOC_EXTENSIONS,
|
|
102
|
-
True: ENGLISH_DOC_EXTENSIONS,
|
|
103
|
-
},
|
|
104
100
|
},
|
|
105
101
|
}
|
|
@@ -6,12 +6,27 @@ from __future__ import annotations
|
|
|
6
6
|
from dataclasses import dataclass
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
|
|
9
|
-
from .
|
|
9
|
+
from .ReportFolderType import ReportFolderType
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
@dataclass(frozen=True)
|
|
13
13
|
class UploadContents:
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
reports: dict[ReportFolderType, frozenset[Path]]
|
|
15
|
+
uploadPaths: dict[Path, UploadPathInfo]
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def sortedPaths(self) -> list[Path]:
|
|
19
|
+
return sorted(self.uploadPaths.keys())
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True)
|
|
23
|
+
class UploadPathInfo:
|
|
24
|
+
isAttachment: bool
|
|
25
|
+
isCorrection: bool
|
|
26
|
+
isCoverPage: bool
|
|
27
|
+
isDirectory: bool
|
|
28
|
+
isRoot: bool
|
|
29
|
+
isSubdirectory: bool
|
|
30
|
+
path: Path
|
|
31
|
+
reportFolderType: ReportFolderType | None
|
|
32
|
+
reportPath: Path | None
|
|
@@ -3,10 +3,8 @@ See COPYRIGHT.md for copyright information.
|
|
|
3
3
|
"""
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
|
-
import os
|
|
7
6
|
from collections import defaultdict
|
|
8
7
|
from datetime import timedelta
|
|
9
|
-
from lxml.etree import XML, DTD
|
|
10
8
|
from typing import Any, cast, Iterable
|
|
11
9
|
|
|
12
10
|
import regex
|
|
@@ -30,7 +28,7 @@ from arelle.utils.validate.Validation import Validation
|
|
|
30
28
|
from arelle.utils.validate.ValidationUtil import etreeIterWithDepth
|
|
31
29
|
from ..DisclosureSystems import (DISCLOSURE_SYSTEM_EDINET)
|
|
32
30
|
from ..PluginValidationDataExtension import PluginValidationDataExtension
|
|
33
|
-
|
|
31
|
+
|
|
34
32
|
|
|
35
33
|
_: TypeGetText
|
|
36
34
|
|
|
@@ -368,28 +366,15 @@ def rule_gfm_1_2_14(
|
|
|
368
366
|
(a format that conforms to XML grammar, such as all start and end tags being paired, and the end tag of a nested tag not coming after the end tag of its parent tag, etc.).
|
|
369
367
|
Please modify it so that it is well-formed.
|
|
370
368
|
"""
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
concept = fact.concept
|
|
381
|
-
if not fact.isNil and concept is not None and concept.isTextBlock and XMLpattern.match(fact.value):
|
|
382
|
-
for xmlText in [fact.value] + CDATApattern.findall(fact.value):
|
|
383
|
-
xmlBodyWithoutEntities = htmlBodyTemplate.format(namedEntityPattern.sub("", xmlText).replace('&','&'))
|
|
384
|
-
textblockXml = XML(xmlBodyWithoutEntities)
|
|
385
|
-
if not dtd.validate(textblockXml):
|
|
386
|
-
yield Validation.warning(
|
|
387
|
-
codes='EDINET.EC5700W.GFM.1.2.14',
|
|
388
|
-
msg=_('The content of an element with a data type of nonnum:textBlockItemType is not well-formed XML (a format that conforms to XML grammar, '
|
|
389
|
-
'such as all start and end tags being in pairs, and the end tag of a nested tag not coming after the end tag of its parent tag). '
|
|
390
|
-
'Correct the content so that it is well-formed.'),
|
|
391
|
-
modelObject = fact
|
|
392
|
-
)
|
|
369
|
+
problematicFacts = pluginData.getProblematicTextBlocks(val.modelXbrl)
|
|
370
|
+
if len(problematicFacts) > 0:
|
|
371
|
+
yield Validation.warning(
|
|
372
|
+
codes='EDINET.EC5700W.GFM.1.2.14',
|
|
373
|
+
msg=_('The content of an element with a data type of nonnum:textBlockItemType is not well-formed XML (a format that conforms to XML grammar, '
|
|
374
|
+
'such as all start and end tags being in pairs, and the end tag of a nested tag not coming after the end tag of its parent tag). '
|
|
375
|
+
'Correct the content so that it is well-formed.'),
|
|
376
|
+
modelObject = problematicFacts
|
|
377
|
+
)
|
|
393
378
|
|
|
394
379
|
|
|
395
380
|
@validation(
|
|
@@ -628,3 +613,31 @@ def rule_gfm_1_2_30(
|
|
|
628
613
|
msg=_("A context must not contain the xbrli:forever element."),
|
|
629
614
|
modelObject=errors
|
|
630
615
|
)
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
@validation(
|
|
619
|
+
hook=ValidationHook.XBRL_FINALLY,
|
|
620
|
+
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
621
|
+
)
|
|
622
|
+
def rule_gfm_1_3_1(
|
|
623
|
+
pluginData: PluginValidationDataExtension,
|
|
624
|
+
val: ValidateXbrl,
|
|
625
|
+
*args: Any,
|
|
626
|
+
**kwargs: Any,
|
|
627
|
+
) -> Iterable[Validation]:
|
|
628
|
+
"""
|
|
629
|
+
EDINET.EC5700W: [GFM 1.3.1] The submitter-specific taxonomy contains include elements.
|
|
630
|
+
"""
|
|
631
|
+
warnings = []
|
|
632
|
+
for modelDocument in val.modelXbrl.urlDocs.values():
|
|
633
|
+
if pluginData.isStandardTaxonomyUrl(modelDocument.uri, val.modelXbrl):
|
|
634
|
+
continue
|
|
635
|
+
rootElt = modelDocument.xmlRootElement
|
|
636
|
+
for elt in rootElt.iterdescendants(XbrlConst.qnXsdInclude.clarkNotation):
|
|
637
|
+
warnings.append(elt)
|
|
638
|
+
if len(warnings) > 0:
|
|
639
|
+
yield Validation.warning(
|
|
640
|
+
codes='EDINET.EC5700W.GFM.1.3.1',
|
|
641
|
+
msg=_("The submitter-specific taxonomy contains include elements."),
|
|
642
|
+
modelObject=warnings
|
|
643
|
+
)
|
|
@@ -8,6 +8,8 @@ from collections import defaultdict
|
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
from typing import Any, Iterable, TYPE_CHECKING
|
|
10
10
|
|
|
11
|
+
import regex
|
|
12
|
+
|
|
11
13
|
from arelle.Cntlr import Cntlr
|
|
12
14
|
from arelle.FileSource import FileSource
|
|
13
15
|
from arelle.ValidateXbrl import ValidateXbrl
|
|
@@ -15,8 +17,9 @@ from arelle.typing import TypeGetText
|
|
|
15
17
|
from arelle.utils.PluginHooks import ValidationHook
|
|
16
18
|
from arelle.utils.validate.Decorator import validation
|
|
17
19
|
from arelle.utils.validate.Validation import Validation
|
|
20
|
+
from .. import Constants
|
|
18
21
|
from ..DisclosureSystems import (DISCLOSURE_SYSTEM_EDINET)
|
|
19
|
-
from ..
|
|
22
|
+
from ..ReportFolderType import ReportFolderType, HTML_EXTENSIONS, IMAGE_EXTENSIONS
|
|
20
23
|
from ..PluginValidationDataExtension import PluginValidationDataExtension
|
|
21
24
|
|
|
22
25
|
if TYPE_CHECKING:
|
|
@@ -24,6 +27,16 @@ if TYPE_CHECKING:
|
|
|
24
27
|
|
|
25
28
|
_: TypeGetText
|
|
26
29
|
|
|
30
|
+
ALLOWED_ROOT_FOLDERS = {
|
|
31
|
+
"AttachDoc",
|
|
32
|
+
"AuditDoc",
|
|
33
|
+
"PrivateAttach",
|
|
34
|
+
"PrivateDoc",
|
|
35
|
+
"PublicAttach",
|
|
36
|
+
"PublicDoc",
|
|
37
|
+
"XBRL",
|
|
38
|
+
}
|
|
39
|
+
|
|
27
40
|
FILE_COUNT_LIMITS = {
|
|
28
41
|
Path("AttachDoc"): 990,
|
|
29
42
|
Path("AuditDoc"): 990,
|
|
@@ -38,12 +51,54 @@ FILE_COUNT_LIMITS = {
|
|
|
38
51
|
|
|
39
52
|
FILENAME_STEM_PATTERN = re.compile(r'[a-zA-Z0-9_-]*')
|
|
40
53
|
|
|
54
|
+
PATTERN_CODE = r'(?P<code>[A-Za-z\d]*)'
|
|
55
|
+
PATTERN_CONSOLIDATED = r'(?P<consolidated>c|n)'
|
|
56
|
+
PATTERN_COUNT = r'(?P<count>\d{2})'
|
|
57
|
+
PATTERN_DATE1 = r'(?P<year1>\d{4})-(?P<month1>\d{2})-(?P<day1>\d{2})'
|
|
58
|
+
PATTERN_DATE2 = r'(?P<year2>\d{4})-(?P<month2>\d{2})-(?P<day2>\d{2})'
|
|
59
|
+
PATTERN_FORM = r'(?P<form>\d{6})'
|
|
60
|
+
PATTERN_LINKBASE = r'(?P<linkbase>lab|lab-en|gla|pre|def|cal)'
|
|
61
|
+
PATTERN_MAIN = r'(?P<main>\d{7})'
|
|
62
|
+
PATTERN_NAME = r'(?P<name>[a-z]{6})'
|
|
63
|
+
PATTERN_ORDINANCE = r'(?P<ordinance>[a-z]*)'
|
|
64
|
+
PATTERN_PERIOD = r'(?P<period>c|p)' # TODO: Have only seen "c" in sample/public filings, assuming "p" for previous.
|
|
65
|
+
PATTERN_REPORT = r'(?P<report>[a-z]*)'
|
|
66
|
+
PATTERN_REPORT_SERIAL = r'(?P<report_serial>\d{3})'
|
|
67
|
+
PATTERN_SERIAL = r'(?P<serial>\d{3})'
|
|
68
|
+
|
|
69
|
+
PATTERN_AUDIT_REPORT_PREFIX = rf'jpaud-{PATTERN_REPORT}-{PATTERN_PERIOD}{PATTERN_CONSOLIDATED}'
|
|
70
|
+
PATTERN_REPORT_PREFIX = rf'jp{PATTERN_ORDINANCE}{PATTERN_FORM}-{PATTERN_REPORT}'
|
|
71
|
+
PATTERN_SUFFIX = rf'{PATTERN_REPORT_SERIAL}_{PATTERN_CODE}-{PATTERN_SERIAL}_{PATTERN_DATE1}_{PATTERN_COUNT}_{PATTERN_DATE2}'
|
|
72
|
+
|
|
73
|
+
PATTERNS = list(regex.compile(p) for p in (
|
|
74
|
+
# Schema file for report
|
|
75
|
+
# Example: jpcrp050300-esr-001_X99007-000_2025-04-10_01_2025-04-10.xsd
|
|
76
|
+
rf'{PATTERN_REPORT_PREFIX}-{PATTERN_SUFFIX}.xsd',
|
|
77
|
+
# Schema file for audit report
|
|
78
|
+
# Example: jpaud-aar-cn-001_X99001-000_2025-03-31_01_2025-06-28.xsd
|
|
79
|
+
rf'{PATTERN_AUDIT_REPORT_PREFIX}-{PATTERN_SUFFIX}.xsd',
|
|
80
|
+
# Linkbase file for report
|
|
81
|
+
# Example: jpcrp020000-srs-001_X99001-000_2025-03-31_01_2025-11-20_cal.xml
|
|
82
|
+
rf'{PATTERN_REPORT_PREFIX}-{PATTERN_SUFFIX}_{PATTERN_LINKBASE}.xml',
|
|
83
|
+
# Linkbase file for audit report
|
|
84
|
+
# Example: jpaud-qrr-cc-001_X99001-000_2025-03-31_01_2025-11-20_pre.xml
|
|
85
|
+
rf'{PATTERN_AUDIT_REPORT_PREFIX}-{PATTERN_SUFFIX}_{PATTERN_LINKBASE}.xml',
|
|
86
|
+
# Cover page file for report
|
|
87
|
+
# Example: 0000000_header_jpcrp020000-srs-001_X99001-000_2025-03-31_01_2025-11-20_ixbrl.htm
|
|
88
|
+
rf'{Constants.COVER_PAGE_FILENAME_PREFIX}{PATTERN_REPORT_PREFIX}-{PATTERN_SUFFIX}_ixbrl.htm',
|
|
89
|
+
# Main file for report
|
|
90
|
+
# Example: 0205020_honbun_jpcrp020000-srs-001_X99001-000_2025-03-31_01_2025-11-20_ixbrl.htm
|
|
91
|
+
rf'{PATTERN_MAIN}_{PATTERN_NAME}_{PATTERN_REPORT_PREFIX}-{PATTERN_SUFFIX}_ixbrl.htm',
|
|
92
|
+
# Main file for audit report
|
|
93
|
+
# Example: jpaud-qrr-cc-001_X99001-000_2025-03-31_01_2025-11-20_pre.xml
|
|
94
|
+
rf'{PATTERN_AUDIT_REPORT_PREFIX}-{PATTERN_SUFFIX}_ixbrl.htm',
|
|
95
|
+
))
|
|
41
96
|
|
|
42
97
|
@validation(
|
|
43
98
|
hook=ValidationHook.FILESOURCE,
|
|
44
99
|
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
45
100
|
)
|
|
46
|
-
def
|
|
101
|
+
def rule_EC0100E(
|
|
47
102
|
pluginData: ControllerPluginData,
|
|
48
103
|
cntlr: Cntlr,
|
|
49
104
|
fileSource: FileSource,
|
|
@@ -51,30 +106,32 @@ def rule_EC0121E(
|
|
|
51
106
|
**kwargs: Any,
|
|
52
107
|
) -> Iterable[Validation]:
|
|
53
108
|
"""
|
|
54
|
-
EDINET.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
109
|
+
EDINET.EC0100E: An illegal directory is found directly under the transferred directory.
|
|
110
|
+
Only the following root folders are allowed:
|
|
111
|
+
AttachDoc
|
|
112
|
+
AuditDoc*
|
|
113
|
+
PrivateAttach
|
|
114
|
+
PrivateDoc*
|
|
115
|
+
PublicAttach
|
|
116
|
+
PublicDoc*
|
|
117
|
+
XBRL
|
|
118
|
+
* Only when reporting corrections
|
|
119
|
+
|
|
120
|
+
NOTE: since we do not have access to the submission type, we can't determine if the submission is a correction or not.
|
|
121
|
+
For this implementation, we will allow all directories that may be valid for at least one submission type.
|
|
122
|
+
This allows for a false-negative outcome when a non-correction submission has a correction-only root directory.
|
|
61
123
|
"""
|
|
62
124
|
uploadContents = pluginData.getUploadContents(fileSource)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
paths.update(amendmentPaths)
|
|
66
|
-
for path in paths:
|
|
67
|
-
if len(str(path.name)) > 31 or not FILENAME_STEM_PATTERN.match(path.stem):
|
|
125
|
+
for path, pathInfo in uploadContents.uploadPaths.items():
|
|
126
|
+
if pathInfo.isRoot and path.name not in ALLOWED_ROOT_FOLDERS:
|
|
68
127
|
yield Validation.error(
|
|
69
|
-
codes='EDINET.
|
|
70
|
-
msg=_("
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
"
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
basename=path.name,
|
|
77
|
-
file=str(path)
|
|
128
|
+
codes='EDINET.EC0100E',
|
|
129
|
+
msg=_("An illegal directory is found directly under the transferred directory. "
|
|
130
|
+
"Directory name or file name: '%(rootDirectory)s'. "
|
|
131
|
+
"Delete all folders except the following folders that exist directly "
|
|
132
|
+
"under the root folder, and then upload again: %(allowedDirectories)s."),
|
|
133
|
+
rootDirectory=path.name,
|
|
134
|
+
allowedDirectories=', '.join(f"'{d}'" for d in ALLOWED_ROOT_FOLDERS)
|
|
78
135
|
)
|
|
79
136
|
|
|
80
137
|
|
|
@@ -82,7 +139,7 @@ def rule_EC0121E(
|
|
|
82
139
|
hook=ValidationHook.FILESOURCE,
|
|
83
140
|
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
84
141
|
)
|
|
85
|
-
def
|
|
142
|
+
def rule_EC0124E_EC0187E(
|
|
86
143
|
pluginData: ControllerPluginData,
|
|
87
144
|
cntlr: Cntlr,
|
|
88
145
|
fileSource: FileSource,
|
|
@@ -90,7 +147,8 @@ def rule_EC0124E(
|
|
|
90
147
|
**kwargs: Any,
|
|
91
148
|
) -> Iterable[Validation]:
|
|
92
149
|
"""
|
|
93
|
-
EDINET.EC0124E: There are no empty directories.
|
|
150
|
+
EDINET.EC0124E: There are no empty root directories.
|
|
151
|
+
EDINET.EC0187E: There are no empty subdirectories.
|
|
94
152
|
"""
|
|
95
153
|
uploadFilepaths = pluginData.getUploadFilepaths(fileSource)
|
|
96
154
|
emptyDirectories = []
|
|
@@ -98,15 +156,24 @@ def rule_EC0124E(
|
|
|
98
156
|
if path.suffix:
|
|
99
157
|
continue
|
|
100
158
|
if not any(path in p.parents for p in uploadFilepaths):
|
|
101
|
-
emptyDirectories.append(
|
|
159
|
+
emptyDirectories.append(path)
|
|
102
160
|
for emptyDirectory in emptyDirectories:
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
161
|
+
if len(emptyDirectory.parts) <= 1:
|
|
162
|
+
yield Validation.error(
|
|
163
|
+
codes='EDINET.EC0124E',
|
|
164
|
+
msg=_("There is no file directly under '%(emptyDirectory)s'. "
|
|
165
|
+
"No empty root folders. "
|
|
166
|
+
"Please store the file in the appropriate folder or delete the folder and upload again."),
|
|
167
|
+
emptyDirectory=str(emptyDirectory),
|
|
168
|
+
)
|
|
169
|
+
else:
|
|
170
|
+
yield Validation.error(
|
|
171
|
+
codes='EDINET.EC0187E',
|
|
172
|
+
msg=_("'%(parentDirectory)s' contains a subordinate directory ('%(emptyDirectory)s') with no files. "
|
|
173
|
+
"Please store the file in the corresponding subfolder or delete the subfolder and upload again."),
|
|
174
|
+
parentDirectory=str(emptyDirectory.parent),
|
|
175
|
+
emptyDirectory=str(emptyDirectory),
|
|
176
|
+
)
|
|
110
177
|
|
|
111
178
|
|
|
112
179
|
@validation(
|
|
@@ -160,22 +227,13 @@ def rule_EC0130E(
|
|
|
160
227
|
EDINET.EC0130E: File extensions must match the file extensions allowed in Figure 2-1-3 and Figure 2-1-5.
|
|
161
228
|
"""
|
|
162
229
|
uploadContents = pluginData.getUploadContents(fileSource)
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
for amendmentPath in amendmentPaths:
|
|
166
|
-
isSubdirectory = amendmentPath.parent.name != instanceType.value
|
|
167
|
-
checks.append((amendmentPath, True, instanceType, isSubdirectory))
|
|
168
|
-
for instanceType, formPaths in uploadContents.instances.items():
|
|
169
|
-
for amendmentPath in formPaths:
|
|
170
|
-
isSubdirectory = amendmentPath.parent.name != instanceType.value
|
|
171
|
-
checks.append((amendmentPath, False, instanceType, isSubdirectory))
|
|
172
|
-
for path, isAmendment, instanceType, isSubdirectory in checks:
|
|
173
|
-
ext = path.suffix
|
|
174
|
-
if len(ext) == 0:
|
|
230
|
+
for path, pathInfo in uploadContents.uploadPaths.items():
|
|
231
|
+
if pathInfo.reportFolderType is None or pathInfo.isDirectory:
|
|
175
232
|
continue
|
|
176
|
-
validExtensions =
|
|
233
|
+
validExtensions = pathInfo.reportFolderType.getValidExtensions(pathInfo.isCorrection, pathInfo.isSubdirectory)
|
|
177
234
|
if validExtensions is None:
|
|
178
235
|
continue
|
|
236
|
+
ext = path.suffix
|
|
179
237
|
if ext not in validExtensions:
|
|
180
238
|
yield Validation.error(
|
|
181
239
|
codes='EDINET.EC0130E',
|
|
@@ -207,18 +265,17 @@ def rule_EC0132E(
|
|
|
207
265
|
EDINET.EC0132E: Store the manifest file directly under the relevant folder.
|
|
208
266
|
"""
|
|
209
267
|
uploadContents = pluginData.getUploadContents(fileSource)
|
|
210
|
-
for
|
|
211
|
-
if
|
|
212
|
-
continue
|
|
213
|
-
if instanceType.manifestPath in uploadContents.instances.get(instanceType, []):
|
|
268
|
+
for reportFolderType, paths in uploadContents.reports.items():
|
|
269
|
+
if reportFolderType.isAttachment:
|
|
214
270
|
continue
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
271
|
+
if reportFolderType.manifestPath not in paths:
|
|
272
|
+
yield Validation.error(
|
|
273
|
+
codes='EDINET.EC0132E',
|
|
274
|
+
msg=_("'%(expectedManifestName)s' does not exist in '%(expectedManifestDirectory)s'. "
|
|
275
|
+
"Please store the manifest file (or cover file) directly under the relevant folder and upload it again. "),
|
|
276
|
+
expectedManifestName=reportFolderType.manifestPath.name,
|
|
277
|
+
expectedManifestDirectory=str(reportFolderType.manifestPath.parent),
|
|
278
|
+
)
|
|
222
279
|
|
|
223
280
|
|
|
224
281
|
@validation(
|
|
@@ -279,6 +336,36 @@ def rule_EC0188E(
|
|
|
279
336
|
)
|
|
280
337
|
|
|
281
338
|
|
|
339
|
+
@validation(
|
|
340
|
+
hook=ValidationHook.FILESOURCE,
|
|
341
|
+
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
342
|
+
)
|
|
343
|
+
def rule_EC0192E(
|
|
344
|
+
pluginData: ControllerPluginData,
|
|
345
|
+
cntlr: Cntlr,
|
|
346
|
+
fileSource: FileSource,
|
|
347
|
+
*args: Any,
|
|
348
|
+
**kwargs: Any,
|
|
349
|
+
) -> Iterable[Validation]:
|
|
350
|
+
"""
|
|
351
|
+
EDINET.EC0192E: The cover file for PrivateDoc cannot be set because it uses a
|
|
352
|
+
PublicDoc cover file. Please delete the cover file from PrivateDoc and upload
|
|
353
|
+
it again.
|
|
354
|
+
"""
|
|
355
|
+
uploadContents = pluginData.getUploadContents(fileSource)
|
|
356
|
+
for path, pathInfo in uploadContents.uploadPaths.items():
|
|
357
|
+
if not pathInfo.isCoverPage:
|
|
358
|
+
continue
|
|
359
|
+
# Only applies to PrivateDoc correction reports
|
|
360
|
+
if pathInfo.isCorrection and pathInfo.reportFolderType == ReportFolderType.PRIVATE_DOC:
|
|
361
|
+
yield Validation.error(
|
|
362
|
+
codes='EDINET.EC0192E',
|
|
363
|
+
msg=_("The cover file for PrivateDoc ('%(file)s') cannot be set because it uses a PublicDoc cover file. "
|
|
364
|
+
"Please delete the cover file from PrivateDoc and upload it again."),
|
|
365
|
+
file=str(path),
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
|
|
282
369
|
@validation(
|
|
283
370
|
hook=ValidationHook.FILESOURCE,
|
|
284
371
|
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
@@ -315,6 +402,82 @@ def rule_EC0198E(
|
|
|
315
402
|
)
|
|
316
403
|
|
|
317
404
|
|
|
405
|
+
@validation(
|
|
406
|
+
hook=ValidationHook.FILESOURCE,
|
|
407
|
+
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
408
|
+
)
|
|
409
|
+
def rule_EC0233E(
|
|
410
|
+
pluginData: ControllerPluginData,
|
|
411
|
+
cntlr: Cntlr,
|
|
412
|
+
fileSource: FileSource,
|
|
413
|
+
*args: Any,
|
|
414
|
+
**kwargs: Any,
|
|
415
|
+
) -> Iterable[Validation]:
|
|
416
|
+
"""
|
|
417
|
+
EDINET.EC0233E: There is a file in the report directory that comes before the cover file
|
|
418
|
+
in file name sort order.
|
|
419
|
+
|
|
420
|
+
NOTE: This includes files in subdirectories. For example, PublicDoc/00000000_images/image.png
|
|
421
|
+
comes before PublicDoc/0000000_header_*.htm
|
|
422
|
+
"""
|
|
423
|
+
uploadContents = pluginData.getUploadContents(fileSource)
|
|
424
|
+
directories = defaultdict(list)
|
|
425
|
+
for path in uploadContents.sortedPaths:
|
|
426
|
+
pathInfo = uploadContents.uploadPaths[path]
|
|
427
|
+
if pathInfo.isDirectory:
|
|
428
|
+
continue
|
|
429
|
+
if pathInfo.reportFolderType in (ReportFolderType.PRIVATE_DOC, ReportFolderType.PUBLIC_DOC):
|
|
430
|
+
directories[pathInfo.reportPath].append(pathInfo)
|
|
431
|
+
for reportPath, pathInfos in directories.items():
|
|
432
|
+
coverPagePath = next(iter(p for p in pathInfos if p.isCoverPage), None)
|
|
433
|
+
if coverPagePath is None:
|
|
434
|
+
continue
|
|
435
|
+
errorPathInfos = pathInfos[:pathInfos.index(coverPagePath)]
|
|
436
|
+
for pathInfo in errorPathInfos:
|
|
437
|
+
yield Validation.error(
|
|
438
|
+
codes='EDINET.EC0233E',
|
|
439
|
+
msg=_("There is a file in the report directory in '%(reportPath)s' that comes before the cover "
|
|
440
|
+
"file ('%(coverPage)s') in file name sort order. "
|
|
441
|
+
"Directory name or file name: '%(path)s'. "
|
|
442
|
+
"Please make sure that there are no files that come before the cover file in the file "
|
|
443
|
+
"name sort order, and then upload again."),
|
|
444
|
+
reportPath=str(reportPath),
|
|
445
|
+
coverPage=str(coverPagePath.path.name),
|
|
446
|
+
path=str(pathInfo.path),
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
@validation(
|
|
451
|
+
hook=ValidationHook.FILESOURCE,
|
|
452
|
+
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
453
|
+
)
|
|
454
|
+
def rule_EC0234E(
|
|
455
|
+
pluginData: ControllerPluginData,
|
|
456
|
+
cntlr: Cntlr,
|
|
457
|
+
fileSource: FileSource,
|
|
458
|
+
*args: Any,
|
|
459
|
+
**kwargs: Any,
|
|
460
|
+
) -> Iterable[Validation]:
|
|
461
|
+
"""
|
|
462
|
+
EDINET.EC0234E: A cover file exists in an unsupported subdirectory.
|
|
463
|
+
"""
|
|
464
|
+
uploadContents = pluginData.getUploadContents(fileSource)
|
|
465
|
+
for path, pathInfo in uploadContents.uploadPaths.items():
|
|
466
|
+
if pathInfo.isDirectory:
|
|
467
|
+
continue
|
|
468
|
+
if pathInfo.reportFolderType not in (ReportFolderType.PRIVATE_DOC, ReportFolderType.PUBLIC_DOC):
|
|
469
|
+
continue
|
|
470
|
+
if pathInfo.isSubdirectory and pathInfo.isCoverPage:
|
|
471
|
+
yield Validation.error(
|
|
472
|
+
codes='EDINET.EC0234E',
|
|
473
|
+
msg=_("A cover file ('%(coverPage)s') exists in an unsupported subdirectory. "
|
|
474
|
+
"Directory: '%(directory)s'. "
|
|
475
|
+
"Please make sure there is no cover file in the subfolder and upload again."),
|
|
476
|
+
coverPage=str(path.name),
|
|
477
|
+
directory=str(path.parent),
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
|
|
318
481
|
@validation(
|
|
319
482
|
hook=ValidationHook.FILESOURCE,
|
|
320
483
|
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
@@ -370,6 +533,75 @@ def rule_EC0206E(
|
|
|
370
533
|
)
|
|
371
534
|
|
|
372
535
|
|
|
536
|
+
@validation(
|
|
537
|
+
hook=ValidationHook.FILESOURCE,
|
|
538
|
+
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
539
|
+
)
|
|
540
|
+
def rule_EC0349E(
|
|
541
|
+
pluginData: ControllerPluginData,
|
|
542
|
+
cntlr: Cntlr,
|
|
543
|
+
fileSource: FileSource,
|
|
544
|
+
*args: Any,
|
|
545
|
+
**kwargs: Any,
|
|
546
|
+
) -> Iterable[Validation]:
|
|
547
|
+
"""
|
|
548
|
+
EDINET.EC0349E: An unexpected directory or file exists in the XBRL directory.
|
|
549
|
+
Only PublicDoc, PrivateDoc, or AuditDoc directories may exist beneath the XBRL directory.
|
|
550
|
+
"""
|
|
551
|
+
uploadContent = pluginData.getUploadContents(fileSource)
|
|
552
|
+
xbrlDirectoryPath = Path('XBRL')
|
|
553
|
+
allowedPaths = {p.xbrlDirectory for p in (
|
|
554
|
+
ReportFolderType.AUDIT_DOC,
|
|
555
|
+
ReportFolderType.PRIVATE_DOC,
|
|
556
|
+
ReportFolderType.PUBLIC_DOC,
|
|
557
|
+
)}
|
|
558
|
+
for path, pathInfo in uploadContent.uploadPaths.items():
|
|
559
|
+
if path.parent != xbrlDirectoryPath:
|
|
560
|
+
continue
|
|
561
|
+
if path not in allowedPaths:
|
|
562
|
+
if not any(pattern.fullmatch(path.name) for pattern in PATTERNS):
|
|
563
|
+
yield Validation.error(
|
|
564
|
+
codes='EDINET.EC0349E',
|
|
565
|
+
msg=_("An unexpected directory or file exists in the XBRL directory. "
|
|
566
|
+
"Directory or file name: '%(file)s'."),
|
|
567
|
+
file=path.name,
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
@validation(
|
|
572
|
+
hook=ValidationHook.FILESOURCE,
|
|
573
|
+
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
574
|
+
)
|
|
575
|
+
def rule_EC0352E(
|
|
576
|
+
pluginData: ControllerPluginData,
|
|
577
|
+
cntlr: Cntlr,
|
|
578
|
+
fileSource: FileSource,
|
|
579
|
+
*args: Any,
|
|
580
|
+
**kwargs: Any,
|
|
581
|
+
) -> Iterable[Validation]:
|
|
582
|
+
"""
|
|
583
|
+
EDINET.EC0352E: An XBRL file with an invalid name exists.
|
|
584
|
+
"""
|
|
585
|
+
uploadContent = pluginData.getUploadContents(fileSource)
|
|
586
|
+
for path, pathInfo in uploadContent.uploadPaths.items():
|
|
587
|
+
if (
|
|
588
|
+
pathInfo.isDirectory or
|
|
589
|
+
pathInfo.isCorrection or
|
|
590
|
+
pathInfo.isSubdirectory or
|
|
591
|
+
pathInfo.isAttachment or
|
|
592
|
+
pathInfo.reportFolderType is None or
|
|
593
|
+
any(path == t.manifestPath for t in ReportFolderType)
|
|
594
|
+
):
|
|
595
|
+
continue
|
|
596
|
+
if not any(pattern.fullmatch(path.name) for pattern in PATTERNS):
|
|
597
|
+
yield Validation.error(
|
|
598
|
+
codes='EDINET.EC0352E',
|
|
599
|
+
msg=_("A file with an invalid name exists. "
|
|
600
|
+
"File path: '%(path)s'."),
|
|
601
|
+
path=str(path),
|
|
602
|
+
)
|
|
603
|
+
|
|
604
|
+
|
|
373
605
|
@validation(
|
|
374
606
|
hook=ValidationHook.FILESOURCE,
|
|
375
607
|
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
@@ -444,6 +676,61 @@ def rule_EC1020E(
|
|
|
444
676
|
)
|
|
445
677
|
|
|
446
678
|
|
|
679
|
+
@validation(
|
|
680
|
+
hook=ValidationHook.FILESOURCE,
|
|
681
|
+
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
682
|
+
)
|
|
683
|
+
def rule_filenames(
|
|
684
|
+
pluginData: ControllerPluginData,
|
|
685
|
+
cntlr: Cntlr,
|
|
686
|
+
fileSource: FileSource,
|
|
687
|
+
*args: Any,
|
|
688
|
+
**kwargs: Any,
|
|
689
|
+
) -> Iterable[Validation]:
|
|
690
|
+
"""
|
|
691
|
+
EDINET.EC0121E: There is a directory or file that contains
|
|
692
|
+
more than 31 characters or uses characters other than those allowed (alphanumeric characters,
|
|
693
|
+
'-' and '_').
|
|
694
|
+
Note: Applies to everything EXCEPT files directly beneath non-correction report folders.
|
|
695
|
+
|
|
696
|
+
EDINET.EC0200E: There is a file that uses characters other
|
|
697
|
+
than those allowed (alphanumeric characters, '-' and '_').
|
|
698
|
+
Note: Applies ONLY to files directly beneath non-correction report folders.
|
|
699
|
+
"""
|
|
700
|
+
for path, pathInfo in pluginData.getUploadContents(fileSource).uploadPaths.items():
|
|
701
|
+
isReportFile = (
|
|
702
|
+
not pathInfo.isAttachment and
|
|
703
|
+
not pathInfo.isCorrection and
|
|
704
|
+
not pathInfo.isDirectory and
|
|
705
|
+
not pathInfo.isSubdirectory
|
|
706
|
+
)
|
|
707
|
+
charactersAreValid = FILENAME_STEM_PATTERN.fullmatch(path.stem)
|
|
708
|
+
lengthIsValid = isReportFile or (len(path.name) <= 31)
|
|
709
|
+
if charactersAreValid and lengthIsValid:
|
|
710
|
+
continue
|
|
711
|
+
if isReportFile:
|
|
712
|
+
yield Validation.error(
|
|
713
|
+
codes='EDINET.EC0200E',
|
|
714
|
+
msg=_("There is a file inside the XBRL directory that uses characters "
|
|
715
|
+
"other than those allowed (alphanumeric characters, '-' and '_'). "
|
|
716
|
+
"File: '%(path)s'. "
|
|
717
|
+
"Please change the filename to usable characters, and upload again."),
|
|
718
|
+
path=str(path)
|
|
719
|
+
)
|
|
720
|
+
else:
|
|
721
|
+
yield Validation.error(
|
|
722
|
+
codes='EDINET.EC0121E',
|
|
723
|
+
msg=_("There is a directory or file in '%(directory)s' that contains more "
|
|
724
|
+
"than 31 characters or uses characters other than those allowed "
|
|
725
|
+
"(alphanumeric characters, '-' and '_'). "
|
|
726
|
+
"Directory or filename: '%(basename)s'. "
|
|
727
|
+
"Please change the file name (or folder name) to within 31 characters and to usable "
|
|
728
|
+
"characters, and upload again."),
|
|
729
|
+
directory=str(path.parent),
|
|
730
|
+
basename=path.name,
|
|
731
|
+
)
|
|
732
|
+
|
|
733
|
+
|
|
447
734
|
@validation(
|
|
448
735
|
hook=ValidationHook.FILESOURCE,
|
|
449
736
|
disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
|
|
@@ -8,10 +8,10 @@ References:
|
|
|
8
8
|
- [HMRC CT Inline XBRL Style Guide](https://www.gov.uk/government/uploads/system/uploads/attachment_data/file/434588/xbrl-style-guide.pdf)
|
|
9
9
|
"""
|
|
10
10
|
import os
|
|
11
|
-
from math import isnan
|
|
12
11
|
from arelle import ModelDocument, XmlUtil
|
|
13
12
|
from arelle.ModelValue import qname, dateTime, DATE
|
|
14
|
-
from arelle.
|
|
13
|
+
from arelle.ValidateDuplicateFacts import getDuplicateFactSets
|
|
14
|
+
from arelle.ValidateXbrlCalcs import insignificantDigits
|
|
15
15
|
from arelle.Version import authorLabel, copyrightLabel
|
|
16
16
|
from arelle.XbrlConst import xbrli, qnXbrliXbrl
|
|
17
17
|
import regex as re
|
|
@@ -374,62 +374,12 @@ def validateXbrlFinally(val, *args, **kwargs):
|
|
|
374
374
|
_("Generic dimension members have no associated name or description item, member names (name or description item): %(memberNames)s"),
|
|
375
375
|
modelObject=modelXbrl, memberNames=", ".join(sorted(memLocalNamesMissing)))
|
|
376
376
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
if getattr(f,"xValid", 0) >= 4:
|
|
384
|
-
cuDict = aspectEqualFacts[(f.qname,
|
|
385
|
-
(f.xmlLang or "").lower() if f.concept.type.isWgnStringFactType else None)]
|
|
386
|
-
_matched = False
|
|
387
|
-
for (_cntx,_unit),fList in cuDict.items():
|
|
388
|
-
if (((_cntx is None and f.context is None) or (f.context is not None and f.context.isEqualTo(_cntx))) and
|
|
389
|
-
((_unit is None and f.unit is None) or (f.unit is not None and f.unit.isEqualTo(_unit)))):
|
|
390
|
-
_matched = True
|
|
391
|
-
fList.append(f)
|
|
392
|
-
break
|
|
393
|
-
if not _matched:
|
|
394
|
-
cuDict[(f.context,f.unit)] = [f]
|
|
395
|
-
decVals = {}
|
|
396
|
-
for cuDict in aspectEqualFacts.values(): # dups by qname, lang
|
|
397
|
-
for fList in cuDict.values(): # dups by equal-context equal-unit
|
|
398
|
-
if len(fList) > 1:
|
|
399
|
-
f0 = fList[0]
|
|
400
|
-
if f0.concept.isNumeric:
|
|
401
|
-
if any(f.isNil for f in fList):
|
|
402
|
-
_inConsistent = not all(f.isNil for f in fList)
|
|
403
|
-
else: # not all have same decimals
|
|
404
|
-
_d = inferredDecimals(f0)
|
|
405
|
-
_v = f0.xValue
|
|
406
|
-
_inConsistent = isnan(_v) # NaN is incomparable, always makes dups inconsistent
|
|
407
|
-
decVals[_d] = _v
|
|
408
|
-
aMax, bMin, _inclA, _inclB = rangeValue(_v, _d)
|
|
409
|
-
for f in fList[1:]:
|
|
410
|
-
_d = inferredDecimals(f)
|
|
411
|
-
_v = f.xValue
|
|
412
|
-
if isnan(_v):
|
|
413
|
-
_inConsistent = True
|
|
414
|
-
break
|
|
415
|
-
if _d in decVals:
|
|
416
|
-
_inConsistent |= _v != decVals[_d]
|
|
417
|
-
else:
|
|
418
|
-
decVals[_d] = _v
|
|
419
|
-
a, b, _inclA, _inclB = rangeValue(_v, _d)
|
|
420
|
-
if a > aMax: aMax = a
|
|
421
|
-
if b < bMin: bMin = b
|
|
422
|
-
if not _inConsistent:
|
|
423
|
-
_inConsistent = (bMin < aMax)
|
|
424
|
-
decVals.clear()
|
|
425
|
-
else:
|
|
426
|
-
_inConsistent = any(not f.isVEqualTo(f0) for f in fList[1:])
|
|
427
|
-
if _inConsistent:
|
|
428
|
-
modelXbrl.error("JFCVC.3314",
|
|
429
|
-
"Inconsistent duplicate fact values %(fact)s: %(values)s.",
|
|
430
|
-
modelObject=fList, fact=f0.qname, contextID=f0.contextID, values=", ".join(f.value for f in fList))
|
|
431
|
-
aspectEqualFacts.clear()
|
|
432
|
-
del factForConceptContextUnitHash, aspectEqualFacts
|
|
377
|
+
for duplicateFactSet in getDuplicateFactSets(modelXbrl.facts, includeSingles=False):
|
|
378
|
+
if duplicateFactSet.areAnyInconsistent:
|
|
379
|
+
f0 = duplicateFactSet.facts[0]
|
|
380
|
+
modelXbrl.error("JFCVC.3314",
|
|
381
|
+
"Inconsistent duplicate fact values %(fact)s: %(values)s.",
|
|
382
|
+
modelObject=duplicateFactSet, fact=f0.qname, values=", ".join(f'"{f.value}"' for f in duplicateFactSet))
|
|
433
383
|
|
|
434
384
|
if modelXbrl.modelDocument.type == ModelDocument.Type.INLINEXBRL:
|
|
435
385
|
rootElt = modelXbrl.modelDocument.xmlRootElement
|
|
@@ -116,7 +116,7 @@ arelle/ViewWinVersReport.py,sha256=aYfsOgynVZpMzl6f2EzQCBLzdihYGycwb5SiTghkgMQ,9
|
|
|
116
116
|
arelle/ViewWinXml.py,sha256=4ZGKtjaoCwU9etKYm9ZAS7jSmUxba1rqNEdv0OIyjTY,1250
|
|
117
117
|
arelle/WatchRss.py,sha256=5Ih4igH2MM4hpOuAXy9eO0QAyZ7jZR3S5bPzo2sdFpw,14097
|
|
118
118
|
arelle/WebCache.py,sha256=SLk-S5StYUIucm5bd2BqT-o8ZA0NdYw2Xl4O9vIt7O8,45257
|
|
119
|
-
arelle/XbrlConst.py,sha256=
|
|
119
|
+
arelle/XbrlConst.py,sha256=p5GV8x7ZLNVT-lMo9EEtw8CkVICITxJ4GZXXQFmZQEk,58561
|
|
120
120
|
arelle/XbrlUtil.py,sha256=s2Vmrh-sZI5TeuqsziKignOc3ao-uUgnCNoelP4dDj0,9212
|
|
121
121
|
arelle/XhtmlValidate.py,sha256=0gtm7N-kXK0RB5o3c1AQXjfFuRp1w2fKZZAeyruNANw,5727
|
|
122
122
|
arelle/XmlUtil.py,sha256=1VToOOylF8kbEorEdZLThmq35j9bmuF_DS2q9NthnHU,58774
|
|
@@ -125,7 +125,7 @@ arelle/XmlValidateConst.py,sha256=U_wN0Q-nWKwf6dKJtcu_83FXPn9c6P8JjzGA5b0w7P0,33
|
|
|
125
125
|
arelle/XmlValidateParticles.py,sha256=Mn6vhFl0ZKC_vag1mBwn1rH_x2jmlusJYqOOuxFPO2k,9231
|
|
126
126
|
arelle/XmlValidateSchema.py,sha256=6frtZOc1Yrx_5yYF6V6oHbScnglWrVbWr6xW4EHtLQI,7428
|
|
127
127
|
arelle/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
128
|
-
arelle/_version.py,sha256=
|
|
128
|
+
arelle/_version.py,sha256=gK8aRhTgf0JSYF4-ziM2ZwD_sWJNac2Cf_ZEEYaXGq4,708
|
|
129
129
|
arelle/typing.py,sha256=PRe-Fxwr2SBqYYUVPCJ3E7ddDX0_oOISNdT5Q97EbRM,1246
|
|
130
130
|
arelle/api/Session.py,sha256=kgSxS7VckA1sQ7xp0pJiK7IK-vRxAdAZKUo8gEx27s8,7549
|
|
131
131
|
arelle/config/creationSoftwareNames.json,sha256=5MK7XUjfDJ9OpRCCHXeOErJ1SlTBZji4WEcEOdOacx0,3128
|
|
@@ -312,14 +312,14 @@ arelle/plugin/validate/DBA/rules/tm.py,sha256=ui9oKBqlAForwkQ9kk9KBiUogTJE5pv1Rb
|
|
|
312
312
|
arelle/plugin/validate/DBA/rules/tr.py,sha256=4TootFjl0HXsKZk1XNBCyj-vnjRs4lg35hfiz_b_4wU,14684
|
|
313
313
|
arelle/plugin/validate/EBA/__init__.py,sha256=x3zXNcdSDJ3kHfL7kMs0Ve0Vs9oWbzNFVf1TK4Avmy8,45924
|
|
314
314
|
arelle/plugin/validate/EBA/config.xml,sha256=37wMVUAObK-XEqakqD8zPNog20emYt4a_yfL1AKubF8,2022
|
|
315
|
-
arelle/plugin/validate/EDINET/Constants.py,sha256=
|
|
316
|
-
arelle/plugin/validate/EDINET/ControllerPluginData.py,sha256=
|
|
315
|
+
arelle/plugin/validate/EDINET/Constants.py,sha256=H_OX8hq7nns6TbKPpmDlg1_pNOd7P-XMFF03MMZBAuk,1387
|
|
316
|
+
arelle/plugin/validate/EDINET/ControllerPluginData.py,sha256=T4m8GFiVg9tNoLcC3HxeegNnKVqAqZdc6kNdxM5Vz1Y,7005
|
|
317
317
|
arelle/plugin/validate/EDINET/DisclosureSystems.py,sha256=3rKG42Eg-17Xx_KXU_V5yHW6I3LTwQunvf4a44C9k_4,36
|
|
318
|
-
arelle/plugin/validate/EDINET/InstanceType.py,sha256=aLKb4-AJ6nDZKMOLCp7u08E9VD64ExeZy9_oGth-LTk,3207
|
|
319
318
|
arelle/plugin/validate/EDINET/ManifestInstance.py,sha256=SkQV-aOsYn3CTgCkH4IXNdM3QKoiz8okwb29ftMtV3Q,6882
|
|
320
|
-
arelle/plugin/validate/EDINET/PluginValidationDataExtension.py,sha256=
|
|
319
|
+
arelle/plugin/validate/EDINET/PluginValidationDataExtension.py,sha256=EkUJvkjk96ehgfnnoUxBAv9x9aauntvF9oXFOyr81Yo,12447
|
|
320
|
+
arelle/plugin/validate/EDINET/ReportFolderType.py,sha256=Q-9a-5tJfhK-cjY8WUB2AT1NI-Nn9cFtARVOIJoLRGU,2979
|
|
321
321
|
arelle/plugin/validate/EDINET/Statement.py,sha256=0Mw5IB7LMtvUZ-2xKZfxmq67xF_dCgJo3eNLweLFRHU,9350
|
|
322
|
-
arelle/plugin/validate/EDINET/UploadContents.py,sha256=
|
|
322
|
+
arelle/plugin/validate/EDINET/UploadContents.py,sha256=IKQYl6lXYTfAZKLIDzRRGSRt3FXoL2Eldbx3Dh7T2I4,712
|
|
323
323
|
arelle/plugin/validate/EDINET/ValidationPluginExtension.py,sha256=oMY0ntLr1qIh3uMi1W_M-bT5bhXPDx048X3oDFP5zOY,2042
|
|
324
324
|
arelle/plugin/validate/EDINET/__init__.py,sha256=OZ7gMknCHd0M-9nt8UOmjEZW50YQzbvSLongr9O7Yi0,3022
|
|
325
325
|
arelle/plugin/validate/EDINET/resources/config.xml,sha256=7uT4GcRgk5veMLpFhPPQJxbGKiQvM52P8EMrjn0qd0g,646
|
|
@@ -328,9 +328,9 @@ arelle/plugin/validate/EDINET/rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQ
|
|
|
328
328
|
arelle/plugin/validate/EDINET/rules/contexts.py,sha256=KPoyWfRaURvxoGVcWP64mTMTAKPMSmQSX06RClCLddw,7590
|
|
329
329
|
arelle/plugin/validate/EDINET/rules/edinet.py,sha256=VYrDZaKbsQuQEvOY5F0Pv4Jzk9YZ4iETOkAFOggrhEY,12632
|
|
330
330
|
arelle/plugin/validate/EDINET/rules/frta.py,sha256=N0YglHYZuLD2IuwE26viR2ViwUYjneBuMFU9vlrS0aQ,7616
|
|
331
|
-
arelle/plugin/validate/EDINET/rules/gfm.py,sha256=
|
|
331
|
+
arelle/plugin/validate/EDINET/rules/gfm.py,sha256=Elyd0Vqooj_rC0yDWC8NneWCQ_Ckb9IZy1XCn7m1_IE,24389
|
|
332
332
|
arelle/plugin/validate/EDINET/rules/manifests.py,sha256=MoT9R_a4BzuYdQVbF7RC5wz134Ve68svSdJ3NlpO_AU,4026
|
|
333
|
-
arelle/plugin/validate/EDINET/rules/upload.py,sha256=
|
|
333
|
+
arelle/plugin/validate/EDINET/rules/upload.py,sha256=jfBt-WjUiJqh8QSkaLMxfsz9dP6fT3z63GT-ws514YY,31415
|
|
334
334
|
arelle/plugin/validate/ESEF/Const.py,sha256=JujF_XV-_TNsxjGbF-8SQS4OOZIcJ8zhCMnr-C1O5Ho,22660
|
|
335
335
|
arelle/plugin/validate/ESEF/Dimensions.py,sha256=MOJM7vwNPEmV5cu-ZzPrhx3347ZvxgD6643OB2HRnIk,10597
|
|
336
336
|
arelle/plugin/validate/ESEF/Util.py,sha256=QH3btcGqBpr42M7WSKZLSdNXygZaZLfEiEjlxoG21jE,7950
|
|
@@ -367,7 +367,7 @@ arelle/plugin/validate/ROS/resources/config.xml,sha256=HXWume5HlrAqOx5AtiWWqgADb
|
|
|
367
367
|
arelle/plugin/validate/ROS/rules/__init__.py,sha256=wW7BUAIb7sRkOxC1Amc_ZKrz03FM-Qh1TyZe6wxYaAU,1567
|
|
368
368
|
arelle/plugin/validate/ROS/rules/ros.py,sha256=Dk5BkfKQYItImdx5FcFvkMWT5BlJ1r_L7Vn-EsCG85A,19870
|
|
369
369
|
arelle/plugin/validate/UK/ValidateUK.py,sha256=h7-tnCubHme8Meaif-o55TV2rCfMWuikfpZCcK6NNDs,56447
|
|
370
|
-
arelle/plugin/validate/UK/__init__.py,sha256=
|
|
370
|
+
arelle/plugin/validate/UK/__init__.py,sha256=0X4_J9Ug0O0xiEm-JYx7LEnGdpXUKZ7D-5eh9mjjqR8,27456
|
|
371
371
|
arelle/plugin/validate/UK/config.xml,sha256=mUFhWDfBzGTn7v0ZSmf4HaweQTMJh_4ZcJmD9mzCHrA,1547
|
|
372
372
|
arelle/plugin/validate/UK/consistencyChecksByName.json,sha256=BgB9YAWzmcsX-_rU74RBkABwEsS75vrMlwBHsYCz2R0,25247
|
|
373
373
|
arelle/plugin/validate/UK/hmrc-taxonomies.xml,sha256=3lR-wb2sooAddQkVqqRzG_VqLuHq_MQ8kIaXAQs1KVk,9623
|
|
@@ -680,9 +680,9 @@ arelle/utils/validate/ValidationUtil.py,sha256=9vmSvShn-EdQy56dfesyV8JjSRVPj7txr
|
|
|
680
680
|
arelle/utils/validate/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
681
681
|
arelle/webserver/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
682
682
|
arelle/webserver/bottle.py,sha256=P-JECd9MCTNcxCnKoDUvGcoi03ezYVOgoWgv2_uH-6M,362
|
|
683
|
-
arelle_release-2.37.
|
|
684
|
-
arelle_release-2.37.
|
|
685
|
-
arelle_release-2.37.
|
|
686
|
-
arelle_release-2.37.
|
|
687
|
-
arelle_release-2.37.
|
|
688
|
-
arelle_release-2.37.
|
|
683
|
+
arelle_release-2.37.52.dist-info/licenses/LICENSE.md,sha256=Q0tn6q0VUbr-NM8916513NCIG8MNzo24Ev-sxMUBRZc,3959
|
|
684
|
+
arelle_release-2.37.52.dist-info/METADATA,sha256=k76rs-k1Xh7_AxEk4QnW0pEKI3JdJxEYvE-9mU9SZIY,9327
|
|
685
|
+
arelle_release-2.37.52.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
686
|
+
arelle_release-2.37.52.dist-info/entry_points.txt,sha256=Uj5niwfwVsx3vaQ3fYj8hrZ1xpfCJyTUA09tYKWbzpo,111
|
|
687
|
+
arelle_release-2.37.52.dist-info/top_level.txt,sha256=fwU7SYawL4_r-sUMRg7r1nYVGjFMSDvRWx8VGAXEw7w,7
|
|
688
|
+
arelle_release-2.37.52.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|