node-pptx-templater 1.0.1 → 1.0.3

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 (37) hide show
  1. package/README.md +336 -281
  2. package/package.json +6 -6
  3. package/src/cli/commands/build.js +32 -31
  4. package/src/cli/commands/debug.js +25 -24
  5. package/src/cli/commands/extract.js +23 -21
  6. package/src/cli/commands/inspect.js +25 -23
  7. package/src/cli/commands/validate.js +19 -17
  8. package/src/cli/index.js +45 -43
  9. package/src/core/OutputWriter.js +81 -78
  10. package/src/core/PPTXTemplater.js +859 -274
  11. package/src/core/TemplateEngine.js +69 -71
  12. package/src/core/ValidationEngine.js +246 -0
  13. package/src/index.js +51 -15
  14. package/src/managers/ChartManager.js +197 -70
  15. package/src/managers/ContentTypesManager.js +51 -45
  16. package/src/managers/HyperlinkManager.js +148 -142
  17. package/src/managers/ImageManager.js +336 -0
  18. package/src/managers/MediaManager.js +64 -81
  19. package/src/managers/RelationshipManager.js +102 -96
  20. package/src/managers/ShapeManager.js +340 -0
  21. package/src/managers/SlideManager.js +410 -311
  22. package/src/managers/TableManager.js +981 -262
  23. package/src/managers/TextManager.js +197 -0
  24. package/src/managers/ZipManager.js +71 -69
  25. package/src/managers/charts/ChartCacheGenerator.js +77 -58
  26. package/src/managers/charts/ChartParser.js +11 -13
  27. package/src/managers/charts/ChartRelationshipManager.js +14 -10
  28. package/src/managers/charts/ChartWorkbookUpdater.js +61 -56
  29. package/src/parsers/XMLParser.js +50 -49
  30. package/src/templates/blankPptx.js +3 -1
  31. package/src/templates/slideTemplate.js +31 -32
  32. package/src/utils/contentTypesHelper.js +41 -53
  33. package/src/utils/errors.js +33 -23
  34. package/src/utils/idUtils.js +23 -15
  35. package/src/utils/logger.js +21 -15
  36. package/src/utils/relationshipUtils.js +28 -22
  37. package/src/utils/xmlUtils.js +37 -29
@@ -1,17 +1,14 @@
1
- import { createLogger } from '../../utils/logger.js';
2
- import { ChartWorkbookUpdater } from './ChartWorkbookUpdater.js';
1
+ const { ChartWorkbookUpdater } = require('./ChartWorkbookUpdater.js')
3
2
 
4
- const logger = createLogger('ChartCacheGenerator');
5
-
6
- export class ChartCacheGenerator {
3
+ class ChartCacheGenerator {
7
4
  /**
8
5
  * Generates a string cache XML string (used for categories or series names).
9
6
  */
10
7
  static generateStrCache(values) {
11
8
  const ptEntries = values
12
9
  .map((val, i) => `<c:pt idx="${i}"><c:v>${this.#escapeXml(String(val))}</c:v></c:pt>`)
13
- .join('');
14
- return `<c:strCache><c:ptCount val="${values.length}"/>${ptEntries}</c:strCache>`;
10
+ .join('')
11
+ return `<c:strCache><c:ptCount val="${values.length}"/>${ptEntries}</c:strCache>`
15
12
  }
16
13
 
17
14
  /**
@@ -20,8 +17,8 @@ export class ChartCacheGenerator {
20
17
  static generateNumCache(values) {
21
18
  const ptEntries = values
22
19
  .map((val, i) => `<c:pt idx="${i}"><c:v>${Number(val) || 0}</c:v></c:pt>`)
23
- .join('');
24
- return `<c:numCache><c:formatCode>General</c:formatCode><c:ptCount val="${values.length}"/>${ptEntries}</c:numCache>`;
20
+ .join('')
21
+ return `<c:numCache><c:formatCode>General</c:formatCode><c:ptCount val="${values.length}"/>${ptEntries}</c:numCache>`
25
22
  }
26
23
 
27
24
  /**
@@ -32,117 +29,137 @@ export class ChartCacheGenerator {
32
29
  * @param {string} sheetName - Target worksheet name.
33
30
  */
34
31
  static updateCategories(xml, categories, sheetName = 'Sheet1') {
35
- const count = categories.length;
32
+ const count = categories.length
36
33
 
37
34
  // Formula for categories: Sheet1!$A$2:$A$N
38
- const formula = ChartWorkbookUpdater.getFormulaRange(sheetName, 2, 0, count + 1, 0);
39
- const newStrCache = this.generateStrCache(categories);
35
+ const formula = ChartWorkbookUpdater.getFormulaRange(sheetName, 2, 0, count + 1, 0)
36
+ const newStrCache = this.generateStrCache(categories)
40
37
 
41
38
  // Replace the entire <c:cat> block to ensure correct formula and cache
42
- const catPattern = /(<c:cat>)([\s\S]*?)(<\/c:cat>)/g;
39
+ const catPattern = /(<c:cat>)([\s\S]*?)(<\/c:cat>)/g
43
40
 
44
41
  return xml.replace(catPattern, (match, open, content, close) => {
45
42
  // Reconstruct the cat block
46
43
  // Try to determine if it used strRef or numRef originally
47
- let refTag = content.includes('<c:numRef>') ? 'numRef' : 'strRef';
44
+ let refTag = content.includes('<c:numRef>') ? 'numRef' : 'strRef'
48
45
  // But typically categories are strings. Let's use strRef.
49
- refTag = 'strRef';
46
+ refTag = 'strRef'
50
47
 
51
- return `${open}<c:${refTag}><c:f>${formula}</c:f>${newStrCache}</c:${refTag}>${close}`;
52
- });
48
+ return `${open}<c:${refTag}><c:f>${formula}</c:f>${newStrCache}</c:${refTag}>${close}`
49
+ })
53
50
  }
54
51
 
55
52
  /**
56
53
  * Updates series names and values in chart XML.
57
54
  */
58
55
  static updateSeries(xml, series, categoriesLength, sheetName = 'Sheet1') {
59
- let updated = xml;
60
- const serPattern = /(<c:ser>)([\s\S]*?)(<\/c:ser>)/g;
61
- const serMatches = [...updated.matchAll(serPattern)];
56
+ let updated = xml
57
+ const serPattern = /(<c:ser>)([\s\S]*?)(<\/c:ser>)/g
58
+ const serMatches = [...updated.matchAll(serPattern)]
62
59
 
63
- if (serMatches.length === 0) return xml;
60
+ if (serMatches.length === 0) return xml
64
61
 
65
- let serIndex = 0;
62
+ let serIndex = 0
66
63
  updated = updated.replace(serPattern, (match, open, content, close) => {
67
64
  if (serIndex >= series.length) {
68
65
  // If there are more series templates than data, we could drop them,
69
66
  // but replacing with an empty string might break the XML layout if we're not careful.
70
67
  // Actually, removing extra series is requested: "Allow removing old series".
71
- return '';
68
+ return ''
72
69
  }
73
70
 
74
- const serData = series[serIndex];
75
- const colIndex = serIndex + 1; // Series data starts in column B (1)
76
- serIndex++;
71
+ const serData = series[serIndex]
72
+ const colIndex = serIndex + 1 // Series data starts in column B (1)
73
+ serIndex++
77
74
 
78
- let updatedContent = content;
75
+ let updatedContent = content
79
76
 
80
77
  // 1. Update Series Name (c:tx)
81
78
  if (serData.name !== undefined) {
82
- const nameFormula = ChartWorkbookUpdater.getFormulaSingleCell(sheetName, 1, colIndex);
83
- const nameCache = `<c:strCache><c:ptCount val="1"/><c:pt idx="0"><c:v>${this.#escapeXml(serData.name)}</c:v></c:pt></c:strCache>`;
79
+ const nameFormula = ChartWorkbookUpdater.getFormulaSingleCell(sheetName, 1, colIndex)
80
+ const nameCache = `<c:strCache><c:ptCount val="1"/><c:pt idx="0"><c:v>${this.#escapeXml(serData.name)}</c:v></c:pt></c:strCache>`
84
81
 
85
- const txPattern = /(<c:tx>)([\s\S]*?)(<\/c:tx>)/;
82
+ const txPattern = /(<c:tx>)([\s\S]*?)(<\/c:tx>)/
86
83
  if (txPattern.test(updatedContent)) {
87
84
  updatedContent = updatedContent.replace(txPattern, (match, p1, p2, p3) => {
88
- return `${p1}<c:strRef><c:f>${nameFormula}</c:f>${nameCache}</c:strRef>${p3}`;
89
- });
85
+ return `${p1}<c:strRef><c:f>${nameFormula}</c:f>${nameCache}</c:strRef>${p3}`
86
+ })
90
87
  } else {
91
88
  // Some charts don't have <c:tx>, we prepend it after <c:order> or <c:idx>
92
- const insertAfter = /(<c:order[^>]*>)/;
89
+ const insertAfter = /(<c:order[^>]*>)/
93
90
  if (insertAfter.test(updatedContent)) {
94
91
  updatedContent = updatedContent.replace(insertAfter, (match, p1) => {
95
- return `${p1}<c:tx><c:strRef><c:f>${nameFormula}</c:f>${nameCache}</c:strRef></c:tx>`;
96
- });
92
+ return `${p1}<c:tx><c:strRef><c:f>${nameFormula}</c:f>${nameCache}</c:strRef></c:tx>`
93
+ })
97
94
  }
98
95
  }
99
96
  }
100
97
 
101
98
  // 2. Update Series Values (c:val)
102
99
  if (serData.values !== undefined) {
103
- const valuesCount = categoriesLength || serData.values.length;
104
- const valFormula = ChartWorkbookUpdater.getFormulaRange(sheetName, 2, colIndex, valuesCount + 1, colIndex);
105
- const valCache = this.generateNumCache(serData.values);
106
-
107
- const valPattern = /(<c:val>)([\s\S]*?)(<\/c:val>)/;
100
+ const valuesCount = categoriesLength || serData.values.length
101
+ const valFormula = ChartWorkbookUpdater.getFormulaRange(
102
+ sheetName,
103
+ 2,
104
+ colIndex,
105
+ valuesCount + 1,
106
+ colIndex
107
+ )
108
+ const valCache = this.generateNumCache(serData.values)
109
+
110
+ const valPattern = /(<c:val>)([\s\S]*?)(<\/c:val>)/
108
111
  if (valPattern.test(updatedContent)) {
109
112
  updatedContent = updatedContent.replace(valPattern, (match, p1, p2, p3) => {
110
- return `${p1}<c:numRef><c:f>${valFormula}</c:f>${valCache}</c:numRef>${p3}`;
111
- });
113
+ return `${p1}<c:numRef><c:f>${valFormula}</c:f>${valCache}</c:numRef>${p3}`
114
+ })
112
115
  }
113
116
  }
114
117
 
115
- return `${open}${updatedContent}${close}`;
116
- });
118
+ return `${open}${updatedContent}${close}`
119
+ })
117
120
 
118
- return updated;
121
+ return updated
119
122
  }
120
123
 
121
124
  /**
122
125
  * Clones a series template to support dynamic series addition.
123
126
  */
124
127
  static appendDynamicSeries(xml, targetCount) {
125
- const serPattern = /(<c:ser>)([\s\S]*?)(<\/c:ser>)/g;
126
- const matches = [...xml.matchAll(serPattern)];
127
- if (matches.length === 0 || matches.length >= targetCount) return xml;
128
+ const serPattern = /(<c:ser>)([\s\S]*?)(<\/c:ser>)/g
129
+ const matches = [...xml.matchAll(serPattern)]
130
+ if (matches.length === 0 || matches.length >= targetCount) return xml
128
131
 
129
132
  // Use the last series as a template to clone
130
- const templateMatch = matches[matches.length - 1];
131
- const template = templateMatch[0];
133
+ const templateMatch = matches[matches.length - 1]
134
+ const template = templateMatch[0]
132
135
 
133
136
  // Find the end of the last series
134
- const lastIndex = templateMatch.index + template.length;
137
+ const lastIndex = templateMatch.index + template.length
135
138
 
136
- let newSeriesBlocks = '';
139
+ let newSeriesBlocks = ''
137
140
  for (let i = matches.length; i < targetCount; i++) {
138
- let clone = template;
141
+ let clone = template
139
142
  // Update c:idx and c:order
140
- clone = clone.replace(/(<c:idx val=")\d+("\/>)/g, `$1${i}$2`);
141
- clone = clone.replace(/(<c:order val=")\d+("\/>)/g, `$1${i}$2`);
142
- newSeriesBlocks += clone;
143
+ clone = clone.replace(/(<c:idx val=")\d+("\/>)/g, `$1${i}$2`)
144
+ clone = clone.replace(/(<c:order val=")\d+("\/>)/g, `$1${i}$2`)
145
+ newSeriesBlocks += clone
143
146
  }
144
147
 
145
- return xml.substring(0, lastIndex) + newSeriesBlocks + xml.substring(lastIndex);
148
+ return xml.substring(0, lastIndex) + newSeriesBlocks + xml.substring(lastIndex)
149
+ }
150
+
151
+ /**
152
+ * Updates the chart title in chart XML.
153
+ */
154
+ static updateTitle(xml, title) {
155
+ const titleBlock = `<c:title><c:tx><c:rich><a:bodyPr/><a:lstStyle/><a:p><a:r><a:t>${this.#escapeXml(title)}</a:t></a:r></a:p></c:rich></c:tx><c:layout/></c:title>`
156
+ if (xml.includes('<c:title>')) {
157
+ const fullTitlePattern = /(<c:title>[\s\S]*?<\/c:title>)/
158
+ return xml.replace(fullTitlePattern, titleBlock)
159
+ } else {
160
+ const chartPattern = /(<c:chart>)/
161
+ return xml.replace(chartPattern, `$1${titleBlock}`)
162
+ }
146
163
  }
147
164
 
148
165
  static #escapeXml(str) {
@@ -151,6 +168,8 @@ export class ChartCacheGenerator {
151
168
  .replace(/</g, '&lt;')
152
169
  .replace(/>/g, '&gt;')
153
170
  .replace(/"/g, '&quot;')
154
- .replace(/'/g, '&apos;');
171
+ .replace(/'/g, '&apos;')
155
172
  }
156
173
  }
174
+
175
+ module.exports = { ChartCacheGenerator }
@@ -1,8 +1,4 @@
1
- import { createLogger } from '../../utils/logger.js';
2
-
3
- const logger = createLogger('ChartParser');
4
-
5
- export class ChartParser {
1
+ class ChartParser {
6
2
  /**
7
3
  * Finds a chart's relationship ID and type in a slide's XML based on shape name/id.
8
4
  *
@@ -15,14 +11,14 @@ export class ChartParser {
15
11
  const shapeNamePattern = new RegExp(
16
12
  `<p:cNvPr[^>]*name="${chartId}"[^>]*>(?:.*?)<c:chart[^>]*r:id="(rId\\d+)"`,
17
13
  's'
18
- );
19
- const rIdMatch = shapeNamePattern.exec(slideXml);
14
+ )
15
+ const rIdMatch = shapeNamePattern.exec(slideXml)
20
16
  if (rIdMatch) {
21
- return { rId: rIdMatch[1] };
17
+ return { rId: rIdMatch[1] }
22
18
  }
23
19
 
24
20
  // Strategy 2: Find all chart graphicFrames and we will match later in manager
25
- return null;
21
+ return null
26
22
  }
27
23
 
28
24
  /**
@@ -33,11 +29,13 @@ export class ChartParser {
33
29
  */
34
30
  static parseChartData(xml) {
35
31
  // This could be used for validation and extracting current chart cache
36
- const ptCountMatch = xml.match(/<c:ptCount val="(\d+)"\/>/);
37
- const pointCount = ptCountMatch ? parseInt(ptCountMatch[1], 10) : 0;
32
+ const ptCountMatch = xml.match(/<c:ptCount val="(\d+)"\/>/)
33
+ const pointCount = ptCountMatch ? parseInt(ptCountMatch[1], 10) : 0
38
34
 
39
35
  return {
40
- pointCount
41
- };
36
+ pointCount,
37
+ }
42
38
  }
43
39
  }
40
+
41
+ module.exports = { ChartParser }
@@ -1,6 +1,6 @@
1
- import { REL_TYPES } from '../RelationshipManager.js';
1
+ const { REL_TYPES } = require('../RelationshipManager.js')
2
2
 
3
- export class ChartRelationshipManager {
3
+ class ChartRelationshipManager {
4
4
  /**
5
5
  * Validates and fixes chart relationships.
6
6
  *
@@ -10,24 +10,28 @@ export class ChartRelationshipManager {
10
10
  * @returns {Object} validation issues
11
11
  */
12
12
  static validateChartRelationships(relationshipManager, zipManager, chartZipPath) {
13
- const issues = { errors: [], warnings: [] };
14
- const rels = relationshipManager.getRelationships(chartZipPath);
13
+ const issues = { errors: [], warnings: [] }
14
+ const rels = relationshipManager.getRelationships(chartZipPath)
15
15
 
16
- let hasWorkbook = false;
16
+ let hasWorkbook = false
17
17
  for (const rel of rels) {
18
18
  if (rel.type === REL_TYPES.PACKAGE) {
19
- hasWorkbook = true;
20
- const xlsxPath = relationshipManager.resolveTarget(chartZipPath, rel.target);
19
+ hasWorkbook = true
20
+ const xlsxPath = relationshipManager.resolveTarget(chartZipPath, rel.target)
21
21
  if (!zipManager.hasFile(xlsxPath)) {
22
- issues.errors.push(`Embedded workbook missing: ${xlsxPath}`);
22
+ issues.errors.push(`Embedded workbook missing: ${xlsxPath}`)
23
23
  }
24
24
  }
25
25
  }
26
26
 
27
27
  if (!hasWorkbook) {
28
- issues.warnings.push(`Chart ${chartZipPath} has no embedded workbook relationship. Live editing in PowerPoint may fail.`);
28
+ issues.warnings.push(
29
+ `Chart ${chartZipPath} has no embedded workbook relationship. Live editing in PowerPoint may fail.`
30
+ )
29
31
  }
30
32
 
31
- return issues;
33
+ return issues
32
34
  }
33
35
  }
36
+
37
+ module.exports = { ChartRelationshipManager }
@@ -1,9 +1,9 @@
1
- import JSZip from 'jszip';
2
- import { createLogger } from '../../utils/logger.js';
1
+ const JSZip = require('jszip')
2
+ const { createLogger } = require('../../utils/logger.js')
3
3
 
4
- const logger = createLogger('ChartWorkbookUpdater');
4
+ const logger = createLogger('ChartWorkbookUpdater')
5
5
 
6
- export class ChartWorkbookUpdater {
6
+ class ChartWorkbookUpdater {
7
7
  /**
8
8
  * Updates the embedded Excel workbook for a chart.
9
9
  *
@@ -12,83 +12,86 @@ export class ChartWorkbookUpdater {
12
12
  * @returns {Promise<Buffer>} - The updated XLSX buffer.
13
13
  */
14
14
  static async updateWorkbook(workbookData, data) {
15
- if (!workbookData) return null;
15
+ if (!workbookData) return null
16
16
 
17
17
  try {
18
- const zip = await JSZip.loadAsync(workbookData);
18
+ const zip = await JSZip.loadAsync(workbookData)
19
19
 
20
20
  // Look for sheet1.xml
21
- const sheetPath = 'xl/worksheets/sheet1.xml';
21
+ const sheetPath = 'xl/worksheets/sheet1.xml'
22
22
  if (!zip.file(sheetPath)) {
23
- logger.warn('sheet1.xml not found in embedded workbook, trying to find first sheet');
23
+ logger.warn('sheet1.xml not found in embedded workbook, trying to find first sheet')
24
24
  // fallback to finding the first sheet
25
25
  }
26
26
 
27
- const newSheetXml = this.#generateSheetXml(data);
28
- zip.file(sheetPath, newSheetXml);
27
+ const newSheetXml = this.#generateSheetXml(data)
28
+ zip.file(sheetPath, newSheetXml)
29
29
 
30
30
  // Clean up any existing Excel tables, as our new sheet data might not align with them
31
- const tableFiles = Object.keys(zip.files).filter(f => f.startsWith('xl/tables/'));
32
- tableFiles.forEach(f => zip.remove(f));
31
+ const tableFiles = Object.keys(zip.files).filter(f => f.startsWith('xl/tables/'))
32
+ tableFiles.forEach(f => zip.remove(f))
33
33
 
34
- const sheetRels = Object.keys(zip.files).filter(f => f.startsWith('xl/worksheets/_rels/'));
35
- sheetRels.forEach(f => zip.remove(f));
34
+ const sheetRels = Object.keys(zip.files).filter(f => f.startsWith('xl/worksheets/_rels/'))
35
+ sheetRels.forEach(f => zip.remove(f))
36
36
 
37
- const contentTypesFile = zip.file('[Content_Types].xml');
37
+ const contentTypesFile = zip.file('[Content_Types].xml')
38
38
  if (contentTypesFile) {
39
- const contentTypesXml = await contentTypesFile.async('text');
40
- const updatedContentTypes = contentTypesXml.replace(/<Override[^>]*PartName="\/xl\/tables\/[^"]*"[^>]*\/>/g, '');
41
- zip.file('[Content_Types].xml', updatedContentTypes);
39
+ const contentTypesXml = await contentTypesFile.async('text')
40
+ const updatedContentTypes = contentTypesXml.replace(
41
+ /<Override[^>]*PartName="\/xl\/tables\/[^"]*"[^>]*\/>/g,
42
+ ''
43
+ )
44
+ zip.file('[Content_Types].xml', updatedContentTypes)
42
45
  }
43
46
 
44
47
  return await zip.generateAsync({
45
48
  type: 'nodebuffer',
46
49
  compression: 'DEFLATE',
47
- compressionOptions: { level: 6 }
48
- });
50
+ compressionOptions: { level: 6 },
51
+ })
49
52
  } catch (err) {
50
- console.error('Failed to update embedded workbook', err);
51
- logger.error('Failed to update embedded workbook', err);
52
- return workbookData; // Return original if failed
53
+ console.error('Failed to update embedded workbook', err)
54
+ logger.error('Failed to update embedded workbook', err)
55
+ return workbookData // Return original if failed
53
56
  }
54
57
  }
55
58
 
56
59
  static #generateSheetXml(data) {
57
- const { categories = [], series = [] } = data;
60
+ const { categories = [], series = [] } = data
58
61
 
59
62
  // Column count = 1 (categories) + series.length
60
- const numCols = 1 + series.length;
61
- const numRows = 1 + categories.length; // Row 1 = headers
63
+ const numCols = 1 + series.length
64
+ const numRows = 1 + categories.length // Row 1 = headers
62
65
 
63
- const lastColLetter = this.getColumnLetter(numCols - 1);
64
- const dimensionRef = `A1:${lastColLetter}${numRows}`;
66
+ const lastColLetter = this.getColumnLetter(numCols - 1)
67
+ const dimensionRef = `A1:${lastColLetter}${numRows}`
65
68
 
66
- let sheetData = '<sheetData>';
69
+ let sheetData = '<sheetData>'
67
70
 
68
71
  // Row 1: Headers (empty cell A1, then series names)
69
- sheetData += '<row r="1">';
70
- sheetData += `<c r="A1" t="inlineStr"><is><t></t></is></c>`;
72
+ sheetData += '<row r="1">'
73
+ sheetData += `<c r="A1" t="inlineStr"><is><t></t></is></c>`
71
74
  series.forEach((ser, i) => {
72
- const colLetter = this.getColumnLetter(i + 1);
73
- sheetData += `<c r="${colLetter}1" t="inlineStr"><is><t>${this.#escapeXml(ser.name || '')}</t></is></c>`;
74
- });
75
- sheetData += '</row>';
75
+ const colLetter = this.getColumnLetter(i + 1)
76
+ sheetData += `<c r="${colLetter}1" t="inlineStr"><is><t>${this.#escapeXml(ser.name || '')}</t></is></c>`
77
+ })
78
+ sheetData += '</row>'
76
79
 
77
80
  // Rows 2..N: Data (category name in A, then values)
78
81
  categories.forEach((cat, rowIndex) => {
79
- const r = rowIndex + 2; // +1 for 1-based, +1 for header row
80
- sheetData += `<row r="${r}">`;
81
- sheetData += `<c r="A${r}" t="inlineStr"><is><t>${this.#escapeXml(String(cat))}</t></is></c>`;
82
+ const r = rowIndex + 2 // +1 for 1-based, +1 for header row
83
+ sheetData += `<row r="${r}">`
84
+ sheetData += `<c r="A${r}" t="inlineStr"><is><t>${this.#escapeXml(String(cat))}</t></is></c>`
82
85
 
83
86
  series.forEach((ser, colIndex) => {
84
- const colLetter = this.getColumnLetter(colIndex + 1);
85
- const val = ser.values && ser.values[rowIndex] !== undefined ? ser.values[rowIndex] : 0;
86
- sheetData += `<c r="${colLetter}${r}"><v>${Number(val)}</v></c>`;
87
- });
88
- sheetData += '</row>';
89
- });
87
+ const colLetter = this.getColumnLetter(colIndex + 1)
88
+ const val = ser.values && ser.values[rowIndex] !== undefined ? ser.values[rowIndex] : 0
89
+ sheetData += `<c r="${colLetter}${r}"><v>${Number(val)}</v></c>`
90
+ })
91
+ sheetData += '</row>'
92
+ })
90
93
 
91
- sheetData += '</sheetData>';
94
+ sheetData += '</sheetData>'
92
95
 
93
96
  return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
94
97
  <worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
@@ -96,27 +99,27 @@ export class ChartWorkbookUpdater {
96
99
  <sheetViews><sheetView workbookViewId="0"/></sheetViews>
97
100
  <sheetFormatPr defaultRowHeight="15"/>
98
101
  ${sheetData}
99
- </worksheet>`;
102
+ </worksheet>`
100
103
  }
101
104
 
102
105
  static getColumnLetter(colIndex) {
103
- let letter = '';
106
+ let letter = ''
104
107
  while (colIndex >= 0) {
105
- letter = String.fromCharCode(65 + (colIndex % 26)) + letter;
106
- colIndex = Math.floor(colIndex / 26) - 1;
108
+ letter = String.fromCharCode(65 + (colIndex % 26)) + letter
109
+ colIndex = Math.floor(colIndex / 26) - 1
107
110
  }
108
- return letter;
111
+ return letter
109
112
  }
110
113
 
111
114
  static getFormulaRange(sheetName, startRow, startCol, endRow, endCol) {
112
- const startLetter = this.getColumnLetter(startCol);
113
- const endLetter = this.getColumnLetter(endCol);
114
- return `${sheetName}!$${startLetter}$${startRow}:$${endLetter}$${endRow}`;
115
+ const startLetter = this.getColumnLetter(startCol)
116
+ const endLetter = this.getColumnLetter(endCol)
117
+ return `${sheetName}!$${startLetter}$${startRow}:$${endLetter}$${endRow}`
115
118
  }
116
119
 
117
120
  static getFormulaSingleCell(sheetName, row, col) {
118
- const letter = this.getColumnLetter(col);
119
- return `${sheetName}!$${letter}$${row}`;
121
+ const letter = this.getColumnLetter(col)
122
+ return `${sheetName}!$${letter}$${row}`
120
123
  }
121
124
 
122
125
  static #escapeXml(str) {
@@ -125,6 +128,8 @@ export class ChartWorkbookUpdater {
125
128
  .replace(/</g, '&lt;')
126
129
  .replace(/>/g, '&gt;')
127
130
  .replace(/"/g, '&quot;')
128
- .replace(/'/g, '&apos;');
131
+ .replace(/'/g, '&apos;')
129
132
  }
130
133
  }
134
+
135
+ module.exports = { ChartWorkbookUpdater }