From d011746d55a962d39cac4941782aae21cc6d34da Mon Sep 17 00:00:00 2001 From: David Bruant Date: Sat, 15 Jun 2024 12:54:53 +0200 Subject: [PATCH] =?UTF-8?q?Premi=C3=A8re=20version=20brute?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 8 +- package-lock.json | 32 +++++ package.json | 3 + rollup.config.js | 2 +- scripts/App.svelte | 34 +++-- scripts/{main.js => front-end.js} | 0 scripts/getTableRawContentFromFile.js | 192 ++++++++++++++++++++++++++ 7 files changed, 254 insertions(+), 17 deletions(-) rename scripts/{main.js => front-end.js} (100%) create mode 100644 scripts/getTableRawContentFromFile.js diff --git a/index.html b/index.html index 69bcca9..dba5a8f 100644 --- a/index.html +++ b/index.html @@ -5,7 +5,7 @@ - Front-end template + Upload ods/xlsx @@ -20,13 +20,7 @@
-

Test page

- -

Something should be written here:

-
- -

Did it work?

diff --git a/package-lock.json b/package-lock.json index 9a98a98..dd01c00 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,9 @@ "": { "name": "front-end-template", "version": "2.0.0", + "dependencies": { + "unzipit": "^1.4.3" + }, "devDependencies": { "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-node-resolve": "^15.2.3", @@ -2289,12 +2292,28 @@ "node": ">= 0.8.0" } }, + "node_modules/unzipit": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unzipit/-/unzipit-1.4.3.tgz", + "integrity": "sha512-gsq2PdJIWWGhx5kcdWStvNWit9FVdTewm4SEG7gFskWs+XCVaULt9+BwuoBtJiRE8eo3L1IPAOrbByNLtLtIlg==", + "dependencies": { + "uzip-module": "^1.0.2" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/url-join": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", "dev": true }, + "node_modules/uzip-module": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/uzip-module/-/uzip-module-1.0.3.tgz", + "integrity": "sha512-AMqwWZaknLM77G+VPYNZLEruMGWGzyigPK3/Whg99B3S6vGHuqsyl5ZrOv1UUF3paGK1U6PM0cnayioaryg/fA==" + }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -4021,12 +4040,25 @@ "qs": "^6.4.0" } }, + "unzipit": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unzipit/-/unzipit-1.4.3.tgz", + "integrity": "sha512-gsq2PdJIWWGhx5kcdWStvNWit9FVdTewm4SEG7gFskWs+XCVaULt9+BwuoBtJiRE8eo3L1IPAOrbByNLtLtIlg==", + "requires": { + "uzip-module": "^1.0.2" + } + }, "url-join": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", "dev": true }, + "uzip-module": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/uzip-module/-/uzip-module-1.0.3.tgz", + "integrity": "sha512-AMqwWZaknLM77G+VPYNZLEruMGWGzyigPK3/Whg99B3S6vGHuqsyl5ZrOv1UUF3paGK1U6PM0cnayioaryg/fA==" + }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", diff --git a/package.json b/package.json index d7b4bdb..e79ed73 100644 --- a/package.json +++ b/package.json @@ -20,5 +20,8 @@ "sass": "^1.58.3", "svelte": "^4.2.9", "svelte-preprocess": "^5.1.3" + }, + "dependencies": { + "unzipit": "^1.4.3" } } diff --git a/rollup.config.js b/rollup.config.js index 00e407b..cb15992 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -9,7 +9,7 @@ const production = !process.env.ROLLUP_WATCH; export default { - input: 'scripts/main.js', + input: 'scripts/front-end.js', output: { sourcemap: true, format: 'es', diff --git a/scripts/App.svelte b/scripts/App.svelte index 7106a8b..7529402 100644 --- a/scripts/App.svelte +++ b/scripts/App.svelte @@ -1,8 +1,31 @@ -

Hello {name}! 🧝🏿

+

Import fichier .ods et .xslx

+ +
+ +
+ + diff --git a/scripts/main.js b/scripts/front-end.js similarity index 100% rename from scripts/main.js rename to scripts/front-end.js diff --git a/scripts/getTableRawContentFromFile.js b/scripts/getTableRawContentFromFile.js new file mode 100644 index 0000000..b74cdad --- /dev/null +++ b/scripts/getTableRawContentFromFile.js @@ -0,0 +1,192 @@ +//@ts-check + +import { unzip } from 'unzipit'; + +const parser = new DOMParser(); + +/** + * + * @param {string} str + * @returns {Document} + */ +function parseXML(str){ + return parser.parseFromString(str, 'application/xml'); +} + +/** + * @typedef TableCellRawContent + * @prop {string} value + * @prop {'float' | 'percentage' | 'currency' | 'date' | 'time' | 'boolean' | 'string' | 'b' | 'd' | 'e' | 'inlineStr' | 'n' | 's' | 'str'} type + * + */ + +const ODS_TYPE = "application/vnd.oasis.opendocument.spreadsheet"; +const XLSX_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + + +/** + * Converts a cell value to the appropriate JavaScript type based on its cell type. + * @param {string} value - The value of the cell. + * @param {TableCellRawContent['type']} cellType - The type of the cell. + * @returns {any} The converted value. + */ +function convertCellValue(value, cellType) { + if(value === ''){ + return '' + } + + switch (cellType) { + case 'float': + case 'percentage': + case 'currency': + case 'n': // number + return parseFloat(value); + case 'date': + case 'd': // date + return new Date(value); + case 'boolean': + case 'b': // boolean + return value === '1' || value === 'true'; + case 's': // shared string + case 'inlineStr': // inline string + case 'string': + case 'e': // error + case 'time': + default: + return value; + } +} + +/** + * Extracts raw table content from an ODS file. + * @param {File} file - The ODS file. + * @param {Function} unzip - Function to unzip the file. + * @param {Function} parseXML - Function to parse XML content. + * @returns {Promise>} + */ +async function getTableRawContentFromODSFile(file, unzip, parseXML) { + const zip = await unzip(file); + console.log('zip', zip) + const entries = zip.entries; + + // Extract the content.xml file which contains the spreadsheet data + const contentXml = await entries['content.xml'].text(); + const contentDoc = parseXML(contentXml); + + const tableMap = new Map(); + + // Navigate the XML structure to extract table data + const tables = contentDoc.getElementsByTagName('table:table'); + + for (let table of tables) { + const sheetName = table.getAttribute('table:name'); + const rows = table.getElementsByTagName('table:table-row'); + const sheetData = []; + + for (let row of rows) { + const cells = row.getElementsByTagName('table:table-cell'); + const rowData = []; + + for (let cell of cells) { + const cellType = cell.getAttribute('office:value-type'); + const cellValue = cellType === 'string' ? cell.textContent : cell.getAttribute('office:value'); + rowData.push({ + value: cellValue, + type: cellType + }); + } + + sheetData.push(rowData); + } + + tableMap.set(sheetName, sheetData); + } + + return tableMap; +} + +/** + * Extracts raw table content from an XLSX file. + * @param {File} file - The XLSX file. + * @param {Function} unzip - Function to unzip the file. + * @param {Function} parseXML - Function to parse XML content. + * @returns {Promise>} + */ +async function getTableRawContentFromXSLXFile(file, unzip, parseXML) { + const zip = await unzip(file); + const entries = zip.entries; + + // Read shared strings + const sharedStringsXml = await entries['xl/sharedStrings.xml'].text(); + const sharedStringsDoc = parseXML(sharedStringsXml); + const sharedStrings = Array.from(sharedStringsDoc.getElementsByTagName('sst')[0].getElementsByTagName('si')).map(si => si.textContent); + + // Get sheet names and their corresponding XML files + const workbookXml = await entries['xl/workbook.xml'].text(); + const workbookDoc = parseXML(workbookXml); + const sheets = Array.from(workbookDoc.getElementsByTagName('sheets')[0].getElementsByTagName('sheet')); + const sheetNames = sheets.map(sheet => sheet.getAttribute('name')); + const sheetIds = sheets.map(sheet => sheet.getAttribute('r:id')); + + // Read the relations to get the actual filenames for each sheet + const workbookRelsXml = await entries['xl/_rels/workbook.xml.rels'].text(); + const workbookRelsDoc = parseXML(workbookRelsXml); + const sheetRels = Array.from(workbookRelsDoc.getElementsByTagName('Relationship')); + const sheetFiles = sheetIds.map(id => sheetRels.find(rel => rel.getAttribute('Id') === id).getAttribute('Target').replace('worksheets/', '')); + + // Read each sheet's XML and extract data in parallel + const sheetDataPs = sheetFiles.map((sheetFile, index) => ( + entries[`xl/worksheets/${sheetFile}`].text().then(sheetXml => { + const sheetDoc = parseXML(sheetXml); + + const rows = sheetDoc.getElementsByTagName('sheetData')[0].getElementsByTagName('row'); + const sheetData = []; + + for (let row of rows) { + const cells = row.getElementsByTagName('c'); + const rowData = []; + + for (let cell of cells) { + const cellType = cell.getAttribute('t') || 'n'; + let cellValue = cell.getElementsByTagName('v')[0]?.textContent || ''; + + if (cellType === 's') { + cellValue = sharedStrings[parseInt(cellValue, 10)]; + } + + rowData.push({ + value: cellValue, + type: cellType + }); + } + + sheetData.push(rowData); + } + + return [sheetNames[index], sheetData]; + }) + )); + + return new Map(await Promise.all(sheetDataPs)); +} + + + +/** @typedef {string} SheetName */ + +/** + * + * @param {File} file + * @returns {Promise>} + */ +export default function getTableRawContentFromFile(file){ + if(file.type === ODS_TYPE) + return getTableRawContentFromODSFile(file, unzip, parseXML) + + if(file.type === XLSX_TYPE) + return getTableRawContentFromXSLXFile(file, unzip, parseXML) + + throw new TypeError(`Unsupported file type: ${file.type} (${file.name})`) +} + +