after-text in #each block (#7)
* Text 'text after {/each} in same text node' failing
* some tests passing
* tests passing
* unused var
* styling
This commit is contained in:
parent
6ad0bab069
commit
5400c963a7
@ -85,6 +85,8 @@ function findPlacesToFillInString(str, compartment) {
|
|||||||
* @returns {{startChild: Node, endChild:Node, content: DocumentFragment}}
|
* @returns {{startChild: Node, endChild:Node, content: DocumentFragment}}
|
||||||
*/
|
*/
|
||||||
function extractBlockContent(blockStartNode, blockEndNode) {
|
function extractBlockContent(blockStartNode, blockEndNode) {
|
||||||
|
//console.log('[extractBlockContent] blockEndNode', blockEndNode.textContent)
|
||||||
|
|
||||||
// find common ancestor of blockStartNode and blockEndNode
|
// find common ancestor of blockStartNode and blockEndNode
|
||||||
let commonAncestor
|
let commonAncestor
|
||||||
|
|
||||||
@ -118,7 +120,10 @@ function extractBlockContent(blockStartNode, blockEndNode) {
|
|||||||
const startChild = startAncestryToCommonAncestor.at(-1)
|
const startChild = startAncestryToCommonAncestor.at(-1)
|
||||||
const endChild = endAncestryToCommonAncestor.at(-1)
|
const endChild = endAncestryToCommonAncestor.at(-1)
|
||||||
|
|
||||||
|
//console.log('[extractBlockContent] endChild', endChild.textContent)
|
||||||
|
|
||||||
// Extract DOM content in a documentFragment
|
// Extract DOM content in a documentFragment
|
||||||
|
/** @type {DocumentFragment} */
|
||||||
const contentFragment = blockStartNode.ownerDocument.createDocumentFragment()
|
const contentFragment = blockStartNode.ownerDocument.createDocumentFragment()
|
||||||
|
|
||||||
/** @type {Element[]} */
|
/** @type {Element[]} */
|
||||||
@ -135,6 +140,8 @@ function extractBlockContent(blockStartNode, blockEndNode) {
|
|||||||
contentFragment.appendChild(sibling)
|
contentFragment.appendChild(sibling)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//console.log('extractBlockContent contentFragment', contentFragment.textContent)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
startChild,
|
startChild,
|
||||||
endChild,
|
endChild,
|
||||||
@ -231,8 +238,6 @@ function fillIfBlock(ifOpeningMarkerNode, ifElseMarkerNode, ifClosingMarkerNode,
|
|||||||
*/
|
*/
|
||||||
function fillEachBlock(startNode, iterableExpression, itemExpression, endNode, compartment) {
|
function fillEachBlock(startNode, iterableExpression, itemExpression, endNode, compartment) {
|
||||||
//console.log('fillEachBlock', iterableExpression, itemExpression)
|
//console.log('fillEachBlock', iterableExpression, itemExpression)
|
||||||
//console.log('startNode', startNode.nodeType, startNode.nodeName)
|
|
||||||
//console.log('endNode', endNode.nodeType, endNode.nodeName)
|
|
||||||
|
|
||||||
const {startChild, endChild, content: repeatedFragment} = extractBlockContent(startNode, endNode)
|
const {startChild, endChild, content: repeatedFragment} = extractBlockContent(startNode, endNode)
|
||||||
|
|
||||||
@ -244,9 +249,13 @@ function fillEachBlock(startNode, iterableExpression, itemExpression, endNode, c
|
|||||||
iterable = []
|
iterable = []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let firstItemFirstChild
|
||||||
|
let lastItemLastChild
|
||||||
|
|
||||||
// create each loop result
|
// create each loop result
|
||||||
// using a for-of loop to accept all iterable values
|
// using a for-of loop to accept all iterable values
|
||||||
for(const item of iterable) {
|
for(const item of iterable) {
|
||||||
|
|
||||||
/** @type {DocumentFragment} */
|
/** @type {DocumentFragment} */
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const itemFragment = repeatedFragment.cloneNode(true)
|
const itemFragment = repeatedFragment.cloneNode(true)
|
||||||
@ -262,17 +271,67 @@ function fillEachBlock(startNode, iterableExpression, itemExpression, endNode, c
|
|||||||
insideCompartment
|
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)
|
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
|
// remove block marker elements
|
||||||
startChild.parentNode.removeChild(startChild)
|
startChild.parentNode.removeChild(startChild)
|
||||||
endChild.parentNode.removeChild(endChild)
|
endChild.parentNode.removeChild(endChild)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const IF = 'IF'
|
const IF = ifStartMarkerRegex.source
|
||||||
const EACH = 'EACH'
|
const EACH = eachStartMarkerRegex.source
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
//@ts-check
|
||||||
|
|
||||||
import {traverse, Node} from "../../DOMUtils.js";
|
import {traverse, Node} from "../../DOMUtils.js";
|
||||||
import {closingIfMarker, eachClosingMarker, eachStartMarkerRegex, elseMarker, ifStartMarkerRegex, variableRegex} from './markers.js'
|
import {closingIfMarker, eachClosingMarker, eachStartMarkerRegex, elseMarker, ifStartMarkerRegex, variableRegex} from './markers.js'
|
||||||
|
|
||||||
@ -39,7 +41,7 @@ function findAllMatches(text, pattern) {
|
|||||||
*
|
*
|
||||||
* @param {Node} node1
|
* @param {Node} node1
|
||||||
* @param {Node} node2
|
* @param {Node} node2
|
||||||
* @returns {Node | undefined}
|
* @returns {Node}
|
||||||
*/
|
*/
|
||||||
function findCommonAncestor(node1, node2) {
|
function findCommonAncestor(node1, node2) {
|
||||||
const ancestors1 = getAncestors(node1);
|
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)
|
newStartNode.parentNode?.removeChild(newStartNode)
|
||||||
|
|
||||||
commonAncestor.insertBefore(newStartNode, commonAncestorStartChild.nextSibling)
|
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)
|
endNode.parentNode?.removeChild(endNode)
|
||||||
commonAncestor.insertBefore(endNode, commonAncestorEndChild)
|
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
|
// then, replace all nodes between (new)startNode and (new)endNode with a single textNode in commonAncestor
|
||||||
replaceBetweenNodesWithText(newStartNode, endNode, positionedMarker.marker)
|
replaceBetweenNodesWithText(newStartNode, endNode, positionedMarker.marker)
|
||||||
|
|
||||||
|
//console.log('commonAncestor after replaceBetweenNodesWithText', commonAncestor.textContent )
|
||||||
|
|
||||||
// After consolidation, break as the DOM structure has changed
|
// After consolidation, break as the DOM structure has changed
|
||||||
// and containerTextNodesInTreeOrder needs to be refreshed
|
// and containerTextNodesInTreeOrder needs to be refreshed
|
||||||
consolidatedMarkers.push(positionedMarker)
|
consolidatedMarkers.push(positionedMarker)
|
||||||
@ -316,13 +324,29 @@ 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
|
* isolate markers which are in Text nodes with other texts
|
||||||
*
|
*
|
||||||
* @param {Document} document
|
* @param {Document} document
|
||||||
|
* @returns {Map<Node, MarkerType>}
|
||||||
*/
|
*/
|
||||||
function isolateMarkers(document){
|
function isolateMarkerText(document){
|
||||||
|
/** @type {ReturnType<isolateMarkerText>} */
|
||||||
|
const markerNodes = new Map()
|
||||||
|
|
||||||
traverse(document, currentNode => {
|
traverse(document, currentNode => {
|
||||||
|
//console.log('isolateMarkers', currentNode.nodeName, currentNode.textContent)
|
||||||
|
|
||||||
if(currentNode.nodeType === Node.TEXT_NODE) {
|
if(currentNode.nodeType === Node.TEXT_NODE) {
|
||||||
// find all marker starts and ends and split textNode
|
// find all marker starts and ends and split textNode
|
||||||
let remainingText = currentNode.textContent || ''
|
let remainingText = currentNode.textContent || ''
|
||||||
@ -330,6 +354,8 @@ function isolateMarkers(document){
|
|||||||
while(remainingText.length >= 1) {
|
while(remainingText.length >= 1) {
|
||||||
let matchText;
|
let matchText;
|
||||||
let matchIndex;
|
let matchIndex;
|
||||||
|
/** @type {MarkerType} */
|
||||||
|
let markerType;
|
||||||
|
|
||||||
// looking for a block marker
|
// looking for a block marker
|
||||||
for(const marker of [ifStartMarkerRegex, elseMarker, closingIfMarker, eachStartMarkerRegex, eachClosingMarker]) {
|
for(const marker of [ifStartMarkerRegex, elseMarker, closingIfMarker, eachStartMarkerRegex, eachClosingMarker]) {
|
||||||
@ -339,6 +365,7 @@ function isolateMarkers(document){
|
|||||||
if(index !== -1) {
|
if(index !== -1) {
|
||||||
matchText = marker
|
matchText = marker
|
||||||
matchIndex = index
|
matchIndex = index
|
||||||
|
markerType = marker
|
||||||
|
|
||||||
// found the first match
|
// found the first match
|
||||||
break; // get out of loop
|
break; // get out of loop
|
||||||
@ -351,6 +378,7 @@ function isolateMarkers(document){
|
|||||||
if(match) {
|
if(match) {
|
||||||
matchText = match[0]
|
matchText = match[0]
|
||||||
matchIndex = match.index
|
matchIndex = match.index
|
||||||
|
markerType = marker.source
|
||||||
|
|
||||||
// found the first match
|
// found the first match
|
||||||
break; // get out of loop
|
break; // get out of loop
|
||||||
@ -373,11 +401,21 @@ function isolateMarkers(document){
|
|||||||
|
|
||||||
// per spec, currentNode now contains before-match and match text
|
// per spec, currentNode now contains before-match and match text
|
||||||
|
|
||||||
|
/** @type {Node} */
|
||||||
|
let matchTextNode
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if(matchIndex > 0) {
|
if(matchIndex > 0) {
|
||||||
// @ts-ignore
|
// @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) {
|
if(afterMatchTextNode) {
|
||||||
currentNode = afterMatchTextNode
|
currentNode = afterMatchTextNode
|
||||||
@ -397,8 +435,100 @@ function isolateMarkers(document){
|
|||||||
// skip
|
// 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<Node, MarkerType>} 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
|
* 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:
|
* Specifically, after the call to this function, the document is altered to respect the following property:
|
||||||
@ -415,5 +545,11 @@ function isolateMarkers(document){
|
|||||||
*/
|
*/
|
||||||
export default function prepareTemplateDOMTree(document){
|
export default function prepareTemplateDOMTree(document){
|
||||||
consolidateMarkers(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)
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -247,6 +247,42 @@ Hiver
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
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('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
|
||||||
|
|||||||
BIN
tests/fixtures/text-after-closing-each.odt
vendored
Normal file
BIN
tests/fixtures/text-after-closing-each.odt
vendored
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user