read-excel-file
Version:
Read `.xlsx` files in a web browser or in Node.js
144 lines (132 loc) • 5.16 kB
JavaScript
import { Readable } from 'node:stream';
import sax from 'sax';
// For some strange reason, enabling "streaming" mode results in
// quite a dramatic drop in performance.
// For example, reading a `10 MB` `.xlsx` file in "streaming" mode is about `3.1` secs.
// while in non-"streaming" mode it's about `1.4` secs.
// Because of such strange performance issue, "streaming" mode is turned off.
var STREAMING_MODE = false;
/**
* Parses XML markup in a streaming fashion by calling the supplied callback functions as the XML markup is being input.
* @param {any} state — The initial `state`. This `state` will supposedly be modified by the callback functions as the XML is being parsed.
* @param {function} [onOpenTag]
* @param {function} [onCloseTag]
* @param {function} [onText]
* @returns {object} An object with properties: `promise`, `write(string)`, `end()`. The `promise` resolves with nothing.
*/
export default function parseXmlStream(createInitialState, onOpenTag, onCloseTag, onText) {
// This `state` will be modified by the callback functions as the XML is being parsed.
var state = createInitialState();
var errored = false;
var mustNotHaveErrored = function mustNotHaveErrored() {
if (errored) {
// If this error is thrown then it means that there's a bug in the code
// because the code should not have got here if the XML parsing process
// has already errored.
throw new Error('Errored');
}
};
var strict = true;
var xmlns = true;
var parser = STREAMING_MODE ? undefined : sax.parser(strict, parserOptions);
// This `stream` could be `.pipe()`d to:
// `Readable.from(xml).pipe(parserStream)`
var parserStream = STREAMING_MODE ? sax.createStream(strict, parserOptions) : undefined;
// Inputs a chunk of XML.
var write = function write(xml) {
mustNotHaveErrored();
parser.write(xml);
};
// No more chunks of XMl will be input.
var end = function end() {
mustNotHaveErrored();
parser.close();
};
// This `promise` resolves with the final `state` when finished parsing.
var promise = new Promise(function (resolve, reject) {
var parserOptions = {
lowercase: true,
xmlns: xmlns
};
var prefixedTagNameToUnprefixedTagName = {};
// on XML parsing error
var onerror = function onerror(error) {
if (errored) {
return;
}
errored = true;
// P.S. When the parser encounters an error, it halts until manually resumed.
// I.e. at this stage the parser is in halted state until manually resumed.
reject(error);
};
// got some text. `text` is the string of text.
var ontext = function ontext(text) {
if (onText) {
onText(text, state);
}
};
// opened a tag. `node` has "name" and "attributes"
var onopentag = function onopentag(node) {
if (onOpenTag) {
if (xmlns) {
prefixedTagNameToUnprefixedTagName[node.name] = node.local;
}
onOpenTag(xmlns ? node.local : node.name, xmlns ? getAttributesWithoutXmlnsPrefixes(node.attributes) : node.attributes, state);
}
};
// closed a tag.
var onclosetag = function onclosetag(name) {
if (onCloseTag) {
var tagName = xmlns ? prefixedTagNameToUnprefixedTagName[name] : name;
if (xmlns && !tagName) {
// I think that `sax` already validates closing tags internally,
// i.e. it's likely that it already emits an "error" event
// when it encounters an unclosed tag, in which case this error
// is not technically possible. And if it ever gets thrown
// then that would mean that there's a bug in the code.
throw new Error("Unknown closing tag: ".concat(name));
}
onCloseTag(tagName, state);
}
};
// an attribute. `attribute` has "name" and "value"
var onattribute = function onattribute(attribute) {
// const name = xmlns ? attribute.local : attribute.name
// const value = attribute.value
};
// parser stream is done, and ready to have more stuff written to it.
var onend = function onend() {
if (errored) {
return;
}
resolve(state);
};
if (STREAMING_MODE) {
parserStream.on('error', onerror);
parserStream.on('text', ontext);
parserStream.on('opentag', onopentag);
parserStream.on('closetag', onclosetag);
parserStream.on('attribute', onattribute);
parserStream.on('end', onend);
} else {
parser.onerror = onerror;
parser.ontext = ontext;
parser.onopentag = onopentag;
parser.onclosetag = onclosetag;
parser.onattribute = onattribute;
parser.onend = onend;
}
});
return {
promise: promise,
write: write,
end: end
};
}
function getAttributesWithoutXmlnsPrefixes(attributes) {
return Object.keys(attributes).reduce(function (attributesWithoutPrefixes, nameWithPrefix) {
attributesWithoutPrefixes[attributes[nameWithPrefix].local] = attributes[nameWithPrefix].value;
return attributesWithoutPrefixes;
}, {});
}
//# sourceMappingURL=parseXmlStream.sax.js.map