Skip to content

Commit fa33a1b

Browse files
authored
Fix a bug where a literal colon in a cell between (#202)
two placeholders breaks detection of both placeholders, replacing the whole as empty (from beggining of first placeholder to end of the next one and any literal text in between)
1 parent 8e2bad7 commit fa33a1b

File tree

3 files changed

+51
-9
lines changed

3 files changed

+51
-9
lines changed

src/index.js

+22-9
Original file line numberDiff line numberDiff line change
@@ -807,7 +807,20 @@ class Workbook {
807807
// is the entirety of the string) and `type` (one of `table` or `cell`)
808808
extractPlaceholders(string) {
809809
// Yes, that's right. It's a bunch of brackets and question marks and stuff.
810-
var re = /\${(?:(.+?):)?(.+?)(?:\.(.+?))?(?::(.+?))??}/g;
810+
// IMPORTANT: Instead of matching 'any character' on every capture (by using a dot),
811+
// it captures 'any except colon and brackets' by using [^{}:] to avoid
812+
// a bug where the expression will consume the text in a 'greedy' way,
813+
// capturing colon characters from outside the placeholder (after a closing
814+
// bracket).
815+
// Since that characters are already managed in the RegExp as separators
816+
// of capture groups, it will still match the whole expression,
817+
// but in a safe way (by including brackets and not only the colon from
818+
// the exclusion, it avoids other kinds of mistakes and wrong placeholder
819+
// syntax, but if compatibility is a concern they could be removed and
820+
// left like [^:] ).
821+
// (The non-greedy modifier should have done that, but it's more difficult
822+
// to get it right than expected)
823+
var re = /\${(?:([^{}:]+?):)?([^{}:]+?)(?:\.([^{}:]+?))?(?::([^{}:]+?))??}/g;
811824

812825
var match = null, matches = [];
813826
while ((match = re.exec(string)) !== null) {
@@ -1061,14 +1074,14 @@ class Workbook {
10611074
let mergeCell = self.sheet.root.findall("mergeCells/mergeCell")
10621075
.find(c => self.splitRange(c.attrib.ref).start === cell.attrib.r)
10631076
let isMergeCell = mergeCell != null
1064-
1077+
10651078
if(isMergeCell) {
10661079
let originalMergeRange = self.splitRange(mergeCell.attrib.ref),
10671080
originalMergeStart = self.splitRef(originalMergeRange.start),
10681081
originalMergeEnd = self.splitRef(originalMergeRange.end);
1069-
1082+
10701083
for (let colnum = self.charToNum(originalMergeStart.col) + 1; colnum <= self.charToNum(originalMergeEnd.col); colnum++) {
1071-
const originalRow = self.sheet.root.find('sheetData')._children.find(f=>f.attrib.r == originalMergeStart.row)
1084+
const originalRow = self.sheet.root.find('sheetData')._children.find(f=>f.attrib.r == originalMergeStart.row)
10721085
let col = self.numToChar(colnum)
10731086
let originalCell = originalRow._children.find(f=>f.attrib.r.startsWith(col))
10741087

@@ -1698,7 +1711,7 @@ class Workbook {
16981711
getWidthCell(numCol, sheet) {
16991712
var defaultWidth = sheet.root.find("sheetFormatPr").attrib["defaultColWidth"];
17001713
if (!defaultWidth) {
1701-
// TODO : Check why defaultColWidth is not set ?
1714+
// TODO : Check why defaultColWidth is not set ?
17021715
defaultWidth = 11.42578125;
17031716
}
17041717
var finalWidth = defaultWidth;
@@ -1751,7 +1764,7 @@ class Workbook {
17511764
}
17521765
columnWidthToEMUs(width) {
17531766
// TODO : This is not the true. Change with true calcul
1754-
// can find help here :
1767+
// can find help here :
17551768
// https://docs.microsoft.com/en-us/office/troubleshoot/excel/determine-column-widths
17561769
// https://stackoverflow.com/questions/58021996/how-to-set-the-fixed-column-width-values-in-inches-apache-poi
17571770
// https://poi.apache.org/apidocs/dev/org/apache/poi/ss/usermodel/Sheet.html#setColumnWidth-int-int-
@@ -1766,9 +1779,9 @@ class Workbook {
17661779
}
17671780
/**
17681781
* Find max file id.
1769-
* @param {RegExp} fileNameRegex
1770-
* @param {RegExp} idRegex
1771-
* @returns {number}
1782+
* @param {RegExp} fileNameRegex
1783+
* @param {RegExp} idRegex
1784+
* @returns {number}
17721785
*/
17731786
findMaxFileId(fileNameRegex, idRegex) {
17741787
var self = this;

test/crud-test.ts

+29
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,35 @@ describe("CRUD operations", function() {
279279
done();
280280
});
281281

282+
});
283+
284+
it("can include a literal colon in between two placeholders in the same cell (with and without newlines in between)", function(done) {
285+
286+
fs.readFile(path.join(__dirname, "templates", "colon-allowed-between-placeholders.xlsx"), function(err, data) {
287+
expect(err).toBeNull();
288+
289+
var t = new XlsxTemplate(data);
290+
291+
t.substitute(1, {
292+
foo: "f-value",
293+
bar: "b-value",
294+
});
295+
296+
var newData = t.generate();
297+
298+
var sharedStrings = etree.parse(t.archive.file("xl/sharedStrings.xml").asText()).getroot(),
299+
sheet1 = etree.parse(t.archive.file("xl/worksheets/sheet1.xml").asText()).getroot();
300+
301+
expect(sheet1).toBeDefined();
302+
expect(getSharedString(sharedStrings, sheet1, "A1")).toEqual("f-value and colon in between: b-value on same line");
303+
expect(getSharedString(sharedStrings, sheet1, "A2")).toEqual("f-value\nand colon in between: b-value\non different lines");
304+
305+
// XXX: For debugging only
306+
fs.writeFileSync("test/output/colon-allowed-between-placeholders.xlsx", newData, "binary");
307+
308+
done();
309+
});
310+
282311
});
283312

284313
it("can substitute values when single item array contains an object and generate a file", function(done) {
Binary file not shown.

0 commit comments

Comments
 (0)