├── .babelrc
├── .github
├── FUNDING.yml
├── dependabot.yml
└── workflows
│ └── webpack.yml
├── .gitignore
├── LICENSE.txt
├── README.md
├── bower.json
├── dist
├── excellentexport.d.ts
├── excellentexport.js
├── excellentexport.js.LICENSE.txt
├── format.d.ts
└── utils.d.ts
├── index.bigtable.html
├── index.filters.html
├── index.format.html
├── index.html
├── index.noanchor.html
├── index.require.html
├── index.rtl.html
├── jest.config.ts
├── package-lock.json
├── package.json
├── scripts
└── postinstall.js
├── src
├── excellentexport.ts
├── format.ts
└── utils.ts
├── test
├── checkversion.test.ts
├── convert-filters.test.ts
├── convert-table.test.ts
├── convert.format.ts
├── convert.test.ts
├── fixdata.test.ts
├── negative.test.ts
├── simple.test.ts
├── utils.test.ts
├── utils_fixdata.test.ts
└── utils_removeColumns.test.ts
├── tsconfig.json
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-env" ],
4 | "plugins": [
5 | "@babel/plugin-proposal-class-properties",
6 | "@babel/plugin-transform-flow-strip-types"
7 | ]
8 | }
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: jmaister
2 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: npm
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 | open-pull-requests-limit: 0
8 |
--------------------------------------------------------------------------------
/.github/workflows/webpack.yml:
--------------------------------------------------------------------------------
1 | name: Node CI
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: ubuntu-latest
13 |
14 | strategy:
15 | matrix:
16 | node-version: [22.x]
17 |
18 | steps:
19 | - uses: actions/checkout@v4
20 | with:
21 | fetch-depth: 0
22 |
23 | - name: Use Node.js ${{ matrix.node-version }}
24 | uses: actions/setup-node@v4
25 | with:
26 | node-version: ${{ matrix.node-version }}
27 | cache: 'npm'
28 |
29 | - run: npm install
30 |
31 | - run: npm test
32 |
33 | - name: Upload coverage reports to Codecov
34 | uses: codecov/codecov-action@v5
35 | with:
36 | token: ${{ secrets.CODECOV_TOKEN }}
37 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /nbproject
2 | /.project
3 | node_modules
4 | .tmp
5 | .idea
6 |
7 | coverage/*
8 | /.cache
9 |
10 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016-2025 Jordi Burgos
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.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | [](https://github.com/jmaister/excellentexport/actions/workflows/webpack.yml)
3 | [](https://www.jsdelivr.com/package/npm/excellentexport)
4 | [](https://codecov.io/gh/jmaister/excellentexport)
5 |
6 | # ExcellentExport.js
7 |
8 | - [:heart: Sponsor ExcellentExport.js project:heart:](https://github.com/sponsors/jmaister)
9 |
10 | - JavaScript export to Excel or CSV.
11 |
12 | - A quick JavaScript library to create export to Excel/CSV from HTML tables in the browser. No server required.
13 |
14 | - As part of the new version 3.0.0+, there is support for _XLSX_. The drawback is that the library is 200+ KB.
15 |
16 | - Check My Blog Page for Testing :
17 | [JavaScript export to Excel](http://jordiburgos.com/post/2013/javascript-export-to-excel.html)
18 |
19 | [ExcellentExport.js update: JavaScript export to Excel and CSV](http://jordiburgos.com/post/2017/excellentexport-javascript-export-to-excel-csv.html)
20 |
21 | # Revision history:
22 |
23 | ### 3.9.9
24 |
25 | * Configure codecov to track code coverage.
26 | * Removed coveralls, the library used is too old, not updated in years and has many vulnerabilities.
27 | * Remove unused dependencies (there was a lot of them).
28 |
29 | ### 3.9.8
30 |
31 | * _Update npm dependencies to fix vulnerabilities_
32 | * Moving to npm build (package-lock.json), yarn does not support "audit fix" command to fix dependencies vulnerabilities automatically.
33 |
34 | ### 3.9.7
35 |
36 | * _Update npm dependencies to fix vulnerabilities_
37 | * xlsx package loaded from CDN [SheetJS CDN](https://cdn.sheetjs.com/)
38 |
39 | ### 3.9.6
40 |
41 | * Remove references to openbase.io
42 | * Fix typos
43 | * _Update npm dependencies to fix vulnerabilities_
44 |
45 | ### 3.9.5
46 |
47 | * _Update npm dependencies to fix vulnerabilities_
48 |
49 | ### 3.9.4
50 |
51 | * _Update npm dependencies to fix vulnerabilities_
52 |
53 | ### 3.9.3
54 |
55 | * Fix TypeScript exported types
56 |
57 | ### 3.9.0
58 |
59 | * Cell types and formats!!! Now you can define the cell type and format. For example, you can define a cell as a date or a number. You can also define the format of the cell. For example, you can define a cell as a date with the format "dd/mm/yyyy" or a number with the format "#,##0.00".
60 |
61 | ### 3.8.1
62 |
63 | * Activate XLSX compression by default. The example of index.bigtable.html went from 18Mb to 3Mb.
64 | * _Update npm dependencies to fix vulnerabilities_
65 | * Update to latest version of TypeScript
66 | * Reduced size of the library from 912 KB to 277 KB!!!
67 |
68 | ### 3.8.0
69 |
70 | * Allow RTL options on the whole file or sheet.
71 | * _Update npm dependencies to fix vulnerabilities_
72 |
73 | ### 3.7.3
74 |
75 | * Fix (#591) remove columns parameter. Now it is not affected by repeated column numbers nor its order.
76 |
77 | ### 3.7.2
78 |
79 | * _Update npm dependencies to fix vulnerabilities_
80 |
81 | ### 3.7.1
82 |
83 | * _Update npm dependencies to fix vulnerabilities_
84 | * Start using Dependabot and get rid of Dependabot-preview
85 |
86 | ### 3.7.0
87 |
88 | * Added option `openAsDownload: boolean`. Use this option to download as a file without using an anchor tag. So download can be triggered from a button.
89 | * _Update npm dependencies to fix vulnerabilities_
90 |
91 | ### 3.6.0
92 |
93 | * Added sponsor link to the project [:heart: Sponsor ExcellentExport.js project:heart:](https://github.com/sponsors/jmaister)
94 | * Transform the project from JavaScript to TypeScript
95 | * Configure Jest as test runner
96 | * _Update npm dependencies to fix vulnerabilities_
97 |
98 | ### 3.5.0
99 |
100 | * Add fixValue and fixArray functions to configuration: these configuration functions can be used to manipulate the values of the cells.
101 | * _Update npm dependencies to fix vulnerabilities_
102 |
103 | ### 3.4.3
104 |
105 | * _Update npm dependencies to fix vulnerabilities_
106 |
107 | ### 3.4.2
108 |
109 | * Remove ES6 function syntax to support IE11
110 | * _Update npm dependencies to fix vulnerabilities_
111 |
112 | ### 3.4.0
113 |
114 | * Configure TravisCI on GitHub
115 | * Update npm dependencies to fix vulnerabilities
116 |
117 | ### 3.3.0
118 |
119 | * Remove columns by index
120 | * Filter rows by value
121 | * _Updated build to Webpack 4.x.x_
122 |
123 | ### 3.2.1
124 |
125 | * _Update npm dependencies to fix vulnerabilities_
126 |
127 | ### 3.2.0
128 |
129 | * _Update npm dependencies to fix vulnerabilities_
130 |
131 | ### 3.1.0
132 |
133 | * Fix old API for base64 and escaping problem.
134 |
135 | ### 3.0.0
136 |
137 | * XLSX support. This bumps the build size to 640 KB.
138 | * New API : ExcellentExport.convert(...)
139 | * Autogenerate download filename.
140 | * Data input from arrays or HTML Tables.
141 | * Multiple sheets for XLS or XLSX formats.
142 |
143 | ### 2.1.0
144 |
145 | * Add Webpack build.
146 | * Create UMD JavaScript module. Library can be loaded as a module (import, RequireJS, AMD, etc...) or standalone as window.ExcellentExport.
147 |
148 | ### 2.0.3
149 |
150 | * Fix export as a module.
151 | * Changed minifier to UglifyJS.
152 |
153 | ### 2.0.2
154 |
155 | * Fix CSV Chinese characters and other special characters display error in Windows Excel.
156 | * Fix URL.createObjectURL(...) on Firefox.
157 |
158 |
159 | ### 2.0.0
160 |
161 | * Now it can export to big files +2MB.
162 | * Minimum IE 11.
163 | * Links open with URL.createObjectURL(...).
164 | * NPM package available.
165 | * Using Semantic versioning (2.0.0 instead of 2.0).
166 | * Module can be loaded standalone or with RequireJS.
167 | * Change license to MIT.
168 |
169 | ### 1.5
170 |
171 | * Possibility to select a CSV delimiter.
172 | * Bower package available.
173 | * Compose package available.
174 |
175 | ### 1.4
176 |
177 | * _Add LICENSE.txt with GPL v3_
178 | * UTF-8 characters fixed.
179 |
180 | ### 1.3
181 |
182 | * _Added minified version_
183 |
184 | ### 1.1
185 |
186 | * _Added CSV data export_
187 |
188 | ### 1.0
189 |
190 | * _Added Excel data export_
191 |
192 | ## Compatibility
193 |
194 | - Firefox
195 | - Chrome
196 | - Internet Explorer 11+
197 |
198 | # Install
199 |
200 | ## npm
201 |
202 | npm install excellentexport --save
203 |
204 | ## yarn
205 |
206 | yarn add excellentexport
207 |
208 | ## bower
209 |
210 | bower install excellentexport
211 |
212 | # Load
213 |
214 |
215 | **Include script in your HTML:**
216 |
217 |
218 |
219 | **Include script in your HTML using CDN:**
220 |
221 |
222 |
223 |
224 | **Require.js**
225 |
226 |
227 |
232 |
233 | **ES6 import**
234 |
235 | import ExcellentExport from 'excellentexport';
236 |
237 |
238 | # Usage
239 |
240 |
241 |
242 | 100 | 200 | 300 |
243 |
244 |
245 | 400 | 500 | 600 |
246 |
247 |
248 |
249 | Export to Excel
250 | Export to CSV
251 |
252 | Export to CSV
253 |
254 | # API
255 |
256 | ExcellentExport.convert(options, sheets);
257 |
258 | Options:
259 | {
260 | anchor: String or HTML Element,
261 | format: 'xlsx' or 'xls' or 'csv',
262 | filename: String,
263 | rtl: Use Right-to-left characters, boolean (optional)
264 | }
265 |
266 | Sheets must be an array of sheet configuration objects. Sheet description:
267 | [
268 | {
269 | name: 'Sheet 1', // Sheet name
270 | from: {
271 | table: String/Element, // Table ID or table element
272 | array: [...] // Array with the data. Array where each element is a row. Every row is an array of the cells.
273 | },
274 | removeColumns: [...], // Array of column indexes (from 0)
275 | filterRowFn: function(row) {return true}, // Function to decide which rows are returned
276 | fixValue: function(value, row, column) {return fixedValue} // Function to fix values, receiving value, row num, column num
277 | fixArray: function(array) {return array} // Function to manipulate the whole data array
278 | rtl: Use Right-to-left characters, boolean (optional)
279 | formats: [...] // Array of formats for each column. See formats below.
280 | ...
281 | },
282 | {
283 | ...
284 | }, ...
285 | ]
286 |
287 | ## fixValue example
288 |
289 | This is an example for the _fixValue function_ to handle HTML tags inside a table cell.
290 | It transforms BR to line breaks and then strips all the HTML tags.
291 |
292 | fixValue: (value, row, col) => {
293 | let v = value.replace(/
/gi, "\n");
294 | let strippedString = v.replace(/(<([^>]+)>)/gi, "");
295 | return strippedString;
296 | }
297 |
298 | ## Formats
299 |
300 | You can specify an array with the formats for a specific cell range (i.e. A1:A100, A1:D100, A1:H1, etc).
301 |
302 | Each element in the format array consists on:
303 |
304 | ```typescript
305 | const sheet01 = {
306 | "range": "A1:A100", // Range of cells to apply the format, mandatory
307 | "format": {
308 | "type": "", // Type of format, mandatory
309 | "pattern": "" // Pattern, optional
310 | }
311 | }
312 | ```
313 |
314 | Example:
315 |
316 | ```typescript
317 | formats: [
318 | {
319 | range: "C2:C20",
320 | format: {
321 | type: "n",
322 | pattern: "0.00",
323 | },
324 | },
325 | {
326 | range: "C2:C20",
327 | format: ExcellentExport.formats.NUMBER,
328 | }
329 | ]
330 | ```
331 |
332 | `format` can be used from one of the predefined types if you use TypeScript
333 |
334 |
335 | `cell_type` can be one of the followint:
336 |
337 | 's': String
338 | 'n': Number
339 | 'd': Date
340 | 'b': Boolean
341 |
342 | `pattern` is a string with the format pattern used in Excel. For example:
343 |
344 | '0' // Integer
345 | '0.00' // 2 decimals
346 | 'dd/mm/yyyy' // Date
347 | 'dd/mm/yyyy hh:mm:ss' // Date and time
348 | '0.00%' // Percentage
349 | '0.00e+00' // Scientific notation
350 | '@' // Text
351 |
352 | # Notes
353 |
354 | - IE8 or lower do not support *data:* url schema.
355 | - IE9 does not support *data:* url schema on links.
356 | - IE10 and above and Edge are supported via the Microsoft-specific `msOpenOrSaveBlob` method.
357 |
358 | # Test
359 |
360 | python 2.x:
361 | python -m SimpleHTTPServer 8000
362 |
363 | python 3.x:
364 | python -m http.server 8000
365 |
366 | # Build
367 |
368 | **Install dependencies:**
369 |
370 | npm install
371 |
372 | **Build development version dist/excellentexport.js**
373 |
374 | npm run build
375 |
376 | **Build publish version of dist/excellentexport.js**
377 |
378 | npm run prod
379 |
380 | **Publish**
381 |
382 | npm publish
383 |
384 | ## Dependencies
385 |
386 | - XLSX is not available from NPM anymore. Use https://cdn.sheetjs.com/ to install it.
387 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "excellentexport",
3 | "main": "excellentexport.js",
4 | "homepage": "http://jordiburgos.com/post/2014/excellentexport-javascript-export-to-excel-csv.html",
5 | "authors": [
6 | "Jordi Burgos "
7 | ],
8 | "description": "Client side JavaScript export to Excel or CSV",
9 | "moduleType": [
10 | "globals"
11 | ],
12 | "keywords": [
13 | "excel",
14 | "export",
15 | "csv",
16 | "javascript",
17 | "client",
18 | "side"
19 | ],
20 | "license": "MIT",
21 | "ignore": [
22 | "**/.*",
23 | "node_modules",
24 | "bower_components",
25 | "test",
26 | "tests"
27 | ],
28 | "repository": {
29 | "type": "git",
30 | "url": "git://github.com/jmaister/excellentexport.git"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/dist/excellentexport.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * ExcellentExport 3.9.9
3 | * A client side Javascript export to Excel.
4 | *
5 | * @author: Jordi Burgos (jordiburgos@gmail.com)
6 | * @url: https://github.com/jmaister/excellentexport
7 | *
8 | */
9 | import { CellTypes, FormatDefinition, CellFormats, CellPatterns } from './format';
10 | declare global {
11 | interface Navigator {
12 | msSaveBlob?: (blob: any, defaultName?: string) => boolean;
13 | }
14 | }
15 | export interface ConvertOptions {
16 | anchor?: (string | HTMLAnchorElement);
17 | openAsDownload?: boolean;
18 | format: ('csv' | 'xls' | 'xlsx');
19 | filename?: string;
20 | rtl?: boolean;
21 | }
22 | export interface FromOptions {
23 | table?: (string | HTMLTableElement);
24 | array?: any[][];
25 | }
26 | export interface SheetOptions {
27 | name: string;
28 | from: FromOptions;
29 | removeColumns?: number[];
30 | filterRowFn?(row: any[]): boolean;
31 | fixValue?(value: any, row: number, column: number): any;
32 | fixArray?(array: any[][]): any[][];
33 | rtl?: boolean;
34 | formats?: (FormatDefinition | null)[];
35 | }
36 | declare const ExcellentExport: {
37 | version: () => string;
38 | excel: (anchor: (HTMLAnchorElement | string), table: HTMLTableElement, name: string) => boolean;
39 | csv: (anchor: (HTMLAnchorElement | string), table: HTMLTableElement, delimiter?: string, newLine?: string) => boolean;
40 | convert: (options: ConvertOptions, sheets: SheetOptions[]) => string | false;
41 | formats: CellFormats;
42 | cellTypes: typeof CellTypes;
43 | cellPatterns: typeof CellPatterns;
44 | };
45 | export default ExcellentExport;
46 |
--------------------------------------------------------------------------------
/dist/excellentexport.js.LICENSE.txt:
--------------------------------------------------------------------------------
1 | /*! sheetjs (C) 2013-present SheetJS -- http://sheetjs.com */
2 |
3 | /*! xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */
4 |
--------------------------------------------------------------------------------
/dist/format.d.ts:
--------------------------------------------------------------------------------
1 | export declare enum CellTypes {
2 | TEXT = "s",
3 | NUMBER = "n",
4 | DATE = "d",
5 | BOOLEAN = "b"
6 | }
7 | export declare enum CellPatterns {
8 | INTEGER = "0",
9 | DECIMAL = "0.00",
10 | DATE = "dd/mm/yyyy",
11 | TIME = "hh:mm:ss",
12 | DATETIME = "dd/mm/yyyy hh:mm:ss",
13 | CURRENCY = "[$$-409]#,##0.00;[RED]-[$$-409]#,##0.00",
14 | PERCENTAGE = "0.00%",
15 | EXPONENT = "0.00E+00",
16 | TEXT = "@"
17 | }
18 | export type CellType = 's' | 'n' | 'd' | 'b';
19 | export interface CellFormat {
20 | type: CellType;
21 | pattern?: string;
22 | }
23 | export interface CellFormats {
24 | [key: string]: CellFormat;
25 | }
26 | export declare const PredefinedFormat: CellFormats;
27 | export interface FormatDefinition {
28 | range: string;
29 | format?: CellFormat;
30 | }
31 |
--------------------------------------------------------------------------------
/dist/utils.d.ts:
--------------------------------------------------------------------------------
1 | export declare const b64toBlob: (b64Data: string, contentType: string, sliceSize?: number) => Blob;
2 | export declare const templates: {
3 | excel: string;
4 | };
5 | /**
6 | * Convert a string to Base64.
7 | */
8 | export declare const base64: (s: string) => string;
9 | export declare const format: (s: string, context: any) => string;
10 | /**
11 | * Get element by ID.
12 | * @param {*} element
13 | */
14 | export declare const getTable: (element: (HTMLTableElement | string)) => HTMLTableElement;
15 | /**
16 | * Get element by ID.
17 | * @param {*} element
18 | */
19 | export declare const getAnchor: (element: (HTMLAnchorElement | string)) => HTMLAnchorElement;
20 | /**
21 | * Encode a value for CSV.
22 | * @param {*} value
23 | */
24 | export declare const fixCSVField: (value: string, csvDelimiter: string) => string;
25 | export declare const tableToArray: (table: HTMLTableElement) => any[][];
26 | export declare const tableToCSV: (table: HTMLTableElement, csvDelimiter?: string, csvNewLine?: string) => string;
27 | export declare const createDownloadLink: (anchor: HTMLAnchorElement, base64data: string, exporttype: string, filename: string) => boolean;
28 | export declare const string2ArrayBuffer: (s: string) => ArrayBuffer;
29 | export declare const removeColumns: (dataArray: any[][], columnIndexes: number[]) => void;
30 | export declare const hasContent: (value: any) => boolean;
31 |
--------------------------------------------------------------------------------
/index.bigtable.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Export to excel test
5 |
6 |
11 |
25 |
26 |
27 | ExcellentExport.js
28 |
29 | Check on jordiburgos.com and GitHub.
30 |
31 | Big Table Test page
32 |
33 |
34 | Export to XLSX very big table
35 | Export to XLS very big table
36 | Export to CSV very big table
37 |
40 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/index.filters.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Export to excel test
5 |
6 |
11 |
30 |
31 |
32 | ExcellentExport.js
33 |
34 | Check on jordiburgos.com and GitHub.
35 |
36 | Test page
37 |
38 | Test table:
39 |
40 |
41 | Column 1 |
42 | Column 2 |
43 | Column 3 |
44 | Column 4 |
45 |
46 |
47 | hello world |
48 | text in span |
49 | abc |
50 | def |
51 |
52 |
53 |
54 |
55 |
56 | Export to XLSX from array
57 |
58 | Export to XLS from array
59 |
60 | Export to CSV from array
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/index.format.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Export to excel test
6 |
7 |
12 |
36 |
37 |
38 | ExcellentExport.js
39 |
40 | Test page
41 |
42 | Test table:
43 |
44 |
45 | ID |
46 | Name |
47 | Birthdate |
48 | Salary |
49 | Active |
50 | Big number |
51 |
52 |
53 | 1 |
54 | John |
55 | 1980-12-10 |
56 | 98762000.55 |
57 | 1 |
58 | 987654321987654 |
59 |
60 |
61 | 2 |
62 | Peter |
63 | 1978-01-23 |
64 | 98762500.43 |
65 | 0 |
66 | 876543219987654 |
67 |
68 |
69 | 3 |
70 | George |
71 | 1985-11-30 |
72 | 98761800.98 |
73 | 1 |
74 | 765432198987654 |
75 |
76 |
77 | End |
78 | End |
79 | End |
80 | End |
81 | End |
82 | End |
83 |
84 |
85 | 9876543.21 |
86 | |
87 | |
88 | |
89 | |
90 | |
91 |
92 |
93 |
94 |
95 |
96 | Export Excel
97 | Export CSV
98 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Export to excel test
5 |
6 |
11 |
61 |
62 |
63 | ExcellentExport.js
64 |
65 | Check on jordiburgos.com and GitHub.
66 |
67 | Test page
68 |
69 | Test table:
70 |
71 |
72 | Column 1 |
73 | Column "cool" 2 |
74 | Column 3 |
75 | Column 4 |
76 |
77 |
78 | 100,111 |
79 | 200 |
80 | 030 |
81 | áéíóú |
82 |
83 |
84 | 400 |
85 | 500 |
86 | Chinese chars: 解决导出csv中文乱码问题 |
87 | àèìòù |
88 |
89 |
90 | Text |
91 | More text |
92 | Text with
93 | new line |
94 | ç ñ ÄËÏÖÜ äëïöü |
95 |
96 |
97 |
98 |
99 |
100 | Export to Excel
101 |
102 |
103 | Export to CSV - UTF8
104 |
105 | Export to CSV - Using semicolon ";" separator - UTF8
106 |
107 | NEW API!
108 | Export to Excel: XLS format
109 |
110 | Export to Excel: XLSX format
111 |
112 | Export to CSV
113 |
114 | Export to Excel: XLSX filtering columns and rows
115 |
116 |
117 |
118 | NEW API from Arrays!
119 |
120 | Export to XLSX from array
121 |
122 | Export to XLS from array
123 |
124 | Export to CSV from array
125 |
126 |
127 |
128 |
--------------------------------------------------------------------------------
/index.noanchor.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Export to excel test
5 |
6 |
11 |
21 |
22 |
23 | ExcellentExport.js
24 |
25 | Check on jordiburgos.com and GitHub.
26 |
27 | Test without anchor tab
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/index.require.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Export to excel test
5 |
6 |
11 |
16 |
17 |
18 | ExcellentExport.js
19 |
20 | Check on jordiburgos.com and GitHub.
21 |
22 | Test page
23 |
24 |
25 |
26 | Export to Excel
27 |
28 |
29 | Export to CSV - UTF8
30 |
31 | Export to CSV - Using semicolon ";" separator - UTF8
32 |
33 |
34 |
35 |
36 | Column 1 |
37 | Column "cool" 2 |
38 | Column 3 |
39 | Column 4 |
40 |
41 |
42 | 100,111 |
43 | 200 |
44 | 300 |
45 | áéíóú |
46 |
47 |
48 | 400 |
49 | 500 |
50 | Chinese chars: 解决导出csv中文乱码问题 |
51 | àèìòù |
52 |
53 |
54 | Text |
55 | More text |
56 | Text with
57 | new line |
58 | ç ñ ÄËÏÖÜ äëïöü |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/index.rtl.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Export to excel test
5 |
6 |
11 |
26 |
27 |
28 | ExcellentExport.js RTL text
29 |
30 | Check on jordiburgos.com and GitHub.
31 |
32 | Test page
33 |
34 | Test table
35 |
36 |
37 |
38 | االلغة |
39 | عدد الأحرف |
40 | Country |
41 | miص-ص |
42 |
43 |
44 |
45 |
46 | العربية |
47 | ٢٨ |
48 | Earth |
49 | ن0ن |
50 |
51 |
52 | العبرية |
53 | ٢٢ |
54 | Isreal |
55 | ضSض |
56 |
57 |
58 |
59 |
60 |
61 |
62 | Export to Excel: XLS format
63 |
64 | Export to Excel: XLSX format
65 |
66 | Export to CSV
67 |
68 |
69 |
--------------------------------------------------------------------------------
/jest.config.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * For a detailed explanation regarding each configuration property and type check, visit:
3 | * https://jestjs.io/docs/en/configuration.html
4 | */
5 |
6 | export default {
7 | // All imported modules in your tests should be mocked automatically
8 | // automock: false,
9 |
10 | // Stop running tests after `n` failures
11 | // bail: 0,
12 |
13 | // The directory where Jest should store its cached dependency information
14 | // cacheDirectory: "/tmp/jest_rs",
15 |
16 | // Automatically clear mock calls and instances between every test
17 | // clearMocks: false,
18 |
19 | // Indicates whether the coverage information should be collected while executing the test
20 | // collectCoverage: false,
21 |
22 | // An array of glob patterns indicating a set of files for which coverage information should be collected
23 | // collectCoverageFrom: undefined,
24 |
25 | // The directory where Jest should output its coverage files
26 | coverageDirectory: "coverage",
27 |
28 | // An array of regexp pattern strings used to skip coverage collection
29 | // coveragePathIgnorePatterns: [
30 | // "/node_modules/"
31 | // ],
32 |
33 | // Indicates which provider should be used to instrument code for coverage
34 | coverageProvider: "v8",
35 |
36 | // A list of reporter names that Jest uses when writing coverage reports
37 | // coverageReporters: [
38 | // "json",
39 | // "text",
40 | // "lcov",
41 | // "clover"
42 | // ],
43 |
44 | // An object that configures minimum threshold enforcement for coverage results
45 | // coverageThreshold: undefined,
46 |
47 | // A path to a custom dependency extractor
48 | // dependencyExtractor: undefined,
49 |
50 | // Make calling deprecated APIs throw helpful error messages
51 | // errorOnDeprecated: false,
52 |
53 | // Force coverage collection from ignored files using an array of glob patterns
54 | // forceCoverageMatch: [],
55 |
56 | // A path to a module which exports an async function that is triggered once before all test suites
57 | // globalSetup: undefined,
58 |
59 | // A path to a module which exports an async function that is triggered once after all test suites
60 | // globalTeardown: undefined,
61 |
62 | // A set of global variables that need to be available in all test environments
63 | // globals: {},
64 |
65 | // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
66 | // maxWorkers: "50%",
67 |
68 | // An array of directory names to be searched recursively up from the requiring module's location
69 | // moduleDirectories: [
70 | // "node_modules"
71 | // ],
72 |
73 | // An array of file extensions your modules use
74 | // moduleFileExtensions: [
75 | // "js",
76 | // "json",
77 | // "jsx",
78 | // "ts",
79 | // "tsx",
80 | // "node"
81 | // ],
82 |
83 | // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
84 | // moduleNameMapper: {},
85 |
86 | // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
87 | // modulePathIgnorePatterns: [],
88 |
89 | // Activates notifications for test results
90 | // notify: false,
91 |
92 | // An enum that specifies notification mode. Requires { notify: true }
93 | // notifyMode: "failure-change",
94 |
95 | // A preset that is used as a base for Jest's configuration
96 | // preset: undefined,
97 | preset: 'jest-puppeteer',
98 |
99 | // Run tests from one or more projects
100 | // projects: undefined,
101 |
102 | // Use this configuration option to add custom reporters to Jest
103 | // reporters: undefined,
104 |
105 | // Automatically reset mock state between every test
106 | // resetMocks: false,
107 |
108 | // Reset the module registry before running each individual test
109 | // resetModules: false,
110 |
111 | // A path to a custom resolver
112 | // resolver: undefined,
113 |
114 | // Automatically restore mock state between every test
115 | // restoreMocks: false,
116 |
117 | // The root directory that Jest should scan for tests and modules within
118 | // rootDir: undefined,
119 |
120 | // A list of paths to directories that Jest should use to search for files in
121 | // roots: [
122 | // ""
123 | // ],
124 | roots: [
125 | "src",
126 | "test"
127 | ],
128 |
129 | // Allows you to use a custom runner instead of Jest's default test runner
130 | // runner: "jest-runner",
131 |
132 | // The paths to modules that run some code to configure or set up the testing environment before each test
133 | // setupFiles: [],
134 |
135 | // A list of paths to modules that run some code to configure or set up the testing framework before each test
136 | // setupFilesAfterEnv: [],
137 |
138 | // The number of seconds after which a test is considered as slow and reported as such in the results.
139 | // slowTestThreshold: 5,
140 |
141 | // A list of paths to snapshot serializer modules Jest should use for snapshot testing
142 | // snapshotSerializers: [],
143 |
144 | // The test environment that will be used for testing
145 | // testEnvironment: "jest-environment-jsdom",
146 | testEnvironment: "jsdom",
147 |
148 | // Options that will be passed to the testEnvironment
149 | // testEnvironmentOptions: {},
150 |
151 | // Adds a location field to test results
152 | // testLocationInResults: false,
153 |
154 | // The glob patterns Jest uses to detect test files
155 | // testMatch: [
156 | // "**/__tests__/**/*.[jt]s?(x)",
157 | // "**/?(*.)+(spec|test).[tj]s?(x)"
158 | // ],
159 |
160 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
161 | // testPathIgnorePatterns: [
162 | // "/node_modules/"
163 | // ],
164 |
165 | // The regexp pattern or array of patterns that Jest uses to detect test files
166 | // testRegex: [],
167 |
168 | // This option allows the use of a custom results processor
169 | // testResultsProcessor: undefined,
170 |
171 | // This option allows use of a custom test runner
172 | // testRunner: "jasmine2",
173 |
174 | // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
175 | // testURL: "http://localhost",
176 |
177 | // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
178 | // timers: "real",
179 |
180 | // A map from regular expressions to paths to transformers
181 | // transform: undefined,
182 | transform: {
183 | "^.+\\.ts?$": "ts-jest"
184 | },
185 |
186 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
187 | // transformIgnorePatterns: [
188 | // "/node_modules/",
189 | // "\\.pnp\\.[^\\/]+$"
190 | // ],
191 |
192 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
193 | // unmockedModulePathPatterns: undefined,
194 |
195 | // Indicates whether each individual test should be reported during the run
196 | // verbose: undefined,
197 |
198 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
199 | // watchPathIgnorePatterns: [],
200 |
201 | // Whether to use watchman for file crawling
202 | // watchman: true,
203 | };
204 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "excellentexport",
3 | "version": "3.9.9",
4 | "description": "Client side JavaScript export to Excel or CSV",
5 | "license": "MIT",
6 | "homepage": "https://jordiburgos.com",
7 | "author": "Jordi Burgos ",
8 | "bugs": "https://github.com/jmaister/excellentexport/issues",
9 | "repository": {
10 | "type": "git",
11 | "url": "git://github.com/jmaister/excellentexport.git"
12 | },
13 | "keywords": [
14 | "excel",
15 | "export",
16 | "csv",
17 | "javascript",
18 | "client",
19 | "side"
20 | ],
21 | "scripts": {
22 | "build": "webpack --config webpack.config.js --progress --mode development --watch",
23 | "prod": "webpack --config webpack.config.js --progress --mode production",
24 | "test": "jest --coverage",
25 | "watch": "jest --watch",
26 | "postinstall": "node scripts/postinstall.js"
27 | },
28 | "main": "dist/excellentexport.js",
29 | "devDependencies": {
30 | "@types/jest": "29.5.14",
31 | "@types/jest-environment-puppeteer": "5.0.6",
32 | "@types/node": "22.12.0",
33 | "jest": "29.7.0",
34 | "jest-environment-jsdom": "29.7.0",
35 | "jest-puppeteer": "11.0.0",
36 | "puppeteer": "24.1.1",
37 | "ts-jest": "29.2.5",
38 | "ts-loader": "9.5.2",
39 | "typescript": "5.7.3",
40 | "webpack": "5.97.1",
41 | "webpack-cli": "6.0.1",
42 | "webpack-dev-server": "5.2.0",
43 | "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/scripts/postinstall.js:
--------------------------------------------------------------------------------
1 | const color = '\x1b[33m%s\x1b[0m';
2 | console.log(color, "Thank you for installing ExcellentExport!");
3 | console.log(color, "If you like ExcellentExport, please consider a donation: https://github.com/sponsors/jmaister");
4 |
--------------------------------------------------------------------------------
/src/excellentexport.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * ExcellentExport 3.9.9
3 | * A client side Javascript export to Excel.
4 | *
5 | * @author: Jordi Burgos (jordiburgos@gmail.com)
6 | * @url: https://github.com/jmaister/excellentexport
7 | *
8 | */
9 |
10 | import * as XLSX from 'xlsx';
11 | import { CellTypes, FormatDefinition, PredefinedFormat, CellFormats, CellPatterns } from './format';
12 |
13 | import * as utils from './utils';
14 |
15 | // Fix for IE11: https://stackoverflow.com/questions/69485778/new-typescript-version-does-not-include-window-navigator-mssaveblob
16 | declare global {
17 | interface Navigator {
18 | msSaveBlob?: (blob: any, defaultName?: string) => boolean
19 | }
20 | }
21 |
22 | export interface ConvertOptions {
23 | anchor?: (string|HTMLAnchorElement),
24 | openAsDownload?: boolean,
25 | format: ('csv' | 'xls' | 'xlsx'),
26 | filename?: string,
27 | rtl?: boolean,
28 | }
29 | export interface FromOptions {
30 | table?: (string|HTMLTableElement),
31 | array?: any[][],
32 | }
33 |
34 | export interface SheetOptions {
35 | name: string,
36 | from: FromOptions,
37 | removeColumns?: number[],
38 | filterRowFn?(row:any[]): boolean ,
39 | fixValue?(value:any, row:number, column:number): any,
40 | fixArray?(array:any[][]): any[][],
41 | rtl?: boolean,
42 | formats?: (FormatDefinition | null)[],
43 | }
44 |
45 | /*
46 | export type ExcellentExportType = {
47 | version: () => string,
48 | formats: CellFormats,
49 | excel: (anchor:(HTMLAnchorElement|string), table:HTMLTableElement, name:string) => void,
50 | csv: (anchor:(HTMLAnchorElement|string), table:HTMLTableElement, delimiter?:string, newLine?:string) => void,
51 | convert: (options:ConvertOptions, sheets:SheetOptions[]) => void,
52 | }
53 | */
54 |
55 | const ExcellentExport = function() {
56 |
57 | const version = "3.9.9";
58 |
59 | /*
60 | ExcellentExport.convert(options, sheets);
61 |
62 | Options:
63 | {
64 | anchor: String or HTML Element,
65 | openAsDownload: boolean, // Use this options if not using an anchor tag
66 | format: 'xlsx' or 'xls' or 'csv',
67 | filename: String,
68 | rtl: boolean (optional), specify if all the workbook has text in RTL mode
69 | }
70 |
71 | Sheets must be an array of sheet configuration objects. Sheet description:
72 | [
73 | {
74 | name: 'Sheet 1', // Sheet name
75 | from: {
76 | table: String/Element, // Table ID or table element
77 | array: [...] // Array with the data. Array where each element is a row. Every row is an array of the cells.
78 | },
79 | removeColumns: [...], // Array of column indexes (from 0)
80 | filterRowFn: function(row) {return true}, // Function to decide which rows are returned
81 | fixValue: function(value, row, column) {return fixedValue} // Function to fix values, receiving value, row num, column num
82 | fixArray: function(array) {return array} // Function to manipulate the whole data array
83 | rtl: boolean // optional: specify if the sheet has text in RTL mode
84 | ...
85 | },
86 | {
87 | ...
88 | }, ...
89 | ]
90 | */
91 | const convert = function(options:ConvertOptions, sheets:SheetOptions[]) {
92 | const workbook = {
93 | SheetNames: [],
94 | Sheets: {},
95 | Views: []
96 | };
97 |
98 | if (!options.format) {
99 | throw new Error("'format' option must be defined");
100 | }
101 | if (options.format === 'csv' && sheets.length > 1) {
102 | throw new Error("'csv' format only supports one sheet");
103 | }
104 |
105 | sheets.forEach(function(sheetConf:SheetOptions, index:number) {
106 | const name = sheetConf.name;
107 | if (!name) {
108 | throw new Error('Sheet ' + index + ' must have the property "name".');
109 | }
110 |
111 | // Select data source
112 | let dataArray: any[][];
113 | if (sheetConf.from && sheetConf.from.table) {
114 | dataArray = utils.tableToArray(utils.getTable(sheetConf.from.table));
115 | } else if(sheetConf.from && sheetConf.from.array) {
116 | dataArray = sheetConf.from.array
117 | } else {
118 | throw new Error('No data for sheet: [' + name + ']');
119 | }
120 |
121 | // Filter rows
122 | if (sheetConf.filterRowFn) {
123 | if (sheetConf.filterRowFn instanceof Function) {
124 | dataArray = dataArray.filter(sheetConf.filterRowFn);
125 | } else {
126 | throw new Error('Parameter "filterRowFn" must be a function.');
127 | }
128 | }
129 | // Filter columns
130 | if (sheetConf.removeColumns) {
131 | utils.removeColumns(dataArray, sheetConf.removeColumns);
132 | }
133 |
134 | // Convert data. Function applied to each value independently, receiving (value, rownum, colnum)
135 | if (sheetConf.fixValue && typeof sheetConf.fixValue === 'function') {
136 | const fn = sheetConf.fixValue;
137 | dataArray.map((r, rownum) => {
138 | r.map((value, colnum) => {
139 | dataArray[rownum][colnum] = fn(value, rownum, colnum);
140 | });
141 | });
142 | }
143 |
144 | // Convert data, whole array
145 | if (sheetConf.fixArray && typeof sheetConf.fixArray === 'function') {
146 | const fn = sheetConf.fixArray;
147 | dataArray = fn(dataArray);
148 | }
149 |
150 | // Create sheet
151 | workbook.SheetNames.push(name);
152 | const worksheet = XLSX.utils.aoa_to_sheet(dataArray, {sheet: name} as XLSX.AOA2SheetOpts);
153 |
154 | // Apply format
155 | if (sheetConf.formats) {
156 | sheetConf.formats.forEach(f => {
157 | const range = XLSX.utils.decode_range(f.range);
158 | for (let R = range.s.r; R <= range.e.r; ++R) {
159 | for (let C = range.s.c; C <= range.e.c; ++C) {
160 | const cell = worksheet[XLSX.utils.encode_cell({r: R, c: C})];
161 | if (cell && utils.hasContent(cell.v)) {
162 | // type
163 | cell.t = f.format.type;
164 |
165 | // type fix
166 | if (f.format?.type == CellTypes.BOOLEAN) {
167 | const v = cell.v.toString().toLowerCase();
168 | if (v == 'true' || v == '1') cell.v = true;
169 | if (v == 'false' || v == '0') cell.v = false;
170 | }
171 | // pattern
172 | if (f.format?.pattern) {
173 | cell.z = f.format.pattern;
174 | }
175 | }
176 | }
177 | }
178 | });
179 | }
180 |
181 |
182 | workbook.Sheets[name] = worksheet;
183 | workbook.Views.push({RTL: options.rtl || sheetConf.rtl || false});
184 | });
185 |
186 | const wbOut:string = XLSX.write(workbook, {bookType: options.format, bookSST:true, type: 'binary', compression: true});
187 | try {
188 | const blob = new Blob([utils.string2ArrayBuffer(wbOut)], { type: "application/octet-stream" });
189 | const filename = (options.filename || 'download') + '.' + options.format;
190 | // Support for IE.
191 | if (window.navigator.msSaveBlob) {
192 | window.navigator.msSaveBlob(blob, filename);
193 | return false;
194 | }
195 | if (options.anchor) {
196 | const anchor = utils.getAnchor(options.anchor);
197 | anchor.href = window.URL.createObjectURL(blob);
198 | anchor.download = filename;
199 | } else if (options.openAsDownload) {
200 | const a = document.createElement("a");
201 | a.href = URL.createObjectURL(blob);
202 | a.download = filename;
203 | document.body.appendChild(a);
204 | a.click();
205 | document.body.removeChild(a);
206 | } else {
207 | throw new Error('Options should specify an anchor or openAsDownload=true.')
208 | }
209 |
210 | } catch(e) {
211 | throw new Error('Error converting to '+ options.format + '. ' + e);
212 | }
213 | return wbOut;
214 |
215 | };
216 |
217 | return {
218 | version: function(): string {
219 | return version;
220 | },
221 | excel: function(anchor:(HTMLAnchorElement|string), table:HTMLTableElement, name:string) {
222 | table = utils.getTable(table);
223 | anchor = utils.getAnchor(anchor);
224 | const ctx = {worksheet: name || 'Worksheet', table: table.innerHTML};
225 | const b64 = utils.base64(utils.format(utils.templates.excel, ctx));
226 | return utils.createDownloadLink(anchor, b64, 'application/vnd.ms-excel','export.xls');
227 | },
228 | csv: function(anchor:(HTMLAnchorElement|string), table:HTMLTableElement, delimiter?:string, newLine?:string) {
229 | let csvDelimiter = ",";
230 | let csvNewLine = "\r\n";
231 |
232 | if (delimiter !== undefined && delimiter) {
233 | csvDelimiter = delimiter;
234 | }
235 | if (newLine !== undefined && newLine) {
236 | csvNewLine = newLine;
237 | }
238 |
239 | table = utils.getTable(table);
240 | anchor = utils.getAnchor(anchor);
241 | const csvData = "\uFEFF" + utils.tableToCSV(table, csvDelimiter, csvNewLine);
242 | const b64 = utils.base64(csvData);
243 | return utils.createDownloadLink(anchor, b64, 'application/csv', 'export.csv');
244 | },
245 | convert: function(options:ConvertOptions, sheets:SheetOptions[]) {
246 | return convert(options, sheets);
247 | },
248 | formats: PredefinedFormat,
249 | cellTypes: CellTypes,
250 | cellPatterns: CellPatterns,
251 | };
252 | }();
253 |
254 | export default ExcellentExport;
255 |
--------------------------------------------------------------------------------
/src/format.ts:
--------------------------------------------------------------------------------
1 |
2 | // Constants for cell types
3 | export enum CellTypes {
4 | TEXT = 's',
5 | NUMBER = 'n',
6 | DATE = 'd',
7 | BOOLEAN = 'b',
8 | }
9 |
10 | // Constants for cell patterns
11 | export enum CellPatterns {
12 | INTEGER = '0',
13 | DECIMAL = '0.00',
14 | DATE = 'dd/mm/yyyy',
15 | TIME = 'hh:mm:ss',
16 | DATETIME = 'dd/mm/yyyy hh:mm:ss',
17 | CURRENCY = '[$$-409]#,##0.00;[RED]-[$$-409]#,##0.00',
18 | PERCENTAGE = '0.00%',
19 | EXPONENT = '0.00E+00',
20 | TEXT = '@',
21 | }
22 |
23 | export type CellType = 's' | 'n' | 'd' | 'b';
24 |
25 | export interface CellFormat {
26 | type: CellType,
27 | pattern?: string,
28 | }
29 |
30 | // Define structure for predefined formats
31 | export interface CellFormats {
32 | [key: string]: CellFormat
33 | }
34 | export const PredefinedFormat : CellFormats = {
35 | NUMBER: { type: CellTypes.NUMBER},
36 | INTEGER: { type: CellTypes.NUMBER, pattern: CellPatterns.INTEGER },
37 | DECIMAL: { type: CellTypes.NUMBER, pattern: CellPatterns.DECIMAL },
38 | CURRENCY: { type: CellTypes.NUMBER, pattern: CellPatterns.CURRENCY },
39 | PERCENTAGE: { type: CellTypes.NUMBER, pattern: CellPatterns.PERCENTAGE },
40 | EXPONENT: { type: CellTypes.NUMBER, pattern: CellPatterns.EXPONENT },
41 |
42 | DATE: { type: CellTypes.DATE, pattern: CellPatterns.DATE },
43 |
44 | TIME: { type: CellTypes.DATE, pattern: CellPatterns.TIME },
45 | DATETIME: { type: CellTypes.DATE, pattern: CellPatterns.DATETIME },
46 |
47 | TEXT: { type: CellTypes.TEXT, pattern: CellPatterns.TEXT },
48 |
49 | BOOLEAN: { type: CellTypes.BOOLEAN },
50 | }
51 |
52 | export interface FormatDefinition {
53 | range: string,
54 | format?: CellFormat,
55 | }
56 |
57 |
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 |
2 | export const b64toBlob = function (b64Data:string, contentType:string, sliceSize?:number): Blob {
3 | // function taken from http://stackoverflow.com/a/16245768/2591950
4 | // author Jeremy Banks http://stackoverflow.com/users/1114/jeremy-banks
5 | contentType = contentType || '';
6 | sliceSize = sliceSize || 512;
7 |
8 | const byteCharacters = atob(b64Data);
9 | const byteArrays = [];
10 |
11 | for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
12 | const slice = byteCharacters.slice(offset, offset + sliceSize);
13 |
14 | const byteNumbers = new Array(slice.length);
15 | for (let i = 0; i < slice.length; i = i + 1) {
16 | byteNumbers[i] = slice.charCodeAt(i);
17 | }
18 |
19 | const byteArray = new Uint8Array(byteNumbers);
20 |
21 | byteArrays.push(byteArray);
22 | }
23 |
24 | return new Blob(byteArrays, {
25 | type: contentType
26 | });
27 | };
28 |
29 | export const templates = {excel: ' '};
30 |
31 | /**
32 | * Convert a string to Base64.
33 | */
34 | export const base64 = function(s:string) : string {
35 | return btoa(unescape(encodeURIComponent(s)));
36 | };
37 |
38 | export const format = function(s:string, context:any) : string {
39 | return s.replace(new RegExp("{(\\w+)}", "g"), function(m, p) {
40 | return context[p] || "{" + p + "}";
41 | });
42 | };
43 |
44 | /**
45 | * Get element by ID.
46 | * @param {*} element
47 | */
48 | export const getTable = function(element :(HTMLTableElement|string)) : HTMLTableElement {
49 | if (typeof element === 'string') {
50 | return document.getElementById(element) as HTMLTableElement;
51 | }
52 | return element;
53 | };
54 |
55 | /**
56 | * Get element by ID.
57 | * @param {*} element
58 | */
59 | export const getAnchor = function(element :(HTMLAnchorElement|string)) : HTMLAnchorElement {
60 | if (typeof element === 'string') {
61 | return document.getElementById(element) as HTMLAnchorElement;
62 | }
63 | return element;
64 | };
65 |
66 | /**
67 | * Encode a value for CSV.
68 | * @param {*} value
69 | */
70 | export const fixCSVField = function(value:string, csvDelimiter:string) : string {
71 | let fixedValue = value;
72 | const addQuotes = (value.indexOf(csvDelimiter) !== -1) || (value.indexOf('\r') !== -1) || (value.indexOf('\n') !== -1);
73 | const replaceDoubleQuotes = (value.indexOf('"') !== -1);
74 |
75 | if (replaceDoubleQuotes) {
76 | fixedValue = fixedValue.replace(/"/g, '""');
77 | }
78 | if (addQuotes || replaceDoubleQuotes) {
79 | fixedValue = '"' + fixedValue + '"';
80 | }
81 |
82 | return fixedValue;
83 | };
84 |
85 | export const tableToArray = function(table:HTMLTableElement) : any[][] {
86 | let tableInfo = Array.prototype.map.call(table.querySelectorAll('tr'), function(tr) {
87 | return Array.prototype.map.call(tr.querySelectorAll('th,td'), function(td) {
88 | return td.innerHTML;
89 | });
90 | });
91 | return tableInfo;
92 | };
93 |
94 | export const tableToCSV = function(table:HTMLTableElement, csvDelimiter:string = ',', csvNewLine:string = '\n') : string {
95 | let data = "";
96 | for (let i = 0; i < table.rows.length; i=i+1) {
97 | const row = table.rows[i];
98 | for (let j = 0; j < row.cells.length; j=j+1) {
99 | const col = row.cells[j];
100 | data = data + (j ? csvDelimiter : '') + fixCSVField(col.textContent.trim(), csvDelimiter);
101 | }
102 | data = data + csvNewLine;
103 | }
104 | return data;
105 | };
106 |
107 | export const createDownloadLink = function(anchor:HTMLAnchorElement, base64data:string, exporttype:string, filename:string) : boolean {
108 | if (window.navigator.msSaveBlob) {
109 | const blob = b64toBlob(base64data, exporttype);
110 | window.navigator.msSaveBlob(blob, filename);
111 | return false;
112 | } else if (window.URL.createObjectURL) {
113 | const blob = b64toBlob(base64data, exporttype);
114 | anchor.href = window.URL.createObjectURL(blob);
115 | } else {
116 | anchor.download = filename;
117 | anchor.href = "data:" + exporttype + ";base64," + base64data;
118 | }
119 |
120 | // Return true to allow the link to work
121 | return true;
122 | };
123 |
124 | // String to ArrayBuffer
125 | export const string2ArrayBuffer = function (s:string): ArrayBuffer {
126 | let buf = new ArrayBuffer(s.length);
127 | let view = new Uint8Array(buf);
128 | for (let i=0; i !== s.length; ++i) {
129 | view[i] = s.charCodeAt(i) & 0xFF;
130 | }
131 | return buf;
132 | };
133 |
134 | export const removeColumns = function(dataArray:any[][], columnIndexes:number[]) {
135 | const uniqueIndexes = [...new Set(columnIndexes)].sort().reverse();
136 | uniqueIndexes.forEach(function(columnIndex) {
137 | dataArray.forEach(function(row) {
138 | row.splice(columnIndex, 1);
139 | });
140 | });
141 | };
142 |
143 | export const hasContent = function(value:any) : boolean {
144 | return value !== undefined && value !== null && value !== "";
145 | }
--------------------------------------------------------------------------------
/test/checkversion.test.ts:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 |
3 | import ExcellentExport from '../src/excellentexport';
4 | const pkg = require('../package.json');
5 |
6 | describe('version() API', function() {
7 | describe('get version', function() {
8 | it('should get the current version number', function() {
9 | const version = ExcellentExport.version();
10 |
11 | assert.ok(version, 'Version must be returned');
12 | assert.equal(pkg.version, version, "Version must be " + pkg.version);
13 | });
14 | });
15 | });
16 |
17 |
--------------------------------------------------------------------------------
/test/convert-filters.test.ts:
--------------------------------------------------------------------------------
1 |
2 | import 'expect-puppeteer';
3 |
4 | import ExcellentExport, { ConvertOptions, SheetOptions } from '../src/excellentexport';
5 |
6 |
7 | describe('convert() API', function() {
8 | describe('test sheet options', function() {
9 |
10 | beforeEach(() => {
11 | window.URL.createObjectURL = () => "blob:fake_URL";
12 |
13 | document.body.innerHTML = '';
14 | const element = document.createElement("div");
15 | element.innerHTML =
16 | '' +
17 | 'Link';
18 |
19 | document.body.appendChild(element);
20 | });
21 |
22 | test('filterRowFn', function() {
23 | const options = {
24 | anchor: 'anchor',
25 | filename: 'data_from_table',
26 | format: 'xlsx'
27 | } as ConvertOptions;
28 |
29 | const sheets = [
30 | {
31 | name: 'Sheet Name Here 1',
32 | from: {
33 | table: 'sometable'
34 | },
35 | filterRowFn: (row) => {
36 | if (row[0] === 'first') {
37 | return true;
38 | }
39 | }
40 | }
41 | ] as SheetOptions[];
42 |
43 | const workbook = ExcellentExport.convert(options, sheets);
44 | expect(workbook).not.toBeNull();
45 | });
46 |
47 | test('removeColumns', function() {
48 | const options = {
49 | anchor: 'anchor',
50 | filename: 'data_from_table',
51 | format: 'xlsx'
52 | } as ConvertOptions;
53 |
54 | const sheets = [
55 | {
56 | name: 'Sheet Name Here 1',
57 | from: {
58 | table: 'sometable'
59 | },
60 | removeColumns: [1]
61 | }
62 | ] as SheetOptions[];
63 |
64 | const workbook = ExcellentExport.convert(options, sheets);
65 | expect(workbook).not.toBeNull();
66 | });
67 |
68 | });
69 | });
70 |
71 |
--------------------------------------------------------------------------------
/test/convert-table.test.ts:
--------------------------------------------------------------------------------
1 |
2 | import 'expect-puppeteer';
3 |
4 | import ExcellentExport, { ConvertOptions, SheetOptions } from '../src/excellentexport';
5 |
6 |
7 | describe('convert() API', function() {
8 | describe('convert from HTML table', function() {
9 |
10 | beforeEach(() => {
11 | window.URL.createObjectURL = () => "blob:fake_URL";
12 |
13 | document.body.innerHTML = '';
14 | const element = document.createElement("div");
15 | element.innerHTML =
16 | '' +
17 | 'Link';
18 |
19 | document.body.appendChild(element);
20 | });
21 |
22 | test('should create a XLSX from HTML table by #id', function() {
23 | const options = {
24 | anchor: 'anchor',
25 | filename: 'data_from_table',
26 | format: 'xlsx'
27 | } as ConvertOptions;
28 |
29 | const sheets = [
30 | {
31 | name: 'Sheet Name Here 1',
32 | from: {
33 | table: 'sometable'
34 | }
35 | }
36 | ] as SheetOptions[];
37 |
38 | const workbook = ExcellentExport.convert(options, sheets);
39 | expect(workbook).not.toBeNull();
40 |
41 | const anchor = document.getElementById('anchor') as HTMLAnchorElement;
42 | expect(anchor.href).not.toBeNull();
43 | expect(anchor.href).toMatch(/blob:/);
44 | });
45 |
46 | test('should create a XLSX from HTML table by DOM element', function() {
47 | const options = {
48 | anchor: document.getElementById('anchor'),
49 | filename: 'data_from_table',
50 | format: 'xlsx'
51 | } as ConvertOptions;
52 |
53 | const sheets = [
54 | {
55 | name: 'Sheet Name Here 1',
56 | from: {
57 | table: document.getElementById('sometable')
58 | }
59 | }
60 | ] as SheetOptions[];
61 |
62 | const workbook = ExcellentExport.convert(options, sheets);
63 | expect(workbook).not.toBeNull();
64 |
65 | const anchor = document.getElementById('anchor') as HTMLAnchorElement;
66 | expect(anchor.href).not.toBeNull();
67 | expect(anchor.href).toMatch(/blob:/);
68 | });
69 | });
70 | });
71 |
72 |
--------------------------------------------------------------------------------
/test/convert.format.ts:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 |
3 | import ExcellentExport, { ConvertOptions, SheetOptions } from '../src/excellentexport';
4 | import { PredefinedFormat } from '../src/format';
5 |
6 |
7 | describe('convert() API with column formats', function() {
8 |
9 | beforeEach(() => {
10 | window.URL.createObjectURL = () => "blob:fake_URL";
11 |
12 | document.body.innerHTML = '';
13 | const element = document.createElement("div");
14 | element.innerHTML = 'Link';
15 |
16 | document.body.appendChild(element);
17 | });
18 |
19 | it('should create a XLSX with types', function() {
20 | const options = {
21 | anchor: 'anchor',
22 | filename: 'data_from_array',
23 | format: 'xlsx'
24 | } as ConvertOptions;
25 |
26 | const sheets = [
27 | {
28 | name: 'People',
29 | from: {
30 | array: [
31 | ["ID", "Name", "Birthdate", "Active", "Salary"],
32 | [11, "John", "1980-01-01", true, 1000.98],
33 | [22, "Mary", "1985-02-02", false, 2000.88],
34 | [33, "Peter", "1990-03-03", true, 3000.32],
35 | ]
36 | },
37 | formats: [
38 | { range: 'A2:A10', format: PredefinedFormat.INTEGER },
39 | { range: 'C2:C10', format: PredefinedFormat.DATE },
40 | { range: 'D2:D10', format: PredefinedFormat.BOOLEAN },
41 | { range: 'E2:E10', format: PredefinedFormat.DECIMAL },
42 | ]
43 | },
44 |
45 | ] as SheetOptions[];
46 |
47 | const workbook = ExcellentExport.convert(options, sheets);
48 |
49 | assert.ok(workbook, 'Result must not be null');
50 |
51 | const anchor = document.getElementById('anchor') as HTMLAnchorElement;
52 | assert.ok(anchor.href, 'Element must have href');
53 | assert.ok(anchor.href.indexOf('blob:') === 0, 'Element href myst be a blob:');
54 | });
55 | });
56 |
57 |
--------------------------------------------------------------------------------
/test/convert.test.ts:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 |
3 | import ExcellentExport, { ConvertOptions, SheetOptions } from '../src/excellentexport';
4 |
5 |
6 | describe('convert() API', function() {
7 | describe('convert from array', function() {
8 |
9 | beforeEach(() => {
10 | window.URL.createObjectURL = () => "blob:fake_URL";
11 |
12 | document.body.innerHTML = '';
13 | const element = document.createElement("div");
14 | element.innerHTML = 'Link';
15 |
16 | document.body.appendChild(element);
17 | });
18 |
19 | it('should create a XLSX from array', function() {
20 | const options = {
21 | anchor: 'anchor',
22 | filename: 'data_from_array',
23 | format: 'xlsx'
24 | } as ConvertOptions;
25 |
26 | const sheets = [
27 | {
28 | name: 'Sheet Name Here 1',
29 | from: {
30 | array: [
31 | [1, 2, 3],
32 | ['hello', '2200', 'bye'],
33 | ['quo"te', 'dobl"e qu"ote', 'singl\'e quote']
34 | ]
35 | }
36 | },
37 | {
38 | name: 'Sheet Number 2',
39 | from: {
40 | array: [
41 | [6666, 7777, 8888],
42 | ['lorem', 'ipsum', 'dolor']
43 | ]
44 | }
45 | },
46 |
47 | ] as SheetOptions[];
48 |
49 | const workbook = ExcellentExport.convert(options, sheets);
50 |
51 | assert.ok(workbook, 'Result must not be null');
52 |
53 | const anchor = document.getElementById('anchor') as HTMLAnchorElement;
54 | assert.ok(anchor.href, 'Element must have href');
55 | assert.ok(anchor.href.indexOf('blob:') === 0, 'Element href myst be a blob:');
56 | });
57 | });
58 | });
59 |
60 |
--------------------------------------------------------------------------------
/test/fixdata.test.ts:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 |
3 | import ExcellentExport, { ConvertOptions } from '../src/excellentexport';
4 |
5 |
6 | describe('Fix data', function() {
7 | beforeEach(() => {
8 | window.URL.createObjectURL = () => "blob:fake_URL";
9 |
10 | document.body.innerHTML = '';
11 | const element = document.createElement("div");
12 | element.innerHTML = 'Link';
13 |
14 | document.body.appendChild(element);
15 | });
16 |
17 | it('should fix values', function() {
18 | const options = {
19 | anchor: 'anchor',
20 | filename: 'data_from_array',
21 | format: 'xlsx'
22 | } as ConvertOptions;
23 |
24 | const sheets = [
25 | {
26 | name: 'Sheet Name Here 1',
27 | from: {
28 | array: [
29 | ['hello', 'hello | ', 'bye'],
30 | ]
31 | },
32 | fixValue: (value, row, col) => {
33 | let v = value.replace(/
/gi, "\n");
34 | let strippedString = v.replace(/(<([^>]+)>)/gi, "");
35 | return strippedString;
36 | }
37 | }
38 | ];
39 |
40 | const workbook = ExcellentExport.convert(options, sheets);
41 | assert.ok(workbook, 'Result must not be null');
42 | });
43 |
44 | it('should process the whole array', function() {
45 | const options = {
46 | anchor: 'anchor',
47 | filename: 'data_from_array',
48 | format: 'xlsx'
49 | } as ConvertOptions;
50 |
51 | const sheets = [
52 | {
53 | name: 'Sheet Name Here 1',
54 | from: {
55 | array: [
56 | ['hello', 'hello | ', 'bye'],
57 | ]
58 | },
59 | fixData: (array) => {
60 | return array.map(r => {
61 | return r.map(v => {
62 | return "fixed-" + v;
63 | })
64 | });
65 | }
66 | }
67 | ];
68 |
69 | const workbook = ExcellentExport.convert(options, sheets);
70 | assert.ok(workbook, 'Result must not be null');
71 | });
72 |
73 | });
74 |
75 |
--------------------------------------------------------------------------------
/test/negative.test.ts:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 |
3 | import ExcellentExport, { ConvertOptions, SheetOptions } from '../src/excellentexport';
4 |
5 |
6 | describe('convert() API', function() {
7 | describe('Negative tests', function() {
8 |
9 | beforeEach(() => {
10 | window.URL.createObjectURL = () => "blob:fake_URL";
11 |
12 | const element = document.createElement("div");
13 | element.innerHTML = 'Link';
14 |
15 | document.body.appendChild(element);
16 | });
17 |
18 | it('should fail if CSV has more than one sheet', function() {
19 | const options = {
20 | anchor: 'anchor',
21 | filename: 'data_from_array',
22 | format: 'csv'
23 | } as ConvertOptions;
24 |
25 | const sheets = [{
26 | name: 'Sheet Name Here 1',
27 | from: {}
28 | }, {
29 | name: 'Sheet Number 2',
30 | from: {}
31 | }];
32 |
33 | assert.throws(() => {
34 | ExcellentExport.convert(options, sheets)
35 | }, Error);
36 |
37 | });
38 |
39 | it('should fail if sheet does not have name', function() {
40 | const options = {
41 | anchor: 'anchor',
42 | filename: 'data_from_array',
43 | format: 'csv'
44 | } as ConvertOptions;
45 |
46 | const sheets = [{
47 | // name: 'Sheet Name Here 1',
48 | from: {}
49 | }] as SheetOptions[];
50 |
51 | assert.throws(() => {
52 | ExcellentExport.convert(options, sheets)
53 | }, Error);
54 |
55 | });
56 |
57 | it('should fail if sheet does not have data', function() {
58 | const options = {
59 | anchor: 'anchor',
60 | filename: 'data_from_array',
61 | format: 'csv'
62 | } as ConvertOptions;
63 |
64 | const sheets = [{
65 | name: 'Sheet Name Here 1',
66 | // from: {}
67 | }] as SheetOptions[];
68 |
69 | assert.throws(() => {
70 | ExcellentExport.convert(options, sheets)
71 | }, Error);
72 |
73 | });
74 |
75 | it('should fail if there is not format defined', function() {
76 | const options = {
77 | anchor: 'anchor',
78 | filename: 'data_from_array',
79 | //format: 'csv'
80 | } as ConvertOptions;
81 |
82 | const sheets = [{
83 | name: 'Sheet Name Here 1',
84 | from: {}
85 | }];
86 |
87 | assert.throws(() => {
88 | ExcellentExport.convert(options, sheets)
89 | }, Error);
90 |
91 | });
92 |
93 | it('should fail if anchor is not defined/valid', function() {
94 | const options = {
95 | anchor: 'anchor1235d5d5d5d_invalid',
96 | filename: 'data_from_array',
97 | format: 'csv'
98 | } as ConvertOptions;
99 |
100 | const sheets = [{
101 | name: 'Sheet Name Here 1',
102 | from: {}
103 | }];
104 |
105 | assert.throws(() => {
106 | ExcellentExport.convert(options, sheets)
107 | }, Error);
108 |
109 | });
110 |
111 | it('should fail if no anchor and not openAsDownload', function() {
112 | const options = {
113 | filename: 'data_from_array',
114 | format: 'csv'
115 | } as ConvertOptions;
116 |
117 | const sheets = [{
118 | name: 'Sheet Name Here 1',
119 | from: {}
120 | }];
121 |
122 | assert.throws(() => {
123 | ExcellentExport.convert(options, sheets)
124 | }, Error);
125 |
126 | });
127 | });
128 | });
129 |
130 |
--------------------------------------------------------------------------------
/test/simple.test.ts:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 |
3 | describe('Array', function() {
4 | describe('#indexOf()', function() {
5 | it('should return -1 when the value is not present', function() {
6 | assert.equal(-1, [1,2,3].indexOf(4));
7 | });
8 | });
9 | });
10 |
11 |
--------------------------------------------------------------------------------
/test/utils.test.ts:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 |
3 | import * as utils from '../src/utils';
4 |
5 |
6 | describe('Test utility functions', () => {
7 |
8 | describe('base64', () => {
9 | it('should encode a string to base64', () => {
10 | assert.equal(utils.base64('test'), 'dGVzdA==');
11 | });
12 |
13 | it('should encode a unicode string to base64', () => {
14 | assert.equal(utils.base64('test\u00A9'), "dGVzdMKp");
15 | });
16 |
17 | });
18 |
19 |
20 | describe('test format function', () => {
21 | it('should format a string', () => {
22 | assert.equal(utils.format('aaaa {a} bbbb {b} cccc', {a:'1', b:'2', c:'3', d:'4'}), 'aaaa 1 bbbb 2 cccc');
23 | });
24 |
25 | it('should not replace if no data is provided', () => {
26 | assert.equal(utils.format('aaaa {a} bbbb {b} cccc', {}), 'aaaa {a} bbbb {b} cccc');
27 | });
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/test/utils_fixdata.test.ts:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 |
3 | import { fixCSVField } from '../src/utils';
4 |
5 |
6 | describe('Test utility functions: csv functions', () => {
7 |
8 | it('should keep the value if not delimiter found', () => {
9 | assert.equal(fixCSVField('test', ','), 'test');
10 | });
11 |
12 | it('should fix a string with double quotes', () => {
13 | const str = 'aaa"bbb';
14 | const result = fixCSVField(str, "\"");
15 | assert.equal(result, '\"aaa\"\"bbb\"');
16 | });
17 |
18 | it('should fix a field with space delimiter', () => {
19 | const str = 'aaa bbb';
20 | const result = fixCSVField(str, " ");
21 | assert.equal(result, '\"aaa bbb\"');
22 | });
23 |
24 | });
25 |
--------------------------------------------------------------------------------
/test/utils_removeColumns.test.ts:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 |
3 | import { removeColumns } from '../src/utils';
4 |
5 |
6 | describe('Test utility functions: removeColumns', () => {
7 |
8 | it('should remove one column correctly', function() {
9 | const columns = [
10 | ['a', 'b', 'c'],
11 | ['d', 'e', 'f'],
12 | ['g', 'h', 'i'],
13 | ];
14 |
15 | removeColumns(columns, [0]);
16 | expect(columns.length).toEqual(3)
17 | expect(columns[0]).toStrictEqual(['b', 'c']);
18 | expect(columns[1]).toStrictEqual(['e', 'f']);
19 | expect(columns[2]).toStrictEqual(['h', 'i']);
20 | });
21 |
22 | it('should remove two columns', () => {
23 | const columns = [
24 | ['a', 'b', 'c'],
25 | ['d', 'e', 'f'],
26 | ['g', 'h', 'i'],
27 | ];
28 |
29 | removeColumns(columns, [0, 1]);
30 | expect(columns.length).toEqual(3)
31 | expect(columns[0]).toStrictEqual(['c']);
32 | expect(columns[1]).toStrictEqual(['f']);
33 | });
34 |
35 | it('should remove the last column', () => {
36 | const columns = [
37 | ['a', 'b', 'c'],
38 | ['d', 'e', 'f'],
39 | ['g', 'h', 'i'],
40 | ];
41 |
42 | removeColumns(columns, [2]);
43 | expect(columns.length).toEqual(3)
44 | expect(columns[0]).toStrictEqual(['a', 'b']);
45 | expect(columns[1]).toStrictEqual(['d', 'e']);
46 | expect(columns[2]).toStrictEqual(['g', 'h']);
47 | });
48 |
49 | it('should skip if remove out of range', () => {
50 | const columns = [
51 | ['a', 'b', 'c'],
52 | ['d', 'e', 'f'],
53 | ['g', 'h', 'i'],
54 | ];
55 |
56 | removeColumns(columns, [99]);
57 | expect(columns.length).toEqual(3);
58 | expect(columns[0]).toEqual(['a', 'b', 'c']);
59 | });
60 |
61 | it('should skip if remove out of range', () => {
62 | const columns = [
63 | ['a', 'b', 'c'],
64 | ['d', 'e', 'f'],
65 | ['g', 'h', 'i'],
66 | ];
67 |
68 | removeColumns(columns, [0, 99]);
69 | expect(columns.length).toEqual(3);
70 | expect(columns[0]).toEqual(['b', 'c']);
71 | });
72 |
73 | it('should not remove repeated columns', () => {
74 | const columns = [
75 | ['a', 'b', 'c'],
76 | ['d', 'e', 'f'],
77 | ['g', 'h', 'i'],
78 | ];
79 |
80 | removeColumns(columns, [0, 0]);
81 | expect(columns.length).toEqual(3);
82 | expect(columns[0]).toEqual(['b', 'c']);
83 | });
84 |
85 | it('should remove columns in the correct order', () => {
86 | const columns = [
87 | ['a', 'b', 'c'],
88 | ['d', 'e', 'f'],
89 | ['g', 'h', 'i'],
90 | ];
91 |
92 | removeColumns(columns, [0, 2]);
93 | expect(columns.length).toEqual(3);
94 | expect(columns[0]).toEqual(['b']);
95 | expect(columns[1]).toEqual(['e']);
96 | });
97 |
98 | });
99 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "./dist",
4 | "allowJs": true,
5 | "target": "ESNext",
6 | "sourceMap": true,
7 | "removeComments": false,
8 | "declaration": true,
9 | "rootDir": "src",
10 | "moduleResolution": "node",
11 | "esModuleInterop": true
12 | },
13 | "include": [
14 | "src/**/*.ts",
15 | "test/**/*.{ts,js}"
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | entry: './src/excellentexport.ts',
5 | // devtool: "inline-source-map",
6 | output: {
7 | path: path.resolve(__dirname, 'dist'),
8 | filename: 'excellentexport.js',
9 | library: 'ExcellentExport',
10 | libraryTarget: 'umd',
11 | libraryExport: 'default',
12 | auxiliaryComment: 'ExcellentExport.js'
13 | },
14 | module: {
15 | rules: [
16 | {
17 | test: /\.tsx?$/,
18 | exclude: /(node_modules)/,
19 | use: {
20 | loader: 'ts-loader'
21 | }
22 | }
23 | ]
24 | },
25 | performance: {
26 | hints: false
27 | },
28 | resolve: {
29 | extensions: ['.ts', '.js']
30 | },
31 | };
32 |
--------------------------------------------------------------------------------