diff --git a/package-lock.json b/package-lock.json index 011e342..c54c517 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,8 @@ "version": "0.7.0", "dependencies": { "@xmldom/xmldom": "^0.8.10", - "unzipit": "^1.4.3" + "unzipit": "^1.4.3", + "xlsx": "^0.18.5" }, "devDependencies": { "@rollup/plugin-commonjs": "^25.0.7", @@ -649,6 +650,15 @@ "node": ">=0.4.0" } }, + "node_modules/adler-32": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", + "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -1062,6 +1072,19 @@ "node": ">=16" } }, + "node_modules/cfb": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", + "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "crc-32": "~1.2.0" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -1247,6 +1270,15 @@ "@types/estree": "^1.0.0" } }, + "node_modules/codepage": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", + "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -1350,6 +1382,18 @@ "node": ">= 0.4.0" } }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/css-tree": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", @@ -1667,6 +1711,15 @@ } } }, + "node_modules/frac": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", + "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", @@ -3666,6 +3719,18 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, + "node_modules/ssf": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", + "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", + "license": "Apache-2.0", + "dependencies": { + "frac": "~1.1.2" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -4212,6 +4277,24 @@ "node": ">=8" } }, + "node_modules/wmf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", + "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/word": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz", + "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -4343,6 +4426,27 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/xlsx": { + "version": "0.18.5", + "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", + "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "cfb": "~1.2.1", + "codepage": "~1.15.0", + "crc-32": "~1.2.1", + "ssf": "~0.11.2", + "wmf": "~1.0.1", + "word": "~0.3.0" + }, + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -4846,6 +4950,11 @@ "acorn": "^8.11.0" } }, + "adler-32": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", + "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==" + }, "agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -5156,6 +5265,15 @@ "nofilter": "^3.1.0" } }, + "cfb": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", + "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", + "requires": { + "adler-32": "~1.3.0", + "crc-32": "~1.2.0" + } + }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -5301,6 +5419,11 @@ } } }, + "codepage": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", + "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==" + }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -5388,6 +5511,11 @@ "integrity": "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==", "dev": true }, + "crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==" + }, "css-tree": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", @@ -5621,6 +5749,11 @@ "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", "dev": true }, + "frac": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", + "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==" + }, "fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", @@ -7091,6 +7224,14 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, + "ssf": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", + "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", + "requires": { + "frac": "~1.1.2" + } + }, "stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -7478,6 +7619,16 @@ } } }, + "wmf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", + "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==" + }, + "word": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz", + "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==" + }, "wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -7577,6 +7728,20 @@ } } }, + "xlsx": { + "version": "0.18.5", + "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", + "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", + "requires": { + "adler-32": "~1.3.0", + "cfb": "~1.2.1", + "codepage": "~1.15.0", + "crc-32": "~1.2.1", + "ssf": "~0.11.2", + "wmf": "~1.0.1", + "word": "~0.3.0" + } + }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index 995d911..f0dd816 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ }, "dependencies": { "@xmldom/xmldom": "^0.8.10", - "unzipit": "^1.4.3" + "unzipit": "^1.4.3", + "xlsx": "^0.18.5" } } diff --git a/readme.md b/readme.md index 93bfa68..8317e72 100644 --- a/readme.md +++ b/readme.md @@ -8,13 +8,13 @@ Small lib to parse/understand .ods and .xsls files in the browser and node.js ### Install ```sh -npm i https://github.com/DavidBruant/ods-xlsx.git#v0.8.0 +npm i https://github.com/DavidBruant/ods-xlsx.git#v0.9.0 ``` ### Usage -#### Basic +#### Basic - reading an ods/xlsx file ```js import {tableRawContentToObjects, tableWithoutEmptyRows, getODSTableRawContent} from 'ods-xlsx' @@ -36,6 +36,40 @@ the **values** are automatically converted from the .ods or .xlsx files (which t to the appropriate JavaScript value +#### Basic - creating an ods file + +```js +import {createOdsFile} from 'ods-xlsx' + +const content = new Map([ + [ + 'La feuille', + [ + [ + {value: '37', type: 'float'}, + {value: '26', type: 'string'} + ] + ], + ], + [ + "L'autre feuille", + [ + [ + {value: '1', type: 'string'}, + {value: '2', type: 'string'}, + {value: '3', type: 'string'}, + {value: '5', type: 'string'}, + {value: '8', type: 'string'} + ] + ], + ] +]) + +const ods = await createOdsFile(content) +// ods is an ArrayBuffer representing an ods file with the content described by the Map +``` + + #### Low-level See exports diff --git a/scripts/browser.js b/scripts/browser.js index d46dc75..d79b77d 100644 --- a/scripts/browser.js +++ b/scripts/browser.js @@ -1,14 +1,15 @@ //@ts-check -function parseXML(str){ - return (new DOMParser()).parseFromString(str, 'application/xml'); -} - import { _getODSTableRawContent, _getXLSXTableRawContent } from './shared.js' + +function parseXML(str){ + return (new DOMParser()).parseFromString(str, 'application/xml'); +} + /** * @param {ArrayBuffer} odsArrBuff * @returns {ReturnType<_getODSTableRawContent>} @@ -26,6 +27,8 @@ export function getXLSXTableRawContent(xlsxArrBuff){ } +export {createOdsFile} from './createOdsFile.js' + export { // table-level exports tableWithoutEmptyRows, diff --git a/scripts/createOdsFile.js b/scripts/createOdsFile.js new file mode 100644 index 0000000..ff10a0a --- /dev/null +++ b/scripts/createOdsFile.js @@ -0,0 +1,27 @@ +//@ts-check + +import XLSX from 'xlsx' + +import {tableRawContentToValues} from './shared.js' + +/** @import {SheetName, SheetRawContent, SheetRowRawContent, SheetCellRawContent} from './types.js' */ + +const officeVersion = '1.2' + +/** + * Crée un fichier .ods à partir d'un Map de feuilles de calcul + * @param {Map} sheetsData + * @returns {Promise} + */ +export async function createOdsFile(sheetsData) { + const workbook = XLSX.utils.book_new(); + + const sheetsDataValues = tableRawContentToValues(sheetsData) + + for(const [sheetName, table] of sheetsDataValues){ + const worksheet = XLSX.utils.aoa_to_sheet(table); + XLSX.utils.book_append_sheet(workbook, worksheet, sheetName); + } + + return XLSX.write(workbook, {bookType: 'ods', type: 'array'}); +} diff --git a/scripts/node.js b/scripts/node.js index 50f8dda..a48e788 100644 --- a/scripts/node.js +++ b/scripts/node.js @@ -2,15 +2,17 @@ import {DOMParser} from '@xmldom/xmldom' -function parseXML(str){ - return (new DOMParser()).parseFromString(str, 'application/xml'); -} - import { _getODSTableRawContent, _getXLSXTableRawContent } from './shared.js' + +function parseXML(str){ + return (new DOMParser()).parseFromString(str, 'application/xml'); +} + + /** * @param {ArrayBuffer} odsArrBuff * @returns {ReturnType<_getODSTableRawContent>} @@ -27,6 +29,7 @@ export function getXLSXTableRawContent(xlsxArrBuff){ return _getXLSXTableRawContent(xlsxArrBuff, parseXML) } +export {createOdsFile} from './createOdsFile.js' export { // table-level exports diff --git a/scripts/shared.js b/scripts/shared.js index 6bd8c52..599d7b7 100644 --- a/scripts/shared.js +++ b/scripts/shared.js @@ -1,7 +1,7 @@ //@ts-check import { unzip } from 'unzipit'; -import './types.js' +/** @import {SheetName, SheetRawContent, SheetRowRawContent, SheetCellRawContent} from './types.js' */ // https://dom.spec.whatwg.org/#interface-node const TEXT_NODE = 3 @@ -44,7 +44,7 @@ function extraxtODSCellText(cell) { /** * Extracts raw table content from an ODS file. * @param {ArrayBuffer} arrayBuffer - The ODS file. - * @param {(str: String) => Document} parseXML - Function to parse XML content. + * @param {(str: string) => Document} parseXML - Function to parse XML content. * @returns {Promise>} */ export async function _getODSTableRawContent(arrayBuffer, parseXML) { @@ -105,7 +105,7 @@ export async function _getODSTableRawContent(arrayBuffer, parseXML) { /** * Extracts raw table content from an XLSX file. * @param {ArrayBuffer} arrayBuffer - The XLSX file. - * @param {(str: String) => Document} parseXML - Function to parse XML content. + * @param {(str: string) => Document} parseXML - Function to parse XML content. * @returns {Promise>} */ export async function _getXLSXTableRawContent(arrayBuffer, parseXML) { diff --git a/scripts/types.js b/scripts/types.js index 20c3303..9463fe0 100644 --- a/scripts/types.js +++ b/scripts/types.js @@ -8,4 +8,6 @@ /** @typedef {SheetCellRawContent[]} SheetRowRawContent */ /** @typedef {SheetRowRawContent[]} SheetRawContent */ -/** @typedef {string} SheetName */ \ No newline at end of file +/** @typedef {string} SheetName */ + +export {} \ No newline at end of file diff --git a/tests/create-ods-file.js b/tests/create-ods-file.js new file mode 100644 index 0000000..8ff8977 --- /dev/null +++ b/tests/create-ods-file.js @@ -0,0 +1,34 @@ +import test from 'ava'; + +import {getODSTableRawContent, createOdsFile} from '../scripts/node.js' + +test('basic file creation', async t => { + const content = new Map([ + [ + 'La feuille', + [ + [ + {value: 'azerty', type: 'string'}, + {value: '37', type: 'float'} + ] + ] + ] + ]) + + // @ts-ignore + const odsFile = await createOdsFile(content) + + const parsedContent = await getODSTableRawContent(odsFile) + + t.assert(parsedContent.has('La feuille')) + + const feuille = parsedContent.get('La feuille') + + t.deepEqual(feuille, [ + [ + {value: 'azerty', type: 'string'}, + {value: '37', type: 'float'} + ] + ]) + +}); diff --git a/tools/create-an-ods-file.js b/tools/create-an-ods-file.js new file mode 100644 index 0000000..132f156 --- /dev/null +++ b/tools/create-an-ods-file.js @@ -0,0 +1,35 @@ +//@ts-check + +import {createOdsFile} from '../scripts/node.js' + +const content = new Map([ + [ + 'La feuille', + [ + [ + {value: '37', type: 'float'}, + {value: '26', type: 'string'} + ] + ], + ], + [ + "L'autre feuille", + [ + [ + {value: '1', type: 'string'}, + {value: '2', type: 'string'}, + {value: '3', type: 'string'}, + {value: '5', type: 'string'}, + {value: '8', type: 'string'} + ] + ], + ] +]) + +// @ts-ignore +const ods = await createOdsFile(content) + +//console.log('writableHighWaterMark', process.stdout.writableHighWaterMark) // 16384 + +process.stdout.write(new Uint8Array(ods)) +