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.
@@ -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.match === 'string' && template.match(/^https:/)) {
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
- valuesToReplace[i] = { isTable, value: value.map(guessDataType) }
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 => newRow.append(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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xlsx-template-browser",
3
- "version": "0.1.9",
3
+ "version": "0.2.1",
4
4
  "description": "On-the-fly generation of .xlsx (Excel) files directly within the browser using templates constructed in Excel.",
5
5
  "main": "./lib/index",
6
6
  "files": [