xlsx-template-browser 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Kyrylo Zakharov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,153 @@
1
+ # XLSX Template in Browser
2
+
3
+ On-the-fly generation of .xlsx (Excel) files directly within the browser using
4
+ templates constructed in Excel.
5
+
6
+ The basic principle is this: You create a template in Excel. This can be
7
+ formatted as you wish, contain formulae etc. In this file, you put placeholders
8
+ using a specific syntax (see below). In code, you build a map of placeholders
9
+ to values and then load the template, substitute the placeholders for the
10
+ relevant values, and generate a new .xlsx file that you can then serve to the
11
+ user.
12
+
13
+ ## Placeholders
14
+
15
+ Placeholders are inserted in cells in a spreadsheet. It does not matter how
16
+ those cells are formatted, so e.g. it is OK to insert a placeholder (which is
17
+ text content) into a cell formatted as a number or currecy or date, if you
18
+ expect the placeholder to resolve to a number or currency or date.
19
+
20
+ ### Scalars
21
+
22
+ Simple placholders take the format `${name}`. Here, `name` is the name of a
23
+ key in the placeholders map. The value of this placholder here should be a
24
+ scalar, i.e. not an array or object. The placeholder may appear on its own in a
25
+ cell, or as part of a text string. For example:
26
+
27
+ | Extracted on: | ${extractDate} |
28
+
29
+ might result in (depending on date formatting in the second cell):
30
+
31
+ | Extracted on: | Jun-01-2013 |
32
+
33
+ Here, `extractDate` may be a date and the second cell may be formatted as a
34
+ number.
35
+
36
+ Inside scalars there possibility to use array indexers.
37
+ For example:
38
+
39
+ Given data
40
+
41
+ var template = { extractDates: ["Jun-01-2113", "Jun-01-2013" ]}
42
+
43
+ which will be applied to following template
44
+
45
+ | Extracted on: | ${extractDates[0]} |
46
+
47
+ will results in the
48
+
49
+ | Extracted on: | Jun-01-2113 |
50
+
51
+ ### Columns
52
+
53
+ You can use arrays as placeholder values to indicate that the placeholder cell
54
+ is to be replicated across columns. In this case, the placeholder cannot appear
55
+ inside a text string - it must be the only thing in its cell. For example,
56
+ if the placehodler value `dates` is an array of dates:
57
+
58
+ | ${dates} |
59
+
60
+ might result in:
61
+
62
+ | Jun-01-2013 | Jun-02-2013 | Jun-03-2013 |
63
+
64
+ ### Tables
65
+
66
+ Finally, you can build tables made up of multiple rows. In this case, each
67
+ placeholder should be prefixed by `table:` and contain both the name of the
68
+ placeholder variable (a list of objects) and a key (in each object in the list).
69
+ For example:
70
+
71
+ | Name | Age |
72
+ | ${table:people.name} | ${table:people.age} |
73
+
74
+ If the replacement value under `people` is an array of objects, and each of
75
+ those objects have keys `name` and `age`, you may end up with something like:
76
+
77
+ | Name | Age |
78
+ | John Smith | 20 |
79
+ | Bob Johnson | 22 |
80
+
81
+ If a particular value is an array, then it will be repeated across columns as
82
+ above.
83
+
84
+ ## Generating reports
85
+
86
+ To use the library, you need the code like this:
87
+
88
+ ```
89
+ import { generateXlsx, downloadXlsx } from 'xlsx-template-browser'
90
+
91
+ // Set your values to fill the template
92
+ const values = {
93
+ extractDate: new Date(),
94
+ dates: [ new Date("2013-06-01"), new Date("2013-06-02"), new Date("2013-06-03") ],
95
+ people: [
96
+ {name: "John Smith", age: 20},
97
+ {name: "Bob Johnson", age: 22}
98
+ ]
99
+ }
100
+
101
+ // You can provide URL to the Excel template or the array buffer
102
+ const template = '[URL TO YOUR TEMPLATE]'
103
+
104
+ // You can generate the report and keep it in memory
105
+ const generateReport = async () => {
106
+ return await generateXlsx(template, values)
107
+ }
108
+
109
+ // Alternatively, you can download the generated report immediately
110
+ const downloadReport = async () => {
111
+ return await downloadXlsx(template, values, 'My fabulous report.xlsx')
112
+ }
113
+ ```
114
+
115
+ ## Caveats
116
+
117
+ * The spreadsheet must be saved in `.xlsx` format. `.xls`, `.xlsb` or `.xlsm`
118
+ won't work.
119
+ * Column (array) and table (array-of-objects) insertions cause rows and cells to
120
+ be inserted or removed. When this happens, only a limited number of
121
+ adjustments are made:
122
+ * Merged cells and named cells/ranges to the right of cells where insertions
123
+ or deletions are made are moved right or left, appropriately. This may
124
+ not work well if cells are merged across rows, unless all rows have the
125
+ same number of insertions.
126
+ * Merged cells, named tables or named cells/ranges below rows where further
127
+ rows are inserted are moved down.
128
+ Formulae are not adjusted.
129
+ * As a corollary to this, it is not always easy to build formulae that refer
130
+ to cells in a table (e.g. summing all rows) where the exact number of rows
131
+ or columns is not known in advance. There are two strategies for dealing
132
+ with this:
133
+ * Put the table as the last (or only) thing on a particular sheet, and
134
+ use a formula that includes a large number of rows or columns in the
135
+ hope that the actual table will be smaller than this number.
136
+ * Use named tables. When a placeholder in a named table causes columns or
137
+ rows to be added, the table definition (i.e. the cells included in the
138
+ table) will be updated accordingly. You can then use things like
139
+ `TableName[ColumnName]` in your formula to refer to all values in a given
140
+ column in the table as a logical range.
141
+ * Placeholders only work in simple cells and tables.
142
+
143
+ ## Alternatives
144
+
145
+ There are few altertantives for this library:
146
+
147
+ * [xlsx-template](https://www.npmjs.com/package/xlsx-template) - this library was the source of
148
+ inspiration. The only issue that it works in Node only. Also it allows you to insert images and
149
+ has more dependencies.
150
+ * [node-xlsx](https://www.npmjs.com/package/node-xlsx) - this library allows to manipulate the
151
+ data in worksheet, but the style of cells will be lost. The fork
152
+ [js-xlsx](https://github.com/protobi/js-xlsx/) might address the issue, but it hasn't been updated
153
+ for 4 years and could be considered overkill. Also the module is under Apache2 license.
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Converts a value to a string.
3
+ * @param {*} value - The value to be converted.
4
+ * @returns {string} - The converted string representation of the value.
5
+ */
6
+ export const valueToString = (value) => {
7
+ if (!value) return ''
8
+ if (Array.isArray(value)) return value.map(valueToString).toString()
9
+ if (typeof value === 'object') value = JSON.stringify(value)
10
+ return value.toString()
11
+ }
12
+
13
+ /**
14
+ * Converts dates to Excel serial index.
15
+ * @param {Date} value - The date to be converted.
16
+ * @returns {number} - The Excel serial index representing the date.
17
+ */
18
+ const dateToExcel = (value) => {
19
+ let date = new Date(value)
20
+ return 25569 + ((date.getTime() - (date.getTimezoneOffset() * 60 * 1000)) / (1000 * 60 * 60 * 24))
21
+ }
22
+
23
+ /**
24
+ * Guesses the Excel data type for the given JavaScript primitive value.
25
+ * @param {*} value - The JavaScript primitive value to be converted.
26
+ * @returns {Object|undefined} - An object with 'cellType' and 'value' properties representing the Excel data type,
27
+ * or undefined if the data type is not recognized.
28
+ */
29
+ export const guessDataType = (value) => {
30
+ if (typeof value === 'undefined') return undefined
31
+ if (typeof value === 'number') {
32
+ if (isFinite(value)) return { cellType: 'n', value }
33
+ return undefined
34
+ }
35
+ if (value instanceof Date) {
36
+ if (isNaN(value)) return undefined
37
+ return { cellType: 'n', value: dateToExcel(value) }
38
+ }
39
+ if (typeof value === 'boolean') return { cellType: 'b', value: value + 0 }
40
+ return { cellType: 's', value: valueToString(value )}
41
+ }
42
+
43
+ /**
44
+ * Creates a new shared string for an Excel file.
45
+ * @param {string} text - The text content of the shared string.
46
+ * @param {Element} template - (Optional) An existing node with cell to clone for the shared string.
47
+ * @returns {Element} - The newly created or cloned shared string element.
48
+ */
49
+ export const createSharedString = (text, template) => {
50
+ const si = template ? template.cloneNode() : document.createElement('si')
51
+ let t = document.createElement('t')
52
+ t.textContent = text
53
+ si.appendChild(t)
54
+ return si
55
+ }
56
+
57
+ /**
58
+ * Creates a cell with the specified value.
59
+ * @param {Object} options - An object containing parameters for cell creation.
60
+ * @param {*} options.value - The value to be set in the cell.
61
+ * @param {string|number} options.row - The row reference for the cell.
62
+ * @param {string|number} options.column - The column reference (either string or numeric) for the cell.
63
+ * @param {Element} options.template - (Optional) An existing template to clone for the cell.
64
+ * @param {string} options.cellType - The type of the cell (e.g., 's', 'n', 'b') for Excel formatting.
65
+ * @returns {Element} - The created cell element.
66
+ */
67
+ export const createCell = ({ value, row, column, template, cellType }) => {
68
+ // Clone initial node with the style or create a new one
69
+ const cell = template ? template.cloneNode() : document.createElement('c')
70
+ // Create a cell reference
71
+ if (row && column) {
72
+ column = typeof column === 'number' ? getExcelColumnName(column) : column
73
+ cell.setAttribute('r', column + row)
74
+ }
75
+ // If no value is provided, return cell
76
+ if (typeof value === 'undefined') return cell
77
+ const valueTag = document.createElement('v')
78
+ cell.setAttribute('t', cellType)
79
+ valueTag.textContent = value
80
+ cell.appendChild(valueTag)
81
+ return cell
82
+ }
83
+
84
+ /**
85
+ * Converts a numeric index to an Excel column name.
86
+ * @param {number} index - The numeric index to be converted.
87
+ * @returns {string} - The Excel column name corresponding to the index.
88
+ */
89
+ const getExcelColumnName = (index) => {
90
+ let result = ''
91
+ while (index > 0) {
92
+ const remainder = (index - 1) % 26
93
+ result = String.fromCharCode(65 + remainder) + result
94
+ index = Math.floor((index - 1) / 26)
95
+ }
96
+ return result
97
+ }
98
+
99
+ /**
100
+ * Converts an Excel column name to its numeric index.
101
+ * @param {string} columnName - The Excel column name to be converted.
102
+ * @returns {number} - The numeric index corresponding to the Excel column name.
103
+ */
104
+ export const getExcelColumnIndex = (columnName) => {
105
+ columnName = columnName.replace(/\d+/g, '')
106
+ let index = 0
107
+ for (let i = 0; i < columnName.length; i++) {
108
+ const charCode = columnName.charCodeAt(i) - 64
109
+ index = index * 26 + charCode
110
+ }
111
+ return index
112
+ }
113
+
package/lib/helpers.js ADDED
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Downloads a file using the provided data and file name.
3
+ * @param {string} data - The file data
4
+ * @param {string} fileName - The desired name for the downloaded file
5
+ */
6
+
7
+ const downloadURL = (data, fileName) => {
8
+ const a = document.createElement('a')
9
+ a.href = data
10
+ a.download = fileName
11
+ a.style = 'display: none'
12
+ document.body.appendChild(a)
13
+ a.click()
14
+ a.remove()
15
+ }
16
+
17
+ /**
18
+ * Converts an array to a Blob and initiates the download with the specified file name and MIME type.
19
+ * @param {Array} data - The array data to be converted to a Blob.
20
+ * @param {string} fileName - The desired name for the downloaded file.
21
+ * @param {string} mimeType - The MIME type of the file.
22
+ */
23
+ export const downloadBlob = (data, fileName, mimeType) => {
24
+ const blob = new Blob([data], { type: mimeType })
25
+ const url = window.URL.createObjectURL(blob)
26
+ downloadURL(url, fileName)
27
+ setTimeout(() => { return window.URL.revokeObjectURL(url) }, 100)
28
+ }
29
+
30
+
31
+ // Regular expression for extracting fields with deep notation data
32
+ const notationRegex = /(?<=\[(?<qoute>['"]))[^'"\]].*?(?=\k<qoute>\])|(?<=\[)[^'"\]].*?(?=\])|[^.\["''\]]+(?=\.|\[|$)/g
33
+
34
+ /**
35
+ * Retrieves nested properties from an object using an array of accessors.
36
+ * @param {*} obj - The object from which to retrieve the nested properties.
37
+ * @param {Array} accessors - An array of accessors representing the path to the desired properties.
38
+ * @returns {*} - The value of the nested properties, or undefined if any accessor along the path is undefined.
39
+ */
40
+ const getDeep = (obj, accessors) => {
41
+ let length = accessors.length
42
+ for (let i = 0; i < length; i++) {
43
+ if (typeof obj === 'undefined') return undefined
44
+ if (Array.isArray(obj) && typeof accessors[i] === 'string') return obj.map(el => getDeep(el, accessors.slice(i)))
45
+ obj = obj[accessors[i]]
46
+ }
47
+ return obj
48
+ }
49
+
50
+ /**
51
+ * Retrieves nested properties from an object using dot and bracket notation.
52
+ * @param {*} obj - The object from which to retrieve the nested properties.
53
+ * @param {string} props - The notation representing the path to the desired properties.
54
+ * @returns {*} - The value of the nested properties, or the original object if the notation is empty.
55
+ */
56
+ export const getByNotation = (obj, props) => {
57
+ let accessors = props.match(notationRegex)
58
+ if (!accessors.length) return obj
59
+ accessors = accessors.map(el => /^\d+$/.test(el) ? parseInt(el) : el)
60
+ return getDeep(obj, accessors)
61
+ }
62
+
package/lib/index.js ADDED
@@ -0,0 +1,16 @@
1
+ import { downloadBlob } from './helpers'
2
+ import replace from './main'
3
+
4
+ export const generateXlsx = async (template, data) => {
5
+ 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)
8
+ template = response.arrayBuffer()
9
+ }
10
+ return await replace(template, data)
11
+ }
12
+
13
+ export const downloadXlsx = async (template, data, fileName, mimeType) => {
14
+ if (!fileName) fileName = new Date().toISOString().substring(0, 10) + ' - Report.xlsx'
15
+ downloadBlob(await generateXlsx(template, data), fileName, mimeType)
16
+ }
package/lib/main.js ADDED
@@ -0,0 +1,213 @@
1
+ // Refer to Excel OpenXML format:
2
+ // https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.cell?view=openxml-2.8.1
3
+
4
+ // Import JSZip library for working with ZIP files
5
+ import JSZip from 'jszip'
6
+ import {
7
+ createCell,
8
+ createSharedString,
9
+ getExcelColumnIndex,
10
+ guessDataType,
11
+ valueToString
12
+ } from './excelHelpers'
13
+
14
+ import {
15
+ getByNotation
16
+ } from './helpers'
17
+
18
+ // Regular expression for parsing a property accessor enclosed in ${}
19
+ const accessorRegex = /(?<=^\$\{)(?<table>table:)?(?<accessor>[^}]+)(?=}$)/
20
+
21
+ // Global regular expression for extracting all property accessors enclosed in ${}
22
+ const globalAccessorRegex = /\$\{[^}]+}/g
23
+
24
+ const replace = async (template, data) => {
25
+ if (!template || !data) return template
26
+
27
+ /* INITIALIZATION */
28
+
29
+ // Create a new DOMParser instance for parsing XML. Refer to https://developer.mozilla.org/en-US/docs/Web/API/DOMParser
30
+ const parser = new DOMParser()
31
+ const serializer = new XMLSerializer()
32
+ // Create a new instance of JSZip for ZIP file manipulation
33
+ const new_zip = new JSZip()
34
+ // Load xlsx (zipped file)
35
+ let res = await new_zip.loadAsync(template)
36
+
37
+ /* PROCESSING OF THE SHARED STRINGS */
38
+
39
+ // Read all shared text values
40
+ let xmlText = await res.file('xl/sharedStrings.xml').async('string')
41
+ // Get the xml with shared shared strings
42
+ const xml = parser.parseFromString(xmlText, 'application/xml')
43
+ // Get the header of shared strings
44
+ const sst = xml.querySelector('sst')
45
+ // Array with new values
46
+ const valuesToReplace = []
47
+ // New list of shared string values
48
+ let newSharedStrings = []
49
+
50
+ // We need to replace only text values with the new ones, therefore we process shared strings first
51
+ // Get all string items
52
+ xml.querySelectorAll('si').forEach((si, i) => {
53
+ // Get all text tags
54
+ const t = si.querySelector('t')
55
+ if (!t) return false
56
+ // Get text value
57
+ const textValue = t.textContent
58
+ // Get all accessors
59
+ let match = textValue.match(accessorRegex)
60
+ // If the cell does not contain only one placeholder, check for multiple ones
61
+ if (!match) {
62
+ // Check if the text contains any accessors at all
63
+ let newText = textValue.replace(globalAccessorRegex, s => valueToString(getByNotation(data, s.replace(/\$\{|}/g, ''))))
64
+ newSharedStrings.push(createSharedString(newText, si))
65
+ valuesToReplace[i] = { value: newSharedStrings.length - 1, cellType: 's' }
66
+ return false
67
+ }
68
+ // Process a value with an accessor only
69
+ let { accessor, table } = match.groups
70
+ // If we have table values, we need to process them in a separate way
71
+ const isTable = typeof table === 'string'
72
+ const value = getByNotation(data, accessor)
73
+ // Save tables for further processing
74
+ if (isTable) {
75
+ valuesToReplace[i] = { isTable, value: value.map(guessDataType) }
76
+ return false
77
+ }
78
+ if (Array.isArray(value)) {
79
+ valuesToReplace[i] = value.map(guessDataType)
80
+ return false
81
+ }
82
+ valuesToReplace[i] = guessDataType(value)
83
+ })
84
+
85
+ /* PROCESSING OF WORKSHEETS */
86
+
87
+ // To prevent string duplication, store unique strings (they will be added to shared strings)
88
+ const newStrings = []
89
+
90
+ // Adds a string to the collection, avoiding duplication, and returns its index.
91
+ const addString = (value) => {
92
+ let stringIndex = newStrings.indexOf(value)
93
+ if (stringIndex === -1) {
94
+ newStrings.push(value)
95
+ stringIndex = newStrings.length - 1
96
+ }
97
+ value = stringIndex + newSharedStrings.length
98
+ return value
99
+ }
100
+
101
+ // Get all worksheets
102
+ const worksheets = Object.keys(res.files).filter(el => /xl\/worksheets\/[^/]+$/.test(el))
103
+ for (let worksheet of worksheets) {
104
+ // Unzip the file
105
+ let xmlText = await res.file(worksheet).async('string')
106
+ // Parse file to DOM
107
+ let xml = parser.parseFromString(xmlText, 'application/xml')
108
+ // Process rows
109
+ let rows = xml.querySelectorAll('sheetData row')
110
+ // Array with the list of the new rows
111
+ const newRows = []
112
+ // Process cells in rows
113
+ let rowOffset = 0
114
+ for (let row of rows) {
115
+ // Since some rows can be skipped, we want to get an actual row number from the template
116
+ let currentRow = parseInt(row.getAttribute('r'))
117
+ // List of new cells
118
+ let newCells = []
119
+ // If we have an array of values to extend the list of cells, we should keep an offset to move static cells
120
+ let cellOffset = 0
121
+ // Process each cell
122
+ let cells = row.querySelectorAll('c').forEach((c) => {
123
+ // Current cell index (note that Excel has 1-based index)
124
+ let index = getExcelColumnIndex(c.getAttribute('r'))
125
+ let newIndex = index + cellOffset
126
+ // Get the cell value tag
127
+ let v = c.querySelector('v') || {}
128
+ // Get the new value to replace
129
+ let newValue = valuesToReplace[v.textContent] || {}
130
+ if (!Array.isArray(newValue)) {
131
+ let { value, cellType, isTable } = newValue
132
+ // If the new value is an array from the table, then return an array of values
133
+ if (isTable) {
134
+ newCells[newIndex] = value.map(el => {
135
+ if (!el) return { template: c }
136
+ if (el.cellType === 's' && typeof el.value === 'string') el.value = addString(el.value)
137
+ return { value: el.value, cellType: el.cellType, template: c }
138
+ })
139
+ return false
140
+ }
141
+ // Return a new value
142
+ if (cellType === 's' && typeof value === 'string') value = addString(value)
143
+ newCells[newIndex] = Object.assign({}, { value, cellType }, { template: c })
144
+ return false
145
+ }
146
+ // If the value is an array (not from table), then extend the existing list of cells
147
+ for (let i = 0; i < newValue.length; i++) {
148
+ let { value, cellType } = newValue[i]
149
+ if (cellType === 's' && typeof value === 'string') value = addString(value)
150
+ newCells[newIndex + i] = Object.assign({}, { value, cellType }, { template: c })
151
+ cellOffset++
152
+ }
153
+ })
154
+
155
+ /* GENERATION OF THE NEW ROWS */
156
+
157
+ // Check if the row contains arrays and the values should be duplicated
158
+ let length = Math.max(...newCells.filter(el => Array.isArray(el)).map(el => el.length))
159
+
160
+ // Create a row
161
+ if (length <= 0) {
162
+ let rowIndex = rowOffset + currentRow
163
+ const rowValues = newCells.map((el, i) => {
164
+ if (!el) return undefined
165
+ return createCell(Object.assign({}, el, { row: rowIndex, column: i }))
166
+ }).filter(Boolean)
167
+ const newRow = row.cloneNode()
168
+ newRow.setAttribute('r', rowIndex)
169
+ rowValues.forEach(value => newRow.append(value))
170
+ newRows.push(newRow)
171
+ continue
172
+ }
173
+
174
+ // Create table rows
175
+ for (let i = 0; i < length; i++) {
176
+ let rowIndex = rowOffset + currentRow
177
+ rowOffset++
178
+ const rowValues = newCells.map((el, index) => {
179
+ if (!el) return undefined
180
+ if (Array.isArray(el)) {
181
+ if (typeof el[i] === 'undefined') return undefined
182
+ return createCell(Object.assign({}, el[i], { row: rowIndex, column: index }))
183
+ }
184
+ return createCell(Object.assign({}, el, { row: rowIndex, column: index }))
185
+ })
186
+ const newRow = row.cloneNode()
187
+ newRow.setAttribute('r', rowIndex)
188
+ rowValues.forEach(value => newRow.append(value))
189
+ newRows.push(newRow)
190
+ }
191
+ }
192
+
193
+ // Generate new sheet data
194
+ const newSheetData = xml.querySelector('sheetData').cloneNode()
195
+ newRows.forEach(row => newSheetData.append(row))
196
+ xml.querySelector('sheetData').replaceWith(newSheetData)
197
+ const newData = serializer.serializeToString(xml).replace(/ ?xmlns="http:\/\/www\.w3\.org\/1999\/xhtml"/g, '')
198
+ // Save the data of the worksheet
199
+ await new_zip.file(worksheet, newData)
200
+ }
201
+
202
+ /* FINALIZE NEW SHARED STRINGS */
203
+ newSharedStrings = [...newSharedStrings, ...newStrings.map(el => createSharedString(el))]
204
+ const newSst = sst.cloneNode()
205
+ newSharedStrings.forEach(si => newSst.append(si))
206
+ sst.replaceWith(newSst)
207
+ const newData = serializer.serializeToString(xml).replace(/ ?xmlns="http:\/\/www\.w3\.org\/1999\/xhtml"/g, '')
208
+ // Save new shared strings
209
+ await new_zip.file('xl/sharedStrings.xml', newData)
210
+ return await new_zip.generateAsync({ type: 'blob' })
211
+ }
212
+
213
+ export default replace
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "xlsx-template-browser",
3
+ "version": "0.1.0",
4
+ "description": "On-the-fly generation of .xlsx (Excel) files directly within the browser using templates constructed in Excel.",
5
+ "main": "./lib/index",
6
+ "files": [
7
+ "lib"
8
+ ],
9
+ "author": {
10
+ "name": "Kyrylo Zakharov"
11
+ },
12
+ "license": "MIT",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/Amice13/xlsx-template-browser"
16
+ },
17
+ "bugs": {
18
+ "url": "https://github.com/Amice13/xlsx-template-browser/issues"
19
+ },
20
+ "licenses": [
21
+ {
22
+ "type": "MIT",
23
+ "url": "https://raw.githubusercontent.com/Amice13/xlsx-template-browser/main/LICENSE"
24
+ }
25
+ ],
26
+ "dependencies": {
27
+ "jszip": "^3.10.1"
28
+ },
29
+ "readme": "",
30
+ "readmeFilename": "README.md"
31
+ }