arelle-release 2.37.44__py3-none-any.whl → 2.37.46__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/ModelXbrl.py CHANGED
@@ -3,23 +3,25 @@ See COPYRIGHT.md for copyright information.
3
3
  '''
4
4
  from __future__ import annotations
5
5
 
6
- import os, sys, traceback, uuid
7
- import regex as re
6
+ import logging
7
+ import os
8
+ import sys
9
+ import traceback
10
+ import uuid
8
11
  from collections import defaultdict
9
- from collections.abc import Iterable
10
12
  from typing import TYPE_CHECKING, Any, TypeVar, Union, cast, Optional
11
- import logging
12
- from decimal import Decimal
13
+
14
+ import regex as re
15
+ from collections.abc import Iterable
13
16
 
14
17
  import arelle
15
- from arelle import FileSource, ModelRelationshipSet, UrlUtil, XmlUtil, ModelValue, XbrlConst, XmlValidate
16
- from arelle.FileSource import FileNamedStringIO
17
- from arelle.ModelObject import ModelObject, ObjectPropertyViewWrapper
18
- from arelle.ModelValue import dateUnionEqual
18
+ from arelle import FileSource, ModelRelationshipSet, XmlUtil, ModelValue, XbrlConst, XmlValidate
19
+ from arelle.ErrorManager import ErrorManager
19
20
  from arelle.Locale import format_string
21
+ from arelle.ModelObject import ModelObject
22
+ from arelle.ModelValue import dateUnionEqual
20
23
  from arelle.PluginManager import pluginClassMethods
21
24
  from arelle.PrototypeInstanceObject import FactPrototype, DimValuePrototype
22
- from arelle.PythonUtil import flattenSequence
23
25
  from arelle.UrlUtil import isHttpUrl
24
26
  from arelle.ValidateXbrlDimensions import isFactDimensionallyValid
25
27
  from arelle.XbrlConst import standardLabel
@@ -37,7 +39,7 @@ if TYPE_CHECKING:
37
39
  from arelle.ModelRelationshipSet import ModelRelationshipSet as ModelRelationshipSetClass
38
40
  from arelle.ModelValue import QName
39
41
  from arelle.PrototypeDtsObject import LinkPrototype
40
- from arelle.typing import EmptyTuple, TypeGetText, LocaleDict
42
+ from arelle.typing import TypeGetText, LocaleDict
41
43
  from arelle.ValidateUtr import UtrEntry
42
44
 
43
45
  _: TypeGetText # Handle gettext
@@ -52,13 +54,10 @@ AUTO_LOCATE_ELEMENT = '771407c0-1d0c-11e1-be5e-028037ec0200' # singleton meaning
52
54
  DEFAULT = sys.intern("default")
53
55
  NONDEFAULT = sys.intern("non-default")
54
56
  DEFAULTorNONDEFAULT = sys.intern("default-or-non-default")
55
- EMPTY_TUPLE: EmptyTuple = ()
56
57
  _NOT_FOUND = object()
57
58
 
58
- LoggableValue = Union[str, dict[Any, Any], list[Any], set[Any], tuple[Any, ...]]
59
-
60
59
 
61
- def load(modelManager: ModelManager, url: str | FileSourceClass, nextaction: str | None = None, base: str | None = None, useFileSource: FileSourceClass | None = None, errorCaptureLevel: str | None = None, **kwargs: Any) -> ModelXbrl:
60
+ def load(modelManager: ModelManager, url: str | FileSourceClass, nextaction: str | None = None, base: str | None = None, useFileSource: FileSourceClass | None = None, errorCaptureLevel: int | None = None, **kwargs: Any) -> ModelXbrl:
62
61
  """Each loaded instance, DTS, testcase, testsuite, versioning report, or RSS feed, is represented by an
63
62
  instance of a ModelXbrl object. The ModelXbrl object has a collection of ModelDocument objects, each
64
63
  representing an XML document (for now, with SQL whenever its time comes). One of the modelDocuments of
@@ -111,7 +110,7 @@ def load(modelManager: ModelManager, url: str | FileSourceClass, nextaction: str
111
110
 
112
111
  def create(
113
112
  modelManager: ModelManager, newDocumentType: int | None = None, url: str | None = None, schemaRefs: str|None = None, createModelDocument: bool = True, isEntry: bool = False,
114
- errorCaptureLevel: str | None = None, initialXml: str | None = None, initialComment: str | None = None, base: str | None = None, discover: bool = True, xbrliNamespacePrefix: str | None = None
113
+ errorCaptureLevel: int | None = None, initialXml: str | None = None, initialComment: str | None = None, base: str | None = None, discover: bool = True, xbrliNamespacePrefix: str | None = None
115
114
  ) -> ModelXbrl:
116
115
  modelXbrl = ModelXbrl(modelManager, errorCaptureLevel=errorCaptureLevel)
117
116
  modelXbrl.locale = modelManager.locale
@@ -310,19 +309,18 @@ class ModelXbrl:
310
309
  _startedTimeStat: float
311
310
  _qnameUtrUnits: dict[QName, UtrEntry]
312
311
 
313
- def __init__(self, modelManager: ModelManager, errorCaptureLevel: str | None = None) -> None:
312
+ def __init__(self, modelManager: ModelManager, errorCaptureLevel: int | None = None) -> None:
314
313
  self.modelManager = modelManager
315
314
  self.skipDTS: bool = modelManager.skipDTS
316
315
  self.init(errorCaptureLevel=errorCaptureLevel)
317
316
 
318
- def init(self, keepViews: bool = False, errorCaptureLevel: str | None = None) -> None:
317
+ def init(self, keepViews: bool = False, errorCaptureLevel: int | None = None) -> None:
319
318
  self.uuid: str = uuid.uuid1().urn
320
319
  self.namespaceDocs: defaultdict[str, list[ModelDocumentClass]] = defaultdict(list)
321
320
  self.urlDocs: dict[str, ModelDocumentClass] = {}
322
321
  self.urlUnloadableDocs: dict[bool, str] = {} # if entry is True, entry is blocked and unloadable, False means loadable but warned
323
- self.errorCaptureLevel: str = (errorCaptureLevel or logging._checkLevel("INCONSISTENCY")) # type: ignore[attr-defined]
324
- self.errors: list[str | None] = []
325
- self.logCount: dict[str, int] = {}
322
+ self.errorCaptureLevel: int = (errorCaptureLevel or logging._checkLevel("INCONSISTENCY")) # type: ignore[attr-defined]
323
+ self.errorManager = ErrorManager(self.modelManager, self.errorCaptureLevel)
326
324
  self.arcroleTypes: defaultdict[str, list[ModelRoleType]] = defaultdict(list)
327
325
  self.roleTypes: defaultdict[str, list[ModelRoleType]] = defaultdict(list)
328
326
  self.qnameConcepts: dict[QName, ModelConcept] = {} # indexed by qname of element
@@ -361,8 +359,6 @@ class ModelXbrl:
361
359
  self.logRefObjectProperties: bool = getattr(self.logger, "logRefObjectProperties", False)
362
360
  self.logRefHasPluginAttrs: bool = any(True for m in pluginClassMethods("Logging.Ref.Attributes"))
363
361
  self.logRefHasPluginProperties: bool = any(True for m in pluginClassMethods("Logging.Ref.Properties"))
364
- self.logHasRelevelerPlugin: bool = any(True for m in pluginClassMethods("Logging.Severity.Releveler"))
365
- self.logRefFileRelUris: defaultdict[Any, dict[str, str]] = defaultdict(dict)
366
362
  self.profileStats: dict[str, tuple[int, float, float | int]] = {}
367
363
  self.schemaDocsToValidate: set[ModelDocumentClass] = set()
368
364
  self.modelXbrl = self # for consistency in addressing modelXbrl
@@ -1011,195 +1007,12 @@ class ModelXbrl:
1011
1007
  modelObject,
1012
1008
  err, traceback.format_tb(sys.exc_info()[2])))
1013
1009
 
1014
- def effectiveMessageCode(self, messageCodes: tuple[Any] | str) -> str | None:
1015
- """
1016
- If codes includes EFM, GFM, HMRC, or SBR-coded error then the code chosen (if a sequence)
1017
- corresponds to whether EFM, GFM, HMRC, or SBR validation is in effect.
1018
- """
1019
- effectiveMessageCode = None
1020
- _validationType = self.modelManager.disclosureSystem.validationType
1021
- _exclusiveTypesPattern = self.modelManager.disclosureSystem.exclusiveTypesPattern
1022
-
1023
- for argCode in messageCodes if isinstance(messageCodes,tuple) else (messageCodes,):
1024
- if (isinstance(argCode, ModelValue.QName) or
1025
- (_validationType and argCode and argCode.startswith(_validationType)) or
1026
- (not _exclusiveTypesPattern or _exclusiveTypesPattern.match(argCode or "") == None)):
1027
- effectiveMessageCode = argCode
1028
- break
1029
- return effectiveMessageCode
1030
-
1031
1010
  # isLoggingEffectiveFor( messageCodes= messageCode= level= )
1032
1011
  def isLoggingEffectiveFor(self, **kwargs: Any) -> bool: # args can be messageCode(s) and level
1033
1012
  logger = self.logger
1034
1013
  if logger is None:
1035
1014
  return False
1036
- assert hasattr(logger, 'messageCodeFilter'), 'messageCodeFilter not set on controller logger.'
1037
- assert hasattr(logger, 'messageLevelFilter'), 'messageLevelFilter not set on controller logger.'
1038
- if "messageCodes" in kwargs or "messageCode" in kwargs:
1039
- if "messageCodes" in kwargs:
1040
- messageCodes = kwargs["messageCodes"]
1041
- else:
1042
- messageCodes = kwargs["messageCode"]
1043
- messageCode = self.effectiveMessageCode(messageCodes)
1044
- codeEffective = (messageCode and
1045
- (not logger.messageCodeFilter or logger.messageCodeFilter.match(messageCode)))
1046
- else:
1047
- codeEffective = True
1048
- if "level" in kwargs and logger.messageLevelFilter:
1049
- levelEffective = logger.messageLevelFilter.match(kwargs["level"].lower())
1050
- else:
1051
- levelEffective = True
1052
- return bool(codeEffective and levelEffective)
1053
-
1054
- def logArguments(self, messageCode: str, msg: str, codedArgs: dict[str, str]) -> Any:
1055
- # Prepares arguments for logger function as per info() below.
1056
-
1057
- def propValues(properties: Any) -> Any:
1058
- # deref objects in properties
1059
- return [(p[0], str(p[1])) if len(p) == 2 else (p[0], str(p[1]), propValues(p[2]))
1060
- for p in properties if 2 <= len(p) <= 3]
1061
- # determine message and extra arguments
1062
- fmtArgs: dict[str, LoggableValue] = {}
1063
- extras: dict[str, Any] = {"messageCode":messageCode}
1064
- modelObjectArgs: tuple[Any, ...] | list[Any] = ()
1065
-
1066
- for argName, argValue in codedArgs.items():
1067
- if argName in ("modelObject", "modelXbrl", "modelDocument"):
1068
- try:
1069
- entryUrl = self.modelDocument.uri # type: ignore[union-attr]
1070
- except AttributeError:
1071
- try:
1072
- entryUrl = self.entryLoadingUrl
1073
- except AttributeError:
1074
- entryUrl = self.fileSource.url
1075
- refs: list[dict[str, Any]] = []
1076
- modelObjectArgs_complex = argValue if isinstance(argValue, (tuple,list,set)) else (argValue,)
1077
- modelObjectArgs = flattenSequence(modelObjectArgs_complex)
1078
- for arg in modelObjectArgs:
1079
- if arg is not None:
1080
- if isinstance(arg, str):
1081
- objectUrl = arg
1082
- else:
1083
- try:
1084
- objectUrl = arg.modelDocument.displayUri
1085
- except AttributeError:
1086
- try:
1087
- objectUrl = arg.displayUri
1088
- except AttributeError:
1089
- try:
1090
- objectUrl = self.modelDocument.displayUri # type: ignore[union-attr]
1091
- except AttributeError:
1092
- objectUrl = getattr(self, "entryLoadingUrl", "")
1093
- try:
1094
- if objectUrl.endswith("/_IXDS"):
1095
- file = objectUrl[:-6] # inline document set or report package
1096
- elif objectUrl in self.logRefFileRelUris.get(entryUrl, EMPTY_TUPLE):
1097
- file = self.logRefFileRelUris[entryUrl][objectUrl]
1098
- else:
1099
- file = UrlUtil.relativeUri(entryUrl, objectUrl)
1100
- self.logRefFileRelUris[entryUrl][objectUrl] = file
1101
- except:
1102
- file = ""
1103
- ref: dict[str, Any] = {}
1104
- if isinstance(arg,(ModelObject, ObjectPropertyViewWrapper)):
1105
- _arg:ModelObject = arg.modelObject if isinstance(arg, ObjectPropertyViewWrapper) else arg
1106
- if len(modelObjectArgs) > 1 and getattr(arg,"tag",None) == "instance":
1107
- continue # skip IXDS top level element
1108
- ref["href"] = file + "#" + cast(str, XmlUtil.elementFragmentIdentifier(_arg))
1109
- ref["sourceLine"] = _arg.sourceline
1110
- ref["objectId"] = _arg.objectId()
1111
- if self.logRefObjectProperties:
1112
- try:
1113
- ref["properties"] = propValues(arg.propertyView)
1114
- except AttributeError:
1115
- pass # is a default properties entry appropriate or needed?
1116
- if self.logRefHasPluginProperties:
1117
- refProperties: Any = ref.get("properties", {})
1118
- for pluginXbrlMethod in pluginClassMethods("Logging.Ref.Properties"):
1119
- pluginXbrlMethod(arg, refProperties, codedArgs)
1120
- if refProperties:
1121
- ref["properties"] = refProperties
1122
- else:
1123
- ref["href"] = file
1124
- try:
1125
- ref["sourceLine"] = arg.sourceline
1126
- except AttributeError:
1127
- pass # arg may not have sourceline, ignore if so
1128
- if self.logRefHasPluginAttrs:
1129
- refAttributes: dict[str, str] = {}
1130
- for pluginXbrlMethod in pluginClassMethods("Logging.Ref.Attributes"):
1131
- pluginXbrlMethod(arg, refAttributes, codedArgs)
1132
- if refAttributes:
1133
- ref["customAttributes"] = refAttributes
1134
- refs.append(ref)
1135
- extras["refs"] = refs
1136
- elif argName == "sourceFileLine":
1137
- # sourceFileLines is pairs of file and line numbers, e.g., ((file,line),(file2,line2),...)
1138
- ref = {}
1139
- if isinstance(argValue, (tuple,list)):
1140
- ref["href"] = str(argValue[0])
1141
- if len(argValue) > 1 and argValue[1]:
1142
- ref["sourceLine"] = str(argValue[1])
1143
- else:
1144
- ref["href"] = str(argValue)
1145
- extras["refs"] = [ref]
1146
- elif argName == "sourceFileLines":
1147
- # sourceFileLines is tuple/list of pairs of file and line numbers, e.g., ((file,line),(file2,line2),...)
1148
- sf_refs: list[dict[str, str]] = []
1149
- argvalues: tuple[Any, ...] | list[Any] = argValue if isinstance(argValue, (tuple, list)) else (argValue,)
1150
- for arg in argvalues:
1151
- ref = {}
1152
- if isinstance(arg, (tuple, list)):
1153
- arg_: tuple[Any, ...] | list[Any] = arg
1154
- ref["href"] = str(arg_[0])
1155
- if len(arg_) > 1 and arg_[1]:
1156
- ref["sourceLine"] = str(arg_[1])
1157
- else:
1158
- ref["href"] = str(arg)
1159
- sf_refs.append(ref)
1160
- extras["refs"] = sf_refs
1161
- elif argName == "sourceLine":
1162
- if isinstance(argValue, int): # must be sortable with int's in logger
1163
- extras["sourceLine"] = argValue
1164
- elif argName not in ("exc_info", "messageCodes"):
1165
- fmtArgs[argName] = self.loggableValue(argValue) # dereference anything not loggable
1166
-
1167
- if "refs" not in extras:
1168
- try:
1169
- file = os.path.basename(cast('ModelDocumentClass', self.modelDocument).displayUri)
1170
- except AttributeError:
1171
- try:
1172
- file = os.path.basename(self.entryLoadingUrl)
1173
- except:
1174
- file = ""
1175
- extras["refs"] = [{"href": file}]
1176
- for pluginXbrlMethod in pluginClassMethods("Logging.Message.Parameters"):
1177
- # plug in can rewrite msg string or return msg if not altering msg
1178
- msg = pluginXbrlMethod(messageCode, msg, modelObjectArgs, fmtArgs) or msg
1179
- return (messageCode,
1180
- (msg, fmtArgs) if fmtArgs else (msg,),
1181
- extras)
1182
-
1183
- def loggableValue(self, argValue: Any) -> LoggableValue: # must be dereferenced and not related to object lifetimes
1184
- if argValue is None:
1185
- return "(none)"
1186
- if isinstance(argValue, bool):
1187
- return str(argValue).lower() # show lower case true/false xml values
1188
- if isinstance(argValue, int):
1189
- # need locale-dependent formatting
1190
- return format_string(self.modelManager.locale, '%i', argValue)
1191
- if isinstance(argValue, (float, Decimal)):
1192
- # need locale-dependent formatting
1193
- return format_string(self.modelManager.locale, '%f', argValue)
1194
- if isinstance(argValue, tuple):
1195
- return tuple(self.loggableValue(x) for x in argValue)
1196
- if isinstance(argValue, list):
1197
- return [self.loggableValue(x) for x in argValue]
1198
- if isinstance(argValue, set):
1199
- return {self.loggableValue(x) for x in argValue}
1200
- if isinstance(argValue, dict):
1201
- return dict((self.loggableValue(k), self.loggableValue(v)) for k, v in argValue.items())
1202
- return str(argValue)
1015
+ return self.errorManager.isLoggingEffectiveFor(logger, **kwargs)
1203
1016
 
1204
1017
  def debug(self, codes: str | tuple[str, ...], msg: str, **args: Any) -> None:
1205
1018
  """Same as error(), but as info
@@ -1222,33 +1035,24 @@ class ModelXbrl:
1222
1035
  def log(self, level: str, codes: Any, msg: str, **args: Any) -> None:
1223
1036
  """Same as error(), but level passed in as argument
1224
1037
  """
1225
- logger = self.logger
1226
- if logger is None:
1227
- return
1228
- assert hasattr(logger, 'messageCodeFilter'), 'messageCodeFilter not set on controller logger.'
1229
- assert hasattr(logger, 'messageLevelFilter'), 'messageLevelFilter not set on controller logger.'
1230
- # determine logCode
1231
- messageCode = self.effectiveMessageCode(codes)
1232
- if messageCode == "asrtNoLog":
1233
- self.errors.append(args["assertionResults"])
1038
+ if self.logger is None:
1234
1039
  return
1235
- if self.logHasRelevelerPlugin:
1236
- for pluginXbrlMethod in pluginClassMethods("Logging.Severity.Releveler"):
1237
- level, messageCode = pluginXbrlMethod(self, level, messageCode, args) # args must be passed as dict because it may contain modelXbrl or messageCode key value
1238
- if (messageCode and
1239
- (not logger.messageCodeFilter or logger.messageCodeFilter.match(messageCode)) and
1240
- (not logger.messageLevelFilter or logger.messageLevelFilter.match(level.lower()))):
1241
- # note that plugin Logging.Message.Parameters may rewrite messageCode which now occurs after filtering on messageCode
1242
- messageCode, logArgs, extras = self.logArguments(messageCode, msg, args)
1243
- numericLevel = logging._checkLevel(level) #type: ignore[attr-defined]
1244
- self.logCount[numericLevel] = self.logCount.get(numericLevel, 0) + 1
1245
- if numericLevel >= self.errorCaptureLevel:
1246
- try: # if there's a numeric errorCount arg, extend messages codes by count
1247
- self.errors.extend([messageCode] * int(logArgs[1]["errorCount"]))
1248
- except (IndexError, KeyError, ValueError): # no msgArgs, no errorCount, or not int
1249
- self.errors.append(messageCode) # assume one error occurence
1250
- """@messageCatalog=[]"""
1251
- logger.log(numericLevel, *logArgs, exc_info=args.get("exc_info"), extra=extras)
1040
+ entryLoadingUrl = None
1041
+ try:
1042
+ entryLoadingUrl = self.entryLoadingUrl
1043
+ except AttributeError:
1044
+ pass
1045
+ self.errorManager.log(
1046
+ self.logger,
1047
+ level,
1048
+ codes,
1049
+ msg,
1050
+ sourceModelXbrl=self,
1051
+ fileSource=self.fileSource,
1052
+ entryLoadingUrl=entryLoadingUrl,
1053
+ logRefObjectProperties=self.logRefObjectProperties,
1054
+ **args
1055
+ )
1252
1056
 
1253
1057
  def error(self, codes: str | tuple[str, ...], msg: str, **args: Any) -> None:
1254
1058
  """Logs a message as info, by code, logging-system message text (using %(name)s named arguments
@@ -1373,3 +1177,11 @@ class ModelXbrl:
1373
1177
  qnameUtrUnits[unitQName] = unit
1374
1178
  self._qnameUtrUnits = qnameUtrUnits
1375
1179
  return self._qnameUtrUnits
1180
+
1181
+ @property
1182
+ def errors(self) -> list[str | None]:
1183
+ return self.errorManager.errors
1184
+
1185
+ @property
1186
+ def logCount(self) -> dict[str, int]:
1187
+ return self.errorManager.logCount
arelle/PluginManager.py CHANGED
@@ -602,6 +602,10 @@ def loadModule(moduleInfo: dict[str, Any], packagePrefix: str="") -> None:
602
602
  logPluginTrace(_msg, logging.ERROR)
603
603
 
604
604
 
605
+ def hasPluginWithHook(name: str) -> bool:
606
+ return next(pluginClassMethods(name), None) is not None
607
+
608
+
605
609
  def pluginClassMethods(className: str) -> Iterator[Callable[..., Any]]:
606
610
  if pluginConfig:
607
611
  try:
arelle/Validate.py CHANGED
@@ -298,6 +298,7 @@ class Validate:
298
298
  self._testcaseValidateInputDTS(testcase, modelTestcaseVariation, errorCaptureLevel, parameters, inputDTSes, baseForElement, resultIsXbrlInstance)
299
299
  # update ui thread via modelManager (running in background here)
300
300
  self.modelXbrl.modelManager.viewModelObject(self.modelXbrl, modelTestcaseVariation.objectId())
301
+ self.modelXbrl.modelManager.cntlr.testcaseVariationReset()
301
302
  modelTestcaseVariation.duration = time.perf_counter() - startTime
302
303
 
303
304
  def _testcaseLoadReadMeFirstUri(self, testcase, modelTestcaseVariation, index, readMeFirstUri, resultIsVersioningReport, resultIsTaxonomyPackage, inputDTSes, errorCaptureLevel, baseForElement, parameters):
@@ -730,6 +731,7 @@ class Validate:
730
731
  _errors = [e for e in errors if isinstance(e,str) and not _blockPattern.match(e)]
731
732
  else:
732
733
  _errors = errors
734
+ _errors.extend(self.modelXbrl.modelManager.cntlr.errors)
733
735
  numErrors = sum(isinstance(e,(QName,str)) for e in _errors) # does not include asserton dict results
734
736
  hasAssertionResult = any(isinstance(e,dict) for e in _errors)
735
737
  expected = modelTestcaseVariation.expected
arelle/XbrlConst.py CHANGED
@@ -40,6 +40,7 @@ builtinAttributes = {
40
40
  }
41
41
  ref2004 = "http://www.xbrl.org/2004/ref"
42
42
  ref2006 = "http://www.xbrl.org/2006/ref"
43
+ svg = "http://www.w3.org/2000/svg"
43
44
  xml = "http://www.w3.org/XML/1998/namespace"
44
45
  xbrli = "http://www.xbrl.org/2003/instance"
45
46
  xhtmlBaseIdentifier = "{http://www.w3.org/1999/xhtml}base"
arelle/_version.py CHANGED
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '2.37.44'
21
- __version_tuple__ = version_tuple = (2, 37, 44)
20
+ __version__ = version = '2.37.46'
21
+ __version_tuple__ = version_tuple = (2, 37, 46)
@@ -5,15 +5,15 @@ from enum import Enum
5
5
 
6
6
  from arelle.ModelValue import qname
7
7
 
8
- class DeiFormType(Enum):
8
+ class FormType(Enum):
9
9
  FORM_2_4 = '第二号の四様式'
10
10
  FORM_2_7 = '第二号の七様式'
11
11
  FORM_3 = '第三号様式'
12
12
 
13
13
  CORPORATE_FORMS =frozenset([
14
- DeiFormType.FORM_2_4,
15
- DeiFormType.FORM_2_7,
16
- DeiFormType.FORM_3,
14
+ FormType.FORM_2_4,
15
+ FormType.FORM_2_7,
16
+ FormType.FORM_3,
17
17
  ])
18
18
  qnEdinetManifestInsert = qname("{http://disclosure.edinet-fsa.go.jp/2013/manifest}insert")
19
19
  qnEdinetManifestInstance = qname("{http://disclosure.edinet-fsa.go.jp/2013/manifest}instance")
@@ -0,0 +1,80 @@
1
+ """
2
+ See COPYRIGHT.md for copyright information.
3
+ """
4
+ from __future__ import annotations
5
+
6
+ from dataclasses import dataclass
7
+ from typing import TYPE_CHECKING
8
+
9
+ from arelle.Cntlr import Cntlr
10
+ from arelle.typing import TypeGetText
11
+ from arelle.utils.PluginData import PluginData
12
+
13
+ if TYPE_CHECKING:
14
+ from .ManifestInstance import ManifestInstance
15
+
16
+ _: TypeGetText
17
+
18
+
19
+ @dataclass
20
+ class ControllerPluginData(PluginData):
21
+ _manifestInstancesById: dict[str, ManifestInstance]
22
+
23
+ def __init__(self, name: str):
24
+ super().__init__(name)
25
+ self._manifestInstancesById = {}
26
+
27
+ def __hash__(self) -> int:
28
+ return id(self)
29
+
30
+ def addManifestInstance(self, manifestInstance: ManifestInstance) -> None:
31
+ """
32
+ Add a manifest instance with unique ID to the plugin data.
33
+ """
34
+ self._manifestInstancesById[manifestInstance.id] = manifestInstance
35
+
36
+ def getManifestInstances(self) -> list[ManifestInstance]:
37
+ """
38
+ Retrieve all loaded manifest instances.
39
+ """
40
+ return list(self._manifestInstancesById.values())
41
+
42
+ def matchManifestInstance(self, ixdsDocUrls: list[str]) -> ManifestInstance | None:
43
+ """
44
+ Match a manifest instance based on the provided ixdsDocUrls.
45
+ A one-to-one mapping must exist between the model's IXDS document URLs and the manifest instance's IXBRL files.
46
+ :param ixdsDocUrls: A model's list of IXDS document URLs.
47
+ :return: A matching ManifestInstance if found, otherwise None.
48
+ """
49
+ modelUrls = set(ixdsDocUrls)
50
+ matchedInstance = None
51
+ for instance in self._manifestInstancesById.values():
52
+ if len(instance.ixbrlFiles) != len(ixdsDocUrls):
53
+ continue
54
+ manifestUrls = {str(path) for path in instance.ixbrlFiles}
55
+ unmatchedModelUrls = set(modelUrls)
56
+ unmatchedManifestUrls = set(manifestUrls)
57
+ for modelUrl in modelUrls:
58
+ if modelUrl not in unmatchedModelUrls:
59
+ continue
60
+ for manifestUrl in manifestUrls:
61
+ if modelUrl.endswith(manifestUrl):
62
+ unmatchedModelUrls.remove(modelUrl)
63
+ unmatchedManifestUrls.remove(manifestUrl)
64
+ break
65
+ if len(unmatchedModelUrls) > 0:
66
+ continue
67
+ if len(unmatchedManifestUrls) > 0:
68
+ continue
69
+ matchedInstance = instance
70
+ break
71
+ return matchedInstance
72
+
73
+ @staticmethod
74
+ def get(cntlr: Cntlr, name: str) -> ControllerPluginData:
75
+ controllerPluginData = cntlr.getPluginData(name)
76
+ if controllerPluginData is None:
77
+ controllerPluginData = ControllerPluginData(name)
78
+ cntlr.setPluginData(controllerPluginData)
79
+ assert isinstance(controllerPluginData, ControllerPluginData), "Expected ControllerPluginData instance."
80
+ return controllerPluginData
@@ -8,7 +8,7 @@ from functools import cached_property, lru_cache
8
8
  from pathlib import Path
9
9
 
10
10
 
11
- class FormType(Enum):
11
+ class InstanceType(Enum):
12
12
  ATTACH_DOC = "AttachDoc"
13
13
  AUDIT_DOC = "AuditDoc"
14
14
  ENGLISH_DOC = "EnglishDoc"
@@ -17,7 +17,7 @@ class FormType(Enum):
17
17
  PUBLIC_DOC = "PublicDoc"
18
18
 
19
19
  @classmethod
20
- def parse(cls, value: str) -> FormType | None:
20
+ def parse(cls, value: str) -> InstanceType | None:
21
21
  try:
22
22
  return cls(value)
23
23
  except ValueError:
@@ -60,12 +60,12 @@ class ExtensionCategory(Enum):
60
60
 
61
61
 
62
62
  FORM_TYPE_EXTENSION_CATEGORIES = {
63
- FormType.ATTACH_DOC: ExtensionCategory.ATTACH,
64
- FormType.AUDIT_DOC: ExtensionCategory.DOC,
65
- FormType.ENGLISH_DOC: ExtensionCategory.ENGLISH_DOC,
66
- FormType.PRIVATE_ATTACH: ExtensionCategory.ATTACH,
67
- FormType.PRIVATE_DOC: ExtensionCategory.DOC,
68
- FormType.PUBLIC_DOC: ExtensionCategory.DOC,
63
+ InstanceType.ATTACH_DOC: ExtensionCategory.ATTACH,
64
+ InstanceType.AUDIT_DOC: ExtensionCategory.DOC,
65
+ InstanceType.ENGLISH_DOC: ExtensionCategory.ENGLISH_DOC,
66
+ InstanceType.PRIVATE_ATTACH: ExtensionCategory.ATTACH,
67
+ InstanceType.PRIVATE_DOC: ExtensionCategory.DOC,
68
+ InstanceType.PUBLIC_DOC: ExtensionCategory.DOC,
69
69
  }
70
70
 
71
71