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);
+});