diff --git a/scripts/DOMUtils.js b/scripts/DOMUtils.js index 5df3e65..14dfb3b 100644 --- a/scripts/DOMUtils.js +++ b/scripts/DOMUtils.js @@ -42,6 +42,51 @@ export function traverse(node, visit) { visit(node); } + +/** + * + * @param {Node} node1 + * @param {Node} node2 + * @returns {Node} + */ +export function findCommonAncestor(node1, node2) { + const ancestors1 = getAncestors(node1); + const ancestors2 = new Set(getAncestors(node2)); + + for(const ancestor of ancestors1) { + if(ancestors2.has(ancestor)) { + return ancestor; + } + } + + throw new Error(`node1 and node2 do not have a common ancestor`) +} + +/** + * returns ancestors youngest first, oldest last + * + * @param {Node} node + * @param {Node} [until] + * @returns {Node[]} + */ +export function getAncestors(node, until = undefined) { + const ancestors = []; + let current = node; + + while(current && current !== until) { + ancestors.push(current); + current = current.parentNode; + } + + if(current === until){ + ancestors.push(until); + } + + return ancestors; +} + + + export { DOMParser, XMLSerializer, diff --git a/scripts/odf/templating/fillOdtElementTemplate.js b/scripts/odf/templating/fillOdtElementTemplate.js index a23043c..f7df3cf 100644 --- a/scripts/odf/templating/fillOdtElementTemplate.js +++ b/scripts/odf/templating/fillOdtElementTemplate.js @@ -1,4 +1,4 @@ -import {traverse, Node} from '../../DOMUtils.js' +import {traverse, Node, getAncestors, findCommonAncestor} from "../../DOMUtils.js"; import {closingIfMarker, eachClosingMarker, eachStartMarkerRegex, elseMarker, ifStartMarkerRegex, variableRegex} from './markers.js' /** @@ -7,6 +7,348 @@ import {closingIfMarker, eachClosingMarker, eachStartMarkerRegex, elseMarker, if * @property {() => void} fill */ +class TemplateDOMBranch{ + /** @type {Node} */ + #branchBaseNode + + /** @type {Node} */ + #leafNode + + // ancestors with this.#ancestors[0] === this.#startNode and this.#ancestors.at(-1) === this.#leafNode + /** @type {Node[]} */ + #ancestors + + /** + * + * @param {Node} branchBaseNode + * @param {Node} leafNode + */ + constructor(branchBaseNode, leafNode){ + this.#branchBaseNode = branchBaseNode + this.#leafNode = leafNode + + this.#ancestors = getAncestors(this.#leafNode, this.#branchBaseNode).reverse() + } + + /** + * + * @param {number} n + * @returns {Node | undefined} + */ + at(n){ + return this.#ancestors.at(n) + } + + removeLeafAndEmptyAncestors(){ + //this.logBranch('[removeLeafAndEmptyAncestors] branch at the start') + + // it may happen (else marker of if/else/endif) that the leaf was already removed as part of another block + // so before removing anything, let's update #ancestors and #leaf + + this.#ancestors.every((ancestor, i) => { + if(!ancestor.parentNode){ + // ancestor already removed from tree + this.#ancestors = this.#ancestors.slice(0, i) + return false; + } + + return true // continue + }) + + this.#leafNode = this.#ancestors.at(-1) + + //this.logBranch('[removeLeafAndEmptyAncestors] after adjusting this.#ancestors') + + //console.log('removeLeafAndEmptyAncestors', this.#startNode.textContent) + let nextLeaf + if(this.#leafNode !== this.#branchBaseNode){ + nextLeaf = this.#leafNode.parentNode + //console.log('nextLeaf', !!nextLeaf) + nextLeaf.removeChild(this.#leafNode) + this.#leafNode = nextLeaf + } + + while(this.#leafNode !== this.#branchBaseNode && + (this.#leafNode.textContent === null || this.#leafNode.textContent.trim() === '')) + { + nextLeaf = this.#leafNode.parentNode + this.#leafNode.parentNode.removeChild(this.#leafNode) + this.#leafNode = nextLeaf + } + + this.#ancestors = getAncestors(this.#leafNode, this.#branchBaseNode).reverse() + } + + /** + * + * @param {number} [startIndex] + */ + removeRightContent(startIndex = 0){ + //console.log('[removeRightContent]', startIndex, this.#ancestors.slice(startIndex).length) + + for(const branchNode of this.#ancestors.slice(startIndex)){ + //console.log('[removeRightContent]', branchNode.nodeType, branchNode.nodeName) + + let toRemove = branchNode.nextSibling + + while(toRemove){ + const toRemoveNext = toRemove.nextSibling + toRemove.parentNode.removeChild(toRemove) + toRemove = toRemoveNext + } + } + } + + /** + * + * @param {number} [startIndex] + */ + removeLeftContent(startIndex = 0){ + for(const branchNode of this.#ancestors.slice(startIndex)){ + let toRemove = branchNode.previousSibling + + while(toRemove){ + const toRemoveNext = toRemove.previousSibling + toRemove.parentNode.removeChild(toRemove) + toRemove = toRemoveNext + } + } + } + + /** + * + * @returns {number[]} + */ + getBranchPath(){ + //console.log('[getBranchPath]', this.#branchBaseNode.nodeName, this.#branchBaseNode.textContent) + //console.log('[getBranchPath] leaf', this.#leafNode.nodeName, this.#leafNode.textContent) + + /** @type {ReturnType} */ + const pathFromLeafToBase = []; + + let currentNode = this.#leafNode + let currentNodeParent = currentNode.parentNode + + while(currentNodeParent){ + //console.log('[getBranchPath] currentNodeParent', currentNodeParent.nodeName) + //console.log('[getBranchPath] looking for currentNode', currentNode.nodeName, currentNode.textContent) + //console.log('[getBranchPath] currentNodeParent.childNodes.length', currentNodeParent.childNodes.length) + /*console.log('[getBranchPath] currentNodeParent.childNodes', Array.from(currentNodeParent.childNodes) + .map(n => `${n.nodeName} - ${n.textContent}`) + )*/ + + const index = Array.from(currentNodeParent.childNodes).indexOf(currentNode) + //console.log('[getBranchPath] indexOf', index) + if(index === -1){ + throw new Error(`Could not find currentNode in currentNodeParent's childNodes`) + } + pathFromLeafToBase.push(index) + //console.log('[getBranchPath] currentNodeParent and index', currentNodeParent.nodeName, index) + + if(currentNodeParent === this.#ancestors[0]){ + break; // path is fnished + } + else{ + currentNode = currentNodeParent + currentNodeParent = currentNode.parentNode + } + } + + //@ts-expect-error ES2023 + return pathFromLeafToBase.toReversed() + } + + logBranch(message){ + console.group('[TemplateDOMBranch] Showing branch') + console.log(message) + for(const node of this.#ancestors){ + console.log('branch node', node.nodeType, node.nodeName, node.nodeType === node.TEXT_NODE ? node.textContent : '') + } + console.groupEnd() + } + + +} + + +class TemplateBlock{ + /** @type {Element | Document | DocumentFragment} */ + #commonAncestor; + /** @type {TemplateDOMBranch} */ + startBranch; + /** @type {TemplateDOMBranch} */ + endBranch; + + /** @type {Node[]} */ + #middleContent; + + /** + * + * @param {Node} startNode + * @param {Node} endNode + */ + constructor(startNode, endNode){ + // @ts-expect-error xmldom.Node + this.#commonAncestor = findCommonAncestor(startNode, endNode) + + //console.log('create start branch') + this.startBranch = new TemplateDOMBranch(this.#commonAncestor, startNode) + //console.log('create end branch') + this.endBranch = new TemplateDOMBranch(this.#commonAncestor, endNode) + + + + this.#middleContent = [] + + let content = this.startBranch.at(1).nextSibling + while(content && content !== this.endBranch.at(1)){ + this.#middleContent.push(content) + content = content.nextSibling + } + + //console.group('\n== TemplateBlock ==') + //console.log('startBranch', this.startBranch.at(1).nodeName, this.startBranch.at(1).textContent) + //console.log('middleContent', this.#middleContent.map(n => n.textContent).join('')) + //console.log('endBranch', this.startBranch.at(1).nodeName, this.endBranch.at(1).textContent) + //console.log('common ancestor', this.#commonAncestor.nodeName, '\n') + //console.groupEnd() + } + + removeMarkersAndEmptyAncestors(){ + //console.log('[removeMarkersAndEmptyAncestors]', this.#commonAncestor.textContent) + this.startBranch.removeLeafAndEmptyAncestors() + this.endBranch.removeLeafAndEmptyAncestors() + //console.log('[removeMarkersAndEmptyAncestors] after', this.#commonAncestor.textContent) + } + + /** + * + * @param {Compartment} compartement + */ + fillBlockContentTemplate(compartement){ + //console.log('[fillBlockContentTemplate] start') + + const startChild = this.startBranch.at(1) + if(startChild /*&& startChild !== */){ + //console.log('[fillBlockContentTemplate] startChild', startChild.nodeName, startChild.textContent) + fillOdtElementTemplate(startChild, compartement) + } + //console.log('[fillBlockContentTemplate] after startChild') + + for(const content of this.#middleContent){ + fillOdtElementTemplate(content, compartement) + } + //console.log('[fillBlockContentTemplate] after middleContent') + + const endChild = this.endBranch.at(1) + //console.log('fillBlockContentTemplate] [endBranch]') + //this.endBranch.logBranch('endBranch') + + if(endChild){ + //console.log('[fillBlockContentTemplate] endChild', endChild.nodeName, endChild.textContent) + fillOdtElementTemplate(endChild, compartement) + } + //console.log('[fillBlockContentTemplate] after endChild') + + //console.log('[fillBlockContentTemplate] end') + } + + removeContent(){ + this.startBranch.removeRightContent(2) + + for(const content of this.#middleContent){ + content.parentNode.removeChild(content) + } + + this.endBranch.removeLeftContent(2) + } + + + + /** + * @returns {TemplateBlock} + */ + cloneAndAppendAfter(){ + //console.log('[cloneAndAppendAfter]') + const clonedPieces = [] + + let startBranchClone; + let endBranchClone; + + for(const sibling of [this.startBranch.at(1), ...this.#middleContent, this.endBranch.at(1)]){ + if(sibling){ + const siblingClone = sibling.cloneNode(true) + clonedPieces.push(siblingClone) + + if(sibling === this.startBranch.at(1)) + startBranchClone = siblingClone + + if(sibling === this.endBranch.at(1)) + endBranchClone = siblingClone + + } + } + + let startChildPreviousSiblingsCount = 0 + let previousSibling = this.startBranch.at(1).previousSibling + while(previousSibling){ + startChildPreviousSiblingsCount = startChildPreviousSiblingsCount + 1 + previousSibling = previousSibling.previousSibling + } + + const startBranchPathFromBaseToLeaf = this.startBranch.getBranchPath().slice(1) + const endBranchPathFromBaseToLeaf = this.endBranch.getBranchPath().slice(1) + + + //console.log('startBranchClone', !!startBranchClone) + //console.log('startBranchPathFromBaseToLeaf', startBranchPathFromBaseToLeaf) + + let startLeafCloneNode + { + let node = startBranchClone + for(let pathIndex of startBranchPathFromBaseToLeaf){ + //console.log('[startLeafCloneNode] node.childNodes.length', node.childNodes.length) + //console.log('[startLeafCloneNode] pathIndex', pathIndex) + + node = node.childNodes[pathIndex] + } + startLeafCloneNode = node + } + + //console.log('endBranchClone', !!endBranchClone) + //console.log('endBranchPathFromBaseToLeaf', endBranchPathFromBaseToLeaf) + + let endLeafCloneNode + { + let node = endBranchClone + for(let pathIndex of endBranchPathFromBaseToLeaf){ + + //console.log('[endLeafCloneNode] node.childNodes.length', node.childNodes.length) + //console.log('[endLeafCloneNode] pathIndex', pathIndex) + + node = node.childNodes[pathIndex] + } + endLeafCloneNode = node + } + + let insertBeforePoint = this.endBranch.at(1) && this.endBranch.at(1).nextSibling + + if(insertBeforePoint){ + for(const node of clonedPieces){ + this.#commonAncestor.insertBefore(node, insertBeforePoint) + } + } + else{ + for(const node of clonedPieces){ + this.#commonAncestor.appendChild(node) + } + } + + return new TemplateBlock(startLeafCloneNode, endLeafCloneNode) + } + +} + /** * @param {string} str @@ -71,130 +413,6 @@ function findPlacesToFillInString(str, compartment) { } -/** - * Content between blockStartNode and blockEndNode is extracted to a documentFragment - * The original document is modified because nodes are removed from it to be part of the returned documentFragment - * - * startChild and endChild are ancestors of, respectively, blockStartNode and blockEndNode - * and startChild.parentNode === endChild.parentNode - * - * @precondition blockStartNode needs to be before blockEndNode in document order - * - * @param {Node} blockStartNode - * @param {Node} blockEndNode - * @returns {{startChild: Node, endChild:Node, content: DocumentFragment}} - */ -function extractBlockContent(blockStartNode, blockEndNode) { - //console.log('[extractBlockContent] blockStartNode', blockStartNode.textContent) - //console.log('[extractBlockContent] blockEndNode', blockEndNode.textContent) - - // find common ancestor of blockStartNode and blockEndNode - let commonAncestor - - let startAncestor = blockStartNode - let endAncestor = blockEndNode - - // ancestries in order of deepest first, closest to root last - const startAncestry = new Set([startAncestor]) - const endAncestry = new Set([endAncestor]) - - while(!startAncestry.has(endAncestor) && !endAncestry.has(startAncestor)) { - if(startAncestor.parentNode) { - startAncestor = startAncestor.parentNode - startAncestry.add(startAncestor) - } - if(endAncestor.parentNode) { - endAncestor = endAncestor.parentNode - endAncestry.add(endAncestor) - } - } - - if(startAncestry.has(endAncestor)) { - commonAncestor = endAncestor - } - else { - commonAncestor = startAncestor - } - - const startAncestryToCommonAncestor = [...startAncestry].slice(0, [...startAncestry].indexOf(commonAncestor)) - const endAncestryToCommonAncestor = [...endAncestry].slice(0, [...endAncestry].indexOf(commonAncestor)) - - // direct children of commonAncestor in the branch or blockStartNode and blockEndNode respectively - const startChild = startAncestryToCommonAncestor.at(-1) - const endChild = endAncestryToCommonAncestor.at(-1) - - //console.log('[extractBlockContent] startChild', startChild.textContent) - //console.log('[extractBlockContent] endChild', endChild.textContent) - - // Extract DOM content in a documentFragment - /** @type {DocumentFragment} */ - const contentFragment = blockStartNode.ownerDocument.createDocumentFragment() - - /** @type {Element[]} */ - const blockContent = [] - - // get start branch "right" content - for(const startAncestor of startAncestryToCommonAncestor){ - if(startAncestor === startChild) - break; - - let sibling = startAncestor.nextSibling - - while(sibling) { - blockContent.push(sibling) - sibling = sibling.nextSibling; - } - } - - - let sibling = startChild.nextSibling - - while(sibling !== endChild) { - blockContent.push(sibling) - sibling = sibling.nextSibling; - } - - - // get end branch "left" content - for(const endAncestor of [...endAncestryToCommonAncestor].reverse()){ - if(endAncestor === endChild) - continue; // already taken care of - - let sibling = endAncestor.previousSibling - - const reversedBlockContentContribution = [] - - while(sibling) { - reversedBlockContentContribution.push(sibling) - sibling = sibling.previousSibling; - } - - const blockContentContribution = reversedBlockContentContribution.reverse() - - blockContent.push(...blockContentContribution) - - if(endAncestor === blockEndNode) - break; - } - - - //console.log('blockContent', blockContent.map(n => n.textContent)) - - - for(const sibling of blockContent) { - sibling.parentNode?.removeChild(sibling) - contentFragment.appendChild(sibling) - } - - //console.log('extractBlockContent contentFragment', contentFragment.textContent) - - return { - startChild, - endChild, - content: contentFragment - } -} - @@ -209,65 +427,43 @@ function extractBlockContent(blockStartNode, blockEndNode) { function fillIfBlock(ifOpeningMarkerNode, ifElseMarkerNode, ifClosingMarkerNode, ifBlockConditionExpression, compartment) { const conditionValue = compartment.evaluate(ifBlockConditionExpression) - let startChild - let endChild - - let markerNodes = new Set() - - let chosenFragment + /** @type {TemplateBlock | undefined} */ + let thenTemplateBlock + /** @type {TemplateBlock | undefined} */ + let elseTemplateBlock if(ifElseMarkerNode) { - const { - startChild: startIfThenChild, - endChild: endIfThenChild, - content: thenFragment - } = extractBlockContent(ifOpeningMarkerNode, ifElseMarkerNode) + /*console.log('before first extract', + ifOpeningMarkerNode.childNodes.length, ifOpeningMarkerNode.textContent, + ifElseMarkerNode.childNodes.length, ifElseMarkerNode.textContent + )*/ - const { - startChild: startIfElseChild, - endChild: endIfElseChild, - content: elseFragment - } = extractBlockContent(ifElseMarkerNode, ifClosingMarkerNode) - - chosenFragment = conditionValue ? thenFragment : elseFragment - startChild = startIfThenChild - endChild = endIfElseChild - - markerNodes - .add(startIfThenChild).add(endIfThenChild) - .add(startIfElseChild).add(endIfElseChild) + thenTemplateBlock = new TemplateBlock(ifOpeningMarkerNode, ifElseMarkerNode) + elseTemplateBlock = new TemplateBlock(ifElseMarkerNode, ifClosingMarkerNode) } else { - const { - startChild: startIfThenChild, - endChild: endIfThenChild, - content: thenFragment - } = extractBlockContent(ifOpeningMarkerNode, ifClosingMarkerNode) - - chosenFragment = conditionValue ? thenFragment : undefined - startChild = startIfThenChild - endChild = endIfThenChild - - markerNodes - .add(startIfThenChild).add(endIfThenChild) + thenTemplateBlock = new TemplateBlock(ifOpeningMarkerNode, ifClosingMarkerNode) } - if(chosenFragment) { - fillOdtElementTemplate( - chosenFragment, - compartment - ) - - endChild.parentNode.insertBefore(chosenFragment, endChild) + thenTemplateBlock.removeMarkersAndEmptyAncestors() + if(elseTemplateBlock){ + elseTemplateBlock.removeMarkersAndEmptyAncestors() } - for(const markerNode of markerNodes) { - try { - // may throw if node already out of tree - // might happen if - markerNode.parentNode.removeChild(markerNode) + + if(conditionValue) { + thenTemplateBlock.fillBlockContentTemplate(compartment) + + if(elseTemplateBlock){ + elseTemplateBlock.removeContent() + } + } + else{ + thenTemplateBlock.removeContent() + + if(elseTemplateBlock){ + elseTemplateBlock.fillBlockContentTemplate(compartment) } - catch(e) {} } } @@ -284,94 +480,67 @@ function fillIfBlock(ifOpeningMarkerNode, ifElseMarkerNode, ifClosingMarkerNode, function fillEachBlock(startNode, iterableExpression, itemExpression, endNode, compartment) { //console.log('fillEachBlock', iterableExpression, itemExpression) - const {startChild, endChild, content: repeatedFragment} = extractBlockContent(startNode, endNode) + const docEl = startNode.ownerDocument.documentElement + //console.log('[fillEachBlock] docEl', docEl.textContent) + + const repeatedTemplateBlock = new TemplateBlock(startNode, endNode) // Find the iterable in the data - // PPP eventually, evaluate the expression as a JS expression let iterable = compartment.evaluate(iterableExpression) if(!iterable || typeof iterable[Symbol.iterator] !== 'function') { // when there is no iterable, silently replace with empty array iterable = [] } - let firstItemFirstChild - let lastItemLastChild + // convert to array to know the size and know which element is last + if(!Array.isArray(iterable)) + iterable = [...iterable] - // create each loop result - // using a for-of loop to accept all iterable values - for(const item of iterable) { - /** @type {DocumentFragment} */ - // @ts-ignore - const itemFragment = repeatedFragment.cloneNode(true) + if(iterable.length === 0){ + repeatedTemplateBlock.removeMarkersAndEmptyAncestors() + repeatedTemplateBlock.removeContent() + } + else{ + let nextTemplateBlock = repeatedTemplateBlock + + iterable.forEach((item, i) => { + //console.log('[fillEachBlock] loop i', i, docEl.textContent) + const firstItem = i === 0 + const lastItem = i === iterable.length - 1 + let currentTemplateBlock = nextTemplateBlock; + + //console.log('currentTemplateBlock', currentTemplateBlock.startBranch.at(0).textContent) + + if(!lastItem){ + nextTemplateBlock = currentTemplateBlock.cloneAndAppendAfter() + } + + let insideCompartment = new Compartment({ + globals: Object.assign({}, compartment.globalThis, {[itemExpression]: item}), + __options__: true + }) + + if(!firstItem){ + currentTemplateBlock.startBranch.removeLeftContent(2) + } + if(!lastItem){ + //console.log('[fillEachBlock] removeRightContent') + currentTemplateBlock.endBranch.removeRightContent(2) + } + + //console.log('[fillEachBlock] docEl i before removeMarkers', i, docEl.textContent) + currentTemplateBlock.removeMarkersAndEmptyAncestors() + //console.log('[fillEachBlock] docEl i after removeMarkers', i, docEl.textContent) + + //console.log('\nrecursive call to fillBlockContentTemplate') + currentTemplateBlock.fillBlockContentTemplate(insideCompartment) + + //console.log('[fillEachBlock] docEl i after remove contents', i, docEl.textContent) - let insideCompartment = new Compartment({ - globals: Object.assign({}, compartment.globalThis, {[itemExpression]: item}), - __options__: true }) - - // recursive call to fillTemplatedOdtElement on itemFragment - fillOdtElementTemplate( - itemFragment, - insideCompartment - ) - - if(!firstItemFirstChild){ - firstItemFirstChild = itemFragment.firstChild - } - - // eventually, will be set to the last item's last child - lastItemLastChild = itemFragment.lastChild - - 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) - } - } - - - // remove block marker elements - startChild.parentNode.removeChild(startChild) - endChild.parentNode.removeChild(endChild) - } @@ -385,9 +554,9 @@ const EACH = eachStartMarkerRegex.source * @returns {void} */ export default function fillOdtElementTemplate(rootElement, compartment) { - //console.log('fillTemplatedOdtElement', rootElement.nodeType, rootElement.nodeName, rootElement.textContent) - //console.log('fillTemplatedOdtElement', rootElement.childNodes[0].childNodes.length) - + //console.log('[fillTemplatedOdtElement]', rootElement.nodeType, rootElement.nodeName, rootElement.textContent) + //console.log('[fillTemplatedOdtElement]', rootElement.documentElement && rootElement.documentElement.textContent) + let currentlyOpenBlocks = [] /** @type {Node | undefined} */ diff --git a/scripts/odf/templating/prepareTemplateDOMTree.js b/scripts/odf/templating/prepareTemplateDOMTree.js index 8c75d9e..3068ab0 100644 --- a/scripts/odf/templating/prepareTemplateDOMTree.js +++ b/scripts/odf/templating/prepareTemplateDOMTree.js @@ -1,6 +1,6 @@ //@ts-check -import {traverse, Node} from "../../DOMUtils.js"; +import {traverse, Node, getAncestors, findCommonAncestor} from "../../DOMUtils.js"; import {closingIfMarker, eachClosingMarker, eachStartMarkerRegex, elseMarker, ifStartMarkerRegex, variableRegex} from './markers.js' @@ -38,41 +38,7 @@ function findAllMatches(text, pattern) { return results; } -/** - * - * @param {Node} node1 - * @param {Node} node2 - * @returns {Node} - */ -function findCommonAncestor(node1, node2) { - const ancestors1 = getAncestors(node1); - const ancestors2 = new Set(getAncestors(node2)); - for(const ancestor of ancestors1) { - if(ancestors2.has(ancestor)) { - return ancestor; - } - } - - throw new Error(`node1 and node2 do not have a common ancestor`) -} - -/** - * - * @param {Node} node - * @returns {Node[]} - */ -function getAncestors(node) { - const ancestors = []; - let current = node; - - while(current) { - ancestors.push(current); - current = current.parentNode; - } - - return ancestors; -} /** * text position of a node relative to a text nodes within a container @@ -326,6 +292,8 @@ function consolidateMarkers(document){ consolidatedMarkers.push(positionedMarker) } } + + //console.log('consolidatedMarkers', consolidatedMarkers) } } @@ -442,6 +410,8 @@ function isolateMarkerText(document){ } }) + //console.log('markerNodes', [...markerNodes].map(([node, markerType]) => [node.textContent, markerType])) + return markerNodes } diff --git a/tests/fill-odt-template/each.js b/tests/fill-odt-template/each.js index 515e57b..3233b66 100644 --- a/tests/fill-odt-template/each.js +++ b/tests/fill-odt-template/each.js @@ -28,7 +28,7 @@ test('basic template filling with {#each}', async t => { const templateTextContent = await getOdtTextContent(odtTemplate) t.deepEqual(templateTextContent, templateContent, 'reconnaissance du template') - +try{ const odtResult = await fillOdtTemplate(odtTemplate, data) const odtResultTextContent = await getOdtTextContent(odtResult) @@ -38,6 +38,7 @@ Radis Jus d'orange Pâtes à lasagne (fraîches !) `) + }catch(e){console.error(e); throw e} }); @@ -277,7 +278,8 @@ test('template filling with text after {/each} in same text node', async t => { Asperge, Betterave, -Blette, en Printemps +Blette, + en Printemps `) }); @@ -327,22 +329,16 @@ Année Année Énergie par personne - 1970 36252.637 - 1980 43328.78 - 1990 46971.94 - 2000 53147.277 - 2010 48062.32 - 2020 37859.246 `.trim()) diff --git a/tests/fill-odt-template/formatting.js b/tests/fill-odt-template/formatting.js index 4a6aaf1..8f29efc 100644 --- a/tests/fill-odt-template/formatting.js +++ b/tests/fill-odt-template/formatting.js @@ -157,10 +157,33 @@ test('template filling - formatted-start-each-single-paragraph', async t => { const odtResult = await fillOdtTemplate(odtTemplate, data) const odtResultTextContent = await getOdtTextContent(odtResult) - t.deepEqual(odtResultTextContent.trim(), ` + t.deepEqual(odtResultTextContent, ` + 37 38 39 +`) + +}); + + +test('template filling - formatted ghost if', async t => { + const templatePath = join(import.meta.dirname, '../fixtures/reducing.odt') + const templateContent = ` + Utilisation de sources lumineuses : {#if scientifique.source_lumineuses}Oui{:else}Non{/if} +` + + const data = {scientifique: {source_lumineuses: true}} + + const odtTemplate = await getOdtTemplate(templatePath) + + const templateTextContent = await getOdtTextContent(odtTemplate) + t.deepEqual(templateTextContent.trim(), templateContent.trim(), 'reconnaissance du template') + let odtResult = await fillOdtTemplate(odtTemplate, data) + + const odtResultTextContent = await getOdtTextContent(odtResult) + t.deepEqual(odtResultTextContent.trim(), ` + Utilisation de sources lumineuses : Oui `.trim()) }); diff --git a/tests/fill-odt-template/if.js b/tests/fill-odt-template/if.js index c934313..5840bdf 100644 --- a/tests/fill-odt-template/if.js +++ b/tests/fill-odt-template/if.js @@ -6,7 +6,7 @@ import {getOdtTemplate} from '../../scripts/odf/odtTemplate-forNode.js' import {fillOdtTemplate, getOdtTextContent} from '../../exports.js' -test('basic template filling with {#if}', async t => { +test('basic template filling with {#if}{:else} - then branch', async t => { const templatePath = join(import.meta.dirname, '../fixtures/description-nombre.odt') const templateContent = `Description du nombre {n} @@ -28,7 +28,27 @@ n est un grand nombre n est un petit nombre `) + + +}); + + +test('basic template filling with {#if}{:else} - else branch', async t => { + const templatePath = join(import.meta.dirname, '../fixtures/description-nombre.odt') + const templateContent = `Description du nombre {n} + +{#if n<5} +n est un petit nombre +{:else} +n est un grand nombre +{/if} +` + + const odtTemplate = await getOdtTemplate(templatePath) + const templateTextContent = await getOdtTextContent(odtTemplate) + t.deepEqual(templateTextContent, templateContent, 'reconnaissance du template') + try{ // else branch const odtResult8 = await fillOdtTemplate(odtTemplate, {n: 8}) const odtResult8TextContent = await getOdtTextContent(odtResult8) @@ -36,12 +56,14 @@ n est un petit nombre n est un grand nombre `) + } + catch(e){console.error(e); throw e} }); -test('weird bug', async t => { +test('complex structured if', async t => { const templatePath = join(import.meta.dirname, '../fixtures/left-branch-content-and-two-consecutive-ifs.odt') const templateContent = `Utilisation de sources lumineuses : {#if scientifique.source_lumineuses}Oui{:else}Non{/if} {#if scientifique.source_lumineuses && scientifique.modalités_source_lumineuses } diff --git a/tests/fixtures/reducing.odt b/tests/fixtures/reducing.odt new file mode 100644 index 0000000..604ff08 Binary files /dev/null and b/tests/fixtures/reducing.odt differ diff --git a/tools/create-odt-file-from-template.js b/tools/create-odt-file-from-template.js index d850684..1c1a8f3 100644 --- a/tools/create-odt-file-from-template.js +++ b/tools/create-odt-file-from-template.js @@ -5,27 +5,24 @@ import {getOdtTemplate} from '../scripts/odf/odtTemplate-forNode.js' import {fillOdtTemplate} from '../exports.js' /* -const templatePath = join(import.meta.dirname, '../tests/data/template-anniversaire.odt') +const templatePath = join(import.meta.dirname, '../tests/fixtures/template-anniversaire.odt') const data = { nom: 'David Bruant', dateNaissance: '8 mars 1987' } */ - -/* -const templatePath = join(import.meta.dirname, '../tests/data/liste-courses.odt') +/*const templatePath = join(import.meta.dirname, '../tests/fixtures/enum-courses.odt') const data = { listeCourses : [ 'Radis', `Jus d'orange`, 'Pâtes à lasagne (fraîches !)' ] -} -*/ +}*/ /* -const templatePath = join(import.meta.dirname, '../tests/data/liste-fruits-et-légumes.odt') +const templatePath = join(import.meta.dirname, '../tests/fixtures/liste-fruits-et-légumes.odt') const data = { fruits : [ 'Pastèque 🍉', @@ -40,7 +37,7 @@ const data = { }*/ /* -const templatePath = join(import.meta.dirname, '../tests/data/légumes-de-saison.odt') +const templatePath = join(import.meta.dirname, '../tests/fixtures/légumes-de-saison.odt') const data = { légumesSaison : [ { @@ -80,7 +77,7 @@ const data = { */ /* -const templatePath = join(import.meta.dirname, '../tests/data/tableau-simple.odt') +const templatePath = join(import.meta.dirname, '../tests/fixtures/tableau-simple.odt') const data = { annéeConsos : [ { année: 1970, conso: 36252.637}, @@ -95,16 +92,26 @@ const data = { /* -const templatePath = join(import.meta.dirname, '../tests/data/template-avec-image.odt') +const templatePath = join(import.meta.dirname, '../tests/fixtures/template-avec-image.odt') const data = { commentaire : `J'adooooooore 🤩 West covinaaaaaaaaaaa 🎶` } */ - +/* const templatePath = join(import.meta.dirname, '../tests/fixtures/partially-formatted-variable.odt') const data = {nombre : 37} - +*/ + +const templatePath = join(import.meta.dirname, '../tests/fixtures/text-after-closing-each.odt') +const data = { + saison: 'Printemps', + légumes: [ + 'Asperge', + 'Betterave', + 'Blette' + ] +}