diff --git a/scripts/odf/fillOdtTemplate.js b/scripts/odf/fillOdtTemplate.js index 8ef4e4b..0e09c51 100644 --- a/scripts/odf/fillOdtTemplate.js +++ b/scripts/odf/fillOdtTemplate.js @@ -217,6 +217,7 @@ function fillEachBlock(startNode, iterableExpression, itemExpression, endNode, d } + /** * * @param {Element | DocumentFragment} rootElement @@ -227,6 +228,70 @@ function fillEachBlock(startNode, iterableExpression, itemExpression, endNode, d function fillTemplatedOdtElement(rootElement, data, Node){ //console.log('fillTemplatedOdtElement', rootElement.nodeType, rootElement.nodeName) + // Perform a first traverse to split textnodes when they contain several block markers + traverse(rootElement, currentNode => { + if(currentNode.nodeType === Node.TEXT_NODE){ + // trouver tous les débuts et fin de each et découper le textNode + + let remainingText = currentNode.textContent || '' + + while(remainingText.length >= 1){ + let match; + + // looking for opening {#each ...} block + const eachBlockOpeningRegex = /{#each\s+([^}]+?)\s+as\s+([^}]+?)\s*}/; + const eachBlockClosingRegex = /{\/each}/; + + for(const regexp of [eachBlockOpeningRegex, eachBlockClosingRegex]){ + let thisMatch = remainingText.match(regexp) + + // trying to find only the first match in remainingText string + if(thisMatch && (!match || match.index > thisMatch.index)){ + match = thisMatch + } + } + + if(match){ + // split 3-way : before-match, match and after-match + + if(match[0].length < remainingText.length){ + let afterMatchTextNode = currentNode.splitText(match.index + match[0].length) + if(afterMatchTextNode.textContent && afterMatchTextNode.textContent.length >= 1){ + remainingText = afterMatchTextNode.textContent + } + else{ + remainingText = '' + } + + // per spec, currentNode now contains before-match and match text + + if(match.index > 0){ + currentNode.splitText(match.index) + } + + if(afterMatchTextNode){ + currentNode = afterMatchTextNode + } + } + else{ + remainingText = '' + } + } + else{ + remainingText = '' + } + } + + } + else{ + // skip + } + }) + + // now, each Node contains at most one block marker + + + /** @type {Node | undefined} */ let eachBlockStartNode /** @type {Node | undefined} */ @@ -246,17 +311,15 @@ function fillTemplatedOdtElement(rootElement, data, Node){ const text = currentNode.textContent || '' // looking for {#each x as y} - const eachStartRegex = /{#each\s+([^}]+?)\s+as\s+([^}]+?)\s*}/g; - const startMatches = [...text.matchAll(eachStartRegex)]; + const eachStartRegex = /{#each\s+([^}]+?)\s+as\s+([^}]+?)\s*}/; + const startMatch = text.match(eachStartRegex); - if(startMatches && startMatches.length >= 1){ + if(startMatch){ if(insideAnEachBlock){ nestedEach = nestedEach + 1 } else{ - // PPP for now, consider only the first set of matches - // eventually, consider all of them for in-text-node {#each}...{/each} - let [_, _iterableExpression, _itemExpression] = startMatches[0] + let [_, _iterableExpression, _itemExpression] = startMatch iterableExpression = _iterableExpression itemExpression = _itemExpression @@ -265,10 +328,10 @@ function fillTemplatedOdtElement(rootElement, data, Node){ } // trying to find an {/each} - const eachEndRegex = /{\/each}/g - const endMatches = [...text.matchAll(eachEndRegex)]; + const eachEndRegex = /{\/each}/ + const endMatch = text.match(eachEndRegex) - if(endMatches && endMatches.length >= 1){ + if(endMatch){ if(!eachBlockStartNode) throw new TypeError(`{/each} found without corresponding opening {#each x as y}`) diff --git a/tests/basic-node.js b/tests/basic-node.js index 5f538c4..fb07e98 100644 --- a/tests/basic-node.js +++ b/tests/basic-node.js @@ -4,7 +4,7 @@ import test from 'ava'; import {getODSTableRawContent} from '../exports.js' -const nomAgeContent = (await readFile('./tests/data/nom-age.ods')).buffer +const nomAgeContent = (await readFile('./tests/fixtures/nom-age.ods')).buffer test('basic', async t => { const table = await getODSTableRawContent(nomAgeContent); diff --git a/tests/fill-odt-template.js b/tests/fill-odt-template.js index dcec8b4..ec06b9a 100644 --- a/tests/fill-odt-template.js +++ b/tests/fill-odt-template.js @@ -4,11 +4,11 @@ import {join} from 'node:path'; import {getOdtTemplate} from '../scripts/odf/odtTemplate-forNode.js' import {fillOdtTemplate, getOdtTextContent} from '../exports.js' -import { listZipEntries } from './_helpers/zip-analysis.js'; +import { listZipEntries } from './helpers/zip-analysis.js'; test('basic template filling with variable substitution', async t => { - const templatePath = join(import.meta.dirname, './data/template-anniversaire.odt') + const templatePath = join(import.meta.dirname, './fixtures/template-anniversaire.odt') const templateContent = `Yo {nom} ! Tu es né.e le {dateNaissance} @@ -36,9 +36,8 @@ Bonjoir ☀️ }); - test('basic template filling with {#each}', async t => { - const templatePath = join(import.meta.dirname, './data/enum-courses.odt') + const templatePath = join(import.meta.dirname, './fixtures/enum-courses.odt') const templateContent = `🧺 La liste de courses incroyable 🧺 {#each listeCourses as élément} @@ -73,8 +72,9 @@ Pâtes à lasagne (fraîches !) }); + test('Filling with {#each} and non-iterable value results in no error and empty result', async t => { - const templatePath = join(import.meta.dirname, './data/enum-courses.odt') + const templatePath = join(import.meta.dirname, './fixtures/enum-courses.odt') const templateContent = `🧺 La liste de courses incroyable 🧺 {#each listeCourses as élément} @@ -103,9 +103,8 @@ test('Filling with {#each} and non-iterable value results in no error and empty }); - test('template filling with {#each} generating a list', async t => { - const templatePath = join(import.meta.dirname, './data/liste-courses.odt') + const templatePath = join(import.meta.dirname, './fixtures/liste-courses.odt') const templateContent = `🧺 La liste de courses incroyable 🧺 - {#each listeCourses as élément} @@ -142,7 +141,7 @@ test('template filling with {#each} generating a list', async t => { test('template filling with 2 sequential {#each}', async t => { - const templatePath = join(import.meta.dirname, './data/liste-fruits-et-légumes.odt') + const templatePath = join(import.meta.dirname, './fixtures/liste-fruits-et-légumes.odt') const templateContent = `Liste de fruits et légumes Fruits @@ -193,9 +192,8 @@ Poivron 🫑 }); - test('template filling with nested {#each}s', async t => { - const templatePath = join(import.meta.dirname, './data/légumes-de-saison.odt') + const templatePath = join(import.meta.dirname, './fixtures/légumes-de-saison.odt') const templateContent = `Légumes de saison {#each légumesSaison as saisonLégumes} @@ -279,9 +277,36 @@ Hiver }); +test('template filling {#each ...}{/each} within a single text node', async t => { + const templatePath = join(import.meta.dirname, './fixtures/liste-nombres.odt') + const templateContent = `Liste de nombres + +Les nombres : {#each nombres as n}{n} {/each} !! +` + + const data = { + nombres : [1,1,2,3,5,8,13,21] + } + + const odtTemplate = await getOdtTemplate(templatePath) + + const templateTextContent = await getOdtTextContent(odtTemplate) + t.deepEqual(templateTextContent, templateContent, 'reconnaissance du template') + + const odtResult = await fillOdtTemplate(odtTemplate, data) + + const odtResultTextContent = await getOdtTextContent(odtResult) + t.deepEqual(odtResultTextContent, `Liste de nombres + +Les nombres : 1 1 2 3 5 8 13 21  !! +`) + +}); + + test('template filling of a table', async t => { - const templatePath = join(import.meta.dirname, './data/tableau-simple.odt') + const templatePath = join(import.meta.dirname, './fixtures/tableau-simple.odt') const templateContent = `Évolution énergie en kWh par personne en France Année @@ -341,9 +366,8 @@ Année }); - test('template filling preserves images', async t => { - const templatePath = join(import.meta.dirname, './data/template-avec-image.odt') + const templatePath = join(import.meta.dirname, './fixtures/template-avec-image.odt') const data = { commentaire : `J'adooooooore 🤩 West covinaaaaaaaaaaa 🎶` diff --git a/tests/data/cellule avec sauts.ods b/tests/fixtures/cellule avec sauts.ods similarity index 100% rename from tests/data/cellule avec sauts.ods rename to tests/fixtures/cellule avec sauts.ods diff --git a/tests/data/cellules avec dates.ods b/tests/fixtures/cellules avec dates.ods similarity index 100% rename from tests/data/cellules avec dates.ods rename to tests/fixtures/cellules avec dates.ods diff --git a/tests/data/cellules-répétées.ods b/tests/fixtures/cellules-répétées.ods similarity index 100% rename from tests/data/cellules-répétées.ods rename to tests/fixtures/cellules-répétées.ods diff --git a/tests/data/enum-courses.odt b/tests/fixtures/enum-courses.odt similarity index 100% rename from tests/data/enum-courses.odt rename to tests/fixtures/enum-courses.odt diff --git a/tests/data/liste-courses.odt b/tests/fixtures/liste-courses.odt similarity index 100% rename from tests/data/liste-courses.odt rename to tests/fixtures/liste-courses.odt diff --git a/tests/data/liste-fruits-et-légumes.odt b/tests/fixtures/liste-fruits-et-légumes.odt similarity index 100% rename from tests/data/liste-fruits-et-légumes.odt rename to tests/fixtures/liste-fruits-et-légumes.odt diff --git a/tests/fixtures/liste-nombres.odt b/tests/fixtures/liste-nombres.odt new file mode 100644 index 0000000..5088eee Binary files /dev/null and b/tests/fixtures/liste-nombres.odt differ diff --git a/tests/data/légumes-de-saison.odt b/tests/fixtures/légumes-de-saison.odt similarity index 100% rename from tests/data/légumes-de-saison.odt rename to tests/fixtures/légumes-de-saison.odt diff --git a/tests/data/nom-age.ods b/tests/fixtures/nom-age.ods similarity index 100% rename from tests/data/nom-age.ods rename to tests/fixtures/nom-age.ods diff --git a/tests/data/tableau-simple.odt b/tests/fixtures/tableau-simple.odt similarity index 100% rename from tests/data/tableau-simple.odt rename to tests/fixtures/tableau-simple.odt diff --git a/tests/data/template-anniversaire.odt b/tests/fixtures/template-anniversaire.odt similarity index 100% rename from tests/data/template-anniversaire.odt rename to tests/fixtures/template-anniversaire.odt diff --git a/tests/data/template-avec-image.odt b/tests/fixtures/template-avec-image.odt similarity index 100% rename from tests/data/template-avec-image.odt rename to tests/fixtures/template-avec-image.odt diff --git a/tests/_helpers/zip-analysis.js b/tests/helpers/zip-analysis.js similarity index 100% rename from tests/_helpers/zip-analysis.js rename to tests/helpers/zip-analysis.js diff --git a/tests/ods-files.js b/tests/ods-files.js index 66017c9..5926b5b 100644 --- a/tests/ods-files.js +++ b/tests/ods-files.js @@ -5,7 +5,7 @@ import test from 'ava'; 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 + const repeatedCellFileContent = (await readFile('./tests/fixtures/cellules-répétées.ods')).buffer const table = await getODSTableRawContent(repeatedCellFileContent); @@ -17,7 +17,7 @@ test('.ods file with table:number-columns-repeated attribute in cell', async t = test('.ods cells with dates should be recognized', async t => { - const odsFileWithDates = (await readFile('./tests/data/cellules avec dates.ods')).buffer + const odsFileWithDates = (await readFile('./tests/fixtures/cellules avec dates.ods')).buffer const table = await getODSTableRawContent(odsFileWithDates); const feuille1 = table.get('Feuille1') @@ -39,7 +39,7 @@ test('.ods cells with dates should be recognized', async t => { test('.ods file with new lines in content is ', async t => { - const repeatedCellFileContent = (await readFile('./tests/data/cellule avec sauts.ods')).buffer + const repeatedCellFileContent = (await readFile('./tests/fixtures/cellule avec sauts.ods')).buffer const table = await getODSTableRawContent(repeatedCellFileContent);