diff --git a/scripts/odf/templating/fillOdtElementTemplate.js b/scripts/odf/templating/fillOdtElementTemplate.js index fd6995f..3dc1458 100644 --- a/scripts/odf/templating/fillOdtElementTemplate.js +++ b/scripts/odf/templating/fillOdtElementTemplate.js @@ -1,5 +1,6 @@ import {traverse, Node} from '../../DOMUtils.js' import {closingIfMarker, eachClosingMarker, eachStartMarkerRegex, elseMarker, ifStartMarkerRegex, variableRegex} from './markers.js' +import prepareTemplateDOMTree from './prepareTemplateDOMTree.js'; /** * @typedef TextPlaceToFill @@ -85,6 +86,8 @@ function findPlacesToFillInString(str, compartment) { * @returns {{startChild: Node, endChild:Node, content: DocumentFragment}} */ function extractBlockContent(blockStartNode, blockEndNode) { + console.log('[extractBlockContent] blockEndNode', blockEndNode.textContent) + // find common ancestor of blockStartNode and blockEndNode let commonAncestor @@ -118,7 +121,10 @@ function extractBlockContent(blockStartNode, blockEndNode) { const startChild = startAncestryToCommonAncestor.at(-1) const endChild = endAncestryToCommonAncestor.at(-1) + console.log('[extractBlockContent] endChild', endChild.textContent) + // Extract DOM content in a documentFragment + /** @type {DocumentFragment} */ const contentFragment = blockStartNode.ownerDocument.createDocumentFragment() /** @type {Element[]} */ @@ -135,6 +141,8 @@ function extractBlockContent(blockStartNode, blockEndNode) { contentFragment.appendChild(sibling) } + console.log('extractBlockContent contentFragment', contentFragment.textContent) + return { startChild, endChild, @@ -231,11 +239,19 @@ function fillIfBlock(ifOpeningMarkerNode, ifElseMarkerNode, ifClosingMarkerNode, */ function fillEachBlock(startNode, iterableExpression, itemExpression, endNode, compartment) { //console.log('fillEachBlock', iterableExpression, itemExpression) - //console.log('startNode', startNode.nodeType, startNode.nodeName) - //console.log('endNode', endNode.nodeType, endNode.nodeName) + console.log('startNode', startNode.nodeName, startNode.textContent) + console.log('endNode', endNode.nodeName, endNode.textContent) + console.log('endNode parent', endNode.parentNode.childNodes.length, endNode.parentNode.textContent) + + const doc = startNode.ownerDocument.documentElement + + console.log('doc text', doc.textContent) const {startChild, endChild, content: repeatedFragment} = extractBlockContent(startNode, endNode) + console.log('endChild after extractBlockContent', endChild.textContent) + console.log('doc text', doc.textContent) + // Find the iterable in the data // PPP eventually, evaluate the expression as a JS expression let iterable = compartment.evaluate(iterableExpression) @@ -244,12 +260,18 @@ function fillEachBlock(startNode, iterableExpression, itemExpression, endNode, c iterable = [] } + + // create each loop result // using a for-of loop to accept all iterable values for(const item of iterable) { + console.log('{#each}', itemExpression, item) + console.log('doc text', doc.textContent) + /** @type {DocumentFragment} */ // @ts-ignore const itemFragment = repeatedFragment.cloneNode(true) + console.log('itemFragment', itemFragment.textContent) let insideCompartment = new Compartment({ globals: Object.assign({}, compartment.globalThis, {[itemExpression]: item}), @@ -262,12 +284,19 @@ function fillEachBlock(startNode, iterableExpression, itemExpression, endNode, c insideCompartment ) + console.log('{#each} fragment', itemFragment.textContent) + endChild.parentNode.insertBefore(itemFragment, endChild) } + console.log('doc text after each', doc.textContent) + // remove block marker elements startChild.parentNode.removeChild(startChild) endChild.parentNode.removeChild(endChild) + + console.log('doc text after removes', doc.textContent) + } @@ -352,10 +381,14 @@ export default function fillOdtElementTemplate(rootElement, compartment) { if(currentlyOpenBlocks.length === 1) { eachClosingMarkerNode = currentNode + console.log('before fillEachBlock', eachClosingMarkerNode.parentNode.textContent) + // found an {#each} and its corresponding {/each} // execute replacement loop fillEachBlock(eachOpeningMarkerNode, eachBlockIterableExpression, eachBlockItemExpression, eachClosingMarkerNode, compartment) + console.log('after fillEachBlock', rootElement.documentElement.textContent) + eachOpeningMarkerNode = undefined eachBlockIterableExpression = undefined eachBlockItemExpression = undefined diff --git a/scripts/odf/templating/prepareTemplateDOMTree.js b/scripts/odf/templating/prepareTemplateDOMTree.js index b631ca8..33893ad 100644 --- a/scripts/odf/templating/prepareTemplateDOMTree.js +++ b/scripts/odf/templating/prepareTemplateDOMTree.js @@ -1,3 +1,5 @@ +//@ts-check + import {traverse, Node} from "../../DOMUtils.js"; import {closingIfMarker, eachClosingMarker, eachStartMarkerRegex, elseMarker, ifStartMarkerRegex, variableRegex} from './markers.js' @@ -39,7 +41,7 @@ function findAllMatches(text, pattern) { * * @param {Node} node1 * @param {Node} node2 - * @returns {Node | undefined} + * @returns {Node} */ function findCommonAncestor(node1, node2) { const ancestors1 = getAncestors(node1); @@ -51,7 +53,7 @@ function findCommonAncestor(node1, node2) { } } - return undefined; + throw new Error(`node1 and node2 do not have a common ancestor`) } /** @@ -281,6 +283,8 @@ function consolidateMarkers(document){ newStartNode.parentNode?.removeChild(newStartNode) commonAncestor.insertBefore(newStartNode, commonAncestorStartChild.nextSibling) + + //console.log('commonAncestor after before-text split', commonAncestor.textContent ) } @@ -299,11 +303,15 @@ function consolidateMarkers(document){ endNode.parentNode?.removeChild(endNode) commonAncestor.insertBefore(endNode, commonAncestorEndChild) } + + //console.log('commonAncestor after after-text split', commonAncestor.textContent ) } // then, replace all nodes between (new)startNode and (new)endNode with a single textNode in commonAncestor replaceBetweenNodesWithText(newStartNode, endNode, positionedMarker.marker) + //console.log('commonAncestor after replaceBetweenNodesWithText', commonAncestor.textContent ) + // After consolidation, break as the DOM structure has changed // and containerTextNodesInTreeOrder needs to be refreshed consolidatedMarkers.push(positionedMarker) @@ -323,6 +331,8 @@ function consolidateMarkers(document){ */ function isolateMarkers(document){ traverse(document, currentNode => { + //console.log('isolateMarkers', currentNode.nodeName, currentNode.textContent) + if(currentNode.nodeType === Node.TEXT_NODE) { // find all marker starts and ends and split textNode let remainingText = currentNode.textContent || '' diff --git a/tests/fill-odt-template/each.js b/tests/fill-odt-template/each.js index 8c9cc43..859a4a7 100644 --- a/tests/fill-odt-template/each.js +++ b/tests/fill-odt-template/each.js @@ -6,7 +6,7 @@ import {getOdtTemplate} from '../../scripts/odf/odtTemplate-forNode.js' import {fillOdtTemplate, getOdtTextContent} from '../../exports.js' -test('basic template filling with {#each}', async t => { +test.skip('basic template filling with {#each}', async t => { const templatePath = join(import.meta.dirname, '../fixtures/enum-courses.odt') const templateContent = `🧺 La liste de courses incroyable 🧺 @@ -43,7 +43,7 @@ Pâtes à lasagne (fraîches !) }); -test('Filling with {#each} and non-iterable value results in no error and empty result', async t => { +test.skip('Filling with {#each} and non-iterable value results in no error and empty result', async t => { const templatePath = join(import.meta.dirname, '../fixtures/enum-courses.odt') const templateContent = `🧺 La liste de courses incroyable 🧺 @@ -73,7 +73,7 @@ test('Filling with {#each} and non-iterable value results in no error and empty }); -test('template filling with {#each} generating a list', async t => { +test.skip('template filling with {#each} generating a list', async t => { const templatePath = join(import.meta.dirname, '../fixtures/liste-courses.odt') const templateContent = `🧺 La liste de courses incroyable 🧺 @@ -110,7 +110,7 @@ test('template filling with {#each} generating a list', async t => { }); -test('template filling with 2 sequential {#each}', async t => { +test.skip('template filling with 2 sequential {#each}', async t => { const templatePath = join(import.meta.dirname, '../fixtures/liste-fruits-et-légumes.odt') const templateContent = `Liste de fruits et légumes @@ -162,7 +162,7 @@ Poivron 🫑 }); -test('template filling with nested {#each}s', async t => { +test.skip('template filling with nested {#each}s', async t => { const templatePath = join(import.meta.dirname, '../fixtures/légumes-de-saison.odt') const templateContent = `Légumes de saison @@ -247,7 +247,43 @@ Hiver }); -test('template filling of a table', async t => { +test('template filling with text after {/each} in same text node', async t => { + const templatePath = join(import.meta.dirname, '../fixtures/text-after-closing-each.odt') + const templateContent = `Légumes de saison + +{#each légumes as légume} +{légume}, +{/each} en {saison} +` + + const data = { + saison: 'Printemps', + légumes: [ + 'Asperge', + 'Betterave', + 'Blette' + ] + } + + 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, `Légumes de saison + +Asperge, +Betterave, +Blette, en Printemps +`) + +}); + + +test.skip('template filling of a table', async t => { const templatePath = join(import.meta.dirname, '../fixtures/tableau-simple.odt') const templateContent = `Évolution énergie en kWh par personne en France diff --git a/tests/fixtures/text-after-closing-each.odt b/tests/fixtures/text-after-closing-each.odt new file mode 100644 index 0000000..b452f15 Binary files /dev/null and b/tests/fixtures/text-after-closing-each.odt differ