first commit
SAP-ERP Portal CI/CD / build (push) Failing after 5m20s

This commit is contained in:
John
2026-09-23 17:31:02 +05:30
commit 69b4e68baf
51657 changed files with 3864077 additions and 0 deletions
+83
View File
@@ -0,0 +1,83 @@
const {EventEmitter} = require('events');
const parseSax = require('../../utils/parse-sax');
const Enums = require('../../doc/enums');
const RelType = require('../../xlsx/rel-type');
class HyperlinkReader extends EventEmitter {
constructor({workbook, id, iterator, options}) {
super();
this.workbook = workbook;
this.id = id;
this.iterator = iterator;
this.options = options;
}
get count() {
return (this.hyperlinks && this.hyperlinks.length) || 0;
}
each(fn) {
return this.hyperlinks.forEach(fn);
}
async read() {
const {iterator, options} = this;
let emitHyperlinks = false;
let hyperlinks = null;
switch (options.hyperlinks) {
case 'emit':
emitHyperlinks = true;
break;
case 'cache':
this.hyperlinks = hyperlinks = {};
break;
default:
break;
}
if (!emitHyperlinks && !hyperlinks) {
this.emit('finished');
return;
}
try {
for await (const events of parseSax(iterator)) {
for (const {eventType, value} of events) {
if (eventType === 'opentag') {
const node = value;
if (node.name === 'Relationship') {
const rId = node.attributes.Id;
switch (node.attributes.Type) {
case RelType.Hyperlink:
{
const relationship = {
type: Enums.RelationshipType.Styles,
rId,
target: node.attributes.Target,
targetMode: node.attributes.TargetMode,
};
if (emitHyperlinks) {
this.emit('hyperlink', relationship);
} else {
hyperlinks[relationship.rId] = relationship;
}
}
break;
default:
break;
}
}
}
}
}
this.emit('finished');
} catch (error) {
this.emit('error', error);
}
}
}
module.exports = HyperlinkReader;
+121
View File
@@ -0,0 +1,121 @@
const XmlStream = require('../../utils/xml-stream');
const RelType = require('../../xlsx/rel-type');
const colCache = require('../../utils/col-cache');
const CommentXform = require('../../xlsx/xform/comment/comment-xform');
const VmlShapeXform = require('../../xlsx/xform/comment/vml-shape-xform');
class SheetCommentsWriter {
constructor(worksheet, sheetRelsWriter, options) {
// in a workbook, each sheet will have a number
this.id = options.id;
this.count = 0;
this._worksheet = worksheet;
this._workbook = options.workbook;
this._sheetRelsWriter = sheetRelsWriter;
}
get commentsStream() {
if (!this._commentsStream) {
// eslint-disable-next-line no-underscore-dangle
this._commentsStream = this._workbook._openStream(`/xl/comments${this.id}.xml`);
}
return this._commentsStream;
}
get vmlStream() {
if (!this._vmlStream) {
// eslint-disable-next-line no-underscore-dangle
this._vmlStream = this._workbook._openStream(`xl/drawings/vmlDrawing${this.id}.vml`);
}
return this._vmlStream;
}
_addRelationships() {
const commentRel = {
Type: RelType.Comments,
Target: `../comments${this.id}.xml`,
};
this._sheetRelsWriter.addRelationship(commentRel);
const vmlDrawingRel = {
Type: RelType.VmlDrawing,
Target: `../drawings/vmlDrawing${this.id}.vml`,
};
this.vmlRelId = this._sheetRelsWriter.addRelationship(vmlDrawingRel);
}
_addCommentRefs() {
this._workbook.commentRefs.push({
commentName: `comments${this.id}`,
vmlDrawing: `vmlDrawing${this.id}`,
});
}
_writeOpen() {
this.commentsStream.write(
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
'<comments xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">' +
'<authors><author>Author</author></authors>' +
'<commentList>'
);
this.vmlStream.write(
'<?xml version="1.0" encoding="UTF-8"?>' +
'<xml xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:x="urn:schemas-microsoft-com:office:excel">' +
'<o:shapelayout v:ext="edit">' +
'<o:idmap v:ext="edit" data="1" />' +
'</o:shapelayout>' +
'<v:shapetype id="_x0000_t202" coordsize="21600,21600" o:spt="202" path="m,l,21600r21600,l21600,xe">' +
'<v:stroke joinstyle="miter" />' +
'<v:path gradientshapeok="t" o:connecttype="rect" />' +
'</v:shapetype>'
);
}
_writeComment(comment, index) {
const commentXform = new CommentXform();
const commentsXmlStream = new XmlStream();
commentXform.render(commentsXmlStream, comment);
this.commentsStream.write(commentsXmlStream.xml);
const vmlShapeXform = new VmlShapeXform();
const vmlXmlStream = new XmlStream();
vmlShapeXform.render(vmlXmlStream, comment, index);
this.vmlStream.write(vmlXmlStream.xml);
}
_writeClose() {
this.commentsStream.write('</commentList></comments>');
this.vmlStream.write('</xml>');
}
addComments(comments) {
if (comments && comments.length) {
if (!this.startedData) {
this._worksheet.comments = [];
this._writeOpen();
this._addRelationships();
this._addCommentRefs();
this.startedData = true;
}
comments.forEach(item => {
item.refAddress = colCache.decodeAddress(item.ref);
});
comments.forEach(comment => {
this._writeComment(comment, this.count);
this.count += 1;
});
}
}
commit() {
if (this.count) {
this._writeClose();
this.commentsStream.end();
this.vmlStream.end();
}
}
}
module.exports = SheetCommentsWriter;
+119
View File
@@ -0,0 +1,119 @@
/* eslint-disable max-classes-per-file */
const utils = require('../../utils/utils');
const RelType = require('../../xlsx/rel-type');
class HyperlinksProxy {
constructor(sheetRelsWriter) {
this.writer = sheetRelsWriter;
}
push(hyperlink) {
this.writer.addHyperlink(hyperlink);
}
}
class SheetRelsWriter {
constructor(options) {
// in a workbook, each sheet will have a number
this.id = options.id;
// count of all relationships
this.count = 0;
// keep record of all hyperlinks
this._hyperlinks = [];
this._workbook = options.workbook;
}
get stream() {
if (!this._stream) {
// eslint-disable-next-line no-underscore-dangle
this._stream = this._workbook._openStream(`/xl/worksheets/_rels/sheet${this.id}.xml.rels`);
}
return this._stream;
}
get length() {
return this._hyperlinks.length;
}
each(fn) {
return this._hyperlinks.forEach(fn);
}
get hyperlinksProxy() {
return this._hyperlinksProxy || (this._hyperlinksProxy = new HyperlinksProxy(this));
}
addHyperlink(hyperlink) {
// Write to stream
const relationship = {
Target: hyperlink.target,
Type: RelType.Hyperlink,
TargetMode: 'External',
};
const rId = this._writeRelationship(relationship);
// store sheet stuff for later
this._hyperlinks.push({
rId,
address: hyperlink.address,
});
}
addMedia(media) {
return this._writeRelationship(media);
}
addRelationship(rel) {
return this._writeRelationship(rel);
}
commit() {
if (this.count) {
// write xml utro
this._writeClose();
// and close stream
this.stream.end();
}
}
// ================================================================================
_writeOpen() {
this.stream.write(
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">`
);
}
_writeRelationship(relationship) {
if (!this.count) {
this._writeOpen();
}
const rId = `rId${++this.count}`;
if (relationship.TargetMode) {
this.stream.write(
`<Relationship Id="${rId}"` +
` Type="${relationship.Type}"` +
` Target="${utils.xmlEncode(relationship.Target)}"` +
` TargetMode="${relationship.TargetMode}"` +
'/>'
);
} else {
this.stream.write(
`<Relationship Id="${rId}" Type="${relationship.Type}" Target="${relationship.Target}"/>`
);
}
return rId;
}
_writeClose() {
this.stream.write('</Relationships>');
}
}
module.exports = SheetRelsWriter;
+337
View File
@@ -0,0 +1,337 @@
const fs = require('fs');
const {EventEmitter} = require('events');
const {PassThrough, Readable} = require('readable-stream');
const nodeStream = require('stream');
const unzip = require('unzipper');
const tmp = require('tmp');
const iterateStream = require('../../utils/iterate-stream');
const parseSax = require('../../utils/parse-sax');
const StyleManager = require('../../xlsx/xform/style/styles-xform');
const WorkbookXform = require('../../xlsx/xform/book/workbook-xform');
const RelationshipsXform = require('../../xlsx/xform/core/relationships-xform');
const WorksheetReader = require('./worksheet-reader');
const HyperlinkReader = require('./hyperlink-reader');
tmp.setGracefulCleanup();
class WorkbookReader extends EventEmitter {
constructor(input, options = {}) {
super();
this.input = input;
this.options = {
worksheets: 'emit',
sharedStrings: 'cache',
hyperlinks: 'ignore',
styles: 'ignore',
entries: 'ignore',
...options,
};
this.styles = new StyleManager();
this.styles.init();
}
_getStream(input) {
if (input instanceof nodeStream.Readable || input instanceof Readable) {
return input;
}
if (typeof input === 'string') {
return fs.createReadStream(input);
}
throw new Error(`Could not recognise input: ${input}`);
}
async read(input, options) {
try {
for await (const {eventType, value} of this.parse(input, options)) {
switch (eventType) {
case 'shared-strings':
this.emit(eventType, value);
break;
case 'worksheet':
this.emit(eventType, value);
await value.read();
break;
case 'hyperlinks':
this.emit(eventType, value);
break;
}
}
this.emit('end');
this.emit('finished');
} catch (error) {
this.emit('error', error);
}
}
async *[Symbol.asyncIterator]() {
for await (const {eventType, value} of this.parse()) {
if (eventType === 'worksheet') {
yield value;
}
}
}
async *parse(input, options) {
if (options) this.options = options;
const stream = (this.stream = this._getStream(input || this.input));
const zip = unzip.Parse({forceStream: true});
stream.pipe(zip);
// worksheets, deferred for parsing after shared strings reading
const waitingWorkSheets = [];
for await (const entry of iterateStream(zip)) {
let match;
let sheetNo;
switch (entry.path) {
case '_rels/.rels':
break;
case 'xl/_rels/workbook.xml.rels':
await this._parseRels(entry);
break;
case 'xl/workbook.xml':
await this._parseWorkbook(entry);
break;
case 'xl/sharedStrings.xml':
yield* this._parseSharedStrings(entry);
break;
case 'xl/styles.xml':
await this._parseStyles(entry);
break;
default:
if (entry.path.match(/xl\/worksheets\/sheet\d+[.]xml/)) {
match = entry.path.match(/xl\/worksheets\/sheet(\d+)[.]xml/);
sheetNo = match[1];
if (this.sharedStrings && this.workbookRels) {
yield* this._parseWorksheet(iterateStream(entry), sheetNo);
} else {
// create temp file for each worksheet
await new Promise((resolve, reject) => {
tmp.file((err, path, fd, tempFileCleanupCallback) => {
if (err) {
return reject(err);
}
waitingWorkSheets.push({sheetNo, path, tempFileCleanupCallback});
const tempStream = fs.createWriteStream(path);
tempStream.on('error', reject);
entry.pipe(tempStream);
return tempStream.on('finish', () => {
return resolve();
});
});
});
}
} else if (entry.path.match(/xl\/worksheets\/_rels\/sheet\d+[.]xml.rels/)) {
match = entry.path.match(/xl\/worksheets\/_rels\/sheet(\d+)[.]xml.rels/);
sheetNo = match[1];
yield* this._parseHyperlinks(iterateStream(entry), sheetNo);
}
break;
}
entry.autodrain();
}
for (const {sheetNo, path, tempFileCleanupCallback} of waitingWorkSheets) {
let fileStream = fs.createReadStream(path);
// TODO: Remove once node v8 is deprecated
// Detect and upgrade old fileStreams
if (!fileStream[Symbol.asyncIterator]) {
fileStream = fileStream.pipe(new PassThrough());
}
yield* this._parseWorksheet(fileStream, sheetNo);
tempFileCleanupCallback();
}
}
_emitEntry(payload) {
if (this.options.entries === 'emit') {
this.emit('entry', payload);
}
}
async _parseRels(entry) {
const xform = new RelationshipsXform();
this.workbookRels = await xform.parseStream(iterateStream(entry));
}
async _parseWorkbook(entry) {
this._emitEntry({type: 'workbook'});
const workbook = new WorkbookXform();
await workbook.parseStream(iterateStream(entry));
this.properties = workbook.map.workbookPr;
this.model = workbook.model;
}
async *_parseSharedStrings(entry) {
this._emitEntry({type: 'shared-strings'});
switch (this.options.sharedStrings) {
case 'cache':
this.sharedStrings = [];
break;
case 'emit':
break;
default:
return;
}
let text = null;
let richText = [];
let index = 0;
let font = null;
for await (const events of parseSax(iterateStream(entry))) {
for (const {eventType, value} of events) {
if (eventType === 'opentag') {
const node = value;
switch (node.name) {
case 'b':
font = font || {};
font.bold = true;
break;
case 'charset':
font = font || {};
font.charset = parseInt(node.attributes.charset, 10);
break;
case 'color':
font = font || {};
font.color = {};
if (node.attributes.rgb) {
font.color.argb = node.attributes.argb;
}
if (node.attributes.val) {
font.color.argb = node.attributes.val;
}
if (node.attributes.theme) {
font.color.theme = node.attributes.theme;
}
break;
case 'family':
font = font || {};
font.family = parseInt(node.attributes.val, 10);
break;
case 'i':
font = font || {};
font.italic = true;
break;
case 'outline':
font = font || {};
font.outline = true;
break;
case 'rFont':
font = font || {};
font.name = node.value;
break;
case 'si':
font = null;
richText = [];
text = null;
break;
case 'sz':
font = font || {};
font.size = parseInt(node.attributes.val, 10);
break;
case 'strike':
break;
case 't':
text = null;
break;
case 'u':
font = font || {};
font.underline = true;
break;
case 'vertAlign':
font = font || {};
font.vertAlign = node.attributes.val;
break;
}
} else if (eventType === 'text') {
text = text ? text + value : value;
} else if (eventType === 'closetag') {
const node = value;
switch (node.name) {
case 'r':
richText.push({
font,
text,
});
font = null;
text = null;
break;
case 'si':
if (this.options.sharedStrings === 'cache') {
this.sharedStrings.push(richText.length ? {richText} : text);
} else if (this.options.sharedStrings === 'emit') {
yield {index: index++, text: richText.length ? {richText} : text};
}
richText = [];
font = null;
text = null;
break;
}
}
}
}
}
async _parseStyles(entry) {
this._emitEntry({type: 'styles'});
if (this.options.styles === 'cache') {
this.styles = new StyleManager();
await this.styles.parseStream(iterateStream(entry));
}
}
*_parseWorksheet(iterator, sheetNo) {
this._emitEntry({type: 'worksheet', id: sheetNo});
const worksheetReader = new WorksheetReader({
workbook: this,
id: sheetNo,
iterator,
options: this.options,
});
const matchingRel = (this.workbookRels || []).find(rel => rel.Target === `worksheets/sheet${sheetNo}.xml`);
const matchingSheet = matchingRel && (this.model.sheets || []).find(sheet => sheet.rId === matchingRel.Id);
if (matchingSheet) {
worksheetReader.id = matchingSheet.id;
worksheetReader.name = matchingSheet.name;
worksheetReader.state = matchingSheet.state;
}
if (this.options.worksheets === 'emit') {
yield {eventType: 'worksheet', value: worksheetReader};
}
}
*_parseHyperlinks(iterator, sheetNo) {
this._emitEntry({type: 'hyperlinks', id: sheetNo});
const hyperlinksReader = new HyperlinkReader({
workbook: this,
id: sheetNo,
iterator,
options: this.options,
});
if (this.options.hyperlinks === 'emit') {
yield {eventType: 'hyperlinks', value: hyperlinksReader};
}
}
}
// for reference - these are the valid values for options
WorkbookReader.Options = {
worksheets: ['emit', 'ignore'],
sharedStrings: ['cache', 'emit', 'ignore'],
hyperlinks: ['cache', 'emit', 'ignore'],
styles: ['cache', 'ignore'],
entries: ['emit', 'ignore'],
};
module.exports = WorkbookReader;
+347
View File
@@ -0,0 +1,347 @@
const fs = require('fs');
const Archiver = require('archiver');
const StreamBuf = require('../../utils/stream-buf');
const RelType = require('../../xlsx/rel-type');
const StylesXform = require('../../xlsx/xform/style/styles-xform');
const SharedStrings = require('../../utils/shared-strings');
const DefinedNames = require('../../doc/defined-names');
const CoreXform = require('../../xlsx/xform/core/core-xform');
const RelationshipsXform = require('../../xlsx/xform/core/relationships-xform');
const ContentTypesXform = require('../../xlsx/xform/core/content-types-xform');
const AppXform = require('../../xlsx/xform/core/app-xform');
const WorkbookXform = require('../../xlsx/xform/book/workbook-xform');
const SharedStringsXform = require('../../xlsx/xform/strings/shared-strings-xform');
const WorksheetWriter = require('./worksheet-writer');
const theme1Xml = require('../../xlsx/xml/theme1.js');
class WorkbookWriter {
constructor(options) {
options = options || {};
this.created = options.created || new Date();
this.modified = options.modified || this.created;
this.creator = options.creator || 'ExcelJS';
this.lastModifiedBy = options.lastModifiedBy || 'ExcelJS';
this.lastPrinted = options.lastPrinted;
// using shared strings creates a smaller xlsx file but may use more memory
this.useSharedStrings = options.useSharedStrings || false;
this.sharedStrings = new SharedStrings();
// style manager
this.styles = options.useStyles ? new StylesXform(true) : new StylesXform.Mock(true);
// defined names
this._definedNames = new DefinedNames();
this._worksheets = [];
this.views = [];
this.zipOptions = options.zip;
this.media = [];
this.commentRefs = [];
this.zip = Archiver('zip', this.zipOptions);
if (options.stream) {
this.stream = options.stream;
} else if (options.filename) {
this.stream = fs.createWriteStream(options.filename);
} else {
this.stream = new StreamBuf();
}
this.zip.pipe(this.stream);
// these bits can be added right now
this.promise = Promise.all([this.addThemes(), this.addOfficeRels()]);
}
get definedNames() {
return this._definedNames;
}
_openStream(path) {
const stream = new StreamBuf({bufSize: 65536, batch: true});
this.zip.append(stream, {name: path});
stream.on('finish', () => {
stream.emit('zipped');
});
return stream;
}
_commitWorksheets() {
const commitWorksheet = function(worksheet) {
if (!worksheet.committed) {
return new Promise(resolve => {
worksheet.stream.on('zipped', () => {
resolve();
});
worksheet.commit();
});
}
return Promise.resolve();
};
// if there are any uncommitted worksheets, commit them now and wait
const promises = this._worksheets.map(commitWorksheet);
if (promises.length) {
return Promise.all(promises);
}
return Promise.resolve();
}
async commit() {
// commit all worksheets, then add suplimentary files
await this.promise;
await this.addMedia();
await this._commitWorksheets();
await Promise.all([
this.addContentTypes(),
this.addApp(),
this.addCore(),
this.addSharedStrings(),
this.addStyles(),
this.addWorkbookRels(),
]);
await this.addWorkbook();
return this._finalize();
}
get nextId() {
// find the next unique spot to add worksheet
let i;
for (i = 1; i < this._worksheets.length; i++) {
if (!this._worksheets[i]) {
return i;
}
}
return this._worksheets.length || 1;
}
addImage(image) {
const id = this.media.length;
const medium = Object.assign({}, image, {type: 'image', name: `image${id}.${image.extension}`});
this.media.push(medium);
return id;
}
getImage(id) {
return this.media[id];
}
addWorksheet(name, options) {
// it's possible to add a worksheet with different than default
// shared string handling
// in fact, it's even possible to switch it mid-sheet
options = options || {};
const useSharedStrings =
options.useSharedStrings !== undefined ? options.useSharedStrings : this.useSharedStrings;
if (options.tabColor) {
// eslint-disable-next-line no-console
console.trace('tabColor option has moved to { properties: tabColor: {...} }');
options.properties = Object.assign(
{
tabColor: options.tabColor,
},
options.properties
);
}
const id = this.nextId;
name = name || `sheet${id}`;
const worksheet = new WorksheetWriter({
id,
name,
workbook: this,
useSharedStrings,
properties: options.properties,
state: options.state,
pageSetup: options.pageSetup,
views: options.views,
autoFilter: options.autoFilter,
headerFooter: options.headerFooter,
});
this._worksheets[id] = worksheet;
return worksheet;
}
getWorksheet(id) {
if (id === undefined) {
return this._worksheets.find(() => true);
}
if (typeof id === 'number') {
return this._worksheets[id];
}
if (typeof id === 'string') {
return this._worksheets.find(worksheet => worksheet && worksheet.name === id);
}
return undefined;
}
addStyles() {
return new Promise(resolve => {
this.zip.append(this.styles.xml, {name: 'xl/styles.xml'});
resolve();
});
}
addThemes() {
return new Promise(resolve => {
this.zip.append(theme1Xml, {name: 'xl/theme/theme1.xml'});
resolve();
});
}
addOfficeRels() {
return new Promise(resolve => {
const xform = new RelationshipsXform();
const xml = xform.toXml([
{Id: 'rId1', Type: RelType.OfficeDocument, Target: 'xl/workbook.xml'},
{Id: 'rId2', Type: RelType.CoreProperties, Target: 'docProps/core.xml'},
{Id: 'rId3', Type: RelType.ExtenderProperties, Target: 'docProps/app.xml'},
]);
this.zip.append(xml, {name: '/_rels/.rels'});
resolve();
});
}
addContentTypes() {
return new Promise(resolve => {
const model = {
worksheets: this._worksheets.filter(Boolean),
sharedStrings: this.sharedStrings,
commentRefs: this.commentRefs,
media: this.media,
};
const xform = new ContentTypesXform();
const xml = xform.toXml(model);
this.zip.append(xml, {name: '[Content_Types].xml'});
resolve();
});
}
addMedia() {
return Promise.all(
this.media.map(medium => {
if (medium.type === 'image') {
const filename = `xl/media/${medium.name}`;
if (medium.filename) {
return this.zip.file(medium.filename, {name: filename});
}
if (medium.buffer) {
return this.zip.append(medium.buffer, {name: filename});
}
if (medium.base64) {
const dataimg64 = medium.base64;
const content = dataimg64.substring(dataimg64.indexOf(',') + 1);
return this.zip.append(content, {name: filename, base64: true});
}
}
throw new Error('Unsupported media');
})
);
}
addApp() {
return new Promise(resolve => {
const model = {
worksheets: this._worksheets.filter(Boolean),
};
const xform = new AppXform();
const xml = xform.toXml(model);
this.zip.append(xml, {name: 'docProps/app.xml'});
resolve();
});
}
addCore() {
return new Promise(resolve => {
const coreXform = new CoreXform();
const xml = coreXform.toXml(this);
this.zip.append(xml, {name: 'docProps/core.xml'});
resolve();
});
}
addSharedStrings() {
if (this.sharedStrings.count) {
return new Promise(resolve => {
const sharedStringsXform = new SharedStringsXform();
const xml = sharedStringsXform.toXml(this.sharedStrings);
this.zip.append(xml, {name: '/xl/sharedStrings.xml'});
resolve();
});
}
return Promise.resolve();
}
addWorkbookRels() {
let count = 1;
const relationships = [
{Id: `rId${count++}`, Type: RelType.Styles, Target: 'styles.xml'},
{Id: `rId${count++}`, Type: RelType.Theme, Target: 'theme/theme1.xml'},
];
if (this.sharedStrings.count) {
relationships.push({
Id: `rId${count++}`,
Type: RelType.SharedStrings,
Target: 'sharedStrings.xml',
});
}
this._worksheets.forEach(worksheet => {
if (worksheet) {
worksheet.rId = `rId${count++}`;
relationships.push({
Id: worksheet.rId,
Type: RelType.Worksheet,
Target: `worksheets/sheet${worksheet.id}.xml`,
});
}
});
return new Promise(resolve => {
const xform = new RelationshipsXform();
const xml = xform.toXml(relationships);
this.zip.append(xml, {name: '/xl/_rels/workbook.xml.rels'});
resolve();
});
}
addWorkbook() {
const {zip} = this;
const model = {
worksheets: this._worksheets.filter(Boolean),
definedNames: this._definedNames.model,
views: this.views,
properties: {},
calcProperties: {},
};
return new Promise(resolve => {
const xform = new WorkbookXform();
xform.prepare(model);
zip.append(xform.toXml(model), {name: '/xl/workbook.xml'});
resolve();
});
}
_finalize() {
return new Promise((resolve, reject) => {
this.stream.on('error', reject);
this.stream.on('finish', () => {
resolve(this);
});
this.zip.on('error', reject);
this.zip.finalize();
});
}
}
module.exports = WorkbookWriter;
+374
View File
@@ -0,0 +1,374 @@
const {EventEmitter} = require('events');
const parseSax = require('../../utils/parse-sax');
const _ = require('../../utils/under-dash');
const utils = require('../../utils/utils');
const colCache = require('../../utils/col-cache');
const Dimensions = require('../../doc/range');
const Row = require('../../doc/row');
const Column = require('../../doc/column');
class WorksheetReader extends EventEmitter {
constructor({workbook, id, iterator, options}) {
super();
this.workbook = workbook;
this.id = id;
this.iterator = iterator;
this.options = options || {};
// and a name
this.name = `Sheet${this.id}`;
// column definitions
this._columns = null;
this._keys = {};
// keep a record of dimensions
this._dimensions = new Dimensions();
}
// 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');
}
// return the current dimensions of the writer
get dimensions() {
return this._dimensions;
}
// =========================================================================
// Columns
// get the current columns array.
get columns() {
return this._columns;
}
// 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;
}
// otherise, 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];
}
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);
}
async read() {
try {
for await (const events of this.parse()) {
for (const {eventType, value} of events) {
this.emit(eventType, value);
}
}
this.emit('finished');
} catch (error) {
this.emit('error', error);
}
}
async *[Symbol.asyncIterator]() {
for await (const events of this.parse()) {
for (const {eventType, value} of events) {
if (eventType === 'row') {
yield value;
}
}
}
}
async *parse() {
const {iterator, options} = this;
let emitSheet = false;
let emitHyperlinks = false;
let hyperlinks = null;
switch (options.worksheets) {
case 'emit':
emitSheet = true;
break;
case 'prep':
break;
default:
break;
}
switch (options.hyperlinks) {
case 'emit':
emitHyperlinks = true;
break;
case 'cache':
this.hyperlinks = hyperlinks = {};
break;
default:
break;
}
if (!emitSheet && !emitHyperlinks && !hyperlinks) {
return;
}
// references
const {sharedStrings, styles, properties} = this.workbook;
// xml position
let inCols = false;
let inRows = false;
let inHyperlinks = false;
// parse state
let cols = null;
let row = null;
let c = null;
let current = null;
for await (const events of parseSax(iterator)) {
const worksheetEvents = [];
for (const {eventType, value} of events) {
if (eventType === 'opentag') {
const node = value;
if (emitSheet) {
switch (node.name) {
case 'cols':
inCols = true;
cols = [];
break;
case 'sheetData':
inRows = true;
break;
case 'col':
if (inCols) {
cols.push({
min: parseInt(node.attributes.min, 10),
max: parseInt(node.attributes.max, 10),
width: parseFloat(node.attributes.width),
styleId: parseInt(node.attributes.style || '0', 10),
});
}
break;
case 'row':
if (inRows) {
const r = parseInt(node.attributes.r, 10);
row = new Row(this, r);
if (node.attributes.ht) {
row.height = parseFloat(node.attributes.ht);
}
if (node.attributes.s) {
const styleId = parseInt(node.attributes.s, 10);
const style = styles.getStyleModel(styleId);
if (style) {
row.style = style;
}
}
}
break;
case 'c':
if (row) {
c = {
ref: node.attributes.r,
s: parseInt(node.attributes.s, 10),
t: node.attributes.t,
};
}
break;
case 'f':
if (c) {
current = c.f = {text: ''};
}
break;
case 'v':
if (c) {
current = c.v = {text: ''};
}
break;
case 'is':
case 't':
if (c) {
current = c.v = {text: ''};
}
break;
case 'mergeCell':
break;
default:
break;
}
}
// =================================================================
//
if (emitHyperlinks || hyperlinks) {
switch (node.name) {
case 'hyperlinks':
inHyperlinks = true;
break;
case 'hyperlink':
if (inHyperlinks) {
const hyperlink = {
ref: node.attributes.ref,
rId: node.attributes['r:id'],
};
if (emitHyperlinks) {
worksheetEvents.push({eventType: 'hyperlink', value: hyperlink});
} else {
hyperlinks[hyperlink.ref] = hyperlink;
}
}
break;
default:
break;
}
}
} else if (eventType === 'text') {
// only text data is for sheet values
if (emitSheet) {
if (current) {
current.text += value;
}
}
} else if (eventType === 'closetag') {
const node = value;
if (emitSheet) {
switch (node.name) {
case 'cols':
inCols = false;
this._columns = Column.fromModel(cols);
break;
case 'sheetData':
inRows = false;
break;
case 'row':
this._dimensions.expandRow(row);
worksheetEvents.push({eventType: 'row', value: row});
row = null;
break;
case 'c':
if (row && c) {
const address = colCache.decodeAddress(c.ref);
const cell = row.getCell(address.col);
if (c.s) {
const style = styles.getStyleModel(c.s);
if (style) {
cell.style = style;
}
}
if (c.f) {
const cellValue = {
formula: c.f.text,
};
if (c.v) {
if (c.t === 'str') {
cellValue.result = utils.xmlDecode(c.v.text);
} else {
cellValue.result = parseFloat(c.v.text);
}
}
cell.value = cellValue;
} else if (c.v) {
switch (c.t) {
case 's': {
const index = parseInt(c.v.text, 10);
if (sharedStrings) {
cell.value = sharedStrings[index];
} else {
cell.value = {
sharedString: index,
};
}
break;
}
case 'inlineStr':
case 'str':
cell.value = utils.xmlDecode(c.v.text);
break;
case 'e':
cell.value = {error: c.v.text};
break;
case 'b':
cell.value = parseInt(c.v.text, 10) !== 0;
break;
default:
if (utils.isDateFmt(cell.numFmt)) {
cell.value = utils.excelToDate(
parseFloat(c.v.text),
properties.model && properties.model.date1904
);
} else {
cell.value = parseFloat(c.v.text);
}
break;
}
}
if (hyperlinks) {
const hyperlink = hyperlinks[c.ref];
if (hyperlink) {
cell.text = cell.value;
cell.value = undefined;
cell.hyperlink = hyperlink;
}
}
c = null;
}
break;
default:
break;
}
}
if (emitHyperlinks || hyperlinks) {
switch (node.name) {
case 'hyperlinks':
inHyperlinks = false;
break;
default:
break;
}
}
}
}
if (worksheetEvents.length > 0) {
yield worksheetEvents;
}
}
}
}
module.exports = WorksheetReader;
+717
View File
@@ -0,0 +1,717 @@
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;