diff --git a/lib/xlsx/xlsx.js b/lib/xlsx/xlsx.js index 0d66a8c73..b84684ae7 100644 --- a/lib/xlsx/xlsx.js +++ b/lib/xlsx/xlsx.js @@ -97,7 +97,7 @@ class XLSX { Object.keys(model.drawings).forEach(name => { const drawing = model.drawings[name]; const drawingRel = model.drawingRels[name]; - if (drawingRel) { + if (drawing && drawingRel) { drawingOptions.rels = drawingRel.reduce((o, rel) => { o[rel.Id] = rel; return o; diff --git a/spec/integration/issues/issue-45-drawing-without-anchors.spec.js b/spec/integration/issues/issue-45-drawing-without-anchors.spec.js new file mode 100644 index 000000000..8d677cf03 --- /dev/null +++ b/spec/integration/issues/issue-45-drawing-without-anchors.spec.js @@ -0,0 +1,105 @@ +'use strict'; + +// Regression test for issue #45: +// Drawings that produce no anchors (empty xdr:wsDr body) previously crashed +// in the reconcile loop because drawing.anchors was undefined. The fix +// guards the reconcile block with `if (drawing && drawingRel)` and uses +// `(drawing.anchors || [])` so both cases are safe. +// +// Note: the complementary case where DrawingXform.parseStream() itself returns +// undefined (non-xdr:wsDr root) is covered by PR #70 and its test. That +// path requires the StreamBuf hang fix to be present; test it after #70 merges. + +const JSZip = require('jszip'); +const ExcelJS = verquire('exceljs'); + +// Minimal XLSX parts ------------------------------------------------------- + +const WORKBOOK_XML = ` + + + + +`; + +const WORKBOOK_RELS_XML = ` + + +`; + +const SHEET1_XML = ` + + + Hello + + +`; + +const SHEET1_RELS_XML = ` + + +`; + +// A valid xdr:wsDr root but with NO anchor children — DrawingXform returns +// {anchors: []} and reconcile must not crash on the empty anchors array. +const DRAWING1_XML = ` + +`; + +const DRAWING1_RELS_XML = ` + +`; + +const CONTENT_TYPES_XML = ` + + + + + + +`; + +const ROOT_RELS_XML = ` + + +`; + +async function buildXlsxBuffer() { + const zip = new JSZip(); + zip.file('[Content_Types].xml', CONTENT_TYPES_XML); + zip.file('_rels/.rels', ROOT_RELS_XML); + zip.file('xl/workbook.xml', WORKBOOK_XML); + zip.file('xl/_rels/workbook.xml.rels', WORKBOOK_RELS_XML); + zip.file('xl/worksheets/sheet1.xml', SHEET1_XML); + zip.file('xl/worksheets/_rels/sheet1.xml.rels', SHEET1_RELS_XML); + zip.file('xl/drawings/drawing1.xml', DRAWING1_XML); + zip.file('xl/drawings/_rels/drawing1.xml.rels', DRAWING1_RELS_XML); + return zip.generateAsync({type: 'nodebuffer'}); +} + +// -------------------------------------------------------------------------- + +describe('github issue 45 - drawing reconcile does not crash on empty drawing', () => { + it('loads an XLSX with a drawing that has no anchors without throwing', async () => { + const buffer = await buildXlsxBuffer(); + const wb = new ExcelJS.Workbook(); + // Must not throw; before the fix `drawing.anchors` access could crash when + // the drawing model had no anchors array. + await wb.xlsx.load(buffer); + const ws = wb.getWorksheet('Sheet1'); + expect(ws).to.exist; + expect(ws.getCell('A1').value).to.equal('Hello'); + }).timeout(10000); +});