You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
718 lines
21 KiB
718 lines
21 KiB
const _ = require('../../utils/under-dash');
|
|
|
|
const RelType = require('../../xlsx/rel-type');
|
|
|
|
const colCache = require('../../utils/col-cache');
|
|
const Encryptor = require('../../utils/encryptor');
|
|
const Dimensions = require('../../doc/range');
|
|
const StringBuf = require('../../utils/string-buf');
|
|
|
|
const Row = require('../../doc/row');
|
|
const Column = require('../../doc/column');
|
|
|
|
const SheetRelsWriter = require('./sheet-rels-writer');
|
|
const SheetCommentsWriter = require('./sheet-comments-writer');
|
|
const DataValidations = require('../../doc/data-validations');
|
|
|
|
const xmlBuffer = new StringBuf();
|
|
|
|
// ============================================================================================
|
|
// Xforms
|
|
const ListXform = require('../../xlsx/xform/list-xform');
|
|
const DataValidationsXform = require('../../xlsx/xform/sheet/data-validations-xform');
|
|
const SheetPropertiesXform = require('../../xlsx/xform/sheet/sheet-properties-xform');
|
|
const SheetFormatPropertiesXform = require('../../xlsx/xform/sheet/sheet-format-properties-xform');
|
|
const ColXform = require('../../xlsx/xform/sheet/col-xform');
|
|
const RowXform = require('../../xlsx/xform/sheet/row-xform');
|
|
const HyperlinkXform = require('../../xlsx/xform/sheet/hyperlink-xform');
|
|
const SheetViewXform = require('../../xlsx/xform/sheet/sheet-view-xform');
|
|
const SheetProtectionXform = require('../../xlsx/xform/sheet/sheet-protection-xform');
|
|
const PageMarginsXform = require('../../xlsx/xform/sheet/page-margins-xform');
|
|
const PageSetupXform = require('../../xlsx/xform/sheet/page-setup-xform');
|
|
const AutoFilterXform = require('../../xlsx/xform/sheet/auto-filter-xform');
|
|
const PictureXform = require('../../xlsx/xform/sheet/picture-xform');
|
|
const ConditionalFormattingsXform = require('../../xlsx/xform/sheet/cf/conditional-formattings-xform');
|
|
const HeaderFooterXform = require('../../xlsx/xform/sheet/header-footer-xform');
|
|
const RowBreaksXform = require('../../xlsx/xform/sheet/row-breaks-xform');
|
|
|
|
// since prepare and render are functional, we can use singletons
|
|
const xform = {
|
|
dataValidations: new DataValidationsXform(),
|
|
sheetProperties: new SheetPropertiesXform(),
|
|
sheetFormatProperties: new SheetFormatPropertiesXform(),
|
|
columns: new ListXform({tag: 'cols', length: false, childXform: new ColXform()}),
|
|
row: new RowXform(),
|
|
hyperlinks: new ListXform({tag: 'hyperlinks', length: false, childXform: new HyperlinkXform()}),
|
|
sheetViews: new ListXform({tag: 'sheetViews', length: false, childXform: new SheetViewXform()}),
|
|
sheetProtection: new SheetProtectionXform(),
|
|
pageMargins: new PageMarginsXform(),
|
|
pageSeteup: new PageSetupXform(),
|
|
autoFilter: new AutoFilterXform(),
|
|
picture: new PictureXform(),
|
|
conditionalFormattings: new ConditionalFormattingsXform(),
|
|
headerFooter: new HeaderFooterXform(),
|
|
rowBreaks: new RowBreaksXform(),
|
|
};
|
|
|
|
// ============================================================================================
|
|
|
|
class WorksheetWriter {
|
|
constructor(options) {
|
|
// in a workbook, each sheet will have a number
|
|
this.id = options.id;
|
|
|
|
// and a name
|
|
this.name = options.name || `Sheet${this.id}`;
|
|
|
|
// add a state
|
|
this.state = options.state || 'visible';
|
|
|
|
// rows are stored here while they need to be worked on.
|
|
// when they are committed, they will be deleted.
|
|
this._rows = [];
|
|
|
|
// column definitions
|
|
this._columns = null;
|
|
|
|
// column keys (addRow convenience): key ==> this._columns index
|
|
this._keys = {};
|
|
|
|
// keep a record of all row and column pageBreaks
|
|
this._merges = [];
|
|
this._merges.add = function() {}; // ignore cell instruction
|
|
|
|
// keep record of all hyperlinks
|
|
this._sheetRelsWriter = new SheetRelsWriter(options);
|
|
|
|
this._sheetCommentsWriter = new SheetCommentsWriter(this, this._sheetRelsWriter, options);
|
|
|
|
// keep a record of dimensions
|
|
this._dimensions = new Dimensions();
|
|
|
|
// first uncommitted row
|
|
this._rowZero = 1;
|
|
|
|
// committed flag
|
|
this.committed = false;
|
|
|
|
// for data validations
|
|
this.dataValidations = new DataValidations();
|
|
|
|
// for sharing formulae
|
|
this._formulae = {};
|
|
this._siFormulae = 0;
|
|
|
|
// keep a record of conditionalFormattings
|
|
this.conditionalFormatting = [];
|
|
|
|
// keep a record of all row and column pageBreaks
|
|
this.rowBreaks = [];
|
|
|
|
// for default row height, outline levels, etc
|
|
this.properties = Object.assign(
|
|
{},
|
|
{
|
|
defaultRowHeight: 15,
|
|
dyDescent: 55,
|
|
outlineLevelCol: 0,
|
|
outlineLevelRow: 0,
|
|
},
|
|
options.properties
|
|
);
|
|
|
|
this.headerFooter = Object.assign(
|
|
{},
|
|
{
|
|
differentFirst: false,
|
|
differentOddEven: false,
|
|
oddHeader: null,
|
|
oddFooter: null,
|
|
evenHeader: null,
|
|
evenFooter: null,
|
|
firstHeader: null,
|
|
firstFooter: null,
|
|
},
|
|
options.headerFooter
|
|
);
|
|
|
|
// for all things printing
|
|
this.pageSetup = Object.assign(
|
|
{},
|
|
{
|
|
margins: {left: 0.7, right: 0.7, top: 0.75, bottom: 0.75, header: 0.3, footer: 0.3},
|
|
orientation: 'portrait',
|
|
horizontalDpi: 4294967295,
|
|
verticalDpi: 4294967295,
|
|
fitToPage: !!(
|
|
options.pageSetup &&
|
|
(options.pageSetup.fitToWidth || options.pageSetup.fitToHeight) &&
|
|
!options.pageSetup.scale
|
|
),
|
|
pageOrder: 'downThenOver',
|
|
blackAndWhite: false,
|
|
draft: false,
|
|
cellComments: 'None',
|
|
errors: 'displayed',
|
|
scale: 100,
|
|
fitToWidth: 1,
|
|
fitToHeight: 1,
|
|
paperSize: undefined,
|
|
showRowColHeaders: false,
|
|
showGridLines: false,
|
|
horizontalCentered: false,
|
|
verticalCentered: false,
|
|
rowBreaks: null,
|
|
colBreaks: null,
|
|
},
|
|
options.pageSetup
|
|
);
|
|
|
|
// using shared strings creates a smaller xlsx file but may use more memory
|
|
this.useSharedStrings = options.useSharedStrings || false;
|
|
|
|
this._workbook = options.workbook;
|
|
|
|
this.hasComments = false;
|
|
|
|
// views
|
|
this._views = options.views || [];
|
|
|
|
// auto filter
|
|
this.autoFilter = options.autoFilter || null;
|
|
|
|
this._media = [];
|
|
|
|
// worksheet protection
|
|
this.sheetProtection = null;
|
|
|
|
// start writing to stream now
|
|
this._writeOpenWorksheet();
|
|
|
|
this.startedData = false;
|
|
}
|
|
|
|
get workbook() {
|
|
return this._workbook;
|
|
}
|
|
|
|
get stream() {
|
|
if (!this._stream) {
|
|
// eslint-disable-next-line no-underscore-dangle
|
|
this._stream = this._workbook._openStream(`/xl/worksheets/sheet${this.id}.xml`);
|
|
|
|
// pause stream to prevent 'data' events
|
|
this._stream.pause();
|
|
}
|
|
return this._stream;
|
|
}
|
|
|
|
// destroy - not a valid operation for a streaming writer
|
|
// even though some streamers might be able to, it's a bad idea.
|
|
destroy() {
|
|
throw new Error('Invalid Operation: destroy');
|
|
}
|
|
|
|
commit() {
|
|
if (this.committed) {
|
|
return;
|
|
}
|
|
// commit all rows
|
|
this._rows.forEach(cRow => {
|
|
if (cRow) {
|
|
// write the row to the stream
|
|
this._writeRow(cRow);
|
|
}
|
|
});
|
|
|
|
// we _cannot_ accept new rows from now on
|
|
this._rows = null;
|
|
|
|
if (!this.startedData) {
|
|
this._writeOpenSheetData();
|
|
}
|
|
this._writeCloseSheetData();
|
|
this._writeAutoFilter();
|
|
this._writeMergeCells();
|
|
|
|
// for some reason, Excel can't handle dimensions at the bottom of the file
|
|
// this._writeDimensions();
|
|
|
|
this._writeHyperlinks();
|
|
this._writeConditionalFormatting();
|
|
this._writeDataValidations();
|
|
this._writeSheetProtection();
|
|
this._writePageMargins();
|
|
this._writePageSetup();
|
|
this._writeBackground();
|
|
this._writeHeaderFooter();
|
|
this._writeRowBreaks();
|
|
|
|
// Legacy Data tag for comments
|
|
this._writeLegacyData();
|
|
|
|
this._writeCloseWorksheet();
|
|
// signal end of stream to workbook
|
|
this.stream.end();
|
|
|
|
this._sheetCommentsWriter.commit();
|
|
// also commit the hyperlinks if any
|
|
this._sheetRelsWriter.commit();
|
|
|
|
this.committed = true;
|
|
}
|
|
|
|
// return the current dimensions of the writer
|
|
get dimensions() {
|
|
return this._dimensions;
|
|
}
|
|
|
|
get views() {
|
|
return this._views;
|
|
}
|
|
|
|
// =========================================================================
|
|
// Columns
|
|
|
|
// get the current columns array.
|
|
get columns() {
|
|
return this._columns;
|
|
}
|
|
|
|
// set the columns from an array of column definitions.
|
|
// Note: any headers defined will overwrite existing values.
|
|
set columns(value) {
|
|
// calculate max header row count
|
|
this._headerRowCount = value.reduce((pv, cv) => {
|
|
const headerCount = (cv.header && 1) || (cv.headers && cv.headers.length) || 0;
|
|
return Math.max(pv, headerCount);
|
|
}, 0);
|
|
|
|
// construct Column objects
|
|
let count = 1;
|
|
const columns = (this._columns = []);
|
|
value.forEach(defn => {
|
|
const column = new Column(this, count++, false);
|
|
columns.push(column);
|
|
column.defn = defn;
|
|
});
|
|
}
|
|
|
|
getColumnKey(key) {
|
|
return this._keys[key];
|
|
}
|
|
|
|
setColumnKey(key, value) {
|
|
this._keys[key] = value;
|
|
}
|
|
|
|
deleteColumnKey(key) {
|
|
delete this._keys[key];
|
|
}
|
|
|
|
eachColumnKey(f) {
|
|
_.each(this._keys, f);
|
|
}
|
|
|
|
// get a single column by col number. If it doesn't exist, it and any gaps before it
|
|
// are created.
|
|
getColumn(c) {
|
|
if (typeof c === 'string') {
|
|
// if it matches a key'd column, return that
|
|
const col = this._keys[c];
|
|
if (col) return col;
|
|
|
|
// otherwise, assume letter
|
|
c = colCache.l2n(c);
|
|
}
|
|
if (!this._columns) {
|
|
this._columns = [];
|
|
}
|
|
if (c > this._columns.length) {
|
|
let n = this._columns.length + 1;
|
|
while (n <= c) {
|
|
this._columns.push(new Column(this, n++));
|
|
}
|
|
}
|
|
return this._columns[c - 1];
|
|
}
|
|
|
|
// =========================================================================
|
|
// Rows
|
|
get _nextRow() {
|
|
return this._rowZero + this._rows.length;
|
|
}
|
|
|
|
// iterate over every uncommitted row in the worksheet, including maybe empty rows
|
|
eachRow(options, iteratee) {
|
|
if (!iteratee) {
|
|
iteratee = options;
|
|
options = undefined;
|
|
}
|
|
if (options && options.includeEmpty) {
|
|
const n = this._nextRow;
|
|
for (let i = this._rowZero; i < n; i++) {
|
|
iteratee(this.getRow(i), i);
|
|
}
|
|
} else {
|
|
this._rows.forEach(row => {
|
|
if (row.hasValues) {
|
|
iteratee(row, row.number);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
_commitRow(cRow) {
|
|
// since rows must be written in order, we commit all rows up till and including cRow
|
|
let found = false;
|
|
while (this._rows.length && !found) {
|
|
const row = this._rows.shift();
|
|
this._rowZero++;
|
|
if (row) {
|
|
this._writeRow(row);
|
|
found = row.number === cRow.number;
|
|
this._rowZero = row.number + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
get lastRow() {
|
|
// returns last uncommitted row
|
|
if (this._rows.length) {
|
|
return this._rows[this._rows.length - 1];
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
// find a row (if exists) by row number
|
|
findRow(rowNumber) {
|
|
const index = rowNumber - this._rowZero;
|
|
return this._rows[index];
|
|
}
|
|
|
|
getRow(rowNumber) {
|
|
const index = rowNumber - this._rowZero;
|
|
|
|
// may fail if rows have been comitted
|
|
if (index < 0) {
|
|
throw new Error('Out of bounds: this row has been committed');
|
|
}
|
|
let row = this._rows[index];
|
|
if (!row) {
|
|
this._rows[index] = row = new Row(this, rowNumber);
|
|
}
|
|
return row;
|
|
}
|
|
|
|
addRow(value) {
|
|
const row = new Row(this, this._nextRow);
|
|
this._rows[row.number - this._rowZero] = row;
|
|
row.values = value;
|
|
return row;
|
|
}
|
|
|
|
// ================================================================================
|
|
// Cells
|
|
|
|
// returns the cell at [r,c] or address given by r. If not found, return undefined
|
|
findCell(r, c) {
|
|
const address = colCache.getAddress(r, c);
|
|
const row = this.findRow(address.row);
|
|
return row ? row.findCell(address.column) : undefined;
|
|
}
|
|
|
|
// return the cell at [r,c] or address given by r. If not found, create a new one.
|
|
getCell(r, c) {
|
|
const address = colCache.getAddress(r, c);
|
|
const row = this.getRow(address.row);
|
|
return row.getCellEx(address);
|
|
}
|
|
|
|
mergeCells(...cells) {
|
|
// may fail if rows have been comitted
|
|
const dimensions = new Dimensions(cells);
|
|
|
|
// check cells aren't already merged
|
|
this._merges.forEach(merge => {
|
|
if (merge.intersects(dimensions)) {
|
|
throw new Error('Cannot merge already merged cells');
|
|
}
|
|
});
|
|
|
|
// apply merge
|
|
const master = this.getCell(dimensions.top, dimensions.left);
|
|
for (let i = dimensions.top; i <= dimensions.bottom; i++) {
|
|
for (let j = dimensions.left; j <= dimensions.right; j++) {
|
|
if (i > dimensions.top || j > dimensions.left) {
|
|
this.getCell(i, j).merge(master);
|
|
}
|
|
}
|
|
}
|
|
|
|
// index merge
|
|
this._merges.push(dimensions);
|
|
}
|
|
|
|
// ===========================================================================
|
|
// Conditional Formatting
|
|
addConditionalFormatting(cf) {
|
|
this.conditionalFormatting.push(cf);
|
|
}
|
|
|
|
removeConditionalFormatting(filter) {
|
|
if (typeof filter === 'number') {
|
|
this.conditionalFormatting.splice(filter, 1);
|
|
} else if (filter instanceof Function) {
|
|
this.conditionalFormatting = this.conditionalFormatting.filter(filter);
|
|
} else {
|
|
this.conditionalFormatting = [];
|
|
}
|
|
}
|
|
|
|
// =========================================================================
|
|
|
|
addBackgroundImage(imageId) {
|
|
this._background = {
|
|
imageId,
|
|
};
|
|
}
|
|
|
|
getBackgroundImageId() {
|
|
return this._background && this._background.imageId;
|
|
}
|
|
|
|
// =========================================================================
|
|
// Worksheet Protection
|
|
protect(password, options) {
|
|
// TODO: make this function truly async
|
|
// perhaps marshal to worker thread or something
|
|
return new Promise(resolve => {
|
|
this.sheetProtection = {
|
|
sheet: true,
|
|
};
|
|
if (options && 'spinCount' in options) {
|
|
// force spinCount to be integer >= 0
|
|
options.spinCount = Number.isFinite(options.spinCount) ? Math.round(Math.max(0, options.spinCount)) : 100000;
|
|
}
|
|
if (password) {
|
|
this.sheetProtection.algorithmName = 'SHA-512';
|
|
this.sheetProtection.saltValue = Encryptor.randomBytes(16).toString('base64');
|
|
this.sheetProtection.spinCount = options && 'spinCount' in options ? options.spinCount : 100000; // allow user specified spinCount
|
|
this.sheetProtection.hashValue = Encryptor.convertPasswordToHash(
|
|
password,
|
|
'SHA512',
|
|
this.sheetProtection.saltValue,
|
|
this.sheetProtection.spinCount
|
|
);
|
|
}
|
|
if (options) {
|
|
this.sheetProtection = Object.assign(this.sheetProtection, options);
|
|
if (!password && 'spinCount' in options) {
|
|
delete this.sheetProtection.spinCount;
|
|
}
|
|
}
|
|
resolve();
|
|
});
|
|
}
|
|
|
|
unprotect() {
|
|
this.sheetProtection = null;
|
|
}
|
|
|
|
// ================================================================================
|
|
|
|
_write(text) {
|
|
xmlBuffer.reset();
|
|
xmlBuffer.addText(text);
|
|
this.stream.write(xmlBuffer);
|
|
}
|
|
|
|
_writeSheetProperties(xmlBuf, properties, pageSetup) {
|
|
const sheetPropertiesModel = {
|
|
outlineProperties: properties && properties.outlineProperties,
|
|
tabColor: properties && properties.tabColor,
|
|
pageSetup:
|
|
pageSetup && pageSetup.fitToPage
|
|
? {
|
|
fitToPage: pageSetup.fitToPage,
|
|
}
|
|
: undefined,
|
|
};
|
|
|
|
xmlBuf.addText(xform.sheetProperties.toXml(sheetPropertiesModel));
|
|
}
|
|
|
|
_writeSheetFormatProperties(xmlBuf, properties) {
|
|
const sheetFormatPropertiesModel = properties
|
|
? {
|
|
defaultRowHeight: properties.defaultRowHeight,
|
|
dyDescent: properties.dyDescent,
|
|
outlineLevelCol: properties.outlineLevelCol,
|
|
outlineLevelRow: properties.outlineLevelRow,
|
|
}
|
|
: undefined;
|
|
if (properties.defaultColWidth) {
|
|
sheetFormatPropertiesModel.defaultColWidth = properties.defaultColWidth;
|
|
}
|
|
|
|
xmlBuf.addText(xform.sheetFormatProperties.toXml(sheetFormatPropertiesModel));
|
|
}
|
|
|
|
_writeOpenWorksheet() {
|
|
xmlBuffer.reset();
|
|
|
|
xmlBuffer.addText('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>');
|
|
xmlBuffer.addText(
|
|
'<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"' +
|
|
' xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"' +
|
|
' xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"' +
|
|
' mc:Ignorable="x14ac"' +
|
|
' xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac">'
|
|
);
|
|
|
|
this._writeSheetProperties(xmlBuffer, this.properties, this.pageSetup);
|
|
|
|
xmlBuffer.addText(xform.sheetViews.toXml(this.views));
|
|
|
|
this._writeSheetFormatProperties(xmlBuffer, this.properties);
|
|
|
|
this.stream.write(xmlBuffer);
|
|
}
|
|
|
|
_writeColumns() {
|
|
const cols = Column.toModel(this.columns);
|
|
if (cols) {
|
|
xform.columns.prepare(cols, {styles: this._workbook.styles});
|
|
this.stream.write(xform.columns.toXml(cols));
|
|
}
|
|
}
|
|
|
|
_writeOpenSheetData() {
|
|
this._write('<sheetData>');
|
|
}
|
|
|
|
_writeRow(row) {
|
|
if (!this.startedData) {
|
|
this._writeColumns();
|
|
this._writeOpenSheetData();
|
|
this.startedData = true;
|
|
}
|
|
|
|
if (row.hasValues || row.height) {
|
|
const {model} = row;
|
|
const options = {
|
|
styles: this._workbook.styles,
|
|
sharedStrings: this.useSharedStrings ? this._workbook.sharedStrings : undefined,
|
|
hyperlinks: this._sheetRelsWriter.hyperlinksProxy,
|
|
merges: this._merges,
|
|
formulae: this._formulae,
|
|
siFormulae: this._siFormulae,
|
|
comments: [],
|
|
};
|
|
xform.row.prepare(model, options);
|
|
this.stream.write(xform.row.toXml(model));
|
|
|
|
if (options.comments.length) {
|
|
this.hasComments = true;
|
|
this._sheetCommentsWriter.addComments(options.comments);
|
|
}
|
|
}
|
|
}
|
|
|
|
_writeCloseSheetData() {
|
|
this._write('</sheetData>');
|
|
}
|
|
|
|
_writeMergeCells() {
|
|
if (this._merges.length) {
|
|
xmlBuffer.reset();
|
|
xmlBuffer.addText(`<mergeCells count="${this._merges.length}">`);
|
|
this._merges.forEach(merge => {
|
|
xmlBuffer.addText(`<mergeCell ref="${merge}"/>`);
|
|
});
|
|
xmlBuffer.addText('</mergeCells>');
|
|
|
|
this.stream.write(xmlBuffer);
|
|
}
|
|
}
|
|
|
|
_writeHyperlinks() {
|
|
// eslint-disable-next-line no-underscore-dangle
|
|
this.stream.write(xform.hyperlinks.toXml(this._sheetRelsWriter._hyperlinks));
|
|
}
|
|
|
|
_writeConditionalFormatting() {
|
|
const options = {
|
|
styles: this._workbook.styles,
|
|
};
|
|
xform.conditionalFormattings.prepare(this.conditionalFormatting, options);
|
|
this.stream.write(xform.conditionalFormattings.toXml(this.conditionalFormatting));
|
|
}
|
|
|
|
_writeRowBreaks() {
|
|
this.stream.write(xform.rowBreaks.toXml(this.rowBreaks));
|
|
}
|
|
|
|
_writeDataValidations() {
|
|
this.stream.write(xform.dataValidations.toXml(this.dataValidations.model));
|
|
}
|
|
|
|
_writeSheetProtection() {
|
|
this.stream.write(xform.sheetProtection.toXml(this.sheetProtection));
|
|
}
|
|
|
|
_writePageMargins() {
|
|
this.stream.write(xform.pageMargins.toXml(this.pageSetup.margins));
|
|
}
|
|
|
|
_writePageSetup() {
|
|
this.stream.write(xform.pageSeteup.toXml(this.pageSetup));
|
|
}
|
|
|
|
_writeHeaderFooter() {
|
|
this.stream.write(xform.headerFooter.toXml(this.headerFooter));
|
|
}
|
|
|
|
_writeAutoFilter() {
|
|
this.stream.write(xform.autoFilter.toXml(this.autoFilter));
|
|
}
|
|
|
|
_writeBackground() {
|
|
if (this._background) {
|
|
if (this._background.imageId !== undefined) {
|
|
const image = this._workbook.getImage(this._background.imageId);
|
|
const pictureId = this._sheetRelsWriter.addMedia({
|
|
Target: `../media/${image.name}`,
|
|
Type: RelType.Image,
|
|
});
|
|
|
|
this._background = {
|
|
...this._background,
|
|
rId: pictureId,
|
|
};
|
|
}
|
|
this.stream.write(xform.picture.toXml({rId: this._background.rId}));
|
|
}
|
|
}
|
|
|
|
_writeLegacyData() {
|
|
if (this.hasComments) {
|
|
xmlBuffer.reset();
|
|
xmlBuffer.addText(`<legacyDrawing r:id="${this._sheetCommentsWriter.vmlRelId}"/>`);
|
|
this.stream.write(xmlBuffer);
|
|
}
|
|
}
|
|
|
|
_writeDimensions() {
|
|
// for some reason, Excel can't handle dimensions at the bottom of the file
|
|
// and we don't know the dimensions until the commit, so don't write them.
|
|
// this._write('<dimension ref="' + this._dimensions + '"/>');
|
|
}
|
|
|
|
_writeCloseWorksheet() {
|
|
this._write('</worksheet>');
|
|
}
|
|
}
|
|
|
|
module.exports = WorksheetWriter;
|