xlsx-template-browser 0.1.9 → 0.2.1
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 +7 -3
- package/lib/index.js +17 -2
- package/lib/main.js +66 -10
- package/package.json +1 -1
package/lib/excelHelpers.js
CHANGED
|
@@ -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,59 @@ 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
|
+
let stylesAreSet = Boolean(numFmts)
|
|
45
|
+
if (!numFmts) numFmts = document.createElementNS(null, 'numFmts')
|
|
46
|
+
let numFmt = document.createElementNS(null, 'numFmt')
|
|
47
|
+
numFmt.setAttributeNS(null, 'numFmtId', '166')
|
|
48
|
+
numFmt.setAttributeNS(null, 'formatCode', 'yyyy\\-mm\\-dd;@')
|
|
49
|
+
let oldNumFmt = domStyle.querySelectorAll('numFmt')
|
|
50
|
+
let count = 0
|
|
51
|
+
const newNumFmts = numFmts.cloneNode()
|
|
52
|
+
oldNumFmt.forEach(numFmt => {
|
|
53
|
+
newNumFmts.append(numFmt)
|
|
54
|
+
count++
|
|
55
|
+
})
|
|
56
|
+
newNumFmts.append(numFmt)
|
|
57
|
+
count++
|
|
58
|
+
newNumFmts.setAttribute('count', count)
|
|
59
|
+
if (stylesAreSet) {
|
|
60
|
+
numFmts.replaceWith(newNumFmts)
|
|
61
|
+
} else {
|
|
62
|
+
styleSheet.prepend(newNumFmts)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Add a style for the date format
|
|
66
|
+
const cellXfs = domStyle.querySelector('cellXfs')
|
|
67
|
+
const xf = document.createElement('xf')
|
|
68
|
+
xf.setAttributeNS(null, 'numFmtId', 166)
|
|
69
|
+
xf.setAttributeNS(null, 'fontId', 0)
|
|
70
|
+
xf.setAttributeNS(null, 'fillId', 0)
|
|
71
|
+
xf.setAttributeNS(null, 'borderId', 0)
|
|
72
|
+
xf.setAttributeNS(null, 'xfId', 0)
|
|
73
|
+
xf.setAttributeNS(null, 'applyNumberFormat', 1)
|
|
74
|
+
const oldXf = domStyle.querySelectorAll('cellXfs xf')
|
|
75
|
+
count = 0
|
|
76
|
+
const newCellXfs = cellXfs.cloneNode()
|
|
77
|
+
oldXf.forEach(xf => {
|
|
78
|
+
newCellXfs.append(xf)
|
|
79
|
+
count++
|
|
80
|
+
})
|
|
81
|
+
count++
|
|
82
|
+
newCellXfs.append(xf)
|
|
83
|
+
newCellXfs.setAttribute('count', count)
|
|
84
|
+
const dateStyle = count - 1
|
|
85
|
+
cellXfs.replaceWith(newCellXfs)
|
|
86
|
+
const newXfData = serializer.serializeToString(domStyle).replace(/ ?xmlns="(http:\/\/www\.w3\.org\/1999\/xhtml)?"/g, '')
|
|
87
|
+
// Save new shared strings
|
|
88
|
+
await new_zip.file('xl/styles.xml', newXfData)
|
|
89
|
+
|
|
37
90
|
/* PROCESSING OF THE SHARED STRINGS */
|
|
38
91
|
|
|
39
92
|
// Read all shared text values
|
|
@@ -83,14 +136,15 @@ const replace = async (template, data) => {
|
|
|
83
136
|
if (typeof value === 'undefined') return false
|
|
84
137
|
// Save tables for further processing
|
|
85
138
|
if (isTable) {
|
|
86
|
-
|
|
139
|
+
if (!value) value = []
|
|
140
|
+
valuesToReplace[i] = { isTable, value: value.map(el => guessDataType(el, { dateStyle })) }
|
|
87
141
|
return false
|
|
88
142
|
}
|
|
89
143
|
if (Array.isArray(value)) {
|
|
90
|
-
valuesToReplace[i] = value.map(guessDataType)
|
|
144
|
+
valuesToReplace[i] = value.map(el => guessDataType(el, { dateStyle }))
|
|
91
145
|
return false
|
|
92
146
|
}
|
|
93
|
-
valuesToReplace[i] = guessDataType(value)
|
|
147
|
+
valuesToReplace[i] = guessDataType(value, { dateStyle })
|
|
94
148
|
})
|
|
95
149
|
|
|
96
150
|
/* PROCESSING OF WORKSHEETS */
|
|
@@ -142,7 +196,7 @@ const replace = async (template, data) => {
|
|
|
142
196
|
const value = c
|
|
143
197
|
const cellType = 'f'
|
|
144
198
|
newCells[newIndex] = Object.assign({}, { value, cellType }, { template: c })
|
|
145
|
-
return false
|
|
199
|
+
return false
|
|
146
200
|
}
|
|
147
201
|
// Check if the cell contains string
|
|
148
202
|
let isString = c.getAttribute('t')
|
|
@@ -153,26 +207,26 @@ const replace = async (template, data) => {
|
|
|
153
207
|
// Get the new value to replace
|
|
154
208
|
let newValue = valuesToReplace[v.textContent] || {}
|
|
155
209
|
if (!Array.isArray(newValue)) {
|
|
156
|
-
let { value, cellType, isTable } = newValue
|
|
210
|
+
let { value, cellType, style, isTable } = newValue
|
|
157
211
|
// If the new value is an array from the table, then return an array of values
|
|
158
212
|
if (isTable) {
|
|
159
213
|
newCells[newIndex] = value.map(el => {
|
|
160
214
|
if (!el) return { template: c }
|
|
161
215
|
if (el.cellType === 's' && typeof el.value === 'string') el.value = addString(el.value)
|
|
162
|
-
return { value: el.value, cellType: el.cellType, template: c }
|
|
216
|
+
return { value: el.value, cellType: el.cellType, style: el.style, template: c }
|
|
163
217
|
})
|
|
164
218
|
return false
|
|
165
219
|
}
|
|
166
220
|
// Return a new value
|
|
167
221
|
if (cellType === 's' && typeof value === 'string') value = addString(value)
|
|
168
|
-
newCells[newIndex] = Object.assign({}, { value, cellType }, { template: c })
|
|
222
|
+
newCells[newIndex] = Object.assign({}, { value, cellType, style }, { template: c })
|
|
169
223
|
return false
|
|
170
224
|
}
|
|
171
225
|
// If the value is an array (not from table), then extend the existing list of cells
|
|
172
226
|
for (let i = 0; i < newValue.length; i++) {
|
|
173
|
-
let { value, cellType } = newValue[i] || {}
|
|
227
|
+
let { value, cellType, style } = newValue[i] || {}
|
|
174
228
|
if (cellType === 's' && typeof value === 'string') value = addString(value)
|
|
175
|
-
newCells[newIndex + i] = Object.assign({}, { value, cellType }, { template: c })
|
|
229
|
+
newCells[newIndex + i] = Object.assign({}, { value, cellType, style }, { template: c })
|
|
176
230
|
if (i) cellOffset++
|
|
177
231
|
}
|
|
178
232
|
})
|
|
@@ -210,7 +264,9 @@ const replace = async (template, data) => {
|
|
|
210
264
|
})
|
|
211
265
|
const newRow = row.cloneNode()
|
|
212
266
|
newRow.setAttribute('r', rowIndex)
|
|
213
|
-
rowValues.forEach(value =>
|
|
267
|
+
rowValues.forEach(value => {
|
|
268
|
+
if (value) newRow.append(value)
|
|
269
|
+
})
|
|
214
270
|
newRows.push(newRow)
|
|
215
271
|
}
|
|
216
272
|
}
|
package/package.json
CHANGED