From 40aed538cda3e52c9ee8b1ffa4bcfc37d5ea3e60 Mon Sep 17 00:00:00 2001 From: IagoSRL Date: Sat, 9 Mar 2024 17:57:39 +0100 Subject: [PATCH] Move hyperlinks references on added rows and columns (#184) * Push down and right hyperlinks references * Test push down and right hyperlinks --- src/index.js | 19 +++++++ test/crud-test.ts | 60 ++++++++++++++++++++++ test/templates/test-moved-hyperlinks.xlsx | Bin 0 -> 7673 bytes 3 files changed, 79 insertions(+) create mode 100644 test/templates/test-moved-hyperlinks.xlsx diff --git a/src/index.js b/src/index.js index 9f5a938..d7a28ea 100755 --- a/src/index.js +++ b/src/index.js @@ -1312,6 +1312,16 @@ class Workbook { } }); + + // Update hyperlinks refs + sheet.findall("hyperlinks/hyperlink").forEach(function (hyperlink) { + var ref = self.splitRef(hyperlink.attrib.ref); + var colNumber = self.charToNum(ref.col); + if (colNumber > currentCol) { + ref.col = self.numToChar(colNumber + numCols); + hyperlink.attrib.ref = self.joinRef(ref); + } + }); } // Look for any merged cell, named table or named range definitions below // `currentRow` and push down by `numRows` (used when rows are inserted). @@ -1414,6 +1424,15 @@ class Workbook { } }); + + // Update hyperlinks refs + sheet.findall("hyperlinks/hyperlink").forEach(function (hyperlink) { + var ref = self.splitRef(hyperlink.attrib.ref); + if (ref.row > currentRow) { + ref.row += numRows; + hyperlink.attrib.ref = self.joinRef(ref); + } + }); } getWidthCell(numCol, sheet) { var defaultWidth = sheet.root.find("sheetFormatPr").attrib["defaultColWidth"]; diff --git a/test/crud-test.ts b/test/crud-test.ts index b926837..aacac31 100644 --- a/test/crud-test.ts +++ b/test/crud-test.ts @@ -684,6 +684,66 @@ describe("CRUD operations", function() { }); }); + it("moved hyperlinks in sheet", function(done) { + fs.readFile(path.join(__dirname, "templates", "test-moved-hyperlinks.xlsx"), function(err, data) { + expect(err).toBeNull(); + + var t = new XlsxTemplate(data); + + t.substitute(1, { + email: "john@bob.com", + subject: "hello", + url: "http://www.google.com", + domain: "google", + rows: [{ + name: 'One', + amount: 1, + }, { + name: 'Two', + amount: 2, + }, { + name: 'Three', + amount: 3, + }], + list: ['A', 'B', 'C', 'D'] + }); + + var newData = t.generate(); + + var sharedStrings = etree.parse(t.archive.file("xl/sharedStrings.xml").asText()).getroot(), + sheet1 = etree.parse(t.archive.file("xl/worksheets/sheet1.xml").asText()).getroot(), + rels = etree.parse(t.archive.file("xl/worksheets/_rels/sheet1.xml.rels").asText()).getroot() + ; + + // Every hyperlink has being substituted + expect(rels.find("./Relationship[@Id='rId1']").attrib.Target).toEqual("mailto:john@bob.com?subject=Hello%20hello"); + expect(rels.find("./Relationship[@Id='rId2']").attrib.Target).toEqual("http://www.google.com"); + expect(rels.find("./Relationship[@Id='rId3']").attrib.Target).toEqual("mailto:john@bob.com?subject=Hello%20hello"); + expect(rels.find("./Relationship[@Id='rId4']").attrib.Target).toEqual("http://www.google.com"); + expect(rels.find("./Relationship[@Id='rId5']").attrib.Target).toEqual("mailto:john@bob.com?subject=Hello%20hello"); + expect(rels.find("./Relationship[@Id='rId6']").attrib.Target).toEqual("http://www.google.com"); + + // Hyperlinks have moved + expect(sheet1.find("./hyperlinks/hyperlink[@ref='B7']")).not.toBeNull(); // before table and list - unchanged + expect(sheet1.find("./hyperlinks/hyperlink[@ref='C7']")).not.toBeNull(); // before table and list - unchanged + + expect(sheet1.find("./hyperlinks/hyperlink[@ref='B14']")).toBeNull(); // pushed down + expect(sheet1.find("./hyperlinks/hyperlink[@ref='B16']")).not.toBeNull(); // pushed down + expect(sheet1.find("./hyperlinks/hyperlink[@ref='C14']")).toBeNull(); // pushed down + expect(sheet1.find("./hyperlinks/hyperlink[@ref='C16']")).not.toBeNull(); // pushed down + + expect(sheet1.find("./hyperlinks/hyperlink[@ref='F14']")).toBeNull(); // pushed down and accross + expect(sheet1.find("./hyperlinks/hyperlink[@ref='I16']")).not.toBeNull(); // pushed down and accross + expect(sheet1.find("./hyperlinks/hyperlink[@ref='G14']")).toBeNull(); // pushed down and accross + expect(sheet1.find("./hyperlinks/hyperlink[@ref='J16']")).not.toBeNull(); // pushed down and accross + + // XXX: For debugging only + fs.writeFileSync("test/output/test-moved-hyperlinks.xlsx", newData, "binary"); + + done(); + }); + }); + it("moves named tables, named cells and merged cells", function(done) { fs.readFile(path.join(__dirname, "templates", "test-named-tables.xlsx"), function(err, data) { diff --git a/test/templates/test-moved-hyperlinks.xlsx b/test/templates/test-moved-hyperlinks.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..32b6d2012b9d946011d85774419169365e1e81a0 GIT binary patch literal 7673 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 G-u!=IjQn!| literal 0 HcmV?d00001