if in a single text node works

This commit is contained in:
David Bruant 2025-04-30 17:43:37 +02:00
parent b982700d80
commit ca5aa1c0bf
4 changed files with 104 additions and 48 deletions

View File

@ -293,6 +293,15 @@ function fillEachBlock(startNode, iterableExpression, itemExpression, endNode, c
const IF = 'IF' const IF = 'IF'
const EACH = 'EACH' const EACH = 'EACH'
// the regexps below are shared, so they shoudn't have state (no 'g' flag)
const ifStartRegex = /{#if\s+([^}]+?)\s*}/;
const elseMarker = '{:else}'
const closingIfMarker = '{/if}'
const eachStartMarkerRegex = /{#each\s+([^}]+?)\s+as\s+([^}]+?)\s*}/;
const eachClosingBlockString = '{/each}'
/** /**
* *
@ -334,8 +343,7 @@ function fillTemplatedOdtElement(rootElement, compartment){
/** /**
* looking for {#each x as y} * looking for {#each x as y}
*/ */
const eachStartRegex = /{#each\s+([^}]+?)\s+as\s+([^}]+?)\s*}/; const eachStartMatch = text.match(eachStartMarkerRegex);
const eachStartMatch = text.match(eachStartRegex);
if(eachStartMatch){ if(eachStartMatch){
//console.log('startMatch', startMatch) //console.log('startMatch', startMatch)
@ -358,7 +366,6 @@ function fillTemplatedOdtElement(rootElement, compartment){
/** /**
* Looking for {/each} * Looking for {/each}
*/ */
const eachClosingBlockString = '{/each}'
const isEachClosingBlock = text.includes(eachClosingBlockString) const isEachClosingBlock = text.includes(eachClosingBlockString)
if(isEachClosingBlock){ if(isEachClosingBlock){
@ -394,7 +401,6 @@ function fillTemplatedOdtElement(rootElement, compartment){
/** /**
* Looking for {#if ...} * Looking for {#if ...}
*/ */
const ifStartRegex = /{#if\s+([^}]+?)\s*}/;
const ifStartMatch = text.match(ifStartRegex); const ifStartMatch = text.match(ifStartRegex);
if(ifStartMatch){ if(ifStartMatch){
@ -415,7 +421,6 @@ function fillTemplatedOdtElement(rootElement, compartment){
/** /**
* Looking for {:else} * Looking for {:else}
*/ */
const elseMarker = '{:else}'
const hasElseMarker = text.includes(elseMarker); const hasElseMarker = text.includes(elseMarker);
if(hasElseMarker){ if(hasElseMarker){
@ -438,7 +443,6 @@ function fillTemplatedOdtElement(rootElement, compartment){
/** /**
* Looking for {/if} * Looking for {/if}
*/ */
const closingIfMarker = '{/if}'
const hasClosingMarker = text.includes(closingIfMarker); const hasClosingMarker = text.includes(closingIfMarker);
if(hasClosingMarker){ if(hasClosingMarker){
@ -529,28 +533,42 @@ function fillTemplatedOdtDocument(document, compartment){
let remainingText = currentNode.textContent || '' let remainingText = currentNode.textContent || ''
while(remainingText.length >= 1){ while(remainingText.length >= 1){
let match; let matchText;
let matchIndex;
// looking for opening {#each ...} block // looking for a block marker
const eachBlockOpeningRegex = /{#each\s+([^}]+?)\s+as\s+([^}]+?)\s*}/; for(const marker of [ifStartRegex, elseMarker, closingIfMarker, eachStartMarkerRegex, eachClosingBlockString]){
const eachBlockClosingRegex = /{\/each}/; if(typeof marker === 'string'){
const index = remainingText.indexOf(marker)
for(const regexp of [eachBlockOpeningRegex, eachBlockClosingRegex]){ if(index !== -1){
let thisMatch = remainingText.match(regexp) matchText = marker
matchIndex = index
// trying to find only the first match in remainingText string // found the first match
// @ts-ignore break; // get out of loop
if(thisMatch && (!match || match.index > thisMatch.index)){ }
match = thisMatch }
else{
// marker is a RegExp
const match = remainingText.match(marker)
if(match){
matchText = match[0]
matchIndex = match.index
// found the first match
break; // get out of loop
}
} }
} }
if(match){ if(matchText){
// split 3-way : before-match, match and after-match // split 3-way : before-match, match and after-match
if(match[0].length < remainingText.length){ if(matchText.length < remainingText.length){
// @ts-ignore // @ts-ignore
let afterMatchTextNode = currentNode.splitText(match.index + match[0].length) let afterMatchTextNode = currentNode.splitText(matchIndex + matchText.length)
if(afterMatchTextNode.textContent && afterMatchTextNode.textContent.length >= 1){ if(afterMatchTextNode.textContent && afterMatchTextNode.textContent.length >= 1){
remainingText = afterMatchTextNode.textContent remainingText = afterMatchTextNode.textContent
} }
@ -561,9 +579,9 @@ function fillTemplatedOdtDocument(document, compartment){
// per spec, currentNode now contains before-match and match text // per spec, currentNode now contains before-match and match text
// @ts-ignore // @ts-ignore
if(match.index > 0){ if(matchIndex > 0){
// @ts-ignore // @ts-ignore
currentNode.splitText(match.index) currentNode.splitText(matchIndex)
} }
if(afterMatchTextNode){ if(afterMatchTextNode){

View File

@ -247,33 +247,6 @@ 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 => { test('template filling of a table', async t => {
const templatePath = join(import.meta.dirname, '../fixtures/tableau-simple.odt') const templatePath = join(import.meta.dirname, '../fixtures/tableau-simple.odt')
const templateContent = `Évolution énergie en kWh par personne en France const templateContent = `Évolution énergie en kWh par personne en France

View File

@ -0,0 +1,65 @@
import test from 'ava';
import {join} from 'node:path';
import {getOdtTemplate} from '../../scripts/odf/odtTemplate-forNode.js'
import {fillOdtTemplate, getOdtTextContent} from '../../exports.js'
test('template filling {#if ...}{/if} within a single text node', async t => {
const templatePath = join(import.meta.dirname, '../fixtures/inline-if-nombres.odt')
const templateContent = `Taille de nombre
Le nombre {n} est {#if n<5}petit{:else}grand{/if}.
`
const odtTemplate = await getOdtTemplate(templatePath)
const templateTextContent = await getOdtTextContent(odtTemplate)
t.deepEqual(templateTextContent, templateContent, 'reconnaissance du template')
const odtResult3 = await fillOdtTemplate(odtTemplate, {n : 3})
const odtResult3TextContent = await getOdtTextContent(odtResult3)
t.deepEqual(odtResult3TextContent, `Taille de nombre
Le nombre 3 est petit.
`)
const odtResult9 = await fillOdtTemplate(odtTemplate, {n : 9})
const odtResult9TextContent = await getOdtTextContent(odtResult9)
t.deepEqual(odtResult9TextContent, `Taille de nombre
Le nombre 9 est grand.
`)
});
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  !!
`)
});

BIN
tests/fixtures/inline-if-nombres.odt vendored Normal file

Binary file not shown.