UNPKG

read-excel-file

Version:

Read `.xlsx` files in a web browser or in Node.js

144 lines (132 loc) 5.16 kB
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