ixbrl-viewer 1.4.21__py3-none-any.whl → 1.4.49__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 ixbrl-viewer might be problematic. Click here for more details.

Files changed (114) hide show
  1. iXBRLViewerPlugin/__init__.py +77 -49
  2. iXBRLViewerPlugin/_version.py +2 -2
  3. iXBRLViewerPlugin/constants.py +86 -1
  4. iXBRLViewerPlugin/featureConfig.py +4 -1
  5. iXBRLViewerPlugin/iXBRLViewer.py +202 -131
  6. iXBRLViewerPlugin/plugin.py +7 -0
  7. iXBRLViewerPlugin/viewer/dist/ixbrlviewer.js +1 -1
  8. iXBRLViewerPlugin/viewer/dist/ixbrlviewer.js.LICENSE.txt +9 -2
  9. iXBRLViewerPlugin/viewer/i18next-parser.config.js +1 -1
  10. iXBRLViewerPlugin/viewer/src/data/utr.json +1 -0
  11. iXBRLViewerPlugin/viewer/src/html/fact-details.html +69 -38
  12. iXBRLViewerPlugin/viewer/src/html/footer-logo.html +4 -0
  13. iXBRLViewerPlugin/viewer/src/html/footnote-details.html +1 -1
  14. iXBRLViewerPlugin/viewer/src/html/inspector.html +324 -211
  15. iXBRLViewerPlugin/viewer/src/i18n/cy/balancetypes.json +1 -0
  16. iXBRLViewerPlugin/viewer/src/i18n/cy/currencies.json +13 -0
  17. iXBRLViewerPlugin/viewer/src/i18n/cy/datatypes.json +9 -0
  18. iXBRLViewerPlugin/viewer/src/i18n/cy/labelroles.json +24 -0
  19. iXBRLViewerPlugin/viewer/src/i18n/cy/referenceparts.json +10 -0
  20. iXBRLViewerPlugin/viewer/src/i18n/cy/scale.json +16 -0
  21. iXBRLViewerPlugin/viewer/src/i18n/cy/tooltips.json +17 -0
  22. iXBRLViewerPlugin/viewer/src/i18n/cy/translation.json +179 -0
  23. iXBRLViewerPlugin/viewer/src/i18n/en/balancetypes.json +4 -0
  24. iXBRLViewerPlugin/viewer/src/i18n/en/datatypes.json +10 -0
  25. iXBRLViewerPlugin/viewer/src/i18n/en/labelroles.json +4 -0
  26. iXBRLViewerPlugin/viewer/src/i18n/en/scale.json +16 -0
  27. iXBRLViewerPlugin/viewer/src/i18n/en/tooltips.json +17 -0
  28. iXBRLViewerPlugin/viewer/src/i18n/en/translation.json +56 -24
  29. iXBRLViewerPlugin/viewer/src/i18n/es/balancetypes.json +4 -0
  30. iXBRLViewerPlugin/viewer/src/i18n/es/datatypes.json +10 -0
  31. iXBRLViewerPlugin/viewer/src/i18n/es/labelroles.json +24 -0
  32. iXBRLViewerPlugin/viewer/src/i18n/es/scale.json +16 -0
  33. iXBRLViewerPlugin/viewer/src/i18n/es/tooltips.json +17 -0
  34. iXBRLViewerPlugin/viewer/src/i18n/es/translation.json +70 -37
  35. iXBRLViewerPlugin/viewer/src/icons/dark-mode.svg +4 -0
  36. iXBRLViewerPlugin/viewer/src/img/arelle-dark.svg +179 -0
  37. iXBRLViewerPlugin/viewer/src/img/inline-viewer-dark.svg +59 -0
  38. iXBRLViewerPlugin/viewer/src/js/accordian.js +3 -2
  39. iXBRLViewerPlugin/viewer/src/js/aspect.js +18 -10
  40. iXBRLViewerPlugin/viewer/src/js/aspect.test.js +2 -2
  41. iXBRLViewerPlugin/viewer/src/js/balance.js +14 -0
  42. iXBRLViewerPlugin/viewer/src/js/calculation.js +45 -34
  43. iXBRLViewerPlugin/viewer/src/js/calculationInspector.js +4 -7
  44. iXBRLViewerPlugin/viewer/src/js/chart.js +23 -21
  45. iXBRLViewerPlugin/viewer/src/js/concept.js +27 -2
  46. iXBRLViewerPlugin/viewer/src/js/concept.test.js +23 -2
  47. iXBRLViewerPlugin/viewer/src/js/datatype.js +20 -0
  48. iXBRLViewerPlugin/viewer/src/js/datatype.test.js +62 -0
  49. iXBRLViewerPlugin/viewer/src/js/dialog.js +6 -4
  50. iXBRLViewerPlugin/viewer/src/js/fact.js +40 -7
  51. iXBRLViewerPlugin/viewer/src/js/fact.test.js +3 -0
  52. iXBRLViewerPlugin/viewer/src/js/index.js +11 -3
  53. iXBRLViewerPlugin/viewer/src/js/inspector.js +560 -160
  54. iXBRLViewerPlugin/viewer/src/js/inspector.test.js +1 -2
  55. iXBRLViewerPlugin/viewer/src/js/ixbrlviewer.js +129 -31
  56. iXBRLViewerPlugin/viewer/src/js/ixbrlviewer.test.js +133 -20
  57. iXBRLViewerPlugin/viewer/src/js/ixnode.js +1 -1
  58. iXBRLViewerPlugin/viewer/src/js/menu.js +25 -7
  59. iXBRLViewerPlugin/viewer/src/js/number-matcher.js +2 -2
  60. iXBRLViewerPlugin/viewer/src/js/outline.js +2 -4
  61. iXBRLViewerPlugin/viewer/src/js/period.js +0 -1
  62. iXBRLViewerPlugin/viewer/src/js/report.js +68 -13
  63. iXBRLViewerPlugin/viewer/src/js/report.test.js +77 -6
  64. iXBRLViewerPlugin/viewer/src/js/reportset.js +33 -3
  65. iXBRLViewerPlugin/viewer/src/js/reportset.test.js +32 -6
  66. iXBRLViewerPlugin/viewer/src/js/search.js +61 -35
  67. iXBRLViewerPlugin/viewer/src/js/search.test.js +8 -5
  68. iXBRLViewerPlugin/viewer/src/js/summary.js +22 -0
  69. iXBRLViewerPlugin/viewer/src/js/summary.test.js +50 -13
  70. iXBRLViewerPlugin/viewer/src/js/tableExport.js +3 -3
  71. iXBRLViewerPlugin/viewer/src/js/taxonomynamer.js +34 -0
  72. iXBRLViewerPlugin/viewer/src/js/taxonomynamer.test.js +32 -0
  73. iXBRLViewerPlugin/viewer/src/js/theme.js +49 -0
  74. iXBRLViewerPlugin/viewer/src/js/unit.js +73 -2
  75. iXBRLViewerPlugin/viewer/src/js/unit.test.js +14 -3
  76. iXBRLViewerPlugin/viewer/src/js/util.js +21 -18
  77. iXBRLViewerPlugin/viewer/src/js/util.test.js +1 -0
  78. iXBRLViewerPlugin/viewer/src/js/utr.js +27 -0
  79. iXBRLViewerPlugin/viewer/src/js/viewer.js +40 -29
  80. iXBRLViewerPlugin/viewer/src/js/viewerOptions.js +0 -2
  81. iXBRLViewerPlugin/viewer/src/less/accordian.less +8 -4
  82. iXBRLViewerPlugin/viewer/src/less/block-list.less +12 -6
  83. iXBRLViewerPlugin/viewer/src/less/calculation-inspector.less +2 -2
  84. iXBRLViewerPlugin/viewer/src/less/chart.less +8 -5
  85. iXBRLViewerPlugin/viewer/src/less/colours-dark-mode.less +40 -0
  86. iXBRLViewerPlugin/viewer/src/less/colours.less +28 -21
  87. iXBRLViewerPlugin/viewer/src/less/common.less +1 -1
  88. iXBRLViewerPlugin/viewer/src/less/components.less +3 -3
  89. iXBRLViewerPlugin/viewer/src/less/core.less +2 -0
  90. iXBRLViewerPlugin/viewer/src/less/dialog.less +13 -10
  91. iXBRLViewerPlugin/viewer/src/less/form-controls.less +33 -11
  92. iXBRLViewerPlugin/viewer/src/less/inspector.less +556 -300
  93. iXBRLViewerPlugin/viewer/src/less/loader.less +2 -2
  94. iXBRLViewerPlugin/viewer/src/less/menu.less +33 -15
  95. iXBRLViewerPlugin/viewer/src/less/summary.less +16 -6
  96. iXBRLViewerPlugin/viewer/src/less/tabs.less +5 -5
  97. iXBRLViewerPlugin/viewer/src/less/text-mixins.less +2 -1
  98. iXBRLViewerPlugin/viewer/src/less/validation-report.less +1 -1
  99. iXBRLViewerPlugin/viewer/src/less/viewer.less +30 -18
  100. iXBRLViewerPlugin/viewer/webpack.common.js +19 -9
  101. {ixbrl_viewer-1.4.21.dist-info → ixbrl_viewer-1.4.49.dist-info}/METADATA +41 -14
  102. ixbrl_viewer-1.4.49.dist-info/RECORD +197 -0
  103. {ixbrl_viewer-1.4.21.dist-info → ixbrl_viewer-1.4.49.dist-info}/WHEEL +1 -1
  104. tests/puppeteer/framework/page_objects/doc_frame.js +3 -3
  105. tests/puppeteer/framework/page_objects/fact_details_panel.js +28 -0
  106. tests/puppeteer/puppeteer_test_run_via_intellij.jpg +0 -0
  107. tests/puppeteer/tests/fact_properties.test.js +10 -4
  108. tests/unit_tests/iXBRLViewerPlugin/test_iXBRLViewer.py +117 -51
  109. iXBRLViewerPlugin/viewer/src/js/interact.min.js +0 -6
  110. ixbrl_viewer-1.4.21.dist-info/RECORD +0 -166
  111. {ixbrl_viewer-1.4.21.dist-info → ixbrl_viewer-1.4.49.dist-info}/LICENSE +0 -0
  112. {ixbrl_viewer-1.4.21.dist-info → ixbrl_viewer-1.4.49.dist-info}/NOTICE +0 -0
  113. {ixbrl_viewer-1.4.21.dist-info → ixbrl_viewer-1.4.49.dist-info}/entry_points.txt +0 -0
  114. {ixbrl_viewer-1.4.21.dist-info → ixbrl_viewer-1.4.49.dist-info}/top_level.txt +0 -0
@@ -17,16 +17,17 @@ function testFact(conceptName, dimensions) {
17
17
  }
18
18
  }
19
19
 
20
- function testReportSet(concepts, facts, documents) {
20
+ function testReportSet(concepts, facts, documents, softwareCredits) {
21
21
  const report = {
22
22
  getConcept: conceptName => concepts[conceptName],
23
- localDocuments: () => documents
23
+ localDocuments: () => documents,
24
24
  }
25
25
  for (const f of facts) {
26
26
  f.report = report;
27
27
  }
28
28
  return {
29
29
  facts: () => facts,
30
+ getSoftwareCredits: () => softwareCredits,
30
31
  reports: [ report ]
31
32
  }
32
33
  }
@@ -34,7 +35,7 @@ function testReportSet(concepts, facts, documents) {
34
35
  describe("Facts summary", () => {
35
36
 
36
37
  test("no facts", () => {
37
- const reportSet = testReportSet({}, [], {});
38
+ const reportSet = testReportSet({}, [], {}, []);
38
39
  const summary = new DocumentSummary(reportSet);
39
40
 
40
41
  expect(summary.totalFacts()).toBe(0);
@@ -43,9 +44,9 @@ describe("Facts summary", () => {
43
44
  test("multiple facts", () => {
44
45
  const facts = [];
45
46
  for (let i = 0; i < 10; i++) {
46
- facts.push(testFact("eg:Concept1", {}));
47
+ facts.push(testFact("eg:Concept1", {}, []));
47
48
  }
48
- const reportSet = testReportSet({}, facts, {});
49
+ const reportSet = testReportSet({}, facts, {}, []);
49
50
  const summary = new DocumentSummary(reportSet);
50
51
 
51
52
  expect(summary.totalFacts()).toBe(10);
@@ -55,7 +56,7 @@ describe("Facts summary", () => {
55
56
  const conceptName = "eg:Concept1";
56
57
  const fact1 = testFact(conceptName, {});
57
58
  const fact2 = testFact(conceptName, {});
58
- const reportSet = testReportSet({}, [fact1, fact2], {});
59
+ const reportSet = testReportSet({}, [fact1, fact2], {}, []);
59
60
  const summary = new DocumentSummary(reportSet);
60
61
 
61
62
  expect(summary.totalFacts()).toBe(2);
@@ -66,7 +67,7 @@ describe("Facts summary", () => {
66
67
  describe("Tags summary", () => {
67
68
 
68
69
  test("no tags", () => {
69
- const reportSet = testReportSet({}, [], {})
70
+ const reportSet = testReportSet({}, [], {}, [])
70
71
  const summary = new DocumentSummary(reportSet);
71
72
 
72
73
  expect(summary.tagCounts()).toEqual(new Map());
@@ -76,7 +77,7 @@ describe("Tags summary", () => {
76
77
  const fact1 = testFact("eg:Concept1", {});
77
78
  const fact2 = testFact("eg:Concept2", {});
78
79
  const fact3 = testFact("xz:Concept1", {});
79
- const reportSet = testReportSet({}, [fact1, fact2, fact3], {})
80
+ const reportSet = testReportSet({}, [fact1, fact2, fact3], {}, [])
80
81
  const summary = new DocumentSummary(reportSet);
81
82
 
82
83
  expect(Object.fromEntries(summary.tagCounts())).toEqual({
@@ -105,7 +106,7 @@ describe("Tags summary", () => {
105
106
  const concepts = {
106
107
  [dimension]: testConcept(),
107
108
  }
108
- const reportSet = testReportSet(concepts, [fact], {})
109
+ const reportSet = testReportSet(concepts, [fact], {}, [])
109
110
  const summary = new DocumentSummary(reportSet);
110
111
 
111
112
  expect(Object.fromEntries(summary.tagCounts())).toEqual({
@@ -135,7 +136,7 @@ describe("Tags summary", () => {
135
136
  [dimension2]: testConcept(),
136
137
  [dimension3]: testConcept(),
137
138
  }
138
- const reportSet = testReportSet(concepts, [fact], {})
139
+ const reportSet = testReportSet(concepts, [fact], {}, [])
139
140
  const summary = new DocumentSummary(reportSet);
140
141
 
141
142
  expect(Object.fromEntries(summary.tagCounts())).toEqual({
@@ -176,7 +177,7 @@ describe("Tags summary", () => {
176
177
  const concepts = {
177
178
  [dimension]: testConcept(typedDomain),
178
179
  }
179
- const reportSet = testReportSet(concepts, [fact], {})
180
+ const reportSet = testReportSet(concepts, [fact], {}, [])
180
181
  const summary = new DocumentSummary(reportSet);
181
182
 
182
183
  expect(Object.fromEntries(summary.tagCounts())).toEqual({
@@ -200,7 +201,7 @@ describe("Files summary", () => {
200
201
 
201
202
  test("no files", () => {
202
203
  const documentData = {}
203
- const reportSet = testReportSet({}, [], documentData);
204
+ const reportSet = testReportSet({}, [], documentData, []);
204
205
  const summary = new DocumentSummary(reportSet);
205
206
 
206
207
  expect(summary.getLocalDocuments()).toEqual({
@@ -228,7 +229,7 @@ describe("Files summary", () => {
228
229
  'labelLinkbase.xml': ['labelLinkbase'],
229
230
  'unrecognizedLinkbase.xml': ['unrecognizedLinkbase'],
230
231
  }
231
- const reportSet = testReportSet({}, [], documentData);
232
+ const reportSet = testReportSet({}, [], documentData, []);
232
233
  const summary = new DocumentSummary(reportSet);
233
234
 
234
235
  expect(summary.getLocalDocuments()).toEqual({
@@ -243,3 +244,39 @@ describe("Files summary", () => {
243
244
  });
244
245
  });
245
246
  });
247
+
248
+ describe("Software credit", () => {
249
+
250
+ test("no credits", () => {
251
+ const softwareCredits = []
252
+ const documentData = {}
253
+ const reportSet = testReportSet({}, [], documentData, softwareCredits);
254
+ const summary = new DocumentSummary(reportSet);
255
+
256
+ expect(summary.getSoftwareCredits()).toEqual([]);
257
+ });
258
+
259
+ test("one credit", () => {
260
+ const softwareCredits = ["Example credit text"]
261
+ const documentData = {}
262
+ const reportSet = testReportSet({}, [], documentData, softwareCredits);
263
+ const summary = new DocumentSummary(reportSet);
264
+
265
+ expect(summary.getSoftwareCredits()).toEqual(["Example credit text"]);
266
+ });
267
+
268
+ test("multiple credits", () => {
269
+ const softwareCredits = [
270
+ "Example credit text A",
271
+ "Example credit text B",
272
+ ]
273
+ const documentData = {}
274
+ const reportSet = testReportSet({}, [], documentData, softwareCredits);
275
+ const summary = new DocumentSummary(reportSet);
276
+
277
+ expect(summary.getSoftwareCredits()).toEqual([
278
+ "Example credit text A",
279
+ "Example credit text B",
280
+ ]);
281
+ });
282
+ });
@@ -1,7 +1,6 @@
1
1
  // See COPYRIGHT.md for copyright information
2
2
 
3
3
  import $ from 'jquery'
4
- import FileSaver from 'file-saver'
5
4
  import writeXlsxFile from 'write-excel-file'
6
5
  import { Fact } from './fact.js';
7
6
 
@@ -15,12 +14,13 @@ export class TableExport {
15
14
  static addHandles(iframe, reportSet) {
16
15
  $('table', iframe).each(function () {
17
16
  const table = $(this);
18
- if (table.find(".ixbrl-element").length > 0) {
17
+ // Require at least two facts on different rows.
18
+ if (table.find("tr").filter((i, row) => $(row).find(".ixbrl-element").length > 0).length > 1) {
19
19
  table.css("position", "relative");
20
20
  const exporter = new TableExport(table, reportSet);
21
21
  $('<div class="ixbrl-table-handle"><span>Export table</span></div>')
22
22
  .appendTo(table)
23
- .click(() => exporter.exportTable());
23
+ .on("click", () => exporter.exportTable());
24
24
  }
25
25
  });
26
26
  }
@@ -0,0 +1,34 @@
1
+ // See COPYRIGHT.md for copyright information
2
+
3
+ export class TaxonomyNamer {
4
+ constructor(map) {
5
+ this.map = new Map(Array.from(map.entries()).map(([k,v]) => [new RegExp(k), new TaxonomyName(v[0], v[1])]));
6
+ }
7
+
8
+
9
+ getName(prefix, uri) {
10
+ for (const [re, name] of this.map.entries()) {
11
+ const m = uri.match(re);
12
+ if (m !== null) {
13
+ return name;
14
+ }
15
+ }
16
+ return new TaxonomyName(prefix, prefix);
17
+ }
18
+
19
+ fromQName(qname) {
20
+ return this.getName(qname.prefix, qname.namespace);
21
+ }
22
+
23
+ convertQName(qname) {
24
+ const name = this.fromQName(qname)
25
+ return name.prefix + ':' + qname.localname;
26
+ }
27
+ }
28
+
29
+ export class TaxonomyName {
30
+ constructor(prefix, name) {
31
+ this.prefix = prefix;
32
+ this.name = name;
33
+ }
34
+ }
@@ -0,0 +1,32 @@
1
+ // See COPYRIGHT.md for copyright information
2
+
3
+ import { TaxonomyNamer } from './taxonomynamer.js';
4
+ import { QName } from './qname.js';
5
+
6
+ const prefixMap = {
7
+ "e": "http://example.com/",
8
+ "g": "http://eggsample.com/",
9
+ };
10
+
11
+ const preferredPrefixMap = new Map([
12
+ [ "http://ex.*\.com/", ["prefix1", "My Example"] ]
13
+ ]);
14
+
15
+ function qname(s) {
16
+ return new QName(prefixMap, s);
17
+ }
18
+
19
+ describe("readableName", () => {
20
+ const namer = new TaxonomyNamer(preferredPrefixMap);
21
+ test("Taxonomy name match", () => {
22
+ expect(namer.fromQName(qname("e:1234")).prefix).toBe("prefix1");
23
+ expect(namer.fromQName(qname("e:1234")).name).toBe("My Example");
24
+ expect(namer.convertQName(qname("e:1234"))).toBe("prefix1:1234");
25
+ });
26
+ test("Unknown taxonomy", () => {
27
+ expect(namer.fromQName(qname("g:1234")).prefix).toBe("g");
28
+ expect(namer.fromQName(qname("g:1234")).name).toBe("g");
29
+ expect(namer.convertQName(qname("g:1234"))).toBe("g:1234");
30
+ });
31
+ });
32
+
@@ -0,0 +1,49 @@
1
+ // See COPYRIGHT.md for copyright information
2
+
3
+ import { STORAGE_THEME } from "./util";
4
+
5
+ const DARK_THEME = 'dark';
6
+ const LIGHT_THEME = 'light';
7
+
8
+ function getHtmlElement() {
9
+ return document.querySelector('html');
10
+ }
11
+
12
+ export function getVariable(name) {
13
+ const html = getHtmlElement();
14
+ return getComputedStyle(html).getPropertyValue(name);
15
+ }
16
+
17
+ function setTheme(theme) {
18
+ const html = getHtmlElement();
19
+ html.dataset.theme = `theme-${theme}`;
20
+ }
21
+
22
+ function getStoredTheme() {
23
+ return localStorage.getItem(STORAGE_THEME);
24
+ }
25
+
26
+ function storeTheme(theme) {
27
+ localStorage.setItem(STORAGE_THEME, theme);
28
+ }
29
+
30
+ export function getTheme() {
31
+ const html = getHtmlElement();
32
+ return html.dataset.theme.replace('theme-','');
33
+ }
34
+
35
+ export function initializeTheme() {
36
+ const storedTheme = getStoredTheme();
37
+ if (storedTheme !== null) {
38
+ setTheme(storedTheme);
39
+ } else {
40
+ setTheme(window.matchMedia(`(prefers-color-scheme: ${DARK_THEME})`).matches ? DARK_THEME : LIGHT_THEME);
41
+ }
42
+ }
43
+
44
+ export function toggleTheme() {
45
+ const currentTheme = getTheme();
46
+ const newTheme = currentTheme === LIGHT_THEME ? DARK_THEME : LIGHT_THEME;
47
+ setTheme(newTheme);
48
+ storeTheme(newTheme);
49
+ }
@@ -1,9 +1,42 @@
1
1
  // See COPYRIGHT.md for copyright information
2
2
 
3
- import { measureLabel, NAMESPACE_ISO4217 } from "./util";
3
+ import { NAMESPACE_ISO4217 } from "./util";
4
+ import i18next from "i18next";
5
+ import { utr } from "./utr";
6
+
7
+ /**
8
+ * Transforms measure qname into title case label (or currency symbol, if applicable).
9
+ * @return {String} Measure Label
10
+ */
11
+
12
+ function measureLabel(report, measure) {
13
+ const qname = report.qname(measure);
14
+ if (qname.namespace === NAMESPACE_ISO4217) {
15
+ // Prefer a name from our own i18n resources
16
+ const keyi18n = `currencies:unitFormat${qname.localname}`;
17
+ if (i18next.exists(keyi18n)) {
18
+ return i18next.t(keyi18n);
19
+ }
20
+ // Fall back on symbol from UTR ...
21
+ const utrEntry = utr.get(qname);
22
+ if (utrEntry !== undefined) {
23
+ // ... but disambiguate "$" symbol
24
+ return utrEntry.symbol == '$' ? `${qname.localname} $` : utrEntry.symbol;
25
+ }
26
+ }
27
+ if (measure.includes(':')) {
28
+ return measure.split(':')[1];
29
+ }
30
+ return measure;
31
+ }
32
+
33
+ export function measureName(report, measure) {
34
+ const qname = report.qname(measure);
35
+ const utrEntry = utr.get(qname);
36
+ return utrEntry?.name;
37
+ }
4
38
 
5
39
  export class Unit {
6
-
7
40
  constructor(reportSet, unitKey) {
8
41
  this._reportSet = reportSet;
9
42
  this._value = unitKey;
@@ -50,5 +83,43 @@ export class Unit {
50
83
  value() {
51
84
  return this._value;
52
85
  }
86
+
87
+ measureHTML(m) {
88
+ const span = document.createElement("span");
89
+ const name = measureName(this._reportSet, m);
90
+ if (name !== undefined) {
91
+ span.setAttribute("title", name);
92
+ span.classList.add("measure");
93
+ }
94
+ span.append(document.createTextNode(measureLabel(this._reportSet, m)));
95
+ return span;
96
+ }
97
+
98
+ partsHTML(parts) {
99
+ const span = document.createElement("span");
100
+ if (parts.length > 1) {
101
+ span.appendChild(document.createTextNode("("));
102
+ }
103
+ for (const [i, m] of parts.entries()) {
104
+ span.appendChild(this.measureHTML(m));
105
+ if (i < parts.length - 1) {
106
+ span.appendChild(document.createTextNode(" * "))
107
+ }
108
+ }
109
+ if (parts.length > 1) {
110
+ span.appendChild(document.createTextNode(")"));
111
+ }
112
+ return span;
113
+ }
114
+
115
+ html() {
116
+ const span = document.createElement("span");
117
+ span.append(this.partsHTML(this._numerators));
118
+ if (this._denominators.length > 0) {
119
+ span.append(document.createTextNode(" / "));
120
+ span.append(this.partsHTML(this._denominators));
121
+ }
122
+ return span;
123
+ }
53
124
  }
54
125
 
@@ -11,7 +11,7 @@ var testReportData = {
11
11
  "iso4217": NAMESPACE_ISO4217,
12
12
  "e": "http://example.com/entity",
13
13
  },
14
- "facts": {}
14
+ "facts": {},
15
15
  };
16
16
 
17
17
  function testReport() {
@@ -57,12 +57,23 @@ describe("Unit label", () => {
57
57
 
58
58
  test("Unit label - known currency", () => {
59
59
  var unit = new Unit(testReport(), 'iso4217:USD');
60
+ // Note "US $" from i18n takes precedence over "USD $" generated from UTR
60
61
  expect(unit.label()).toEqual('US $');
61
62
  });
62
63
 
64
+ test("Unit label - UTR currency", () => {
65
+ var unit = new Unit(testReport(), 'iso4217:THB');
66
+ expect(unit.label()).toEqual('฿');
67
+ });
68
+
69
+ test("Unit label - UTR currency with '$' symbol", () => {
70
+ var unit = new Unit(testReport(), 'iso4217:SGD');
71
+ expect(unit.label()).toEqual('SGD $');
72
+ });
73
+
63
74
  test("Unit label - unknown", () => {
64
- var unit = new Unit(testReport(), 'iso4217:ZAR');
65
- expect(unit.label()).toEqual('ZAR');
75
+ var unit = new Unit(testReport(), 'iso4217:ZZZZ');
76
+ expect(unit.label()).toEqual('ZZZZ');
66
77
  });
67
78
  });
68
79
 
@@ -2,16 +2,34 @@
2
2
 
3
3
  import moment from "moment";
4
4
  import Decimal from "decimal.js";
5
- import i18next from "i18next";
6
-
7
5
 
8
6
  export const SHOW_FACT = 'SHOW_FACT';
9
7
 
10
8
  export const NAMESPACE_ISO4217 = 'http://www.xbrl.org/2003/iso4217';
9
+ export const NAMESPACE_XBRLI = 'http://www.xbrl.org/2003/instance';
10
+
11
+ export const CALC_ARCROLE = "calc";
12
+ export const CALC11_ARCROLE = "calc11";
13
+
14
+ export const GLOSSARY_URL = "https://xbrl.org/glossary/";
11
15
 
12
16
  // The number of distinct highlight colors defined in inspector.less
13
17
  export const HIGHLIGHT_COLORS = 3;
14
18
 
19
+ // Feature names
20
+ export const FEATURE_GUIDE_LINK = 'guide_link';
21
+ export const FEATURE_HOME_LINK_LABEL = 'home_link_label';
22
+ export const FEATURE_HOME_LINK_URL = 'home_link_url';
23
+ export const FEATURE_REVIEW = 'review';
24
+ export const FEATURE_SUPPORT_LINK = 'support_link';
25
+ export const FEATURE_SURVEY_LINK = 'survey_link';
26
+ export const FEATURE_SEARCH_ON_STARTUP = 'search_on_startup';
27
+ export const FEATURE_HIGHLIGHT_FACTS_ON_STARTUP = 'highlight_facts_on_startup';
28
+
29
+ export const STORAGE_THEME = "ixbrl-viewer-theme";
30
+ export const STORAGE_HIGHLIGHT_FACTS = "ixbrl-viewer-highlight-all-facts";
31
+ export const STORAGE_HOME_LINK_QUERY = "ixbrl-viewer-home-link-query";
32
+
15
33
  /*
16
34
  * Takes a moment.js oject and converts it to a human readable date, or date
17
35
  * and time if the time component is not midnight. Adjust specifies that a
@@ -205,7 +223,7 @@ export function localId(viewerUniqueId) {
205
223
 
206
224
  export function getIXHiddenLinkStyle(domNode) {
207
225
  if (domNode.hasAttribute('style')) {
208
- const re = /(?:^|\s|;)-(?:sec|esef)-ix-hidden:\s*([^\s;]+)/;
226
+ const re = /(?:^|\s|;)(?:-sec|-esef)?-ix-hidden:\s*([^\s;]+)/;
209
227
  const m = domNode.getAttribute('style').match(re);
210
228
  if (m) {
211
229
  return m[1];
@@ -213,18 +231,3 @@ export function getIXHiddenLinkStyle(domNode) {
213
231
  }
214
232
  return null;
215
233
  }
216
-
217
- /**
218
- * Transforms measure qname into title case label (or currency symbol, if applicable).
219
- * @return {String} Measure Label
220
- */
221
-
222
- export function measureLabel(report, measure) {
223
- const qname = report.qname(measure);
224
- if (qname.namespace === NAMESPACE_ISO4217) {
225
- measure = i18next.t(`currencies:unitFormat${qname.localname}`, {defaultValue: qname.localname});
226
- } else if (measure.includes(':')) {
227
- measure = measure.split(':')[1];
228
- }
229
- return measure;
230
- }
@@ -147,6 +147,7 @@ describe("Regex escape", () => {
147
147
 
148
148
  describe("Get IX Hidden Link Style", () => {
149
149
  it.each([
150
+ ["-ix-hidden:123", "123"],
150
151
  ["-sec-ix-hidden:123", "123"],
151
152
  ["-esef-ix-hidden:123", "123"],
152
153
  ["-xxx-ix-hidden:123", null],
@@ -0,0 +1,27 @@
1
+ // See COPYRIGHT.md for copyright information
2
+
3
+ // Class to expose properties of Unit Type Registry entries from viewer data
4
+
5
+ export class UTREntry {
6
+
7
+ constructor(symbol, name) {
8
+ this.symbol = symbol;
9
+ this.name = name;
10
+ }
11
+ }
12
+
13
+ class UTR {
14
+ constructor() {
15
+ self.entries = require("../data/utr.json");
16
+ }
17
+
18
+ get(qname) {
19
+ const u = self.entries[qname.namespace]?.[qname.localname]
20
+ if (u === undefined) {
21
+ return undefined;
22
+ }
23
+ return new UTREntry(u.s ?? u.n, u.n)
24
+ }
25
+ }
26
+
27
+ export const utr = new UTR();
@@ -1,10 +1,10 @@
1
1
  // See COPYRIGHT.md for copyright information
2
2
 
3
3
  import $ from 'jquery'
4
- import { numberMatchSearch, fullDateMatch } from './number-matcher.js'
4
+ import { numberMatchSearch } from './number-matcher.js'
5
5
  import { TableExport } from './tableExport.js'
6
6
  import { IXNode } from './ixnode.js';
7
- import { getIXHiddenLinkStyle, runGenerator, escapeRegex, viewerUniqueId, HIGHLIGHT_COLORS } from './util.js';
7
+ import { getIXHiddenLinkStyle, runGenerator, viewerUniqueId, HIGHLIGHT_COLORS } from './util.js';
8
8
  import { DocOrderIndex } from './docOrderIndex.js';
9
9
  import { MessageBox } from './messagebox.js';
10
10
 
@@ -65,7 +65,7 @@ export class Viewer {
65
65
  viewer._iframes.each(function (docIndex) {
66
66
  $(this).data("selected", docIndex == viewer._currentDocumentIndex);
67
67
  const reportIndex = $(this).data("report-index");
68
- viewer._preProcessiXBRL($(this).contents().find("body").get(0), reportIndex, docIndex);
68
+ viewer._preProcessiXBRL($(this).contents().find("body").get(0), reportIndex, docIndex, false);
69
69
  });
70
70
 
71
71
  /* Call plugin promise for each document in turn */
@@ -108,11 +108,11 @@ export class Viewer {
108
108
  if (this._reportSet.isMultiDocumentViewer()) {
109
109
  $('#ixv .ixds-tabs').show();
110
110
  for (const [i, doc] of this._reportSet.reportFiles().entries()) {
111
- $('<div class="tab"></div>')
111
+ $('<button class="tab"></button>')
112
112
  .text(doc.file)
113
113
  .prop('title', doc.file)
114
114
  .data('ix-doc-id', i)
115
- .click(() => this.selectDocument(i))
115
+ .on("click", () => this.selectDocument(i))
116
116
  .appendTo($('#ixv #viewer-pane .ixds-tabs .tab-area'));
117
117
  }
118
118
  $('#ixv #viewer-pane .ixds-tabs .tab-area .tab').eq(0).addClass("active");
@@ -123,12 +123,17 @@ export class Viewer {
123
123
  // display: block, a div is used, otherwise a span. Returns the wrapper node
124
124
  // as a jQuery node
125
125
  _wrapNode(n) {
126
- var wrapper = "<span>";
127
- const nn = n.getElementsByTagName("*");
128
- for (var i = 0; i < nn.length; i++) {
129
- if (getComputedStyle(nn[i]).getPropertyValue('display') === "block") {
130
- wrapper = '<div>';
131
- break;
126
+ let wrapper = "<span>";
127
+ if (getComputedStyle(n).getPropertyValue("display") === "block") {
128
+ wrapper = '<div>';
129
+ }
130
+ else {
131
+ const nn = n.getElementsByTagName("*");
132
+ for (var i = 0; i < nn.length; i++) {
133
+ if (getComputedStyle(nn[i]).getPropertyValue('display') === "block") {
134
+ wrapper = '<div>';
135
+ break;
136
+ }
132
137
  }
133
138
  }
134
139
  $(n).wrap(wrapper);
@@ -232,7 +237,7 @@ export class Viewer {
232
237
  const [file, fragment] = url.split('#', 2);
233
238
  const docIndex = this._reportSet.reportFiles().indexOf(file);
234
239
  if (!url.includes('/') && docIndex != -1) {
235
- $(n).click((e) => {
240
+ $(n).on("click", (e) => {
236
241
  this._showDocumentAndElement(docIndex, fragment);
237
242
  e.preventDefault();
238
243
  });
@@ -268,7 +273,7 @@ export class Viewer {
268
273
  nodes.push(tableNode)
269
274
  }
270
275
  }
271
- /* Otherwise, insert a <span> as wrapper */
276
+ /* Otherwise, insert a <span> or <div> as wrapper */
272
277
  if (nodes.length == 0) {
273
278
  nodes.push(this._wrapNode(domNode));
274
279
  }
@@ -440,7 +445,7 @@ export class Viewer {
440
445
  // Handle SEC/ESEF links-to-hidden
441
446
  const vuid = viewerUniqueId(reportIndex, getIXHiddenLinkStyle(n));
442
447
  if (vuid !== null) {
443
- let nodes = $(n);
448
+ let nodes = this._findOrCreateWrapperNode(n, inHidden);
444
449
  nodes.addClass("ixbrl-element").data('ivids', [vuid]);
445
450
  this._docOrderItemIndex.addItem(vuid, docIndex);
446
451
  /* We may have already seen the corresponding ix element in the hidden
@@ -472,7 +477,7 @@ export class Viewer {
472
477
  _applyStyles() {
473
478
  const stlyeElts = $("<style>")
474
479
  .prop("type", "text/css")
475
- .text(require('css-loader!less-loader!../less/viewer.less').toString())
480
+ .text(require('../less/viewer.less').toString())
476
481
  .appendTo(this._iframes.contents().find("head"));
477
482
  this._iv.callPluginMethod("updateViewerStyleElements", stlyeElts);
478
483
  }
@@ -512,18 +517,18 @@ export class Viewer {
512
517
  _bindHandlers() {
513
518
  const viewer = this;
514
519
  $('.ixbrl-element', this._contents)
515
- .click(function (e) {
520
+ .on("click", function (e) {
516
521
  e.stopPropagation();
517
522
  viewer.selectElementByClick($(this));
518
523
  })
519
- .mouseenter(function (e) { viewer._mouseEnter($(this)) })
520
- .mouseleave(function (e) { viewer._mouseLeave($(this)) });
524
+ .on("mouseenter", function (e) { viewer._mouseEnter($(this)) })
525
+ .on("mouseleave", function (e) { viewer._mouseLeave($(this)) });
521
526
  $("body", this._contents)
522
- .click(() => viewer.selectElement(null));
527
+ .on("click", () => viewer.selectElement(null));
523
528
 
524
- $('#iframe-container .zoom-in').click(() => this.zoomIn());
525
- $('#iframe-container .zoom-out').click(() => this.zoomOut());
526
- $('#iframe-container .print').click(() => this.currentDocument().get(0).contentWindow.print());
529
+ $('#iframe-container .zoom-in').on("click", () => this.zoomIn());
530
+ $('#iframe-container .zoom-out').on("click", () => this.zoomOut());
531
+ $('#iframe-container .print').on("click", () => this.currentDocument().get(0).contentWindow.print());
527
532
 
528
533
  TableExport.addHandles(this._contents, this._reportSet);
529
534
  }
@@ -593,7 +598,7 @@ export class Viewer {
593
598
  /* If the specified element is not fully visible, scroll it into the center of
594
599
  * the viewport */
595
600
  showElement(e) {
596
- const ee = e.get(0);
601
+ const ee = e.filter(':not(.ixbrl-no-highlight)').get(0);
597
602
  if (!this.isFullyVisible(ee)) {
598
603
  ee.scrollIntoView({ block: "center", inline: "center" });
599
604
  }
@@ -749,11 +754,14 @@ export class Viewer {
749
754
  // highlight color for an element that is double tagged in a
750
755
  // table cell.
751
756
  const ixn = $(this).data('ivids').map(id => viewer._ixNodeMap[id]).filter(ixn => !ixn.footnote)[0];
752
- if (ixn != undefined) {
753
- const elements = viewer.elementsForItemIds(ixn.chainIXIds());
754
- const i = groups[reportSet.getItemById(ixn.id).conceptQName().prefix];
755
- if (i !== undefined) {
756
- elements.addClass("ixbrl-highlight-" + i);
757
+ if (ixn !== undefined ) {
758
+ const item = reportSet.getItemById(ixn.id);
759
+ if (item !== undefined) {
760
+ const elements = viewer.elementsForItemIds(ixn.chainIXIds());
761
+ const i = groups[item.conceptQName().prefix];
762
+ if (i !== undefined) {
763
+ elements.addClass("ixbrl-highlight-" + i);
764
+ }
757
765
  }
758
766
  }
759
767
  });
@@ -803,7 +811,10 @@ export class Viewer {
803
811
  }
804
812
 
805
813
  _setTitle(docIndex) {
806
- $('#top-bar .document-title').text($('head title', this._iframes.eq(docIndex).contents()).text());
814
+ const title = $('head title', this._iframes.eq(docIndex).contents()).text();
815
+ $('#top-bar .document-title')
816
+ .text(title)
817
+ .attr("aria-label", "Inline Viewer: " + title);
807
818
  }
808
819
 
809
820
  showDocumentForItemId(vuid) {