Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"bracketSpacing": false,
"printWidth": 100,
"trailingComma": "all",
"trailingComma": "es5",
"arrowParens": "avoid",
"singleQuote": true
}
23 changes: 20 additions & 3 deletions lib/xlsx/xform/drawing/drawing-xform.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,24 @@ class DrawingXform extends BaseXform {
switch (node.name) {
case this.tag:
this.reset();
this._rootTag = undefined;
this.model = {
anchors: [],
};
break;
default:
this.parser = this.map[node.name];
if (this.parser) {
this.parser.parseOpen(node);
// If we have not yet opened a model, this is the root element of a
// drawing file with a non-standard namespace (e.g. not xdr:wsDr).
// Capture the actual root tag so parseClose can signal loop-exit when
// the corresponding close tag arrives, preventing a StreamBuf hang.
if (!this.model) {
this._rootTag = node.name;
this.model = {anchors: []};
} else {
this.parser = this.map[node.name];
if (this.parser) {
this.parser.parseOpen(node);
}
}
break;
}
Expand All @@ -85,6 +95,13 @@ class DrawingXform extends BaseXform {
case this.tag:
return false;
default:
// Return false (loop-exit signal) when the actual root element closes,
// even if it used a non-standard namespace instead of xdr:wsDr.
// Without this, the StreamBuf for-await loop exhausts all SAX events
// and blocks waiting for stream data that never arrives (issue #56).
if (name === this._rootTag) {
return false;
}
// could be some unrecognised tags
return true;
}
Expand Down
46 changes: 46 additions & 0 deletions spec/integration/issues/issue-56-streambuf-non-xdr-root.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const fs = require('fs');
const JSZip = require('jszip');

const ExcelJS = verquire('exceljs');

// Test for issue #56: StreamBuf parser hangs when a workbook drawing file uses
// a root element other than <xdr:wsDr> (e.g. a proprietary or unknown namespace).
//
// Root cause: DrawingXform.parseClose() only returned the loop-exit signal (false)
// when the close tag was exactly "xdr:wsDr". Any other close tag fell to
// `default: return true`, keeping the BaseXform for-await loop alive until the
// underlying stream was exhausted — which never happens with StreamBuf unless the
// caller explicitly ends it. Fix: capture the actual root tag in parseOpen() and
// also return false when that tag closes.

function buildFixture() {
const srcBuf = fs.readFileSync('./spec/integration/data/test-issue-1575.xlsx');
const unknownTag = '<some:other xmlns:some="http://example.com/unknown-drawing-ns"/>';

return JSZip.loadAsync(srcBuf).then(zip =>
zip.files['xl/drawings/drawing1.xml'].async('string').then(originalXml => {
// The fixture drawing is a self-closing element: <xdr:wsDr ... />
// Replace the entire tag with a minimal unknown-namespace tag using
// string indexing (avoids regex issues with long attribute strings).
const openStart = originalXml.indexOf('<xdr:wsDr');
const openEnd = originalXml.indexOf('>', openStart);
const modifiedXml =
originalXml.substring(0, openStart) + unknownTag + originalXml.substring(openEnd + 1);

zip.file('xl/drawings/drawing1.xml', modifiedXml);
return zip.generateAsync({type: 'nodebuffer'});
})
);
}

describe('github issues', () => {
it('issue 56 - StreamBuf hang on drawing with non-xdr:wsDr root element', () => {
const wb = new ExcelJS.Workbook();
return buildFixture()
.then(buf => wb.xlsx.load(buf))
.then(() => {
// Arriving here means the parser exited cleanly — no StreamBuf hang.
expect(true).to.equal(true);
});
}).timeout(5000);
});