ixbrl-viewer 1.4.1__py3-none-any.whl → 1.4.86__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 (190) hide show
  1. iXBRLViewerPlugin/__init__.py +231 -127
  2. iXBRLViewerPlugin/_version.py +33 -3
  3. iXBRLViewerPlugin/constants.py +96 -2
  4. iXBRLViewerPlugin/featureConfig.py +8 -1
  5. iXBRLViewerPlugin/iXBRLViewer.py +356 -214
  6. iXBRLViewerPlugin/plugin.py +12 -0
  7. iXBRLViewerPlugin/ui.py +81 -50
  8. iXBRLViewerPlugin/viewer/dist/ixbrlviewer.js +1 -1
  9. iXBRLViewerPlugin/viewer/dist/ixbrlviewer.js.LICENSE.txt +12 -5
  10. iXBRLViewerPlugin/viewer/i18next-parser.config.js +1 -1
  11. iXBRLViewerPlugin/viewer/src/data/utr.json +1244 -0
  12. iXBRLViewerPlugin/viewer/src/html/fact-details.html +69 -38
  13. iXBRLViewerPlugin/viewer/src/html/footer-logo.html +4 -0
  14. iXBRLViewerPlugin/viewer/src/html/footnote-details.html +2 -2
  15. iXBRLViewerPlugin/viewer/src/html/inspector.html +352 -197
  16. iXBRLViewerPlugin/viewer/src/i18n/cy/balancetypes.json +1 -0
  17. iXBRLViewerPlugin/viewer/src/i18n/cy/currencies.json +13 -0
  18. iXBRLViewerPlugin/viewer/src/i18n/cy/datatypes.json +9 -0
  19. iXBRLViewerPlugin/viewer/src/i18n/cy/labelroles.json +24 -0
  20. iXBRLViewerPlugin/viewer/src/i18n/cy/referenceparts.json +10 -0
  21. iXBRLViewerPlugin/viewer/src/i18n/cy/scale.json +16 -0
  22. iXBRLViewerPlugin/viewer/src/i18n/cy/tooltips.json +17 -0
  23. iXBRLViewerPlugin/viewer/src/i18n/cy/translation.json +179 -0
  24. iXBRLViewerPlugin/viewer/src/i18n/da/balancetypes.json +4 -0
  25. iXBRLViewerPlugin/viewer/src/i18n/da/currencies.json +13 -0
  26. iXBRLViewerPlugin/viewer/src/i18n/da/datatypes.json +9 -0
  27. iXBRLViewerPlugin/viewer/src/i18n/da/labelroles.json +24 -0
  28. iXBRLViewerPlugin/viewer/src/i18n/da/referenceparts.json +10 -0
  29. iXBRLViewerPlugin/viewer/src/i18n/da/scale.json +15 -0
  30. iXBRLViewerPlugin/viewer/src/i18n/da/tooltips.json +17 -0
  31. iXBRLViewerPlugin/viewer/src/i18n/da/translation.json +179 -0
  32. iXBRLViewerPlugin/viewer/src/i18n/de/balancetypes.json +4 -0
  33. iXBRLViewerPlugin/viewer/src/i18n/de/currencies.json +13 -0
  34. iXBRLViewerPlugin/viewer/src/i18n/de/datatypes.json +9 -0
  35. iXBRLViewerPlugin/viewer/src/i18n/de/labelroles.json +24 -0
  36. iXBRLViewerPlugin/viewer/src/i18n/de/referenceparts.json +10 -0
  37. iXBRLViewerPlugin/viewer/src/i18n/de/scale.json +15 -0
  38. iXBRLViewerPlugin/viewer/src/i18n/de/tooltips.json +17 -0
  39. iXBRLViewerPlugin/viewer/src/i18n/de/translation.json +179 -0
  40. iXBRLViewerPlugin/viewer/src/i18n/en/balancetypes.json +4 -0
  41. iXBRLViewerPlugin/viewer/src/i18n/en/datatypes.json +10 -0
  42. iXBRLViewerPlugin/viewer/src/i18n/en/labelroles.json +4 -0
  43. iXBRLViewerPlugin/viewer/src/i18n/en/scale.json +16 -0
  44. iXBRLViewerPlugin/viewer/src/i18n/en/tooltips.json +17 -0
  45. iXBRLViewerPlugin/viewer/src/i18n/en/translation.json +73 -23
  46. iXBRLViewerPlugin/viewer/src/i18n/es/balancetypes.json +4 -0
  47. iXBRLViewerPlugin/viewer/src/i18n/es/datatypes.json +10 -0
  48. iXBRLViewerPlugin/viewer/src/i18n/es/labelroles.json +24 -0
  49. iXBRLViewerPlugin/viewer/src/i18n/es/scale.json +16 -0
  50. iXBRLViewerPlugin/viewer/src/i18n/es/tooltips.json +17 -0
  51. iXBRLViewerPlugin/viewer/src/i18n/es/translation.json +87 -37
  52. iXBRLViewerPlugin/viewer/src/i18n/fr/balancetypes.json +4 -0
  53. iXBRLViewerPlugin/viewer/src/i18n/fr/currencies.json +13 -0
  54. iXBRLViewerPlugin/viewer/src/i18n/fr/datatypes.json +9 -0
  55. iXBRLViewerPlugin/viewer/src/i18n/fr/labelroles.json +24 -0
  56. iXBRLViewerPlugin/viewer/src/i18n/fr/referenceparts.json +10 -0
  57. iXBRLViewerPlugin/viewer/src/i18n/fr/scale.json +15 -0
  58. iXBRLViewerPlugin/viewer/src/i18n/fr/tooltips.json +17 -0
  59. iXBRLViewerPlugin/viewer/src/i18n/fr/translation.json +179 -0
  60. iXBRLViewerPlugin/viewer/src/i18n/nl/balancetypes.json +4 -0
  61. iXBRLViewerPlugin/viewer/src/i18n/nl/currencies.json +13 -0
  62. iXBRLViewerPlugin/viewer/src/i18n/nl/datatypes.json +9 -0
  63. iXBRLViewerPlugin/viewer/src/i18n/nl/labelroles.json +24 -0
  64. iXBRLViewerPlugin/viewer/src/i18n/nl/referenceparts.json +10 -0
  65. iXBRLViewerPlugin/viewer/src/i18n/nl/scale.json +15 -0
  66. iXBRLViewerPlugin/viewer/src/i18n/nl/tooltips.json +17 -0
  67. iXBRLViewerPlugin/viewer/src/i18n/nl/translation.json +179 -0
  68. iXBRLViewerPlugin/viewer/src/i18n/uk/balancetypes.json +4 -0
  69. iXBRLViewerPlugin/viewer/src/i18n/uk/currencies.json +13 -0
  70. iXBRLViewerPlugin/viewer/src/i18n/uk/datatypes.json +9 -0
  71. iXBRLViewerPlugin/viewer/src/i18n/uk/labelroles.json +24 -0
  72. iXBRLViewerPlugin/viewer/src/i18n/uk/referenceparts.json +10 -0
  73. iXBRLViewerPlugin/viewer/src/i18n/uk/scale.json +15 -0
  74. iXBRLViewerPlugin/viewer/src/i18n/uk/tooltips.json +17 -0
  75. iXBRLViewerPlugin/viewer/src/i18n/uk/translation.json +179 -0
  76. iXBRLViewerPlugin/viewer/src/icons/calculator.svg +13 -0
  77. iXBRLViewerPlugin/viewer/src/icons/circle-cross.svg +11 -0
  78. iXBRLViewerPlugin/viewer/src/icons/circle-tick.svg +11 -0
  79. iXBRLViewerPlugin/viewer/src/icons/dark-mode.svg +4 -0
  80. iXBRLViewerPlugin/viewer/src/icons/dimension.svg +1 -5
  81. iXBRLViewerPlugin/viewer/src/icons/member.svg +2 -5
  82. iXBRLViewerPlugin/viewer/src/icons/multi-tag.svg +10 -0
  83. iXBRLViewerPlugin/viewer/src/img/arelle-dark.svg +179 -0
  84. iXBRLViewerPlugin/viewer/src/img/inline-viewer-dark.svg +59 -0
  85. iXBRLViewerPlugin/viewer/src/js/accordian.js +5 -4
  86. iXBRLViewerPlugin/viewer/src/js/aspect.js +29 -10
  87. iXBRLViewerPlugin/viewer/src/js/aspect.test.js +40 -31
  88. iXBRLViewerPlugin/viewer/src/js/balance.js +14 -0
  89. iXBRLViewerPlugin/viewer/src/js/calculation.js +213 -0
  90. iXBRLViewerPlugin/viewer/src/js/calculation.test.js +306 -0
  91. iXBRLViewerPlugin/viewer/src/js/calculationInspector.js +187 -0
  92. iXBRLViewerPlugin/viewer/src/js/chart.js +26 -24
  93. iXBRLViewerPlugin/viewer/src/js/chart.test.js +10 -9
  94. iXBRLViewerPlugin/viewer/src/js/concept.js +37 -4
  95. iXBRLViewerPlugin/viewer/src/js/concept.test.js +30 -6
  96. iXBRLViewerPlugin/viewer/src/js/datatype.js +20 -0
  97. iXBRLViewerPlugin/viewer/src/js/datatype.test.js +62 -0
  98. iXBRLViewerPlugin/viewer/src/js/dialog.js +6 -4
  99. iXBRLViewerPlugin/viewer/src/js/docOrderIndex.js +7 -7
  100. iXBRLViewerPlugin/viewer/src/js/fact.js +156 -59
  101. iXBRLViewerPlugin/viewer/src/js/fact.test.js +160 -29
  102. iXBRLViewerPlugin/viewer/src/js/factset.js +64 -15
  103. iXBRLViewerPlugin/viewer/src/js/factset.test.js +102 -31
  104. iXBRLViewerPlugin/viewer/src/js/footnote.js +8 -2
  105. iXBRLViewerPlugin/viewer/src/js/index.js +11 -3
  106. iXBRLViewerPlugin/viewer/src/js/inspector.js +747 -221
  107. iXBRLViewerPlugin/viewer/src/js/inspector.test.js +143 -25
  108. iXBRLViewerPlugin/viewer/src/js/interval.js +70 -0
  109. iXBRLViewerPlugin/viewer/src/js/interval.test.js +153 -0
  110. iXBRLViewerPlugin/viewer/src/js/ixbrlviewer.js +391 -262
  111. iXBRLViewerPlugin/viewer/src/js/ixbrlviewer.test.js +134 -20
  112. iXBRLViewerPlugin/viewer/src/js/ixnode.js +1 -1
  113. iXBRLViewerPlugin/viewer/src/js/menu.js +25 -7
  114. iXBRLViewerPlugin/viewer/src/js/number-matcher.js +7 -3
  115. iXBRLViewerPlugin/viewer/src/js/number-matcher.test.js +4 -0
  116. iXBRLViewerPlugin/viewer/src/js/outline.js +34 -13
  117. iXBRLViewerPlugin/viewer/src/js/outline.test.js +97 -91
  118. iXBRLViewerPlugin/viewer/src/js/period.js +0 -1
  119. iXBRLViewerPlugin/viewer/src/js/report.js +260 -351
  120. iXBRLViewerPlugin/viewer/src/js/report.test.js +95 -27
  121. iXBRLViewerPlugin/viewer/src/js/reportset.js +264 -0
  122. iXBRLViewerPlugin/viewer/src/js/reportset.test.js +357 -0
  123. iXBRLViewerPlugin/viewer/src/js/search.js +72 -38
  124. iXBRLViewerPlugin/viewer/src/js/search.test.js +184 -84
  125. iXBRLViewerPlugin/viewer/src/js/summary.js +34 -8
  126. iXBRLViewerPlugin/viewer/src/js/summary.test.js +69 -25
  127. iXBRLViewerPlugin/viewer/src/js/tableExport.js +9 -9
  128. iXBRLViewerPlugin/viewer/src/js/taxonomynamer.js +34 -0
  129. iXBRLViewerPlugin/viewer/src/js/taxonomynamer.test.js +32 -0
  130. iXBRLViewerPlugin/viewer/src/js/test-utils.js +46 -0
  131. iXBRLViewerPlugin/viewer/src/js/theme.js +50 -0
  132. iXBRLViewerPlugin/viewer/src/js/unit.js +90 -32
  133. iXBRLViewerPlugin/viewer/src/js/unit.test.js +62 -25
  134. iXBRLViewerPlugin/viewer/src/js/util.js +94 -0
  135. iXBRLViewerPlugin/viewer/src/js/util.test.js +33 -1
  136. iXBRLViewerPlugin/viewer/src/js/utr.js +27 -0
  137. iXBRLViewerPlugin/viewer/src/js/viewer.js +205 -181
  138. iXBRLViewerPlugin/viewer/src/js/viewerOptions.js +0 -2
  139. iXBRLViewerPlugin/viewer/src/less/accordian.less +10 -6
  140. iXBRLViewerPlugin/viewer/src/less/block-list.less +16 -5
  141. iXBRLViewerPlugin/viewer/src/less/calculation-inspector.less +83 -0
  142. iXBRLViewerPlugin/viewer/src/less/chart.less +8 -5
  143. iXBRLViewerPlugin/viewer/src/less/colours-dark-mode.less +40 -0
  144. iXBRLViewerPlugin/viewer/src/less/colours.less +32 -20
  145. iXBRLViewerPlugin/viewer/src/less/common.less +3 -3
  146. iXBRLViewerPlugin/viewer/src/less/components.less +6 -4
  147. iXBRLViewerPlugin/viewer/src/less/core.less +2 -0
  148. iXBRLViewerPlugin/viewer/src/less/dialog.less +21 -14
  149. iXBRLViewerPlugin/viewer/src/less/form-controls.less +33 -11
  150. iXBRLViewerPlugin/viewer/src/less/inspector.less +1045 -726
  151. iXBRLViewerPlugin/viewer/src/less/loader.less +2 -2
  152. iXBRLViewerPlugin/viewer/src/less/menu.less +33 -15
  153. iXBRLViewerPlugin/viewer/src/less/summary.less +16 -6
  154. iXBRLViewerPlugin/viewer/src/less/tabs.less +9 -9
  155. iXBRLViewerPlugin/viewer/src/less/text-block-viewer.less +2 -0
  156. iXBRLViewerPlugin/viewer/src/less/text-mixins.less +2 -1
  157. iXBRLViewerPlugin/viewer/src/less/validation-report.less +2 -3
  158. iXBRLViewerPlugin/viewer/src/less/viewer.less +105 -74
  159. iXBRLViewerPlugin/viewer/webpack.common.js +19 -9
  160. iXBRLViewerPlugin/xhtmlserialize.py +59 -45
  161. {ixbrl_viewer-1.4.1.dist-info → ixbrl_viewer-1.4.86.dist-info}/METADATA +181 -50
  162. ixbrl_viewer-1.4.86.dist-info/RECORD +217 -0
  163. {ixbrl_viewer-1.4.1.dist-info → ixbrl_viewer-1.4.86.dist-info}/WHEEL +1 -1
  164. ixbrl_viewer-1.4.1.dist-info/LICENSE → ixbrl_viewer-1.4.86.dist-info/licenses/LICENSE.md +8 -14
  165. {ixbrl_viewer-1.4.1.dist-info → ixbrl_viewer-1.4.86.dist-info}/top_level.txt +0 -1
  166. iXBRLViewerPlugin/viewer/src/js/calculations.js +0 -111
  167. iXBRLViewerPlugin/viewer/src/js/interact.min.js +0 -6
  168. ixbrl_viewer-1.4.1.dist-info/RECORD +0 -155
  169. tests/__init__.py +0 -0
  170. tests/puppeteer/framework/core_elements.js +0 -117
  171. tests/puppeteer/framework/page_objects/doc_frame.js +0 -105
  172. tests/puppeteer/framework/page_objects/fact_details_panel.js +0 -80
  173. tests/puppeteer/framework/page_objects/search_panel.js +0 -76
  174. tests/puppeteer/framework/page_objects/toolbar.js +0 -18
  175. tests/puppeteer/framework/utils.js +0 -3
  176. tests/puppeteer/framework/viewer_page.js +0 -103
  177. tests/puppeteer/puppeteer_test_run_via_intellij.jpg +0 -0
  178. tests/puppeteer/test_filings/filing_documents_smoke_test.zip +0 -0
  179. tests/puppeteer/test_filings/highlights.zip +0 -0
  180. tests/puppeteer/tests/fact_properties.test.js +0 -78
  181. tests/puppeteer/tests/highlight.test.js +0 -186
  182. tests/puppeteer/tests/search.test.js +0 -86
  183. tests/puppeteer/tools/generate.sh +0 -15
  184. tests/unit_tests/__init__.py +0 -0
  185. tests/unit_tests/iXBRLViewerPlugin/__init__.py +0 -0
  186. tests/unit_tests/iXBRLViewerPlugin/mock_arelle.py +0 -39
  187. tests/unit_tests/iXBRLViewerPlugin/test_iXBRLViewer.py +0 -641
  188. tests/unit_tests/iXBRLViewerPlugin/test_xhtmlserialize.py +0 -310
  189. {ixbrl_viewer-1.4.1.dist-info → ixbrl_viewer-1.4.86.dist-info}/entry_points.txt +0 -0
  190. {ixbrl_viewer-1.4.1.dist-info → ixbrl_viewer-1.4.86.dist-info/licenses}/NOTICE +0 -0
@@ -1,39 +0,0 @@
1
- from unittest.mock import Mock, patch
2
- import sys
3
-
4
- def qname_effect(prefix, namespaceURI, localName):
5
- return Mock(
6
- prefix=prefix,
7
- namespaceURI=namespaceURI,
8
- localName=localName
9
- )
10
-
11
- def mrs_effect(dts, reltype):
12
- return Mock(
13
- fromModelObject = lambda source: []
14
- )
15
-
16
- def inferredDecimals_effect(fact):
17
- return float("INF")
18
-
19
- def mock_arelle():
20
-
21
- # Don't replace an existing mocked arelle, as otherwise @patch calls will
22
- # patch the wrong Mock instance.
23
- if 'arelle' not in sys.modules:
24
- sys.modules['arelle'] = Mock()
25
- sys.modules['arelle.FileSource'] = Mock()
26
- sys.modules['arelle.LocalViewer'] = Mock()
27
- sys.modules['arelle.ModelDocument'] = Mock()
28
- sys.modules['arelle.ModelRelationshipSet'] = Mock(ModelRelationshipSet = mrs_effect)
29
- sys.modules['arelle.ModelValue'] = Mock(
30
- QName=qname_effect
31
- )
32
- sys.modules['arelle.PythonUtil'] = Mock()
33
- sys.modules['arelle.UrlUtil'] = Mock(
34
- isHttpUrl=lambda path: path.startswith('http')
35
- )
36
- sys.modules['arelle.ValidateXbrlCalcs'] = Mock(
37
- inferredDecimals=inferredDecimals_effect
38
- )
39
- sys.modules['arelle.webserver.bottle'] = Mock()
@@ -1,641 +0,0 @@
1
- import os
2
- import unittest
3
- import json
4
- import logging
5
- from io import StringIO
6
- from collections import defaultdict
7
-
8
- from arelle import XbrlConst
9
- from arelle.ModelDocument import Type
10
- from arelle.ModelValue import qname
11
- from lxml import etree
12
- from unittest.mock import Mock, patch
13
- from .mock_arelle import mock_arelle
14
-
15
- mock_arelle()
16
-
17
- from iXBRLViewerPlugin.iXBRLViewer import NamespaceMap, IXBRLViewerBuilder
18
-
19
- class TestNamespaceMap(unittest.TestCase):
20
-
21
- def test_getPrefix_with_none(self):
22
- """
23
- Tests NamespaceMap.getPrefix with None. Should return generated prefix.
24
- """
25
- ns_map = NamespaceMap()
26
- result = ns_map.getPrefix(None)
27
- self.assertEqual(result, 'ns0')
28
-
29
- def test_getPrefix_with_none_with_prefix(self):
30
- """
31
- Tests NamespaceMap.getPrefix with None. Should return specified prefix.
32
- """
33
- ns_map = NamespaceMap()
34
- prefix = 'prefix'
35
- result = ns_map.getPrefix(None, prefix)
36
- self.assertEqual(result, prefix)
37
-
38
- def test_getPrefix_with_namespace(self):
39
- """
40
- Tests NamespaceMap.getPrefix for 'namespace'. Should return generated
41
- prefix.
42
- """
43
- ns_map = NamespaceMap()
44
- namespace = 'namespace'
45
- result = ns_map.getPrefix(namespace)
46
- self.assertEqual(result, 'ns0')
47
-
48
- def test_getPrefix_with_namespace_with_prefix(self):
49
- """
50
- Tests NamespaceMap.getPrefix with 'namespace' with preferred prefix.
51
- Should return specified prefix.
52
- """
53
- ns_map = NamespaceMap()
54
- namespace = 'namespace'
55
- prefix = 'prefix'
56
- result = ns_map.getPrefix(namespace, prefix)
57
- self.assertEqual(result, prefix)
58
-
59
- def test_getPrefix_subsequent_call_with_namespace(self):
60
- """
61
- Tests NamespaceMap.getPrefix twice with 'namespace'. Should return same
62
- generated prefix.
63
- """
64
- ns_map = NamespaceMap()
65
- namespace = 'namespace'
66
- result = ns_map.getPrefix(namespace)
67
- self.assertEqual(result, 'ns0')
68
- result_2 = ns_map.getPrefix(namespace)
69
- self.assertEqual(result_2, 'ns0')
70
-
71
- def test_getPrefix_subsequent_call_with_namespace_and_prefix(self):
72
- """
73
- Tests NamespaceMap.getPrefix twice with 'namespace' and specified
74
- prefix. Should return same specified prefix.
75
- """
76
- ns_map = NamespaceMap()
77
- namespace = 'namespace'
78
- prefix = 'prefix'
79
- result = ns_map.getPrefix(namespace, prefix)
80
- self.assertEqual(result, prefix)
81
- result_2 = ns_map.getPrefix(namespace, prefix)
82
- self.assertEqual(result_2, prefix)
83
-
84
- def test_getPrefix_subsequent_call_with_two_namespaces_and_prefix(self):
85
- """
86
- Tests NamespaceMap.getPrefix with two namespaces. Should return
87
- sequential generated prefixes.
88
- """
89
- ns_map = NamespaceMap()
90
- namespace_1 = 'namespace_1'
91
- namespace_2 = 'namespace_2'
92
- result = ns_map.getPrefix(namespace_1)
93
- self.assertEqual(result, 'ns0')
94
- result_2 = ns_map.getPrefix(namespace_2)
95
- self.assertEqual(result_2, 'ns1')
96
-
97
-
98
- class TestIXBRLViewer(unittest.TestCase):
99
-
100
- def setUp(self):
101
- self.usd_qname = Mock(
102
- localName='USD',
103
- prefix='iso4217',
104
- namespaceURI='http://www.xbrl.org/2003/iso4217'
105
- )
106
-
107
- self.usd_unit = Mock(
108
- measures = ([self.usd_qname],[])
109
- )
110
-
111
- self.null_units = Mock(
112
- measures = ([],[])
113
- )
114
-
115
- self.cash_concept = Mock(
116
- qname=Mock(
117
- localName='Cash',
118
- prefix='us-gaap',
119
- namespaceURI='http://viewer.com'
120
- ),
121
- isTypedDimension=False,
122
- )
123
-
124
- to_concept = Mock(
125
- qname=Mock(
126
- localName='to_concept',
127
- prefix='us-gaap',
128
- namespaceURI='http://viewer.com'
129
- ),
130
- isTypedDimension=False,
131
- )
132
- from_concept = Mock(
133
- qname=Mock(
134
- localName='from_concept',
135
- prefix='us-gaap',
136
- namespaceURI='http://viewer.com'
137
- ),
138
- isTypedDimension=False,
139
- )
140
-
141
- dimension_concept = Mock(
142
- qname=Mock(
143
- localName='dimension',
144
- prefix='us-gaap',
145
- namespaceURI='http://viewer.com'
146
- ),
147
- isTypedDimension=False,
148
- )
149
-
150
- typed_dimension_domain_concept = Mock(
151
- qname=Mock(
152
- localName='typed_dimension_domain',
153
- prefix='us-gaap',
154
- namespaceURI='http://viewer.com'
155
- ),
156
- isTypedDimension=False,
157
- )
158
-
159
- typed_dimension_concept = Mock(
160
- qname=Mock(
161
- localName='typed_dimension',
162
- prefix='us-gaap',
163
- namespaceURI='http://viewer.com'
164
- ),
165
- isTypedDimension=True,
166
- typedDomainElement=typed_dimension_domain_concept,
167
- )
168
-
169
- member_concept = Mock(
170
- qname=Mock(
171
- localName='member',
172
- prefix='us-gaap',
173
- namespaceURI='http://viewer.com'
174
- ),
175
- isTypedDimension=False,
176
- )
177
-
178
- rel = Mock(
179
- fromModelObject=from_concept,
180
- toModelObject=to_concept,
181
- weight=1
182
- )
183
-
184
- dimension = Mock(
185
- dimensionQname=dimension_concept.qname,
186
- memberQname=member_concept.qname,
187
- dimension=dimension_concept,
188
- member=member_concept
189
- )
190
-
191
- typed_dimension = Mock(
192
- dimensionQname=typed_dimension_concept.qname,
193
- memberQname=None,
194
- dimension=typed_dimension_concept,
195
- typedMember=Mock(text='typedDimension')
196
- )
197
-
198
- dimension_missing_member = Mock(
199
- dimensionQname=dimension_concept.qname,
200
- memberQname=None,
201
- dimension=dimension_concept,
202
- typedMember=None
203
- )
204
-
205
- def isoformat_effect():
206
- return '01-01-19T00:00:00'
207
-
208
- context_1 = Mock(
209
- entityIdentifier=('scheme', 'ident'),
210
- qnameDims={'d': dimension},
211
- isInstantPeriod=False,
212
- isStartEndPeriod=True,
213
- startDatetime=Mock(isoformat=isoformat_effect),
214
- endDatetime=Mock(isoformat=isoformat_effect)
215
- )
216
-
217
- context_2 = Mock(
218
- entityIdentifier=('scheme', 'ident'),
219
- qnameDims={},
220
- isInstantPeriod=None,
221
- isStartEndPeriod=None
222
- )
223
-
224
- context_with_typed_dimension = Mock(
225
- entityIdentifier=('scheme', 'ident'),
226
- qnameDims={'d': typed_dimension},
227
- isInstantPeriod=False,
228
- isStartEndPeriod=True,
229
- startDatetime=Mock(isoformat=isoformat_effect),
230
- endDatetime=Mock(isoformat=isoformat_effect)
231
- )
232
-
233
- context_with_missing_member_on_dimension = Mock(
234
- entityIdentifier=('scheme', 'ident'),
235
- qnameDims={'d': dimension_missing_member},
236
- isInstantPeriod=False,
237
- isStartEndPeriod=True,
238
- startDatetime=Mock(isoformat=isoformat_effect),
239
- endDatetime=Mock(isoformat=isoformat_effect)
240
- )
241
-
242
- fact_1 = Mock(
243
- id='fact_id1',
244
- qname=self.cash_concept.qname,
245
- value=100,
246
- isNumeric=False,
247
- context=context_1,
248
- concept=self.cash_concept,
249
- format='format'
250
- )
251
-
252
- fact_2 = Mock(
253
- id='fact_id2',
254
- qname=self.cash_concept.qname,
255
- concept=self.cash_concept,
256
- context=context_2,
257
- isNumeric=True,
258
- unit=self.usd_unit,
259
- value=None,
260
- decimals=None,
261
- precision=None,
262
- format=None
263
- )
264
-
265
- fact_3 = Mock(
266
- id='fact_id3',
267
- qname=self.cash_concept.qname,
268
- concept=self.cash_concept,
269
- context=context_2,
270
- isNumeric=True,
271
- unit=self.null_units,
272
- value=None,
273
- decimals=None,
274
- precision=None,
275
- format=None
276
- )
277
-
278
- fact_with_typed_dimension = Mock(
279
- id='fact_typed_dimension',
280
- qname=self.cash_concept.qname,
281
- value=10,
282
- isNumeric=False,
283
- context=context_with_typed_dimension,
284
- concept=self.cash_concept,
285
- format='format'
286
- )
287
-
288
- fact_with_missing_member_on_dimension = Mock(
289
- id='fact_dimension_missing_member',
290
- qname=self.cash_concept.qname,
291
- value=1000,
292
- isNumeric=False,
293
- context=context_with_missing_member_on_dimension,
294
- concept=self.cash_concept,
295
- format='format'
296
- )
297
-
298
- def fromModelObjects_effect(concept):
299
- return []
300
-
301
- def relationshipSet_effect(self, *args):
302
- return Mock(
303
- fromModelObject=fromModelObjects_effect,
304
- modelRelationships=[rel]
305
- )
306
-
307
- def info_effect(info, msg):
308
- # This is a no op for logging
309
- pass
310
-
311
- baseSets = defaultdict(list)
312
- baseSets[('http://www.xbrl.org/2003/arcrole/summation-item', 'ELR', 'linkqname', 'arcqname')] = []
313
- baseSets[("http://xbrl.org/int/dim/arcrole/dimension-default", 'ELR', 'linkqname', 'arcqname')] = []
314
- baseSets[("http://www.xbrl.org/2003/arcrole/parent-child", 'ELR', 'linkqname', 'arcqname')] = []
315
-
316
- roleTypes = defaultdict(list)
317
- roleTypes['ELR'] = [Mock(definition = "ELR Label")]
318
-
319
- root = etree.Element('root')
320
- etree.SubElement(root, '{http://www.w3.org/1999/xhtml}body')
321
-
322
- self.modelDocument = Mock(
323
- xmlDocument=etree.ElementTree(root),
324
- filepath='c.xml'
325
- )
326
-
327
- self.modelDocumentInlineSet = Mock(
328
- referencesDocument={
329
- Mock(
330
- xmlDocument=etree.ElementTree(root),
331
- filepath='a.xml',
332
- objectIndex=0
333
- ): [],
334
- Mock(
335
- xmlDocument=etree.ElementTree(root),
336
- filepath='b.xml',
337
- objectIndex=1
338
- ): []
339
- },
340
- filepath=self.modelDocument.filepath,
341
- type=Type.INLINEXBRLDOCUMENTSET
342
- )
343
-
344
- error1 = logging.LogRecord("arelle", logging.ERROR, "", 0, "Error message", {}, None)
345
- error1.messageCode = "code1"
346
- self.modelManager = Mock(
347
- cntlr = Mock(
348
- logHandler = Mock (
349
- logRecordBuffer = [
350
- error1
351
- ]
352
- )
353
- )
354
- )
355
-
356
- def urlDocEntry(path, docType, linkQName=None):
357
- return path, Mock(
358
- type=docType,
359
- basename=os.path.basename(path),
360
- xmlRootElement=Mock(
361
- iterchildren=Mock(
362
- return_value=[
363
- Mock(qname=linkQName)] if linkQName else []
364
- )
365
- )
366
- )
367
-
368
- self.modelXbrl_1 = Mock(
369
- relationshipSet=relationshipSet_effect,
370
- relationshipSets={},
371
- baseSets=baseSets,
372
- roleTypes=roleTypes,
373
- facts=[fact_1, fact_with_typed_dimension, fact_with_missing_member_on_dimension],
374
- info=info_effect,
375
- modelDocument=self.modelDocument,
376
- modelManager=self.modelManager,
377
- urlDocs=dict((
378
- urlDocEntry('/filesystem/local-inline.htm', Type.INLINEXBRL),
379
- urlDocEntry('https://example.com/remote-inline.htm', Type.INLINEXBRL),
380
- urlDocEntry('/filesystem/local-docset', Type.INLINEXBRLDOCUMENTSET),
381
- urlDocEntry('https://example.com/remote-docset', Type.INLINEXBRLDOCUMENTSET),
382
- urlDocEntry('/filesystem/local-schema.xsd', Type.SCHEMA),
383
- urlDocEntry('https://example.com/remote-schema.xsd', Type.SCHEMA),
384
- urlDocEntry('/filesystem/local-label-linkbase.xml', Type.LINKBASE, XbrlConst.qnLinkLabelLink),
385
- urlDocEntry('https://example.com/remote-label-linkbase.xml', Type.LINKBASE, XbrlConst.qnLinkLabelLink),
386
- urlDocEntry('/filesystem/local-pres-linkbase.xml', Type.LINKBASE, XbrlConst.qnLinkPresentationLink),
387
- urlDocEntry('https://example.com/remote-pres-linkbase.xml', Type.LINKBASE, XbrlConst.qnLinkPresentationLink),
388
- urlDocEntry('/filesystem/local-calc-linkbase.xml', Type.LINKBASE, XbrlConst.qnLinkCalculationLink),
389
- urlDocEntry('https://example.com/remote-calc-linkbase.xml', Type.LINKBASE, XbrlConst.qnLinkCalculationLink),
390
- urlDocEntry('/filesystem/local-def-linkbase.xml', Type.LINKBASE, XbrlConst.qnLinkDefinitionLink),
391
- urlDocEntry('https://example.com/remote-def-linkbase.xml', Type.LINKBASE, XbrlConst.qnLinkDefinitionLink),
392
- urlDocEntry('/filesystem/local-ref-linkbase.xml', Type.LINKBASE, XbrlConst.qnLinkReferenceLink),
393
- urlDocEntry('https://example.com/remote-ref-linkbase.xml', Type.LINKBASE, XbrlConst.qnLinkReferenceLink),
394
- urlDocEntry('/filesystem/local-unrecognized-linkbase.xml', Type.LINKBASE, qname("{http://www.example.org/linkbase}link:unrecognizedLink")),
395
- urlDocEntry('https://example.com/remote-unrecognized-linkbase.xml', Type.LINKBASE, qname("{http://www.example.org/linkbase}link:unrecognizedLink")),
396
- ))
397
- )
398
- self.modelXbrl_2 = Mock(
399
- relationshipSet=relationshipSet_effect,
400
- relationshipSets={},
401
- baseSets=baseSets,
402
- roleTypes=roleTypes,
403
- facts=[fact_2, fact_3],
404
- info=info_effect,
405
- modelDocument=self.modelDocument,
406
- modelManager=self.modelManager,
407
- urlDocs={}
408
- )
409
- self.modelXbrlDocSet = Mock(
410
- relationshipSet=relationshipSet_effect,
411
- relationshipSets={},
412
- baseSets=baseSets,
413
- roleTypes=roleTypes,
414
- facts=[fact_1, fact_with_typed_dimension, fact_with_missing_member_on_dimension],
415
- info=info_effect,
416
- modelDocument=self.modelDocumentInlineSet,
417
- modelManager=self.modelManager,
418
- urlDocs={}
419
- )
420
-
421
- self.cash_concept.modelXbrl = self.modelXbrl_1
422
- to_concept.modelXbrl = self.modelXbrl_1
423
- from_concept.modelXbrl = self.modelXbrl_1
424
- dimension_concept.modelXbrl = self.modelXbrl_1
425
- typed_dimension_concept.modelXbrl = self.modelXbrl_1
426
- typed_dimension_domain_concept.modelXbrl = self.modelXbrl_1
427
- member_concept.modelXbrl = self.modelXbrl_1
428
- self.builder_1 = IXBRLViewerBuilder(self.modelXbrl_1)
429
- self.builder_2 = IXBRLViewerBuilder(self.modelXbrl_1)
430
- self.builder_3 = IXBRLViewerBuilder(self.modelXbrl_2)
431
- self.builder_doc_set = IXBRLViewerBuilder(self.modelXbrlDocSet)
432
-
433
- @patch('arelle.XbrlConst.conceptLabel', 'http://www.xbrl.org/2003/arcrole/concept-label')
434
- @patch('arelle.XbrlConst.conceptReference', 'http://www.xbrl.org/2003/arcrole/concept-reference')
435
- def test_addConcept_simple_case(self):
436
- self.builder_1.addConcept(self.cash_concept)
437
- self.assertTrue(self.builder_1.taxonomyData.get('concepts').get('us-gaap:Cash'))
438
-
439
- @patch('arelle.XbrlConst.parentChild', 'http://www.xbrl.org/2003/arcrole/parent-child')
440
- @patch('arelle.XbrlConst.summationItem', 'http://www.xbrl.org/2003/arcrole/summation-item')
441
- def test_getRelationships_simple_case(self):
442
- modelXbrl = Mock(baseSets=defaultdict(list), relationshipSets={})
443
- builder = IXBRLViewerBuilder(modelXbrl)
444
- result = builder.getRelationships()
445
- self.assertDictEqual(result, {})
446
-
447
- @patch('arelle.XbrlConst.parentChild', 'http://www.xbrl.org/2003/arcrole/parent-child')
448
- @patch('arelle.XbrlConst.summationItem', 'http://www.xbrl.org/2003/arcrole/summation-item')
449
- def test_getRelationships_returns_a_rel(self):
450
- result = self.builder_1.getRelationships()
451
- roleMap = self.builder_1.roleMap
452
- siPrefix = roleMap.getPrefix('http://www.xbrl.org/2003/arcrole/summation-item')
453
- self.assertTrue(result.get(siPrefix).get(roleMap.getPrefix('ELR')).get('us-gaap:from_concept'))
454
-
455
- def test_addELR_no_definition(self):
456
- """
457
- Adding an ELR with no definition should result in no entry in the roleDefs map
458
- """
459
- elr = "http://example.com/unknownELR"
460
- self.builder_1.addELR(elr)
461
- elrPrefix = self.builder_1.roleMap.getPrefix(elr)
462
- self.assertEqual(self.builder_1.taxonomyData.get('roleDefs').get(elrPrefix), None)
463
-
464
- def test_addELR_with_definition(self):
465
- """
466
- Adding an ELR with a definition should result in an "en" label with the definition as its value.
467
- """
468
- elr = "ELR"
469
- self.builder_1.addELR(elr)
470
- elrPrefix = self.builder_1.roleMap.getPrefix(elr)
471
- self.assertEqual(self.builder_1.taxonomyData.get('roleDefs').get(elrPrefix).get("en"), "ELR Label")
472
-
473
- @patch('arelle.XbrlConst.conceptLabel', 'http://www.xbrl.org/2003/arcrole/concept-label')
474
- @patch('arelle.XbrlConst.conceptReference', 'http://www.xbrl.org/2003/arcrole/concept-reference')
475
- @patch('arelle.XbrlConst.dimensionDefault', 'http://xbrl.org/int/dim/arcrole/dimension-default')
476
- @patch('arelle.XbrlConst.parentChild', 'http://www.xbrl.org/2003/arcrole/parent-child')
477
- @patch('arelle.XbrlConst.summationItem', 'http://www.xbrl.org/2003/arcrole/summation-item')
478
- @patch('arelle.XbrlConst.standardLabel', 'http://www.xbrl.org/2003/role/label')
479
- @patch('arelle.XbrlConst.documentationLabel', 'http://www.xbrl.org/2003/role/documentation')
480
- def test_createViewerWithValidation(self):
481
- js_uri = 'ixbrlviewer.js'
482
- result = self.builder_1.createViewer(js_uri)
483
- self.assertEqual(len(result.files),1)
484
- body = result.files[0].xmlDocument.getroot()[0]
485
- self.assertEqual(body[0].text, 'BEGIN IXBRL VIEWER EXTENSIONS')
486
- self.assertEqual(body[1].attrib.get('src'), js_uri)
487
- self.assertEqual(body[1].attrib.get('type'), 'text/javascript')
488
- self.assertEqual(body[2].attrib.get('type'), 'application/x.ixbrl-viewer+json')
489
- self.assertEqual(body[3].text, 'END IXBRL VIEWER EXTENSIONS')
490
-
491
- jsdata = json.loads(body[2].text)
492
- errors = jsdata["validation"]
493
- self.assertEqual(errors, [{"sev": "ERROR", "msg": "Error message", "code": "code1" }])
494
- self.assertEqual(set(jsdata["facts"]), {"fact_id1", "fact_typed_dimension", "fact_dimension_missing_member"})
495
-
496
- @patch('arelle.XbrlConst.conceptLabel', 'http://www.xbrl.org/2003/arcrole/concept-label')
497
- @patch('arelle.XbrlConst.conceptReference', 'http://www.xbrl.org/2003/arcrole/concept-reference')
498
- @patch('arelle.XbrlConst.parentChild', 'http://www.xbrl.org/2003/arcrole/parent-child')
499
- @patch('arelle.XbrlConst.summationItem', 'http://www.xbrl.org/2003/arcrole/summation-item')
500
- @patch('arelle.XbrlConst.standardLabel', 'http://www.xbrl.org/2003/role/label')
501
- @patch('arelle.XbrlConst.documentationLabel', 'http://www.xbrl.org/2003/role/documentation')
502
- @patch('arelle.XbrlConst.dimensionDefault', 'http://xbrl.org/int/dim/arcrole/dimension-default')
503
- def test_createViewer(self):
504
- js_uri = 'ixbrlviewer.js'
505
- result = self.builder_2.createViewer(js_uri, showValidations = False)
506
- self.assertEqual(len(result.files),1)
507
- body = result.files[0].xmlDocument.getroot()[0]
508
- self.assertEqual(body[0].text, 'BEGIN IXBRL VIEWER EXTENSIONS')
509
- self.assertEqual(body[1].attrib.get('src'), js_uri)
510
- self.assertEqual(body[1].attrib.get('type'), 'text/javascript')
511
- self.assertEqual(body[2].attrib.get('type'), 'application/x.ixbrl-viewer+json')
512
- self.assertEqual(body[3].text, 'END IXBRL VIEWER EXTENSIONS')
513
-
514
- jsdata = json.loads(body[2].text)
515
- self.assertNotIn("validation", jsdata)
516
- self.assertEqual(set(jsdata["facts"]), {"fact_id1", "fact_typed_dimension", "fact_dimension_missing_member"})
517
- self.assertEqual(jsdata["concepts"], {
518
- 'us-gaap:Cash': {'e': True, 't': True, 'labels': {}},
519
- 'us-gaap:from_concept': {'e': True, 't': True, 'labels': {}},
520
- 'us-gaap:to_concept': {'e': True, 't': True, 'labels': {}},
521
- 'us-gaap:dimension': {'d': 'e', 'e': True, 't': True, 'labels': {}},
522
- 'us-gaap:member': {'e': True, 't': True, 'labels': {}},
523
- 'us-gaap:typed_dimension_domain': {'e': True, 't': True, 'labels': {}},
524
- 'us-gaap:typed_dimension': {'d': 't', 'e': True, 't': True, 'labels': {}, 'td': 'us-gaap:typed_dimension_domain'},
525
- })
526
- self.assertEqual(jsdata["localDocs"], {
527
- 'local-inline.htm': ['inline'],
528
- 'local-docset': ['inline'],
529
- 'local-schema.xsd': ['schema'],
530
- 'local-pres-linkbase.xml': ['presLinkbase'],
531
- 'local-calc-linkbase.xml': ['calcLinkbase'],
532
- 'local-def-linkbase.xml': ['defLinkbase'],
533
- 'local-label-linkbase.xml': ['labelLinkbase'],
534
- 'local-ref-linkbase.xml': ['refLinkbase'],
535
- 'local-unrecognized-linkbase.xml': ['unrecognizedLinkbase'],
536
- })
537
-
538
- @patch('arelle.XbrlConst.conceptLabel', 'http://www.xbrl.org/2003/arcrole/concept-label')
539
- @patch('arelle.XbrlConst.conceptReference', 'http://www.xbrl.org/2003/arcrole/concept-reference')
540
- @patch('arelle.XbrlConst.parentChild', 'http://www.xbrl.org/2003/arcrole/parent-child')
541
- @patch('arelle.XbrlConst.summationItem', 'http://www.xbrl.org/2003/arcrole/summation-item')
542
- @patch('arelle.XbrlConst.standardLabel', 'http://www.xbrl.org/2003/role/label')
543
- @patch('arelle.XbrlConst.documentationLabel', 'http://www.xbrl.org/2003/role/documentation')
544
- @patch('arelle.XbrlConst.dimensionDefault', 'http://xbrl.org/int/dim/arcrole/dimension-default')
545
- def test_createViewer_docset(self):
546
- js_uri = 'ixbrlviewer.js'
547
- result = self.builder_doc_set.createViewer(js_uri, showValidations=False)
548
- self.assertEqual(len(result.files), 2)
549
- body = result.files[0].xmlDocument.getroot()[0]
550
- self.assertEqual(body[0].text, 'BEGIN IXBRL VIEWER EXTENSIONS')
551
- self.assertEqual(body[1].attrib.get('src'), js_uri)
552
- self.assertEqual(body[1].attrib.get('type'), 'text/javascript')
553
- self.assertEqual(body[2].attrib.get('type'), 'application/x.ixbrl-viewer+json')
554
- self.assertEqual(body[3].text, 'END IXBRL VIEWER EXTENSIONS')
555
-
556
- jsdata = json.loads(body[2].text)
557
- self.assertNotIn("validation", jsdata)
558
- self.assertEqual(set(jsdata["facts"]), {"fact_id1", "fact_typed_dimension", "fact_dimension_missing_member"})
559
-
560
- @patch('arelle.XbrlConst.conceptLabel', 'http://www.xbrl.org/2003/arcrole/concept-label')
561
- @patch('arelle.XbrlConst.conceptReference', 'http://www.xbrl.org/2003/arcrole/concept-reference')
562
- @patch('arelle.XbrlConst.parentChild', 'http://www.xbrl.org/2003/arcrole/parent-child')
563
- @patch('arelle.XbrlConst.dimensionDefault', 'http://xbrl.org/int/dim/arcrole/dimension-default')
564
- @patch('arelle.XbrlConst.summationItem', 'http://www.xbrl.org/2003/arcrole/summation-item')
565
- @patch('arelle.XbrlConst.standardLabel', 'http://www.xbrl.org/2003/role/label')
566
- @patch('arelle.XbrlConst.documentationLabel', 'http://www.xbrl.org/2003/role/documentation')
567
- def test_createViewer_bad_path(self):
568
- js_uri = 'ixbrlviewer.js'
569
- result = self.builder_3.createViewer(js_uri)
570
- self.assertEqual(len(result.files),1)
571
- body = result.files[0].xmlDocument.getroot()[0]
572
- self.assertEqual(body[0].text, 'BEGIN IXBRL VIEWER EXTENSIONS')
573
- self.assertEqual(body[1].tag, '{http://www.w3.org/1999/xhtml}script')
574
- self.assertEqual(body[1].prefix, None)
575
- self.assertEqual(body[1].attrib.get('src'), js_uri)
576
- self.assertEqual(body[1].attrib.get('type'), 'text/javascript')
577
- self.assertEqual(body[2].tag, '{http://www.w3.org/1999/xhtml}script')
578
- self.assertEqual(body[2].prefix, None)
579
- self.assertEqual(body[2].attrib.get('type'), 'application/x.ixbrl-viewer+json')
580
- self.assertEqual(body[3].text, 'END IXBRL VIEWER EXTENSIONS')
581
-
582
- jsdata = json.loads(body[2].text)
583
- facts = jsdata["facts"]
584
- self.assertEqual(facts.keys(), {"fact_id2", "fact_id3"})
585
- self.assertEqual(facts["fact_id2"]["a"]["u"], "iso4217:USD")
586
- self.assertEqual(facts["fact_id3"]["a"]["u"], None)
587
-
588
- def test_enableFeature_valid(self):
589
- """
590
- Enable a defined feature
591
- """
592
- self.builder_1.enableFeature('review')
593
- self.assertEqual(self.builder_1.taxonomyData["features"], ['review'])
594
-
595
- def test_enableFeature_invalid(self):
596
- """
597
- Attempt to enable an undefined feature
598
- """
599
- with self.assertRaisesRegex(AssertionError, rf'^Given feature name `unknown` does not match any defined features'):
600
- self.builder_1.enableFeature('unknown')
601
-
602
- def test_xhtmlNamespaceHandling(self):
603
- # Check the prefix used for our inserted script tags
604
- tests = ('''
605
- <html xmlns="http://www.w3.org/1999/xhtml">
606
- <body>
607
- </body>
608
- </html>
609
- ''',
610
- '''
611
- <html xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns="http://www.w3.org/1999/xhtml">
612
- <body>
613
- </body>
614
- </html>
615
- ''',
616
- # In this case we won't fix the root element to be in the default NS,
617
- # but our <script> tags will be.
618
- '''
619
- <xhtml:html xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns="http://www.w3.org/1999/xhtml">
620
- <body>
621
- </body>
622
- </xhtml:html>
623
- '''
624
- )
625
-
626
- for xmls in tests:
627
- xml = etree.parse(StringIO(xmls))
628
-
629
- js_uri = 'https://example.com/script-url'
630
- result = self.builder_1.addViewerToXMLDocument(xml, js_uri)
631
-
632
- body = xml.getroot()[0]
633
- self.assertEqual(body[0].text, 'BEGIN IXBRL VIEWER EXTENSIONS')
634
- self.assertEqual(body[1].tag, '{http://www.w3.org/1999/xhtml}script')
635
- self.assertEqual(body[1].prefix, None)
636
- self.assertEqual(body[1].attrib.get('src'), js_uri)
637
- self.assertEqual(body[1].attrib.get('type'), 'text/javascript')
638
- self.assertEqual(body[2].tag, '{http://www.w3.org/1999/xhtml}script')
639
- self.assertEqual(body[2].prefix, None)
640
- self.assertEqual(body[2].attrib.get('type'), 'application/x.ixbrl-viewer+json')
641
- self.assertEqual(body[3].text, 'END IXBRL VIEWER EXTENSIONS')