arelle-release 2.36.27__py3-none-any.whl → 2.36.29__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.

@@ -1,31 +1,38 @@
1
1
  """
2
2
  See COPYRIGHT.md for copyright information.
3
3
  """
4
+
4
5
  from __future__ import annotations
5
6
 
6
- from _decimal import Decimal
7
7
  from collections import defaultdict
8
8
  from collections.abc import Iterator
9
9
  from dataclasses import dataclass, field
10
- from enum import auto, Flag, Enum
10
+ from enum import Enum, Flag, auto
11
11
  from functools import cached_property
12
12
  from math import isnan
13
- from typing import cast, Any, SupportsFloat
13
+ from typing import Any, SupportsFloat, cast
14
+
15
+ from _decimal import Decimal
14
16
 
15
17
  from arelle import XmlValidateConst
16
- from arelle.ModelInstanceObject import ModelFact, ModelContext, ModelUnit
18
+ from arelle.ModelInstanceObject import ModelContext, ModelFact, ModelUnit
17
19
  from arelle.ModelValue import DateTime, QName, TypeXValue
18
20
  from arelle.ModelXbrl import ModelXbrl
19
- from arelle.ValidateXbrlCalcs import rangeValue, inferredDecimals
20
21
  from arelle.typing import TypeGetText
22
+ from arelle.ValidateXbrlCalcs import inferredDecimals, rangeValue
23
+
21
24
  _: TypeGetText
22
25
 
23
26
 
24
27
  @dataclass(frozen=True)
25
28
  class DuplicateFactSet:
26
29
  facts: list[ModelFact]
27
- _inferredDecimals: dict[ModelFact, float | int | None] = field(init=False, default_factory=dict)
28
- _ranges: dict[ModelFact, tuple[Decimal, Decimal]] = field(init=False, default_factory=dict)
30
+ _inferredDecimals: dict[ModelFact, float | int | None] = field(
31
+ init=False, default_factory=dict
32
+ )
33
+ _ranges: dict[ModelFact, tuple[Decimal, Decimal]] = field(
34
+ init=False, default_factory=dict
35
+ )
29
36
 
30
37
  def __iter__(self) -> Iterator[ModelFact]:
31
38
  return iter(self.facts)
@@ -80,7 +87,9 @@ class DuplicateFactSet:
80
87
  """
81
88
  :return: Whether any facts in the set are complete duplicates of each other.
82
89
  """
83
- decimalsValueMap: dict[float | int | None, set[TypeFactValueEqualityKey]] = defaultdict(set)
90
+ decimalsValueMap: dict[float | int | None, set[TypeFactValueEqualityKey]] = (
91
+ defaultdict(set)
92
+ )
84
93
  for fact in self.facts:
85
94
  decimals = self.getDecimals(fact)
86
95
  value = getFactValueEqualityKey(fact)
@@ -181,7 +190,10 @@ class DuplicateFactSet:
181
190
  seenKeys = set()
182
191
  results = []
183
192
  for fact in self.facts:
184
- key = (fact.decimals, fact.xValue)
193
+ xValue = (
194
+ tuple(fact.xValue) if isinstance(fact.xValue, list) else fact.xValue
195
+ )
196
+ key = (fact.decimals, xValue)
185
197
  if key in seenKeys:
186
198
  continue
187
199
  seenKeys.add(key)
@@ -208,7 +220,7 @@ class DuplicateFactSet:
208
220
  sortedDecimals = sorted(decimalsMap.keys())
209
221
  results = set(facts)
210
222
 
211
- for a, decimalLower in enumerate(sortedDecimals[:len(sortedDecimals)-1]):
223
+ for a, decimalLower in enumerate(sortedDecimals[: len(sortedDecimals) - 1]):
212
224
  groupLower = decimalsMap[decimalLower]
213
225
  for factA in groupLower:
214
226
  lowerA, upperA = self.getRange(factA)
@@ -216,7 +228,7 @@ class DuplicateFactSet:
216
228
  continue
217
229
  remove = False
218
230
  # Iterate through each higher decimals group
219
- for b, decimalHigher in enumerate(sortedDecimals[a+1:]):
231
+ for b, decimalHigher in enumerate(sortedDecimals[a + 1 :]):
220
232
  groupHigher = decimalsMap[decimalHigher]
221
233
  for factB in groupHigher:
222
234
  lowerB, upperB = self.getRange(factB)
@@ -241,7 +253,7 @@ class DuplicateFactSet:
241
253
  return self.deduplicateCompleteSubsets(), None
242
254
  if not self.areAllConsistent:
243
255
  # If facts are not all consistent, we will only perform complete deduplication
244
- return self.deduplicateCompleteSubsets(), 'Set has inconsistent facts'
256
+ return self.deduplicateCompleteSubsets(), "Set has inconsistent facts"
245
257
  selectedFact = self.facts[0]
246
258
  maxDecimals = self.getDecimals(selectedFact)
247
259
  assert maxDecimals is not None
@@ -259,9 +271,11 @@ class DuplicateFactSet:
259
271
  :param fact:
260
272
  :return: Retrieve cached inferred decimals value for the provided fact.
261
273
  """
262
- assert fact in self.facts, 'Attempted to get decimals for fact not in set'
274
+ assert fact in self.facts, "Attempted to get decimals for fact not in set"
263
275
  if fact not in self._inferredDecimals:
264
- self._inferredDecimals[fact] = None if fact.decimals is None else inferredDecimals(fact)
276
+ self._inferredDecimals[fact] = (
277
+ None if fact.decimals is None else inferredDecimals(fact)
278
+ )
265
279
  return self._inferredDecimals[fact]
266
280
 
267
281
  def getRange(self, fact: ModelFact) -> tuple[Decimal, Decimal]:
@@ -270,8 +284,8 @@ class DuplicateFactSet:
270
284
  :param fact:
271
285
  :return: Retrieve cached range values for the provided fact.
272
286
  """
273
- assert fact in self.facts, 'Attempted to get range for fact not in set'
274
- assert fact.isNumeric, 'Attempted to get range for non-numeric fact'
287
+ assert fact in self.facts, "Attempted to get range for fact not in set"
288
+ assert fact.isNumeric, "Attempted to get range for non-numeric fact"
275
289
  if fact not in self._ranges:
276
290
  lower, upper, __, __ = rangeValue(fact.xValue, self.getDecimals(fact))
277
291
  self._ranges[fact] = lower, upper
@@ -297,25 +311,25 @@ class DuplicateType(Flag):
297
311
 
298
312
  @property
299
313
  def description(self) -> str:
300
- return '|'.join([str(n.name) for n in self if n.name]).lower()
314
+ return "|".join([str(n.name) for n in self if n.name]).lower()
301
315
 
302
316
 
303
317
  class DuplicateTypeArg(Enum):
304
- NONE = 'none'
305
- INCONSISTENT = 'inconsistent'
306
- CONSISTENT = 'consistent'
307
- INCOMPLETE = 'incomplete'
308
- COMPLETE = 'complete'
309
- ALL = 'all'
318
+ NONE = "none"
319
+ INCONSISTENT = "inconsistent"
320
+ CONSISTENT = "consistent"
321
+ INCOMPLETE = "incomplete"
322
+ COMPLETE = "complete"
323
+ ALL = "all"
310
324
 
311
325
  def duplicateType(self) -> DuplicateType:
312
326
  return DUPLICATE_TYPE_ARG_MAP.get(self, DuplicateType.NONE)
313
327
 
314
328
 
315
329
  class DeduplicationType(Enum):
316
- COMPLETE = 'complete'
317
- CONSISTENT_PAIRS = 'consistent-pairs'
318
- CONSISTENT_SETS = 'consistent-sets'
330
+ COMPLETE = "complete"
331
+ CONSISTENT_PAIRS = "consistent-pairs"
332
+ CONSISTENT_SETS = "consistent-sets"
319
333
 
320
334
 
321
335
  DUPLICATE_TYPE_ARG_MAP = {
@@ -328,7 +342,9 @@ DUPLICATE_TYPE_ARG_MAP = {
328
342
  }
329
343
 
330
344
 
331
- def doesSetHaveDuplicateType(duplicateFacts: DuplicateFactSet, duplicateType: DuplicateType) -> bool:
345
+ def doesSetHaveDuplicateType(
346
+ duplicateFacts: DuplicateFactSet, duplicateType: DuplicateType
347
+ ) -> bool:
332
348
  """
333
349
  :param duplicateFacts:
334
350
  :param duplicateType:
@@ -363,7 +379,9 @@ def areFactsValueEqual(factA: ModelFact, factB: ModelFact) -> bool:
363
379
  return getFactValueEqualityKey(factA) == getFactValueEqualityKey(factB)
364
380
 
365
381
 
366
- def getAspectEqualFacts(hashEquivalentFacts: list[ModelFact], includeSingles: bool) -> Iterator[list[ModelFact]]:
382
+ def getAspectEqualFacts(
383
+ hashEquivalentFacts: list[ModelFact], includeSingles: bool
384
+ ) -> Iterator[list[ModelFact]]:
367
385
  """
368
386
  Given a list of concept/context/unit hash-equivalent facts,
369
387
  yields sublists of aspect-equal facts from this list.
@@ -371,12 +389,24 @@ def getAspectEqualFacts(hashEquivalentFacts: list[ModelFact], includeSingles: bo
371
389
  :param includeSingles: Whether to include lists of single facts (with no duplicates).
372
390
  :return: Lists of aspect-equal facts.
373
391
  """
374
- aspectEqualFacts: dict[tuple[QName, str | None], dict[tuple[ModelContext, ModelUnit], list[ModelFact]]] = defaultdict(dict)
375
- for fact in hashEquivalentFacts: # check for hash collision by value checks on context and unit
376
- contextUnitDict = aspectEqualFacts[(
377
- fact.qname,
378
- cast(str, fact.xmlLang or "").lower() if fact.concept.type.isWgnStringFactType else None
379
- )]
392
+ aspectEqualFacts: dict[
393
+ tuple[QName, str | None], dict[tuple[ModelContext, ModelUnit], list[ModelFact]]
394
+ ] = defaultdict(dict)
395
+ for (
396
+ fact
397
+ ) in (
398
+ hashEquivalentFacts
399
+ ): # check for hash collision by value checks on context and unit
400
+ contextUnitDict = aspectEqualFacts[
401
+ (
402
+ fact.qname,
403
+ (
404
+ cast(str, fact.xmlLang or "").lower()
405
+ if fact.concept.type.isWgnStringFactType
406
+ else None
407
+ ),
408
+ )
409
+ ]
380
410
  _matched = False
381
411
  for (context, unit), contextUnitFacts in contextUnitDict.items():
382
412
  if fact.context is None:
@@ -395,12 +425,16 @@ def getAspectEqualFacts(hashEquivalentFacts: list[ModelFact], includeSingles: bo
395
425
  if not _matched:
396
426
  contextUnitDict[(fact.context, fact.unit)] = [fact]
397
427
  for contextUnitDict in aspectEqualFacts.values(): # dups by qname, lang
398
- for duplicateFacts in contextUnitDict.values(): # dups by equal-context equal-unit
428
+ for (
429
+ duplicateFacts
430
+ ) in contextUnitDict.values(): # dups by equal-context equal-unit
399
431
  if includeSingles or len(duplicateFacts) > 1:
400
432
  yield duplicateFacts
401
433
 
402
434
 
403
- def getDeduplicatedFacts(modelXbrl: ModelXbrl, deduplicationType: DeduplicationType) -> list[ModelFact]:
435
+ def getDeduplicatedFacts(
436
+ modelXbrl: ModelXbrl, deduplicationType: DeduplicationType
437
+ ) -> list[ModelFact]:
404
438
  results = []
405
439
  for duplicateFactSet in getDuplicateFactSets(modelXbrl.facts, includeSingles=True):
406
440
  message = None
@@ -418,12 +452,20 @@ def getDeduplicatedFacts(modelXbrl: ModelXbrl, deduplicationType: DeduplicationT
418
452
  if message is not None:
419
453
  modelXbrl.warning(
420
454
  "info:deduplicationNotPossible",
421
- _("Deduplication of %(concept)s fact set not possible: %(message)s. concept=%(concept)s, context=%(context)s"),
422
- modelObject=facts[0], concept=facts[0].concept.qname, context=facts[0].contextID, message=message)
455
+ _(
456
+ "Deduplication of %(concept)s fact set not possible: %(message)s. concept=%(concept)s, context=%(context)s"
457
+ ),
458
+ modelObject=facts[0],
459
+ concept=facts[0].concept.qname,
460
+ context=facts[0].contextID,
461
+ message=message,
462
+ )
423
463
  return results
424
464
 
425
465
 
426
- def getDuplicateFactSets(facts: list[ModelFact], includeSingles: bool) -> Iterator[DuplicateFactSet]:
466
+ def getDuplicateFactSets(
467
+ facts: list[ModelFact], includeSingles: bool
468
+ ) -> Iterator[DuplicateFactSet]:
427
469
  """
428
470
  :param facts: Facts to find duplicate sets from.
429
471
  :param includeSingles: Whether to include lists of single facts (with no duplicates).
@@ -433,11 +475,15 @@ def getDuplicateFactSets(facts: list[ModelFact], includeSingles: bool) -> Iterat
433
475
  for hashEquivalentFacts in hashEquivalentFactGroups:
434
476
  if not includeSingles and len(hashEquivalentFacts) < 2:
435
477
  continue
436
- for duplicateFactList in getAspectEqualFacts(hashEquivalentFacts, includeSingles=includeSingles): # dups by equal-context equal-unit
478
+ for duplicateFactList in getAspectEqualFacts(
479
+ hashEquivalentFacts, includeSingles=includeSingles
480
+ ): # dups by equal-context equal-unit
437
481
  yield DuplicateFactSet(facts=duplicateFactList)
438
482
 
439
483
 
440
- def getDuplicateFactSetsWithType(facts: list[ModelFact], duplicateType: DuplicateType) -> Iterator[DuplicateFactSet]:
484
+ def getDuplicateFactSetsWithType(
485
+ facts: list[ModelFact], duplicateType: DuplicateType
486
+ ) -> Iterator[DuplicateFactSet]:
441
487
  """
442
488
  :param facts: Facts to find duplicate sets from.
443
489
  :param duplicateType: Type of duplicate to filter duplicate sets by.
@@ -451,9 +497,9 @@ def getDuplicateFactSetsWithType(facts: list[ModelFact], duplicateType: Duplicat
451
497
 
452
498
 
453
499
  class FactValueEqualityType(Enum):
454
- DEFAULT = 'default'
455
- DATETIME = 'datetime'
456
- LANGUAGE = 'language'
500
+ DEFAULT = "default"
501
+ DATETIME = "datetime"
502
+ LANGUAGE = "language"
457
503
 
458
504
 
459
505
  TypeFactValueEqualityKey = tuple[FactValueEqualityType, tuple[Any, ...]]
@@ -471,7 +517,9 @@ def getFactValueEqualityKey(fact: ModelFact) -> TypeFactValueEqualityKey:
471
517
  if isnan(cast(SupportsFloat, xValue)):
472
518
  return FactValueEqualityType.DEFAULT, (float("nan"),)
473
519
  if fact.concept.isLanguage:
474
- return FactValueEqualityType.LANGUAGE, (cast(str, xValue).lower() if xValue is not None else None,)
520
+ return FactValueEqualityType.LANGUAGE, (
521
+ cast(str, xValue).lower() if xValue is not None else None,
522
+ )
475
523
  if isinstance(xValue, DateTime): # with/without time makes values unequal
476
524
  return FactValueEqualityType.DATETIME, (xValue, xValue.dateOnly)
477
525
  return FactValueEqualityType.DEFAULT, (fact.value,)
@@ -486,7 +534,16 @@ def getHashEquivalentFactGroups(facts: list[ModelFact]) -> list[list[ModelFact]]
486
534
  """
487
535
  hashDict = defaultdict(list)
488
536
  for f in facts:
489
- if (f.isNil or getattr(f, "xValid", XmlValidateConst.UNVALIDATED) >= XmlValidateConst.VALID) and f.context is not None and f.concept is not None and f.concept.type is not None:
537
+ if (
538
+ (
539
+ f.isNil
540
+ or getattr(f, "xValid", XmlValidateConst.UNVALIDATED)
541
+ >= XmlValidateConst.VALID
542
+ )
543
+ and f.context is not None
544
+ and f.concept is not None
545
+ and f.concept.type is not None
546
+ ):
490
547
  hashDict[f.conceptContextUnitHash].append(f)
491
548
  return list(hashDict.values())
492
549
 
@@ -494,7 +551,9 @@ def getHashEquivalentFactGroups(facts: list[ModelFact]) -> list[list[ModelFact]]
494
551
  def logDeduplicatedFact(modelXbrl: ModelXbrl, fact: ModelFact) -> None:
495
552
  modelXbrl.info(
496
553
  "info:deduplicatedFact",
497
- _("Duplicate fact was excluded from deduplicated instance: %(fact)s, value=%(value)s, decimals=%(decimals)s"),
554
+ _(
555
+ "Duplicate fact was excluded from deduplicated instance: %(fact)s, value=%(value)s, decimals=%(decimals)s"
556
+ ),
498
557
  modelObject=fact,
499
558
  fact=fact.qname,
500
559
  value=fact.xValue,
@@ -502,7 +561,9 @@ def logDeduplicatedFact(modelXbrl: ModelXbrl, fact: ModelFact) -> None:
502
561
  )
503
562
 
504
563
 
505
- def saveDeduplicatedInstance(modelXbrl: ModelXbrl, deduplicationType: DeduplicationType, outputFilepath: str) -> None:
564
+ def saveDeduplicatedInstance(
565
+ modelXbrl: ModelXbrl, deduplicationType: DeduplicationType, outputFilepath: str
566
+ ) -> None:
506
567
  deduplicatedFacts = frozenset(getDeduplicatedFacts(modelXbrl, deduplicationType))
507
568
  duplicateFacts = set(modelXbrl.facts) - deduplicatedFacts
508
569
  for fact in duplicateFacts:
@@ -513,7 +574,9 @@ def saveDeduplicatedInstance(modelXbrl: ModelXbrl, deduplicationType: Deduplicat
513
574
  modelXbrl.saveInstance(overrideFilepath=outputFilepath)
514
575
  modelXbrl.info(
515
576
  "info:deduplicatedInstance",
516
- _("Deduplicated instance was saved after removing %(count)s fact(s): %(filepath)s"),
577
+ _(
578
+ "Deduplicated instance was saved after removing %(count)s fact(s): %(filepath)s"
579
+ ),
517
580
  count=len(duplicateFacts),
518
581
  filepath=outputFilepath,
519
582
  )
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.36.27'
21
- __version_tuple__ = version_tuple = (2, 36, 27)
20
+ __version__ = version = '2.36.29'
21
+ __version_tuple__ = version_tuple = (2, 36, 29)
@@ -1,105 +1,222 @@
1
- '''
1
+ """
2
2
  Save HTML EBA Tables is an example of a plug-in to both GUI menu and command line/web service
3
3
  that will save a directory containing HTML Tablesets with an EBA index page.
4
4
 
5
5
  See COPYRIGHT.md for copyright information.
6
- '''
7
- from arelle.Version import authorLabel, copyrightLabel
6
+ """
8
7
 
9
- def generateHtmlEbaTablesetFiles(dts, indexFile, lang="en"):
10
- try:
11
- import os, io
12
- from arelle import Version, XbrlConst, XmlUtil
13
- from arelle.ViewFileRenderedGrid import viewRenderedGrid
14
- from arelle.ModelRenderingObject import DefnMdlTable
8
+ import os
9
+ import threading
10
+ from operator import itemgetter
11
+ from optparse import OptionParser
12
+ from tkinter import Menu
13
+ from typing import Any
15
14
 
16
- numTableFiles = 0
15
+ from lxml import etree
16
+
17
+ from arelle import Version, XbrlConst, XmlUtil
18
+ from arelle.CntlrCmdLine import CntlrCmdLine
19
+ from arelle.CntlrWinMain import CntlrWinMain
20
+ from arelle.ModelDocument import Type
21
+ from arelle.ModelObjectFactory import parser
22
+ from arelle.ModelRenderingObject import DefnMdlTable
23
+ from arelle.ModelXbrl import ModelXbrl
24
+ from arelle.rendering import RenderingEvaluator
25
+ from arelle.RuntimeOptions import RuntimeOptions
26
+ from arelle.typing import TypeGetText
27
+ from arelle.utils.PluginHooks import PluginHooks
28
+ from arelle.ViewFileRenderedGrid import viewRenderedGrid
29
+
30
+ _: TypeGetText
31
+
32
+ MENU_HTML = """<!DOCTYPE html>
33
+ <html>
34
+ <head>
35
+ <style>
36
+ body {
37
+ margin: 0;
38
+ padding: 10px;
39
+ font-family: Arial, sans-serif;
40
+ color: #243e5e;
41
+ }
42
+ .nav-list-menu-ul {
43
+ list-style-type: none;
44
+ padding: 0;
45
+ margin: 0;
46
+ }
47
+ .nav-list-menu-li {
48
+ margin: 5px 0;
49
+ padding: 5px;
50
+ border-bottom: 1px solid #eee;
51
+ }
52
+ .nav-list-menu-link {
53
+ text-decoration: none;
54
+ cursor: pointer;
55
+ display: block;
56
+ background: none;
57
+ border: none;
58
+ padding: 0;
59
+ }
60
+ .nav-list-menu-link:hover {
61
+ text-decoration: underline;
62
+ }
63
+ </style>
64
+ </head>
65
+ <body>
66
+ <ul class="nav-list-menu-ul"/>
67
+ </body>
68
+ </html>
69
+ """
17
70
 
18
- file = io.StringIO('''
19
- <html xmlns="http://www.w3.org/1999/xhtml">
20
- <head id="Left">
21
- <link type="text/css" rel="stylesheet" href="http://arelle.org/files/EBA/style20121210/eba.css" />
71
+ CENTER_LANDING_HTML = """<!DOCTYPE html>
72
+ <html>
73
+ <head>
74
+ <style>
75
+ body {
76
+ margin: 0;
77
+ padding: 20px;
78
+ font-family: Arial, sans-serif;
79
+ }
80
+ #page-title {
81
+ margin-bottom: 20px;
82
+ }
83
+ #page-title h1 {
84
+ color: #243e5e;
85
+ margin-top: 0;
86
+ }
87
+ #content-center {
88
+ margin-top: 20px;
89
+ line-height: 1.5;
90
+ }
91
+ </style>
22
92
  </head>
23
- <body class="LTR IE7 ENGB">
24
- <ul class="CMSListMenuUL" id="Vertical2"/>
93
+ <body>
94
+ <div id="page-title">
95
+ <h1>Taxonomy Tables Viewer</h1>
96
+ </div>
97
+ <div id="content-center">
98
+ <p>Please select tables to view by clicking in the left column.</p>
99
+ </div>
100
+ </body>
101
+ </html>
102
+ """
103
+
104
+ TABLE_CSS_EXTRAS = """
105
+ table {background:#fff}
106
+ """
107
+
108
+
109
+ def indexFileHTML(indexBaseName: str) -> str:
110
+ return f'''<!DOCTYPE html>
111
+ <html>
112
+ <head>
113
+ <meta charset="utf-8">
114
+ <meta name="viewport" content="width=device-width, initial-scale=1">
115
+ <title>EBA - Tablesets</title>
116
+ <style>
117
+ html, body {{
118
+ margin: 0;
119
+ padding: 0;
120
+ height: 100%;
121
+ width: 100%;
122
+ font-family: Arial, sans-serif;
123
+ display: flex;
124
+ flex-direction: column;
125
+ }}
126
+ #header {{
127
+ background: rgb(36, 62, 94);
128
+ color: rgb(255, 255, 255);
129
+ height: 40px;
130
+ }}
131
+ #header h1 {{
132
+ font-size: 1.5em;
133
+ margin: 0.25em;
134
+ }}
135
+ #main-container {{
136
+ display: flex;
137
+ flex: 1;
138
+ height: calc(100vh - 40px);
139
+ }}
140
+ #menu-container {{
141
+ width: 360px;
142
+ border-right: 2px solid #243e5e;
143
+ overflow-y: auto;
144
+ box-sizing: border-box;
145
+ }}
146
+ #content-container {{
147
+ flex: 1;
148
+ overflow: auto;
149
+ box-sizing: border-box;
150
+ }}
151
+ iframe {{
152
+ border: none;
153
+ width: 100%;
154
+ height: 100%;
155
+ }}
156
+ </style>
157
+ <script>
158
+ function loadContent(url) {{
159
+ document.getElementById('content-frame').src = url;
160
+ }}
161
+ </script>
162
+ </head>
163
+ <body>
164
+ <div id="header"><h1>EBA - Tablesets</h1></div>
165
+ <div id="main-container">
166
+ <div id="menu-container">
167
+ <iframe src="{indexBaseName}FormsFrame.html" width="100%" height="100%" frameborder="0" id="menu-frame"></iframe>
168
+ </div>
169
+ <div id="content-container">
170
+ <iframe src="{indexBaseName}CenterLanding.html" width="100%" height="100%" frameborder="0" id="content-frame"></iframe>
171
+ </div>
172
+ </div>
25
173
  </body>
26
174
  </html>
27
175
  '''
28
- )
29
- from arelle.ModelObjectFactory import parser
30
- parser, parserLookupName, parserLookupClass = parser(dts,None)
31
- from lxml import etree
32
- indexDocument = etree.parse(file,parser=parser,base_url=indexFile)
33
- file.close()
34
- #xmlDocument.getroot().init(self) ## is this needed ??
35
- for listElt in indexDocument.iter(tag="{http://www.w3.org/1999/xhtml}ul"):
36
- break
37
-
38
- class nonTkBooleanVar():
39
- def __init__(self, value=True):
40
- self.value = value
41
- def set(self, value):
42
- self.value = value
43
- def get(self):
44
- return self.value
45
-
46
- class View():
47
- def __init__(self, tableOrELR, ignoreDimValidity, xAxisChildrenFirst, yAxisChildrenFirst):
48
- self.tblELR = tableOrELR
49
- # context menu boolean vars (non-tkinter boolean
50
- self.ignoreDimValidity = nonTkBooleanVar(value=ignoreDimValidity)
51
- self.xAxisChildrenFirst = nonTkBooleanVar(value=xAxisChildrenFirst)
52
- self.yAxisChildrenFirst = nonTkBooleanVar(value=yAxisChildrenFirst)
176
+
177
+
178
+ def generateHtmlEbaTablesetFiles(dts: ModelXbrl, indexFile: str, lang: str = "en") -> None:
179
+ try:
180
+ numTableFiles = 0
181
+ _parser = parser(dts, None)[0]
182
+ menuFrameDocument = etree.fromstring(MENU_HTML, parser=_parser, base_url=indexFile)
183
+ listElt = menuFrameDocument.find(".//ul")
184
+ assert listElt is not None, "No list element in index document"
53
185
 
54
186
  indexBase = indexFile.rpartition(".")[0]
55
187
  groupTableRels = dts.modelXbrl.relationshipSet(XbrlConst.euGroupTable)
56
188
  modelTables = []
57
- tblCssExtras='''
58
- body {background-image:url('http://arelle.org/files/EBA/style20121210/lhsbackground.jpg')}
59
- table {background:#fff}
60
- '''
61
- # order number is missing
62
- def viewTable(modelTable):
189
+
190
+ def viewTable(modelTable: DefnMdlTable) -> None:
63
191
  if modelTable is None:
64
192
  return
65
- if isinstance(modelTable, (DefnMdlTable)):
66
- # status
67
- dts.modelManager.cntlr.addToLog("viewing: " + modelTable.id)
68
- # for table file name, use table ELR
69
- tblFile = os.path.join(os.path.dirname(indexFile), modelTable.id + ".html")
70
- tableName = modelTable.id
193
+ tableId = modelTable.id or ""
194
+ if isinstance(modelTable, DefnMdlTable):
195
+ dts.modelManager.cntlr.addToLog("viewing: " + tableId)
196
+ tblFile = os.path.join(os.path.dirname(indexFile), tableId + ".html")
197
+ tableName = tableId
71
198
  if tableName.startswith("eba_t"):
72
199
  tableName = tableName.removeprefix("eba_t")
73
- if tableName.startswith("srb_t"):
200
+ elif tableName.startswith("srb_t"):
74
201
  tableName = tableName.removeprefix("srb_t")
75
- viewRenderedGrid(dts,
76
- tblFile,
77
- lang=lang,
78
- cssExtras=tblCssExtras,
79
- table=tableName)
80
-
81
- # generaate menu entry
82
- elt = etree.SubElement(listElt, "{http://www.w3.org/1999/xhtml}li")
83
- elt.set("class", "CMSListMenuLI")
84
- elt.set("id", modelTable.id)
85
- elt = etree.SubElement(elt, "{http://www.w3.org/1999/xhtml}a")
202
+ viewRenderedGrid(dts, tblFile, lang=lang, cssExtras=TABLE_CSS_EXTRAS, table=tableName) # type: ignore[no-untyped-call]
203
+
204
+ elt = etree.SubElement(listElt, "li")
205
+ elt.set("class", "nav-list-menu-li")
206
+ elt.set("id", tableId)
207
+ elt = etree.SubElement(elt, "button")
86
208
  elt.text = modelTable.genLabel(lang=lang, strip=True)
87
- elt.set("class", "CMSListMenuLink")
88
- elt.set("href", "javascript:void(0)")
89
- elt.set("onClick", "javascript:parent.body.location.href='{0}';".format(modelTable.id + ".html"))
209
+ elt.set("class", "nav-list-menu-link")
210
+ elt.set("onClick", f"javascript:parent.loadContent('{tableId}.html');")
90
211
  elt.text = modelTable.genLabel(lang=lang, strip=True)
91
-
92
- else: # just a header
93
- # generaate menu entry
94
- elt = etree.SubElement(listElt, "{http://www.w3.org/1999/xhtml}li")
95
- elt.set("class", "CMSListMenuLink")
96
- elt.set("id", modelTable.id)
212
+ else:
213
+ elt = etree.SubElement(listElt, "li")
214
+ elt.set("id", tableId)
97
215
  elt.text = modelTable.label(lang=lang, strip=True)
98
216
 
99
217
  for rel in groupTableRels.fromModelObject(modelTable):
100
218
  viewTable(rel.toModelObject)
101
219
 
102
-
103
220
  for rootConcept in groupTableRels.rootConcepts:
104
221
  sourceline = 0
105
222
  for rel in dts.modelXbrl.relationshipSet(XbrlConst.euGroupTable).fromModelObject(rootConcept):
@@ -107,171 +224,129 @@ table {background:#fff}
107
224
  break
108
225
  modelTables.append((rootConcept, sourceline))
109
226
 
110
- for modelTable, order in sorted(modelTables, key=lambda x: x[1]):
227
+ for modelTable, _order in sorted(modelTables, key=itemgetter(1)):
111
228
  viewTable(modelTable)
112
229
 
113
- with open(indexBase + "FormsFrame.html", "wt", encoding="utf-8") as fh:
114
- XmlUtil.writexml(fh, indexDocument, encoding="utf-8")
230
+ with open(indexBase + "FormsFrame.html", "w", encoding="utf-8") as fh:
231
+ XmlUtil.writexml(fh, menuFrameDocument, encoding="utf-8")
115
232
 
116
- with open(indexFile, "wt", encoding="utf-8") as fh:
117
- fh.write(
118
- '''
119
- <html xmlns="http://www.w3.org/1999/xhtml">
120
- <head id="Head1">
121
- <title>European Banking Authority - EBA - FINREP Taxonomy</title>
122
- <meta name="generator" content="Arelle(r) {0}" />
123
- <meta name="provider" content="Aguilonius(r)" />
124
- <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
125
- <meta http-equiv="pragma" content="no-cache" />
126
- <meta http-equiv="content-style-type" content="text/css" />
127
- <meta http-equiv="content-script-type" content="text/javascript" />
128
- <link type="text/css" rel="stylesheet" href="http://arelle.org/files/EBA/style20121210/eba.css" />
129
- </head>
130
- <frameset border="0" frameborder="0" rows="90,*">
131
- <frame name="head" src="{1}" scrolling="no" marginwidth="0" marginheight="10"/>
132
- <frameset bordercolor="#0000cc" border="10" frameborder="no" framespacing="0" cols="360, *">
133
- <frame src="{2}" name="menu" bordercolor="#0000cc"/>
134
- <frame src="{3}" name="body" bordercolor="#0000cc"/>
135
- </frameset>
136
- </frameset>
137
- '''.format(Version.version,
138
- os.path.basename(indexBase) + "TopFrame.html",
139
- os.path.basename(indexBase) + "FormsFrame.html",
140
- os.path.basename(indexBase) + "CenterLanding.html",
141
- ))
142
-
143
- with open(indexBase + "TopFrame.html", "wt", encoding="utf-8") as fh:
144
- fh.write(
145
- '''
146
- <html xmlns="http://www.w3.org/1999/xhtml">
147
- <head id="Top">
148
- <link type="text/css" rel="stylesheet" href="http://arelle.org/files/EBA/style20121210/eba.css" />
149
- </head>
150
- <body class="LTR IE7 ENGB">
151
- <div id="topsection">
152
- <div id="topsectionLeft" style="cursor:pointer;" onclick="location.href='http://www.eba.europa.eu/home.aspx';"></div>
153
- <div id="topsectionRight"></div>
154
- <div id="topnavigation">
155
- <ul id="menuElem">
156
- <li><a href="http://www.eba.europa.eu/topnav/Contacts.aspx">Contacts</a></li>
157
- <li><a href="http://www.eba.europa.eu/topnav/Links.aspx">Links</a></li>
158
- <li><a href="http://www.eba.europa.eu/topnav/Sitemap.aspx">Sitemap</a></li>
159
- <li><a href="http://www.eba.europa.eu/topnav/Legal-Notice.aspx">Legal Notice</a></li>
160
- </ul>
161
- </div>
162
- </body>
163
- </html>
164
- ''')
165
-
166
- with open(indexBase + "CenterLanding.html", "wt", encoding="utf-8") as fh:
167
- fh.write(
168
- '''
169
- <html xmlns="http://www.w3.org/1999/xhtml">
170
- <head id="Center">
171
- <link type="text/css" rel="stylesheet" href="http://http://arelle.org/files/EBA/style20121210/eba.css" />
172
- </head>
173
- <body class="LTR IE7 ENGB">
174
- <div id="plc_lt_zoneContent_usercontrol_userControlElem_ContentPanel">
175
- <div id="plc_lt_zoneContent_usercontrol_userControlElem_PanelTitle">
176
- <div id="pagetitle" style="float:left;width:500px;">
177
- <h1>Taxonomy Tables Viewer</h1>
178
- </div>
179
- </div>
180
- </div>
181
- <div style="clear:both;"></div>
182
- <div id="contentcenter">
183
- <p style="text-align: justify; margin-top: 0pt; margin-bottom: 0pt">Please select tables to view by clicking in the left column.</p>
184
- </div>
185
- </body>
186
- </html>
187
- ''')
233
+ with open(indexFile, "w", encoding="utf-8") as fh:
234
+ fh.write(indexFileHTML(os.path.basename(indexBase)))
188
235
 
189
- # to merge gif's and style sheets, use a zipfile sibling of the python plug-in file.
190
- #import zipfile
191
- #zf = zipfile.ZipFile(__file__.rpartition('.')[0] + "Files.zip", mode="r")
192
- #zf.extractall(path=os.path.dirname(indexBase))
193
- #zf.close()
236
+ with open(indexBase + "CenterLanding.html", "w", encoding="utf-8") as fh:
237
+ fh.write(CENTER_LANDING_HTML)
194
238
 
195
- dts.info("info:saveEBAtables",
196
- _("Tables index file of %(entryFile)s has %(numberTableFiles)s table files with index file %(indexFile)s."),
197
- modelObject=dts,
198
- entryFile=dts.uri, numberTableFiles=numTableFiles, indexFile=indexFile)
239
+ dts.info(
240
+ "info:saveEBAtables",
241
+ _("Tables index file of %(entryFile)s has %(numberTableFiles)s table files with index file %(indexFile)s."),
242
+ modelObject=dts,
243
+ entryFile=dts.uri,
244
+ numberTableFiles=numTableFiles,
245
+ indexFile=indexFile,
246
+ )
199
247
 
200
248
  dts.modelManager.showStatus(_("Saved EBA HTML Table Files"), 5000)
201
249
  except Exception as ex:
202
- dts.error("exception",
203
- _("HTML EBA Tableset files generation exception: %(error)s"), error=ex,
250
+ dts.error(
251
+ "exception",
252
+ _("HTML EBA Tableset files generation exception: %(error)s"),
253
+ error=ex,
204
254
  modelXbrl=dts,
205
- exc_info=True)
255
+ exc_info=True,
256
+ )
206
257
 
207
- def saveHtmlEbaTablesMenuEntender(cntlr, menu, *args, **kwargs):
208
- # Extend menu with an item for the save infoset plugin
209
- menu.add_command(label="Save HTML EBA Tables",
210
- underline=0,
211
- command=lambda: saveHtmlEbaTablesMenuCommand(cntlr) )
212
258
 
213
- def saveHtmlEbaTablesMenuCommand(cntlr):
214
- # save Infoset menu item has been invoked
215
- from arelle.ModelDocument import Type
259
+ def saveHtmlEbaTablesMenuCommand(cntlr: CntlrWinMain) -> None:
216
260
  if cntlr.modelManager is None or cntlr.modelManager.modelXbrl is None:
217
- cntlr.addToLog("No DTS loaded.")
261
+ cntlr.addToLog("No DTS loaded.") # type: ignore[no-untyped-call]
218
262
  return
219
263
 
220
- # get file name into which to save log file while in foreground thread
221
- indexFile = cntlr.uiFileDialog("save",
222
- title=_("arelle - Save HTML EBA Tables Index file"),
223
- initialdir=cntlr.config.setdefault("htmlEbaTablesFileDir","."),
224
- filetypes=[(_("HTML index file .html"), "*.html")],
225
- defaultextension=".html")
226
- if not indexFile:
227
- return False
228
- import os
264
+ assert cntlr.config is not None
265
+ indexFile = cntlr.uiFileDialog( # type: ignore[no-untyped-call]
266
+ "save",
267
+ title=_("arelle - Save HTML EBA Tables Index file"),
268
+ initialdir=cntlr.config.setdefault("htmlEbaTablesFileDir", "."),
269
+ filetypes=[(_("HTML index file .html"), "*.html")],
270
+ defaultextension=".html",
271
+ )
272
+ if not isinstance(indexFile, str):
273
+ return
229
274
  cntlr.config["htmlEbaTablesFileDir"] = os.path.dirname(indexFile)
230
275
  cntlr.saveConfig()
231
276
 
232
- import threading
233
- thread = threading.Thread(target=lambda
234
- _dts=cntlr.modelManager.modelXbrl,
235
- _indexFile=indexFile:
236
- generateHtmlEbaTablesetFiles(_dts, _indexFile))
277
+ thread = threading.Thread(
278
+ target=lambda _dts=cntlr.modelManager.modelXbrl, _indexFile=indexFile: generateHtmlEbaTablesetFiles(
279
+ _dts, _indexFile
280
+ )
281
+ )
237
282
  thread.daemon = True
238
283
  thread.start()
239
284
 
240
- def saveHtmlEbaTablesCommandLineOptionExtender(parser, *args, **kwargs):
241
- # extend command line options with a save DTS option
242
- parser.add_option("--save-EBA-tablesets",
243
- action="store",
244
- dest="ebaTablesetIndexFile",
245
- help=_("Save HTML EBA Tablesets index file, with tablest HTML files to out directory specify 'generateOutFiles'."))
246
-
247
- def saveHtmlEbaTablesCommandLineXbrlLoaded(cntlr, options, modelXbrl, *args, **kwargs):
248
- # extend XBRL-loaded run processing for this option
249
- from arelle.ModelDocument import Type
250
- if getattr(options, "ebaTablesetIndexFile", None) and options.ebaTablesetIndexFile == "generateEBAFiles" and modelXbrl.modelDocument.type in (Type.TESTCASESINDEX, Type.TESTCASE):
251
- cntlr.modelManager.generateEBAFiles = True
252
-
253
- def saveHtmlEbaTablesCommandLineXbrlRun(cntlr, options, modelXbrl, *args, **kwargs):
254
- # extend XBRL-loaded run processing for this option
255
- if getattr(options, "ebaTablesetIndexFile", None) and options.ebaTablesetIndexFile != "generateEBAFiles":
285
+
286
+ class SaveHtmlEbaTablesPlugin(PluginHooks):
287
+ @staticmethod
288
+ def cntlrWinMainMenuTools(
289
+ cntlr: CntlrWinMain,
290
+ menu: Menu,
291
+ *args: Any,
292
+ **kwargs: Any,
293
+ ) -> None:
294
+ menu.add_command(label="Save HTML EBA Tables", underline=0, command=lambda: saveHtmlEbaTablesMenuCommand(cntlr))
295
+
296
+ @staticmethod
297
+ def cntlrCmdLineOptions(
298
+ parser: OptionParser,
299
+ *args: Any,
300
+ **kwargs: Any,
301
+ ) -> None:
302
+ parser.add_option(
303
+ "--save-EBA-tablesets",
304
+ action="store",
305
+ dest="ebaTablesetIndexFile",
306
+ help=_("Save HTML EBA Tablesets index file with provided filename."),
307
+ )
308
+
309
+ @staticmethod
310
+ def cntlrCmdLineXbrlLoaded(
311
+ cntlr: CntlrCmdLine,
312
+ options: RuntimeOptions,
313
+ modelXbrl: ModelXbrl,
314
+ *args: Any,
315
+ **kwargs: Any,
316
+ ) -> None:
317
+ ebaTablesetIndexFile = getattr(options, "ebaTablesetIndexFile", None)
318
+ modelDocType = getattr(modelXbrl.modelDocument, "type", None)
319
+ if ebaTablesetIndexFile == "generateEBAFiles" and modelDocType in (Type.TESTCASESINDEX, Type.TESTCASE):
320
+ cntlr.modelManager.generateEBAFiles = True # type: ignore[attr-defined]
321
+
322
+ @staticmethod
323
+ def cntlrCmdLineXbrlRun(
324
+ cntlr: CntlrCmdLine,
325
+ options: RuntimeOptions,
326
+ modelXbrl: ModelXbrl,
327
+ *args: Any,
328
+ **kwargs: Any,
329
+ ) -> None:
330
+ ebaTablesetIndexFile = getattr(options, "ebaTablesetIndexFile", None)
331
+ if ebaTablesetIndexFile is None or ebaTablesetIndexFile == "generateEBAFiles":
332
+ return
256
333
  if cntlr.modelManager is None or cntlr.modelManager.modelXbrl is None:
257
334
  cntlr.addToLog("No taxonomy loaded.")
258
335
  return
259
-
260
- from arelle import RenderingEvaluator
261
- RenderingEvaluator.init(modelXbrl)
262
- generateHtmlEbaTablesetFiles(cntlr.modelManager.modelXbrl, options.ebaTablesetIndexFile)
336
+ RenderingEvaluator.init(modelXbrl) # type: ignore[no-untyped-call]
337
+ generateHtmlEbaTablesetFiles(cntlr.modelManager.modelXbrl, ebaTablesetIndexFile)
263
338
 
264
339
 
265
340
  __pluginInfo__ = {
266
- 'name': 'Save HTML EBA Tables',
267
- 'version': '0.9',
268
- 'description': "This plug-in adds a feature to a directory containing HTML Tablesets with an EBA index page. ",
269
- 'license': 'Apache-2',
270
- 'author': authorLabel,
271
- 'copyright': copyrightLabel,
341
+ "name": "Save HTML EBA Tables",
342
+ "version": "0.10",
343
+ "description": "This plug-in adds a feature to a directory containing HTML Tablesets with an EBA index page.",
344
+ "license": "Apache-2",
345
+ "author": Version.authorLabel,
346
+ "copyright": Version.copyrightLabel,
272
347
  # classes of mount points (required)
273
- 'CntlrWinMain.Menu.Tools': saveHtmlEbaTablesMenuEntender,
274
- 'CntlrCmdLine.Options': saveHtmlEbaTablesCommandLineOptionExtender,
275
- 'CntlrCmdLine.Xbrl.Loaded': saveHtmlEbaTablesCommandLineXbrlLoaded,
276
- 'CntlrCmdLine.Xbrl.Run': saveHtmlEbaTablesCommandLineXbrlRun,
348
+ "CntlrWinMain.Menu.Tools": SaveHtmlEbaTablesPlugin.cntlrWinMainMenuTools,
349
+ "CntlrCmdLine.Options": SaveHtmlEbaTablesPlugin.cntlrCmdLineOptions,
350
+ "CntlrCmdLine.Xbrl.Loaded": SaveHtmlEbaTablesPlugin.cntlrCmdLineXbrlLoaded,
351
+ "CntlrCmdLine.Xbrl.Run": SaveHtmlEbaTablesPlugin.cntlrCmdLineXbrlRun,
277
352
  }
@@ -94,6 +94,33 @@ class PluginHooks(ABC):
94
94
  """
95
95
  raise NotImplementedError
96
96
 
97
+ @staticmethod
98
+ def cntlrCmdLineXbrlLoaded(
99
+ cntlr: CntlrCmdLine,
100
+ options: RuntimeOptions,
101
+ modelXbrl: ModelXbrl,
102
+ entrypoint: dict[str, str] | None = None,
103
+ responseZipStream: BinaryIO | None = None,
104
+ *args: Any,
105
+ **kwargs: Any,
106
+ ) -> None:
107
+ """
108
+ Plugin hook: `CntlrCmdLine.Xbrl.Loaded`
109
+
110
+ This hook is triggered after loading, but prior to validation (if requested) and loading views.
111
+ It's useful if you need to perform operations with the XBRL model prior to rendering views.
112
+
113
+ :param cntlr: The [Cntlr](#arelle.Cntlr.Cntlr) being initialized.
114
+ :param options: Parsed options object.
115
+ :param modelXbrl: The loaded [ModelXbrl](#arelle.ModelXbrl.ModelXbrl).
116
+ :param entrypoint: The entrypoint that was parsed to load the model.
117
+ :param responseZipStream: The response zip stream if loaded from the webserver and the user requested a zip response.
118
+ :param args: Argument capture to ensure new parameters don't break plugin hook.
119
+ :param kwargs: Argument capture to ensure new named parameters don't break plugin hook.
120
+ :return: None
121
+ """
122
+ raise NotImplementedError
123
+
97
124
  @staticmethod
98
125
  def cntlrCmdLineXbrlRun(
99
126
  cntlr: CntlrCmdLine,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: arelle-release
3
- Version: 2.36.27
3
+ Version: 2.36.29
4
4
  Summary: An open source XBRL platform.
5
5
  Author-email: "arelle.org" <support@arelle.org>
6
6
  License: Apache-2.0
@@ -66,7 +66,7 @@ arelle/UiUtil.py,sha256=3G0xPclZI8xW_XQDbiFrmylB7Nd5muqi5n2x2oMkMZU,34218
66
66
  arelle/Updater.py,sha256=ho8Z_9GOL39H1jHL3Gaw5uc6av7J8ZBB6dR_X-nF_e0,7124
67
67
  arelle/UrlUtil.py,sha256=HrxZSG59EUMGMMGmWPuZkPi5-0BGqY3jAMkp7V4IdZo,32400
68
68
  arelle/Validate.py,sha256=CBgvAqT_v3LwF0Z1b_QiiePf80x_sTqiSvA9RYBAwqI,55276
69
- arelle/ValidateDuplicateFacts.py,sha256=Bd9K5cv_QS2kDAXhKSkzVFWUjFomQicxoumounFedFY,20976
69
+ arelle/ValidateDuplicateFacts.py,sha256=074y-VWCOBHoi6iV6wDL_IXBtdz9oeI1uPvjEcC1oDs,21717
70
70
  arelle/ValidateFilingText.py,sha256=XLlwKldK__191s6VDbgbhFeNhG5wp8hmZNr4kRCLu_E,53996
71
71
  arelle/ValidateInfoset.py,sha256=Rz_XBi5Ha43KpxXYhjLolURcWVx5qmqyjLxw48Yt9Dg,20396
72
72
  arelle/ValidateUtr.py,sha256=oxOPrOa1XEzBay4miXvx6eRLTnVFYUIJC9ueWUk4EkI,13633
@@ -123,7 +123,7 @@ arelle/XmlValidateConst.py,sha256=U_wN0Q-nWKwf6dKJtcu_83FXPn9c6P8JjzGA5b0w7P0,33
123
123
  arelle/XmlValidateParticles.py,sha256=Mn6vhFl0ZKC_vag1mBwn1rH_x2jmlusJYqOOuxFPO2k,9231
124
124
  arelle/XmlValidateSchema.py,sha256=6frtZOc1Yrx_5yYF6V6oHbScnglWrVbWr6xW4EHtLQI,7428
125
125
  arelle/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
126
- arelle/_version.py,sha256=KK-jzMUiNZ-nw4JlDwy7wbO1_sQsynI1BXJTEqnnBqc,515
126
+ arelle/_version.py,sha256=sRa_qk_q3QBynjyOYjA-67YubqjzRKZedln2S9tzhN4,515
127
127
  arelle/typing.py,sha256=Ct5lrNKRow_o9CraMEXNza8nFsJ_iGIKoUeGfPs2dxI,1084
128
128
  arelle/api/Session.py,sha256=Vd09RAutWX7mxHSsrW7Bl8CsE1UzXpQpAJsZb55mqng,6188
129
129
  arelle/archive/CustomLogger.py,sha256=v_JXOCQLDZcfaFWzxC9FRcEf9tQi4rCI4Sx7jCuAVQI,1231
@@ -321,7 +321,7 @@ arelle/plugin/profileCmdLine.py,sha256=uLL0fGshpiwtzyLKAtW0WuXAvcRtZgxQG6swM0e4B
321
321
  arelle/plugin/profileFormula.py,sha256=PK273bQpYO-87QMFpBVQmnqGKfjDElIXPhAwc8Nyp04,5548
322
322
  arelle/plugin/saveCHComponentFile.py,sha256=phW-n96P0BWONLqX3wBcuNcDa_IQ8QVfFWKL9NRrh7k,7242
323
323
  arelle/plugin/saveDTS.py,sha256=D8hfFiM9Gc9LE7ZV0-pDvOHeU7Y5ebOzZx_nXzoZrl8,3216
324
- arelle/plugin/saveHtmlEBAtables.py,sha256=fZcagH2uE8bGpapsArmVjKLlzexciCi08nlZ72P9RK0,12201
324
+ arelle/plugin/saveHtmlEBAtables.py,sha256=OITN4ftI_atUO5NvrDJDKUc0CAk67GY68Jl16xDBXiI,10961
325
325
  arelle/plugin/saveLoadableExcel.py,sha256=ZPYB6injFabav0dRzgS7adCFzfHkwtftjl3PfBjijuU,20539
326
326
  arelle/plugin/saveLoadableOIM.py,sha256=19R5mZzIDkPohEdabs22nOhIZoEPe8vR0HczqN3oSI0,36239
327
327
  arelle/plugin/saveSKOS.py,sha256=7Z1Qedf83zMo9EbigKkxNpiMjzkTYQvLEnwWMObLc1Y,12465
@@ -697,7 +697,7 @@ arelle/scripts-macOS/startWebServer.command,sha256=KXLSwAwchDZBlL-k9PYXdf39RNBtt
697
697
  arelle/scripts-unix/startWebServer.sh,sha256=_0puRzaGkdMZoFn3R7hDti9a3ryN6kTZAXwLweeZU1s,42
698
698
  arelle/scripts-windows/startWebServer.bat,sha256=qmnF1yrjNo__bi4QodONWlN0qHShVLTKptJQYyZtgcY,122
699
699
  arelle/utils/PluginData.py,sha256=GUnuZaApm1J4Xm9ZA1U2M1aask-AaNGviLtc0fgXbFg,265
700
- arelle/utils/PluginHooks.py,sha256=A9wUuY9ktxmQtDzg6pHm4kHu00f-M3V6iAeU-5CS31s,28997
700
+ arelle/utils/PluginHooks.py,sha256=USi7zEZzwzTnKdKSyM3Xjacodo6YcU_Lb3EPQwWiVg4,30220
701
701
  arelle/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
702
702
  arelle/utils/validate/Decorator.py,sha256=8LGmA171HZgKrALtsMKyHqMNM-XCdwJOv6KpZz4pC2c,3161
703
703
  arelle/utils/validate/DetectScriptsInXhtml.py,sha256=JOgsUP0_kvE6O3TuhzKxqKG1ZFP9LrUYZBgrar6JEm0,2178
@@ -716,6 +716,7 @@ tests/integration_tests/scripts/script_util.py,sha256=MuLV2X4NfaVcLXTSq1C4lxnInH
716
716
  tests/integration_tests/scripts/test_scripts.py,sha256=6kSN385J3KG3k9rQN3aPP9bx70vzpCb4PbUGbGbJYU8,506
717
717
  tests/integration_tests/scripts/tests/duplicate_facts_deduplication.py,sha256=qMWTwbIJmKMncEiDjoHIkQYJYKUmxj3JJ_S1hTlLAMs,4698
718
718
  tests/integration_tests/scripts/tests/duplicate_facts_validate.py,sha256=hxguoZZnyaPdWfAeLwfbLpIRpTbB6O-nJ5E4j7XYLRE,3743
719
+ tests/integration_tests/scripts/tests/eba_tablesets.py,sha256=6uayfssQ7p3zyw6IUcIhraQMJiTsIVPxqYzQWdLx5q8,4232
719
720
  tests/integration_tests/scripts/tests/entry_point_from_taxonomy_package.py,sha256=KCaK08pQ6N18lIUxhWbeyvNeYQdSHk0Rxi86On3W_n8,1525
720
721
  tests/integration_tests/scripts/tests/ixbrl-viewer_cli.py,sha256=OdXj9p148MG9fycO3opyNIClYEDhyf8z4HKGFhrEAYU,2222
721
722
  tests/integration_tests/scripts/tests/ixbrl-viewer_webserver.py,sha256=dDcftyfu9IA2qWih9CZUfoTmjeP_Zmng488N7Gp1wFU,2848
@@ -1552,9 +1553,9 @@ tests/unit_tests/arelle/oim/test_load.py,sha256=NxiUauQwJVfWAHbbpsMHGSU2d3Br8Pki
1552
1553
  tests/unit_tests/arelle/plugin/test_plugin_imports.py,sha256=bdhIs9frAnFsdGU113yBk09_jis-z43dwUItMFYuSYM,1064
1553
1554
  tests/unit_tests/arelle/plugin/validate/ESEF/ESEF_Current/test_validate_css_url.py,sha256=XHABmejQt7RlZ0udh7v42f2Xb2STGk_fSaIaJ9i2xo0,878
1554
1555
  tests/unit_tests/arelle/utils/validate/test_decorator.py,sha256=ZS8FqIY1g-2FCbjF4UYm609dwViax6qBMRJSi0vfuhY,2482
1555
- arelle_release-2.36.27.dist-info/LICENSE.md,sha256=rMbWwFLGzPgLoEjEu8LCmkpWDTqsvfOI-wzLSfeJsis,4107
1556
- arelle_release-2.36.27.dist-info/METADATA,sha256=h1D54IXAymwwmAytd4w-uoNt6wrgRDgcx9i7kBBE38k,9010
1557
- arelle_release-2.36.27.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
1558
- arelle_release-2.36.27.dist-info/entry_points.txt,sha256=Uj5niwfwVsx3vaQ3fYj8hrZ1xpfCJyTUA09tYKWbzpo,111
1559
- arelle_release-2.36.27.dist-info/top_level.txt,sha256=ZYmYGmhW5Jvo3vJ4iXBZPUI29LvYhntom04w90esJvU,13
1560
- arelle_release-2.36.27.dist-info/RECORD,,
1556
+ arelle_release-2.36.29.dist-info/LICENSE.md,sha256=rMbWwFLGzPgLoEjEu8LCmkpWDTqsvfOI-wzLSfeJsis,4107
1557
+ arelle_release-2.36.29.dist-info/METADATA,sha256=15UCnvMqfHvhaCle9jkkfDypnkx9UtyeUkHMfoDbGAk,9010
1558
+ arelle_release-2.36.29.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
1559
+ arelle_release-2.36.29.dist-info/entry_points.txt,sha256=Uj5niwfwVsx3vaQ3fYj8hrZ1xpfCJyTUA09tYKWbzpo,111
1560
+ arelle_release-2.36.29.dist-info/top_level.txt,sha256=ZYmYGmhW5Jvo3vJ4iXBZPUI29LvYhntom04w90esJvU,13
1561
+ arelle_release-2.36.29.dist-info/RECORD,,
@@ -0,0 +1,119 @@
1
+ from __future__ import annotations
2
+
3
+ import html.parser
4
+ import os
5
+ import urllib.request
6
+ import zipfile
7
+ from pathlib import Path
8
+ from shutil import rmtree
9
+
10
+ from tests.integration_tests.integration_test_util import get_s3_uri
11
+ from tests.integration_tests.scripts.script_util import (
12
+ assert_result,
13
+ parse_args,
14
+ prepare_logfile,
15
+ run_arelle,
16
+ validate_log_file,
17
+ )
18
+
19
+ errors = []
20
+ this_file = Path(__file__)
21
+ args = parse_args(this_file.stem, "Confirm EBA Tablesets report runs successfully from the command line.")
22
+ arelle_command = args.arelle
23
+ arelle_offline = args.offline
24
+ working_directory = Path(args.working_directory)
25
+ test_directory = Path(args.test_directory)
26
+ arelle_log_file = prepare_logfile(test_directory, this_file)
27
+ samples_zip_path = test_directory / "eba_samples.zip"
28
+ samples_directory = test_directory / "eba_samples"
29
+ target_path = samples_directory / "DUMMYLEI123456789012_GB_COREP030000_COREPLECON_2021-06-30_20201218154732000.xbrl"
30
+ tablesets_report_path = test_directory / "index.html"
31
+ sample_report_zip_url = get_s3_uri("ci/packages/eba-samples.zip", version_id="iDJU3nFy6_rQ289k.mosenHjUFrXCmCM")
32
+
33
+ samples_url = get_s3_uri("ci/packages/eba_samples.zip", version_id="O7uYHbSYmxe_20nBhWWoXMfjGpquNMMj")
34
+
35
+ print(f"Downloading EBA sample files: {samples_zip_path}")
36
+ urllib.request.urlretrieve(samples_url, samples_zip_path)
37
+
38
+ print(f"Extracting EBA sample files: {samples_directory}")
39
+ with zipfile.ZipFile(samples_zip_path, "r") as zip_ref:
40
+ zip_ref.extractall(samples_directory)
41
+
42
+ print(f"Generating EBA Tablesets report: {tablesets_report_path}")
43
+ run_arelle(
44
+ arelle_command,
45
+ plugins=["saveHtmlEBAtables"],
46
+ additional_args=[
47
+ "-f",
48
+ str(target_path),
49
+ "--package",
50
+ str(samples_directory / "TP-Eurofiling2.1.zip"),
51
+ "--package",
52
+ str(samples_directory / "EBA_CRD_IV_XBRL_3.0_Dictionary_3.0.1.0.Errata3.zip"),
53
+ "--package",
54
+ str(samples_directory / "EBA_CRD_IV_XBRL_3.0_Reporting_COREP_FINREP_Frameworks_hotfix.Errata3.zip"),
55
+ "--save-EBA-tablesets",
56
+ str(tablesets_report_path),
57
+ ],
58
+ offline=arelle_offline,
59
+ logFile=arelle_log_file,
60
+ )
61
+
62
+ print(f"Checking for EBA Tablesets report: {tablesets_report_path}")
63
+ if not tablesets_report_path.exists():
64
+ errors.append(f'EBA Tablesets report not generated at "{tablesets_report_path}"')
65
+
66
+ eba_table_files = [
67
+ "eba_tC_00.01.html",
68
+ "eba_tC_26.00.html",
69
+ "eba_tC_27.00.html",
70
+ "eba_tC_28.00.html",
71
+ "eba_tC_29.00.html",
72
+ ]
73
+
74
+ eba_tablesets_report_files = [
75
+ test_directory / f for f in ("index.html", "indexCenterLanding.html", "indexFormsFrame.html", *eba_table_files)
76
+ ]
77
+
78
+ for report_file in eba_tablesets_report_files:
79
+ if not report_file.exists():
80
+ errors.append(f'EBA Tablesets report file not generated at "{report_file}"')
81
+
82
+
83
+ class ButtonParser(html.parser.HTMLParser):
84
+ def __init__(self, table_files):
85
+ super().__init__()
86
+ self.table_files = table_files
87
+ self.found_buttons = {table_file: False for table_file in table_files}
88
+
89
+ def handle_starttag(self, tag, attrs):
90
+ if tag.lower() == "button":
91
+ attrs_dict = dict(attrs)
92
+ onclick = attrs_dict.get("onclick", "")
93
+ if onclick is not None:
94
+ for table_file in self.table_files:
95
+ expected_onclick = f"javascript:parent.loadContent('{table_file}');"
96
+ if expected_onclick in onclick:
97
+ self.found_buttons[table_file] = True
98
+
99
+
100
+ forms_frame_path = test_directory / "indexFormsFrame.html"
101
+ print("Checking for proper button elements in indexFormsFrame.html")
102
+ with open(forms_frame_path, encoding="utf-8") as fh:
103
+ html_content = fh.read()
104
+ parser = ButtonParser(eba_table_files)
105
+ parser.feed(html_content)
106
+ for table_file, found in parser.found_buttons.items():
107
+ if not found:
108
+ errors.append(f"Button for table {table_file} not found in indexFormsFrame.html")
109
+
110
+ print(f"Checking for log errors: {arelle_log_file}")
111
+ errors += validate_log_file(arelle_log_file)
112
+
113
+ assert_result(errors)
114
+
115
+ print("Cleaning up")
116
+ rmtree(samples_directory)
117
+ os.unlink(samples_zip_path)
118
+ for report_file in eba_tablesets_report_files:
119
+ os.unlink(report_file)