xlsx-template-browser 0.1.8 → 0.2.0
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.
- package/lib/excelHelpers.js +8 -4
- package/lib/index.js +17 -2
- package/lib/main.js +61 -10
- package/package.json +1 -1
package/lib/excelHelpers.js
CHANGED
|
@@ -7,7 +7,7 @@ export const valueToString = (value) => {
|
|
|
7
7
|
if (!value) return ''
|
|
8
8
|
if (Array.isArray(value)) return value.map(valueToString).toString()
|
|
9
9
|
if (typeof value === 'object') value = JSON.stringify(value)
|
|
10
|
-
return
|
|
10
|
+
return value.toString()
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
/**
|
|
@@ -26,7 +26,7 @@ const dateToExcel = (value) => {
|
|
|
26
26
|
* @returns {Object|undefined} - An object with 'cellType' and 'value' properties representing the Excel data type,
|
|
27
27
|
* or undefined if the data type is not recognized.
|
|
28
28
|
*/
|
|
29
|
-
export const guessDataType = (value) => {
|
|
29
|
+
export const guessDataType = (value, { dateStyle }) => {
|
|
30
30
|
if (typeof value === 'undefined') return { cellType: 's', value: '' }
|
|
31
31
|
if (typeof value === 'number') {
|
|
32
32
|
if (isFinite(value)) return { cellType: 'n', value }
|
|
@@ -34,7 +34,7 @@ export const guessDataType = (value) => {
|
|
|
34
34
|
}
|
|
35
35
|
if (value instanceof Date) {
|
|
36
36
|
if (isNaN(value)) return undefined
|
|
37
|
-
return { cellType: 'n', value: dateToExcel(value) }
|
|
37
|
+
return { cellType: 'n', value: dateToExcel(value), style: dateStyle }
|
|
38
38
|
}
|
|
39
39
|
if (typeof value === 'boolean') return { cellType: 'b', value: value + 0 }
|
|
40
40
|
return { cellType: 's', value: valueToString(value) }
|
|
@@ -64,7 +64,7 @@ export const createSharedString = (text, template) => {
|
|
|
64
64
|
* @param {string} options.cellType - The type of the cell (e.g., 's', 'n', 'b', 'f') for Excel formatting.
|
|
65
65
|
* @returns {Element} - The created cell element.
|
|
66
66
|
*/
|
|
67
|
-
export const createCell = ({ value, row, column, template, cellType }) => {
|
|
67
|
+
export const createCell = ({ value, row, column, template, cellType, style }) => {
|
|
68
68
|
// If the cell is not a string, then skip
|
|
69
69
|
if (!cellType && !value) {
|
|
70
70
|
return template
|
|
@@ -87,6 +87,10 @@ export const createCell = ({ value, row, column, template, cellType }) => {
|
|
|
87
87
|
if (typeof value === 'undefined') return cell
|
|
88
88
|
const valueTag = document.createElement('v')
|
|
89
89
|
cell.setAttribute('t', cellType)
|
|
90
|
+
if (style) {
|
|
91
|
+
cell.setAttribute('s', style)
|
|
92
|
+
cell.removeAttribute('t')
|
|
93
|
+
}
|
|
90
94
|
valueTag.textContent = value
|
|
91
95
|
cell.appendChild(valueTag)
|
|
92
96
|
return cell
|
package/lib/index.js
CHANGED
|
@@ -1,15 +1,30 @@
|
|
|
1
1
|
import { downloadBlob } from './helpers'
|
|
2
2
|
import replace from './main'
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Generates an XLSX file by replacing data in a template file.
|
|
6
|
+
* @param {string} templateURL - The URL of the template XLSX file.
|
|
7
|
+
* @param {Object} data - The data object containing values to replace in the template.
|
|
8
|
+
* @returns {Promise<Blob>} A promise that resolves with the Blob of the generated XLSX file.
|
|
9
|
+
* @throws {Error} Throws an error if either the template URL or data is not provided, or if unable to fetch the template file.
|
|
10
|
+
*/
|
|
4
11
|
export const generateXlsx = async (template, data) => {
|
|
5
12
|
if (!template || !data) throw Error('No template or data provided')
|
|
6
|
-
if (typeof template
|
|
7
|
-
const response = await fetch(template)
|
|
13
|
+
if (typeof template === 'string' && template.match(/^https?:/)) {
|
|
14
|
+
const response = await fetch(template).catch(err => { return false})
|
|
15
|
+
if (!response) throw Error('Can\'t fetch the template URL')
|
|
8
16
|
template = response.arrayBuffer()
|
|
9
17
|
}
|
|
10
18
|
return await replace(template, data)
|
|
11
19
|
}
|
|
12
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Downloads an XLSX file by replacing data in a template file and initiates the download in the browser.
|
|
23
|
+
* @param {string} templateURL - The URL of the template XLSX file.
|
|
24
|
+
* @param {Object} data - The data object containing values to replace in the template.
|
|
25
|
+
* @param {string} [fileName] - The name of the file to be downloaded. If not provided, a default name based on the current date will be used.
|
|
26
|
+
* @returns {Promise<void>} A promise that resolves once the download is initiated.
|
|
27
|
+
*/
|
|
13
28
|
export const downloadXlsx = async (template, data, fileName, mimeType) => {
|
|
14
29
|
if (!fileName) fileName = new Date().toISOString().substring(0, 10) + ' - Report.xlsx'
|
|
15
30
|
downloadBlob(await generateXlsx(template, data), fileName, mimeType)
|
package/lib/main.js
CHANGED
|
@@ -34,6 +34,54 @@ const replace = async (template, data) => {
|
|
|
34
34
|
// Load xlsx (zipped file)
|
|
35
35
|
let res = await new_zip.loadAsync(template)
|
|
36
36
|
|
|
37
|
+
/* Add custom style for dates */
|
|
38
|
+
const xmlStyle = await res.file('xl/styles.xml').async('string')
|
|
39
|
+
const domStyle = parser.parseFromString(xmlStyle, 'application/xml')
|
|
40
|
+
|
|
41
|
+
// Create date number format
|
|
42
|
+
const styleSheet = domStyle.querySelector('styleSheet')
|
|
43
|
+
let numFmts = domStyle.querySelector('numFmts')
|
|
44
|
+
if (!numFmts) numFmts = document.createElementNS(null, 'numFmts')
|
|
45
|
+
let numFmt = document.createElementNS(null, 'numFmt')
|
|
46
|
+
numFmt.setAttributeNS(null, 'numFmtId', '166')
|
|
47
|
+
numFmt.setAttributeNS(null, 'formatCode', 'yyyy\\-mm\\-dd;@')
|
|
48
|
+
let oldNumFmt = domStyle.querySelectorAll('numFmt')
|
|
49
|
+
let count = 0
|
|
50
|
+
const newNumFmts = numFmts.cloneNode()
|
|
51
|
+
oldNumFmt.forEach(numFmt => {
|
|
52
|
+
newNumFmts.append(numFmt)
|
|
53
|
+
count++
|
|
54
|
+
})
|
|
55
|
+
newNumFmts.append(numFmt)
|
|
56
|
+
count++
|
|
57
|
+
newNumFmts.setAttribute('count', count)
|
|
58
|
+
styleSheet.prepend(newNumFmts)
|
|
59
|
+
|
|
60
|
+
// Add a style for the date format
|
|
61
|
+
const cellXfs = domStyle.querySelector('cellXfs')
|
|
62
|
+
const xf = document.createElement('xf')
|
|
63
|
+
xf.setAttributeNS(null, 'numFmtId', 166)
|
|
64
|
+
xf.setAttributeNS(null, 'fontId', 0)
|
|
65
|
+
xf.setAttributeNS(null, 'fillId', 0)
|
|
66
|
+
xf.setAttributeNS(null, 'borderId', 0)
|
|
67
|
+
xf.setAttributeNS(null, 'xfId', 0)
|
|
68
|
+
xf.setAttributeNS(null, 'applyNumberFormat', 1)
|
|
69
|
+
const oldXf = domStyle.querySelectorAll('cellXfs xf')
|
|
70
|
+
count = 0
|
|
71
|
+
const newCellXfs = cellXfs.cloneNode()
|
|
72
|
+
oldXf.forEach(xf => {
|
|
73
|
+
newCellXfs.append(xf)
|
|
74
|
+
count++
|
|
75
|
+
})
|
|
76
|
+
count++
|
|
77
|
+
newCellXfs.append(xf)
|
|
78
|
+
newCellXfs.setAttribute('count', count)
|
|
79
|
+
const dateStyle = count - 1
|
|
80
|
+
cellXfs.replaceWith(newCellXfs)
|
|
81
|
+
const newXfData = serializer.serializeToString(domStyle).replace(/ ?xmlns="(http:\/\/www\.w3\.org\/1999\/xhtml)?"/g, '')
|
|
82
|
+
// Save new shared strings
|
|
83
|
+
await new_zip.file('xl/styles.xml', newXfData)
|
|
84
|
+
|
|
37
85
|
/* PROCESSING OF THE SHARED STRINGS */
|
|
38
86
|
|
|
39
87
|
// Read all shared text values
|
|
@@ -83,14 +131,15 @@ const replace = async (template, data) => {
|
|
|
83
131
|
if (typeof value === 'undefined') return false
|
|
84
132
|
// Save tables for further processing
|
|
85
133
|
if (isTable) {
|
|
86
|
-
|
|
134
|
+
if (!value) value = []
|
|
135
|
+
valuesToReplace[i] = { isTable, value: value.map(el => guessDataType(el, { dateStyle })) }
|
|
87
136
|
return false
|
|
88
137
|
}
|
|
89
138
|
if (Array.isArray(value)) {
|
|
90
|
-
valuesToReplace[i] = value.map(guessDataType)
|
|
139
|
+
valuesToReplace[i] = value.map(el => guessDataType(el, { dateStyle }))
|
|
91
140
|
return false
|
|
92
141
|
}
|
|
93
|
-
valuesToReplace[i] = guessDataType(value)
|
|
142
|
+
valuesToReplace[i] = guessDataType(value, { dateStyle })
|
|
94
143
|
})
|
|
95
144
|
|
|
96
145
|
/* PROCESSING OF WORKSHEETS */
|
|
@@ -142,7 +191,7 @@ const replace = async (template, data) => {
|
|
|
142
191
|
const value = c
|
|
143
192
|
const cellType = 'f'
|
|
144
193
|
newCells[newIndex] = Object.assign({}, { value, cellType }, { template: c })
|
|
145
|
-
return false
|
|
194
|
+
return false
|
|
146
195
|
}
|
|
147
196
|
// Check if the cell contains string
|
|
148
197
|
let isString = c.getAttribute('t')
|
|
@@ -153,26 +202,26 @@ const replace = async (template, data) => {
|
|
|
153
202
|
// Get the new value to replace
|
|
154
203
|
let newValue = valuesToReplace[v.textContent] || {}
|
|
155
204
|
if (!Array.isArray(newValue)) {
|
|
156
|
-
let { value, cellType, isTable } = newValue
|
|
205
|
+
let { value, cellType, style, isTable } = newValue
|
|
157
206
|
// If the new value is an array from the table, then return an array of values
|
|
158
207
|
if (isTable) {
|
|
159
208
|
newCells[newIndex] = value.map(el => {
|
|
160
209
|
if (!el) return { template: c }
|
|
161
210
|
if (el.cellType === 's' && typeof el.value === 'string') el.value = addString(el.value)
|
|
162
|
-
return { value: el.value, cellType: el.cellType, template: c }
|
|
211
|
+
return { value: el.value, cellType: el.cellType, style: el.style, template: c }
|
|
163
212
|
})
|
|
164
213
|
return false
|
|
165
214
|
}
|
|
166
215
|
// Return a new value
|
|
167
216
|
if (cellType === 's' && typeof value === 'string') value = addString(value)
|
|
168
|
-
newCells[newIndex] = Object.assign({}, { value, cellType }, { template: c })
|
|
217
|
+
newCells[newIndex] = Object.assign({}, { value, cellType, style }, { template: c })
|
|
169
218
|
return false
|
|
170
219
|
}
|
|
171
220
|
// If the value is an array (not from table), then extend the existing list of cells
|
|
172
221
|
for (let i = 0; i < newValue.length; i++) {
|
|
173
|
-
let { value, cellType } = newValue[i] || {}
|
|
222
|
+
let { value, cellType, style } = newValue[i] || {}
|
|
174
223
|
if (cellType === 's' && typeof value === 'string') value = addString(value)
|
|
175
|
-
newCells[newIndex + i] = Object.assign({}, { value, cellType }, { template: c })
|
|
224
|
+
newCells[newIndex + i] = Object.assign({}, { value, cellType, style }, { template: c })
|
|
176
225
|
if (i) cellOffset++
|
|
177
226
|
}
|
|
178
227
|
})
|
|
@@ -210,7 +259,9 @@ const replace = async (template, data) => {
|
|
|
210
259
|
})
|
|
211
260
|
const newRow = row.cloneNode()
|
|
212
261
|
newRow.setAttribute('r', rowIndex)
|
|
213
|
-
rowValues.forEach(value =>
|
|
262
|
+
rowValues.forEach(value => {
|
|
263
|
+
if (value) newRow.append(value)
|
|
264
|
+
})
|
|
214
265
|
newRows.push(newRow)
|
|
215
266
|
}
|
|
216
267
|
}
|
package/package.json
CHANGED