From 67b5a0e37f451d8967c897fc3afa51526d696b26 Mon Sep 17 00:00:00 2001 From: BRADSEC <7948876+bradsec@users.noreply.github.com> Date: Fri, 3 Jul 2026 10:52:03 +1000 Subject: [PATCH] Insert literal replacement text verbatim when use_regex is off (review SE-2) re.sub always parses the replacement as a template, so a backslash in the replacement (e.g. C:\1) raised PatternError even in literal mode. Also drop an unused import and align js import paths with the documented two-level extension path. Bump to 2.0.13. --- js/string_essentials.js | 4 ++-- pyproject.toml | 2 +- string_conditional_append_node.py | 2 -- string_multi_replace_node.py | 5 ++++- test_string_nodes.py | 14 ++++++++++++++ 5 files changed, 21 insertions(+), 6 deletions(-) diff --git a/js/string_essentials.js b/js/string_essentials.js index 2f04366..9065636 100644 --- a/js/string_essentials.js +++ b/js/string_essentials.js @@ -1,5 +1,5 @@ -import { app } from "../../../scripts/app.js"; -import { ComfyWidgets } from "../../../scripts/widgets.js"; +import { app } from "../../scripts/app.js"; +import { ComfyWidgets } from "../../scripts/widgets.js"; // Consistent sizing for all String Essentials nodes const DEFAULT_NODE_WIDTH = 400; diff --git a/pyproject.toml b/pyproject.toml index 971cb30..8a65197 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "comfyui_stringessentials" description = "Simple string manipulation nodes for ComfyUI (strip/remove text strings, search and replace text strings, preview modified string outputs). Useful for modifying text prompts or results from LLM outputs. Nodes will be located under standard Add Node > utils menu. Node names: String Textbox, String Strip, String Multi Replace, String Conditional Append, String Contains Any and String Preview." -version = "2.0.12" +version = "2.0.13" license = {file = "LICENSE"} [project.urls] diff --git a/string_conditional_append_node.py b/string_conditional_append_node.py index bb6608f..1e8447e 100644 --- a/string_conditional_append_node.py +++ b/string_conditional_append_node.py @@ -1,5 +1,3 @@ -import re - class StringConditionalAppendNode: @classmethod def INPUT_TYPES(cls): diff --git a/string_multi_replace_node.py b/string_multi_replace_node.py index ec8e0db..27daf1e 100644 --- a/string_multi_replace_node.py +++ b/string_multi_replace_node.py @@ -95,7 +95,10 @@ class StringMultiReplaceNode: f"StringMultiReplace: invalid regex for search '{search_str}': {e}. " f"Disable use_regex to treat search strings as literal text." ) - result = regex.sub(replace_str, result) + # re.sub always parses the replacement as a template (\1, \g<>); + # in literal mode insert the user's text verbatim instead. + replacement = replace_str if use_regex else replace_str.replace("\\", "\\\\") + result = regex.sub(replacement, result) if remove_extra_spaces: result = self.cleanup_text(result) diff --git a/test_string_nodes.py b/test_string_nodes.py index 9481ce8..06cb32a 100644 --- a/test_string_nodes.py +++ b/test_string_nodes.py @@ -106,6 +106,20 @@ class TestStringNodes(unittest.TestCase): )[0] self.assertEqual(result, "Hello everyone") + def test_replace_literal_backslash_replacement(self): + # With use_regex disabled, replacement text is literal: backslash + # sequences must not be treated as re.sub group references. + result = self.replace_node.string_replace( + input_string="path is X", + replacement_pairs=r"X::C:\1", + replacement_delimiter="::", + match_case=False, + match_whole_string=True, + preserve_punctuation=True, + remove_extra_spaces=False + )[0] + self.assertEqual(result, r"path is C:\1") + def test_replace_multiple_pairs(self): input_string = "The quick brown fox jumps over the lazy dog" replacement_pairs = """quick::slow