Add images (#16)
* add template * Rename template * Add test for insert 2 images * image marker regex * Ajout d'un test pour vérifier que le texte du template est bon * WORK IN PROGRESS - trouver et évaluer la balise image * Create OfjsImage type * create addImageToOdtFile * Regenerate yo odt to inspect it * Add a draw image and a draw frame into odt file * Test if there are two draw:image in the generated document * Add pictures in manifest.xml to fix corrupted file * Adapt anchor type * Fix images aspect with ratio
This commit is contained in:
parent
a4d273793e
commit
d934d0dfb0
18
package-lock.json
generated
18
package-lock.json
generated
@ -10,6 +10,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@xmldom/xmldom": "^0.9.8",
|
"@xmldom/xmldom": "^0.9.8",
|
||||||
"@zip.js/zip.js": "^2.7.57",
|
"@zip.js/zip.js": "^2.7.57",
|
||||||
|
"image-size": "^2.0.2",
|
||||||
"ses": "^1.12.0"
|
"ses": "^1.12.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -2185,6 +2186,18 @@
|
|||||||
"node": ">=10 <11 || >=12 <13 || >=14"
|
"node": ">=10 <11 || >=12 <13 || >=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/image-size": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/image-size/-/image-size-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"image-size": "bin/image-size.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/immutable": {
|
"node_modules/immutable": {
|
||||||
"version": "4.2.4",
|
"version": "4.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.2.4.tgz",
|
||||||
@ -6129,6 +6142,11 @@
|
|||||||
"integrity": "sha512-yiWd4GVmJp0Q6ghmM2B/V3oZGRmjrKLXvHR3TE1nfoXsmoggllfZUQe74EN0fJdPFZu2NIvNdrMMLm3OsV7Ohw==",
|
"integrity": "sha512-yiWd4GVmJp0Q6ghmM2B/V3oZGRmjrKLXvHR3TE1nfoXsmoggllfZUQe74EN0fJdPFZu2NIvNdrMMLm3OsV7Ohw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"image-size": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/image-size/-/image-size-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w=="
|
||||||
|
},
|
||||||
"immutable": {
|
"immutable": {
|
||||||
"version": "4.2.4",
|
"version": "4.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.2.4.tgz",
|
||||||
|
|||||||
@ -41,6 +41,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@xmldom/xmldom": "^0.9.8",
|
"@xmldom/xmldom": "^0.9.8",
|
||||||
"@zip.js/zip.js": "^2.7.57",
|
"@zip.js/zip.js": "^2.7.57",
|
||||||
|
"image-size": "^2.0.2",
|
||||||
"ses": "^1.12.0"
|
"ses": "^1.12.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import {parseXML, Node} from '../../DOMUtils.js'
|
|||||||
* @param {ODTFile} odtFile
|
* @param {ODTFile} odtFile
|
||||||
* @returns {Promise<Document>}
|
* @returns {Promise<Document>}
|
||||||
*/
|
*/
|
||||||
async function getContentDocument(odtFile) {
|
export async function getContentDocument(odtFile) {
|
||||||
const reader = new ZipReader(new Uint8ArrayReader(new Uint8Array(odtFile)));
|
const reader = new ZipReader(new Uint8ArrayReader(new Uint8Array(odtFile)));
|
||||||
|
|
||||||
const entries = await reader.getEntries();
|
const entries = await reader.getEntries();
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
import {traverse, Node, getAncestors, findCommonAncestor} from "../../DOMUtils.js";
|
import {traverse, Node, getAncestors, findCommonAncestor} from "../../DOMUtils.js";
|
||||||
import {closingIfMarker, eachClosingMarker, eachStartMarkerRegex, elseMarker, ifStartMarkerRegex, variableRegex} from './markers.js'
|
import {closingIfMarker, eachClosingMarker, eachStartMarkerRegex, elseMarker, ifStartMarkerRegex, imageMarkerRegex, variableRegex} from './markers.js'
|
||||||
|
import {isOdfjsImage} from "../../shared.js"
|
||||||
|
import imageSize from "image-size";
|
||||||
|
/** @import {OdfjsImage} from "../../types.js" */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef TextPlaceToFill
|
* @typedef TextPlaceToFill
|
||||||
@ -182,12 +185,18 @@ class TemplateBlock{
|
|||||||
/** @type {Node[]} */
|
/** @type {Node[]} */
|
||||||
#middleContent;
|
#middleContent;
|
||||||
|
|
||||||
|
/**@type {any} */
|
||||||
|
#addImageToOdtFile;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {Node} startNode
|
* @param {Node} startNode
|
||||||
* @param {Node} endNode
|
* @param {Node} endNode
|
||||||
|
* @param {(OdfjsImage) => string} addImageToOdtFile
|
||||||
*/
|
*/
|
||||||
constructor(startNode, endNode){
|
constructor(startNode, endNode, addImageToOdtFile){
|
||||||
|
this.#addImageToOdtFile = addImageToOdtFile
|
||||||
|
|
||||||
// @ts-expect-error xmldom.Node
|
// @ts-expect-error xmldom.Node
|
||||||
this.#commonAncestor = findCommonAncestor(startNode, endNode)
|
this.#commonAncestor = findCommonAncestor(startNode, endNode)
|
||||||
|
|
||||||
@ -231,7 +240,7 @@ class TemplateBlock{
|
|||||||
const startChild = this.startBranch.at(1)
|
const startChild = this.startBranch.at(1)
|
||||||
if(startChild /*&& startChild !== */){
|
if(startChild /*&& startChild !== */){
|
||||||
//console.log('[fillBlockContentTemplate] startChild', startChild.nodeName, startChild.textContent)
|
//console.log('[fillBlockContentTemplate] startChild', startChild.nodeName, startChild.textContent)
|
||||||
fillOdtElementTemplate(startChild, compartement)
|
fillOdtElementTemplate(startChild, compartement, this.#addImageToOdtFile)
|
||||||
}
|
}
|
||||||
//console.log('[fillBlockContentTemplate] after startChild')
|
//console.log('[fillBlockContentTemplate] after startChild')
|
||||||
|
|
||||||
@ -239,7 +248,7 @@ class TemplateBlock{
|
|||||||
// if content consists of several parts of an {#each}{/each}
|
// if content consists of several parts of an {#each}{/each}
|
||||||
// when arriving to the {/each}, it will be alone (and imbalanced)
|
// when arriving to the {/each}, it will be alone (and imbalanced)
|
||||||
// and will trigger an error
|
// and will trigger an error
|
||||||
fillOdtElementTemplate(Array.from(this.#middleContent), compartement)
|
fillOdtElementTemplate(Array.from(this.#middleContent), compartement, this.#addImageToOdtFile)
|
||||||
|
|
||||||
//console.log('[fillBlockContentTemplate] after middleContent')
|
//console.log('[fillBlockContentTemplate] after middleContent')
|
||||||
|
|
||||||
@ -249,7 +258,7 @@ class TemplateBlock{
|
|||||||
|
|
||||||
if(endChild){
|
if(endChild){
|
||||||
//console.log('[fillBlockContentTemplate] endChild', endChild.nodeName, endChild.textContent)
|
//console.log('[fillBlockContentTemplate] endChild', endChild.nodeName, endChild.textContent)
|
||||||
fillOdtElementTemplate(endChild, compartement)
|
fillOdtElementTemplate(endChild, compartement, this.#addImageToOdtFile)
|
||||||
}
|
}
|
||||||
//console.log('[fillBlockContentTemplate] after endChild')
|
//console.log('[fillBlockContentTemplate] after endChild')
|
||||||
|
|
||||||
@ -347,7 +356,7 @@ class TemplateBlock{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new TemplateBlock(startLeafCloneNode, endLeafCloneNode)
|
return new TemplateBlock(startLeafCloneNode, endLeafCloneNode, this.#addImageToOdtFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -426,8 +435,9 @@ function findPlacesToFillInString(str, compartment) {
|
|||||||
* @param {Node} ifClosingMarkerNode
|
* @param {Node} ifClosingMarkerNode
|
||||||
* @param {string} ifBlockConditionExpression
|
* @param {string} ifBlockConditionExpression
|
||||||
* @param {Compartment} compartment
|
* @param {Compartment} compartment
|
||||||
|
* // TODO type,addImageToOdtFile
|
||||||
*/
|
*/
|
||||||
function fillIfBlock(ifOpeningMarkerNode, ifElseMarkerNode, ifClosingMarkerNode, ifBlockConditionExpression, compartment) {
|
function fillIfBlock(ifOpeningMarkerNode, ifElseMarkerNode, ifClosingMarkerNode, ifBlockConditionExpression, compartment, addImageToOdtFile) {
|
||||||
//const docEl = ifOpeningMarkerNode.ownerDocument.documentElement
|
//const docEl = ifOpeningMarkerNode.ownerDocument.documentElement
|
||||||
|
|
||||||
const conditionValue = compartment.evaluate(ifBlockConditionExpression)
|
const conditionValue = compartment.evaluate(ifBlockConditionExpression)
|
||||||
@ -443,11 +453,11 @@ function fillIfBlock(ifOpeningMarkerNode, ifElseMarkerNode, ifClosingMarkerNode,
|
|||||||
ifElseMarkerNode.childNodes.length, ifElseMarkerNode.textContent
|
ifElseMarkerNode.childNodes.length, ifElseMarkerNode.textContent
|
||||||
)*/
|
)*/
|
||||||
|
|
||||||
thenTemplateBlock = new TemplateBlock(ifOpeningMarkerNode, ifElseMarkerNode)
|
thenTemplateBlock = new TemplateBlock(ifOpeningMarkerNode, ifElseMarkerNode, addImageToOdtFile)
|
||||||
elseTemplateBlock = new TemplateBlock(ifElseMarkerNode, ifClosingMarkerNode)
|
elseTemplateBlock = new TemplateBlock(ifElseMarkerNode, ifClosingMarkerNode, addImageToOdtFile)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
thenTemplateBlock = new TemplateBlock(ifOpeningMarkerNode, ifClosingMarkerNode)
|
thenTemplateBlock = new TemplateBlock(ifOpeningMarkerNode, ifClosingMarkerNode, addImageToOdtFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
if(conditionValue) {
|
if(conditionValue) {
|
||||||
@ -486,14 +496,15 @@ function fillIfBlock(ifOpeningMarkerNode, ifElseMarkerNode, ifClosingMarkerNode,
|
|||||||
* @param {string} itemExpression
|
* @param {string} itemExpression
|
||||||
* @param {Node} endNode
|
* @param {Node} endNode
|
||||||
* @param {Compartment} compartment
|
* @param {Compartment} compartment
|
||||||
|
* // TODO type addImageToOdtFile
|
||||||
*/
|
*/
|
||||||
function fillEachBlock(startNode, iterableExpression, itemExpression, endNode, compartment) {
|
function fillEachBlock(startNode, iterableExpression, itemExpression, endNode, compartment, addImageToOdtFile) {
|
||||||
//console.log('fillEachBlock', iterableExpression, itemExpression)
|
//console.log('fillEachBlock', iterableExpression, itemExpression)
|
||||||
|
|
||||||
const docEl = startNode.ownerDocument.documentElement
|
const docEl = startNode.ownerDocument.documentElement
|
||||||
//console.log('[fillEachBlock] docEl', docEl.textContent)
|
//console.log('[fillEachBlock] docEl', docEl.textContent)
|
||||||
|
|
||||||
const repeatedTemplateBlock = new TemplateBlock(startNode, endNode)
|
const repeatedTemplateBlock = new TemplateBlock(startNode, endNode, addImageToOdtFile)
|
||||||
|
|
||||||
// Find the iterable in the data
|
// Find the iterable in the data
|
||||||
let iterable = compartment.evaluate(iterableExpression)
|
let iterable = compartment.evaluate(iterableExpression)
|
||||||
@ -554,6 +565,29 @@ function fillEachBlock(startNode, iterableExpression, itemExpression, endNode, c
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} str
|
||||||
|
* @param {Compartement} compartment
|
||||||
|
* @returns { {expression: string, odfjsImage: OdfjsImage | undefined} | undefined}
|
||||||
|
*/
|
||||||
|
function findImageMarker(str, compartment) {
|
||||||
|
const imageRexExp = new RegExp(imageMarkerRegex.source, 'g');
|
||||||
|
const match = imageRexExp.exec(str)
|
||||||
|
|
||||||
|
if (match===null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const expression = match[1]
|
||||||
|
const value = compartment.evaluate(expression)
|
||||||
|
|
||||||
|
if (isOdfjsImage(value)) {
|
||||||
|
return { expression, odfjsImage: value}
|
||||||
|
} else {
|
||||||
|
return { expression }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const IF = ifStartMarkerRegex.source
|
const IF = ifStartMarkerRegex.source
|
||||||
const EACH = eachStartMarkerRegex.source
|
const EACH = eachStartMarkerRegex.source
|
||||||
|
|
||||||
@ -564,9 +598,10 @@ const EACH = eachStartMarkerRegex.source
|
|||||||
*
|
*
|
||||||
* @param {RootElementArgument | RootElementArgument[]} rootElements
|
* @param {RootElementArgument | RootElementArgument[]} rootElements
|
||||||
* @param {Compartment} compartment
|
* @param {Compartment} compartment
|
||||||
|
* @param {(OdfjsImage) => string} addImageToOdtFile
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
export default function fillOdtElementTemplate(rootElements, compartment) {
|
export default function fillOdtElementTemplate(rootElements, compartment, addImageToOdtFile) {
|
||||||
|
|
||||||
if(!Array.isArray(rootElements)){
|
if(!Array.isArray(rootElements)){
|
||||||
rootElements = [rootElements]
|
rootElements = [rootElements]
|
||||||
@ -653,7 +688,7 @@ export default function fillOdtElementTemplate(rootElements, compartment) {
|
|||||||
// execute replacement loop
|
// execute replacement loop
|
||||||
//console.log('start of fillEachBlock')
|
//console.log('start of fillEachBlock')
|
||||||
|
|
||||||
fillEachBlock(eachOpeningMarkerNode, eachBlockIterableExpression, eachBlockItemExpression, eachClosingMarkerNode, compartment)
|
fillEachBlock(eachOpeningMarkerNode, eachBlockIterableExpression, eachBlockItemExpression, eachClosingMarkerNode, compartment, addImageToOdtFile)
|
||||||
|
|
||||||
//console.log('end of fillEachBlock')
|
//console.log('end of fillEachBlock')
|
||||||
|
|
||||||
@ -728,7 +763,7 @@ export default function fillOdtElementTemplate(rootElements, compartment) {
|
|||||||
|
|
||||||
// found an {#if} and its corresponding {/if}
|
// found an {#if} and its corresponding {/if}
|
||||||
// execute replacement loop
|
// execute replacement loop
|
||||||
fillIfBlock(ifOpeningMarkerNode, ifElseMarkerNode, ifClosingMarkerNode, ifBlockConditionExpression, compartment)
|
fillIfBlock(ifOpeningMarkerNode, ifElseMarkerNode, ifClosingMarkerNode, ifBlockConditionExpression, compartment, addImageToOdtFile)
|
||||||
|
|
||||||
ifOpeningMarkerNode = undefined
|
ifOpeningMarkerNode = undefined
|
||||||
ifElseMarkerNode = undefined
|
ifElseMarkerNode = undefined
|
||||||
@ -761,6 +796,54 @@ export default function fillOdtElementTemplate(rootElements, compartment) {
|
|||||||
const newTextNode = currentNode.ownerDocument?.createTextNode(newText)
|
const newTextNode = currentNode.ownerDocument?.createTextNode(newText)
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
currentNode.parentNode?.replaceChild(newTextNode, currentNode)
|
currentNode.parentNode?.replaceChild(newTextNode, currentNode)
|
||||||
|
} else {
|
||||||
|
const imageMarker = findImageMarker(currentNode.data, compartment)
|
||||||
|
if (imageMarker){
|
||||||
|
console.log({imageMarker}, "dans le if imageMarker")
|
||||||
|
if (imageMarker.odfjsImage) {
|
||||||
|
const href = addImageToOdtFile(imageMarker.odfjsImage)
|
||||||
|
|
||||||
|
const newImageNode = currentNode.ownerDocument?.createElement("draw:image")
|
||||||
|
newImageNode.setAttribute("xlink:href", href)
|
||||||
|
newImageNode.setAttribute("xlink:type", "simple")
|
||||||
|
newImageNode.setAttribute("xlink:show", "embed")
|
||||||
|
newImageNode.setAttribute("xlink:actuate", "onLoad")
|
||||||
|
newImageNode.setAttribute("draw:mime-type", imageMarker.odfjsImage.mediaType)
|
||||||
|
|
||||||
|
const newFrameNode = currentNode.ownerDocument?.createElement('draw:frame')
|
||||||
|
newFrameNode.setAttribute("text:anchor-type", "as-char")
|
||||||
|
const buffer = new Uint8Array(imageMarker.odfjsImage.content)
|
||||||
|
|
||||||
|
const dimensions = imageSize(buffer)
|
||||||
|
|
||||||
|
const MAX_WIDTH = 10 // cm
|
||||||
|
const MAX_HEIGHT = 10 // cm
|
||||||
|
|
||||||
|
let width;
|
||||||
|
let height;
|
||||||
|
|
||||||
|
if(dimensions.width > dimensions.height){
|
||||||
|
// image in landscape
|
||||||
|
width = MAX_WIDTH;
|
||||||
|
height = width*dimensions.height/dimensions.width
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
// image in portrait
|
||||||
|
height = MAX_HEIGHT;
|
||||||
|
width = height*dimensions.width/dimensions.height
|
||||||
|
}
|
||||||
|
|
||||||
|
newFrameNode.setAttribute("svg:width", `${width}cm`)
|
||||||
|
newFrameNode.setAttribute("svg:height", `${height}cm`)
|
||||||
|
newFrameNode.appendChild(newImageNode)
|
||||||
|
|
||||||
|
currentNode.parentNode?.replaceChild(newFrameNode, currentNode)
|
||||||
|
} else {
|
||||||
|
throw new Error(`No valid OdfjsImage value has been found for expression: ${imageMarker.expression}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,8 @@ lockdown();
|
|||||||
|
|
||||||
|
|
||||||
/** @import {Reader, ZipWriterAddDataOptions} from '@zip.js/zip.js' */
|
/** @import {Reader, ZipWriterAddDataOptions} from '@zip.js/zip.js' */
|
||||||
/** @import {ODFManifest} from '../manifest.js' */
|
/** @import {ODFManifest, ODFManifestFileEntry} from '../manifest.js' */
|
||||||
|
/** @import {OdfjsImage} from '../../types.js' */
|
||||||
|
|
||||||
/** @typedef {ArrayBuffer} ODTFile */
|
/** @typedef {ArrayBuffer} ODTFile */
|
||||||
|
|
||||||
@ -23,11 +24,12 @@ const ODTMimetype = 'application/vnd.oasis.opendocument.text'
|
|||||||
*
|
*
|
||||||
* @param {Document} document
|
* @param {Document} document
|
||||||
* @param {Compartment} compartment
|
* @param {Compartment} compartment
|
||||||
|
* @param {(OdfjsImage) => string} addImageToOdtFile
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
function fillOdtDocumentTemplate(document, compartment) {
|
function fillOdtDocumentTemplate(document, compartment, addImageToOdtFile) {
|
||||||
prepareTemplateDOMTree(document)
|
prepareTemplateDOMTree(document)
|
||||||
fillOdtElementTemplate(document, compartment)
|
fillOdtElementTemplate(document, compartment, addImageToOdtFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -64,6 +66,21 @@ export default async function fillOdtTemplate(odtTemplate, data) {
|
|||||||
|
|
||||||
/** @type {{filename: string, content: Reader, options?: ZipWriterAddDataOptions}[]} */
|
/** @type {{filename: string, content: Reader, options?: ZipWriterAddDataOptions}[]} */
|
||||||
const zipEntriesToAdd = []
|
const zipEntriesToAdd = []
|
||||||
|
/** @type {ODFManifestFileEntry[]} */
|
||||||
|
const newManifestEntries = []
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return href
|
||||||
|
* @param {OdfjsImage} odfjsImage
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function addImageToOdtFile(odfjsImage) {
|
||||||
|
// console.log({odfjsImage})
|
||||||
|
const filename = `Pictures/${odfjsImage.fileName}`
|
||||||
|
zipEntriesToAdd.push({content: new Uint8ArrayReader(new Uint8Array(odfjsImage.content)), filename})
|
||||||
|
newManifestEntries.push({fullPath: filename, mediaType: odfjsImage.mediaType})
|
||||||
|
return filename
|
||||||
|
}
|
||||||
|
|
||||||
// Parcourir chaque entrée du fichier ODT
|
// Parcourir chaque entrée du fichier ODT
|
||||||
for await(const entry of entries) {
|
for await(const entry of entries) {
|
||||||
@ -96,13 +113,15 @@ export default async function fillOdtTemplate(odtTemplate, data) {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const contentXml = await entry.getData(new TextWriter());
|
const contentXml = await entry.getData(new TextWriter());
|
||||||
const contentDocument = parseXML(contentXml);
|
const contentDocument = parseXML(contentXml);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const compartment = new Compartment({
|
const compartment = new Compartment({
|
||||||
globals: data,
|
globals: data,
|
||||||
__options__: true
|
__options__: true
|
||||||
})
|
})
|
||||||
|
|
||||||
fillOdtDocumentTemplate(contentDocument, compartment)
|
fillOdtDocumentTemplate(contentDocument, compartment, addImageToOdtFile)
|
||||||
|
|
||||||
const updatedContentXml = serializeToString(contentDocument)
|
const updatedContentXml = serializeToString(contentDocument)
|
||||||
|
|
||||||
@ -138,6 +157,9 @@ export default async function fillOdtTemplate(odtTemplate, data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for(const {fullPath, mediaType} of newManifestEntries){
|
||||||
|
manifestFileData.fileEntries.set(fullPath, {fullPath, mediaType})
|
||||||
|
}
|
||||||
|
|
||||||
for(const {filename, content, options} of zipEntriesToAdd) {
|
for(const {filename, content, options} of zipEntriesToAdd) {
|
||||||
await writer.add(filename, content, options);
|
await writer.add(filename, content, options);
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
// the regexps below are shared, so they shoudn't have state (no 'g' flag)
|
// the regexps below are shared, so they shoudn't have state (no 'g' flag)
|
||||||
export const variableRegex = /\{([^{#\/:]+?)\}/
|
export const variableRegex = /\{([^{#\/:]+?)\}/
|
||||||
|
export const imageMarkerRegex = /{#image\s+([^}]+?)\s*}/;
|
||||||
|
|
||||||
export const ifStartMarkerRegex = /{#if\s+([^}]+?)\s*}/;
|
export const ifStartMarkerRegex = /{#if\s+([^}]+?)\s*}/;
|
||||||
export const elseMarker = '{:else}'
|
export const elseMarker = '{:else}'
|
||||||
export const closingIfMarker = '{/if}'
|
export const closingIfMarker = '{/if}'
|
||||||
|
|
||||||
export const eachStartMarkerRegex = /{#each\s+([^}]+?)\s+as\s+([^}]+?)\s*}/;
|
export const eachStartMarkerRegex = /{#each\s+([^}]+?)\s+as\s+([^}]+?)\s*}/;
|
||||||
export const eachClosingMarker = '{/each}'
|
export const eachClosingMarker = '{/each}'
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { Uint8ArrayReader, ZipReader, TextWriter } from '@zip.js/zip.js';
|
|||||||
import {parseXML} from './DOMUtils.js'
|
import {parseXML} from './DOMUtils.js'
|
||||||
|
|
||||||
/** @import {Entry} from '@zip.js/zip.js'*/
|
/** @import {Entry} from '@zip.js/zip.js'*/
|
||||||
/** @import {SheetName, SheetRawContent, SheetRowRawContent, SheetCellRawContent} from './types.js' */
|
/** @import {SheetName, SheetRawContent, SheetRowRawContent, SheetCellRawContent, OdfjsImage} from './types.js' */
|
||||||
|
|
||||||
|
|
||||||
// https://dom.spec.whatwg.org/#interface-node
|
// https://dom.spec.whatwg.org/#interface-node
|
||||||
@ -160,6 +160,22 @@ export function convertCellValue({value, type}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {unknown} value
|
||||||
|
* @returns {value is OdfjsImage}
|
||||||
|
*/
|
||||||
|
export function isOdfjsImage(value) {
|
||||||
|
if (typeof value === 'object' && value!==null
|
||||||
|
&& "content" in value && value.content instanceof ArrayBuffer
|
||||||
|
&& "fileName" in value && typeof value.fileName === 'string'
|
||||||
|
&& "mediaType" in value && typeof value.mediaType === 'string'
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -10,4 +10,12 @@
|
|||||||
|
|
||||||
/** @typedef {string} SheetName */
|
/** @typedef {string} SheetName */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef OdfjsImage
|
||||||
|
* @prop {ArrayBuffer} content
|
||||||
|
* @prop {string} fileName
|
||||||
|
* @prop {string} mediaType
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
export {}
|
export {}
|
||||||
@ -1,13 +1,15 @@
|
|||||||
import test from 'ava';
|
import test from 'ava';
|
||||||
import {join} from 'node:path';
|
import {join} from 'node:path';
|
||||||
|
import { readFile } from 'node:fs/promises'
|
||||||
|
|
||||||
import {getOdtTemplate} from '../../scripts/odf/odtTemplate-forNode.js'
|
import {getOdtTemplate} from '../../scripts/odf/odtTemplate-forNode.js'
|
||||||
|
|
||||||
import {fillOdtTemplate} from '../../exports.js'
|
import {fillOdtTemplate, getOdtTextContent} from '../../exports.js'
|
||||||
import { listZipEntries } from '../helpers/zip-analysis.js';
|
import { listZipEntries } from '../helpers/zip-analysis.js';
|
||||||
|
import { getContentDocument } from '../../scripts/odf/odt/getOdtTextContent.js';
|
||||||
|
|
||||||
|
|
||||||
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 templatePath = join(import.meta.dirname, '../fixtures/template-avec-image.odt')
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
@ -35,4 +37,48 @@ test('template filling preserves images', async t => {
|
|||||||
`One zip entry of the result is expected to have a name that starts with 'Pictures/'`
|
`One zip entry of the result is expected to have a name that starts with 'Pictures/'`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
test('insert 2 images', async t => {
|
||||||
|
const templatePath = join(import.meta.dirname, '../fixtures/basic-image-insertion.odt')
|
||||||
|
|
||||||
|
|
||||||
|
const odtTemplate = await getOdtTemplate(templatePath)
|
||||||
|
const templateContent = `{title}
|
||||||
|
|
||||||
|
{#each photos as photo}
|
||||||
|
{#image photo}
|
||||||
|
{/each}
|
||||||
|
`
|
||||||
|
const templateTextContent = await getOdtTextContent(odtTemplate)
|
||||||
|
|
||||||
|
t.is(templateTextContent, templateContent, 'reconnaissance du template')
|
||||||
|
|
||||||
|
const photo1Path = join(import.meta.dirname, '../fixtures/pitchou-1.png')
|
||||||
|
const photo2Path = join(import.meta.dirname, '../fixtures/pitchou-2.png')
|
||||||
|
|
||||||
|
const photo1Buffer = (await readFile(photo1Path)).buffer
|
||||||
|
const photo2Buffer = (await readFile(photo2Path)).buffer
|
||||||
|
|
||||||
|
const photos = [{content: photo1Buffer, fileName: 'pitchou-1.png', mediaType: 'image/png'}, {content: photo2Buffer, fileName: 'pitchou-2.png', mediaType: 'image/png'}]
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
title: 'Titre de mon projet',
|
||||||
|
photos,
|
||||||
|
}
|
||||||
|
|
||||||
|
const odtResult = await fillOdtTemplate(odtTemplate, data)
|
||||||
|
const resultEntries = await listZipEntries(odtResult)
|
||||||
|
|
||||||
|
|
||||||
|
t.is(
|
||||||
|
resultEntries.filter(entry => entry.filename.startsWith('Pictures/')).length, 2,
|
||||||
|
`Two pictures in 'Pictures/' folder are expected`
|
||||||
|
)
|
||||||
|
|
||||||
|
const odtContentDocument = await getContentDocument(odtResult)
|
||||||
|
|
||||||
|
const drawImageElements = odtContentDocument.getElementsByTagName('draw:image')
|
||||||
|
t.is(drawImageElements.length, 2, 'Two draw:image elements should be in the generated document.')
|
||||||
|
|
||||||
})
|
})
|
||||||
BIN
tests/fixtures/basic-image-insertion.odt
vendored
Normal file
BIN
tests/fixtures/basic-image-insertion.odt
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/pitchou-1.png
vendored
Normal file
BIN
tests/fixtures/pitchou-1.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 768 KiB |
BIN
tests/fixtures/pitchou-2.png
vendored
Normal file
BIN
tests/fixtures/pitchou-2.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 799 KiB |
@ -1,4 +1,4 @@
|
|||||||
import {writeFile} from 'node:fs/promises'
|
import {writeFile, readFile} from 'node:fs/promises'
|
||||||
import {join} from 'node:path';
|
import {join} from 'node:path';
|
||||||
|
|
||||||
import {getOdtTemplate} from '../scripts/odf/odtTemplate-forNode.js'
|
import {getOdtTemplate} from '../scripts/odf/odtTemplate-forNode.js'
|
||||||
@ -114,9 +114,33 @@ const data = {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
// const templatePath = join(import.meta.dirname, '../tests/fixtures/text-after-closing-each.odt')
|
||||||
|
// const data = {
|
||||||
|
// saison: 'Printemps',
|
||||||
|
// légumes: [
|
||||||
|
// 'Asperge',
|
||||||
|
// 'Betterave',
|
||||||
|
// 'Blette'
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
|
||||||
const templatePath = join(import.meta.dirname, '../tests/fixtures/if-then-each.odt')
|
// const templatePath = join(import.meta.dirname, '../tests/fixtures/if-then-each.odt')
|
||||||
const data = {liste_départements : ['95', '33']}
|
// const data = {liste_départements : ['95', '33']}
|
||||||
|
|
||||||
|
|
||||||
|
const templatePath = join(import.meta.dirname, '../tests/fixtures/basic-image-insertion.odt')
|
||||||
|
const photo1Path = join(import.meta.dirname, '../tests/fixtures/pitchou-1.png')
|
||||||
|
const photo2Path = join(import.meta.dirname, '../tests/fixtures/pitchou-2.png')
|
||||||
|
|
||||||
|
const photo1Buffer = (await readFile(photo1Path)).buffer
|
||||||
|
const photo2Buffer = (await readFile(photo2Path)).buffer
|
||||||
|
|
||||||
|
const photos = [{content: photo1Buffer, fileName: 'pitchou-1.png', mediaType: 'image/png'}, {content: photo2Buffer, fileName: 'pitchou-2.png', mediaType: 'image/png'}]
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
title: 'Titre de mon projet',
|
||||||
|
photos,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const odtTemplate = await getOdtTemplate(templatePath)
|
const odtTemplate = await getOdtTemplate(templatePath)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user