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.
@@ -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 escapeXml(value.toString())
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.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,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
- valuesToReplace[i] = { isTable, value: value.map(guessDataType) }
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 => newRow.append(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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xlsx-template-browser",
3
- "version": "0.1.8",
3
+ "version": "0.2.0",
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": [