arelle-release 2.37.71__py3-none-any.whl → 2.38.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- arelle/BetaFeatures.py +0 -21
- arelle/Cntlr.py +7 -1
- arelle/CntlrCmdLine.py +95 -19
- arelle/CntlrWinMain.py +4 -1
- arelle/DialogFind.py +1 -1
- arelle/ModelDtsObject.py +2 -0
- arelle/ModelObject.py +16 -18
- arelle/ModelObjectFactory.py +17 -15
- arelle/ModelXbrl.py +7 -1
- arelle/PluginManager.py +1 -5
- arelle/RuntimeOptions.py +1 -0
- arelle/UrlUtil.py +11 -0
- arelle/Validate.py +3 -3
- arelle/ValidateXbrl.py +2 -1
- arelle/ValidateXbrlCalcs.py +210 -186
- arelle/WebCache.py +2 -8
- arelle/XbrlConst.py +2 -0
- arelle/XmlUtil.py +16 -21
- arelle/XmlValidate.py +4 -6
- arelle/_version.py +2 -2
- arelle/config/rosettaEntitlements.plist +8 -0
- arelle/conformance/CSVTestcaseLoader.py +1 -1
- arelle/formula/XPathContext.py +3 -3
- arelle/logging/formatters/LogFormatter.py +3 -1
- arelle/packages/report/ReportPackage.py +9 -1
- arelle/plugin/inlineXbrlDocumentSet.py +1 -3
- arelle/plugin/validate/DBA/DisclosureSystems.py +19 -1
- arelle/plugin/validate/DBA/resources/config.xml +5 -0
- arelle/plugin/validate/DBA/rules/fr.py +19 -2
- arelle/plugin/validate/DBA/rules/tc.py +2 -0
- arelle/plugin/validate/DBA/rules/th.py +6 -0
- arelle/plugin/validate/DBA/rules/tm.py +18 -5
- arelle/plugin/validate/DBA/rules/tr.py +11 -5
- arelle/plugin/validate/EDINET/ControllerPluginData.py +2 -1
- arelle/plugin/validate/EDINET/NamespaceConfig.py +50 -0
- arelle/plugin/validate/EDINET/PluginValidationDataExtension.py +33 -78
- arelle/plugin/validate/EDINET/TableOfContentsBuilder.py +153 -51
- arelle/plugin/validate/EDINET/rules/contexts.py +1 -1
- arelle/plugin/validate/EDINET/rules/edinet.py +163 -20
- arelle/plugin/validate/EDINET/rules/gfm.py +88 -1
- arelle/plugin/validate/EDINET/rules/upload.py +1 -1
- arelle/plugin/validate/ESEF/ESEF_2021/ValidateXbrlFinally.py +3 -3
- arelle/plugin/validate/ESEF/ESEF_Current/DTS.py +42 -14
- arelle/plugin/validate/ESEF/ESEF_Current/ValidateXbrlFinally.py +14 -3
- arelle/plugin/validate/ESEF/__init__.py +10 -5
- arelle/plugin/validate/ESEF/resources/authority-validations.json +10 -5
- arelle/plugin/validate/NL/DisclosureSystems.py +22 -0
- arelle/plugin/validate/NL/PluginValidationDataExtension.py +20 -0
- arelle/plugin/validate/NL/ValidationPluginExtension.py +48 -3
- arelle/plugin/validate/NL/resources/config.xml +18 -0
- arelle/plugin/validate/NL/rules/br_kvk.py +9 -54
- arelle/plugin/validate/NL/rules/fg_nl.py +7 -38
- arelle/plugin/validate/NL/rules/fr_kvk.py +7 -42
- arelle/plugin/validate/NL/rules/fr_nl.py +25 -140
- arelle/plugin/validate/NL/rules/nl_kvk.py +125 -12
- arelle/plugin/validate/ROS/rules/ros.py +3 -1
- arelle/plugin/validate/UK/__init__.py +70 -14
- arelle/utils/EntryPointDetection.py +17 -11
- arelle/utils/validate/ESEFImage.py +3 -3
- arelle/utils/validate/Validation.py +9 -0
- arelle/utils/validate/ValidationPlugin.py +14 -12
- {arelle_release-2.37.71.dist-info → arelle_release-2.38.0.dist-info}/METADATA +10 -5
- {arelle_release-2.37.71.dist-info → arelle_release-2.38.0.dist-info}/RECORD +67 -69
- {arelle_release-2.37.71.dist-info → arelle_release-2.38.0.dist-info}/licenses/LICENSE.md +0 -3
- arelle/model/CommentBase.py +0 -9
- arelle/model/ElementBase.py +0 -11
- arelle/model/PIBase.py +0 -10
- arelle/model/__init__.py +0 -15
- {arelle_release-2.37.71.dist-info → arelle_release-2.38.0.dist-info}/WHEEL +0 -0
- {arelle_release-2.37.71.dist-info → arelle_release-2.38.0.dist-info}/entry_points.txt +0 -0
- {arelle_release-2.37.71.dist-info → arelle_release-2.38.0.dist-info}/top_level.txt +0 -0
arelle/BetaFeatures.py
CHANGED
|
@@ -3,27 +3,6 @@ See COPYRIGHT.md for copyright information.
|
|
|
3
3
|
"""
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
|
-
BETA_OBJECT_MODEL_FEATURE = "betaObjectModel"
|
|
7
6
|
# Add camelCaseOptionName
|
|
8
7
|
BETA_FEATURES_AND_DESCRIPTIONS: dict[str, str] = {
|
|
9
|
-
BETA_OBJECT_MODEL_FEATURE: "Replace lxml based object model with a pure Python class hierarchy.",
|
|
10
8
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
_NEW_OBJECT_MODEL_STATUS_ACCESSED = False
|
|
14
|
-
_USE_NEW_OBJECT_MODEL = False
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def enableNewObjectModel() -> None:
|
|
18
|
-
global _USE_NEW_OBJECT_MODEL
|
|
19
|
-
if _USE_NEW_OBJECT_MODEL:
|
|
20
|
-
return
|
|
21
|
-
if _NEW_OBJECT_MODEL_STATUS_ACCESSED:
|
|
22
|
-
raise RuntimeError("Can't change object model transition setting after classes have already been defined.")
|
|
23
|
-
_USE_NEW_OBJECT_MODEL = True
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def newObjectModelEnabled() -> bool:
|
|
27
|
-
global _NEW_OBJECT_MODEL_STATUS_ACCESSED
|
|
28
|
-
_NEW_OBJECT_MODEL_STATUS_ACCESSED = True
|
|
29
|
-
return _USE_NEW_OBJECT_MODEL
|
arelle/Cntlr.py
CHANGED
|
@@ -35,6 +35,7 @@ from arelle.logging.handlers.StructuredMessageLogHandler import StructuredMessag
|
|
|
35
35
|
from arelle.SystemInfo import PlatformOS, getSystemWordSize, hasFileSystem, hasWebServer, isCGI, isGAE
|
|
36
36
|
from arelle.typing import TypeGetText
|
|
37
37
|
from arelle.utils.PluginData import PluginData
|
|
38
|
+
from arelle.utils.validate.Validation import Validation
|
|
38
39
|
from arelle.WebCache import WebCache
|
|
39
40
|
|
|
40
41
|
_: TypeGetText
|
|
@@ -166,7 +167,7 @@ class Cntlr:
|
|
|
166
167
|
betaFeatures = {}
|
|
167
168
|
self.betaFeatures = {
|
|
168
169
|
b: betaFeatures.get(b, False)
|
|
169
|
-
for b in BETA_FEATURES_AND_DESCRIPTIONS
|
|
170
|
+
for b in BETA_FEATURES_AND_DESCRIPTIONS
|
|
170
171
|
}
|
|
171
172
|
self.errorManager = None
|
|
172
173
|
self.hasWin32gui = False
|
|
@@ -298,6 +299,11 @@ class Cntlr:
|
|
|
298
299
|
self.startLogging(logFileName, logFileMode, logFileEncoding, logFormat)
|
|
299
300
|
self.errorManager = ErrorManager(self.modelManager, logging._checkLevel("INCONSISTENCY")) # type: ignore[attr-defined]
|
|
300
301
|
|
|
302
|
+
def validation(self, val: Validation, fileSource: FileSource | None = None) -> None:
|
|
303
|
+
"""Same as `error`, but parameters passed in from Validation object
|
|
304
|
+
"""
|
|
305
|
+
self.error(codes=val.codes, msg=val.msg, level=val.level.name, fileSource=fileSource, **val.args)
|
|
306
|
+
|
|
301
307
|
def error(self, codes: Any, msg: str, level: str = "ERROR", fileSource: FileSource | None = None, **args: Any) -> None:
|
|
302
308
|
if self.logger is None or self.errorManager is None:
|
|
303
309
|
self.addToLog(
|
arelle/CntlrCmdLine.py
CHANGED
|
@@ -11,6 +11,7 @@ import datetime
|
|
|
11
11
|
import fnmatch
|
|
12
12
|
import gettext
|
|
13
13
|
import glob
|
|
14
|
+
import json
|
|
14
15
|
import logging
|
|
15
16
|
import multiprocessing
|
|
16
17
|
import os
|
|
@@ -97,6 +98,38 @@ def parseAndRun(args):
|
|
|
97
98
|
return cntlr
|
|
98
99
|
|
|
99
100
|
|
|
101
|
+
PREPARSE_ARG_CONFIGS = frozenset([
|
|
102
|
+
(re.compile(r'^--plugins?.*$'), 'plugins'),
|
|
103
|
+
(re.compile(r'^--options(F|f)ile.*$'), 'optionsFile'),
|
|
104
|
+
])
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def preparseArgs(args: list[str], parser: OptionParser) -> dict[str, str]:
|
|
108
|
+
"""
|
|
109
|
+
Some command line arguments influence the actual parsing of other arguments.
|
|
110
|
+
This function pre-parses those arguments to allow for processing before full
|
|
111
|
+
argument parseing occurs.
|
|
112
|
+
:param args: Command line arguments
|
|
113
|
+
:param parser: OptionParser to report errors
|
|
114
|
+
:return: Dictionary of pre-parsed options
|
|
115
|
+
"""
|
|
116
|
+
preparsedArgs = {}
|
|
117
|
+
for i, arg in enumerate(args):
|
|
118
|
+
for pattern, preparsedArg in PREPARSE_ARG_CONFIGS:
|
|
119
|
+
if pattern.fullmatch(arg):
|
|
120
|
+
__, sep, value = arg.partition('=')
|
|
121
|
+
if sep: # --arg=value
|
|
122
|
+
preparsedValue = value
|
|
123
|
+
elif i < len(args) - 1: # --arg value
|
|
124
|
+
preparsedValue = args[i+1]
|
|
125
|
+
else: # --arg
|
|
126
|
+
preparsedValue = ""
|
|
127
|
+
if preparsedArg in preparsedArgs:
|
|
128
|
+
parser.error(_("Multiple '{}' values found during argument preparsing.").format(preparsedArg))
|
|
129
|
+
preparsedArgs[preparsedArg] = preparsedValue
|
|
130
|
+
return preparsedArgs
|
|
131
|
+
|
|
132
|
+
|
|
100
133
|
def parseArgs(args):
|
|
101
134
|
"""
|
|
102
135
|
Parses the command line arguments and generates runtimeOptions and arellePluginModules
|
|
@@ -411,24 +444,27 @@ def parseArgs(args):
|
|
|
411
444
|
pluginOptionsIndex = len(parser.option_list)
|
|
412
445
|
pluginOptionsGroupIndex = len(parser.option_groups)
|
|
413
446
|
|
|
447
|
+
preparsedArgs = preparseArgs(args, parser)
|
|
448
|
+
|
|
449
|
+
preloadPlugins = []
|
|
450
|
+
optionsFile = preparsedArgs.get('optionsFile')
|
|
451
|
+
optionsFileOptions = {}
|
|
452
|
+
if optionsFile:
|
|
453
|
+
optionsFileOptions = _parseOptionsFile(optionsFile, parser)
|
|
454
|
+
preloadPlugins.extend(optionsFileOptions.get('plugins', '').split('|'))
|
|
455
|
+
|
|
456
|
+
preloadPlugins.extend(preparsedArgs.get('plugins', '').split('|'))
|
|
457
|
+
|
|
414
458
|
# install any dynamic plugins so their command line options can be parsed if present
|
|
415
459
|
arellePluginModules = {}
|
|
416
|
-
for
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
for pluginCmd in preloadPlugins.split('|'):
|
|
425
|
-
cmd = pluginCmd.strip()
|
|
426
|
-
if cmd not in ("show", "temp") and len(cmd) > 0 and cmd[0] not in ('-', '~', '+'):
|
|
427
|
-
moduleInfo = PluginManager.addPluginModule(cmd)
|
|
428
|
-
if moduleInfo:
|
|
429
|
-
arellePluginModules[cmd] = moduleInfo
|
|
430
|
-
PluginManager.reset()
|
|
431
|
-
break
|
|
460
|
+
for pluginCmd in preloadPlugins:
|
|
461
|
+
cmd = pluginCmd.strip()
|
|
462
|
+
if cmd not in ("show", "temp") and len(cmd) > 0 and cmd[0] not in ('-', '~', '+'):
|
|
463
|
+
moduleInfo = PluginManager.addPluginModule(cmd)
|
|
464
|
+
if moduleInfo:
|
|
465
|
+
arellePluginModules[cmd] = moduleInfo
|
|
466
|
+
PluginManager.reset()
|
|
467
|
+
|
|
432
468
|
# add plug-in options
|
|
433
469
|
for optionsExtender in PluginManager.pluginClassMethods("CntlrCmdLine.Options"):
|
|
434
470
|
optionsExtender(parser)
|
|
@@ -439,6 +475,10 @@ def parseArgs(args):
|
|
|
439
475
|
help=_("Show product version, copyright, and license."))
|
|
440
476
|
parser.add_option("--diagnostics", action="store_true", dest="diagnostics",
|
|
441
477
|
help=_("output system diagnostics information"))
|
|
478
|
+
parser.add_option("--optionsFile", "--optionsfile",
|
|
479
|
+
action="store", dest="optionsFile",
|
|
480
|
+
help=_("Provide a path to a JSON file containing runtime options. "
|
|
481
|
+
"These options will be overridden by any command line options provided."))
|
|
442
482
|
|
|
443
483
|
if not args and isGAE():
|
|
444
484
|
args = ["--webserver=::gae"]
|
|
@@ -517,9 +557,22 @@ def parseArgs(args):
|
|
|
517
557
|
for optGroup in parser.option_groups[pluginOptionsGroupIndex:pluginLastOptionsGroupIndex]:
|
|
518
558
|
for groupOption in optGroup.option_list:
|
|
519
559
|
pluginOptionDestinations.add(groupOption.dest)
|
|
560
|
+
|
|
520
561
|
baseOptions = {}
|
|
521
|
-
|
|
562
|
+
# Collect options from options file
|
|
563
|
+
for optionName, optionValue in optionsFileOptions.items():
|
|
564
|
+
if not hasattr(RuntimeOptions, optionName) and optionName not in pluginOptionDestinations:
|
|
565
|
+
parser.error(_("Unexpected name '{}' found in options file.").format(optionName))
|
|
566
|
+
continue
|
|
567
|
+
baseOptions[optionName] = optionValue
|
|
568
|
+
# Collect options from command line
|
|
522
569
|
for optionName, optionValue in vars(options).items():
|
|
570
|
+
if optionName not in baseOptions or optionValue is not None:
|
|
571
|
+
baseOptions[optionName] = optionValue
|
|
572
|
+
|
|
573
|
+
pluginOptions = {}
|
|
574
|
+
finalOptions = {} # Validated options for RuntimeOptions
|
|
575
|
+
for optionName, optionValue in baseOptions.items():
|
|
523
576
|
if optionName in pluginOptionDestinations:
|
|
524
577
|
pluginOptions[optionName] = optionValue
|
|
525
578
|
else:
|
|
@@ -531,9 +584,10 @@ def parseArgs(args):
|
|
|
531
584
|
parser.error(_("--testcaseExpectedErrors must be in the format '--testcaseExpectedErrors=testcase-index.xml:v-1|errorCode1,errorCode2,...'"))
|
|
532
585
|
expectedErrors[expectedErrorSplit[0]] = expectedErrorSplit[1].split(',')
|
|
533
586
|
optionValue = expectedErrors
|
|
534
|
-
|
|
587
|
+
if optionValue is not None or optionName not in finalOptions:
|
|
588
|
+
finalOptions[optionName] = optionValue
|
|
535
589
|
try:
|
|
536
|
-
runtimeOptions = RuntimeOptions(pluginOptions=pluginOptions, **
|
|
590
|
+
runtimeOptions = RuntimeOptions(pluginOptions=pluginOptions, **finalOptions)
|
|
537
591
|
except RuntimeOptionsException as e:
|
|
538
592
|
parser.error(f"{e}, please try\n python CntlrCmdLine.py --help")
|
|
539
593
|
if (
|
|
@@ -626,6 +680,28 @@ def _pluginHasCliOptions(moduleInfo):
|
|
|
626
680
|
return False
|
|
627
681
|
|
|
628
682
|
|
|
683
|
+
def _parseOptionsFile(optionsFile: str, parser: OptionParser) -> dict:
|
|
684
|
+
"""
|
|
685
|
+
Parse the JSON options within the provided filepath.
|
|
686
|
+
:param optionsFile: The path to the JSON options file.
|
|
687
|
+
:param parser: The parser to log an error to if needed.
|
|
688
|
+
:return: The parsed options as a dictionary.
|
|
689
|
+
"""
|
|
690
|
+
try:
|
|
691
|
+
with open(optionsFile) as f:
|
|
692
|
+
jsonOptions = json.load(f)
|
|
693
|
+
except OSError:
|
|
694
|
+
parser.error(_("Options file path does not exist: {}").format(optionsFile))
|
|
695
|
+
return {}
|
|
696
|
+
except Exception as e:
|
|
697
|
+
parser.error(_("Unable to parse options JSON file: {}").format(e))
|
|
698
|
+
return {}
|
|
699
|
+
if not isinstance(jsonOptions, dict):
|
|
700
|
+
parser.error(_("Options JSON file must contain a JSON object at its root."))
|
|
701
|
+
return {}
|
|
702
|
+
return jsonOptions
|
|
703
|
+
|
|
704
|
+
|
|
629
705
|
class CntlrCmdLine(Cntlr.Cntlr):
|
|
630
706
|
"""
|
|
631
707
|
.. class:: CntlrCmdLin()
|
arelle/CntlrWinMain.py
CHANGED
|
@@ -17,7 +17,7 @@ from typing import Any
|
|
|
17
17
|
|
|
18
18
|
import regex as re
|
|
19
19
|
|
|
20
|
-
from arelle import ValidateDuplicateFacts
|
|
20
|
+
from arelle import UrlUtil, ValidateDuplicateFacts
|
|
21
21
|
from arelle.ValidateFileSource import ValidateFileSource
|
|
22
22
|
from arelle.logging.formatters.LogFormatter import logRefsFileLines
|
|
23
23
|
from arelle.utils.EntryPointDetection import parseEntrypointFileInput
|
|
@@ -868,6 +868,9 @@ class CntlrWinMain (Cntlr.Cntlr):
|
|
|
868
868
|
entrypointFiles = entrypointParseResult.entrypointFiles
|
|
869
869
|
# check for archive files
|
|
870
870
|
if filesource.isArchive:
|
|
871
|
+
filenameWithoutFakeIxdsPrefix = UrlUtil.stripIxdsSurrogatePrefix(filename)
|
|
872
|
+
if all(e.get("file") == filenameWithoutFakeIxdsPrefix for e in entrypointFiles):
|
|
873
|
+
entrypointFiles = []
|
|
871
874
|
if (
|
|
872
875
|
len(entrypointFiles) == 0 and
|
|
873
876
|
not filesource.selection and
|
arelle/DialogFind.py
CHANGED
|
@@ -186,7 +186,7 @@ class DialogFind(Toplevel):
|
|
|
186
186
|
else:
|
|
187
187
|
if not self.modelManager.modelXbrl or not docType in (
|
|
188
188
|
ModelDocument.Type.SCHEMA, ModelDocument.Type.LINKBASE, ModelDocument.Type.INSTANCE, ModelDocument.Type.INLINEXBRL,
|
|
189
|
-
ModelDocument.Type.RSSFEED):
|
|
189
|
+
ModelDocument.Type.RSSFEED, ModelDocument.Type.INLINEXBRLDOCUMENTSET):
|
|
190
190
|
messagebox.showerror(_("Find cannot be completed"),
|
|
191
191
|
_("Find requires an opened DTS or RSS Feed"), parent=self.parent)
|
|
192
192
|
return
|
arelle/ModelDtsObject.py
CHANGED
|
@@ -2015,6 +2015,7 @@ class ModelRelationship(ModelObject):
|
|
|
2015
2015
|
@property
|
|
2016
2016
|
def equivalenceHash(self): # not exact, use equivalenceKey if hashes are the same
|
|
2017
2017
|
return hash((self.qname,
|
|
2018
|
+
self.arcrole,
|
|
2018
2019
|
self.linkQname,
|
|
2019
2020
|
self.linkrole, # needed when linkrole=None merges multiple links
|
|
2020
2021
|
self.fromModelObject.objectIndex if isinstance(self.fromModelObject, ModelObject) else -1,
|
|
@@ -2028,6 +2029,7 @@ class ModelRelationship(ModelObject):
|
|
|
2028
2029
|
"""(tuple) -- Key to determine relationship equivalence per 2.1 spec"""
|
|
2029
2030
|
# cannot be cached because this is unique per relationship
|
|
2030
2031
|
return (self.qname,
|
|
2032
|
+
self.arcrole,
|
|
2031
2033
|
self.linkQname,
|
|
2032
2034
|
self.linkrole, # needed when linkrole=None merges multiple links
|
|
2033
2035
|
self.fromModelObject.objectIndex if isinstance(self.fromModelObject, ModelObject) else -1,
|
arelle/ModelObject.py
CHANGED
|
@@ -8,7 +8,6 @@ from lxml import etree
|
|
|
8
8
|
from arelle import Locale
|
|
9
9
|
from arelle import ModelValue
|
|
10
10
|
from arelle.XmlValidateConst import VALID_NO_CONTENT
|
|
11
|
-
from arelle.model import CommentBase, ElementBase, PIBase
|
|
12
11
|
|
|
13
12
|
if TYPE_CHECKING:
|
|
14
13
|
from arelle.ModelDocument import ModelDocument
|
|
@@ -21,7 +20,7 @@ if TYPE_CHECKING:
|
|
|
21
20
|
from arelle.ModelInstanceObject import ModelInlineFootnote
|
|
22
21
|
from arelle.ModelInstanceObject import ModelInlineFact
|
|
23
22
|
from arelle.ModelInstanceObject import ModelDimensionValue
|
|
24
|
-
from arelle.ModelValue import
|
|
23
|
+
from arelle.ModelValue import QName, TypeSValue, TypeXValue
|
|
25
24
|
|
|
26
25
|
XmlUtil: Any = None
|
|
27
26
|
|
|
@@ -32,7 +31,7 @@ def init() -> None: # init globals
|
|
|
32
31
|
if XmlUtil is None:
|
|
33
32
|
from arelle import XmlUtil
|
|
34
33
|
|
|
35
|
-
class ModelObject(ElementBase):
|
|
34
|
+
class ModelObject(etree.ElementBase):
|
|
36
35
|
"""ModelObjects represent the XML elements within a document, and are implemented as custom
|
|
37
36
|
lxml proxy objects. Each modelDocument has a parser with the parser objects in ModelObjectFactory.py,
|
|
38
37
|
to determine the type of model object to correspond to a proxied lxml XML element.
|
|
@@ -117,6 +116,7 @@ class ModelObject(ElementBase):
|
|
|
117
116
|
xValueError: Exception | None
|
|
118
117
|
xValid: int
|
|
119
118
|
xlinkLabel: str
|
|
119
|
+
tag: str
|
|
120
120
|
targetModelXbrl: ModelXbrl
|
|
121
121
|
|
|
122
122
|
def _init(self) -> None:
|
|
@@ -125,9 +125,9 @@ class ModelObject(ElementBase):
|
|
|
125
125
|
if parent is not None and hasattr(parent, "modelDocument"):
|
|
126
126
|
self.init(parent.modelDocument)
|
|
127
127
|
|
|
128
|
-
def clear(self) -> None:
|
|
128
|
+
def clear(self, keep_tail: bool = False) -> None:
|
|
129
129
|
self.__dict__.clear() # delete local attributes
|
|
130
|
-
super(
|
|
130
|
+
super().clear(keep_tail) # delete children
|
|
131
131
|
|
|
132
132
|
def init(self, modelDocument: ModelDocument) -> None:
|
|
133
133
|
self.modelDocument = modelDocument
|
|
@@ -160,9 +160,10 @@ class ModelObject(ElementBase):
|
|
|
160
160
|
return emptySet
|
|
161
161
|
|
|
162
162
|
def setNamespaceLocalName(self) -> None:
|
|
163
|
-
|
|
163
|
+
tag = self.tag
|
|
164
|
+
ns, sep, self._localName = tag.rpartition("}")
|
|
164
165
|
if sep:
|
|
165
|
-
self._namespaceURI = ns[1:]
|
|
166
|
+
self._namespaceURI: str | None = ns[1:]
|
|
166
167
|
else:
|
|
167
168
|
self._namespaceURI = None
|
|
168
169
|
if self.prefix:
|
|
@@ -257,7 +258,7 @@ class ModelObject(ElementBase):
|
|
|
257
258
|
return self._parentQname
|
|
258
259
|
except AttributeError:
|
|
259
260
|
parentObj = self.getparent()
|
|
260
|
-
self._parentQname = parentObj.elementQname if parentObj is not None else None
|
|
261
|
+
self._parentQname = parentObj.elementQname if parentObj is not None else None
|
|
261
262
|
return self._parentQname
|
|
262
263
|
|
|
263
264
|
|
|
@@ -267,18 +268,18 @@ class ModelObject(ElementBase):
|
|
|
267
268
|
|
|
268
269
|
@property
|
|
269
270
|
def stringValue(self) -> str: # "string value" of node, text of all Element descendants
|
|
270
|
-
return ''.join(self.
|
|
271
|
+
return ''.join(self.textNodes(recurse=True)) # return text of Element descendants
|
|
271
272
|
|
|
272
273
|
@property
|
|
273
274
|
def textValue(self) -> str: # xml axis text() differs from string value, no descendant element text
|
|
274
|
-
return ''.join(self.
|
|
275
|
+
return ''.join(self.textNodes()) # no text nodes returns ''
|
|
275
276
|
|
|
276
|
-
def
|
|
277
|
+
def textNodes(self, recurse:bool = False) -> Generator[str | Any, None, None]:
|
|
277
278
|
if self.text and getattr(self,"xValid", 0) != VALID_NO_CONTENT: # skip tuple whitespaces
|
|
278
279
|
yield self.text
|
|
279
280
|
for c in self.iterchildren():
|
|
280
281
|
if recurse and isinstance(c, ModelObject):
|
|
281
|
-
for nestedText in c.
|
|
282
|
+
for nestedText in c.textNodes(recurse):
|
|
282
283
|
yield nestedText
|
|
283
284
|
if c.tail and getattr(self,"xValid", 0) != VALID_NO_CONTENT: # skip tuple whitespaces
|
|
284
285
|
yield c.tail # get tail of nested element, comment or processor nodes
|
|
@@ -305,10 +306,7 @@ class ModelObject(ElementBase):
|
|
|
305
306
|
|
|
306
307
|
@property
|
|
307
308
|
def elementAttributesStr(self) -> str:
|
|
308
|
-
|
|
309
|
-
# Mypy raises the following error. Not sure why this is the case, this returns a str not binary data?
|
|
310
|
-
# On Python 3 formatting "b'abc'" with "{}" produces "b'abc'", not "abc"; use "{!r}" if this is desired behavior
|
|
311
|
-
return ', '.join(["{0}='{1}'".format(name, value) for name, value in self.items()]) # type: ignore[str-bytes-safe]
|
|
309
|
+
return ', '.join(["{0}='{1}'".format(name, value) for name, value in self.items()])
|
|
312
310
|
|
|
313
311
|
def resolveUri(
|
|
314
312
|
self,
|
|
@@ -398,7 +396,7 @@ class ModelObject(ElementBase):
|
|
|
398
396
|
def __repr__(self) -> str:
|
|
399
397
|
return ("{0}[{1}, {2} line {3})".format(type(self).__name__, self.objectIndex, self.modelDocument.basename, self.sourceline))
|
|
400
398
|
|
|
401
|
-
class ModelComment(CommentBase):
|
|
399
|
+
class ModelComment(etree.CommentBase):
|
|
402
400
|
"""ModelConcept is a custom proxy objects for etree.
|
|
403
401
|
"""
|
|
404
402
|
def _init(self) -> None:
|
|
@@ -410,7 +408,7 @@ class ModelComment(CommentBase): # type: ignore[misc]
|
|
|
410
408
|
def init(self, modelDocument: ModelDocument) -> None:
|
|
411
409
|
self.modelDocument = modelDocument
|
|
412
410
|
|
|
413
|
-
class ModelProcessingInstruction(PIBase):
|
|
411
|
+
class ModelProcessingInstruction(etree.PIBase):
|
|
414
412
|
"""ModelProcessingInstruction is a custom proxy object for etree.
|
|
415
413
|
"""
|
|
416
414
|
def _init(self) -> None:
|
arelle/ModelObjectFactory.py
CHANGED
|
@@ -4,7 +4,7 @@ See COPYRIGHT.md for copyright information.
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
6
|
from arelle.ModelObject import ModelObject, init as moduleObject_init
|
|
7
|
-
from typing import Any, Optional, TYPE_CHECKING, Type
|
|
7
|
+
from typing import Any, cast, Optional, TYPE_CHECKING, Type
|
|
8
8
|
|
|
9
9
|
if TYPE_CHECKING:
|
|
10
10
|
from arelle.ModelValue import QName
|
|
@@ -45,18 +45,18 @@ def parser(
|
|
|
45
45
|
modelXbrl: ModelXbrl,
|
|
46
46
|
baseUrl: str | None,
|
|
47
47
|
target: None = None
|
|
48
|
-
) -> tuple[etree.XMLParser, KnownNamespacesModelObjectClassLookup, DiscoveringClassLookup]:
|
|
48
|
+
) -> tuple[etree.XMLParser[etree._Element], KnownNamespacesModelObjectClassLookup, DiscoveringClassLookup]:
|
|
49
49
|
moduleObject_init() # init ModelObject globals
|
|
50
|
-
_parser = etree.XMLParser(recover=True, huge_tree=True, target=target,
|
|
51
|
-
|
|
50
|
+
_parser = etree.XMLParser(recover=True, huge_tree=True, target=target, # type: ignore[call-overload]
|
|
51
|
+
resolve_entities=False)
|
|
52
52
|
return setParserElementClassLookup(_parser, modelXbrl, baseUrl)
|
|
53
53
|
|
|
54
54
|
|
|
55
55
|
def setParserElementClassLookup(
|
|
56
|
-
_parser: etree.XMLParser,
|
|
56
|
+
_parser: etree.XMLParser[etree._Element],
|
|
57
57
|
modelXbrl: ModelXbrl,
|
|
58
58
|
baseUrl: str | None = None,
|
|
59
|
-
) -> tuple[etree.XMLParser, KnownNamespacesModelObjectClassLookup, DiscoveringClassLookup]:
|
|
59
|
+
) -> tuple[etree.XMLParser[etree._Element], KnownNamespacesModelObjectClassLookup, DiscoveringClassLookup]:
|
|
60
60
|
classLookup = DiscoveringClassLookup(modelXbrl, baseUrl)
|
|
61
61
|
nsNameLookup = KnownNamespacesModelObjectClassLookup(modelXbrl, fallback=classLookup)
|
|
62
62
|
_parser.set_element_class_lookup(nsNameLookup)
|
|
@@ -91,9 +91,10 @@ class KnownNamespacesModelObjectClassLookup(etree.CustomElementClassLookup):
|
|
|
91
91
|
self.modelXbrl = modelXbrl
|
|
92
92
|
self.type: int | None = None
|
|
93
93
|
|
|
94
|
-
def lookup(self, node_type: str, document:
|
|
94
|
+
def lookup(self, node_type: str, document: object, ns: str | None, ln: str | None) -> type[etree._Element] | None:
|
|
95
95
|
# node_type is "element", "comment", "PI", or "entity"
|
|
96
96
|
if node_type == "element":
|
|
97
|
+
assert ln is not None, "element nodes must have a local name"
|
|
97
98
|
if ns == XbrlConst.xsd:
|
|
98
99
|
if self.type is None:
|
|
99
100
|
self.type = SCHEMA
|
|
@@ -160,14 +161,14 @@ class KnownNamespacesModelObjectClassLookup(etree.CustomElementClassLookup):
|
|
|
160
161
|
|
|
161
162
|
return ModelComment
|
|
162
163
|
elif node_type == "PI":
|
|
163
|
-
return etree.PIBase
|
|
164
|
+
return etree.PIBase
|
|
164
165
|
elif node_type == "entity":
|
|
165
|
-
return etree.EntityBase
|
|
166
|
+
return etree.EntityBase
|
|
166
167
|
# returning None delegates to fallback lookup classes
|
|
167
168
|
return None
|
|
168
169
|
|
|
169
170
|
|
|
170
|
-
class DiscoveringClassLookup(etree.PythonElementClassLookup):
|
|
171
|
+
class DiscoveringClassLookup(etree.PythonElementClassLookup):
|
|
171
172
|
def __init__(self, modelXbrl: ModelXbrl, baseUrl: str | None, fallback: etree.ElementClassLookup | None = None) -> None:
|
|
172
173
|
super(DiscoveringClassLookup, self).__init__(fallback)
|
|
173
174
|
self.modelXbrl = modelXbrl
|
|
@@ -180,10 +181,11 @@ class DiscoveringClassLookup(etree.PythonElementClassLookup): # type: ignore[mi
|
|
|
180
181
|
if self.streamingOrSkipDTS and ModelFact is None:
|
|
181
182
|
from arelle.ModelInstanceObject import ModelFact
|
|
182
183
|
|
|
183
|
-
def lookup(self, document:
|
|
184
|
+
def lookup(self, document: object, proxyElement: etree._Element) -> type[etree._Element] | None:
|
|
184
185
|
# check if proxyElement's namespace is not known
|
|
185
186
|
ns: str | None
|
|
186
|
-
|
|
187
|
+
tag = cast(str, proxyElement.tag)
|
|
188
|
+
ns, sep, ln = tag.partition("}")
|
|
187
189
|
if sep:
|
|
188
190
|
ns = ns[1:]
|
|
189
191
|
else:
|
|
@@ -208,9 +210,9 @@ class DiscoveringClassLookup(etree.PythonElementClassLookup): # type: ignore[mi
|
|
|
208
210
|
# self.makeelementParentModelObject is set in streamingExtensions.py and ModelXbrl.createFact
|
|
209
211
|
ancestor = proxyElement.getparent() or getattr(self.modelXbrl, "makeelementParentModelObject", None)
|
|
210
212
|
while ancestor is not None:
|
|
211
|
-
|
|
212
|
-
if
|
|
213
|
-
if
|
|
213
|
+
ancestorTag = cast(str, ancestor.tag) # not a modelObject yet, just parser prototype
|
|
214
|
+
if ancestorTag.startswith("{http://www.xbrl.org/2003/instance}") or ancestorTag.startswith("{http://www.xbrl.org/2003/linkbase}"):
|
|
215
|
+
if ancestorTag == "{http://www.xbrl.org/2003/instance}xbrl":
|
|
214
216
|
# element not parented by context or footnoteLink
|
|
215
217
|
return ModelFact # type: ignore[no-any-return]
|
|
216
218
|
else:
|
arelle/ModelXbrl.py
CHANGED
|
@@ -26,6 +26,7 @@ from arelle.UrlUtil import isHttpUrl
|
|
|
26
26
|
from arelle.ValidateXbrlDimensions import isFactDimensionallyValid
|
|
27
27
|
from arelle.XbrlConst import standardLabel
|
|
28
28
|
from arelle.XbrlUtil import sEqual
|
|
29
|
+
from arelle.utils.validate.Validation import Validation
|
|
29
30
|
|
|
30
31
|
if TYPE_CHECKING:
|
|
31
32
|
from datetime import date, datetime
|
|
@@ -603,7 +604,7 @@ class ModelXbrl:
|
|
|
603
604
|
all([cDim.isEqualTo(dims[cDimQn]) for cDimQn, cDim in c.qnameDims.items()]))) and
|
|
604
605
|
# OCCs match for either dimensional or non-dimensional modle
|
|
605
606
|
all(
|
|
606
|
-
all([sEqual(self, cOCCs[i], mOCCs[i]) for i in range(len(mOCCs))])
|
|
607
|
+
all([sEqual(self, cOCCs[i], mOCCs[i]) for i in range(len(mOCCs))])
|
|
607
608
|
if len(cOCCs) == len(mOCCs) else False
|
|
608
609
|
for cOCCs,mOCCs in ((c.nonDimValues(segAspect),segOCCs),
|
|
609
610
|
(c.nonDimValues(scenAspect),scenOCCs)))
|
|
@@ -1043,6 +1044,11 @@ class ModelXbrl:
|
|
|
1043
1044
|
"""@messageCatalog=[]"""
|
|
1044
1045
|
self.log('WARNING', codes, msg, **args)
|
|
1045
1046
|
|
|
1047
|
+
def validation(self, val: Validation) -> None:
|
|
1048
|
+
"""Same as log, but parameters passed in from Validation object
|
|
1049
|
+
"""
|
|
1050
|
+
self.log(level=val.level.name, codes=val.codes, msg=val.msg, **val.args)
|
|
1051
|
+
|
|
1046
1052
|
def log(self, level: str, codes: Any, msg: str, **args: Any) -> None:
|
|
1047
1053
|
"""Same as error(), but level passed in as argument
|
|
1048
1054
|
"""
|
arelle/PluginManager.py
CHANGED
|
@@ -856,11 +856,7 @@ class EntryPointRef:
|
|
|
856
856
|
Retrieve all installed plugin entry points.
|
|
857
857
|
:return: List of all discovered entry points.
|
|
858
858
|
"""
|
|
859
|
-
entryPoints
|
|
860
|
-
if sys.version_info < (3, 10):
|
|
861
|
-
entryPoints = [e for e in entry_points().get('arelle.plugin', [])]
|
|
862
|
-
else:
|
|
863
|
-
entryPoints = list(entry_points(group='arelle.plugin'))
|
|
859
|
+
entryPoints = list(entry_points(group='arelle.plugin'))
|
|
864
860
|
entryPointRefs = []
|
|
865
861
|
for entryPoint in entryPoints:
|
|
866
862
|
entryPointRef = EntryPointRef.fromEntryPoint(entryPoint)
|
arelle/RuntimeOptions.py
CHANGED
|
@@ -106,6 +106,7 @@ class RuntimeOptions:
|
|
|
106
106
|
logXmlMaxAttributeLength: Optional[int] = None
|
|
107
107
|
monitorParentProcess: Optional[bool] = None
|
|
108
108
|
noCertificateCheck: Optional[bool] = None
|
|
109
|
+
optionsFile: Optional[str] = None
|
|
109
110
|
outputAttribution: Optional[str] = None
|
|
110
111
|
packageManifestName: Optional[str] = None
|
|
111
112
|
packages: Optional[list[str]] = None
|
arelle/UrlUtil.py
CHANGED
|
@@ -12,6 +12,17 @@ from email.utils import parsedate
|
|
|
12
12
|
from datetime import datetime
|
|
13
13
|
from typing import overload
|
|
14
14
|
|
|
15
|
+
IXDS_DOC_SEPARATOR = "#?#" # the files of the document set follow the "surrogate" with these separators
|
|
16
|
+
IXDS_SURROGATE = f"_IXDS{IXDS_DOC_SEPARATOR}" # surrogate (fake) file name for inline XBRL doc set (IXDS)
|
|
17
|
+
|
|
18
|
+
def stripIxdsSurrogatePrefix(path: str) -> str:
|
|
19
|
+
"""If path contains IXDS surrogate prefix, strip it and return the rest."""
|
|
20
|
+
if path:
|
|
21
|
+
_, found, after = path.partition(IXDS_SURROGATE)
|
|
22
|
+
if found:
|
|
23
|
+
return after
|
|
24
|
+
return path
|
|
25
|
+
|
|
15
26
|
def authority(url: str, includeScheme: bool=True) -> str:
|
|
16
27
|
if url:
|
|
17
28
|
authSep = url.find(':')
|
arelle/Validate.py
CHANGED
|
@@ -136,7 +136,7 @@ class Validate:
|
|
|
136
136
|
instance=self.modelXbrl.modelDocument.basename if hasattr(self.modelXbrl, "modelDocument") and hasattr(self.modelXbrl.modelDocument, "basename") else "(closed)",
|
|
137
137
|
error=err,
|
|
138
138
|
# traceback=traceback.format_tb(sys.exc_info()[2]),
|
|
139
|
-
exc_info=
|
|
139
|
+
exc_info=True)
|
|
140
140
|
self.close()
|
|
141
141
|
|
|
142
142
|
def validateRssFeed(self):
|
|
@@ -489,7 +489,7 @@ class Validate:
|
|
|
489
489
|
except Exception as err:
|
|
490
490
|
model.error("exception:" + type(err).__name__,
|
|
491
491
|
_("Testcase variation validation exception: %(error)s, instance: %(instance)s"),
|
|
492
|
-
modelXbrl=model, instance=model.modelDocument.basename, error=err, exc_info=
|
|
492
|
+
modelXbrl=model, instance=model.modelDocument.basename, error=err, exc_info=True)
|
|
493
493
|
model.hasFormulae = _hasFormulae
|
|
494
494
|
for pluginXbrlMethod in pluginClassMethods("Validate.Complete"):
|
|
495
495
|
pluginXbrlMethod(self.modelXbrl.modelManager.cntlr, filesource)
|
|
@@ -575,7 +575,7 @@ class Validate:
|
|
|
575
575
|
except Exception as err:
|
|
576
576
|
modelXbrl.error("exception:" + type(err).__name__,
|
|
577
577
|
_("Testcase formula variation validation exception: %(error)s, instance: %(instance)s"),
|
|
578
|
-
modelXbrl=modelXbrl, instance=modelXbrl.modelDocument.basename, error=err, exc_info=
|
|
578
|
+
modelXbrl=modelXbrl, instance=modelXbrl.modelDocument.basename, error=err, exc_info=True)
|
|
579
579
|
if modelTestcaseVariation.resultIsInfoset and self.modelXbrl.modelManager.validateInfoset:
|
|
580
580
|
for pluginXbrlMethod in pluginClassMethods("Validate.Infoset"):
|
|
581
581
|
pluginXbrlMethod(modelXbrl, modelTestcaseVariation.resultInfosetUri)
|
arelle/ValidateXbrl.py
CHANGED
|
@@ -428,7 +428,8 @@ class ValidateXbrl:
|
|
|
428
428
|
|
|
429
429
|
if self.validateCalcs:
|
|
430
430
|
modelXbrl.modelManager.showStatus(_("Validating instance calculations"))
|
|
431
|
-
ValidateXbrlCalcs.validate(modelXbrl, self.validateCalcs)
|
|
431
|
+
for val in ValidateXbrlCalcs.validate(modelXbrl, self.validateCalcs):
|
|
432
|
+
modelXbrl.validation(val)
|
|
432
433
|
modelXbrl.profileStat(_("validateCalculations"))
|
|
433
434
|
|
|
434
435
|
if self.validateUTR:
|