From b6ce353a7d0131a06b9081eae2f7ee5776882ebf Mon Sep 17 00:00:00 2001 From: Saq Imtiaz Date: Mon, 1 Nov 2021 14:24:30 +0100 Subject: [PATCH] Fix: resolved search-replace operator regexp encoding bug (#6162) * fix: resolved search-replace operator bug with $ character in replacement strings, added test and more examples * fix: reset regexp after each title --- core/modules/filters/strings.js | 20 ++++++++++--------- editions/test/tiddlers/tests/test-filters.js | 4 ++++ .../search-replace Operator (Examples).tid | 10 ++++++++-- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/core/modules/filters/strings.js b/core/modules/filters/strings.js index 96efc0760..60a36265d 100644 --- a/core/modules/filters/strings.js +++ b/core/modules/filters/strings.js @@ -121,21 +121,23 @@ exports["search-replace"] = function(source,operator,options) { flagSuffix = (suffixes[0] ? (suffixes[0][0] || "") : ""), flags = (flagSuffix.indexOf("g") !== -1 ? "g" : "") + (flagSuffix.indexOf("i") !== -1 ? "i" : "") + (flagSuffix.indexOf("m") !== -1 ? "m" : ""), isRegExp = (suffixes[1] && suffixes[1][0] === "regexp") ? true : false, - searchTerm, + //Escape regexp characters if the operand is not a regular expression + searchTerm = isRegExp ? operator.operand : $tw.utils.escapeRegExp(operator.operand), + //Escape $ character in replacement string if not in regular expression mode + replacement = isRegExp ? operator.operands[1] : (operator.operands[1]||"").replace(/\$/g,"$$$$"), regExp; + try { + regExp = new RegExp(searchTerm,flags); + } catch(ex) { + return ["RegExp error: " + ex]; + } source(function(tiddler,title) { if(title && (operator.operands.length > 1)) { - //Escape regexp characters if the operand is not a regular expression - searchTerm = isRegExp ? operator.operand : $tw.utils.escapeRegExp(operator.operand); - try { - regExp = new RegExp(searchTerm,flags); - } catch(ex) { - return ["RegExp error: " + ex]; - } results.push( - title.replace(regExp,operator.operands[1]) + title.replace(regExp,replacement) ); + regExp.lastIndex = 0; } else { results.push(title); } diff --git a/editions/test/tiddlers/tests/test-filters.js b/editions/test/tiddlers/tests/test-filters.js index 04328f58f..05c5e3c83 100644 --- a/editions/test/tiddlers/tests/test-filters.js +++ b/editions/test/tiddlers/tests/test-filters.js @@ -815,6 +815,10 @@ Tests the filtering mechanism. expect(wiki.filterTiddlers("[[Unlike conventional online services, TiddlyWiki lets you choose where to keep your data\nUnlike conventional online services, TiddlyWiki lets you choose where to keep your data\nUnlike conventional online services, TiddlyWiki lets you choose where to keep your data\nUnlike conventional online services, TiddlyWiki lets you choose where to keep your data]search-replace:g:regexp,[]]",anchorWidget).join(",")).toBe("conventional online services, TiddlyWiki lets you choose where to keep your data\nUnlike conventional online services, TiddlyWiki lets you choose where to keep your data\nUnlike conventional online services, TiddlyWiki lets you choose where to keep your data\nUnlike conventional online services, TiddlyWiki lets you choose where to keep your data"); expect(wiki.filterTiddlers("[[Unlike conventional online services, TiddlyWiki lets you choose where to keep your data\nUnlike conventional online services, TiddlyWiki lets you choose where to keep your data\nUnlike conventional online services, TiddlyWiki lets you choose where to keep your data\nUnlike conventional online services, TiddlyWiki lets you choose where to keep your data]search-replace:gm:regexp,[]]",anchorWidget).join(",")).toBe("conventional online services, TiddlyWiki lets you choose where to keep your data\nconventional online services, TiddlyWiki lets you choose where to keep your data\nconventional online services, TiddlyWiki lets you choose where to keep your data\nconventional online services, TiddlyWiki lets you choose where to keep your data"); expect(wiki.filterTiddlers("[[Hello There\nUnlike conventional online services, TiddlyWiki lets you choose where to keep your data\nguaranteeing that in the decades to come you will still be able to use the notes you take today.]search-replace:gm:regexp,[]]",anchorWidget).join(",")).toBe("\nUnlike conventional online services, TiddlyWiki lets you choose where to keep your data\n"); + expect(wiki.filterTiddlers("[[This is equation $$x$$ end.]search-replace[equation $$x$$ end.],[relation $$x$$ finish.]]").join(",")).toBe("This is relation $$x$$ finish."); + expect(wiki.filterTiddlers("[[This is an amazing TiddlyWiki]] [[How old is TiddlyWiki?. TiddlyWiki is great]] [[My TiddlyWiki is so fast.]] +[search-replace:g:regexp[TiddlyWiki],[TW]]").join(",")).toBe("This is an amazing TW,How old is TW?. TW is great,My TW is so fast."); + expect(wiki.filterTiddlers("[[This is an amazing TiddlyWiki]] [[How old is TiddlyWiki?. TiddlyWiki is great]] [[My TiddlyWiki is so fast.]] +[search-replace::regexp[TiddlyWiki],[TW]]").join(",")).toBe("This is an amazing TW,How old is TW?. TiddlyWiki is great,My TW is so fast."); + expect(wiki.filterTiddlers("[[This is an amazing TiddlyWiki]] [[How old is TiddlyWiki?. TiddlyWiki is great]] [[My TiddlyWiki is so fast.]] +[search-replace:g[TiddlyWiki],[TW]]").join(",")).toBe("This is an amazing TW,How old is TW?. TW is great,My TW is so fast."); }); it("should handle the pad operator", function() { diff --git a/editions/tw5.com/tiddlers/filters/examples/search-replace Operator (Examples).tid b/editions/tw5.com/tiddlers/filters/examples/search-replace Operator (Examples).tid index cb8050dbb..25b4d1ea7 100644 --- a/editions/tw5.com/tiddlers/filters/examples/search-replace Operator (Examples).tid +++ b/editions/tw5.com/tiddlers/filters/examples/search-replace Operator (Examples).tid @@ -1,5 +1,5 @@ created: 20201107112846692 -modified: 20201118103305351 +modified: 20211101125225197 tags: [[Operator Examples]] [[search-replace Operator]] title: search-replace Operator (Examples) type: text/vnd.tiddlywiki @@ -25,8 +25,14 @@ You can also use regular expression capture groups in the replacement string: `\define names() (\w+)\s(\w+)` <<.operator-example 4 """[[John Smith]search-replace::regexp,[$2,$1]]""" >> +You can reference the portion of the input that matches the regular expression with `$&`: +<<.operator-example 5 """[[John Smith]search-replace::regexp[John .*],[His name is $&]]""">> + +<<.operator-example 6 """[[This is an exciting feature]search-replace::regexp[exciting],[amazing and $&]]""">> + To replace everything but a match using a regular expression and the ''multiline'' (m) flag: `\define myregexp2() ^(?!Unlike).*$` -<<.operator-example 5 """[[HelloThere]get[text]search-replace:gm:regexp,[]]""">> +<<.operator-example 7 """[[HelloThere]get[text]search-replace:gm:regexp,[]]""">> + {{How to remove stop words}}