Better abstraction (#9)
* beginning of refactoring - if tests passing * Beginning of passing tests for each * Les tests each passent * progress * Les tests passent
This commit is contained in:
parent
a1e99b4519
commit
2d559bac5d
@ -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,
|
||||
|
||||
@ -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<typeof TemplateDOMBranch.prototype.getBranchPath>} */
|
||||
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
|
||||
})
|
||||
|
||||
// recursive call to fillTemplatedOdtElement on itemFragment
|
||||
fillOdtElementTemplate(
|
||||
itemFragment,
|
||||
insideCompartment
|
||||
)
|
||||
|
||||
if(!firstItemFirstChild){
|
||||
firstItemFirstChild = itemFragment.firstChild
|
||||
if(!firstItem){
|
||||
currentTemplateBlock.startBranch.removeLeftContent(2)
|
||||
}
|
||||
if(!lastItem){
|
||||
//console.log('[fillEachBlock] removeRightContent')
|
||||
currentTemplateBlock.endBranch.removeRightContent(2)
|
||||
}
|
||||
|
||||
// eventually, will be set to the last item's last child
|
||||
lastItemLastChild = itemFragment.lastChild
|
||||
//console.log('[fillEachBlock] docEl i before removeMarkers', i, docEl.textContent)
|
||||
currentTemplateBlock.removeMarkersAndEmptyAncestors()
|
||||
//console.log('[fillEachBlock] docEl i after removeMarkers', i, docEl.textContent)
|
||||
|
||||
endChild.parentNode.insertBefore(itemFragment, endChild)
|
||||
//console.log('\nrecursive call to fillBlockContentTemplate')
|
||||
currentTemplateBlock.fillBlockContentTemplate(insideCompartment)
|
||||
|
||||
//console.log('[fillEachBlock] docEl i after remove contents', i, docEl.textContent)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
// 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,8 +554,8 @@ 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 = []
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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())
|
||||
|
||||
@ -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())
|
||||
|
||||
});
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -29,6 +29,26 @@ 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 }
|
||||
|
||||
BIN
tests/fixtures/reducing.odt
vendored
Normal file
BIN
tests/fixtures/reducing.odt
vendored
Normal file
Binary file not shown.
@ -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'
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user