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;
 |