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.
Files changed (71) hide show
  1. arelle/BetaFeatures.py +0 -21
  2. arelle/Cntlr.py +7 -1
  3. arelle/CntlrCmdLine.py +95 -19
  4. arelle/CntlrWinMain.py +4 -1
  5. arelle/DialogFind.py +1 -1
  6. arelle/ModelDtsObject.py +2 -0
  7. arelle/ModelObject.py +16 -18
  8. arelle/ModelObjectFactory.py +17 -15
  9. arelle/ModelXbrl.py +7 -1
  10. arelle/PluginManager.py +1 -5
  11. arelle/RuntimeOptions.py +1 -0
  12. arelle/UrlUtil.py +11 -0
  13. arelle/Validate.py +3 -3
  14. arelle/ValidateXbrl.py +2 -1
  15. arelle/ValidateXbrlCalcs.py +210 -186
  16. arelle/WebCache.py +2 -8
  17. arelle/XbrlConst.py +2 -0
  18. arelle/XmlUtil.py +16 -21
  19. arelle/XmlValidate.py +4 -6
  20. arelle/_version.py +2 -2
  21. arelle/config/rosettaEntitlements.plist +8 -0
  22. arelle/conformance/CSVTestcaseLoader.py +1 -1
  23. arelle/formula/XPathContext.py +3 -3
  24. arelle/logging/formatters/LogFormatter.py +3 -1
  25. arelle/packages/report/ReportPackage.py +9 -1
  26. arelle/plugin/inlineXbrlDocumentSet.py +1 -3
  27. arelle/plugin/validate/DBA/DisclosureSystems.py +19 -1
  28. arelle/plugin/validate/DBA/resources/config.xml +5 -0
  29. arelle/plugin/validate/DBA/rules/fr.py +19 -2
  30. arelle/plugin/validate/DBA/rules/tc.py +2 -0
  31. arelle/plugin/validate/DBA/rules/th.py +6 -0
  32. arelle/plugin/validate/DBA/rules/tm.py +18 -5
  33. arelle/plugin/validate/DBA/rules/tr.py +11 -5
  34. arelle/plugin/validate/EDINET/ControllerPluginData.py +2 -1
  35. arelle/plugin/validate/EDINET/NamespaceConfig.py +50 -0
  36. arelle/plugin/validate/EDINET/PluginValidationDataExtension.py +33 -78
  37. arelle/plugin/validate/EDINET/TableOfContentsBuilder.py +153 -51
  38. arelle/plugin/validate/EDINET/rules/contexts.py +1 -1
  39. arelle/plugin/validate/EDINET/rules/edinet.py +163 -20
  40. arelle/plugin/validate/EDINET/rules/gfm.py +88 -1
  41. arelle/plugin/validate/EDINET/rules/upload.py +1 -1
  42. arelle/plugin/validate/ESEF/ESEF_2021/ValidateXbrlFinally.py +3 -3
  43. arelle/plugin/validate/ESEF/ESEF_Current/DTS.py +42 -14
  44. arelle/plugin/validate/ESEF/ESEF_Current/ValidateXbrlFinally.py +14 -3
  45. arelle/plugin/validate/ESEF/__init__.py +10 -5
  46. arelle/plugin/validate/ESEF/resources/authority-validations.json +10 -5
  47. arelle/plugin/validate/NL/DisclosureSystems.py +22 -0
  48. arelle/plugin/validate/NL/PluginValidationDataExtension.py +20 -0
  49. arelle/plugin/validate/NL/ValidationPluginExtension.py +48 -3
  50. arelle/plugin/validate/NL/resources/config.xml +18 -0
  51. arelle/plugin/validate/NL/rules/br_kvk.py +9 -54
  52. arelle/plugin/validate/NL/rules/fg_nl.py +7 -38
  53. arelle/plugin/validate/NL/rules/fr_kvk.py +7 -42
  54. arelle/plugin/validate/NL/rules/fr_nl.py +25 -140
  55. arelle/plugin/validate/NL/rules/nl_kvk.py +125 -12
  56. arelle/plugin/validate/ROS/rules/ros.py +3 -1
  57. arelle/plugin/validate/UK/__init__.py +70 -14
  58. arelle/utils/EntryPointDetection.py +17 -11
  59. arelle/utils/validate/ESEFImage.py +3 -3
  60. arelle/utils/validate/Validation.py +9 -0
  61. arelle/utils/validate/ValidationPlugin.py +14 -12
  62. {arelle_release-2.37.71.dist-info → arelle_release-2.38.0.dist-info}/METADATA +10 -5
  63. {arelle_release-2.37.71.dist-info → arelle_release-2.38.0.dist-info}/RECORD +67 -69
  64. {arelle_release-2.37.71.dist-info → arelle_release-2.38.0.dist-info}/licenses/LICENSE.md +0 -3
  65. arelle/model/CommentBase.py +0 -9
  66. arelle/model/ElementBase.py +0 -11
  67. arelle/model/PIBase.py +0 -10
  68. arelle/model/__init__.py +0 -15
  69. {arelle_release-2.37.71.dist-info → arelle_release-2.38.0.dist-info}/WHEEL +0 -0
  70. {arelle_release-2.37.71.dist-info → arelle_release-2.38.0.dist-info}/entry_points.txt +0 -0
  71. {arelle_release-2.37.71.dist-info → arelle_release-2.38.0.dist-info}/top_level.txt +0 -0
@@ -5,7 +5,7 @@ from __future__ import annotations
5
5
  from collections import defaultdict, OrderedDict
6
6
  from math import (log10, isnan, isinf, fabs, floor, pow)
7
7
  import decimal
8
- from typing import TYPE_CHECKING
8
+ from typing import TYPE_CHECKING, Iterable
9
9
  from regex import compile as re_compile
10
10
  import hashlib
11
11
  from arelle import Locale, XbrlConst, XbrlUtil
@@ -14,6 +14,7 @@ from arelle.PythonUtil import flattenSequence, strTruncate
14
14
  from arelle.XmlValidateConst import UNVALIDATED, VALID
15
15
  from arelle.utils.Contexts import partitionModelXbrlContexts
16
16
  from arelle.utils.Units import partitionModelXbrlUnits
17
+ from arelle.utils.validate.Validation import Validation
17
18
 
18
19
  if TYPE_CHECKING:
19
20
  from _decimal import Decimal
@@ -92,8 +93,8 @@ EMPTY_SET = set()
92
93
  def rangeToStr(a, b, inclA, inclB) -> str:
93
94
  return {True:"[", False: "("}[inclA] + f"{a}, {b}" + {True:"]", False: ")"}[inclB]
94
95
 
95
- def validate(modelXbrl, validateCalcs) -> None:
96
- ValidateXbrlCalcs(modelXbrl, validateCalcs).validate()
96
+ def validate(modelXbrl, validateCalcs) -> Iterable[Validation]:
97
+ yield from ValidateXbrlCalcs(modelXbrl, validateCalcs).validate()
97
98
 
98
99
  class ValidateXbrlCalcs:
99
100
  def __init__(self, modelXbrl, validateCalcs):
@@ -120,7 +121,7 @@ class ValidateXbrlCalcs:
120
121
  self.requiresElementFacts = defaultdict(list)
121
122
  self.conceptsInRequiresElement = set()
122
123
 
123
- def validate(self):
124
+ def validate(self) -> Iterable[Validation]:
124
125
  # note that calc linkbase checks need to be performed even if no facts in instance (e.g., to detect duplicate relationships)
125
126
  modelXbrl = self.modelXbrl
126
127
  xbrl21 = self.xbrl21
@@ -142,10 +143,16 @@ class ValidateXbrlCalcs:
142
143
  oimErrs.add(e)
143
144
  if any(e == "xbrlxe:unsupportedTuple" for e in oimErrs):
144
145
  # ignore this error and change to warning
145
- modelXbrl.warning("calc11e:tuplesInReportWarning","Validating of calculations ignores tuples.")
146
+ yield Validation.warning(
147
+ "calc11e:tuplesInReportWarning",
148
+ _("Validating of calculations ignores tuples."),
149
+ )
146
150
  if any(e in oimXbrlxeBlockingErrorCodes for e in oimErrs if e != "xbrlxe:unsupportedTuple"):
147
- modelXbrl.warning("calc11e:oimIncompatibleReportWarning","Validating of calculations is skipped due to OIM errors.")
148
- return;
151
+ yield Validation.warning(
152
+ "calc11e:oimIncompatibleReportWarning",
153
+ _("Validating of calculations is skipped due to OIM errors."),
154
+ )
155
+ return
149
156
 
150
157
  # identify equal contexts
151
158
  modelXbrl.profileActivity()
@@ -165,14 +172,19 @@ class ValidateXbrlCalcs:
165
172
  if xbrl21:
166
173
  for baseSetKey in modelXbrl.baseSets.keys():
167
174
  arcrole, ELR, linkqname, arcqname = baseSetKey
168
- if ELR and linkqname and arcqname:
169
- if arcrole in (XbrlConst.essenceAlias, XbrlConst.requiresElement):
170
- conceptsSet = {XbrlConst.essenceAlias:self.conceptsInEssencesAlias,
171
- XbrlConst.requiresElement:self.conceptsInRequiresElement}[arcrole]
172
- for modelRel in modelXbrl.relationshipSet(arcrole,ELR,linkqname,arcqname).modelRelationships:
173
- for concept in (modelRel.fromModelObject, modelRel.toModelObject):
174
- if concept is not None and concept.qname is not None:
175
- conceptsSet.add(concept)
175
+ if (
176
+ not ELR or
177
+ not linkqname or
178
+ not arcqname or
179
+ arcrole not in (XbrlConst.essenceAlias, XbrlConst.requiresElement)
180
+ ):
181
+ continue
182
+ conceptsSet = {XbrlConst.essenceAlias:self.conceptsInEssencesAlias,
183
+ XbrlConst.requiresElement:self.conceptsInRequiresElement}[arcrole]
184
+ for modelRel in modelXbrl.relationshipSet(arcrole,ELR,linkqname,arcqname).modelRelationships:
185
+ for concept in (modelRel.fromModelObject, modelRel.toModelObject):
186
+ if concept is not None and concept.qname is not None:
187
+ conceptsSet.add(concept)
176
188
  modelXbrl.profileActivity("... identify requires-element and esseance-aliased concepts", minTimeToShow=1.0)
177
189
 
178
190
  self.bindFacts(modelXbrl.facts,[modelXbrl.modelDocument.xmlRootElement])
@@ -188,168 +200,180 @@ class ValidateXbrlCalcs:
188
200
  # identify calcluation & essence-alias base sets (by key)
189
201
  for baseSetKey in modelXbrl.baseSets.keys():
190
202
  arcrole, ELR, linkqname, arcqname = baseSetKey
191
- if ELR and linkqname and arcqname:
192
- if arcrole in allArcroles:
193
- relsSet = modelXbrl.relationshipSet(arcrole,ELR,linkqname,arcqname)
194
- sumConceptItemRels.clear()
195
- if arcrole in summationArcroles:
196
- fromRelationships = relsSet.fromModelObjects()
197
- for sumConcept, modelRels in fromRelationships.items():
198
- sumBindingKeys = self.sumConceptBindKeys[sumConcept]
199
- dupBindingKeys = set()
200
- boundSumKeys = set()
201
- # determine boundSums
202
- for modelRel in modelRels:
203
- itemConcept = modelRel.toModelObject
204
- if itemConcept is not None and itemConcept.qname is not None:
205
- itemBindingKeys = self.itemConceptBindKeys[itemConcept]
206
- boundSumKeys |= sumBindingKeys & itemBindingKeys
207
- if calc11:
208
- siRels = sumConceptItemRels[sumConcept]
209
- if itemConcept in siRels:
210
- modelXbrl.error("calc11e:duplicateCalculationRelationships",
211
- _("Duplicate summation-item relationships from total concept %(sumConcept)s to contributing concept %(itemConcept)s in link role %(linkrole)s"),
212
- modelObject=(siRels[itemConcept], modelRel), linkrole=modelRel.linkrole,
213
- sumConcept=sumConcept.qname, itemConcept=itemConcept.qname)
214
- siRels[itemConcept] = modelRel
215
- if not sumConcept.isDecimal or not itemConcept.isDecimal:
216
- modelXbrl.error("calc11e:nonDecimalItemNode",
217
- _("The source and target of a Calculations v1.1 relationship MUST both be decimal concepts: %(sumConcept)s, %(itemConcept)s, link role %(linkrole)s"),
218
- modelObject=(sumConcept, itemConcept, modelRel), linkrole=modelRel.linkrole,
219
- sumConcept=sumConcept.qname, itemConcept=itemConcept.qname)
220
-
221
- # add up rounded items
222
- boundSums = defaultdict(decimal.Decimal) # sum of facts meeting factKey
223
- boundIntervals = {} # interval sum of facts meeting factKey
224
- blockedIntervals = set() # bind Keys for summations which have an inconsistency
225
- boundSummationItems = defaultdict(list) # corresponding fact refs for messages
226
- boundIntervalItems = defaultdict(list) # corresponding fact refs for messages
227
- for modelRel in modelRels:
228
- w = modelRel.weightDecimal
229
- itemConcept = modelRel.toModelObject
230
- if itemConcept is not None:
231
- for itemBindKey in boundSumKeys:
232
- ancestor, contextHash, unit = itemBindKey
233
- factKey = (itemConcept, ancestor, contextHash, unit)
234
- _itemFacts = self.itemFacts.get(factKey,())
235
- if xbrl21:
236
- for fact in _itemFacts:
237
- if not fact.isNil:
238
- if fact in self.duplicatedFacts:
239
- dupBindingKeys.add(itemBindKey)
240
- elif fact not in self.consistentDupFacts:
241
- roundedValue = roundFact(fact, self.inferDecimals)
242
- boundSums[itemBindKey] += roundedValue * w
243
- boundSummationItems[itemBindKey].append(wrappedFactWithWeight(fact,w,roundedValue))
244
- if calc11 and _itemFacts:
245
- y1, y2, iY1, iY2 = self.consistentFactValueInterval(_itemFacts, calc11t)
246
- if y1 is INCONSISTENT:
247
- blockedIntervals.add(itemBindKey)
248
- elif y1 is not NIL_FACT_SET:
249
- x1, x2, iX1, iX2 = boundIntervals.get(itemBindKey, ZERO_RANGE)
250
- y1 *= w
251
- y2 *= w
252
- if y2 < y1:
253
- y1, y2 = y2, y1
254
- boundIntervals[itemBindKey] = (x1 + y1, x2 + y2, iX1 and iY1, iX2 and iY2)
255
- boundIntervalItems[itemBindKey].extend(_itemFacts)
256
- for sumBindKey in boundSumKeys:
257
- ancestor, contextHash, unit = sumBindKey
258
- factKey = (sumConcept, ancestor, contextHash, unit)
259
- if factKey in self.sumFacts:
260
- sumFacts = self.sumFacts[factKey]
261
- if xbrl21:
262
- for fact in sumFacts:
263
- if not fact.isNil:
264
- if fact in self.duplicatedFacts:
265
- dupBindingKeys.add(sumBindKey)
266
- elif (sumBindKey in boundSums and sumBindKey not in dupBindingKeys
267
- and fact not in self.consistentDupFacts
268
- and not (len(sumFacts) > 1 and not self.deDuplicate)): # don't bind if sum duplicated without dedup option
269
- roundedSum = roundFact(fact, self.inferDecimals)
270
- roundedItemsSum = roundFact(fact, self.inferDecimals, vDecimal=boundSums[sumBindKey])
271
- if roundedItemsSum != roundFact(fact, self.inferDecimals):
272
- d = inferredDecimals(fact)
273
- if isnan(d) or isinf(d): d = 4
274
- _boundSummationItems = boundSummationItems[sumBindKey]
275
- unreportedContribingItemQnames = [] # list the missing/unreported contributors in relationship order
276
- for modelRel in modelRels:
277
- itemConcept = modelRel.toModelObject
278
- if (itemConcept is not None and
279
- (itemConcept, ancestor, contextHash, unit) not in self.itemFacts):
280
- unreportedContribingItemQnames.append(str(itemConcept.qname))
281
- modelXbrl.log('INCONSISTENCY', "xbrl.5.2.5.2:calcInconsistency",
282
- _("Calculation inconsistent from %(concept)s in link role %(linkrole)s reported sum %(reportedSum)s computed sum %(computedSum)s context %(contextID)s unit %(unitID)s unreportedContributingItems %(unreportedContributors)s"),
283
- modelObject=wrappedSummationAndItems(fact, roundedSum, _boundSummationItems),
284
- concept=sumConcept.qname, linkrole=ELR,
285
- linkroleDefinition=modelXbrl.roleTypeDefinition(ELR),
286
- reportedSum=Locale.format_decimal(modelXbrl.locale, roundedSum, 1, max(d,0)),
287
- computedSum=Locale.format_decimal(modelXbrl.locale, roundedItemsSum, 1, max(d,0)),
288
- contextID=fact.context.id, unitID=fact.unit.id if fact.unit is not None else "(none)",
289
- unreportedContributors=", ".join(unreportedContribingItemQnames) or "none")
290
- del unreportedContribingItemQnames[:]
291
- if calc11:
292
- s1, s2, incls1, incls2 = self.consistentFactValueInterval(sumFacts, calc11t)
293
- if s1 is not INCONSISTENT and s1 is not NIL_FACT_SET and sumBindKey not in blockedIntervals and sumBindKey in boundIntervals:
294
- x1, x2, inclx1, inclx2 = boundIntervals[sumBindKey]
295
- a = max(s1, x1)
296
- b = min(s2, x2)
297
- inclA = incls1 | inclx1
298
- inclB = incls2 | inclx2
299
- if (a == b and not (inclA and inclB)) or (a > b):
300
- modelXbrl.log('INCONSISTENCY', "calc11e:inconsistentCalculationUsing" + self.calc11suffix,
301
- _("Calculation inconsistent from %(concept)s in link role %(linkrole)s reported sum %(reportedSum)s computed sum %(computedSum)s context %(contextID)s unit %(unitID)s"),
302
- modelObject=sumFacts + boundIntervalItems[sumBindKey],
303
- concept=sumConcept.qname, linkrole=ELR,
304
- linkroleDefinition=modelXbrl.roleTypeDefinition(ELR),
305
- reportedSum=rangeToStr(s1,s2,incls1,incls2),
306
- computedSum=rangeToStr(x1,x2,inclx1,inclx2),
307
- contextID=sumFacts[0].context.id, unitID=sumFacts[0].unit.id if sumFacts[0].unit is not None else "(none)")
308
- boundSummationItems.clear() # dereference facts in list
309
- boundIntervalItems.clear()
310
- elif arcrole == XbrlConst.essenceAlias:
311
- for modelRel in relsSet.modelRelationships:
312
- essenceConcept = modelRel.fromModelObject
313
- aliasConcept = modelRel.toModelObject
314
- essenceBindingKeys = self.esAlConceptBindKeys[essenceConcept]
315
- aliasBindingKeys = self.esAlConceptBindKeys[aliasConcept]
316
- for esAlBindKey in essenceBindingKeys & aliasBindingKeys:
317
- ancestor, contextHash = esAlBindKey
318
- essenceFactsKey = (essenceConcept, ancestor, contextHash)
319
- aliasFactsKey = (aliasConcept, ancestor, contextHash)
320
- if essenceFactsKey in self.esAlFacts and aliasFactsKey in self.esAlFacts:
321
- for eF in self.esAlFacts[essenceFactsKey]:
322
- for aF in self.esAlFacts[aliasFactsKey]:
323
- essenceUnit = self.mapUnit.get(eF.unit,eF.unit)
324
- aliasUnit = self.mapUnit.get(aF.unit,aF.unit)
325
- if essenceUnit != aliasUnit:
326
- modelXbrl.log('INCONSISTENCY', "xbrl.5.2.6.2.2:essenceAliasUnitsInconsistency",
327
- _("Essence-Alias inconsistent units from %(essenceConcept)s to %(aliasConcept)s in link role %(linkrole)s context %(contextID)s"),
328
- modelObject=(modelRel, eF, aF),
329
- essenceConcept=essenceConcept.qname, aliasConcept=aliasConcept.qname,
330
- linkrole=ELR,
331
- linkroleDefinition=modelXbrl.roleTypeDefinition(ELR),
332
- contextID=eF.context.id)
333
- if not XbrlUtil.vEqual(eF, aF):
334
- modelXbrl.log('INCONSISTENCY', "xbrl.5.2.6.2.2:essenceAliasUnitsInconsistency",
335
- _("Essence-Alias inconsistent value from %(essenceConcept)s to %(aliasConcept)s in link role %(linkrole)s context %(contextID)s"),
336
- modelObject=(modelRel, eF, aF),
337
- essenceConcept=essenceConcept.qname, aliasConcept=aliasConcept.qname,
338
- linkrole=ELR,
339
- linkroleDefinition=modelXbrl.roleTypeDefinition(ELR),
340
- contextID=eF.context.id)
341
- elif arcrole == XbrlConst.requiresElement:
342
- for modelRel in relsSet.modelRelationships:
343
- sourceConcept = modelRel.fromModelObject
344
- requiredConcept = modelRel.toModelObject
345
- if sourceConcept in self.requiresElementFacts and \
346
- not requiredConcept in self.requiresElementFacts:
347
- modelXbrl.log('INCONSISTENCY', "xbrl.5.2.6.2.4:requiresElementInconsistency",
348
- _("Requires-Element %(requiringConcept)s missing required fact for %(requiredConcept)s in link role %(linkrole)s"),
349
- modelObject=sourceConcept,
350
- requiringConcept=sourceConcept.qname, requiredConcept=requiredConcept.qname,
351
- linkrole=ELR,
352
- linkroleDefinition=modelXbrl.roleTypeDefinition(ELR))
203
+ if (
204
+ not ELR or
205
+ not linkqname or
206
+ not arcqname or
207
+ arcrole not in allArcroles
208
+ ):
209
+ continue
210
+ relsSet = modelXbrl.relationshipSet(arcrole,ELR,linkqname,arcqname)
211
+ sumConceptItemRels.clear()
212
+ if arcrole in summationArcroles:
213
+ fromRelationships = relsSet.fromModelObjects()
214
+ for sumConcept, modelRels in fromRelationships.items():
215
+ sumBindingKeys = self.sumConceptBindKeys[sumConcept]
216
+ dupBindingKeys = set()
217
+ boundSumKeys = set()
218
+ # determine boundSums
219
+ for modelRel in modelRels:
220
+ itemConcept = modelRel.toModelObject
221
+ if itemConcept is not None and itemConcept.qname is not None:
222
+ itemBindingKeys = self.itemConceptBindKeys[itemConcept]
223
+ boundSumKeys |= sumBindingKeys & itemBindingKeys
224
+ if calc11:
225
+ siRels = sumConceptItemRels[sumConcept]
226
+ if itemConcept in siRels:
227
+ yield Validation.error('calc11e:duplicateCalculationRelationships',
228
+ _("Duplicate summation-item relationships from total concept %(sumConcept)s to contributing concept %(itemConcept)s in link role %(linkrole)s"),
229
+ modelObject=(siRels[itemConcept], modelRel), linkrole=modelRel.linkrole,
230
+ sumConcept=sumConcept.qname, itemConcept=itemConcept.qname)
231
+ siRels[itemConcept] = modelRel
232
+ if not sumConcept.isDecimal or not itemConcept.isDecimal:
233
+ yield Validation.error('calc11e:nonDecimalItemNode',
234
+ _("The source and target of a Calculations v1.1 relationship MUST both be decimal concepts: %(sumConcept)s, %(itemConcept)s, link role %(linkrole)s"),
235
+ modelObject=(sumConcept, itemConcept, modelRel), linkrole=modelRel.linkrole,
236
+ sumConcept=sumConcept.qname, itemConcept=itemConcept.qname)
237
+
238
+ # add up rounded items
239
+ boundSums = defaultdict(decimal.Decimal) # sum of facts meeting factKey
240
+ boundIntervals = {} # interval sum of facts meeting factKey
241
+ blockedIntervals = set() # bind Keys for summations which have an inconsistency
242
+ boundSummationItems = defaultdict(list) # corresponding fact refs for messages
243
+ boundIntervalItems = defaultdict(list) # corresponding fact refs for messages
244
+ for modelRel in modelRels:
245
+ w = modelRel.weightDecimal
246
+ itemConcept = modelRel.toModelObject
247
+ if itemConcept is None:
248
+ continue
249
+ for itemBindKey in boundSumKeys:
250
+ ancestor, contextHash, unit = itemBindKey
251
+ factKey = (itemConcept, ancestor, contextHash, unit)
252
+ _itemFacts = self.itemFacts.get(factKey,())
253
+ if xbrl21:
254
+ for fact in _itemFacts:
255
+ if not fact.isNil:
256
+ if fact in self.duplicatedFacts:
257
+ dupBindingKeys.add(itemBindKey)
258
+ elif fact not in self.consistentDupFacts:
259
+ roundedValue = roundFact(fact, self.inferDecimals)
260
+ boundSums[itemBindKey] += roundedValue * w
261
+ boundSummationItems[itemBindKey].append(wrappedFactWithWeight(fact,w,roundedValue))
262
+ if calc11 and _itemFacts:
263
+ validation, (y1, y2, iY1, iY2) = self.consistentFactValueInterval(_itemFacts, calc11t)
264
+ if validation is not None:
265
+ yield validation
266
+ if y1 is INCONSISTENT:
267
+ blockedIntervals.add(itemBindKey)
268
+ elif y1 is not NIL_FACT_SET:
269
+ x1, x2, iX1, iX2 = boundIntervals.get(itemBindKey, ZERO_RANGE)
270
+ y1 *= w
271
+ y2 *= w
272
+ if y2 < y1:
273
+ y1, y2 = y2, y1
274
+ boundIntervals[itemBindKey] = (x1 + y1, x2 + y2, iX1 and iY1, iX2 and iY2)
275
+ boundIntervalItems[itemBindKey].extend(_itemFacts)
276
+ for sumBindKey in boundSumKeys:
277
+ ancestor, contextHash, unit = sumBindKey
278
+ factKey = (sumConcept, ancestor, contextHash, unit)
279
+ if factKey not in self.sumFacts:
280
+ continue
281
+ sumFacts = self.sumFacts[factKey]
282
+ if xbrl21:
283
+ for fact in sumFacts:
284
+ if fact.isNil:
285
+ continue
286
+ if fact in self.duplicatedFacts:
287
+ dupBindingKeys.add(sumBindKey)
288
+ elif (sumBindKey in boundSums and sumBindKey not in dupBindingKeys
289
+ and fact not in self.consistentDupFacts
290
+ and not (len(sumFacts) > 1 and not self.deDuplicate)): # don't bind if sum duplicated without dedup option
291
+ roundedSum = roundFact(fact, self.inferDecimals)
292
+ roundedItemsSum = roundFact(fact, self.inferDecimals, vDecimal=boundSums[sumBindKey])
293
+ if roundedItemsSum != roundFact(fact, self.inferDecimals):
294
+ d = inferredDecimals(fact)
295
+ if isnan(d) or isinf(d): d = 4
296
+ _boundSummationItems = boundSummationItems[sumBindKey]
297
+ unreportedContribingItemQnames = [] # list the missing/unreported contributors in relationship order
298
+ for modelRel in modelRels:
299
+ itemConcept = modelRel.toModelObject
300
+ if (itemConcept is not None and
301
+ (itemConcept, ancestor, contextHash, unit) not in self.itemFacts):
302
+ unreportedContribingItemQnames.append(str(itemConcept.qname))
303
+ yield Validation.inconsistency("xbrl.5.2.5.2:calcInconsistency",
304
+ _("Calculation inconsistent from %(concept)s in link role %(linkrole)s reported sum %(reportedSum)s computed sum %(computedSum)s context %(contextID)s unit %(unitID)s unreportedContributingItems %(unreportedContributors)s"),
305
+ modelObject=wrappedSummationAndItems(fact, roundedSum, _boundSummationItems),
306
+ concept=sumConcept.qname, linkrole=ELR,
307
+ linkroleDefinition=modelXbrl.roleTypeDefinition(ELR),
308
+ reportedSum=Locale.format_decimal(modelXbrl.locale, roundedSum, 1, max(d,0)),
309
+ computedSum=Locale.format_decimal(modelXbrl.locale, roundedItemsSum, 1, max(d,0)),
310
+ contextID=fact.context.id, unitID=fact.unit.id if fact.unit is not None else "(none)",
311
+ unreportedContributors=", ".join(unreportedContribingItemQnames) or "none")
312
+ del unreportedContribingItemQnames[:]
313
+ if calc11:
314
+ validation, (s1, s2, incls1, incls2) = self.consistentFactValueInterval(sumFacts, calc11t)
315
+ if validation is not None:
316
+ yield validation
317
+ if s1 is not INCONSISTENT and s1 is not NIL_FACT_SET and sumBindKey not in blockedIntervals and sumBindKey in boundIntervals:
318
+ x1, x2, inclx1, inclx2 = boundIntervals[sumBindKey]
319
+ a = max(s1, x1)
320
+ b = min(s2, x2)
321
+ inclA = incls1 | inclx1
322
+ inclB = incls2 | inclx2
323
+ if (a == b and not (inclA and inclB)) or (a > b):
324
+ yield Validation.inconsistency("calc11e:inconsistentCalculationUsing" + self.calc11suffix,
325
+ _("Calculation inconsistent from %(concept)s in link role %(linkrole)s reported sum %(reportedSum)s computed sum %(computedSum)s context %(contextID)s unit %(unitID)s"),
326
+ modelObject=sumFacts + boundIntervalItems[sumBindKey],
327
+ concept=sumConcept.qname, linkrole=ELR,
328
+ linkroleDefinition=modelXbrl.roleTypeDefinition(ELR),
329
+ reportedSum=rangeToStr(s1,s2,incls1,incls2),
330
+ computedSum=rangeToStr(x1,x2,inclx1,inclx2),
331
+ contextID=sumFacts[0].context.id, unitID=sumFacts[0].unit.id if sumFacts[0].unit is not None else "(none)")
332
+ boundSummationItems.clear() # dereference facts in list
333
+ boundIntervalItems.clear()
334
+ elif arcrole == XbrlConst.essenceAlias:
335
+ for modelRel in relsSet.modelRelationships:
336
+ essenceConcept = modelRel.fromModelObject
337
+ aliasConcept = modelRel.toModelObject
338
+ essenceBindingKeys = self.esAlConceptBindKeys[essenceConcept]
339
+ aliasBindingKeys = self.esAlConceptBindKeys[aliasConcept]
340
+ for esAlBindKey in essenceBindingKeys & aliasBindingKeys:
341
+ ancestor, contextHash = esAlBindKey
342
+ essenceFactsKey = (essenceConcept, ancestor, contextHash)
343
+ aliasFactsKey = (aliasConcept, ancestor, contextHash)
344
+ if essenceFactsKey in self.esAlFacts and aliasFactsKey in self.esAlFacts:
345
+ for eF in self.esAlFacts[essenceFactsKey]:
346
+ for aF in self.esAlFacts[aliasFactsKey]:
347
+ essenceUnit = self.mapUnit.get(eF.unit,eF.unit)
348
+ aliasUnit = self.mapUnit.get(aF.unit,aF.unit)
349
+ if essenceUnit != aliasUnit:
350
+ yield Validation.inconsistency("xbrl.5.2.6.2.2:essenceAliasUnitsInconsistency",
351
+ _("Essence-Alias inconsistent units from %(essenceConcept)s to %(aliasConcept)s in link role %(linkrole)s context %(contextID)s"),
352
+ modelObject=(modelRel, eF, aF),
353
+ essenceConcept=essenceConcept.qname, aliasConcept=aliasConcept.qname,
354
+ linkrole=ELR,
355
+ linkroleDefinition=modelXbrl.roleTypeDefinition(ELR),
356
+ contextID=eF.context.id)
357
+ if not XbrlUtil.vEqual(eF, aF):
358
+ yield Validation.inconsistency("xbrl.5.2.6.2.2:essenceAliasUnitsInconsistency",
359
+ _("Essence-Alias inconsistent value from %(essenceConcept)s to %(aliasConcept)s in link role %(linkrole)s context %(contextID)s"),
360
+ modelObject=(modelRel, eF, aF),
361
+ essenceConcept=essenceConcept.qname, aliasConcept=aliasConcept.qname,
362
+ linkrole=ELR,
363
+ linkroleDefinition=modelXbrl.roleTypeDefinition(ELR),
364
+ contextID=eF.context.id)
365
+ elif arcrole == XbrlConst.requiresElement:
366
+ for modelRel in relsSet.modelRelationships:
367
+ sourceConcept = modelRel.fromModelObject
368
+ requiredConcept = modelRel.toModelObject
369
+ if sourceConcept in self.requiresElementFacts and \
370
+ not requiredConcept in self.requiresElementFacts:
371
+ yield Validation.inconsistency("xbrl.5.2.6.2.4:requiresElementInconsistency",
372
+ _("Requires-Element %(requiringConcept)s missing required fact for %(requiredConcept)s in link role %(linkrole)s"),
373
+ modelObject=sourceConcept,
374
+ requiringConcept=sourceConcept.qname, requiredConcept=requiredConcept.qname,
375
+ linkrole=ELR,
376
+ linkroleDefinition=modelXbrl.roleTypeDefinition(ELR))
353
377
  modelXbrl.profileActivity("... find inconsistencies", minTimeToShow=1.0)
354
378
  modelXbrl.profileActivity() # reset
355
379
 
@@ -419,11 +443,11 @@ class ValidateXbrlCalcs:
419
443
  if concept in self.conceptsInRequiresElement:
420
444
  self.requiresElementFacts[concept].append(f)
421
445
 
422
- def consistentFactValueInterval(self, fList, truncate=False) -> tuple[decimal.Decimal | str, decimal.Decimal | str, bool, bool]:
446
+ def consistentFactValueInterval(self, fList, truncate=False) -> tuple[Validation | None, tuple[decimal.Decimal | str, decimal.Decimal | str, bool, bool]]:
423
447
  _excessDigitFacts = []
424
448
  if any(f.isNil for f in fList):
425
449
  if all(f.isNil for f in fList):
426
- return (NIL_FACT_SET,NIL_FACT_SET,True,True)
450
+ return None, (NIL_FACT_SET,NIL_FACT_SET,True,True)
427
451
  _inConsistent = True # provide error message
428
452
  else: # not all have same decimals
429
453
  a = b = None
@@ -451,21 +475,21 @@ class ValidateXbrlCalcs:
451
475
  inclB |= _inclB
452
476
  _inConsistent = (a == b and not(inclA and inclB)) or (a > b)
453
477
  if _excessDigitFacts:
454
- self.modelXbrl.log('INCONSISTENCY', "calc11e:excessDigits",
478
+ return Validation.inconsistency("calc11e:excessDigits",
455
479
  _("Calculations check stopped for excess digits in fact values %(element)s: %(values)s, %(contextIDs)s."),
456
480
  modelObject=fList, element=_excessDigitFacts[0].qname,
457
481
  contextIDs=", ".join(sorted(set(f.contextID for f in _excessDigitFacts))),
458
- values=", ".join(strTruncate(f.value,64) for f in _excessDigitFacts))
459
- return (INCONSISTENT, INCONSISTENT,True,True)
482
+ values=", ".join(strTruncate(f.value,64) for f in _excessDigitFacts)
483
+ ), (INCONSISTENT, INCONSISTENT,True,True)
460
484
  if _inConsistent:
461
- self.modelXbrl.log('INCONSISTENCY',
485
+ return Validation.inconsistency(
462
486
  "calc11e:disallowedDuplicateFactsUsingTruncation" if self.calc11t else "oime:disallowedDuplicateFacts",
463
487
  _("Calculations check stopped for duplicate fact values %(element)s: %(values)s, %(contextIDs)s."),
464
488
  modelObject=fList, element=fList[0].qname,
465
489
  contextIDs=", ".join(sorted(set(f.contextID for f in fList))),
466
- values=", ".join(strTruncate(f.value,64) for f in fList))
467
- return (INCONSISTENT, INCONSISTENT,True,True)
468
- return (a, b, inclA, inclB)
490
+ values=", ".join(strTruncate(f.value,64) for f in fList)
491
+ ), (INCONSISTENT, INCONSISTENT,True,True)
492
+ return None, (a, b, inclA, inclB)
469
493
 
470
494
  def roundFact(fact, inferDecimals=False, vDecimal=None):
471
495
  if vDecimal is None:
arelle/WebCache.py CHANGED
@@ -27,6 +27,7 @@ from urllib.parse import quote, unquote, urlsplit, urlunsplit
27
27
 
28
28
  import certifi
29
29
  import regex as re
30
+ import truststore
30
31
  from filelock import FileLock, Timeout
31
32
 
32
33
  from arelle.PythonUtil import isLegacyAbs
@@ -36,12 +37,6 @@ try:
36
37
  except ImportError:
37
38
  ssl = None
38
39
 
39
- try:
40
- import truststore
41
- except ImportError:
42
- # truststore requires Python > 3.9
43
- truststore = None
44
-
45
40
  from arelle.FileSource import SERVER_WEB_CACHE, archiveFilenameParts
46
41
  from arelle.PluginManager import pluginClassMethods
47
42
  from arelle.UrlUtil import isHttpUrl
@@ -282,8 +277,7 @@ class WebCache:
282
277
  self.http_auth_handler = proxyhandlers.HTTPBasicAuthHandler()
283
278
  proxyHandlers = [self.proxy_handler, self.proxy_auth_handler, self.http_auth_handler]
284
279
  if ssl:
285
- # Attempt to load the default CA certificates from the OS using truststore if available, else fallback to OpenSSL's default context.
286
- context = truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT) if truststore else ssl.create_default_context()
280
+ context = truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
287
281
  # Include certifi certificates (Mozilla’s carefully curated
288
282
  # collection) for systems with outdated certs.
289
283
  context.load_verify_locations(cafile=certifi.where())
arelle/XbrlConst.py CHANGED
@@ -143,6 +143,8 @@ qnXlResourceType = qname("{http://www.xbrl.org/2003/XLink}xl:resourceType")
143
143
  qnXlArcType = qname("{http://www.xbrl.org/2003/XLink}xl:arcType")
144
144
  xhtml = "http://www.w3.org/1999/xhtml"
145
145
  qnXhtmlMeta = qname("{http://www.w3.org/1999/xhtml}meta")
146
+ qnXhtmlImg = qname("{http://www.w3.org/1999/xhtml}img")
147
+ qnXhtmlDel = qname("{http://www.w3.org/1999/xhtml}del")
146
148
  ixbrl = "http://www.xbrl.org/2008/inlineXBRL"
147
149
  ixbrl11 = "http://www.xbrl.org/2013/inlineXBRL"
148
150
  ixbrlAll = {ixbrl, ixbrl11}