Compare commits

...

4 Commits

Author SHA1 Message Date
David Bruant
cfa71aeff8 Fixing exports field in package.json 2025-04-17 17:36:42 +02:00
David Bruant
9b2183827d browser DOM exports 2025-04-17 17:27:53 +02:00
David Bruant
df9abcfb56 Restructure exports to avoid duplication of DOM-related code 2025-04-17 16:10:10 +02:00
David Bruant
30cfe0921e Remove xlsx support 2025-04-16 10:19:09 +02:00
20 changed files with 190 additions and 389 deletions

29
exports.js Normal file
View File

@ -0,0 +1,29 @@
//@ts-check
export {default as fillOdtTemplate} from './scripts/odf/fillOdtTemplate.js'
export {getOdtTextContent} from './scripts/odf/odt/getOdtTextContent.js'
export { createOdsFile } from './scripts/createOdsFile.js'
export {
getODSTableRawContent,
// table-level exports
tableWithoutEmptyRows,
tableRawContentToValues,
tableRawContentToStrings,
tableRawContentToObjects,
// sheet-level exports
sheetRawContentToObjects,
sheetRawContentToStrings,
// row-level exports
rowRawContentToStrings,
isRowNotEmpty,
// cell-level exports
cellRawContentToStrings,
convertCellValue
} from './scripts/shared.js'

View File

@ -5,7 +5,7 @@
<meta name="referrer" content="no-referrer"> <meta name="referrer" content="no-referrer">
<link rel="icon" href="data:,"> <link rel="icon" href="data:,">
<title>Upload ods/xlsx</title> <title>Upload ods file</title>
<meta name="description" content=" "> <meta name="description" content=" ">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">

View File

@ -2,8 +2,13 @@
"name": "@odfjs/odfjs", "name": "@odfjs/odfjs",
"version": "0.14.0", "version": "0.14.0",
"type": "module", "type": "module",
"main": "./scripts/node.js", "exports": "./exports.js",
"browser": "./scripts/browser.js", "imports": {
"#DOM": {
"node": "./scripts/DOM/node.js",
"browser": "./scripts/DOM/browser.js"
}
},
"scripts": { "scripts": {
"build": "rollup -c", "build": "rollup -c",
"dev": "npm-run-all --parallel dev:* start", "dev": "npm-run-all --parallel dev:* start",

View File

@ -6,7 +6,7 @@ Small lib to parse/understand .odf files (.odt, .ods) in the browser and node.js
## Rough roadmap ## Rough roadmap
- [x] add odt templating - [x] add odt templating
- [ ] remove support for xlsx - [x] remove support for xlsx
- [ ] add a .ods minifyer - [ ] add a .ods minifyer
- [ ] add a generic .ods visualizer - [ ] add a generic .ods visualizer
- [ ] move to a dedicated odf docs org - [ ] move to a dedicated odf docs org
@ -22,7 +22,7 @@ npm i https://github.com/odfjs/odfjs.git#v0.14.0
``` ```
### Basic - reading an ods/xlsx file ### Basic - reading an ods file
```js ```js
import {tableRawContentToObjects, tableWithoutEmptyRows, getODSTableRawContent} from '@odfjs/odfjs' import {tableRawContentToObjects, tableWithoutEmptyRows, getODSTableRawContent} from '@odfjs/odfjs'
@ -40,14 +40,14 @@ async function getFileData(odsFile){
The return value is an array of objects where The return value is an array of objects where
the **keys** are the column names in the first row and the **keys** are the column names in the first row and
the **values** are automatically converted from the .ods or .xlsx files (which type numbers, strings, booleans and dates) the **values** are automatically converted from the .ods files (which type numbers, strings, booleans and dates)
to the appropriate JavaScript value to the appropriate JavaScript value
### Basic - creating an ods file ### Basic - creating an ods file
```js ```js
import {createOdsFile} from 'ods-xlsx' import {createOdsFile} from '@odfjs/odfjs'
const content = new Map([ const content = new Map([
[ [
@ -128,7 +128,7 @@ They can be used to generate lists or tables in .odt files from data and a templ
### Demo ### Demo
https://davidbruant.github.io/ods-xlsx/ https://odfjs.github.io/odfjs/
## Local dev ## Local dev

View File

@ -1,10 +1,11 @@
<script> <script>
//@ts-check import {tableRawContentToObjects, tableWithoutEmptyRows, getODSTableRawContent, createOdsFile} from '../exports.js'
import {tableRawContentToObjects, tableWithoutEmptyRows, getODSTableRawContent, getXLSXTableRawContent, createOdsFile} from './browser.js' /** @import {SheetName, SheetRawContent} from './types.js' */
const ODS_TYPE = "application/vnd.oasis.opendocument.spreadsheet"; const ODS_TYPE = "application/vnd.oasis.opendocument.spreadsheet";
const XLSX_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
/** /**
* *
@ -15,9 +16,6 @@
if(file.type === ODS_TYPE) if(file.type === ODS_TYPE)
return getODSTableRawContent(await file.arrayBuffer()) return getODSTableRawContent(await file.arrayBuffer())
if(file.type === XLSX_TYPE)
return getXLSXTableRawContent(await file.arrayBuffer())
throw new TypeError(`Unsupported file type: ${file.type} (${file.name})`) throw new TypeError(`Unsupported file type: ${file.type} (${file.name})`)
} }
@ -36,13 +34,13 @@
</script> </script>
<h1>Import fichier .ods et .xslx</h1> <h1>Import fichier .ods</h1>
<section> <section>
<h2>Import</h2> <h2>Import</h2>
<label> <label>
Fichier à importer: Fichier à importer:
<input bind:files type="file" id="file-input" accept="{ ['.ods', '.xlsx', ODS_TYPE, XLSX_TYPE].join(',') }" /> <input bind:files type="file" id="file-input" accept="{ ['.ods', ODS_TYPE].join(',') }" />
</label> </label>
</section> </section>

12
scripts/DOM/browser.js Normal file
View File

@ -0,0 +1,12 @@
console.info('DOM implementation in browser')
/** @type { typeof DOMImplementation.prototype.createDocument } */
export function createDocument(...args){
// @ts-ignore
return document.implementation.createDocument(...args)
}
export const DOMParser = window.DOMParser
export const XMLSerializer = window.XMLSerializer
export const Node = window.Node

17
scripts/DOM/node.js Normal file
View File

@ -0,0 +1,17 @@
import { DOMImplementation } from "@xmldom/xmldom"
console.info('DOM implementation in Node.js based on xmldom')
const implementation = new DOMImplementation()
/** @type { typeof DOMImplementation.prototype.createDocument } */
export function createDocument(...args){
// @ts-ignore
return implementation.createDocument(...args)
}
export {
DOMParser,
XMLSerializer,
Node
} from "@xmldom/xmldom"

View File

@ -1,8 +1,28 @@
import {DOMParser, XMLSerializer} from '#DOM'
/* /*
Since we're using xmldom in Node.js context, the entire DOM API is not implemented Since we're using xmldom in Node.js context, the entire DOM API is not implemented
Functions here are helpers whild xmldom becomes more complete Functions here are helpers whild xmldom becomes more complete
*/ */
/**
*
* @param {string} str
* @returns {Document}
*/
export function parseXML(str){
return (new DOMParser()).parseFromString(str, 'application/xml');
}
const serializer = new XMLSerializer()
/** @type { typeof XMLSerializer.prototype.serializeToString } */
export function serializeToString(node){
return serializer.serializeToString(node)
}
/** /**
* Traverses a DOM tree starting from the given element and applies the visit function * Traverses a DOM tree starting from the given element and applies the visit function
* to each Element node encountered in tree order (depth-first). * to each Element node encountered in tree order (depth-first).
@ -21,3 +41,10 @@ export function traverse(node, visit) {
visit(node); visit(node);
} }
export {
DOMParser,
XMLSerializer,
createDocument,
Node
} from '#DOM'

View File

@ -1,88 +0,0 @@
//@ts-check
import {
_getODSTableRawContent,
_getXLSXTableRawContent
} from './shared.js'
import {_createOdsFile} from './createOdsFile.js'
import _fillOdtTemplate from './odf/fillOdtTemplate.js'
/** @import {SheetCellRawContent, SheetName, SheetRawContent} from './types.js' */
/** @import {ODTFile} from './odf/fillOdtTemplate.js' */
function parseXML(str){
return (new DOMParser()).parseFromString(str, 'application/xml');
}
/**
* @param {ArrayBuffer} odsArrBuff
* @returns {ReturnType<_getODSTableRawContent>}
*/
export function getODSTableRawContent(odsArrBuff){
return _getODSTableRawContent(odsArrBuff, parseXML)
}
/**
* @param {ArrayBuffer} xlsxArrBuff
* @returns {ReturnType<_getXLSXTableRawContent>}
*/
export function getXLSXTableRawContent(xlsxArrBuff){
return _getXLSXTableRawContent(xlsxArrBuff, parseXML)
}
/** @type { typeof DOMImplementation.prototype.createDocument } */
const createDocument = function createDocument(...args){
// @ts-ignore
return document.implementation.createDocument(...args)
}
const serializer = new XMLSerializer()
/** @type { typeof XMLSerializer.prototype.serializeToString } */
const serializeToString = function serializeToString(node){
return serializer.serializeToString(node)
}
/**
* @param {ODTFile} odtTemplate
* @param {any} data
* @returns {Promise<ODTFile>}
*/
export function fillOdtTemplate(odtTemplate, data){
return _fillOdtTemplate(odtTemplate, data, parseXML, serializeToString, Node)
}
/**
* @param {Map<SheetName, SheetRawContent>} sheetsData
*/
export function createOdsFile(sheetsData){
return _createOdsFile(sheetsData, createDocument, serializeToString)
}
export {
// table-level exports
tableWithoutEmptyRows,
tableRawContentToValues,
tableRawContentToStrings,
tableRawContentToObjects,
// sheet-level exports
sheetRawContentToObjects,
sheetRawContentToStrings,
// row-level exports
rowRawContentToStrings,
isRowNotEmpty,
// cell-level exports
cellRawContentToStrings,
convertCellValue
} from './shared.js'

View File

@ -1,5 +1,8 @@
import { ZipWriter, BlobWriter, TextReader } from '@zip.js/zip.js'; import { ZipWriter, BlobWriter, TextReader } from '@zip.js/zip.js';
import {serializeToString, createDocument} from './DOMUtils.js'
/** @import {SheetCellRawContent, SheetName, SheetRawContent} from './types.js' */ /** @import {SheetCellRawContent, SheetName, SheetRawContent} from './types.js' */
const stylesXml = `<?xml version="1.0" encoding="UTF-8"?> const stylesXml = `<?xml version="1.0" encoding="UTF-8"?>
@ -22,11 +25,9 @@ const manifestXml = `<?xml version="1.0" encoding="UTF-8"?>
/** /**
* Crée un fichier .ods à partir d'un Map de feuilles de calcul * Crée un fichier .ods à partir d'un Map de feuilles de calcul
* @param {Map<SheetName, SheetRawContent>} sheetsData * @param {Map<SheetName, SheetRawContent>} sheetsData
* @param {typeof DOMImplementation.prototype.createDocument} createDocument
* @param {typeof XMLSerializer.prototype.serializeToString} serializeToString
* @returns {Promise<ArrayBuffer>} * @returns {Promise<ArrayBuffer>}
*/ */
export async function _createOdsFile(sheetsData, createDocument, serializeToString) { export async function createOdsFile(sheetsData) {
// Create a new zip writer // Create a new zip writer
const zipWriter = new ZipWriter(new BlobWriter('application/vnd.oasis.opendocument.spreadsheet')); const zipWriter = new ZipWriter(new BlobWriter('application/vnd.oasis.opendocument.spreadsheet'));
@ -44,7 +45,7 @@ export async function _createOdsFile(sheetsData, createDocument, serializeToStri
} }
); );
const contentXml = generateContentFileXMLString(sheetsData, createDocument, serializeToString); const contentXml = generateContentFileXMLString(sheetsData);
zipWriter.add("content.xml", new TextReader(contentXml), {level: 9}); zipWriter.add("content.xml", new TextReader(contentXml), {level: 9});
zipWriter.add("styles.xml", new TextReader(stylesXml)); zipWriter.add("styles.xml", new TextReader(stylesXml));
@ -60,11 +61,9 @@ export async function _createOdsFile(sheetsData, createDocument, serializeToStri
/** /**
* Generate the content.xml file with spreadsheet data * Generate the content.xml file with spreadsheet data
* @param {Map<SheetName, SheetRawContent>} sheetsData * @param {Map<SheetName, SheetRawContent>} sheetsData
* @param {typeof DOMImplementation.prototype.createDocument} createDocument
* @param {typeof XMLSerializer.prototype.serializeToString} serializeToString
* @returns {string} * @returns {string}
*/ */
function generateContentFileXMLString(sheetsData, createDocument, serializeToString) { function generateContentFileXMLString(sheetsData) {
const doc = createDocument('urn:oasis:names:tc:opendocument:xmlns:office:1.0', 'office:document-content'); const doc = createDocument('urn:oasis:names:tc:opendocument:xmlns:office:1.0', 'office:document-content');
const root = doc.documentElement; const root = doc.documentElement;

View File

@ -1,94 +0,0 @@
//@ts-check
import {DOMParser, DOMImplementation, XMLSerializer, Node} from '@xmldom/xmldom'
import {
_getODSTableRawContent,
_getXLSXTableRawContent
} from './shared.js'
import { _createOdsFile } from './createOdsFile.js'
import _fillOdtTemplate from './odf/fillOdtTemplate.js'
/** @import {SheetCellRawContent, SheetName, SheetRawContent} from './types.js' */
/** @import {ODTFile} from './odf/fillOdtTemplate.js' */
/**
*
* @param {string} str
* @returns {Document}
*/
function parseXML(str){
return (new DOMParser()).parseFromString(str, 'application/xml');
}
/**
* @param {ArrayBuffer} odsArrBuff
* @returns {ReturnType<_getODSTableRawContent>}
*/
export function getODSTableRawContent(odsArrBuff){
return _getODSTableRawContent(odsArrBuff, parseXML)
}
/**
* @param {ArrayBuffer} xlsxArrBuff
* @returns {ReturnType<_getXLSXTableRawContent>}
*/
export function getXLSXTableRawContent(xlsxArrBuff){
return _getXLSXTableRawContent(xlsxArrBuff, parseXML)
}
const implementation = new DOMImplementation()
/** @type { typeof DOMImplementation.prototype.createDocument } */
const createDocument = function createDocument(...args){
// @ts-ignore
return implementation.createDocument(...args)
}
const serializer = new XMLSerializer()
/** @type { typeof XMLSerializer.prototype.serializeToString } */
const serializeToString = function serializeToString(node){
return serializer.serializeToString(node)
}
/**
* @param {ODTFile} odtTemplate
* @param {any} data
* @returns {Promise<ODTFile>}
*/
export function fillOdtTemplate(odtTemplate, data){
return _fillOdtTemplate(odtTemplate, data, parseXML, serializeToString, Node)
}
/**
* @param {Map<SheetName, SheetRawContent>} sheetsData
*/
export function createOdsFile(sheetsData){
return _createOdsFile(sheetsData, createDocument, serializeToString)
}
export {
// table-level exports
tableWithoutEmptyRows,
tableRawContentToValues,
tableRawContentToStrings,
tableRawContentToObjects,
// sheet-level exports
sheetRawContentToObjects,
sheetRawContentToStrings,
// row-level exports
rowRawContentToStrings,
isRowNotEmpty,
// cell-level exports
cellRawContentToStrings,
convertCellValue
} from './shared.js'

View File

@ -1,6 +1,6 @@
import { ZipReader, ZipWriter, BlobReader, BlobWriter, TextReader, Uint8ArrayReader, TextWriter, Uint8ArrayWriter } from '@zip.js/zip.js'; import { ZipReader, ZipWriter, BlobReader, BlobWriter, TextReader, Uint8ArrayReader, TextWriter, Uint8ArrayWriter } from '@zip.js/zip.js';
import {traverse} from '../DOMUtils.js' import {traverse, parseXML, serializeToString, Node} from '../DOMUtils.js'
import {makeManifestFile, getManifestFileData} from './manifest.js'; import {makeManifestFile, getManifestFileData} from './manifest.js';
/** @import {Reader, ZipWriterAddDataOptions} from '@zip.js/zip.js' */ /** @import {Reader, ZipWriterAddDataOptions} from '@zip.js/zip.js' */
@ -344,12 +344,9 @@ function keepFile(filename){
/** /**
* @param {ODTFile} odtTemplate * @param {ODTFile} odtTemplate
* @param {any} data * @param {any} data
* @param {Function} parseXML
* @param {typeof XMLSerializer.prototype.serializeToString} serializeToString
* @param {typeof Node} Node
* @returns {Promise<ODTFile>} * @returns {Promise<ODTFile>}
*/ */
export default async function _fillOdtTemplate(odtTemplate, data, parseXML, serializeToString, Node) { export default async function fillOdtTemplate(odtTemplate, data) {
const reader = new ZipReader(new Uint8ArrayReader(new Uint8Array(odtTemplate))); const reader = new ZipReader(new Uint8ArrayReader(new Uint8Array(odtTemplate)));

View File

@ -0,0 +1,69 @@
import { ZipReader, Uint8ArrayReader, TextWriter } from '@zip.js/zip.js';
import {parseXML, Node} from '../../DOMUtils.js'
/** @import {ODTFile} from '../fillOdtTemplate.js' */
/**
* @param {ODTFile} odtFile
* @returns {Promise<Document>}
*/
async function getContentDocument(odtFile) {
const reader = new ZipReader(new Uint8ArrayReader(new Uint8Array(odtFile)));
const entries = await reader.getEntries();
const contentEntry = entries.find(entry => entry.filename === 'content.xml');
if (!contentEntry) {
throw new Error('No content.xml found in the ODT file');
}
// @ts-ignore
const contentText = await contentEntry.getData(new TextWriter());
await reader.close();
return parseXML(contentText)
}
/**
*
* @param {Document} odtDocument
* @returns {Element}
*/
function getODTTextElement(odtDocument) {
return odtDocument.getElementsByTagName('office:body')[0]
.getElementsByTagName('office:text')[0]
}
/**
* Extracts plain text content from an ODT file, preserving line breaks
* @param {ArrayBuffer} odtFile - The ODT file as an ArrayBuffer
* @returns {Promise<string>} Extracted text content
*/
export async function getOdtTextContent(odtFile) {
const contentDocument = await getContentDocument(odtFile)
const odtTextElement = getODTTextElement(contentDocument)
/**
*
* @param {Element} element
* @returns {string}
*/
function getElementTextContent(element){
//console.log('tagName', element.tagName)
if(element.tagName === 'text:h' || element.tagName === 'text:p')
return element.textContent + '\n'
else{
const descendantTexts = Array.from(element.childNodes)
.filter(n => n.nodeType === Node.ELEMENT_NODE)
.map(getElementTextContent)
if(element.tagName === 'text:list-item')
return `- ${descendantTexts.join('')}`
return descendantTexts.join('')
}
}
return getElementTextContent(odtTextElement)
}

View File

@ -1,23 +1,5 @@
import { readFile } from 'node:fs/promises' import { readFile } from 'node:fs/promises'
import { ZipReader, Uint8ArrayReader, TextWriter } from '@zip.js/zip.js';
import {DOMParser, Node} from '@xmldom/xmldom'
/** @import {ODTFile} from './fillOdtTemplate.js' */
/**
*
* @param {Document} odtDocument
* @returns {Element}
*/
function getODTTextElement(odtDocument) {
return odtDocument.getElementsByTagName('office:body')[0]
.getElementsByTagName('office:text')[0]
}
/** /**
* *
* @param {string} path * @param {string} path
@ -27,61 +9,3 @@ export async function getOdtTemplate(path) {
const fileBuffer = await readFile(path) const fileBuffer = await readFile(path)
return fileBuffer.buffer return fileBuffer.buffer
} }
/**
* Extracts plain text content from an ODT file, preserving line breaks
* @param {ArrayBuffer} odtFile - The ODT file as an ArrayBuffer
* @returns {Promise<string>} Extracted text content
*/
export async function getOdtTextContent(odtFile) {
const contentDocument = await getContentDocument(odtFile)
const odtTextElement = getODTTextElement(contentDocument)
/**
*
* @param {Element} element
* @returns {string}
*/
function getElementTextContent(element){
//console.log('tagName', element.tagName)
if(element.tagName === 'text:h' || element.tagName === 'text:p')
return element.textContent + '\n'
else{
const descendantTexts = Array.from(element.childNodes)
.filter(n => n.nodeType === Node.ELEMENT_NODE)
.map(getElementTextContent)
if(element.tagName === 'text:list-item')
return `- ${descendantTexts.join('')}`
return descendantTexts.join('')
}
}
return getElementTextContent(odtTextElement)
}
/**
* @param {ODTFile} odtFile
* @returns {Promise<Document>}
*/
async function getContentDocument(odtFile) {
const reader = new ZipReader(new Uint8ArrayReader(new Uint8Array(odtFile)));
const entries = await reader.getEntries();
const contentEntry = entries.find(entry => entry.filename === 'content.xml');
if (!contentEntry) {
throw new Error('No content.xml found in the ODT file');
}
// @ts-ignore
const contentText = await contentEntry.getData(new TextWriter());
await reader.close();
const parser = new DOMParser();
return parser.parseFromString(contentText, 'text/xml');
}

View File

@ -1,6 +1,8 @@
//@ts-check //@ts-check
import { Uint8ArrayReader, ZipReader, TextWriter } from '@zip.js/zip.js'; import { Uint8ArrayReader, ZipReader, TextWriter } from '@zip.js/zip.js';
import {parseXML} from './DOMUtils.js'
/** @import {Entry} from '@zip.js/zip.js'*/ /** @import {Entry} from '@zip.js/zip.js'*/
/** @import {SheetName, SheetRawContent, SheetRowRawContent, SheetCellRawContent} from './types.js' */ /** @import {SheetName, SheetRawContent, SheetRowRawContent, SheetCellRawContent} from './types.js' */
@ -46,10 +48,9 @@ function extraxtODSCellText(cell) {
/** /**
* Extracts raw table content from an ODS file. * Extracts raw table content from an ODS file.
* @param {ArrayBuffer} arrayBuffer - The ODS file. * @param {ArrayBuffer} arrayBuffer - The ODS file.
* @param {(str: string) => Document} parseXML - Function to parse XML content.
* @returns {Promise<Map<SheetName, SheetRawContent>>} * @returns {Promise<Map<SheetName, SheetRawContent>>}
*/ */
export async function _getODSTableRawContent(arrayBuffer, parseXML) { export async function getODSTableRawContent(arrayBuffer) {
const zipDataReader = new Uint8ArrayReader(new Uint8Array(arrayBuffer)); const zipDataReader = new Uint8ArrayReader(new Uint8Array(arrayBuffer));
const zipReader = new ZipReader(zipDataReader); const zipReader = new ZipReader(zipDataReader);
const zipEntries = await zipReader.getEntries() const zipEntries = await zipReader.getEntries()
@ -123,101 +124,6 @@ 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.
* @returns {Promise<Map<SheetName, SheetRawContent>>}
*/
export async function _getXLSXTableRawContent(arrayBuffer, parseXML) {
const zipDataReader = new Uint8ArrayReader(new Uint8Array(arrayBuffer));
const zipReader = new ZipReader(zipDataReader);
const zipEntries = await zipReader.getEntries()
await zipReader.close();
/** @type {Map<Entry['filename'], Entry>} */
const entryByFilename = new Map()
for(const entry of zipEntries){
const filename = entry.filename
entryByFilename.set(filename, entry)
}
const sharedStringsEntry = entryByFilename.get('xl/sharedStrings.xml')
if(!sharedStringsEntry){
throw new TypeError(`entry 'xl/sharedStrings.xml' manquante dans le zip`)
}
//@ts-ignore
const sharedStringsXml = await sharedStringsEntry.getData(new TextWriter());
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 workbookEntry = entryByFilename.get('xl/workbook.xml')
if(!workbookEntry){
throw new TypeError(`entry 'xl/workbook.xml' manquante dans le zip`)
}
//@ts-ignore
const workbookXml = await workbookEntry.getData(new TextWriter());
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 workbookRelsEntry = entryByFilename.get('xl/_rels/workbook.xml.rels')
if(!workbookRelsEntry){
throw new TypeError(`entry 'xl/_rels/workbook.xml.rels' manquante dans le zip`)
}
//@ts-ignore
const workbookRelsXml = await workbookRelsEntry.getData(new TextWriter());
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) => (
// @ts-ignore
entryByFilename.get(`xl/worksheets/${sheetFile}`).getData(new TextWriter()).then(sheetXml => {
const sheetDoc = parseXML(sheetXml);
const rows = sheetDoc.getElementsByTagName('sheetData')[0].getElementsByTagName('row');
const sheetData = [];
for (let row of Array.from(rows)) {
const cells = row.getElementsByTagName('c');
const rowData = [];
for (let cell of Array.from(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));
}
/** /**
* Converts a cell value to the appropriate JavaScript type based on its cell type. * Converts a cell value to the appropriate JavaScript type based on its cell type.
* @param {SheetCellRawContent} _ * @param {SheetCellRawContent} _

View File

@ -2,7 +2,7 @@ import {readFile} from 'node:fs/promises'
import test from 'ava'; import test from 'ava';
import {getODSTableRawContent} from '../scripts/node.js' import {getODSTableRawContent} from '../exports.js'
const nomAgeContent = (await readFile('./tests/data/nom-age.ods')).buffer const nomAgeContent = (await readFile('./tests/data/nom-age.ods')).buffer

View File

@ -1,6 +1,6 @@
import test from 'ava'; import test from 'ava';
import {getODSTableRawContent, createOdsFile} from '../scripts/node.js' import {getODSTableRawContent, createOdsFile} from '../exports.js'
/** @import {SheetName, SheetRawContent} from '../scripts/types.js' */ /** @import {SheetName, SheetRawContent} from '../scripts/types.js' */

View File

@ -1,9 +1,9 @@
import test from 'ava'; import test from 'ava';
import {join} from 'node:path'; import {join} from 'node:path';
import {getOdtTemplate, getOdtTextContent} from '../scripts/odf/odtTemplate-forNode.js' import {getOdtTemplate} from '../scripts/odf/odtTemplate-forNode.js'
import {fillOdtTemplate} from '../scripts/node.js' import {fillOdtTemplate, getOdtTextContent} from '../exports.js'
import { listZipEntries } from './_helpers/zip-analysis.js'; import { listZipEntries } from './_helpers/zip-analysis.js';

View File

@ -2,7 +2,7 @@ import {readFile} from 'node:fs/promises'
import test from 'ava'; import test from 'ava';
import {getODSTableRawContent} from '../scripts/node.js' import {getODSTableRawContent} from '../exports.js'
test('.ods file with table:number-columns-repeated attribute in cell', async t => { test('.ods file with table:number-columns-repeated attribute in cell', async t => {
const repeatedCellFileContent = (await readFile('./tests/data/cellules-répétées.ods')).buffer const repeatedCellFileContent = (await readFile('./tests/data/cellules-répétées.ods')).buffer

View File

@ -1,5 +1,5 @@
import test from 'ava'; import test from 'ava';
import { sheetRawContentToObjects } from "../scripts/shared.js" import { sheetRawContentToObjects } from "../exports.js"
test("Empty header value should be kept", t => { test("Empty header value should be kept", t => {
const rawContent = [ const rawContent = [