From 56576a589db305e556a2d2d2e5448003fba9493e Mon Sep 17 00:00:00 2001 From: David Bruant Date: Fri, 9 May 2025 18:42:30 +0200 Subject: [PATCH] some tests passing --- .../odf/templating/fillOdtElementTemplate.js | 54 ++++++- .../odf/templating/prepareTemplateDOMTree.js | 132 +++++++++++++++++- tests/fill-odt-template/each.js | 4 +- 3 files changed, 184 insertions(+), 6 deletions(-) diff --git a/scripts/odf/templating/fillOdtElementTemplate.js b/scripts/odf/templating/fillOdtElementTemplate.js index 3dc1458..c2ed94a 100644 --- a/scripts/odf/templating/fillOdtElementTemplate.js +++ b/scripts/odf/templating/fillOdtElementTemplate.js @@ -260,7 +260,8 @@ function fillEachBlock(startNode, iterableExpression, itemExpression, endNode, c iterable = [] } - + let firstItemFirstChild + let lastItemLastChild // create each loop result // using a for-of loop to accept all iterable values @@ -284,11 +285,62 @@ function fillEachBlock(startNode, iterableExpression, itemExpression, endNode, c insideCompartment ) + if(!firstItemFirstChild){ + firstItemFirstChild = itemFragment.firstChild + } + + // eventually, will be set to the last item's last child + lastItemLastChild = itemFragment.lastChild + console.log('{#each} fragment', itemFragment.textContent) endChild.parentNode.insertBefore(itemFragment, endChild) } + // add before-text if any + const startNodePreviousSiblings = [] + let startNodePreviousSibling = startNode.previousSibling + while(startNodePreviousSibling){ + startNodePreviousSiblings.push(startNodePreviousSibling) + startNodePreviousSibling = startNodePreviousSibling.previousSibling + } + + // set the array back to tree order + startNodePreviousSiblings.reverse() + + if(startNodePreviousSiblings.length >= 1){ + let firstItemFirstestDescendant = firstItemFirstChild + while(firstItemFirstestDescendant?.firstChild){ + firstItemFirstestDescendant = firstItemFirstestDescendant.firstChild + } + + for(const beforeFirstNodeElement of startNodePreviousSiblings){ + firstItemFirstestDescendant?.parentNode?.insertBefore(beforeFirstNodeElement, firstItemFirstestDescendant) + } + } + + + + // add after-text if any + const endNodeNextSiblings = [] + let endNodeNextSibling = endNode.nextSibling + while(endNodeNextSibling){ + endNodeNextSiblings.push(endNodeNextSibling) + endNodeNextSibling = endNodeNextSibling.nextSibling + } + + if(endNodeNextSiblings.length >= 1){ + let lastItemLatestDescendant = lastItemLastChild + while(lastItemLatestDescendant?.lastChild){ + lastItemLatestDescendant = lastItemLatestDescendant.lastChild + } + + for(const afterEndNodeElement of endNodeNextSiblings){ + lastItemLatestDescendant?.parentNode?.appendChild(afterEndNodeElement) + } + } + + console.log('doc text after each', doc.textContent) // remove block marker elements diff --git a/scripts/odf/templating/prepareTemplateDOMTree.js b/scripts/odf/templating/prepareTemplateDOMTree.js index 33893ad..698e823 100644 --- a/scripts/odf/templating/prepareTemplateDOMTree.js +++ b/scripts/odf/templating/prepareTemplateDOMTree.js @@ -324,12 +324,26 @@ function consolidateMarkers(document){ } } +/** + * @typedef {typeof closingIfMarker | typeof eachClosingMarker | typeof eachStartMarkerRegex.source | typeof elseMarker | typeof ifStartMarkerRegex.source | typeof variableRegex.source} MarkerType + */ + +/** + * @typedef {Object} MarkerNode + * @prop {Node} node + * @prop {MarkerType} markerType + */ + /** * isolate markers which are in Text nodes with other texts * * @param {Document} document + * @returns {Map} */ -function isolateMarkers(document){ +function isolateMarkerText(document){ + /** @type {ReturnType} */ + const markerNodes = new Map() + traverse(document, currentNode => { //console.log('isolateMarkers', currentNode.nodeName, currentNode.textContent) @@ -340,6 +354,8 @@ function isolateMarkers(document){ while(remainingText.length >= 1) { let matchText; let matchIndex; + /** @type {MarkerType} */ + let markerType; // looking for a block marker for(const marker of [ifStartMarkerRegex, elseMarker, closingIfMarker, eachStartMarkerRegex, eachClosingMarker]) { @@ -349,6 +365,7 @@ function isolateMarkers(document){ if(index !== -1) { matchText = marker matchIndex = index + markerType = marker // found the first match break; // get out of loop @@ -361,6 +378,7 @@ function isolateMarkers(document){ if(match) { matchText = match[0] matchIndex = match.index + markerType = marker.source // found the first match break; // get out of loop @@ -383,11 +401,21 @@ function isolateMarkers(document){ // per spec, currentNode now contains before-match and match text + /** @type {Node} */ + let matchTextNode + // @ts-ignore if(matchIndex > 0) { // @ts-ignore - currentNode.splitText(matchIndex) + matchTextNode = currentNode.splitText(matchIndex) } + else{ + matchTextNode = currentNode + } + + markerNodes.set(matchTextNode, markerType) + + // per spec, currentNode now contains only before-match text if(afterMatchTextNode) { currentNode = afterMatchTextNode @@ -407,8 +435,100 @@ function isolateMarkers(document){ // skip } }) + + return markerNodes } + + +/** + * after isolateMatchingMarkersStructure, matching markers (opening/closing each, if/then/closing if) + * are put in isolated branches within their common ancestors + * + * UNFINISHED - maybe another day if relevant + * + * @param {Document} document + * @param {Map} markerNodes + */ +//function isolateMatchingMarkersStructure(document, markerNodes){ + /** @type {MarkerNode[]} */ +/* let currentlyOpenBlocks = [] + + traverse(document, currentNode => { + + const markerType = markerNodes.get(currentNode) + + if(markerType){ + switch(markerType){ + case eachStartMarkerRegex.source: + case ifStartMarkerRegex.source: { + currentlyOpenBlocks.push({ + node: currentNode, + markerType + }) + break; + } + case eachClosingMarker: { + const lastOpenedBlockMarkerNode = currentlyOpenBlocks.pop() + + if(!lastOpenedBlockMarkerNode) + throw new Error(`{/each} found without corresponding opening {#each x as y}`) + + if(lastOpenedBlockMarkerNode.markerType !== eachStartMarkerRegex.source) + throw new Error(`{/each} found while the last opened block was not an opening {#each x as y} (it was a ${lastOpenedBlockMarkerNode.markerType})`) + + const openingEachNode = lastOpenedBlockMarkerNode.node + const closingEachNode = currentNode + + const commonAncestor = findCommonAncestor(openingEachNode, closingEachNode) + + if(openingEachNode.parentNode !== commonAncestor && openingEachNode.parentNode.childNodes.length >= 2){ + if(openingEachNode.previousSibling){ + // create branch for previousSiblings + let previousSibling = openingEachNode.previousSibling + const previousSiblings = [] + while(previousSibling){ + previousSiblings.push(previousSibling.previousSibling) + previousSibling = previousSibling.previousSibling + } + + // put previous siblings in tree order + previousSiblings.reverse() + + const parent = openingEachNode.parentNode + const parentClone = parent.cloneNode(false) + for(const previousSibling of previousSiblings){ + previousSibling.parentNode.removeChild(previousSibling) + parentClone.appendChild(previousSibling) + } + + let openingEachNodeBranch = openingEachNode.parentNode + let branchForPreviousSiblings = parentClone + + while(openingEachNodeBranch.parentNode !== commonAncestor){ + const newParentClone = openingEachNodeBranch.parentNode.cloneNode(false) + branchForPreviousSiblings.parentNode.removeChild(branchForPreviousSiblings) + newParentClone.appendChild(branchForPreviousSiblings) + } + } + } + + + + + break; + } + + default: + throw new TypeError(`MarkerType not recognized: '${markerType}`) + } + } + + }) + +}*/ + + /** * This function prepares the template DOM tree in a way that makes it easily processed by the template execution * Specifically, after the call to this function, the document is altered to respect the following property: @@ -425,5 +545,11 @@ function isolateMarkers(document){ */ export default function prepareTemplateDOMTree(document){ consolidateMarkers(document) - isolateMarkers(document) + // after consolidateMarkers, each marker is in at most one text node + // (formatting with markers is removed) + + isolateMarkerText(document) + // after isolateMarkerText, each marker is in exactly one text node + // (markers are separated from text that was before or after in the same text node) + } \ No newline at end of file diff --git a/tests/fill-odt-template/each.js b/tests/fill-odt-template/each.js index 859a4a7..0fa3bdf 100644 --- a/tests/fill-odt-template/each.js +++ b/tests/fill-odt-template/each.js @@ -277,13 +277,13 @@ test('template filling with text after {/each} in same text node', async t => { Asperge, Betterave, -Blette, en Printemps +Blette, en Printemps `) }); -test.skip('template filling of a table', async t => { +test('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