diff --git a/src/__tests__/__snapshots__/images.test.ts.snap b/src/__tests__/__snapshots__/images.test.ts.snap index 6e1d1a84..a0eb18a7 100644 --- a/src/__tests__/__snapshots__/images.test.ts.snap +++ b/src/__tests__/__snapshots__/images.test.ts.snap @@ -2375,3 +2375,261 @@ Object { "_tag": "w:document", } `; + +exports[`007: can inject an image in a document that already contains images (regression test for #144) 1`] = ` +" + + + + + + + + + + + + + + + + + + + + 0 + + + 197485 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +" +`; diff --git a/src/__tests__/fixtures/imageExistingMultiple.docx b/src/__tests__/fixtures/imageExistingMultiple.docx new file mode 100644 index 00000000..ce06860d Binary files /dev/null and b/src/__tests__/fixtures/imageExistingMultiple.docx differ diff --git a/src/__tests__/images.test.ts b/src/__tests__/images.test.ts index c2064b19..b63f8a9d 100644 --- a/src/__tests__/images.test.ts +++ b/src/__tests__/images.test.ts @@ -271,21 +271,23 @@ it('007: can inject an image in a document that already contains images (regress const buff = await fs.promises.readFile( path.join(__dirname, 'fixtures', 'sample.png') ); - const report = await createReport({ - template, - data: { - cv: { ProfilePicture: { url: 'abc' } }, - }, - additionalJsContext: { - getImage: () => ({ - width: 6, - height: 6, - data: buff, - extension: '.png', - }), - }, - }); - // TODO: can only be tested by loading the result into MS Word. It will show a file corruption warning. - fs.writeFileSync('test.docx', report); - expect(report).toBeInstanceOf(Uint8Array); + expect( + await createReport( + { + template, + data: { + cv: { ProfilePicture: { url: 'abc' } }, + }, + additionalJsContext: { + getImage: () => ({ + width: 6, + height: 6, + data: buff, + extension: '.png', + }), + }, + }, + 'XML' + ) + ).toMatchSnapshot(); }); diff --git a/src/__tests__/unit.test.ts b/src/__tests__/unit.test.ts index f664e0fe..df9beddd 100644 --- a/src/__tests__/unit.test.ts +++ b/src/__tests__/unit.test.ts @@ -1,8 +1,14 @@ import path from 'path'; import { zipLoad } from '../zip'; -import { readContentTypes, getMainDoc, getMetadata } from '../main'; +import { + readContentTypes, + getMainDoc, + getMetadata, + parseTemplate, +} from '../main'; import fs from 'fs'; import { setDebugLogSink } from '../debug'; +import { findHighestImgId } from '../processTemplate'; if (process.env.DEBUG) setDebugLogSink(console.log); @@ -69,3 +75,13 @@ describe('getMetadata', () => { } }); }); + +describe('findHighestImgId', () => { + it('returns 0 when doc contains no images', async () => { + const template = await fs.promises.readFile( + path.join(__dirname, 'fixtures', 'imageExistingMultiple.docx') + ); + const { jsTemplate } = await parseTemplate(template); + expect(findHighestImgId(jsTemplate)).toBe(3); + }); +}); diff --git a/src/main.ts b/src/main.ts index b22c5b36..055de255 100644 --- a/src/main.ts +++ b/src/main.ts @@ -15,6 +15,7 @@ import { walkTemplate, getCommand, splitCommand, + newContext, } from './processTemplate'; import { UserOptions, @@ -37,7 +38,7 @@ const DEFAULT_LITERAL_XML_DELIMITER = '||' as const; const CONTENT_TYPES_PATH = '[Content_Types].xml' as const; const TEMPLATE_PATH = 'word' as const; -async function parseTemplate(template: Buffer) { +export async function parseTemplate(template: Buffer) { logger.debug('Unzipping...'); const zip = await zipLoad(template); @@ -318,7 +319,8 @@ export async function listCommands( const prepped = preprocessTemplate(jsTemplate, opts.cmdDelimiter); const commands: CommandSummary[] = []; - await walkTemplate(undefined, prepped, opts, async (data, node, ctx) => { + const ctx = newContext(opts); + await walkTemplate(undefined, prepped, ctx, async (data, node, ctx) => { const raw = getCommand(ctx.cmd, ctx.shorthands, ctx.options.fixSmartQuotes); ctx.cmd = ''; // flush the context const { cmdName, cmdRest: code } = splitCommand(raw); diff --git a/src/processTemplate.ts b/src/processTemplate.ts index 5c41b35a..482dba55 100755 --- a/src/processTemplate.ts +++ b/src/processTemplate.ts @@ -32,7 +32,7 @@ import { } from './errors'; import { logger } from './debug'; -function newContext(options: CreateReportOptions): Context { +export function newContext(options: CreateReportOptions, imageId = 0): Context { return { gCntIf: 0, level: 1, @@ -43,7 +43,7 @@ function newContext(options: CreateReportOptions): Context { 'w:p': { text: '', cmds: '', fInsertedText: false }, 'w:tr': { text: '', cmds: '', fInsertedText: false }, }, - imageId: 0, + imageId, images: {}, linkId: 0, links: {}, @@ -120,17 +120,39 @@ export async function produceJsReport( template: Node, options: CreateReportOptions ): Promise { - return walkTemplate(data, template, options, processCmd); + const highestImgId = findHighestImgId(template); + const ctx = newContext(options, highestImgId); + return walkTemplate(data, template, ctx, processCmd); +} + +export function findHighestImgId(mainDoc: Node): number { + const doc_ids: number[] = []; + const search = (n: Node) => { + for (const c of n._children) { + const tag = c._fTextNode ? null : c._tag; + if (tag == null) continue; + if (tag === 'wp:docPr') { + if (c._fTextNode) continue; + const raw = c._attrs.id; + if (typeof raw !== 'string') continue; + const id = Number.parseInt(raw, 10); + if (Number.isSafeInteger(id)) doc_ids.push(id); + } + if (c._children.length > 0) search(c); + } + }; + search(mainDoc); + if (doc_ids.length > 0) return Math.max(...doc_ids); + return 0; } export async function walkTemplate( data: ReportData | undefined, template: Node, - options: CreateReportOptions, + ctx: Context, processor: CommandProcessor ): Promise { const out: Node = cloneNodeWithoutChildren(template); - const ctx = newContext(options); let nodeIn: Node = template; let nodeOut: Node = out; let move;