From 52c6ba8663a14cdc3bf7beecff65fc6099df34fc Mon Sep 17 00:00:00 2001 From: Jonathan Durand <38976316+jdugh@users.noreply.github.com> Date: Tue, 26 Mar 2024 05:07:18 +0100 Subject: [PATCH] Adding 'imageincell' substitution keyword (#189) --- lib/index.d.ts | 4 + src/index.js | 256 ++++++++++++++++++ test/crud-test.ts | 35 +++ .../templates/test-insert-images_in_cell.xlsx | Bin 0 -> 9211 bytes 4 files changed, 295 insertions(+) create mode 100644 test/templates/test-insert-images_in_cell.xlsx diff --git a/lib/index.d.ts b/lib/index.d.ts index 7ad0535..9dc32da 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -88,6 +88,9 @@ class XlsxTemplate protected loadSheetRels(sheetFilename : string) : { rels : any}; protected loadDrawing(sheet : any, sheetFilename : string, rels : any) : { drawing : any}; protected writeDrawing(drawing : any); + protected initRichData() : void; + protected writeRichDataAlreadyExist(element : etree.ElementTree, elementSearchName : string, attributeName : string, attributeValue : string) : boolean; + protected writeRichData() : void; protected moveAllImages(drawing : any, fromRow : number, nbRow : number); protected loadTables(sheet : any, sheetFilename : any) : any; protected writeTables(tables : any) : void; @@ -97,6 +100,7 @@ class XlsxTemplate protected substituteArray(cells : any[], cell : any, substitution : any); protected substituteTable(row : any, newTableRows : any, cells : any[], cell : any, namedTables : any, substitution : any, key : any, placeholder : TemplatePlaceholder, drawing : etree.ElementTree) : any; protected substituteImage(cell : any, string : string, placeholder: TemplatePlaceholder, substitution : any, drawing : etree.ElementTree) : boolean + protected substituteImageInCell(cell : any, substitution : any) : boolean; protected cloneElement(element : any, deep? : any) : any; protected replaceChildren(parent : any, children : any) : void; protected getCurrentRow(row : any, rowsInserted : any) : number; diff --git a/src/index.js b/src/index.js index d7a28ea..26684af 100755 --- a/src/index.js +++ b/src/index.js @@ -321,6 +321,8 @@ class Workbook { } else { console.log("Need to implement initRels. Or init this with Excel"); } + } else if (placeholder.type === "imageincell" && placeholder.full) { + string = self.substituteImageInCell(cell, substitution); } else { if (placeholder.key) { substitution = _get(substitutions, placeholder.name + '.' + placeholder.key); @@ -439,6 +441,7 @@ class Workbook { if (rels) { self.archive.file(rels.filename, etree.tostring(rels.root)); } + self.writeRichData(); self.archive.file('[Content_Types].xml', etree.tostring(self.contentTypes)); // Remove calc chain - Excel will re-build, and we may have moved some formulae if (self.calcChainPath && self.archive.file(self.calcChainPath)) { @@ -1092,6 +1095,259 @@ class Workbook { return newCellsInserted; } + + /** + * Init the RichData structure for ImageInCell + * There are 6 xml to init. + * If one of the files is available in the Excel archive, we read it rather than using the default value + */ + initRichData() { + + if (!this.richDataIsInit) { + const _relsrichValueRel = ` + + `; + const rdrichvalue = ` + + `; + const rdrichvaluestructure = ` + + + + + + `; + const rdRichValueTypes = ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + `; + const richValueRel = ` + + `; + const metadata = ` + + + + + + + + + `; + const _relsrichValueRelFileName = 'xl/richData/_rels/richValueRel.xml.rels'; + const rdrichvalueFileName = 'xl/richData/rdrichvalue.xml'; + const rdrichvaluestructureFileName = 'xl/richData/rdrichvaluestructure.xml'; + const rdRichValueTypesFileName = 'xl/richData/rdRichValueTypes.xml'; + const richValueRelFileName = 'xl/richData/richValueRel.xml'; + const metadataFileName = 'xl/metadata.xml'; + + this._relsrichValueRel = etree.parse(_relsrichValueRel).getroot(); + this.rdrichvalue = etree.parse(rdrichvalue).getroot(); + this.rdrichvaluestructure = etree.parse(rdrichvaluestructure).getroot(); + this.rdRichValueTypes = etree.parse(rdRichValueTypes).getroot(); + this.richValueRel = etree.parse(richValueRel).getroot(); + this.metadata = etree.parse(metadata).getroot(); + if(this.archive.file(_relsrichValueRelFileName) ){ + this._relsrichValueRel = etree.parse(this.archive.file(_relsrichValueRelFileName).asText()).getroot() + } + if(this.archive.file(rdrichvalueFileName) ){ + this.rdrichvalue = etree.parse(this.archive.file(rdrichvalueFileName).asText()).getroot() + } + if(this.archive.file(rdrichvaluestructureFileName) ){ + this.rdrichvaluestructure = etree.parse(this.archive.file(rdrichvaluestructureFileName).asText()).getroot() + } + if(this.archive.file(rdRichValueTypesFileName) ){ + this.rdRichValueTypes = etree.parse(this.archive.file(rdRichValueTypesFileName).asText()).getroot() + } + if(this.archive.file(richValueRelFileName) ){ + this.richValueRel = etree.parse(this.archive.file(richValueRelFileName).asText()).getroot() + } + if(this.archive.file(metadataFileName) ){ + this.metadata = etree.parse(this.archive.file(metadataFileName).asText()).getroot() + } + this.richDataIsInit = true; + } + }; + + writeRichDataAlreadyExist(element, elementSearchName, attributeName, attributeValue) { + for (const e of element.findall(elementSearchName)) { + if (e.attrib[attributeName] == attributeValue) { + return true; + } + }; + return false; + }; + + /** + * Write the new RichData structure with the updated XML Value for each RichData files + */ + writeRichData() { + if (this.richDataIsInit) { + const _relsrichValueRelFileName = 'xl/richData/_rels/richValueRel.xml.rels'; + const rdrichvalueFileName = 'xl/richData/rdrichvalue.xml'; + const rdrichvaluestructureFileName = 'xl/richData/rdrichvaluestructure.xml'; + const rdRichValueTypesFileName = 'xl/richData/rdRichValueTypes.xml'; + const richValueRelFileName = 'xl/richData/richValueRel.xml'; + const metadataFileName = 'xl/metadata.xml'; + this.archive.file(_relsrichValueRelFileName, etree.tostring(this._relsrichValueRel)); + this.archive.file(rdrichvalueFileName, etree.tostring(this.rdrichvalue)); + this.archive.file(rdrichvaluestructureFileName, etree.tostring(this.rdrichvaluestructure)); + this.archive.file(rdRichValueTypesFileName, etree.tostring(this.rdRichValueTypes)); + this.archive.file(richValueRelFileName, etree.tostring(this.richValueRel)); + this.archive.file(metadataFileName, etree.tostring(this.metadata)); + + const wbrelsidMax = this.findMaxId(this.workbookRels, 'Relationship', 'Id', /rId(\d*)/); + if (!this.writeRichDataAlreadyExist(this.workbookRels, 'Relationship', 'Target', "richData/rdrichvaluestructure.xml")) { + var _rel = etree.SubElement(this.workbookRels, 'Relationship'); + _rel.set('Id', 'rId' + wbrelsidMax); + _rel.set('Type', "http://schemas.microsoft.com/office/2017/06/relationships/rdRichValueStructure"); + _rel.set('Target', "richData/rdrichvaluestructure.xml"); + } + if (!this.writeRichDataAlreadyExist(this.workbookRels, 'Relationship', 'Target', "richData/rdrichvalue.xml")) { + _rel = etree.SubElement(this.workbookRels, 'Relationship'); + _rel.set('Id', `rId${wbrelsidMax + 1}`); + _rel.set('Type', "http://schemas.microsoft.com/office/2017/06/relationships/rdRichValue"); + _rel.set('Target', "richData/rdrichvalue.xml"); + } + if (!this.writeRichDataAlreadyExist(this.workbookRels, 'Relationship', 'Target', "richData/richValueRel.xml")) { + _rel = etree.SubElement(this.workbookRels, 'Relationship'); + _rel.set('Id', `rId${wbrelsidMax + 2}`); + _rel.set('Type', "http://schemas.microsoft.com/office/2022/10/relationships/richValueRel"); + _rel.set('Target', "richData/richValueRel.xml"); + } + if (!this.writeRichDataAlreadyExist(this.workbookRels, 'Relationship', 'Target', "metadata.xml")) { + _rel = etree.SubElement(this.workbookRels, 'Relationship'); + _rel.set('Id', `rId${wbrelsidMax + 3}`); + _rel.set('Type', "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sheetMetadata"); + _rel.set('Target', "metadata.xml"); + } + if (!this.writeRichDataAlreadyExist(this.workbookRels, 'Relationship', 'Target', "richData/rdRichValueTypes.xml")) { + _rel = etree.SubElement(this.workbookRels, 'Relationship'); + _rel.set('Id', `rId${wbrelsidMax + 4}`); + _rel.set('Type', "http://schemas.microsoft.com/office/2017/06/relationships/rdRichValueTypes"); + _rel.set('Target', "richData/rdRichValueTypes.xml"); + } + if (!this.writeRichDataAlreadyExist(this.contentTypes, 'Override', 'PartName', "/xl/metadata.xml")) { + var ctOverride = etree.SubElement(this.contentTypes, 'Override'); + ctOverride.set('PartName', "/xl/metadata.xml"); + ctOverride.set('ContentType', "application/vnd.openxmlformats-officedocument.spreadsheetml.sheetMetadata+xml"); + } + if (!this.writeRichDataAlreadyExist(this.contentTypes, 'Override', 'PartName', "/xl/richData/richValueRel.xml")) { + ctOverride = etree.SubElement(this.contentTypes, 'Override'); + ctOverride.set('PartName', "/xl/richData/richValueRel.xml"); + ctOverride.set('ContentType', "application/vnd.ms-excel.richvaluerel+xml"); + } + if (!this.writeRichDataAlreadyExist(this.contentTypes, 'Override', 'PartName', "/xl/richData/rdrichvalue.xml")) { + ctOverride = etree.SubElement(this.contentTypes, 'Override'); + ctOverride.set('PartName', "/xl/richData/rdrichvalue.xml"); + ctOverride.set('ContentType', "application/vnd.ms-excel.rdrichvalue+xml"); + } + if (!this.writeRichDataAlreadyExist(this.contentTypes, 'Override', 'PartName', "/xl/richData/rdrichvaluestructure.xml")) { + ctOverride = etree.SubElement(this.contentTypes, 'Override'); + ctOverride.set('PartName', "/xl/richData/rdrichvaluestructure.xml"); + ctOverride.set('ContentType', "application/vnd.ms-excel.rdrichvaluestructure+xml"); + } + if (!this.writeRichDataAlreadyExist(this.contentTypes, 'Override', 'PartName', "/xl/richData/rdRichValueTypes.xml")) { + ctOverride = etree.SubElement(this.contentTypes, 'Override'); + ctOverride.set('PartName', "/xl/richData/rdRichValueTypes.xml"); + ctOverride.set('ContentType', "application/vnd.ms-excel.rdrichvaluetypes+xml"); + } + this._rebuild() + } + } + + substituteImageInCell(cell, substitution) { + if (substitution == null || substitution == "") { + this.insertCellValue(cell, ""); + return true; + } + this.initRichData(); + const maxFildId = this.findMaxFileId(/xl\/media\/image\d*.jpg/, /image(\d*)\.jpg/); + try { + substitution = this.imageToBuffer(substitution); + } + catch (error) { + if (this.option && this.option.handleImageError && typeof this.option.handleImageError === "function") { + this.option.handleImageError(substitution, error); + } + else { + throw error; + } + } + this.archive.file('xl/media/image' + maxFildId + '.jpg', this.toArrayBuffer(substitution), { binary: true, base64: false }); + const maxIdRichData = this.findMaxId(this._relsrichValueRel, 'Relationship', 'Id', /rId(\d*)/); + const _rel = etree.SubElement(this._relsrichValueRel, 'Relationship'); + _rel.set('Id', 'rId' + maxIdRichData); + _rel.set('Type', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image'); + _rel.set('Target', '../media/image' + maxFildId + '.jpg'); + const currentCountrdRichValue = this.rdrichvalue.get('count'); + this.rdrichvalue.set('count', parseInt(currentCountrdRichValue) + 1); + const rv = etree.SubElement(this.rdrichvalue, 'rv'); + rv.set('s', "0"); + const firstV = etree.SubElement(rv, 'v'); + const secondV = etree.SubElement(rv, 'v'); + firstV.text = currentCountrdRichValue; + secondV.text = "5"; + const rel = etree.SubElement(this.richValueRel, 'rel'); + rel.set("r:id", 'rId' + maxIdRichData); + const futureMetadataCount = this.metadata.find('futureMetadata').get('count'); + this.metadata.find('futureMetadata').set('count', parseInt(futureMetadataCount) + 1); + const bk = etree.SubElement(this.metadata.find('futureMetadata'), 'bk'); + const extLst = etree.SubElement(bk, 'extLst'); + const ext = etree.SubElement(extLst, 'ext'); + ext.set("uri", "{3e2802c4-a4d2-4d8b-9148-e3be6c30e623}"); + const xlrd_rvb = etree.SubElement(ext, 'xlrd:rvb'); + xlrd_rvb.set("i", futureMetadataCount); + const valueMetadataCount = this.metadata.find('valueMetadata').get('count'); + this.metadata.find('valueMetadata').set('count', parseInt(valueMetadataCount) + 1); + const bk_VM = etree.SubElement(this.metadata.find('valueMetadata'), 'bk'); + const rc = etree.SubElement(bk_VM, 'rc'); + rc.set("t", "1"); + rc.set("v", valueMetadataCount); + cell.set("t", "e"); + cell.set("vm", parseInt(currentCountrdRichValue) + 1); + this.insertCellValue(cell, "#VALUE!"); + return true; + }; + substituteImage(cell, string, placeholder, substitution, drawing) { var self = this; var self = this; diff --git a/test/crud-test.ts b/test/crud-test.ts index aacac31..419e72e 100644 --- a/test/crud-test.ts +++ b/test/crud-test.ts @@ -1150,6 +1150,41 @@ describe("CRUD operations", function() { }); }); + it("Insert imageincells and create rels", function(done) { + fs.readFile(path.join(__dirname, 'templates', 'test-insert-images_in_cell.xlsx'), function(err, data) { + expect(err).toBeNull(); + var option = { + imageRootPath : path.join(__dirname, 'templates', 'dataset') + } + var t = new XlsxTemplate(data, option); + var imgB64 = 'iVBORw0KGgoAAAANSUhEUgAAALAAAAA2CAYAAABnXhObAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsIAAA7CARUoSoAAAAUjSURBVHhe7ZtbyGVjGMfXlmmccpqRcYgZmQsTMeVYQiJzozEYJRHCcCUaNy5kyg3KhUNEhjSRbwipIcoF0pRpxiFiLsYUIqcoZ8b2/6/1vKtnv3u9+/smuXjW/v/qv593PetZa+/17f9617vetb9BVVVDSIiQ7GFRiJC0PfBwOGRbiBAMBoPat+qBRWhkYBEaGViERgYWoZGBRWhkYBEaGViERgYWoZGBRWhkYBEaGViERgYWoZGBRWhkYBEaGViERgYWoenND9oHg8EFCOdBPJ57cDzfWf5MhLPZBr9AL2LdzmZxFNTui3AdtF+dqKqtqH3F2iOg9g6EVLcFdTPW7gT1CxAuhE6FFkG/Ql9Cm7HtC4gtqL0S4ehmaRzU32XNiWA/VyGcDGGT4c11sifg2Np/hWODB8iXsAJ3p2OBznL5rS5Pfey38wJPuzpqe6HuXFdDfdRVlwRo9r+stkvboYWu/ifLlzTP7z8XOBZ63mqTDuyqjap0XNMwhGCvStjjkeNw9q6ydgtyCxEub5Za5lnMWW0xsQzbn2btEZBfh0DtWSeq6h3oQeghaBMTYCl0UtMc4U/obegN6FXoJWgGXyBPhk7wfrcgfAJdXCemgNrJ3t0RBUo98KeW+wCiIdjmMCLfnpdYNrw+y+us9ltb/6FFisOWvO4Ut/4HaHWh5j7odJdLPfAXvnYuAk/Ztuuhn61NqQcOzt/QM02zWome6gBrJzj2Jey9imC7lQjsrcn1FsmlFj33WiS34Q+/0dotyL0L3QptttR/hT32cuzvWkQec6+ZNgNvaJo1ay3SlLyxOr5Zqm6wWKI1qpluW7NULcF+zrE29zkfId08sid/3Nq7BfazwMvSRfA+j0Lv2eIui71lqgyML/Z1xO+bxepqi+RGizTAW9Ycw0yZxr+8VJP7LZJLLJJjLBIOY2qwj1XQRtOM6VmIMxM5R0CcTWmFunRSzAX1wD0ifZnpMn4kzHCita+xyLEoKX3xNC9NTB7mCwz/ZL3U4A18sEWSThpyCMRenOL+qMsgfxNXD/IKTFqXox64RyRTcqossQ4mvsna5AmLacYinxv3sw9/YNvlFNpp/HoYlldY+2uLhLMMiTchvifFGYlJcJ6YJ0IrnDDcfq703sCkvpsDfAkrMNssxGsut8Ny1O8W2xkHQPOxsdPleOPGxmx6zG3Dyz4bfI9FKe/WP2DrqRUu/6PldnsWwgvwgU3av2YhguOHBX42IA0J/Fj2N4se3/tOwg8j0qwH3+POpjnC/z1G1RCipzxn0eMfBU8y8C70AINcyKeT4iAMIy6y9u3QN02zWoP8y9D50OHQMuTOaFaVQd0JuWxVJ1i/N7SUwqI/QY5CbjG0vy33hrorBmPddCSB2YYQm7L69y3ftW6L5eshBFhiy9R6X5sEOD+caja4PG8QeROX1pXUNYQoaX6qzQV4JcnrvTo/fzSl45nWHpj4YUT+Q5y8B+YPhRJ8nDsG/qjMf9UsVVdYZJ43hpxS4+NjPsHL4Xj8EdTxUXEi7adE/Q0WKD5mNno1rOClr7FzcxkMDS6P+zDiWNIsQg3yeyHHG6kRmGcsrfP50r5zZqvD+sUIh0L8ZdznqONj4zHSZ+ui6/N6sG2xY8K2/1gzNDjG2re9MrCYHpKBp3kIIXqADCxCIwOL0MjAIjQysAiNDCxCIwOL0MjAIjQysAiNDCxCIwOL0MjAIjQysAiNDCxCIwOL0MjAIjQysAhN+x8ZQkREPbAITFX9C5ozpqaetbGcAAAAAElFTkSuQmCC'; + + t.substitute('init_rels', { + imgB64 : imgB64, + }); + var newData = t.generate(); + var richDataFile = etree.parse(t.archive.file("xl/richData/_rels/richValueRel.xml.rels").asText()).getroot(); + expect(richDataFile.findall("Relationship").length).toEqual(4); + var richDataFile = etree.parse(t.archive.file("xl/richData/rdrichvalue.xml").asText()).getroot(); + expect(richDataFile.findall("rv").length).toEqual(4); + expect(parseInt(richDataFile.attrib.count)).toEqual(richDataFile.findall("rv").length); + var richDataFile = etree.parse(t.archive.file("xl/richData/richValueRel.xml").asText()).getroot(); + expect(richDataFile.findall("rel").length).toEqual(4); + var richDataFile = etree.parse(t.archive.file("xl/metadata.xml").asText()).getroot(); + expect(parseInt(richDataFile.find('futureMetadata').attrib.count)).toEqual(4); + expect(parseInt(richDataFile.find('valueMetadata').attrib.count)).toEqual(4); + expect(richDataFile.find('futureMetadata').findall("bk").length).toEqual(4); + expect(richDataFile.find('valueMetadata').findall("bk").length).toEqual(4); + var sheet1 = etree.parse(t.archive.file("xl/worksheets/sheet1.xml").asText()).getroot(); + expect(sheet1.find("./sheetData/row[@r='3']/c[@r='B3']").attrib.vm).toEqual("1"); + expect(sheet1.find("./sheetData/row[@r='5']/c[@r='K5']").attrib.vm).toEqual("2"); + expect(sheet1.find("./sheetData/row[@r='6']/c[@r='D6']").attrib.vm).toEqual("3"); + expect(sheet1.find("./sheetData/row[@r='10']/c[@r='H10']").attrib.vm).toEqual("4"); + fs.writeFileSync('test/output/insert_imageincell.xlsx', newData, 'binary'); + done(); + }); + }); + it("Insert some format of image", function(done) { fs.readFile(path.join(__dirname, "templates", "test-insert-images.xlsx"), function(err, data) { expect(err).toBeNull(); diff --git a/test/templates/test-insert-images_in_cell.xlsx b/test/templates/test-insert-images_in_cell.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..beb58cbbb2a91965385bf9c5e6bcd8dfcc10c6b9 GIT binary patch literal 9211 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 literal 0 HcmV?d00001