├── .eslintrc.json ├── .stylelintrc.json ├── CHANGELOG.md ├── index.js ├── .gitignore ├── .editorconfig ├── LICENSE.md ├── package.json ├── .github └── workflows │ └── main.yml ├── test └── index.js ├── lib └── xlsx.js └── README.md /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ "apostrophe" ] 3 | } 4 | -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-apostrophe" 3 | } 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.0.0 (2024-05-15) 4 | 5 | ### Adds 6 | 7 | * Initial release. -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const xlsx = require('./lib/xlsx'); 2 | 3 | module.exports = { 4 | improve: '@apostrophecms/import-export', 5 | init(self) { 6 | self.registerFormats({ xlsx }); 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore MacOS X metadata forks (fusefs) 2 | ._* 3 | package-lock.json 4 | *.DS_Store 5 | node_modules 6 | 7 | # Never commit a CSS map file, anywhere 8 | *.css.map 9 | 10 | # vim swp files 11 | .*.sw* 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | indent_size = 2 9 | indent_style = space 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 Apostrophe Technologies 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@apostrophecms/import-export-xlsx", 3 | "version": "1.0.0", 4 | "description": "XLSX format for Import Export module", 5 | "main": "index.js", 6 | "scripts": { 7 | "eslint": "eslint --ext .js,.vue .", 8 | "lint": "npm run eslint", 9 | "test": "npm run lint && mocha --exit" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/apostrophecms/import-export-xlsx.git" 14 | }, 15 | "homepage": "https://github.com/apostrophecms/import-export-xlsx#readme", 16 | "author": "Apostrophe Technologies", 17 | "license": "UNLICENSED", 18 | "devDependencies": { 19 | "eslint": "^8.44.0", 20 | "eslint-config-apostrophe": "^4.2.0", 21 | "eslint-config-standard": "^17.1.0", 22 | "eslint-plugin-import": "^2.29.0", 23 | "eslint-plugin-node": "^11.1.0", 24 | "eslint-plugin-promise": "^6.1.1", 25 | "mocha": "^10.2.0", 26 | "stylelint": "^15.9.0", 27 | "stylelint-config-apostrophe": "^3.0.0" 28 | }, 29 | "dependencies": { 30 | "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz" 31 | } 32 | } -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: tests 4 | 5 | # Controls when the action will run. 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | pull_request: 10 | branches: [ '*' ] 11 | 12 | # Allows you to run this workflow manually from the Actions tab 13 | workflow_dispatch: 14 | 15 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 16 | jobs: 17 | # This workflow contains a single job called "build" 18 | build: 19 | # The type of runner that the job will run on 20 | runs-on: ubuntu-latest 21 | strategy: 22 | matrix: 23 | node-version: [16, 18, 20] 24 | mongodb-version: ['4.4', '5.0', '6.0'] 25 | 26 | # Steps represent a sequence of tasks that will be executed as part of the job 27 | steps: 28 | - name: Git checkout 29 | uses: actions/checkout@v3 30 | 31 | - name: Use Node.js ${{ matrix.node-version }} 32 | uses: actions/setup-node@v3 33 | with: 34 | node-version: ${{ matrix.node-version }} 35 | 36 | - name: Start MongoDB 37 | uses: supercharge/mongodb-github-action@1.8.0 38 | with: 39 | mongodb-version: ${{ matrix.mongodb-version }} 40 | 41 | - run: npm install 42 | 43 | - run: npm test 44 | env: 45 | CI: true 46 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | const assert = require('assert').strict; 4 | const xlsx = require('../lib/xlsx'); 5 | 6 | describe('@apostrophecms/import-export-xlsx', function () { 7 | const filepath = path.join(__dirname, 'test.xlsx'); 8 | 9 | this.timeout(2000); 10 | 11 | after(() => { 12 | fs.unlinkSync(filepath); 13 | }); 14 | 15 | describe('output', function () { 16 | it('should write a .xlsx file with the docs and their properties stringified', async function () { 17 | const docs = [ 18 | { 19 | name: 'John Doe', 20 | age: 42, 21 | object: { foo: 'bar' }, 22 | array: [ 1, 2, { foo: 'bar' } ] 23 | }, 24 | { 25 | name: 'Jane Doe', 26 | age: 43, 27 | empty: '' 28 | } 29 | ]; 30 | await xlsx.output(filepath, { docs }); 31 | }); 32 | }); 33 | 34 | describe('input', function () { 35 | it('should read a .xlsx file and return the parsed docs in an object', async function () { 36 | const result = await xlsx.input(filepath); 37 | const expected = [ 38 | { 39 | name: 'John Doe', 40 | age: 42, 41 | object: { foo: 'bar' }, 42 | array: [ 1, 2, { foo: 'bar' } ] 43 | }, 44 | { 45 | name: 'Jane Doe', 46 | age: 43 47 | } 48 | ]; 49 | assert.deepEqual(result.docs, expected); 50 | }); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /lib/xlsx.js: -------------------------------------------------------------------------------- 1 | const XLSX = require('xlsx'); 2 | 3 | module.exports = { 4 | label: 'XLSX', 5 | extension: '.xlsx', 6 | allowedExtension: '.xlsx', 7 | allowedTypes: [ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ], 8 | async input(filepath) { 9 | const workbook = XLSX.readFile(filepath); 10 | 11 | const sheet = workbook.Sheets[workbook.SheetNames[0]]; 12 | const docs = XLSX.utils.sheet_to_json(sheet); 13 | 14 | return { docs: docs.map(parse) }; 15 | }, 16 | async output(filepath, { docs }) { 17 | const workbook = XLSX.utils.book_new(); 18 | const worksheet = XLSX.utils.json_to_sheet( 19 | docs.map(stringify) 20 | ); 21 | 22 | XLSX.utils.book_append_sheet(workbook, worksheet); 23 | XLSX.writeFile(workbook, filepath, { compression: true }); 24 | 25 | console.info(`[xlsx] docs and attachments written to ${filepath}`); 26 | } 27 | }; 28 | 29 | function stringify(doc) { 30 | const object = {}; 31 | for (const key in doc) { 32 | object[key] = typeof doc[key] === 'object' 33 | ? JSON.stringify(doc[key]) 34 | : doc[key]; 35 | } 36 | return object; 37 | } 38 | 39 | function parse(doc) { 40 | const object = {}; 41 | for (const key in doc) { 42 | if (!doc[key]) { 43 | // Avoid setting empty values as empty strings 44 | continue; 45 | } 46 | try { 47 | object[key] = JSON.parse(doc[key]); 48 | } catch { 49 | object[key] = doc[key]; 50 | } 51 | } 52 | return object; 53 | } 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | ApostropheCMS logo 3 | 4 |

XLSX format for Import Export Module

5 |

6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

16 |
17 | 18 | This module improves [@apostrophecms/import-export](https://github.com/apostrophecms/import-export) by adding the `xlsx` format. 19 | 20 | > Why does this specific format lies in another module? 21 | 22 | Because it relies on a [dependency that is not hosted on NPM](https://github.com/SheetJS/sheetjs/issues/2667), which could lead to installation issues on networks that limit connections to NPM repository only. 23 | 24 | ## Requirement 25 | 26 | [@apostrophecms/import-export](https://github.com/apostrophecms/import-export) should be installed and instantiated. 27 | 28 | Please refer to the module's [README](https://github.com/apostrophecms/import-export#readme) before continuing. 29 | 30 | ## Installation 31 | 32 | To install the module, use the command line to run this command in an Apostrophe project's root directory: 33 | 34 | ``` 35 | npm install @apostrophecms/import-export-xlsx 36 | ``` 37 | 38 | ## Usage 39 | 40 | Configure the module in the `app.js` file: 41 | 42 | ```javascript 43 | require('apostrophe')({ 44 | shortName: 'my-project', 45 | modules: { 46 | '@apostrophecms/import-export': {}, 47 | '@apostrophecms/import-export-xlsx': {} 48 | } 49 | }); 50 | ``` 51 | 52 | > Please note that `apos` is not passed to the `input` and `output` functions of the format, as they should remain agnostic of apostrophe and only deal with retrieving and writing the documents from/to the `.xlsx` file. 53 | 54 | --------------------------------------------------------------------------------