diff --git a/exports.js b/exports.js new file mode 100644 index 0000000..2c2e9e8 --- /dev/null +++ b/exports.js @@ -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' + diff --git a/package.json b/package.json index d1dd7e8..8b6257f 100644 --- a/package.json +++ b/package.json @@ -2,8 +2,13 @@ "name": "@odfjs/odfjs", "version": "0.14.0", "type": "module", - "main": "./scripts/node.js", - "browser": "./scripts/browser.js", + "exports": "./scripts/exports.js", + "imports": { + "#DOM": { + "node": "./scripts/DOM/node.js", + "browser": "./scripts/DOM/browser.js" + } + }, "scripts": { "build": "rollup -c", "dev": "npm-run-all --parallel dev:* start", diff --git a/scripts/DOM/browser.js b/scripts/DOM/browser.js new file mode 100644 index 0000000..76bc5f8 --- /dev/null +++ b/scripts/DOM/browser.js @@ -0,0 +1,8 @@ + +console.info('DOM implementation in browser') + +/** @type { typeof DOMImplementation.prototype.createDocument } */ +export function createDocument(...args){ + // @ts-ignore + return document.implementation.createDocument(...args) +} \ No newline at end of file diff --git a/scripts/DOM/node.js b/scripts/DOM/node.js new file mode 100644 index 0000000..5b54354 --- /dev/null +++ b/scripts/DOM/node.js @@ -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" \ No newline at end of file diff --git a/scripts/DOMUtils.js b/scripts/DOMUtils.js index ff0b1d6..fea84dd 100644 --- a/scripts/DOMUtils.js +++ b/scripts/DOMUtils.js @@ -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 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 * to each Element node encountered in tree order (depth-first). @@ -21,3 +41,10 @@ export function traverse(node, visit) { visit(node); } + +export { + DOMParser, + XMLSerializer, + createDocument, + Node +} from '#DOM' \ No newline at end of file diff --git a/scripts/browser.js b/scripts/browser.js deleted file mode 100644 index 7db6663..0000000 --- a/scripts/browser.js +++ /dev/null @@ -1,78 +0,0 @@ -//@ts-check - -import { _getODSTableRawContent } 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) -} - - - -/** @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} - */ -export function fillOdtTemplate(odtTemplate, data){ - return _fillOdtTemplate(odtTemplate, data, parseXML, serializeToString, Node) -} - - -/** - * @param {Map} 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' - diff --git a/scripts/createOdsFile.js b/scripts/createOdsFile.js index cbd9170..9a014df 100644 --- a/scripts/createOdsFile.js +++ b/scripts/createOdsFile.js @@ -1,5 +1,8 @@ import { ZipWriter, BlobWriter, TextReader } from '@zip.js/zip.js'; +import {serializeToString, createDocument} from './DOMUtils.js' + + /** @import {SheetCellRawContent, SheetName, SheetRawContent} from './types.js' */ const stylesXml = ` @@ -22,11 +25,9 @@ const manifestXml = ` /** * Crée un fichier .ods à partir d'un Map de feuilles de calcul * @param {Map} sheetsData - * @param {typeof DOMImplementation.prototype.createDocument} createDocument - * @param {typeof XMLSerializer.prototype.serializeToString} serializeToString * @returns {Promise} */ -export async function _createOdsFile(sheetsData, createDocument, serializeToString) { +export async function createOdsFile(sheetsData) { // Create a new zip writer 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("styles.xml", new TextReader(stylesXml)); @@ -60,11 +61,9 @@ export async function _createOdsFile(sheetsData, createDocument, serializeToStri /** * Generate the content.xml file with spreadsheet data * @param {Map} sheetsData - * @param {typeof DOMImplementation.prototype.createDocument} createDocument - * @param {typeof XMLSerializer.prototype.serializeToString} serializeToString * @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 root = doc.documentElement; diff --git a/scripts/node.js b/scripts/node.js deleted file mode 100644 index c75c2fd..0000000 --- a/scripts/node.js +++ /dev/null @@ -1,84 +0,0 @@ -//@ts-check - -import {DOMParser, DOMImplementation, XMLSerializer, Node} from '@xmldom/xmldom' - -import {_getODSTableRawContent} 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) -} - - -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} - */ -export function fillOdtTemplate(odtTemplate, data){ - return _fillOdtTemplate(odtTemplate, data, parseXML, serializeToString, Node) -} - - -/** - * @param {Map} 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' - diff --git a/scripts/odf/fillOdtTemplate.js b/scripts/odf/fillOdtTemplate.js index c6a7c01..82a9bb1 100644 --- a/scripts/odf/fillOdtTemplate.js +++ b/scripts/odf/fillOdtTemplate.js @@ -1,6 +1,6 @@ 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 {Reader, ZipWriterAddDataOptions} from '@zip.js/zip.js' */ @@ -344,12 +344,9 @@ function keepFile(filename){ /** * @param {ODTFile} odtTemplate * @param {any} data - * @param {Function} parseXML - * @param {typeof XMLSerializer.prototype.serializeToString} serializeToString - * @param {typeof Node} Node * @returns {Promise} */ -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))); diff --git a/scripts/odf/odt/getOdtTextContent.js b/scripts/odf/odt/getOdtTextContent.js new file mode 100644 index 0000000..cd468e3 --- /dev/null +++ b/scripts/odf/odt/getOdtTextContent.js @@ -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} + */ +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} 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) +} \ No newline at end of file diff --git a/scripts/odf/odtTemplate-forNode.js b/scripts/odf/odtTemplate-forNode.js index e1f19f2..f4cd662 100644 --- a/scripts/odf/odtTemplate-forNode.js +++ b/scripts/odf/odtTemplate-forNode.js @@ -1,23 +1,5 @@ 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 @@ -27,61 +9,3 @@ export async function getOdtTemplate(path) { const fileBuffer = await readFile(path) 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} 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} - */ -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'); -} \ No newline at end of file diff --git a/scripts/shared.js b/scripts/shared.js index 7f5be53..725a80b 100644 --- a/scripts/shared.js +++ b/scripts/shared.js @@ -1,6 +1,8 @@ //@ts-check import { Uint8ArrayReader, ZipReader, TextWriter } from '@zip.js/zip.js'; +import {parseXML} from './DOMUtils.js' + /** @import {Entry} from '@zip.js/zip.js'*/ /** @import {SheetName, SheetRawContent, SheetRowRawContent, SheetCellRawContent} from './types.js' */ @@ -46,10 +48,9 @@ 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. * @returns {Promise>} */ -export async function _getODSTableRawContent(arrayBuffer, parseXML) { +export async function getODSTableRawContent(arrayBuffer) { const zipDataReader = new Uint8ArrayReader(new Uint8Array(arrayBuffer)); const zipReader = new ZipReader(zipDataReader); const zipEntries = await zipReader.getEntries() diff --git a/tests/basic-node.js b/tests/basic-node.js index d4e1fd1..5f538c4 100644 --- a/tests/basic-node.js +++ b/tests/basic-node.js @@ -2,7 +2,7 @@ import {readFile} from 'node:fs/promises' 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 diff --git a/tests/create-ods-file.js b/tests/create-ods-file.js index 35f8054..d9d6533 100644 --- a/tests/create-ods-file.js +++ b/tests/create-ods-file.js @@ -1,6 +1,6 @@ import test from 'ava'; -import {getODSTableRawContent, createOdsFile} from '../scripts/node.js' +import {getODSTableRawContent, createOdsFile} from '../exports.js' /** @import {SheetName, SheetRawContent} from '../scripts/types.js' */ diff --git a/tests/fill-odt-template.js b/tests/fill-odt-template.js index 073f8cf..bbb66dc 100644 --- a/tests/fill-odt-template.js +++ b/tests/fill-odt-template.js @@ -1,9 +1,9 @@ import test from 'ava'; 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'; diff --git a/tests/ods-files.js b/tests/ods-files.js index 6635de9..66017c9 100644 --- a/tests/ods-files.js +++ b/tests/ods-files.js @@ -2,7 +2,7 @@ import {readFile} from 'node:fs/promises' 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 => { const repeatedCellFileContent = (await readFile('./tests/data/cellules-répétées.ods')).buffer diff --git a/tests/sheetRawContentToObjects.js b/tests/sheetRawContentToObjects.js index 377136c..46c4d0d 100644 --- a/tests/sheetRawContentToObjects.js +++ b/tests/sheetRawContentToObjects.js @@ -1,5 +1,5 @@ import test from 'ava'; -import { sheetRawContentToObjects } from "../scripts/shared.js" +import { sheetRawContentToObjects } from "../exports.js" test("Empty header value should be kept", t => { const rawContent = [