From c140ab9b2cfc17c8ccbfbe192658369262624b79 Mon Sep 17 00:00:00 2001 From: bruno Date: Sat, 3 Aug 2019 21:16:24 -0400 Subject: [PATCH 1/2] Introducing comments for the Stream Writer --- lib/stream/xlsx/sheet-comments-writer.js | 121 +++++++++++++++++++++++ lib/stream/xlsx/sheet-rels-writer.js | 4 + lib/stream/xlsx/workbook-writer.js | 2 + lib/stream/xlsx/worksheet-writer.js | 39 ++++++-- test/test-comment-stream-writer.js | 35 +++++++ 5 files changed, 194 insertions(+), 7 deletions(-) create mode 100644 lib/stream/xlsx/sheet-comments-writer.js create mode 100644 test/test-comment-stream-writer.js diff --git a/lib/stream/xlsx/sheet-comments-writer.js b/lib/stream/xlsx/sheet-comments-writer.js new file mode 100644 index 000000000..316673a02 --- /dev/null +++ b/lib/stream/xlsx/sheet-comments-writer.js @@ -0,0 +1,121 @@ +'use strict'; + +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 VmlNoteXform = require('../../xlsx/xform/comment/vml-note-xform'); + +const SheetCommentsWriter = (module.exports = function(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; +}); + +SheetCommentsWriter.prototype = { + 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( + '' + + '' + + 'Author' + + '' + ); + this.vmlStream.write( + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + ); + }, + + _writeComment(comment, index) { + const commentXform = new CommentXform(); + const commentsXmlStream = new XmlStream(); + commentXform.render(commentsXmlStream, comment); + this.commentsStream.write(commentsXmlStream.xml); + + const vmlNoteXform = new VmlNoteXform(); + const vmlXmlStream = new XmlStream(); + vmlNoteXform.render(vmlXmlStream, comment, index); + this.vmlStream.write(vmlXmlStream.xml); + }, + + _writeClose() { + this.commentsStream.write(''); + this.vmlStream.write(''); + }, + + 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(); + } + }, + +}; diff --git a/lib/stream/xlsx/sheet-rels-writer.js b/lib/stream/xlsx/sheet-rels-writer.js index fbfcc365d..29446003e 100644 --- a/lib/stream/xlsx/sheet-rels-writer.js +++ b/lib/stream/xlsx/sheet-rels-writer.js @@ -64,6 +64,10 @@ SheetRelsWriter.prototype = { return this._writeRelationship(media); }, + addRelationship(rel) { + return this._writeRelationship(rel); + }, + commit() { if (this.count) { // write xml utro diff --git a/lib/stream/xlsx/workbook-writer.js b/lib/stream/xlsx/workbook-writer.js index c881fab4a..e081423ce 100644 --- a/lib/stream/xlsx/workbook-writer.js +++ b/lib/stream/xlsx/workbook-writer.js @@ -47,6 +47,7 @@ const WorkbookWriter = (module.exports = function(options) { this.zipOptions = options.zip; this.media = []; + this.commentRefs = []; this.zip = Archiver('zip', this.zipOptions); if (options.stream) { @@ -197,6 +198,7 @@ WorkbookWriter.prototype = { const model = { worksheets: this._worksheets.filter(Boolean), sharedStrings: this.sharedStrings, + commentRefs: this.commentRefs, }; const xform = new ContentTypesXform(); const xml = xform.toXml(model); diff --git a/lib/stream/xlsx/worksheet-writer.js b/lib/stream/xlsx/worksheet-writer.js index 62087738f..25a7eaf7d 100644 --- a/lib/stream/xlsx/worksheet-writer.js +++ b/lib/stream/xlsx/worksheet-writer.js @@ -13,6 +13,7 @@ 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(); @@ -37,10 +38,10 @@ const xform = { dataValidations: new DataValidationsXform(), sheetProperties: new SheetPropertiesXform(), sheetFormatProperties: new SheetFormatPropertiesXform(), - columns: new ListXform({ tag: 'cols', length: false, childXform: new ColXform() }), + 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() }), + hyperlinks: new ListXform({tag: 'hyperlinks', length: false, childXform: new HyperlinkXform()}), + sheetViews: new ListXform({tag: 'sheetViews', length: false, childXform: new SheetViewXform()}), pageMargins: new PageMarginsXform(), pageSeteup: new PageSetupXform(), autoFilter: new AutoFilterXform(), @@ -76,6 +77,9 @@ const WorksheetWriter = (module.exports = function(options) { // 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(); @@ -108,7 +112,7 @@ const WorksheetWriter = (module.exports = function(options) { this.pageSetup = Object.assign( {}, { - margins: { left: 0.7, right: 0.7, top: 0.75, bottom: 0.75, header: 0.3, footer: 0.3 }, + 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, @@ -137,6 +141,8 @@ const WorksheetWriter = (module.exports = function(options) { this._workbook = options.workbook; + this.hasComments = false; + // views this._views = options.views || []; @@ -198,6 +204,7 @@ WorksheetWriter.prototype = { // we _cannot_ accept new rows from now on this._rows = null; + if (!this.startedData) { this._writeOpenSheetData(); } @@ -213,11 +220,15 @@ WorksheetWriter.prototype = { this._writePageMargins(); this._writePageSetup(); this._writeBackground(); + + // 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(); @@ -468,7 +479,7 @@ WorksheetWriter.prototype = { _writeColumns() { const cols = Column.toModel(this.columns); if (cols) { - xform.columns.prepare(cols, { styles: this._workbook.styles }); + xform.columns.prepare(cols, {styles: this._workbook.styles}); this.stream.write(xform.columns.toXml(cols)); } }, @@ -483,7 +494,7 @@ WorksheetWriter.prototype = { } if (row.hasValues || row.height) { - const { model } = row; + const {model} = row; const options = { styles: this._workbook.styles, sharedStrings: this.useSharedStrings ? this._workbook.sharedStrings : undefined, @@ -491,9 +502,16 @@ WorksheetWriter.prototype = { 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() { @@ -532,6 +550,13 @@ WorksheetWriter.prototype = { this.stream.write(xform.picture.toXml(this._background)); } }, + _writeLegacyData() { + if(this._sheetCommentsWriter.count){ + xmlBuffer.reset(); + xmlBuffer.addText(``); + 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. diff --git a/test/test-comment-stream-writer.js b/test/test-comment-stream-writer.js new file mode 100644 index 000000000..61a150961 --- /dev/null +++ b/test/test-comment-stream-writer.js @@ -0,0 +1,35 @@ +const Excel = require('../lib/exceljs.nodejs.js'); +const HrStopwatch = require('./utils/hr-stopwatch'); + +const [, , filename] = process.argv; + +const wb = new Excel.stream.xlsx.WorkbookWriter({filename}); +const ws = wb.addWorksheet('Foo'); +ws.getCell('B2').value = 5; +ws.getCell('B2').note = { + texts: [ + {'font': {'size': 12, 'color': {'theme': 0}, 'name': 'Calibri', 'family': 2, 'scheme': 'minor'}, 'text': 'This is '}, + {'font': {'italic': true, 'size': 12, 'color': {'theme': 0}, 'name': 'Calibri', 'scheme': 'minor'}, 'text': 'a'}, + {'font': {'size': 12, 'color': {'theme': 1}, 'name': 'Calibri', 'family': 2, 'scheme': 'minor'}, 'text': ' '}, + {'font': {'size': 12, 'color': {'argb': 'FFFF6600'}, 'name': 'Calibri', 'scheme': 'minor'}, 'text': 'colorful'}, + {'font': {'size': 12, 'color': {'theme': 1}, 'name': 'Calibri', 'family': 2, 'scheme': 'minor'}, 'text': ' text '}, + {'font': {'size': 12, 'color': {'argb': 'FFCCFFCC'}, 'name': 'Calibri', 'scheme': 'minor'}, 'text': 'with'}, + {'font': {'size': 12, 'color': {'theme': 1}, 'name': 'Calibri', 'family': 2, 'scheme': 'minor'}, 'text': ' in-cell '}, + {'font': {'bold': true, 'size': 12, 'color': {'theme': 1}, 'name': 'Calibri', 'family': 2, 'scheme': 'minor'}, 'text': 'format'}, + ], +}; + +ws.getCell('D2').value = 'Zoo'; +ws.getCell('D2').note = 'Plain Text Comment'; + +const stopwatch = new HrStopwatch(); +stopwatch.start(); +wb.commit() + .then(() => { + const micros = stopwatch.microseconds; + console.log('Done.'); + console.log('Time taken:', micros); + }) + .catch(error => { + console.log(error.message); + }); From 1321237879fee35bc70a88a0bce59dd9a2f5fdea Mon Sep 17 00:00:00 2001 From: bruno Date: Sun, 3 Nov 2019 00:01:56 -0400 Subject: [PATCH 2/2] Added background images, includes test --- .gitignore | 3 ++ lib/stream/xlsx/sheet-rels-writer.js | 4 --- lib/stream/xlsx/workbook-writer.js | 44 +++++++++++++++++++++++++++- lib/stream/xlsx/worksheet-writer.js | 42 ++++++++++++++++++-------- test/test-image-stream-writer.js | 28 ++++++++++++++++++ 5 files changed, 103 insertions(+), 18 deletions(-) create mode 100644 test/test-image-stream-writer.js diff --git a/.gitignore b/.gitignore index 2810f3742..032779be9 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,9 @@ tmp/* # WebStorm .idea +# VS Code +.vscode + # Intellij IDEA exceljs.iml diff --git a/lib/stream/xlsx/sheet-rels-writer.js b/lib/stream/xlsx/sheet-rels-writer.js index 27524a95e..28fb8b92a 100644 --- a/lib/stream/xlsx/sheet-rels-writer.js +++ b/lib/stream/xlsx/sheet-rels-writer.js @@ -70,10 +70,6 @@ class SheetRelsWriter { return this._writeRelationship(rel); } - addRelationship(rel) { - return this._writeRelationship(rel); - }, - commit() { if (this.count) { // write xml utro diff --git a/lib/stream/xlsx/workbook-writer.js b/lib/stream/xlsx/workbook-writer.js index 2cdd7f950..8e412566d 100644 --- a/lib/stream/xlsx/workbook-writer.js +++ b/lib/stream/xlsx/workbook-writer.js @@ -97,8 +97,16 @@ class WorkbookWriter { 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 Promise.all([ + this.addContentTypes(), + this.addApp(), + this.addCore(), + this.addSharedStrings(), + this.addStyles(), + this.addWorkbookRels(), + ]); await this.addWorkbook(); return this._finalize(); } @@ -114,6 +122,16 @@ class WorkbookWriter { return this._worksheets.length || 1; } + addImage(image) { + const id = this.media.length; + this.media.push(Object.assign({}, image, {type: 'image'})); + 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 @@ -197,6 +215,7 @@ class WorkbookWriter { worksheets: this._worksheets.filter(Boolean), sharedStrings: this.sharedStrings, commentRefs: this.commentRefs, + media: this.media, }; const xform = new ContentTypesXform(); const xml = xform.toXml(model); @@ -205,6 +224,29 @@ class WorkbookWriter { }); } + addMedia() { + return Promise.all( + this.media.map((medium, idx) => { + medium.name = `image${idx+1}.${medium.extension}`; + 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 = { diff --git a/lib/stream/xlsx/worksheet-writer.js b/lib/stream/xlsx/worksheet-writer.js index 499cc6e6f..930bb0143 100644 --- a/lib/stream/xlsx/worksheet-writer.js +++ b/lib/stream/xlsx/worksheet-writer.js @@ -150,21 +150,11 @@ class WorksheetWriter { // auto filter this.autoFilter = options.autoFilter || null; + this._media = []; + // start writing to stream now this._writeOpenWorksheet(); - // background - if (options.background && options.background.type === 'image') { - const imageName = this._workbook.addMedia(options.background); - const pictureId = this._sheetRelsWriter.addMedia({ - Target: `../media/${imageName}`, - Type: RelType.Image, - }); - this._background = { - rId: pictureId, - }; - } - this.startedData = false; } @@ -429,8 +419,21 @@ class WorksheetWriter { this._merges.push(dimensions); } + // ========================================================================= + + addBackgroundImage(imageId) { + this._background = { + imageId, + }; + } + + getBackgroundImageId() { + return this._background && this._background.imageId; + } + // ================================================================================ + _write(text) { xmlBuffer.reset(); xmlBuffer.addText(text); @@ -571,7 +574,20 @@ class WorksheetWriter { _writeBackground() { if (this._background) { - this.stream.write(xform.picture.toXml(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})); } } diff --git a/test/test-image-stream-writer.js b/test/test-image-stream-writer.js new file mode 100644 index 000000000..d5f30af59 --- /dev/null +++ b/test/test-image-stream-writer.js @@ -0,0 +1,28 @@ +const path = require('path'); +const Excel = require('../lib/exceljs.nodejs.js'); +const HrStopwatch = require('./utils/hr-stopwatch'); + +const [, , filename] = process.argv; + +const wb = new Excel.stream.xlsx.WorkbookWriter({filename}); + +const imageId = wb.addImage({ + filename: path.join(__dirname, 'data/image2.png'), + extension: 'png', +}); + +const ws = wb.addWorksheet('Foo'); +ws.addBackgroundImage(imageId); + +const stopwatch = new HrStopwatch(); +stopwatch.start(); + +wb.commit() +.then(() => { + const micros = stopwatch.microseconds; + console.log('Done.'); + console.log('Time taken:', micros); +}) +.catch(error => { + console.log(error.message); +}); pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy