From ad7c87b012a7f34868a43340019d1656977332b3 Mon Sep 17 00:00:00 2001 From: KesavaPradhaR Date: Fri, 3 Nov 2023 17:10:18 +0530 Subject: [PATCH] fix: add image --- lib/doc/anchor.js | 156 +++++++------- lib/doc/image.js | 108 +++++----- lib/doc/workbook.js | 24 +-- lib/doc/worksheet.js | 52 ++--- lib/utils/u-zip.js | 4 +- lib/xlsx/xform/book/workbook-xform.js | 14 +- lib/xlsx/xform/core/content-types-xform.js | 20 +- .../xform/drawing/base-cell-anchor-xform.js | 24 +-- .../xform/drawing/one-cell-anchor-xform.js | 2 +- .../xform/drawing/two-cell-anchor-xform.js | 2 +- lib/xlsx/xform/sheet/worksheet-xform.js | 200 +++++++++--------- lib/xlsx/xlsx.js | 51 ++++- package.json | 3 +- 13 files changed, 353 insertions(+), 307 deletions(-) diff --git a/lib/doc/anchor.js b/lib/doc/anchor.js index 583112f9f..9aa5f70e2 100644 --- a/lib/doc/anchor.js +++ b/lib/doc/anchor.js @@ -1,91 +1,91 @@ -// 'use strict'; +'use strict'; -// const colCache = require('../utils/col-cache'); +const colCache = require('../utils/col-cache'); -// class Anchor { -// constructor(worksheet, address, offset = 0) { -// if (!address) { -// this.nativeCol = 0; -// this.nativeColOff = 0; -// this.nativeRow = 0; -// this.nativeRowOff = 0; -// } else if (typeof address === 'string') { -// const decoded = colCache.decodeAddress(address); -// this.nativeCol = decoded.col + offset; -// this.nativeColOff = 0; -// this.nativeRow = decoded.row + offset; -// this.nativeRowOff = 0; -// } else if (address.nativeCol !== undefined) { -// this.nativeCol = address.nativeCol || 0; -// this.nativeColOff = address.nativeColOff || 0; -// this.nativeRow = address.nativeRow || 0; -// this.nativeRowOff = address.nativeRowOff || 0; -// } else if (address.col !== undefined) { -// this.col = address.col + offset; -// this.row = address.row + offset; -// } else { -// this.nativeCol = 0; -// this.nativeColOff = 0; -// this.nativeRow = 0; -// this.nativeRowOff = 0; -// } +class Anchor { + constructor(worksheet, address, offset = 0) { + if (!address) { + this.nativeCol = 0; + this.nativeColOff = 0; + this.nativeRow = 0; + this.nativeRowOff = 0; + } else if (typeof address === 'string') { + const decoded = colCache.decodeAddress(address); + this.nativeCol = decoded.col + offset; + this.nativeColOff = 0; + this.nativeRow = decoded.row + offset; + this.nativeRowOff = 0; + } else if (address.nativeCol !== undefined) { + this.nativeCol = address.nativeCol || 0; + this.nativeColOff = address.nativeColOff || 0; + this.nativeRow = address.nativeRow || 0; + this.nativeRowOff = address.nativeRowOff || 0; + } else if (address.col !== undefined) { + this.col = address.col + offset; + this.row = address.row + offset; + } else { + this.nativeCol = 0; + this.nativeColOff = 0; + this.nativeRow = 0; + this.nativeRowOff = 0; + } -// this.worksheet = worksheet; -// } + this.worksheet = worksheet; + } -// static asInstance(model) { -// return model instanceof Anchor || model == null ? model : new Anchor(model); -// } + static asInstance(model) { + return model instanceof Anchor || model == null ? model : new Anchor(model); + } -// get col() { -// return this.nativeCol + (Math.min(this.colWidth - 1, this.nativeColOff) / this.colWidth); -// } + get col() { + return this.nativeCol + (Math.min(this.colWidth - 1, this.nativeColOff) / this.colWidth); + } -// set col(v) { -// this.nativeCol = Math.floor(v); -// this.nativeColOff = Math.floor((v - this.nativeCol) * this.colWidth); -// } + set col(v) { + this.nativeCol = Math.floor(v); + this.nativeColOff = Math.floor((v - this.nativeCol) * this.colWidth); + } -// get row() { -// return this.nativeRow + (Math.min(this.rowHeight - 1, this.nativeRowOff) / this.rowHeight); -// } + get row() { + return this.nativeRow + (Math.min(this.rowHeight - 1, this.nativeRowOff) / this.rowHeight); + } -// set row(v) { -// this.nativeRow = Math.floor(v); -// this.nativeRowOff = Math.floor((v - this.nativeRow) * this.rowHeight); -// } + set row(v) { + this.nativeRow = Math.floor(v); + this.nativeRowOff = Math.floor((v - this.nativeRow) * this.rowHeight); + } -// get colWidth() { -// return this.worksheet && -// this.worksheet.getColumn(this.nativeCol + 1) && -// this.worksheet.getColumn(this.nativeCol + 1).isCustomWidth -// ? Math.floor(this.worksheet.getColumn(this.nativeCol + 1).width * 10000) -// : 640000; -// } + get colWidth() { + return this.worksheet && + this.worksheet.getColumn(this.nativeCol + 1) && + this.worksheet.getColumn(this.nativeCol + 1).isCustomWidth + ? Math.floor(this.worksheet.getColumn(this.nativeCol + 1).width * 10000) + : 640000; + } -// get rowHeight() { -// return this.worksheet && -// this.worksheet.getRow(this.nativeRow + 1) && -// this.worksheet.getRow(this.nativeRow + 1).height -// ? Math.floor(this.worksheet.getRow(this.nativeRow + 1).height * 10000) -// : 180000; -// } + get rowHeight() { + return this.worksheet && + this.worksheet.getRow(this.nativeRow + 1) && + this.worksheet.getRow(this.nativeRow + 1).height + ? Math.floor(this.worksheet.getRow(this.nativeRow + 1).height * 10000) + : 180000; + } -// get model() { -// return { -// nativeCol: this.nativeCol, -// nativeColOff: this.nativeColOff, -// nativeRow: this.nativeRow, -// nativeRowOff: this.nativeRowOff, -// }; -// } + get model() { + return { + nativeCol: this.nativeCol, + nativeColOff: this.nativeColOff, + nativeRow: this.nativeRow, + nativeRowOff: this.nativeRowOff, + }; + } -// set model(value) { -// this.nativeCol = value.nativeCol; -// this.nativeColOff = value.nativeColOff; -// this.nativeRow = value.nativeRow; -// this.nativeRowOff = value.nativeRowOff; -// } -// } + set model(value) { + this.nativeCol = value.nativeCol; + this.nativeColOff = value.nativeColOff; + this.nativeRow = value.nativeRow; + this.nativeRowOff = value.nativeRowOff; + } +} -// module.exports = Anchor; +module.exports = Anchor; diff --git a/lib/doc/image.js b/lib/doc/image.js index 38933d7d7..beefae839 100644 --- a/lib/doc/image.js +++ b/lib/doc/image.js @@ -1,59 +1,59 @@ -// const colCache = require('../utils/col-cache'); -// const Anchor = require('./anchor'); +const colCache = require('../utils/col-cache'); +const Anchor = require('./anchor'); -// class Image { -// constructor(worksheet, model) { -// this.worksheet = worksheet; -// this.model = model; -// } +class Image { + constructor(worksheet, model) { + this.worksheet = worksheet; + this.model = model; + } -// get model() { -// switch (this.type) { -// case 'background': -// return { -// type: this.type, -// imageId: this.imageId, -// }; -// case 'image': -// return { -// type: this.type, -// imageId: this.imageId, -// hyperlinks: this.range.hyperlinks, -// range: { -// tl: this.range.tl.model, -// br: this.range.br && this.range.br.model, -// ext: this.range.ext, -// editAs: this.range.editAs, -// }, -// }; -// default: -// throw new Error('Invalid Image Type'); -// } -// } + get model() { + switch (this.type) { + case 'background': + return { + type: this.type, + imageId: this.imageId, + }; + case 'image': + return { + type: this.type, + imageId: this.imageId, + hyperlinks: this.range.hyperlinks, + range: { + tl: this.range.tl.model, + br: this.range.br && this.range.br.model, + ext: this.range.ext, + editAs: this.range.editAs, + }, + }; + default: + throw new Error('Invalid Image Type'); + } + } -// set model({type, imageId, range, hyperlinks}) { -// this.type = type; -// this.imageId = imageId; + set model({type, imageId, range, hyperlinks}) { + this.type = type; + this.imageId = imageId; -// if (type === 'image') { -// if (typeof range === 'string') { -// const decoded = colCache.decode(range); -// this.range = { -// tl: new Anchor(this.worksheet, {col: decoded.left, row: decoded.top}, -1), -// br: new Anchor(this.worksheet, {col: decoded.right, row: decoded.bottom}, 0), -// editAs: 'oneCell', -// }; -// } else { -// this.range = { -// tl: new Anchor(this.worksheet, range.tl, 0), -// br: range.br && new Anchor(this.worksheet, range.br, 0), -// ext: range.ext, -// editAs: range.editAs, -// hyperlinks: hyperlinks || range.hyperlinks, -// }; -// } -// } -// } -// } + if (type === 'image') { + if (typeof range === 'string') { + const decoded = colCache.decode(range); + this.range = { + tl: new Anchor(this.worksheet, {col: decoded.left, row: decoded.top}, -1), + br: new Anchor(this.worksheet, {col: decoded.right, row: decoded.bottom}, 0), + editAs: 'oneCell', + }; + } else { + this.range = { + tl: new Anchor(this.worksheet, range.tl, 0), + br: range.br && new Anchor(this.worksheet, range.br, 0), + ext: range.ext, + editAs: range.editAs, + hyperlinks: hyperlinks || range.hyperlinks, + }; + } + } + } +} -// module.exports = Image; +module.exports = Image; diff --git a/lib/doc/workbook.js b/lib/doc/workbook.js index cc8e6f30d..c1c06f2c1 100644 --- a/lib/doc/workbook.js +++ b/lib/doc/workbook.js @@ -25,7 +25,7 @@ class Workbook { this.subject = ''; this.title = ''; // this.views = []; - // this.media = []; + this.media = []; // this._definedNames = new DefinedNames(); } @@ -153,16 +153,16 @@ class Workbook { // this._themes = undefined; // } - // addImage(image) { - // // TODO: validation? - // const id = this.media.length; - // this.media.push(Object.assign({}, image, {type: 'image'})); - // return id; - // } + addImage(image) { + // TODO: validation? + const id = this.media.length; + this.media.push(Object.assign({}, image, {type: 'image'})); + return id; + } - // getImage(id) { - // return this.media[id]; - // } + getImage(id) { + return this.media[id]; + } get model() { return { @@ -187,7 +187,7 @@ class Workbook { revision: this.revision, contentStatus: this.contentStatus, // themes: this._themes, - // media: this.media, + media: this.media, calcProperties: this.calcProperties, }; } @@ -229,7 +229,7 @@ class Workbook { // this._definedNames.model = value.definedNames; // this.views = value.views; // this._themes = value.themes; - // this.media = value.media || []; + this.media = value.media || []; } } diff --git a/lib/doc/worksheet.js b/lib/doc/worksheet.js index 1b5015302..e40659c53 100644 --- a/lib/doc/worksheet.js +++ b/lib/doc/worksheet.js @@ -5,7 +5,7 @@ const Range = require('./range'); const Row = require('./row'); const Column = require('./column'); // const Enums = require('./enums'); -// const Image = require('./image'); +const Image = require('./image'); const Table = require('./table'); const DataValidations = require('./data-validations'); // const Encryptor = require('../utils/encryptor'); @@ -117,7 +117,7 @@ class Worksheet { // this.autoFilter = options.autoFilter || null; // for images, etc - // this._media = []; + this._media = []; // worksheet protection // this.sheetProtection = null; @@ -674,31 +674,31 @@ class Worksheet { // ========================================================================= // Images - // addImage(imageId, range) { - // const model = { - // type: 'image', - // imageId, - // range, - // }; - // this._media.push(new Image(this, model)); - // } + addImage(imageId, range) { + const model = { + type: 'image', + imageId, + range, + }; + this._media.push(new Image(this, model)); + } - // getImages() { - // return this._media.filter(m => m.type === 'image'); - // } + getImages() { + return this._media.filter(m => m.type === 'image'); + } - // addBackgroundImage(imageId) { - // const model = { - // type: 'background', - // imageId, - // }; - // this._media.push(new Image(this, model)); - // } + addBackgroundImage(imageId) { + const model = { + type: 'background', + imageId, + }; + this._media.push(new Image(this, model)); + } - // getBackgroundImageId() { - // const image = this._media.find(m => m.type === 'background'); - // return image && image.imageId; - // } + getBackgroundImageId() { + const image = this._media.find(m => m.type === 'background'); + return image && image.imageId; + } // ========================================================================= // Worksheet Protection @@ -803,7 +803,7 @@ class Worksheet { // rowBreaks: this.rowBreaks, views: this.views, // autoFilter: this.autoFilter, - // media: this._media.map(medium => medium.model), + media: this._media.map(medium => medium.model), // sheetProtection: this.sheetProtection, tables: Object.values(this.tables).map(table => table.model), conditionalFormattings: this.conditionalFormattings, @@ -864,7 +864,7 @@ class Worksheet { this.headerFooter = value.headerFooter; this.views = value.views; // this.autoFilter = value.autoFilter; - // this._media = value.media.map(medium => new Image(this, medium)); + this._media = value.media.map(medium => new Image(this, medium)); // this.sheetProtection = value.sheetProtection; this.tables = value.tables.reduce((tables, table) => { const t = new Table(); diff --git a/lib/utils/u-zip.js b/lib/utils/u-zip.js index 11f8250dc..df1826da6 100644 --- a/lib/utils/u-zip.js +++ b/lib/utils/u-zip.js @@ -11,7 +11,9 @@ class UZipWriter extends events.EventEmitter { } append(data, options) { - if (typeof data === 'string') { + if(options.base64) { + this.files[options.name]= Buffer.from(data, 'base64'); + } else if (typeof data === 'string') { this.files[options.name] = stringToBuffer(data); } else { this.files[options.name] = new Uint8Array(data); diff --git a/lib/xlsx/xform/book/workbook-xform.js b/lib/xlsx/xform/book/workbook-xform.js index 08684aaf3..b57c72e6f 100644 --- a/lib/xlsx/xform/book/workbook-xform.js +++ b/lib/xlsx/xform/book/workbook-xform.js @@ -84,10 +84,10 @@ class WorkbookXform extends BaseXform { model.definedNames = (model.definedNames || []).concat(printAreas); } - // (model.media || []).forEach((medium, i) => { - // // assign name - // medium.name = medium.type + (i + 1); - // }); + (model.media || []).forEach((medium, i) => { + // assign name + medium.name = medium.type + (i + 1); + }); } render(xmlStream, model) { @@ -233,9 +233,9 @@ class WorkbookXform extends BaseXform { // model.definedNames = definedNames; // used by sheets to build their image models - // model.media.forEach((media, i) => { - // media.index = i; - // }); + model.media.forEach((media, i) => { + media.index = i; + }); } } diff --git a/lib/xlsx/xform/core/content-types-xform.js b/lib/xlsx/xform/core/content-types-xform.js index 8fa66657e..cbec136ef 100644 --- a/lib/xlsx/xform/core/content-types-xform.js +++ b/lib/xlsx/xform/core/content-types-xform.js @@ -10,16 +10,16 @@ class ContentTypesXform extends BaseXform { xmlStream.openNode('Types', ContentTypesXform.PROPERTY_ATTRIBUTES); - // const mediaHash = {}; - // (model.media || []).forEach(medium => { - // if (medium.type === 'image') { - // const imageType = medium.extension; - // if (!mediaHash[imageType]) { - // mediaHash[imageType] = true; - // xmlStream.leafNode('Default', {Extension: imageType, ContentType: `image/${imageType}`}); - // } - // } - // }); + const mediaHash = {}; + (model.media || []).forEach(medium => { + if (medium.type === 'image') { + const imageType = medium.extension; + if (!mediaHash[imageType]) { + mediaHash[imageType] = true; + xmlStream.leafNode('Default', {Extension: imageType, ContentType: `image/${imageType}`}); + } + } + }); xmlStream.leafNode('Default', { Extension: 'rels', diff --git a/lib/xlsx/xform/drawing/base-cell-anchor-xform.js b/lib/xlsx/xform/drawing/base-cell-anchor-xform.js index a7015e978..7972fb4f1 100644 --- a/lib/xlsx/xform/drawing/base-cell-anchor-xform.js +++ b/lib/xlsx/xform/drawing/base-cell-anchor-xform.js @@ -31,18 +31,18 @@ class BaseCellAnchorXform extends BaseXform { } } - // reconcilePicture(model, options) { - // if (model && model.rId) { - // const rel = options.rels[model.rId]; - // const match = rel.Target.match(/.*\/media\/(.+[.][a-zA-Z]{3,4})/); - // if (match) { - // const name = match[1]; - // const mediaId = options.mediaIndex[name]; - // return options.media[mediaId]; - // } - // } - // return undefined; - // } + reconcilePicture(model, options) { + if (model && model.rId) { + const rel = options.rels[model.rId]; + const match = rel.Target.match(/.*\/media\/(.+[.][a-zA-Z]{3,4})/); + if (match) { + const name = match[1]; + const mediaId = options.mediaIndex[name]; + return options.media[mediaId]; + } + } + return undefined; + } } module.exports = BaseCellAnchorXform; diff --git a/lib/xlsx/xform/drawing/one-cell-anchor-xform.js b/lib/xlsx/xform/drawing/one-cell-anchor-xform.js index 1bbdba9a9..dab78db7e 100644 --- a/lib/xlsx/xform/drawing/one-cell-anchor-xform.js +++ b/lib/xlsx/xform/drawing/one-cell-anchor-xform.js @@ -56,7 +56,7 @@ class OneCellAnchorXform extends BaseCellAnchorXform { } reconcile(model, options) { - // model.medium = this.reconcilePicture(model.picture, options); + model.medium = this.reconcilePicture(model.picture, options); } } diff --git a/lib/xlsx/xform/drawing/two-cell-anchor-xform.js b/lib/xlsx/xform/drawing/two-cell-anchor-xform.js index a1ed9f117..bc567e3b7 100644 --- a/lib/xlsx/xform/drawing/two-cell-anchor-xform.js +++ b/lib/xlsx/xform/drawing/two-cell-anchor-xform.js @@ -55,7 +55,7 @@ class TwoCellAnchorXform extends BaseCellAnchorXform { } reconcile(model, options) { - // model.medium = this.reconcilePicture(model.picture, options); + model.medium = this.reconcilePicture(model.picture, options); } } diff --git a/lib/xlsx/xform/sheet/worksheet-xform.js b/lib/xlsx/xform/sheet/worksheet-xform.js index 1c8f8ab13..27d15b2fa 100644 --- a/lib/xlsx/xform/sheet/worksheet-xform.js +++ b/lib/xlsx/xform/sheet/worksheet-xform.js @@ -188,74 +188,74 @@ class WorkSheetXform extends BaseXform { }); } - // const drawingRelsHash = []; - // let bookImage; - // model.media.forEach(medium => { - // if (medium.type === 'background') { - // const rId = nextRid(rels); - // bookImage = options.media[medium.imageId]; - // rels.push({ - // Id: rId, - // Type: RelType.Image, - // Target: `../media/${bookImage.name}.${bookImage.extension}`, - // }); - // model.background = { - // rId, - // }; - // model.image = options.media[medium.imageId]; - // } else if (medium.type === 'image') { - // let {drawing} = model; - // bookImage = options.media[medium.imageId]; - // if (!drawing) { - // drawing = model.drawing = { - // rId: nextRid(rels), - // name: `drawing${++options.drawingsCount}`, - // anchors: [], - // rels: [], - // }; - // options.drawings.push(drawing); - // rels.push({ - // Id: drawing.rId, - // Type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing', - // Target: `../drawings/${drawing.name}.xml`, - // }); - // } - // let rIdImage = - // this.preImageId === medium.imageId ? drawingRelsHash[medium.imageId] : drawingRelsHash[drawing.rels.length]; - // if (!rIdImage) { - // rIdImage = nextRid(drawing.rels); - // drawingRelsHash[drawing.rels.length] = rIdImage; - // drawing.rels.push({ - // Id: rIdImage, - // Type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image', - // Target: `../media/${bookImage.name}.${bookImage.extension}`, - // }); - // } - - // const anchor = { - // picture: { - // rId: rIdImage, - // }, - // range: medium.range, - // }; - // if (medium.hyperlinks && medium.hyperlinks.hyperlink) { - // const rIdHyperLink = nextRid(drawing.rels); - // drawingRelsHash[drawing.rels.length] = rIdHyperLink; - // anchor.picture.hyperlinks = { - // tooltip: medium.hyperlinks.tooltip, - // rId: rIdHyperLink, - // }; - // drawing.rels.push({ - // Id: rIdHyperLink, - // Type: RelType.Hyperlink, - // Target: medium.hyperlinks.hyperlink, - // TargetMode: 'External', - // }); - // } - // this.preImageId = medium.imageId; - // drawing.anchors.push(anchor); - // } - // }); + const drawingRelsHash = []; + let bookImage; + model.media.forEach(medium => { + if (medium.type === 'background') { + const rId = nextRid(rels); + bookImage = options.media[medium.imageId]; + rels.push({ + Id: rId, + Type: RelType.Image, + Target: `../media/${bookImage.name}.${bookImage.extension}`, + }); + model.background = { + rId, + }; + model.image = options.media[medium.imageId]; + } else if (medium.type === 'image') { + let {drawing} = model; + bookImage = options.media[medium.imageId]; + if (!drawing) { + drawing = model.drawing = { + rId: nextRid(rels), + name: `drawing${++options.drawingsCount}`, + anchors: [], + rels: [], + }; + options.drawings.push(drawing); + rels.push({ + Id: drawing.rId, + Type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing', + Target: `../drawings/${drawing.name}.xml`, + }); + } + let rIdImage = + this.preImageId === medium.imageId ? drawingRelsHash[medium.imageId] : drawingRelsHash[drawing.rels.length]; + if (!rIdImage) { + rIdImage = nextRid(drawing.rels); + drawingRelsHash[drawing.rels.length] = rIdImage; + drawing.rels.push({ + Id: rIdImage, + Type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image', + Target: `../media/${bookImage.name}.${bookImage.extension}`, + }); + } + + const anchor = { + picture: { + rId: rIdImage, + }, + range: medium.range, + }; + if (medium.hyperlinks && medium.hyperlinks.hyperlink) { + const rIdHyperLink = nextRid(drawing.rels); + drawingRelsHash[drawing.rels.length] = rIdHyperLink; + anchor.picture.hyperlinks = { + tooltip: medium.hyperlinks.tooltip, + rId: rIdHyperLink, + }; + drawing.rels.push({ + Id: rIdHyperLink, + Type: RelType.Hyperlink, + Target: medium.hyperlinks.hyperlink, + TargetMode: 'External', + }); + } + this.preImageId = medium.imageId; + drawing.anchors.push(anchor); + } + }); // prepare tables model.tables.forEach(table => { @@ -480,38 +480,38 @@ class WorkSheetXform extends BaseXform { this.map.sheetData.reconcile(model.rows, options); this.map.conditionalFormatting.reconcile(model.conditionalFormattings, options); - // model.media = []; - // if (model.drawing) { - // const drawingRel = rels[model.drawing.rId]; - // const match = drawingRel.Target.match(/\/drawings\/([a-zA-Z0-9]+)[.][a-zA-Z]{3,4}$/); - // if (match) { - // const drawingName = match[1]; - // const drawing = options.drawings[drawingName]; - // drawing.anchors.forEach(anchor => { - // if (anchor.medium) { - // const image = { - // type: 'image', - // imageId: anchor.medium.index, - // range: anchor.range, - // hyperlinks: anchor.picture.hyperlinks, - // }; - // model.media.push(image); - // } - // }); - // } - // } - - // const backgroundRel = model.background && rels[model.background.rId]; - // if (backgroundRel) { - // const target = backgroundRel.Target.split('/media/')[1]; - // const imageId = options.mediaIndex && options.mediaIndex[target]; - // if (imageId !== undefined) { - // model.media.push({ - // type: 'background', - // imageId, - // }); - // } - // } + model.media = []; + if (model.drawing) { + const drawingRel = rels[model.drawing.rId]; + const match = drawingRel.Target.match(/\/drawings\/([a-zA-Z0-9]+)[.][a-zA-Z]{3,4}$/); + if (match) { + const drawingName = match[1]; + const drawing = options.drawings[drawingName]; + drawing.anchors.forEach(anchor => { + if (anchor.medium) { + const image = { + type: 'image', + imageId: anchor.medium.index, + range: anchor.range, + hyperlinks: anchor.picture.hyperlinks, + }; + model.media.push(image); + } + }); + } + } + + const backgroundRel = model.background && rels[model.background.rId]; + if (backgroundRel) { + const target = backgroundRel.Target.split('/media/')[1]; + const imageId = options.mediaIndex && options.mediaIndex[target]; + if (imageId !== undefined) { + model.media.push({ + type: 'background', + imageId, + }); + } + } model.tables = (model.tables || []).map(tablePart => { const rel = rels[tablePart.rId]; diff --git a/lib/xlsx/xlsx.js b/lib/xlsx/xlsx.js index d8b2d7ea2..4d0c93242 100644 --- a/lib/xlsx/xlsx.js +++ b/lib/xlsx/xlsx.js @@ -18,6 +18,18 @@ const VmlNotesXform = require('./xform/comment/vml-notes-xform'); const theme1Xml = require('./xml/theme1'); +function fsReadFileAsync(filename, options) { + return new Promise((resolve, reject) => { + fs.readFile(filename, options, (error, data) => { + if (error) { + reject(error); + } else { + resolve(data); + } + }); + }); +} + class XLSX { constructor(workbook) { this.workbook = workbook; @@ -25,6 +37,29 @@ class XLSX { // ========================================================================= // Write + async addMedia(zip, model) { + await Promise.all( + model.media.map(async medium => { + if (medium.type === 'image') { + const filename = `xl/media/${medium.name}.${medium.extension}`; + if (medium.filename) { + const data = await fsReadFileAsync(medium.filename); + return zip.append(data, {name: filename}); + } + if (medium.buffer) { + return zip.append(medium.buffer, {name: filename}); + } + if (medium.base64) { + const dataimg64 = medium.base64; + const content = dataimg64.substring(dataimg64.indexOf(',') + 1); + return zip.append(content, {name: filename, base64: true}); + } + } + throw new Error('Unsupported media'); + }) + ); + } + addDrawings(zip, model) { const drawingXform = new DrawingXform(); const relsXform = new RelationshipsXform(); @@ -186,6 +221,7 @@ class XLSX { styles: model.styles, date1904: model.properties.date1904, drawingsCount: 0, + media: model.media, }; worksheetOptions.drawings = model.drawings = []; worksheetOptions.commentRefs = model.commentRefs = []; @@ -219,9 +255,12 @@ class XLSX { await this.addOfficeRels(zip, model); await this.addWorkbookRels(zip, model); await this.addWorksheets(zip, model); + // await this.addSharedStrings(zip, model); // always after worksheets await this.addDrawings(zip, model); await this.addTables(zip, model); + // await this.addPivotTables(zip, model); await Promise.all([this.addThemes(zip, model), this.addStyles(zip, model)]); + await this.addMedia(zip, model); await Promise.all([this.addApp(zip, model), this.addCore(zip, model)]); await this.addWorkbook(zip, model); return this._finalize(zip); @@ -238,9 +277,13 @@ class XLSX { reject(error); }); - this.write(stream, options).then(() => { - stream.end(); - }); + this.write(stream, options) + .then(() => { + stream.end(); + }) + .catch(err => { + reject(err); + }); }); } @@ -253,4 +296,4 @@ class XLSX { XLSX.RelType = require('./rel-type'); -module.exports = XLSX; +module.exports = XLSX; \ No newline at end of file diff --git a/package.json b/package.json index 6412f291f..f72216c2d 100644 --- a/package.json +++ b/package.json @@ -97,7 +97,8 @@ ], "dependencies": { "uuid": "^8.3.0", - "uzip": "^0.20201231.0" + "uzip": "^0.20201231.0", + "readable-stream": "^3.6.0" }, "devDependencies": { "@babel/cli": "^7.10.5",