accessibility-checker 4.0.31 → 4.0.32

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 (29) hide show
  1. package/README.md +29 -0
  2. package/cjs/lib/common/config/ACConfigManager.js +15 -1
  3. package/cjs/lib/common/config/ACConfigManager.js.map +1 -1
  4. package/cjs/lib/common/config/ACConstants.js +4 -4
  5. package/cjs/lib/common/config/ACConstants.js.map +1 -1
  6. package/cjs/lib/common/report/ACReporterXLSX.d.ts +2 -2
  7. package/cjs/lib/common/report/ACReporterXLSX.js +408 -649
  8. package/cjs/lib/common/report/ACReporterXLSX.js.map +1 -1
  9. package/cjs/lib/common/report/genReport.js +2 -2
  10. package/cjs/lib/common/report/genReport.js.map +1 -1
  11. package/lib/common/config/ACConfigManager.js +15 -1
  12. package/lib/common/config/ACConfigManager.js.map +1 -1
  13. package/lib/common/config/ACConstants.js +4 -4
  14. package/lib/common/config/ACConstants.js.map +1 -1
  15. package/lib/common/report/ACReporterXLSX.d.ts +2 -2
  16. package/lib/common/report/ACReporterXLSX.js +408 -649
  17. package/lib/common/report/ACReporterXLSX.js.map +1 -1
  18. package/lib/common/report/genReport.js +2 -2
  19. package/lib/common/report/genReport.js.map +1 -1
  20. package/mjs/lib/common/config/ACConfigManager.js +15 -1
  21. package/mjs/lib/common/config/ACConfigManager.js.map +1 -1
  22. package/mjs/lib/common/config/ACConstants.js +4 -4
  23. package/mjs/lib/common/config/ACConstants.js.map +1 -1
  24. package/mjs/lib/common/report/ACReporterXLSX.d.ts +2 -2
  25. package/mjs/lib/common/report/ACReporterXLSX.js +421 -650
  26. package/mjs/lib/common/report/ACReporterXLSX.js.map +1 -1
  27. package/mjs/lib/common/report/genReport.js +2 -2
  28. package/mjs/lib/common/report/genReport.js.map +1 -1
  29. package/package.json +4 -11
@@ -17,24 +17,23 @@ import { eRuleLevel } from "../config/IConfig.js";
17
17
  import { eToolkitLevel } from "../engine/IGuideline.js";
18
18
  import { eRuleConfidence } from "../engine/IRule.js";
19
19
  import { ReporterManager } from "./ReporterManager.js";
20
- // Lazy load exceljs - will be null if not installed
21
- let exceljsLoadPromise = null;
22
- let exceljsWarningShown = false;
23
- function loadExcelJS() {
24
- if (!exceljsLoadPromise) {
25
- // Use dynamic import with a variable to avoid TypeScript checking the module at compile time
26
- const moduleName = "exceljs";
27
- exceljsLoadPromise = import(/* webpackIgnore: true */ moduleName)
28
- .then(module => module)
29
- .catch(e => null);
20
+ // Lazy-load write-excel-file — will be null if not installed
21
+ let writeExcelLoadPromise = null;
22
+ let writeExcelWarningShown = false;
23
+ function loadWriteExcel() {
24
+ if (!writeExcelLoadPromise) {
25
+ const moduleName = "write-excel-file/node";
26
+ writeExcelLoadPromise = import(/* webpackIgnore: true */ moduleName)
27
+ .then(module => module.default ?? module)
28
+ .catch(() => null);
30
29
  }
31
- return exceljsLoadPromise;
30
+ return writeExcelLoadPromise;
32
31
  }
33
- function showExcelJSWarning() {
34
- if (!exceljsWarningShown) {
35
- exceljsWarningShown = true;
36
- console.warn("Warning: exceljs is not installed. XLSX report generation is disabled.");
37
- console.warn("To enable XLSX reports, install exceljs: npm install exceljs");
32
+ function showWriteExcelWarning() {
33
+ if (!writeExcelWarningShown) {
34
+ writeExcelWarningShown = true;
35
+ console.warn("Warning: write-excel-file is not installed. XLSX report generation is disabled.");
36
+ console.warn("To enable XLSX reports, install write-excel-file: npm install write-excel-file");
38
37
  }
39
38
  }
40
39
  function dropDupes(arr) {
@@ -48,6 +47,121 @@ function dropDupes(arr) {
48
47
  }
49
48
  });
50
49
  }
50
+ // Convert ExcelJS-style ARGB (FFRRGGBB) to 6-digit hex (#RRGGBB)
51
+ function argbToHex(argb) {
52
+ if (!argb || argb.length < 6)
53
+ return "#000000";
54
+ // Strip leading alpha if 8 chars
55
+ const rgb = argb.length === 8 ? argb.slice(2) : argb;
56
+ return `#${rgb}`;
57
+ }
58
+ // Helper: cell with dark purple header style
59
+ function headerCell(value, columnSpan) {
60
+ const cell = {
61
+ value,
62
+ backgroundColor: "#403151",
63
+ textColor: "#FFFFFF",
64
+ fontFamily: "Calibri",
65
+ fontSize: 16,
66
+ alignVertical: "center",
67
+ align: "left",
68
+ height: 36,
69
+ };
70
+ if (columnSpan && columnSpan > 1)
71
+ cell.columnSpan = columnSpan;
72
+ return cell;
73
+ }
74
+ // Helper: standard data cell
75
+ function dataCell(value, opts) {
76
+ return {
77
+ value,
78
+ fontFamily: "Calibri",
79
+ fontSize: 12,
80
+ textColor: "#000000",
81
+ ...opts
82
+ };
83
+ }
84
+ // Helper: bordered cell
85
+ function borderedCell(value, opts) {
86
+ return {
87
+ value,
88
+ fontFamily: "Calibri",
89
+ fontSize: 12,
90
+ textColor: "#000000",
91
+ borderColor: "#A6A6A6",
92
+ borderStyle: "thin",
93
+ ...opts
94
+ };
95
+ }
96
+ // ─── XLSX table injection ──────────────────────────────────────────────────────
97
+ // write-excel-file has no native Excel table API. We post-process the output
98
+ // file: unzip it with Node's built-in zlib, inject the table definition XML,
99
+ // wire the sheet to reference it, and re-zip.
100
+ //
101
+ // The Issues sheet is always sheet index 3 (0-based) → sheetId "4".
102
+ // TableStyleMedium2 + showRowStripes matches the original ExcelJS output.
103
+ const ISSUES_SHEET_ID = "4";
104
+ const ISSUES_TABLE_ID = "1";
105
+ const ISSUES_COL_COUNT = 14;
106
+ const ISSUES_COL_NAMES = [
107
+ "Page title", "Page URL", "Scan label", "Issue ID", "Issue type",
108
+ "Toolkit level", "Checkpoint", "WCAG level", "Rule", "Issue",
109
+ "Element", "Code", "Xpath", "Help",
110
+ ];
111
+ function buildTableXml(rowCount) {
112
+ const lastCol = "ABCDEFGHIJKLMN"[ISSUES_COL_COUNT - 1];
113
+ const ref = `A1:${lastCol}${rowCount}`;
114
+ const cols = ISSUES_COL_NAMES.map((name, i) => `<tableColumn id="${i + 1}" name="${name}"/>`).join("");
115
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>` +
116
+ `<table xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"` +
117
+ ` id="${ISSUES_TABLE_ID}" name="IssuesTable" displayName="IssuesTable" ref="${ref}" headerRowCount="1">` +
118
+ `<autoFilter ref="${ref}"/>` +
119
+ `<tableColumns count="${ISSUES_COL_COUNT}">${cols}</tableColumns>` +
120
+ `<tableStyleInfo name="TableStyleMedium2" showFirstColumn="0"` +
121
+ ` showLastColumn="0" showRowStripes="1" showColumnStripes="0"/>` +
122
+ `</table>`;
123
+ }
124
+ async function injectTableIntoFile(filePath, issueRowCount) {
125
+ let AdmZip;
126
+ try {
127
+ // webpackIgnore prevents Cypress/webpack from trying to bundle this Node-only module.
128
+ // The try/catch ensures graceful degradation if adm-zip is not installed.
129
+ AdmZip = require(/* webpackIgnore: true */ "adm-zip");
130
+ }
131
+ catch {
132
+ // adm-zip not available — skip table injection gracefully
133
+ return;
134
+ }
135
+ const zip = new AdmZip(filePath);
136
+ // 1. Add xl/tables/table1.xml
137
+ const tableXml = buildTableXml(issueRowCount);
138
+ zip.addFile(`xl/tables/table${ISSUES_TABLE_ID}.xml`, Buffer.from(tableXml, "utf8"));
139
+ // 2. Patch xl/worksheets/_rels/sheet4.xml.rels
140
+ const relsPath = `xl/worksheets/_rels/sheet${ISSUES_SHEET_ID}.xml.rels`;
141
+ const relsEntry = zip.getEntry(relsPath);
142
+ const tableRel = `<Relationship Id="rIdTbl"` +
143
+ ` Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/table"` +
144
+ ` Target="../tables/table${ISSUES_TABLE_ID}.xml"/>`;
145
+ if (relsEntry) {
146
+ const existing = relsEntry.getData().toString("utf8");
147
+ zip.updateFile(relsPath, Buffer.from(existing.replace("</Relationships>", tableRel + "</Relationships>"), "utf8"));
148
+ }
149
+ else {
150
+ zip.addFile(relsPath, Buffer.from(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>` +
151
+ `<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">` +
152
+ tableRel + `</Relationships>`, "utf8"));
153
+ }
154
+ // 3. Patch xl/worksheets/sheet4.xml — add <tableParts> before </worksheet>
155
+ const sheetPath = `xl/worksheets/sheet${ISSUES_SHEET_ID}.xml`;
156
+ const sheetXml = zip.getEntry(sheetPath).getData().toString("utf8");
157
+ zip.updateFile(sheetPath, Buffer.from(sheetXml.replace("</worksheet>", `<tableParts count="1"><tablePart r:id="rIdTbl"/></tableParts></worksheet>`), "utf8"));
158
+ // 4. Patch [Content_Types].xml
159
+ const ctPath = "[Content_Types].xml";
160
+ const ctXml = zip.getEntry(ctPath).getData().toString("utf8");
161
+ zip.updateFile(ctPath, Buffer.from(ctXml.replace("</Types>", `<Override ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml"` +
162
+ ` PartName="/xl/tables/table${ISSUES_TABLE_ID}.xml"/></Types>`), "utf8"));
163
+ zip.writeZip(filePath);
164
+ }
51
165
  export class ACReporterXLSX {
52
166
  name() {
53
167
  return "xlsx";
@@ -74,7 +188,6 @@ export class ACReporterXLSX {
74
188
  policyInfo[ruleId].tkLevels = dropDupes(policyInfo[ruleId].tkLevels);
75
189
  policyInfo[ruleId].tkLevels.sort();
76
190
  }
77
- // const buffer: any = await workbook.xlsx.writeBuffer();
78
191
  let startScan = new Date(storedReport.engineReport.summary.startScan);
79
192
  let reportFilename = `results_${startScan.toISOString().replace(/:/g, "-")}.xlsx`;
80
193
  if (config.outputFilenameTimestamp === false) {
@@ -83,34 +196,33 @@ export class ACReporterXLSX {
83
196
  return {
84
197
  summaryPath: reportFilename,
85
198
  summary: async (filename) => {
86
- const ExcelJSModule = await loadExcelJS();
87
- if (!ExcelJSModule) {
88
- showExcelJSWarning();
199
+ const writeExcelFile = await loadWriteExcel();
200
+ if (!writeExcelFile) {
201
+ showWriteExcelWarning();
89
202
  return;
90
203
  }
91
- // @ts-ignore
92
- const workbook = new ExcelJSModule.stream.xlsx.WorkbookWriter({ filename, useStyles: true });
93
- // const workbook = new ExcelJS.Workbook({ useStyles: true });
94
- ACReporterXLSX.createOverviewSheet(config, summaryData, workbook);
95
- ACReporterXLSX.createScanSummarySheet(config, summaryData, workbook);
96
- ACReporterXLSX.createIssueSummarySheet(config, policyInfo, summaryData, workbook);
97
- ACReporterXLSX.createIssuesSheet(config, policyInfo, summaryData, workbook);
98
- ACReporterXLSX.createDefinitionsSheet(workbook);
99
- workbook.commit();
204
+ const issuesSheet = ACReporterXLSX.createIssuesSheet(config, policyInfo, summaryData);
205
+ const issueRowCount = issuesSheet._issueRowCount;
206
+ delete issuesSheet._issueRowCount;
207
+ const sheets = [
208
+ ACReporterXLSX.createOverviewSheet(config, summaryData),
209
+ ACReporterXLSX.createScanSummarySheet(config, summaryData),
210
+ ACReporterXLSX.createIssueSummarySheet(config, policyInfo, summaryData),
211
+ issuesSheet,
212
+ ACReporterXLSX.createDefinitionsSheet(),
213
+ ];
214
+ await writeExcelFile(sheets).toFile(filename);
215
+ await injectTableIntoFile(filename, issueRowCount);
100
216
  }
101
217
  };
102
218
  }
103
- static createOverviewSheet(config, compressedScans, workbook) {
219
+ static createOverviewSheet(config, compressedScans) {
104
220
  let violations = 0;
105
221
  let needsReviews = 0;
106
222
  let recommendations = 0;
107
223
  let archived = 0;
108
224
  let totalIssues = 0;
109
225
  let startScan = 0;
110
- // BIG QUESTION: is report
111
- // 1. for current scan (from menu)
112
- // 2. all stored scans (from menu)
113
- // 3. selected stored scans (from scan manager)
114
226
  for (const compressedScan of compressedScans) {
115
227
  let storedScan = ReporterManager.uncompressReport(compressedScan);
116
228
  if (startScan === 0)
@@ -122,311 +234,147 @@ export class ACReporterXLSX {
122
234
  archived += counts.ignored;
123
235
  }
124
236
  totalIssues = violations + needsReviews + recommendations + archived;
125
- const worksheet = workbook.addWorksheet("Overview");
126
- // Report Title
127
- worksheet.mergeCells('A1', "E1");
128
- const titleRow = worksheet.getRow(1);
129
- titleRow.height = 27;
130
- const cellA1 = worksheet.getCell('A1');
131
- cellA1.value = "Accessibility Scan Report";
132
- cellA1.alignment = { vertical: "middle", horizontal: "left" };
133
- cellA1.font = { name: "Calibri", color: { argb: "FFFFFFFF" }, size: 16 };
134
- cellA1.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF403151' } };
135
- // what are column widths - can't get it till you set it
136
- const colWidthData = [
137
- { col: 'A', width: 15.1 },
138
- { col: 'B', width: 15.9 },
139
- { col: 'C', width: 16.23 },
140
- { col: 'D', width: 19.4 },
237
+ const columns = [
238
+ { width: 15.1 },
239
+ { width: 15.9 },
240
+ { width: 16.23 },
241
+ { width: 19.4 },
242
+ { width: 15 },
141
243
  ];
142
- for (let i = 0; i < 4; i++) {
143
- worksheet.getColumn(colWidthData[i].col).width = colWidthData[i].width;
144
- }
145
- // note except for Report Date this is the same for all scans
146
244
  const rowData = [
147
245
  { key1: 'Tool:', key2: 'IBM Equal Access Accessibility Checker' },
148
- // {key1: 'Version:', key2: "chrome.runtime.getManifest().version"},
149
246
  { key1: 'Version:', key2: config.toolID },
150
- //@ts-ignore
151
- // {key1: 'Rule set:', key2: (theCurrentScan.ruleSet === "Latest Deployment") ? archives[1].name : theCurrentScan.ruleSet },
152
247
  { key1: 'Rule set:', key2: config.ruleArchiveLabel },
153
248
  { key1: 'Guidelines:', key2: config.policies.join(", ") },
154
- { key1: 'Report date:', key2: new Date(startScan).toLocaleString() }, // do we need to get actual date?
155
- { key1: 'Scans:', key2: "" + compressedScans.length }, // *** NEED TO FIX FOR selected
156
- // { key1: 'Pages:', key2: "" }
249
+ { key1: 'Report date:', key2: new Date(startScan).toLocaleString() },
250
+ { key1: 'Scans:', key2: "" + compressedScans.length },
157
251
  ];
158
- for (let idx = 0; idx < rowData.length; ++idx) {
159
- worksheet.mergeCells(`B${idx + 2}`, `E${idx + 2}`);
160
- let i = idx + 2;
161
- worksheet.getRow(i).height = 12; // results in a row height of 16
162
- worksheet.getRow(i).getCell(1).font = { name: "Calibri", color: { argb: "FF000000" }, size: 12 };
163
- worksheet.getRow(i).getCell(2).font = { name: "Calibri", color: { argb: "FF000000" }, size: 12 };
164
- worksheet.getRow(i).getCell(1).alignment = { horizontal: "left" };
165
- worksheet.getRow(i).getCell(2).alignment = { horizontal: "left" };
166
- // if (i == 7) {
167
- // worksheet.getRow(i).getCell(1).alignment = { vertical: "top" };
168
- // worksheet.getRow(i).getCell(2).alignment = { wrapText: true };
169
- // }
170
- worksheet.getRow(i).getCell(1).value = rowData[i - 2].key1;
171
- worksheet.getRow(i).getCell(2).value = rowData[i - 2].key2;
172
- }
173
- // Summary Title
174
- worksheet.mergeCells('A11', "E11");
175
- const summaryRow = worksheet.getRow(11);
176
- summaryRow.height = 27;
177
- const cellA11 = worksheet.getCell('A11');
178
- cellA11.value = "Summary";
179
- cellA11.alignment = { vertical: "middle", horizontal: "left" };
180
- cellA11.font = { name: "Calibri", color: { argb: "FFFFFFFF" }, size: 16 };
181
- cellA11.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF403151' } };
182
- // Scans info Headers
183
- worksheet.getRow(12).height = 16; // actual height is
184
- const cellA12 = worksheet.getCell('A12');
185
- cellA12.value = "Total issues";
186
- const cellB12 = worksheet.getCell('B12');
187
- cellB12.value = "Violations";
188
- const cellC12 = worksheet.getCell('C12');
189
- cellC12.value = "Needs review";
190
- const cellD12 = worksheet.getCell('D12');
191
- cellD12.value = "Recommendations";
192
- const cellE12 = worksheet.getCell('E12');
193
- cellE12.value = "Archived";
194
- const cellObjects1 = [cellA12, cellB12, cellC12, cellD12, cellE12];
195
- for (let i = 0; i < cellObjects1.length; i++) {
196
- cellObjects1[i].alignment = { vertical: "middle", horizontal: "center" };
197
- if (i == 1 || i == 2 || i == 3 || i === 4) {
198
- cellObjects1[i].font = { name: "Calibri", color: { argb: "FF000000" }, size: 12 };
199
- }
200
- else {
201
- cellObjects1[i].font = { name: "Calibri", color: { argb: "FFFFFFFF" }, size: 12 };
202
- }
203
- // cellObjects1[i].fill = { type: 'pattern', pattern: 'solid', fgColor:{argb:'FFC65911'} };
204
- cellObjects1[i].border = {
205
- top: { style: 'thin', color: { argb: 'FFA6A6A6' } },
206
- left: { style: 'thin', color: { argb: 'FFA6A6A6' } },
207
- bottom: { style: 'thin', color: { argb: 'FFA6A6A6' } },
208
- right: { style: 'thin', color: { argb: 'FFA6A6A6' } }
209
- };
210
- }
211
- cellA12.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF000000' } };
212
- cellB12.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFE4AAAF' } };
213
- cellC12.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFF4E08A' } };
214
- cellD12.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF96A9D7' } };
215
- // Scans info Values
216
- worksheet.getRow(13).height = 27; // actual height is
217
- const cellA13 = worksheet.getCell('A13');
218
- cellA13.value = totalIssues;
219
- const cellB13 = worksheet.getCell('B13');
220
- cellB13.value = violations;
221
- const cellC13 = worksheet.getCell('C13');
222
- cellC13.value = needsReviews;
223
- const cellD13 = worksheet.getCell('D13');
224
- cellD13.value = recommendations;
225
- const cellE13 = worksheet.getCell('E13');
226
- cellE13.value = archived;
227
- const cellObjects2 = [cellA13, cellB13, cellC13, cellD13, cellE13];
228
- for (let i = 0; i < cellObjects2.length; i++) {
229
- cellObjects2[i].alignment = { vertical: "middle", horizontal: "center" };
230
- cellObjects2[i].font = { name: "Calibri", color: { argb: "FF000000" }, size: 12 };
231
- // cellObjects2[i].fill = { type: 'pattern', pattern: 'solid', fgColor:{argb:'FFf8cbad'} };
232
- cellObjects2[i].border = {
233
- top: { style: 'thin', color: { argb: 'FFA6A6A6' } },
234
- left: { style: 'thin', color: { argb: 'FFA6A6A6' } },
235
- bottom: { style: 'thin', color: { argb: 'FFA6A6A6' } },
236
- right: { style: 'thin', color: { argb: 'FFA6A6A6' } }
237
- };
252
+ const data = [];
253
+ // Row 1: Title spanning 5 cols
254
+ data.push([
255
+ { value: "Accessibility Scan Report", backgroundColor: "#403151", textColor: "#FFFFFF", fontFamily: "Calibri", fontSize: 16, alignVertical: "center", align: "left", height: 27, columnSpan: 5 },
256
+ null, null, null, null
257
+ ]);
258
+ // Rows 2–7: metadata key/value pairs
259
+ for (const row of rowData) {
260
+ data.push([
261
+ dataCell(row.key1, { align: "left", height: 12 }),
262
+ { value: row.key2, fontFamily: "Calibri", fontSize: 12, textColor: "#000000", align: "left", columnSpan: 4 },
263
+ null, null, null
264
+ ]);
238
265
  }
266
+ // Row 8: blank spacer
267
+ data.push([null]);
268
+ // Row 9: blank spacer
269
+ data.push([null]);
270
+ // Row 10: blank spacer
271
+ data.push([null]);
272
+ // Row 11: "Summary" title spanning 5 cols
273
+ data.push([
274
+ { value: "Summary", backgroundColor: "#403151", textColor: "#FFFFFF", fontFamily: "Calibri", fontSize: 16, alignVertical: "center", align: "left", height: 27, columnSpan: 5 },
275
+ null, null, null, null
276
+ ]);
277
+ // Row 12: column headers
278
+ data.push([
279
+ borderedCell("Total issues", { align: "center", alignVertical: "center", backgroundColor: "#000000", textColor: "#FFFFFF", height: 16 }),
280
+ borderedCell("Violations", { align: "center", alignVertical: "center", backgroundColor: "#E4AAAF", textColor: "#000000" }),
281
+ borderedCell("Needs review", { align: "center", alignVertical: "center", backgroundColor: "#F4E08A", textColor: "#000000" }),
282
+ borderedCell("Recommendations", { align: "center", alignVertical: "center", backgroundColor: "#96A9D7", textColor: "#000000" }),
283
+ borderedCell("Archived", { align: "center", alignVertical: "center", backgroundColor: "#FFFFFF", textColor: "#000000" }),
284
+ ]);
285
+ // Row 13: values
286
+ data.push([
287
+ borderedCell(totalIssues, { align: "center", alignVertical: "center", height: 27 }),
288
+ borderedCell(violations, { align: "center", alignVertical: "center" }),
289
+ borderedCell(needsReviews, { align: "center", alignVertical: "center" }),
290
+ borderedCell(recommendations, { align: "center", alignVertical: "center" }),
291
+ borderedCell(archived, { align: "center", alignVertical: "center" }),
292
+ ]);
293
+ return { data, sheet: "Overview", columns };
239
294
  }
240
- static createScanSummarySheet(config, compressedScans, workbook) {
241
- const worksheet = workbook.addWorksheet("Scan summary");
242
- // Scans info Headers
243
- worksheet.getRow(1).height = 39; // actual height is 52
244
- const colWidthData = [
245
- { col: 'A', width: 27.0 },
246
- { col: 'B', width: 46.0 },
247
- { col: 'C', width: 20.17 },
248
- { col: 'D', width: 18.5 },
249
- { col: 'E', width: 17.17 },
250
- { col: 'F', width: 17.17 },
251
- { col: 'G', width: 17.17 },
252
- { col: 'H', width: 17.17 },
253
- { col: 'I', width: 17.17 },
295
+ static createScanSummarySheet(config, compressedScans) {
296
+ const columns = [
297
+ { width: 27.0 },
298
+ { width: 46.0 },
299
+ { width: 20.17 },
300
+ { width: 18.5 },
301
+ { width: 17.17 },
302
+ { width: 17.17 },
303
+ { width: 17.17 },
304
+ { width: 17.17 },
305
+ { width: 17.17 },
254
306
  ];
255
- for (let i = 0; i < 9; i++) {
256
- worksheet.getColumn(colWidthData[i].col).width = colWidthData[i].width;
257
- }
258
- const cellA1 = worksheet.getCell('A1');
259
- cellA1.value = "Page title";
260
- const cellB1 = worksheet.getCell('B1');
261
- cellB1.value = "Page url";
262
- const cellC1 = worksheet.getCell('C1');
263
- cellC1.value = "Scan label";
264
- const cellObjects1 = [cellA1, cellB1, cellC1];
265
- for (let i = 0; i < cellObjects1.length; i++) {
266
- cellObjects1[i].alignment = { vertical: "middle", horizontal: "left" };
267
- cellObjects1[i].font = { name: "Calibri", color: { argb: "FFFFFFFF" }, size: 12 };
268
- cellObjects1[i].fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF403151' } };
269
- cellObjects1[i].border = {
270
- top: { style: 'thin', color: { argb: 'FFA6A6A6' } },
271
- left: { style: 'thin', color: { argb: 'FFA6A6A6' } },
272
- bottom: { style: 'thin', color: { argb: 'FFA6A6A6' } },
273
- right: { style: 'thin', color: { argb: 'FFA6A6A6' } }
274
- };
275
- }
276
- const cellD1 = worksheet.getCell('D1');
277
- cellD1.value = "Violations";
278
- const cellE1 = worksheet.getCell('E1');
279
- cellE1.value = "Needs review";
280
- const cellF1 = worksheet.getCell('F1');
281
- cellF1.value = "Recommendations";
282
- const cellG1 = worksheet.getCell('G1');
283
- cellG1.value = "Archived";
284
- const cellH1 = worksheet.getCell('H1');
285
- cellH1.value = "% elements without violations";
286
- const cellI1 = worksheet.getCell('I1');
287
- cellI1.value = "% elements without violations or items to review";
288
- const cellObjects2 = [cellD1, cellE1, cellF1, cellG1, cellH1, cellI1];
289
- for (let i = 0; i < cellObjects2.length; i++) {
290
- cellObjects2[i].alignment = { vertical: "middle", horizontal: "center", wrapText: true };
291
- if (i == 0 || i == 1 || i == 2 || i == 3) {
292
- cellObjects2[i].font = { name: "Calibri", color: { argb: "FF000000" }, size: 12 };
293
- }
294
- else {
295
- cellObjects2[i].font = { name: "Calibri", color: { argb: "FFFFFFFF" }, size: 12 };
296
- }
297
- // cellObjects2[i].fill = { type: 'pattern', pattern: 'solid', fgColor:{argb:'FFC65911'} };
298
- cellObjects2[i].border = {
299
- top: { style: 'thin', color: { argb: 'FFA6A6A6' } },
300
- left: { style: 'thin', color: { argb: 'FFA6A6A6' } },
301
- bottom: { style: 'thin', color: { argb: 'FFA6A6A6' } },
302
- right: { style: 'thin', color: { argb: 'FFA6A6A6' } }
303
- };
304
- }
305
- cellD1.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFE4AAAF' } };
306
- cellE1.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFF4E08A' } };
307
- cellF1.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF96A9D7' } };
308
- cellG1.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFFFFFFF' } };
309
- cellH1.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF000000' } };
310
- cellI1.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF000000' } };
307
+ const headerStyle = { backgroundColor: "#403151", textColor: "#FFFFFF", fontFamily: "Calibri", fontSize: 12, borderColor: "#A6A6A6", borderStyle: "thin", alignVertical: "center", height: 39 };
308
+ const countHeaderStyle = { ...headerStyle, align: "center", wrap: true };
309
+ const data = [];
310
+ // Row 1: header row
311
+ data.push([
312
+ { value: "Page title", ...headerStyle, align: "left" },
313
+ { value: "Page url", ...headerStyle, align: "left" },
314
+ { value: "Scan label", ...headerStyle, align: "left" },
315
+ { value: "Violations", ...countHeaderStyle, backgroundColor: "#E4AAAF", textColor: "#000000" },
316
+ { value: "Needs review", ...countHeaderStyle, backgroundColor: "#F4E08A", textColor: "#000000" },
317
+ { value: "Recommendations", ...countHeaderStyle, backgroundColor: "#96A9D7", textColor: "#000000" },
318
+ { value: "Archived", ...countHeaderStyle, backgroundColor: "#FFFFFF", textColor: "#000000" },
319
+ { value: "% elements without violations", ...countHeaderStyle },
320
+ { value: "% elements without violations or items to review", ...countHeaderStyle },
321
+ ]);
311
322
  for (const compressedScan of compressedScans) {
312
323
  let storedScan = ReporterManager.uncompressReport(compressedScan);
313
324
  let counts = storedScan.engineReport.summary.counts;
314
- let row = worksheet.addRow([
315
- storedScan.pageTitle,
316
- storedScan.engineReport.summary.URL,
317
- storedScan.label,
318
- counts.violation,
319
- counts.potentialviolation + counts.manual,
320
- counts.recommendation + counts.potentialrecommendation,
321
- counts.ignored,
322
- (100 * (counts.elements - counts.elementsViolation) / counts.elements).toFixed(0),
323
- (100 * (counts.elements - counts.elementsViolationReview) / counts.elements).toFixed(0),
325
+ const textStyle = { fontFamily: "Calibri", fontSize: 12, textColor: "#000000", alignVertical: "center", height: 37, wrap: true };
326
+ const numStyle = { ...textStyle, align: "center", borderColor: "#A6A6A6", borderStyle: "thin" };
327
+ data.push([
328
+ { value: storedScan.pageTitle, ...textStyle, align: "left" },
329
+ { value: storedScan.engineReport.summary.URL, ...textStyle, align: "left" },
330
+ { value: storedScan.label, ...textStyle, align: "left" },
331
+ { value: counts.violation, ...numStyle },
332
+ { value: counts.potentialviolation + counts.manual, ...numStyle },
333
+ { value: counts.recommendation + counts.potentialrecommendation, ...numStyle },
334
+ { value: counts.ignored, ...numStyle },
335
+ { value: counts.elements > 0 ? parseFloat((100 * (counts.elements - counts.elementsViolation) / counts.elements).toFixed(0)) : 0, ...numStyle },
336
+ { value: counts.elements > 0 ? parseFloat((100 * (counts.elements - counts.elementsViolationReview) / counts.elements).toFixed(0)) : 0, ...numStyle },
324
337
  ]);
325
- row.height = 37; // actual height is
326
- for (let i = 1; i < 4; i++) {
327
- row.getCell(i).alignment = { vertical: "middle", horizontal: "left", wrapText: true };
328
- row.getCell(i).font = { name: "Calibri", color: { argb: "00000000" }, size: 12 };
329
- }
330
- for (let i = 4; i < 10; i++) {
331
- row.getCell(i).alignment = { vertical: "middle", horizontal: "center", wrapText: true };
332
- row.getCell(i).font = { name: "Calibri", color: { argb: "00000000" }, size: 12 };
333
- // row.getCell(i).fill = { type: 'pattern', pattern: 'solid', fgColor:{argb:'FFf8cbad'} };
334
- row.getCell(i).border = {
335
- top: { style: 'thin', color: { argb: 'FFA6A6A6' } },
336
- left: { style: 'thin', color: { argb: 'FFA6A6A6' } },
337
- bottom: { style: 'thin', color: { argb: 'FFA6A6A6' } },
338
- right: { style: 'thin', color: { argb: 'FFA6A6A6' } }
339
- };
340
- }
341
- row.commit();
342
338
  }
343
- worksheet.commit();
339
+ return { data, sheet: "Scan summary", columns };
344
340
  }
345
- static buildIssueSummaryLevel(worksheet, fillColor, title, levelCount, levelrowValues) {
346
- // Level 1 Violation title
347
- const level1ViolationRow = worksheet.addRow(["", 0]);
348
- level1ViolationRow.height = 18; // target is 21
349
- const cellA6 = level1ViolationRow.getCell(1);
350
- cellA6.value = ` ${title}`;
351
- cellA6.alignment = { vertical: "middle", horizontal: "left" };
352
- cellA6.font = { name: "Calibri", color: { argb: "FF000000" }, size: 12 };
353
- cellA6.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: fillColor } };
354
- level1ViolationRow.getCell(1).border = {
355
- top: { style: 'thin', color: { argb: 'FFA6A6A6' } },
356
- left: { style: 'thin', color: { argb: 'FFA6A6A6' } },
357
- bottom: { style: 'thin', color: { argb: 'FFA6A6A6' } },
358
- // right: {style:'thin', color: {argb: 'FFA6A6A6'}}
359
- };
360
- const cellB6 = level1ViolationRow.getCell(2);
361
- cellB6.value = levelCount; // total level violations
362
- cellB6.alignment = { vertical: "middle", horizontal: "right" };
363
- cellB6.font = { name: "Calibri", color: { argb: "FF000000" }, size: 12 };
364
- cellB6.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: fillColor } };
365
- level1ViolationRow.getCell(2).border = {
366
- top: { style: 'thin', color: { argb: 'FFA6A6A6' } },
367
- // left: {style:'thin', color: {argb: 'FFA6A6A6'}},
368
- bottom: { style: 'thin', color: { argb: 'FFA6A6A6' } },
369
- right: { style: 'thin', color: { argb: 'FFA6A6A6' } }
370
- };
371
- // Level 1 Violation Rows
372
- // build rows
373
- let rowArray = [];
374
- // let row:any =[];
341
+ static buildIssueSummaryLevelRows(fillColor, textColor, title, levelCount, levelrowValues) {
342
+ const rows = [];
343
+ // Level type title row (e.g. "Violations")
344
+ rows.push([
345
+ { value: ` ${title}`, backgroundColor: fillColor, textColor, fontFamily: "Calibri", fontSize: 12, borderColor: "#A6A6A6", borderStyle: "thin", alignVertical: "center", align: "left", height: 18 },
346
+ { value: levelCount, backgroundColor: fillColor, textColor, fontFamily: "Calibri", fontSize: 12, borderColor: "#A6A6A6", borderStyle: "thin", alignVertical: "center", align: "right" },
347
+ ]);
348
+ // Individual issue message rows, sorted descending by count
349
+ const rowArray = [];
375
350
  for (const property in levelrowValues) {
376
- let row = [" " + `${property}`, parseInt(`${levelrowValues[property]}`)
377
- ];
378
- rowArray.push(row);
351
+ rowArray.push([" " + property, parseInt(`${levelrowValues[property]}`)]);
379
352
  }
380
- // sort array according to count
381
- rowArray.sort((a, b) => (a[1] < b[1]) ? 1 : -1);
382
- // add array of rows
383
- for (const rowInfo of rowArray) {
384
- const row = worksheet.addRow(rowInfo);
385
- row.height = 14;
386
- row.getCell(1).alignment = { vertical: "middle", horizontal: "left" };
387
- row.getCell(2).alignment = { vertical: "middle", horizontal: "right" };
388
- row.font = { name: "Calibri", color: { argb: "FF000000" }, size: 12 };
389
- // row.getCell(1).fill = { type: 'pattern', pattern: 'solid', fgColor:{argb:'FFf8cbad'} };
390
- // row.getCell(2).fill = { type: 'pattern', pattern: 'solid', fgColor:{argb:'FFf8cbad'} };
391
- row.getCell(1).border = {
392
- top: { style: 'thin', color: { argb: 'FFA6A6A6' } },
393
- left: { style: 'thin', color: { argb: 'FFA6A6A6' } },
394
- bottom: { style: 'thin', color: { argb: 'FFA6A6A6' } },
395
- // right: {style:'thin', color: {argb: 'FFA6A6A6'}}
396
- };
397
- row.getCell(2).border = {
398
- top: { style: 'thin', color: { argb: 'FFA6A6A6' } },
399
- // left: {style:'thin', color: {argb: 'FFA6A6A6'}},
400
- bottom: { style: 'thin', color: { argb: 'FFA6A6A6' } },
401
- right: { style: 'thin', color: { argb: 'FFA6A6A6' } }
402
- };
403
- row.commit();
353
+ rowArray.sort((a, b) => b[1] - a[1]);
354
+ for (const [msg, count] of rowArray) {
355
+ rows.push([
356
+ { value: msg, fontFamily: "Calibri", fontSize: 12, textColor: "#000000", borderColor: "#A6A6A6", borderStyle: "thin", alignVertical: "center", align: "left", height: 14 },
357
+ { value: count, fontFamily: "Calibri", fontSize: 12, textColor: "#000000", borderColor: "#A6A6A6", borderStyle: "thin", alignVertical: "center", align: "right" },
358
+ ]);
404
359
  }
360
+ return rows;
405
361
  }
406
- static buildIssueSummaryTKLevel(worksheet, title, levelCounts, levelVrowValues, levelNRrowValues, levelRrowValues, levelArowValues) {
407
- /////////////////////////////
408
- // build Level title
409
- /////////////////////////////
410
- const level1Row = worksheet.addRow(["", 0]);
411
- level1Row.height = 27; // actual is 36
412
- const cellA5 = level1Row.getCell(1);
413
- cellA5.value = title;
414
- cellA5.alignment = { vertical: "middle", horizontal: "left" };
415
- cellA5.font = { name: "Calibri", color: { argb: "FFFFFFFF" }, size: 16 };
416
- cellA5.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF403151' } };
417
- const cellB5 = level1Row.getCell(2);
418
- cellB5.value = levelCounts[0]; // total Level 1 issues
419
- cellB5.alignment = { vertical: "middle", horizontal: "right" };
420
- cellB5.font = { name: "Calibri", color: { argb: "FFFFFFFF" }, size: 16 };
421
- cellB5.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF403151' } };
422
- ACReporterXLSX.buildIssueSummaryLevel(worksheet, "FFE4AAAF", "Violation", levelCounts[1], levelVrowValues);
423
- ACReporterXLSX.buildIssueSummaryLevel(worksheet, "FFF4E08A", "Needs review", levelCounts[2], levelNRrowValues);
424
- ACReporterXLSX.buildIssueSummaryLevel(worksheet, "FF96A9D7", "Recommendation", levelCounts[3], levelRrowValues);
362
+ static buildIssueSummaryTKLevelRows(title, levelCounts, levelVrowValues, levelNRrowValues, levelRrowValues, levelArowValues) {
363
+ const rows = [];
364
+ // Level title row (e.g. "Level 1 - the most essential...")
365
+ rows.push([
366
+ { value: title, backgroundColor: "#403151", textColor: "#FFFFFF", fontFamily: "Calibri", fontSize: 16, alignVertical: "center", align: "left", height: 27 },
367
+ { value: levelCounts[0], backgroundColor: "#403151", textColor: "#FFFFFF", fontFamily: "Calibri", fontSize: 16, alignVertical: "center", align: "right" },
368
+ ]);
369
+ rows.push(...ACReporterXLSX.buildIssueSummaryLevelRows("#E4AAAF", "#000000", "Violation", levelCounts[1], levelVrowValues));
370
+ rows.push(...ACReporterXLSX.buildIssueSummaryLevelRows("#F4E08A", "#000000", "Needs review", levelCounts[2], levelNRrowValues));
371
+ rows.push(...ACReporterXLSX.buildIssueSummaryLevelRows("#96A9D7", "#000000", "Recommendation", levelCounts[3], levelRrowValues));
425
372
  if (levelCounts[4] > 0) {
426
- ACReporterXLSX.buildIssueSummaryLevel(worksheet, "FFCCCCCC", "Archived", levelCounts[4], levelArowValues);
373
+ rows.push(...ACReporterXLSX.buildIssueSummaryLevelRows("#CCCCCC", "#000000", "Archived", levelCounts[4], levelArowValues));
427
374
  }
375
+ return rows;
428
376
  }
429
- static createIssueSummarySheet(config, policyInfo, compressedScans, workbook) {
377
+ static createIssueSummarySheet(config, policyInfo, compressedScans) {
430
378
  let violations = 0;
431
379
  let needsReviews = 0;
432
380
  let recommendations = 0;
@@ -441,8 +389,7 @@ export class ACReporterXLSX {
441
389
  archive += counts.ignored;
442
390
  }
443
391
  totalIssues = violations + needsReviews + recommendations;
444
- // counts
445
- let level1Counts = [0, 0, 0, 0, 0]; // level 1 total issues, violations, needs reviews, recommendations
392
+ let level1Counts = [0, 0, 0, 0, 0];
446
393
  let level2Counts = [0, 0, 0, 0, 0];
447
394
  let level3Counts = [0, 0, 0, 0, 0];
448
395
  let level4Counts = [0, 0, 0, 0, 0];
@@ -466,10 +413,7 @@ export class ACReporterXLSX {
466
413
  let scan = ReporterManager.uncompressReport(compressedScan);
467
414
  for (const issue of scan.engineReport.results) {
468
415
  if (!(issue.ruleId in policyInfo)) {
469
- policyInfo[issue.ruleId] = {
470
- tkLevels: [],
471
- cps: []
472
- };
416
+ policyInfo[issue.ruleId] = { tkLevels: [], cps: [] };
473
417
  }
474
418
  let levelCounts, levelV, levelNR, levelR, levelA;
475
419
  const issuePolicyInfo = policyInfo[issue.ruleId];
@@ -523,111 +467,69 @@ export class ACReporterXLSX {
523
467
  }
524
468
  }
525
469
  // @ts-ignore
526
- let level1VrowValues = this.countDuplicatesInArray(level1V); // note this returns an object
470
+ let level1VrowValues = ACReporterXLSX.countDuplicatesInArray(level1V);
527
471
  // @ts-ignore
528
- let level1NRrowValues = this.countDuplicatesInArray(level1NR);
472
+ let level1NRrowValues = ACReporterXLSX.countDuplicatesInArray(level1NR);
529
473
  // @ts-ignore
530
- let level1RrowValues = this.countDuplicatesInArray(level1R);
474
+ let level1RrowValues = ACReporterXLSX.countDuplicatesInArray(level1R);
531
475
  // @ts-ignore
532
- let level1ArowValues = this.countDuplicatesInArray(level1A);
476
+ let level1ArowValues = ACReporterXLSX.countDuplicatesInArray(level1A);
533
477
  // @ts-ignore
534
- let level2VrowValues = this.countDuplicatesInArray(level2V); // note this returns an object
478
+ let level2VrowValues = ACReporterXLSX.countDuplicatesInArray(level2V);
535
479
  // @ts-ignore
536
- let level2NRrowValues = this.countDuplicatesInArray(level2NR);
480
+ let level2NRrowValues = ACReporterXLSX.countDuplicatesInArray(level2NR);
537
481
  // @ts-ignore
538
- let level2RrowValues = this.countDuplicatesInArray(level2R);
482
+ let level2RrowValues = ACReporterXLSX.countDuplicatesInArray(level2R);
539
483
  // @ts-ignore
540
- let level2ArowValues = this.countDuplicatesInArray(level2A);
484
+ let level2ArowValues = ACReporterXLSX.countDuplicatesInArray(level2A);
541
485
  // @ts-ignore
542
- let level3VrowValues = this.countDuplicatesInArray(level3V); // note this returns an object
486
+ let level3VrowValues = ACReporterXLSX.countDuplicatesInArray(level3V);
543
487
  // @ts-ignore
544
- let level3NRrowValues = this.countDuplicatesInArray(level3NR);
488
+ let level3NRrowValues = ACReporterXLSX.countDuplicatesInArray(level3NR);
545
489
  // @ts-ignore
546
- let level3RrowValues = this.countDuplicatesInArray(level3R);
490
+ let level3RrowValues = ACReporterXLSX.countDuplicatesInArray(level3R);
547
491
  // @ts-ignore
548
- let level3ArowValues = this.countDuplicatesInArray(level3A);
492
+ let level3ArowValues = ACReporterXLSX.countDuplicatesInArray(level3A);
549
493
  // @ts-ignore
550
- let level4VrowValues = this.countDuplicatesInArray(level4V); // note this returns an object
494
+ let level4VrowValues = ACReporterXLSX.countDuplicatesInArray(level4V);
551
495
  // @ts-ignore
552
- let level4NRrowValues = this.countDuplicatesInArray(level4NR);
496
+ let level4NRrowValues = ACReporterXLSX.countDuplicatesInArray(level4NR);
553
497
  // @ts-ignore
554
- let level4RrowValues = this.countDuplicatesInArray(level4R);
498
+ let level4RrowValues = ACReporterXLSX.countDuplicatesInArray(level4R);
555
499
  // @ts-ignore
556
- let level4ArowValues = this.countDuplicatesInArray(level4A);
557
- const worksheet = workbook.addWorksheet("Issue summary");
558
- // Approach:
559
- // 1. sort by levels
560
- // 2. for each level sort by V, NR and R
561
- // 3. for each V, NR, and R in a level get issue dup counts
562
- // 4. build the rows
563
- // build Issue summary title
564
- worksheet.mergeCells('A1', "B1");
565
- const titleRow = worksheet.getRow(1);
566
- titleRow.height = 27; // actual is 36
567
- const cellA1 = worksheet.getCell('A1');
568
- cellA1.value = "Issue summary";
569
- cellA1.alignment = { vertical: "middle", horizontal: "left" };
570
- cellA1.font = { name: "Calibri", color: { argb: "FFFFFFFF" }, size: 16 };
571
- cellA1.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF403151' } };
572
- const colWidthData = [
573
- { col: 'A', width: 155.51 }, // note .84 added to actual width
574
- { col: 'B', width: 21.16 },
500
+ let level4ArowValues = ACReporterXLSX.countDuplicatesInArray(level4A);
501
+ const columns = [
502
+ { width: 155.51 },
503
+ { width: 21.16 },
575
504
  ];
576
- for (let i = 0; i < 2; i++) {
577
- worksheet.getColumn(colWidthData[i].col).width = colWidthData[i].width;
578
- }
579
- // build Description title
580
- worksheet.mergeCells('A2', "B2");
581
- const descriptionRow = worksheet.getRow(2);
582
- descriptionRow.height = 20.25; // actual is 27
583
- const cellA2 = worksheet.getCell("A2");
584
- cellA2.value = " In the IBM Equal Access Toolkit, issues are divided into three levels (1-3). Tackle the levels in order to address some of the most impactful issues first.";
585
- cellA2.alignment = { vertical: "middle", horizontal: "left" };
586
- cellA2.font = { name: "Calibri", color: { argb: "FF000000" }, size: 12 };
587
- // cellA2.fill = { type: 'pattern', pattern: 'solid', fgColor:{argb:'FFCCC0DA'} };
588
- // build Total issues found: title
589
- // worksheet.mergeCells('A3', "B3");
590
- const totalIssuesRow = worksheet.getRow(3);
591
- totalIssuesRow.height = 27; // actual is 36
592
- const cellA3 = worksheet.getCell("A3");
593
- cellA3.value = "Total issues found:";
594
- cellA3.alignment = { vertical: "middle", horizontal: "left" };
595
- cellA3.font = { name: "Calibri", color: { argb: "FFFFFFFF" }, size: 16 };
596
- cellA3.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF000000' } };
597
- const cellB3 = worksheet.getCell("B3");
598
- cellB3.value = totalIssues;
599
- cellB3.alignment = { vertical: "middle", horizontal: "right" };
600
- cellB3.font = { name: "Calibri", color: { argb: "FFFFFFFF" }, size: 16 };
601
- cellB3.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF000000' } };
602
- // build Number of issues title
603
- const numberOfIssuesRow = worksheet.getRow(4);
604
- numberOfIssuesRow.height = 20.25; // actual is 27
605
- const cellA4 = worksheet.getCell("A4");
606
- // no value
607
- cellA4.alignment = { vertical: "middle", horizontal: "left" };
608
- cellA4.border = {
609
- top: { style: 'thin', color: { argb: 'FFA6A6A6' } },
610
- left: { style: 'thin', color: { argb: 'FFA6A6A6' } },
611
- bottom: { style: 'thin', color: { argb: 'FFA6A6A6' } },
612
- right: { style: 'thin', color: { argb: 'FFFFFFFF' } }
613
- };
614
- const cellB4 = worksheet.getCell("B4");
615
- cellB4.value = "Number of issues";
616
- cellB4.alignment = { vertical: "middle", horizontal: "right" };
617
- cellB4.font = { name: "Calibri", color: { argb: "FF000000" }, size: 12 };
618
- cellB4.border = {
619
- top: { style: 'thin', color: { argb: 'FFA6A6A6' } },
620
- left: { style: 'thin', color: { argb: 'FFFFFFFF' } },
621
- bottom: { style: 'thin', color: { argb: 'FFA6A6A6' } },
622
- right: { style: 'thin', color: { argb: 'FFA6A6A6' } }
623
- };
624
- ACReporterXLSX.buildIssueSummaryTKLevel(worksheet, "Level 1 - the most essential issues to address", level1Counts, level1VrowValues, level1NRrowValues, level1RrowValues, level1ArowValues);
625
- ACReporterXLSX.buildIssueSummaryTKLevel(worksheet, "Level 2 - the next most important issues", level2Counts, level2VrowValues, level2NRrowValues, level2RrowValues, level2ArowValues);
626
- ACReporterXLSX.buildIssueSummaryTKLevel(worksheet, "Level 3 - necessary to meet requirements", level3Counts, level3VrowValues, level3NRrowValues, level3RrowValues, level3ArowValues);
627
- ACReporterXLSX.buildIssueSummaryTKLevel(worksheet, "Level 4 - further recommended improvements to accessibility", level4Counts, level4VrowValues, level4NRrowValues, level4RrowValues, level4ArowValues);
628
- worksheet.commit();
505
+ const data = [];
506
+ // Row 1: "Issue summary" title spanning 2 cols
507
+ data.push([
508
+ { value: "Issue summary", backgroundColor: "#403151", textColor: "#FFFFFF", fontFamily: "Calibri", fontSize: 16, alignVertical: "center", align: "left", height: 27, columnSpan: 2 },
509
+ null
510
+ ]);
511
+ // Row 2: description spanning 2 cols
512
+ data.push([
513
+ { value: " In the IBM Equal Access Toolkit, issues are divided into three levels (1-3). Tackle the levels in order to address some of the most impactful issues first.", fontFamily: "Calibri", fontSize: 12, textColor: "#000000", alignVertical: "center", align: "left", height: 20, columnSpan: 2 },
514
+ null
515
+ ]);
516
+ // Row 3: "Total issues found:" with value
517
+ data.push([
518
+ { value: "Total issues found:", backgroundColor: "#000000", textColor: "#FFFFFF", fontFamily: "Calibri", fontSize: 16, alignVertical: "center", align: "left", height: 27 },
519
+ { value: totalIssues, backgroundColor: "#000000", textColor: "#FFFFFF", fontFamily: "Calibri", fontSize: 16, alignVertical: "center", align: "right" },
520
+ ]);
521
+ // Row 4: "Number of issues" label
522
+ data.push([
523
+ { value: null, borderColor: "#A6A6A6", borderStyle: "thin", height: 20 },
524
+ { value: "Number of issues", fontFamily: "Calibri", fontSize: 12, textColor: "#000000", borderColor: "#A6A6A6", borderStyle: "thin", alignVertical: "center", align: "right" },
525
+ ]);
526
+ data.push(...ACReporterXLSX.buildIssueSummaryTKLevelRows("Level 1 - the most essential issues to address", level1Counts, level1VrowValues, level1NRrowValues, level1RrowValues, level1ArowValues));
527
+ data.push(...ACReporterXLSX.buildIssueSummaryTKLevelRows("Level 2 - the next most important issues", level2Counts, level2VrowValues, level2NRrowValues, level2RrowValues, level2ArowValues));
528
+ data.push(...ACReporterXLSX.buildIssueSummaryTKLevelRows("Level 3 - necessary to meet requirements", level3Counts, level3VrowValues, level3NRrowValues, level3RrowValues, level3ArowValues));
529
+ data.push(...ACReporterXLSX.buildIssueSummaryTKLevelRows("Level 4 - further recommended improvements to accessibility", level4Counts, level4VrowValues, level4NRrowValues, level4RrowValues, level4ArowValues));
530
+ return { data, sheet: "Issue summary", columns };
629
531
  }
630
- static createIssuesSheet(config, policyInfo, compressedScans, workbook) {
532
+ static createIssuesSheet(config, policyInfo, compressedScans) {
631
533
  const valueMap = {
632
534
  "VIOLATION": {
633
535
  "POTENTIAL": "Needs review",
@@ -648,17 +550,48 @@ export class ACReporterXLSX {
648
550
  "MANUAL": "Recommendation"
649
551
  }
650
552
  };
651
- const worksheet = workbook.addWorksheet("Issues");
652
- // build rows
653
- let rowArray = [];
553
+ const columns = [
554
+ { width: 18.0 },
555
+ { width: 20.5 },
556
+ { width: 21.0 },
557
+ { width: 18.5 },
558
+ { width: 17.0 },
559
+ { width: 17.17 },
560
+ { width: 17.17 },
561
+ { width: 17.17 },
562
+ { width: 17.17 },
563
+ { width: 17.17 },
564
+ { width: 14.00 },
565
+ { width: 17.17 },
566
+ { width: 43.00 },
567
+ { width: 17.17 },
568
+ ];
569
+ // Header and data row colors are intentionally omitted — the Excel table
570
+ // (TableStyleMedium2 + showRowStripes) applied by injectTableIntoFile() provides all coloring.
571
+ const hStyle = { fontFamily: "Calibri", fontSize: 12, align: "center", alignVertical: "center", wrap: true, height: 24 };
572
+ const data = [];
573
+ // Header row
574
+ data.push([
575
+ { value: "Page title", ...hStyle },
576
+ { value: "Page URL", ...hStyle },
577
+ { value: "Scan label", ...hStyle },
578
+ { value: "Issue ID", ...hStyle },
579
+ { value: "Issue type", ...hStyle },
580
+ { value: "Toolkit level", ...hStyle },
581
+ { value: "Checkpoint", ...hStyle },
582
+ { value: "WCAG level", ...hStyle },
583
+ { value: "Rule", ...hStyle },
584
+ { value: "Issue", ...hStyle },
585
+ { value: "Element", ...hStyle },
586
+ { value: "Code", ...hStyle },
587
+ { value: "Xpath", ...hStyle },
588
+ { value: "Help", ...hStyle },
589
+ ]);
654
590
  for (const compressedScan of compressedScans) {
655
591
  let storedScan = ReporterManager.uncompressReport(compressedScan);
656
592
  for (const item of storedScan.engineReport.results) {
657
593
  if (!(item.ruleId in policyInfo)) {
658
- policyInfo[item.ruleId] = {
659
- tkLevels: [],
660
- cps: []
661
- };
594
+ policyInfo[item.ruleId] = { tkLevels: [], cps: [] };
662
595
  }
663
596
  let polInfo = policyInfo[item.ruleId];
664
597
  let cps = polInfo.cps.filter(cp => {
@@ -669,195 +602,37 @@ export class ACReporterXLSX {
669
602
  wcagLevels.sort();
670
603
  let cpStrs = dropDupes(cps.map(cp => `${cp.num} ${cp.name}`));
671
604
  cpStrs.sort();
672
- let row = [
673
- storedScan.pageTitle,
674
- storedScan.engineReport.summary.URL,
675
- storedScan.label,
676
- this.stringHash(item.ruleId + item.path.dom),
677
- `${valueMap[item.value[0]][item.value[1]]}${item.ignored ? ` (Archived)` : ``}`,
678
- polInfo.tkLevels.join(", "),
679
- cpStrs.join("; "),
680
- wcagLevels.join(", "),
681
- item.ruleId,
682
- item.message.substring(0, 32767), //max ength for MS Excel 32767 characters
683
- this.get_element(item.snippet),
684
- item.snippet.substring(0, 32767),
685
- item.path.dom,
686
- item.help,
687
- item.source
688
- // engine_end_point + '/tools/help/' + item.ruleId
689
- ];
690
- // let row = [myStoredData[i][0], myStoredData[i][1], storedScans[j].label,
691
- // myStoredData[i][3], myStoredData[i][4], Number.isNaN(myStoredData[i][5]) ? "n/a" : myStoredData[i][5],
692
- // myStoredData[i][6], Number.isNaN(myStoredData[i][5]) ? "n/a" : myStoredData[i][7], myStoredData[i][8],
693
- // myStoredData[i][9], myStoredData[i][10], myStoredData[i][11],
694
- // myStoredData[i][12], myStoredData[i][13]
695
- // ];
696
- rowArray.push(row);
697
- }
698
- }
699
- // column widths
700
- const colWidthData = [
701
- { col: 'A', width: 18.0, alignment: { vertical: "middle", horizontal: "left" } },
702
- { col: 'B', width: 20.5, alignment: { vertical: "middle", horizontal: "left" } },
703
- { col: 'C', width: 21.0, alignment: { vertical: "middle", horizontal: "center" } },
704
- { col: 'D', width: 18.5, alignment: { vertical: "middle", horizontal: "left" } },
705
- { col: 'E', width: 17.0, alignment: { vertical: "middle", horizontal: "center" } },
706
- { col: 'F', width: 17.17, alignment: { vertical: "middle", horizontal: "center" } },
707
- { col: 'G', width: 17.17, alignment: { vertical: "middle", horizontal: "left" } },
708
- { col: 'H', width: 17.17, alignment: { vertical: "middle", horizontal: "center" } },
709
- { col: 'I', width: 17.17, alignment: { vertical: "middle", horizontal: "left" } },
710
- { col: 'J', width: 17.17, alignment: { vertical: "middle", horizontal: "left" } },
711
- { col: 'K', width: 14.00, alignment: { vertical: "middle", horizontal: "center" } },
712
- { col: 'L', width: 17.17, alignment: { vertical: "middle", horizontal: "left" } },
713
- { col: 'M', width: 43.00, alignment: { vertical: "middle", horizontal: "left" } },
714
- { col: 'N', width: 17.17, alignment: { vertical: "middle", horizontal: "fill" } },
715
- ];
716
- for (let i = 0; i < 14; i++) {
717
- worksheet.getColumn(colWidthData[i].col).width = colWidthData[i].width;
718
- worksheet.getColumn(colWidthData[i].col).alignment = colWidthData[i].alignment;
719
- }
720
- // add table to a sheet
721
- let headRow = worksheet.addRow([
722
- "Page title",
723
- "Page URL",
724
- "Scan label",
725
- "Issue ID",
726
- "Issue type",
727
- "Toolkit level",
728
- "Checkpoint",
729
- "WCAG level",
730
- "Rule",
731
- "Issue",
732
- "Element",
733
- "Code",
734
- "Xpath",
735
- "Help",
736
- ""
737
- ]);
738
- // set font and alignment for the header cells
739
- for (let i = 1; i < 15; i++) {
740
- headRow.getCell(i).alignment = { vertical: "middle", horizontal: "center", wrapText: true };
741
- headRow.getCell(i).font = { name: "Calibri", color: { argb: "FFFFFFFF" }, size: 12 };
742
- headRow.getCell(i).fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF403151' } };
743
- headRow.getCell(i).border = {
744
- top: { style: 'thin', color: { argb: 'FFA6A6A6' } },
745
- left: { style: 'thin', color: { argb: 'FFA6A6A6' } },
746
- bottom: { style: 'thin', color: { argb: 'FFA6A6A6' } },
747
- right: { style: 'thin', color: { argb: 'FFA6A6A6' } }
748
- };
749
- }
750
- // height for header row
751
- headRow.height = 24;
752
- headRow.commit();
753
- for (const rowInfo of rowArray) {
754
- const row = worksheet.addRow(rowInfo);
755
- row.height = 14;
756
- for (let j = 1; j <= 14; j++) {
757
- row.getCell(j).border = {
758
- top: { style: 'thin', color: { argb: 'FFA6A6A6' } },
759
- left: { style: 'thin', color: { argb: 'FFA6A6A6' } },
760
- bottom: { style: 'thin', color: { argb: 'FFA6A6A6' } },
761
- right: { style: 'thin', color: { argb: 'FFA6A6A6' } }
762
- };
605
+ const dStyle = { fontFamily: "Calibri", fontSize: 12, height: 14 };
606
+ data.push([
607
+ { value: storedScan.pageTitle, ...dStyle, align: "left", alignVertical: "center" },
608
+ { value: storedScan.engineReport.summary.URL, ...dStyle, align: "left", alignVertical: "center" },
609
+ { value: storedScan.label, ...dStyle, align: "center", alignVertical: "center" },
610
+ { value: ACReporterXLSX.stringHash(item.ruleId + item.path.dom), ...dStyle, align: "left", alignVertical: "center" },
611
+ { value: `${valueMap[item.value[0]][item.value[1]]}${item.ignored ? ` (Archived)` : ``}`, ...dStyle, align: "center", alignVertical: "center" },
612
+ { value: polInfo.tkLevels.join(", "), ...dStyle, align: "center", alignVertical: "center" },
613
+ { value: cpStrs.join("; "), ...dStyle, align: "left", alignVertical: "center" },
614
+ { value: wcagLevels.join(", "), ...dStyle, align: "center", alignVertical: "center" },
615
+ { value: item.ruleId, ...dStyle, align: "left", alignVertical: "center" },
616
+ { value: item.message.substring(0, 32767), ...dStyle, align: "left", alignVertical: "center" },
617
+ { value: ACReporterXLSX.get_element(item.snippet), ...dStyle, align: "center", alignVertical: "center" },
618
+ { value: item.snippet.substring(0, 32767), ...dStyle, align: "left", alignVertical: "center" },
619
+ { value: item.path.dom, ...dStyle, align: "left", alignVertical: "center" },
620
+ { value: item.help, ...dStyle, align: "left", alignVertical: "center" },
621
+ ]);
763
622
  }
764
- row.commit();
765
623
  }
766
- for (const key in worksheet) {
767
- if (typeof worksheet[key] === "function") {
768
- console.log(key);
769
- }
770
- }
771
- // worksheet.addTable({
772
- // name: 'MyTable',
773
- // ref: 'A1',
774
- // headerRow: true,
775
- // // totalsRow: true,
776
- // style: {
777
- // theme: 'TableStyleMedium2',
778
- // showRowStripes: true,
779
- // },
780
- // columns: [
781
- // { name: 'Page title', filterButton: true },
782
- // { name: 'Page URL', filterButton: true },
783
- // { name: 'Scan label', filterButton: true },
784
- // { name: 'Issue ID', filterButton: true },
785
- // { name: 'Issue type', filterButton: true },
786
- // { name: 'Toolkit level', filterButton: true },
787
- // { name: 'Checkpoint', filterButton: true },
788
- // { name: 'WCAG level', filterButton: true },
789
- // { name: 'Rule', filterButton: true },
790
- // { name: 'Issue', filterButton: true },
791
- // { name: 'Element', filterButton: true },
792
- // { name: 'Code', filterButton: true },
793
- // { name: 'Xpath', filterButton: true },
794
- // { name: 'Help', filterButton: true },
795
- // ],
796
- // rows: rowArray
797
- // });
798
- // for (let i = 2; i <= rowArray.length + 1; i++) {
799
- // worksheet.getRow(i).height = 14;
800
- // for (let j = 1; j <= 14; j++) {
801
- // worksheet.getRow(i).getCell(j).border = {
802
- // top: { style: 'thin', color: { argb: 'FFA6A6A6' } },
803
- // left: { style: 'thin', color: { argb: 'FFA6A6A6' } },
804
- // bottom: { style: 'thin', color: { argb: 'FFA6A6A6' } },
805
- // right: { style: 'thin', color: { argb: 'FFA6A6A6' } }
806
- // }
807
- // }
808
- // worksheet.getRow(i).commit();
809
- // }
810
- worksheet.commit();
624
+ return { data, sheet: "Issues", columns, stickyRowsCount: 1, _issueRowCount: data.length };
811
625
  }
812
- static createDefinitionsSheet(workbook) {
813
- const worksheet = workbook.addWorksheet("Definition of fields");
814
- // "Definition of fields" title
815
- worksheet.mergeCells('A1', "B1");
816
- const titleRow = worksheet.getRow(1);
817
- titleRow.height = 36; // actual is 48
818
- titleRow.getCell(1).value = "Definition of fields";
819
- titleRow.getCell(1).alignment = { vertical: "middle", horizontal: "left" };
820
- titleRow.getCell(1).font = { name: "Calibri", color: { argb: "FFFFFFFF" }, size: "20" };
821
- titleRow.getCell(1).fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF403151' } };
822
- const colWidthData = [
823
- { col: 'A', width: '41.51' }, // note .84 added to actual width
824
- { col: 'B', width: '119.51' },
626
+ static createDefinitionsSheet() {
627
+ const columns = [
628
+ { width: 41.51 },
629
+ { width: 119.51 },
825
630
  ];
826
- for (let i = 0; i < 2; i++) {
827
- worksheet.getColumn(colWidthData[i].col).width = colWidthData[i].width;
828
- }
829
- // blank row
830
- worksheet.mergeCells('A2', "B2");
831
- const blankRow = worksheet.getRow(2);
832
- blankRow.height = 12; // actual is 16
833
- // "Scan summary and Issue summary" title
834
- worksheet.mergeCells('A3', "B3");
835
- const summaryRow = worksheet.getRow(3);
836
- summaryRow.height = 20; // actual is 26.75
837
- summaryRow.getCell(1).value = "Scan summary and Issue summary";
838
- summaryRow.getCell(1).alignment = { vertical: "middle", horizontal: "left" };
839
- summaryRow.getCell(1).font = { name: "Calibri", color: { argb: "FFFFFFFF" }, size: 16 };
840
- summaryRow.getCell(1).fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF403151' } };
841
- // row 4 Field / Definition
842
- const row4 = worksheet.getRow(4);
843
- row4.height = 16; // actual is
844
- row4.getCell(1).value = "Field";
845
- row4.getCell(2).value = "Definition";
846
- row4.getCell(1).alignment = row4.getCell(2).alignment = { vertical: "middle", horizontal: "left" };
847
- row4.getCell(1).font = row4.getCell(2).font = { name: "Calibri", color: { argb: "FF000000" }, size: 16 };
848
- row4.getCell(1).fill = row4.getCell(2).fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFCCC0DA' } };
849
- row4.getCell(1).border = row4.getCell(2).border = {
850
- top: { style: 'thin', color: { argb: 'FFA6A6A6' } },
851
- left: { style: 'thin', color: { argb: 'FFA6A6A6' } },
852
- bottom: { style: 'thin', color: { argb: 'FFA6A6A6' } },
853
- right: { style: 'thin', color: { argb: 'FFA6A6A6' } }
854
- };
855
- // rows 5-13
856
- // set row height for rows 5-13
857
- for (let i = 5; i < 14; i++) {
858
- worksheet.getRow(i).height = 12; // results in a row height of 16
859
- }
860
- let rowData = [
631
+ const titleStyle = { backgroundColor: "#403151", textColor: "#FFFFFF", fontFamily: "Calibri", fontSize: 20, alignVertical: "center", align: "left" };
632
+ const sectionStyle = { backgroundColor: "#403151", textColor: "#FFFFFF", fontFamily: "Calibri", fontSize: 16, alignVertical: "center", align: "left" };
633
+ const subHeaderStyle = { backgroundColor: "#CCC0DA", textColor: "#000000", fontFamily: "Calibri", fontSize: 16, borderColor: "#A6A6A6", borderStyle: "thin", alignVertical: "center", align: "left" };
634
+ const dataStyle = { fontFamily: "Calibri", fontSize: 12, textColor: "#000000", align: "left", height: 12 };
635
+ const summaryRowData = [
861
636
  { key1: 'Page', key2: 'Identifies the page or html file that was scanned.' },
862
637
  { key1: 'Scan label', key2: 'Label for the scan. Default values can be edited in the Accessibility Checker before saving this report, or programmatically assigned in automated testing.' },
863
638
  { key1: 'Violations', key2: 'Accessibility failures that need to be corrected.' },
@@ -867,43 +642,7 @@ export class ACReporterXLSX {
867
642
  { key1: '% elements without violations or items to review', key2: 'Percentage of elements on the page that had no violations found and no items to review.' },
868
643
  { key1: 'Level 1,2,3', key2: 'Priority level defined by the IBM Equal Access Toolkit. See https://www.ibm.com/able/toolkit/plan/overview#pace-of-completion for details.' }
869
644
  ];
870
- for (let i = 5; i < rowData.length + 5; i++) {
871
- worksheet.getRow(i).getCell(1).font = worksheet.getRow(i).getCell(2).font = { name: "Calibri", color: { argb: "FF000000" }, size: 12 };
872
- worksheet.getRow(i).getCell(1).alignment = worksheet.getRow(i).getCell(2).alignment = { horizontal: "left" };
873
- }
874
- for (let i = 5; i < rowData.length + 5; i++) {
875
- worksheet.getRow(i).getCell(1).value = rowData[i - 5].key1;
876
- worksheet.getRow(i).getCell(2).value = rowData[i - 5].key2;
877
- }
878
- // "Scan summary and Issue summary" title
879
- worksheet.mergeCells('A14', "B14");
880
- const issuesRow = worksheet.getRow(14);
881
- issuesRow.height = 20; // actual is 26.75
882
- issuesRow.getCell(1).value = "Issues";
883
- issuesRow.getCell(1).alignment = { vertical: "middle", horizontal: "left" };
884
- issuesRow.getCell(1).font = { name: "Calibri", color: { argb: "FFFFFFFF" }, size: 16 };
885
- issuesRow.getCell(1).fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF403151' } };
886
- // row 15 Field / Definition
887
- const row15 = worksheet.getRow(15);
888
- row15.height = 16; // actual is
889
- row15.getCell(1).value = "Field";
890
- row15.getCell(2).value = "Definition";
891
- row15.getCell(1).alignment = row15.getCell(2).alignment = { vertical: "middle", horizontal: "left" };
892
- row15.getCell(1).font = row15.getCell(2).font = { name: "Calibri", color: { argb: "FF000000" }, size: 16 };
893
- row15.getCell(1).fill = row15.getCell(2).fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFCCC0DA' } };
894
- row15.getCell(1).border = row15.getCell(2).border = {
895
- top: { style: 'thin', color: { argb: 'FFA6A6A6' } },
896
- left: { style: 'thin', color: { argb: 'FFA6A6A6' } },
897
- bottom: { style: 'thin', color: { argb: 'FFA6A6A6' } },
898
- right: { style: 'thin', color: { argb: 'FFA6A6A6' } }
899
- };
900
- // rows 16-28
901
- // set row height for rows 16-28
902
- for (let i = 16; i < 29; i++) {
903
- worksheet.getRow(i).height = 12; // results in a row height of 16
904
- }
905
- rowData = [];
906
- rowData = [
645
+ const issuesRowData = [
907
646
  { key1: 'Page', key2: 'Identifies the page or html file that was scanned.' },
908
647
  { key1: 'Scan label', key2: 'Label for the scan. Default values can be edited in the Accessibility Checker before saving this report, or programmatically assigned in automated testing.' },
909
648
  { key1: 'Issue ID', key2: 'Identifier for this issue within this page. Rescanning the same page will produce the same issue ID. ' },
@@ -918,19 +657,54 @@ export class ACReporterXLSX {
918
657
  { key1: 'Xpath', key2: 'Xpath of the HTML element where the issue is found.' },
919
658
  { key1: 'Help', key2: 'Link to a more detailed description of the issue and suggested solutions.' },
920
659
  ];
921
- for (let i = 16; i < 29; i++) {
922
- worksheet.getRow(i).getCell(1).font = worksheet.getRow(i).getCell(2).font = { name: "Calibri", color: { argb: "FF000000" }, size: 12 };
923
- worksheet.getRow(i).getCell(1).alignment = worksheet.getRow(i).getCell(2).alignment = { horizontal: "left" };
660
+ const data = [];
661
+ // Row 1: "Definition of fields" title spanning 2 cols
662
+ data.push([
663
+ { value: "Definition of fields", ...titleStyle, height: 36, columnSpan: 2 },
664
+ null
665
+ ]);
666
+ // Row 2: blank
667
+ data.push([{ value: null, height: 12, columnSpan: 2 }, null]);
668
+ // Row 3: "Scan summary and Issue summary" section header
669
+ data.push([
670
+ { value: "Scan summary and Issue summary", ...sectionStyle, height: 20, columnSpan: 2 },
671
+ null
672
+ ]);
673
+ // Row 4: "Field / Definition" subheader
674
+ data.push([
675
+ { value: "Field", ...subHeaderStyle, height: 16 },
676
+ { value: "Definition", ...subHeaderStyle },
677
+ ]);
678
+ // Rows 5–12: scan summary definitions
679
+ for (const row of summaryRowData) {
680
+ data.push([
681
+ { value: row.key1, ...dataStyle },
682
+ { value: row.key2, ...dataStyle },
683
+ ]);
924
684
  }
925
- for (let i = 16; i < 29; i++) {
926
- worksheet.getRow(i).getCell(1).value = rowData[i - 16].key1;
927
- worksheet.getRow(i).getCell(2).value = rowData[i - 16].key2;
685
+ // Row 13: blank (separator between sections) — kept simple
686
+ data.push([{ value: null, height: 12, columnSpan: 2 }, null]);
687
+ // Row 14: "Issues" section header
688
+ data.push([
689
+ { value: "Issues", ...sectionStyle, height: 20, columnSpan: 2 },
690
+ null
691
+ ]);
692
+ // Row 15: "Field / Definition" subheader
693
+ data.push([
694
+ { value: "Field", ...subHeaderStyle, height: 16 },
695
+ { value: "Definition", ...subHeaderStyle },
696
+ ]);
697
+ // Rows 16–28: issues definitions
698
+ for (const row of issuesRowData) {
699
+ data.push([
700
+ { value: row.key1, ...dataStyle },
701
+ { value: row.key2, ...dataStyle },
702
+ ]);
928
703
  }
929
- worksheet.commit();
704
+ return { data, sheet: "Definition of fields", columns };
930
705
  }
931
706
  static countDuplicatesInArray(array) {
932
707
  let count = {};
933
- // let result = [];
934
708
  array.forEach(item => {
935
709
  if (count[item]) {
936
710
  //@ts-ignore
@@ -964,9 +738,6 @@ export class ACReporterXLSX {
964
738
  while (i) {
965
739
  hash = (hash * 33) ^ str.charCodeAt(--i);
966
740
  }
967
- /* JavaScript does bitwise operations (like XOR, above) on 32-bit signed
968
- * integers. Since we want the results to be always positive, convert the
969
- * signed int to an unsigned by doing an unsigned bitshift. */
970
741
  return hash >>> 0;
971
742
  }
972
743
  }