write-excel-file
Version:
Write simple `*.xlsx` files in a browser or Node.js
325 lines (321 loc) • 12.5 kB
JavaScript
import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
import _regeneratorRuntime from "@babel/runtime/regenerator";
function _createForOfIteratorHelperLoose(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (t) return (t = t.call(r)).next.bind(t); if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var o = 0; return function () { return o >= r.length ? { done: !0 } : { done: !1, value: r[o++] }; }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
import fs from 'fs';
import path from 'path';
import os from 'os';
import Stream, { Readable } from 'stream';
import Archive from './archive.js';
import getImageFileName from './getImageFileName.js';
import generateWorkbookXml from './files/workbook.xml.js';
import generateWorkbookXmlRels from './files/workbook.xml.rels.js';
import rels from './files/rels.js';
import generateContentTypesXml from './files/[Content_Types].xml.js';
import generateDrawingXml from './files/drawing.xml.js';
import generateDrawingXmlRels from './files/drawing.xml.rels.js';
import generateSheetXmlRels from './files/sheet.xml.rels.js';
import generateSharedStringsXml from './files/sharedStrings.xml.js';
import generateStylesXml from './files/styles.xml.js';
import { generateSheets } from './writeXlsxFile.common.js';
export default function writeXlsxFile(_x) {
return _writeXlsxFile.apply(this, arguments);
}
// According to Node.js docs:
// https://nodejs.org/api/fs.html#fswritefilefile-data-options-callback
// `contents` argument could be of type:
// * string — File path
// * Buffer
// * TypedArray
// * DataView
function _writeXlsxFile() {
_writeXlsxFile = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee(data) {
var _ref,
filePath,
buffer,
sheetName,
sheetNames,
schema,
columns,
images,
headerStyle,
getHeaderStyle,
fontFamily,
fontSize,
orientation,
stickyRowsCount,
stickyColumnsCount,
showGridLines,
rightToLeft,
dateFormat,
archive,
_generateSheets,
sheets,
getSharedStrings,
getStyles,
root,
xl,
mediaPath,
drawingsPath,
drawingsRelsPath,
_rels,
worksheetsPath,
worksheetsRelsPath,
promises,
_iterator2,
_step2,
_step2$value,
id,
_data,
_images,
_iterator3,
_step3,
image,
imageContentReadableStream,
imageFilePath,
_args = arguments;
return _regeneratorRuntime.wrap(function _callee$(_context) {
while (1) switch (_context.prev = _context.next) {
case 0:
_ref = _args.length > 1 && _args[1] !== undefined ? _args[1] : {}, filePath = _ref.filePath, buffer = _ref.buffer, sheetName = _ref.sheet, sheetNames = _ref.sheets, schema = _ref.schema, columns = _ref.columns, images = _ref.images, headerStyle = _ref.headerStyle, getHeaderStyle = _ref.getHeaderStyle, fontFamily = _ref.fontFamily, fontSize = _ref.fontSize, orientation = _ref.orientation, stickyRowsCount = _ref.stickyRowsCount, stickyColumnsCount = _ref.stickyColumnsCount, showGridLines = _ref.showGridLines, rightToLeft = _ref.rightToLeft, dateFormat = _ref.dateFormat;
// I dunno why it uses `Archive` here instead of something like `JSZip`
// that is used in `writeXlsxFileBrowser.js`.
archive = new Archive(filePath);
_generateSheets = generateSheets({
data: data,
sheetName: sheetName,
sheetNames: sheetNames,
schema: schema,
columns: columns,
images: images,
headerStyle: headerStyle,
getHeaderStyle: getHeaderStyle,
fontFamily: fontFamily,
fontSize: fontSize,
orientation: orientation,
stickyRowsCount: stickyRowsCount,
stickyColumnsCount: stickyColumnsCount,
showGridLines: showGridLines,
rightToLeft: rightToLeft,
dateFormat: dateFormat
}), sheets = _generateSheets.sheets, getSharedStrings = _generateSheets.getSharedStrings, getStyles = _generateSheets.getStyles; // There doesn't seem to be a way to just append a file into a subdirectory
// in `archiver` library, hence using a hacky temporary directory workaround.
// https://www.npmjs.com/package/archiver
_context.next = 5;
return createTempDirectory();
case 5:
root = _context.sent;
_context.next = 8;
return createDirectory(path.join(root, 'xl'));
case 8:
xl = _context.sent;
_context.next = 11;
return createDirectory(path.join(xl, 'media'));
case 11:
mediaPath = _context.sent;
_context.next = 14;
return createDirectory(path.join(xl, 'drawings'));
case 14:
drawingsPath = _context.sent;
_context.next = 17;
return createDirectory(path.join(drawingsPath, '_rels'));
case 17:
drawingsRelsPath = _context.sent;
_context.next = 20;
return createDirectory(path.join(xl, '_rels'));
case 20:
_rels = _context.sent;
_context.next = 23;
return createDirectory(path.join(xl, 'worksheets'));
case 23:
worksheetsPath = _context.sent;
_context.next = 26;
return createDirectory(path.join(worksheetsPath, '_rels'));
case 26:
worksheetsRelsPath = _context.sent;
promises = [writeFile(path.join(_rels, 'workbook.xml.rels'), generateWorkbookXmlRels({
sheets: sheets
})), writeFile(path.join(xl, 'workbook.xml'), generateWorkbookXml({
sheets: sheets,
stickyRowsCount: stickyRowsCount,
stickyColumnsCount: stickyColumnsCount
})), writeFile(path.join(xl, 'styles.xml'), generateStylesXml(getStyles())), writeFile(path.join(xl, 'sharedStrings.xml'), generateSharedStringsXml(getSharedStrings()))];
for (_iterator2 = _createForOfIteratorHelperLoose(sheets); !(_step2 = _iterator2()).done;) {
_step2$value = _step2.value, id = _step2$value.id, _data = _step2$value.data, _images = _step2$value.images;
promises.push(writeFile(path.join(worksheetsPath, "sheet".concat(id, ".xml")), _data));
promises.push(writeFile(path.join(worksheetsRelsPath, "sheet".concat(id, ".xml.rels")), generateSheetXmlRels({
id: id,
images: _images
})));
if (_images) {
promises.push(writeFile(path.join(drawingsPath, "drawing".concat(id, ".xml")), generateDrawingXml({
images: _images
})));
promises.push(writeFile(path.join(drawingsRelsPath, "drawing".concat(id, ".xml.rels")), generateDrawingXmlRels({
images: _images,
sheetId: id
})));
// Copy images to `xl/media` folder.
for (_iterator3 = _createForOfIteratorHelperLoose(_images); !(_step3 = _iterator3()).done;) {
image = _step3.value;
imageContentReadableStream = getReadableStream(image.content);
imageFilePath = path.join(mediaPath, getImageFileName(image, {
sheetId: id,
sheetImages: _images
}));
promises.push(writeFileFromStream(imageFilePath, imageContentReadableStream));
}
}
}
_context.next = 31;
return Promise.all(promises);
case 31:
archive.directory(xl, 'xl');
archive.append(rels, '_rels/.rels');
archive.append(generateContentTypesXml({
images: images,
sheets: sheets
}), '[Content_Types].xml');
if (!filePath) {
_context.next = 41;
break;
}
_context.next = 37;
return archive.write();
case 37:
_context.next = 39;
return removeDirectoryWithLegacyNodeVersionsSupport(root);
case 39:
_context.next = 46;
break;
case 41:
if (!buffer) {
_context.next = 45;
break;
}
return _context.abrupt("return", streamToBuffer(archive.write()));
case 45:
return _context.abrupt("return", archive.write());
case 46:
case "end":
return _context.stop();
}
}, _callee);
}));
return _writeXlsxFile.apply(this, arguments);
}
function writeFile(path, contents) {
return new Promise(function (resolve, reject) {
fs.writeFile(path, contents, 'utf-8', function (error) {
if (error) {
return reject(error);
}
resolve();
});
});
}
function createDirectory(path) {
return new Promise(function (resolve, reject) {
fs.mkdir(path, function (error) {
if (error) {
return reject(error);
}
resolve(path);
});
});
}
function createTempDirectory() {
return new Promise(function (resolve, reject) {
fs.mkdtemp(path.join(os.tmpdir(), 'write-excel-file-'), function (error, directoryPath) {
if (error) {
return reject(error);
}
resolve(directoryPath);
});
});
}
function removeDirectoryWithLegacyNodeVersionsSupport(path) {
if (fs.rm) {
return removeDirectory(path);
} else {
removeDirectoryLegacySync(path);
return Promise.resolve();
}
}
// `fs.rm()` is available in Node.js since `14.14.0`.
function removeDirectory(path) {
return new Promise(function (resolve, reject) {
fs.rm(path, {
recursive: true,
force: true
}, function (error) {
if (error) {
return reject(error);
}
resolve();
});
});
}
// For Node.js versions below `14.14.0`.
function removeDirectoryLegacySync(directoryPath) {
var childNames = fs.readdirSync(directoryPath);
for (var _iterator = _createForOfIteratorHelperLoose(childNames), _step; !(_step = _iterator()).done;) {
var childName = _step.value;
var childPath = path.join(directoryPath, childName);
var stats = fs.statSync(childPath);
if (childPath === '.' || childPath === '..') {
// Skip.
} else if (stats.isDirectory()) {
// Remove subdirectory recursively.
removeDirectoryLegacySync(childPath);
} else {
// Remove file.
fs.unlinkSync(childPath);
}
}
fs.rmdirSync(directoryPath);
}
// https://stackoverflow.com/a/67729663
function streamToBuffer(stream) {
return new Promise(function (resolve, reject) {
var chunks = [];
stream.on('data', function (chunk) {
return chunks.push(chunk);
});
stream.on('end', function () {
return resolve(Buffer.concat(chunks));
});
stream.on('error', reject);
});
}
function copyFile(fromPath, toPath) {
return new Promise(function (resolve, reject) {
fs.copyFile(fromPath, toPath, function (error) {
if (error) {
return reject(error);
}
resolve();
});
});
}
function getReadableStream(source) {
if (source instanceof Stream) {
return source;
}
if (source instanceof Buffer) {
return Readable.from(source);
}
if (typeof source === 'string') {
return fs.createReadStream(source);
}
throw new Error('Unsupported content source: couldn\'t convert it to a readable stream');
}
function writeFileFromStream(filePath, readableStream) {
var writableStream = fs.createWriteStream(filePath);
readableStream.pipe(writableStream);
return new Promise(function (resolve) {
return writableStream.on('finish', resolve);
});
}
//# sourceMappingURL=writeXlsxFileNode.js.map