First promising result of splitting textNodes to enable {#each}{/each} block within a single text node

This commit is contained in:
David Bruant 2025-04-26 19:41:49 +02:00
parent 9275e5777a
commit 4882c39e32
3 changed files with 86 additions and 21 deletions

View File

@ -217,6 +217,14 @@ function fillEachBlock(startNode, iterableExpression, itemExpression, endNode, d
}
function findEachBlockStartsInString(str){
}
/**
*
* @param {Element | DocumentFragment} rootElement
@ -227,6 +235,68 @@ 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
let afterMatchTextNode
if(match[0].length < remainingText.length){
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{
// skip
}
})
// now, each Node contains at most one block marker
/** @type {Node | undefined} */
let eachBlockStartNode
/** @type {Node | undefined} */
@ -246,17 +316,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 +333,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}`)

View File

@ -36,7 +36,6 @@ Bonjoir ☀️
});
test('basic template filling with {#each}', async t => {
const templatePath = join(import.meta.dirname, './fixtures/enum-courses.odt')
const templateContent = `🧺 La liste de courses incroyable 🧺
@ -73,7 +72,8 @@ 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 🧺
@ -103,8 +103,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 🧺
@ -141,7 +140,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
@ -193,8 +192,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
@ -283,7 +281,7 @@ 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
{#each nombres as n}{n} {/each}
Les nombres : {#each nombres as n}{n} {/each} !!
`
const data = {
@ -300,14 +298,14 @@ test('template filling {#each ...}{/each} within a single text node', async t =>
const odtResultTextContent = await getOdtTextContent(odtResult)
t.deepEqual(odtResultTextContent, `Liste de nombres
1 1 2 3 5 8 13 21
Les nombres : 1 1 2 3 5 8 13 21  !!
`)
});
test('template filling of a table', async t => {
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
@ -368,8 +366,7 @@ Année
});
test('template filling preserves images', async t => {
test.skip('template filling preserves images', async t => {
const templatePath = join(import.meta.dirname, './fixtures/template-avec-image.odt')
const data = {

Binary file not shown.