Add new conditional append node Issue #5 recoommendation. Minor fixes and changes Issues #4 and #6.

This commit is contained in:
BRADSEC
2025-12-02 20:59:02 +10:00
parent a482111411
commit ba182e390e
17 changed files with 398 additions and 50 deletions
+4 -1
View File
@@ -26,4 +26,7 @@ build/
# OS
.DS_Store
Thumbs.db
Thumbs.db
# Claude Code files
CLAUDE.md
+25 -4
View File
@@ -6,16 +6,19 @@ Simple string manipulation nodes for ComfyUI (strip/remove text strings, search
- String Textbox
- String Strip
- String Replace
- String Multi Replace
- String Conditional Append
- String Preview
***Once added nodes can be found under the `Add Node` > `utils` menu.***
***Once added nodes can be found under the `Add Node` > `utils` > `StringEssentials` menu.***
## Features
- Remove or replace text, words, numbers in input strings
- Conditionally append text only when not already present
- Handle multi-word strings (partial sentences/phrases)
- Case-sensitive/insensitive matching
- Whole string matching option
- Preserve punctuation in prompts and special syntax (parentheses, colons, brackets)
## String Textbox
A simple multiline textbox for free form text.
@@ -32,9 +35,10 @@ Removes specified strings from input text. Each line should contain the string t
- **`strings_to_remove`**: List of strings to remove (one per line)
- **`match_case`**: Enable/disable case-sensitive matching
- **`match_whole_string`**: Ensure only exact matches are removed
- **`preserve_punctuation`**: Keep punctuation marks adjacent to matched text (default: enabled)
- **`remove_extra_spaces`**: Clean up extra spaces after removal
## String Replace
## String Multi Replace
Replaces text strings with new strings (replace/swap words etc.). Each line should contain a search and replace pair separated by the replacement_delimiter (default is double colon `::`). Example to replace dog with cat `dog::cat`. Supports mulitple string replacements, just add one replacement per line. You can also remove strings with this node just leave the right side of the delimiter blank.
![stringreplace](images/stringreplace.png)
@@ -45,8 +49,25 @@ Replaces text strings with new strings (replace/swap words etc.). Each line shou
- **`replacement_delimiter`**: Character(s) that separate search and replace strings (default: `::`)
- **`match_case`**: Enable/disable case-sensitive matching
- **`match_whole_string`**: Ensure only exact matches are replaced
- **`preserve_punctuation`**: Keep punctuation marks adjacent to matched text (default: enabled)
- **`remove_extra_spaces`**: Clean up extra spaces after replacement
## String Conditional Append
Conditionally adds strings to the input text only if they're not already present. Useful for ensuring specific keywords or phrases exist in prompts without creating duplicates. Enter multiple strings (one per line) to check and append any that are missing.
![stringreplace](images/stringappend.png)
### Inputs
- **`input_string`**: The string or text to process (connection input)
- **`strings_to_check`**: List of strings to search for and append if not found (one per line)
- **`position`**: Where to add strings if not found (`end` or `beginning`)
- **`match_case`**: Enable/disable case-sensitive search
- **`separator`**: Character(s) to use between text and appended strings (default: `, `)
### Outputs
- **`output_string`**: The resulting text (with or without appended strings)
- **`was_appended`**: Boolean indicating whether any strings were added
## String Preview
Simply displays a string output. Can be used to view the results/changes to the input_string by the `String Strip` or `String Replace`.
@@ -65,7 +86,7 @@ Simply displays a string output. Can be used to view the results/changes to the
git clone https://github.com/bradsec/ComfyUI_StringEssentials.git
```
3. Restart ComfyUI or refresh your nodes.
4. Select node from the `Add Node` > `utils` menu.
4. Select nodes from the `Add Node` > `utils` > `StringEssentials` menu.
### [Found these nodes useful? ⭐ Star the repo github.com/bradsec/ComfyUI_StringEssentials.](https://github.com/bradsec/ComfyUI_StringEssentials "BRADSEC ComfyUI_StringEssentials")
+6 -3
View File
@@ -2,21 +2,24 @@
from .string_textbox_node import StringTextboxNode
from .string_strip_node import StringStripNode
from .string_multi_replace_node import StringMultiReplaceNode
from .string_multi_replace_node import StringMultiReplaceNode
from .string_preview_node import StringPreviewNode
from .string_conditional_append_node import StringConditionalAppendNode
NODE_CLASS_MAPPINGS = {
"StringTextbox": StringTextboxNode,
"StringStrip": StringStripNode,
"StringMultiReplace": StringMultiReplaceNode,
"StringPreview": StringPreviewNode
"StringPreview": StringPreviewNode,
"StringConditionalAppend": StringConditionalAppendNode
}
NODE_DISPLAY_NAME_MAPPINGS = {
"StringTextbox": "String Textbox",
"StringStrip": "String Strip",
"StringMultiReplace": "String Multi Replace",
"StringPreview": "String Preview"
"StringPreview": "String Preview",
"StringConditionalAppend": "String Conditional Append"
}
WEB_DIRECTORY = "./js"
Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

+45
View File
@@ -1,12 +1,16 @@
import { app } from "../../../scripts/app.js";
import { ComfyWidgets } from "../../../scripts/widgets.js";
// Consistent sizing for all String Essentials nodes
const DEFAULT_NODE_SIZE = [400, 200];
app.registerExtension({
name: "string.strip",
async beforeRegisterNodeDef(nodeType) {
if (nodeType.comfyClass === "StringStrip") {
nodeType.prototype.onNodeCreated = function() {
this.addInput("input_string", "STRING");
this.setSize(DEFAULT_NODE_SIZE);
};
}
}
@@ -18,6 +22,7 @@ app.registerExtension({
if (nodeType.comfyClass === "StringMultiReplace") {
nodeType.prototype.onNodeCreated = function() {
this.addInput("input_string", "STRING");
this.setSize(DEFAULT_NODE_SIZE);
};
}
}
@@ -32,6 +37,7 @@ app.registerExtension({
this.showValueWidget = ComfyWidgets["STRING"](this, "preview", ["STRING", { multiline: true }], app).widget;
this.showValueWidget.inputEl.readOnly = true;
this.showValueWidget.inputEl.style.opacity = 0.6;
this.setSize(DEFAULT_NODE_SIZE);
};
const onExecuted = nodeType.prototype.onExecuted;
@@ -39,8 +45,47 @@ app.registerExtension({
onExecuted?.apply(this, arguments);
if (message?.text?.[0] !== undefined) {
this.showValueWidget.value = message.text[0];
} else {
// Clear preview if no text received
this.showValueWidget.value = "";
}
};
// Clear preview when input is disconnected
const onConnectionsChange = nodeType.prototype.onConnectionsChange;
nodeType.prototype.onConnectionsChange = function(type, index, connected, link_info) {
if (onConnectionsChange) {
onConnectionsChange.apply(this, arguments);
}
// If input was disconnected, clear the preview
if (type === 1 && !connected) { // type 1 = input
this.showValueWidget.value = "";
}
};
}
}
});
app.registerExtension({
name: "string.conditionalappend",
async beforeRegisterNodeDef(nodeType) {
if (nodeType.comfyClass === "StringConditionalAppend") {
nodeType.prototype.onNodeCreated = function() {
this.addInput("input_string", "STRING");
this.setSize(DEFAULT_NODE_SIZE);
};
}
}
});
app.registerExtension({
name: "string.textbox",
async beforeRegisterNodeDef(nodeType) {
if (nodeType.comfyClass === "StringTextbox") {
nodeType.prototype.onNodeCreated = function() {
this.setSize(DEFAULT_NODE_SIZE);
};
}
}
});
+2 -2
View File
@@ -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 Replace, and String Preview."
version = "1.0.5"
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 and String Preview."
version = "1.0.6"
license = {file = "LICENSE"}
[project.urls]
+66
View File
@@ -0,0 +1,66 @@
import re
class StringConditionalAppendNode:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"strings_to_check": ("STRING", {
"multiline": True,
"default": "",
"placeholder": "Enter strings to check (one per line)",
"tooltip": "Enter strings to search for and append if not found (one per line)"
}),
"position": (["end", "beginning"], {
"default": "end",
"tooltip": "Where to add strings if not found"
}),
"match_case": ("BOOLEAN", {
"default": False,
"label_on": "enabled",
"label_off": "disabled",
"tooltip": "Whether the search should be case-sensitive"
}),
"separator": ("STRING", {
"default": ", ",
"tooltip": "Character(s) to use between text and appended strings"
})
}
}
RETURN_TYPES = ("STRING", "BOOLEAN")
RETURN_NAMES = ("output_string", "was_appended")
FUNCTION = "conditional_append"
CATEGORY = "utils/StringEssentials"
def conditional_append(self, input_string, strings_to_check, position, match_case, separator):
# Parse strings to check, one per line
strings_list = []
for line in strings_to_check.splitlines():
line = line.strip()
if line:
strings_list.append(line)
if not strings_list:
return (input_string, False)
result = input_string
was_any_appended = False
# Check and append each string if not found
for check_string in strings_list:
# Check if check_string exists in current result
if match_case:
string_found = check_string in result
else:
string_found = check_string.lower() in result.lower()
# If string not found, append it
if not string_found:
if position == "beginning":
result = check_string + separator + result
else: # end
result = result + separator + check_string
was_any_appended = True
return (result, was_any_appended)
+25 -14
View File
@@ -5,14 +5,17 @@ class StringMultiReplaceNode:
def INPUT_TYPES(cls):
return {
"required": {
"replacement_pairs": ("STRING", {"multiline": True, "default": "",
"tooltip": "Enter replacement pairs, one per line. Format: 'search<delimiter>replace'. Default delimiter is ':' (colon)"}),
"replacement_delimiter": ("STRING", {"default": "::",
"replacement_pairs": ("STRING", {"multiline": True, "default": "", "placeholder": "Enter replacement pairs (one per line)",
"tooltip": "Enter replacement pairs (one per line). Format: 'search<delimiter>replace'. Default delimiter is ':' (colon)"}),
"replacement_delimiter": ("STRING", {"default": "::",
"tooltip": "Character(s) that separate search and replace strings. Default is two '::' (colons)"}),
"match_case": ("BOOLEAN", {"default": False,
"label_on": "enabled", "label_off": "disabled"}),
"match_whole_string": ("BOOLEAN", {"default": False,
"label_on": "enabled", "label_off": "disabled"}),
"preserve_punctuation": ("BOOLEAN", {"default": True,
"label_on": "enabled", "label_off": "disabled",
"tooltip": "When enabled, punctuation marks adjacent to matched text are preserved"}),
"remove_extra_spaces": ("BOOLEAN", {"default": True,
"label_on": "enabled", "label_off": "disabled"})
}
@@ -21,14 +24,14 @@ class StringMultiReplaceNode:
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("modified_string",)
FUNCTION = "string_replace"
CATEGORY = "utils"
CATEGORY = "utils/StringEssentials"
def cleanup_text(self, text):
text = re.sub(r'\s+([,;:.])', r'\1', text) # Remove spaces before punctuation
text = re.sub(r'\s{2,}', ' ', text) # Normalize multiple spaces
return text.strip()
def string_replace(self, input_string, replacement_pairs, replacement_delimiter, match_case, match_whole_string, remove_extra_spaces):
def string_replace(self, input_string, replacement_pairs, replacement_delimiter, match_case, match_whole_string, preserve_punctuation, remove_extra_spaces):
# Create a list of all replacements
replacements = []
for line in replacement_pairs.splitlines():
@@ -42,28 +45,36 @@ class StringMultiReplaceNode:
if not search_str:
continue
replacements.append((len(search_str), search_str, replace_str))
# Sort by length in descending order
replacements.sort(reverse=True)
result = input_string
flags = 0 if match_case else re.IGNORECASE
# Perform replacements
for _, search_str, replace_str in replacements:
if match_whole_string:
# Enhanced pattern to handle punctuation properly
pattern = r'(?:\b|(?<=\s)|^)' + re.escape(search_str) + r'(?:[,;:.])?(?=\s|$)'
if preserve_punctuation:
# Match whole words but preserve adjacent punctuation
pattern = r'\b' + re.escape(search_str) + r'\b'
else:
# Original behavior: remove punctuation after match
pattern = r'(?:\b|(?<=\s)|^)' + re.escape(search_str) + r'(?:[,;:.])?(?=\s|$)'
else:
# For non-whole string matching, still handle punctuation
pattern = re.escape(search_str) + r'(?:[,;:.])?'
if preserve_punctuation:
# Simple literal replacement preserving punctuation
pattern = re.escape(search_str)
else:
# Original behavior: remove punctuation after match
pattern = re.escape(search_str) + r'(?:[,;:.])?'
regex = re.compile(pattern, flags=flags)
result = regex.sub(replace_str, result)
if remove_extra_spaces:
result = self.cleanup_text(result)
return (result,)
+7 -2
View File
@@ -4,12 +4,17 @@ class StringPreviewNode:
return {
"required": {},
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("output_string",)
FUNCTION = "preview_string"
CATEGORY = "utils"
CATEGORY = "utils/StringEssentials"
OUTPUT_NODE = True
@classmethod
def IS_CHANGED(cls, **kwargs):
# Force re-evaluation every time to ensure fresh data in PNG metadata
return float("nan")
def preview_string(self, input_string=""):
return {"ui": {"text": (input_string,)}, "result": (input_string,)}
+24 -14
View File
@@ -5,12 +5,15 @@ class StringStripNode:
def INPUT_TYPES(cls):
return {
"required": {
"strings_to_remove": ("STRING", {"multiline": True, "default": "",
"tooltip": "Enter strings to remove, one per line. Can include multiple words or strings on one line like: 'This image is'. This will only match this entire string."}),
"strings_to_remove": ("STRING", {"multiline": True, "default": "", "placeholder": "Enter strings to remove (one per line)",
"tooltip": "Enter strings to remove (one per line). Can include multiple words or strings on one line like: 'This image is'. This will only match this entire string."}),
"match_case": ("BOOLEAN", {"default": False,
"label_on": "enabled", "label_off": "disabled"}),
"match_whole_string": ("BOOLEAN", {"default": True,
"label_on": "enabled", "label_off": "disabled"}),
"preserve_punctuation": ("BOOLEAN", {"default": True,
"label_on": "enabled", "label_off": "disabled",
"tooltip": "When enabled, punctuation marks adjacent to matched text are preserved"}),
"remove_extra_spaces": ("BOOLEAN", {"default": True,
"label_on": "enabled", "label_off": "disabled"})
}
@@ -19,27 +22,27 @@ class StringStripNode:
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("modified_string",)
FUNCTION = "string_strip"
CATEGORY = "utils"
CATEGORY = "utils/StringEssentials"
def cleanup_text(self, text):
text = re.sub(r'\s+([,;:.])', r'\1', text) # Remove spaces before punctuation
text = re.sub(r'\s{2,}', ' ', text) # Normalize multiple spaces
return text.strip()
def string_strip(self, input_string, strings_to_remove, match_case, match_whole_string, remove_extra_spaces):
def string_strip(self, input_string, strings_to_remove, match_case, match_whole_string, preserve_punctuation, remove_extra_spaces):
# Create a list of strings to remove, sorted by length
removals = []
for line in strings_to_remove.splitlines():
line = line.strip()
if not line:
continue
# Store length with the string for sorting
removals.append((len(line), line))
# Sort by length in descending order to handle longer phrases first
removals.sort(reverse=True)
result = input_string
flags = 0 if match_case else re.IGNORECASE
@@ -47,13 +50,20 @@ class StringStripNode:
patterns = []
for _, string_to_remove in removals:
if match_whole_string:
# Enhanced pattern to handle punctuation properly
# Look for the string followed by optional punctuation
pattern = r'(?:\b|(?<=\s)|^)' + re.escape(string_to_remove) + r'(?:[,;:.])?(?=\s|$)'
if preserve_punctuation:
# Match whole words but preserve adjacent punctuation
pattern = r'\b' + re.escape(string_to_remove) + r'\b'
else:
# Original behavior: remove punctuation after match
pattern = r'(?:\b|(?<=\s)|^)' + re.escape(string_to_remove) + r'(?:[,;:.])?(?=\s|$)'
else:
# For non-whole string matching, still handle punctuation
pattern = re.escape(string_to_remove) + r'(?:[,;:.])?'
if preserve_punctuation:
# Simple literal removal preserving punctuation
pattern = re.escape(string_to_remove)
else:
# Original behavior: remove punctuation after match
pattern = re.escape(string_to_remove) + r'(?:[,;:.])?'
patterns.append(re.compile(pattern, flags=flags))
# Apply all removal patterns
@@ -62,5 +72,5 @@ class StringStripNode:
if remove_extra_spaces:
result = self.cleanup_text(result)
return (result,)
+1 -1
View File
@@ -14,7 +14,7 @@ class StringTextboxNode:
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("output_string",)
FUNCTION = "pass_string"
CATEGORY = "utils"
CATEGORY = "utils/StringEssentials"
def pass_string(self, input_string):
return (input_string,)
+193 -9
View File
@@ -1,11 +1,13 @@
import unittest
from string_strip_node import StringStripNode
from string_replace_node import StringReplaceNode
from string_multi_replace_node import StringMultiReplaceNode
from string_conditional_append_node import StringConditionalAppendNode
class TestStringNodes(unittest.TestCase):
def setUp(self):
self.strip_node = StringStripNode()
self.replace_node = StringReplaceNode()
self.replace_node = StringMultiReplaceNode()
self.append_node = StringConditionalAppendNode()
def test_strip_basic_functionality(self):
input_string = "This is a test string with test word"
@@ -15,6 +17,7 @@ class TestStringNodes(unittest.TestCase):
strings_to_remove=strings_to_remove,
match_case=False,
match_whole_string=True,
preserve_punctuation=True,
remove_extra_spaces=True
)[0]
self.assertEqual(result, "This is a string with word")
@@ -28,6 +31,7 @@ class TestStringNodes(unittest.TestCase):
strings_to_remove=strings_to_remove,
match_case=False,
match_whole_string=True,
preserve_punctuation=True,
remove_extra_spaces=True
)[0]
self.assertEqual(result, "This is a string with another and")
@@ -35,28 +39,45 @@ class TestStringNodes(unittest.TestCase):
def test_strip_case_sensitivity(self):
input_string = "Test TEST test"
strings_to_remove = "test"
# Case-insensitive
result1 = self.strip_node.string_strip(
input_string=input_string,
strings_to_remove=strings_to_remove,
match_case=False,
match_whole_string=True,
preserve_punctuation=True,
remove_extra_spaces=True
)[0]
self.assertEqual(result1, "")
# Case-sensitive
result2 = self.strip_node.string_strip(
input_string=input_string,
strings_to_remove=strings_to_remove,
match_case=True,
match_whole_string=True,
preserve_punctuation=True,
remove_extra_spaces=True
)[0]
self.assertEqual(result2, "Test TEST")
def test_strip_punctuation_handling(self):
def test_strip_punctuation_preserved(self):
# Test Issue #6 fix: punctuation should be preserved
input_string = "Let's meet, Grandma!"
strings_to_remove = "meet"
result = self.strip_node.string_strip(
input_string=input_string,
strings_to_remove=strings_to_remove,
match_case=False,
match_whole_string=True,
preserve_punctuation=True,
remove_extra_spaces=True
)[0]
self.assertEqual(result, "Let's, Grandma!")
def test_strip_punctuation_removed(self):
# Test legacy behavior: punctuation removed
input_string = "test, test: test. test; test"
strings_to_remove = "test"
result = self.strip_node.string_strip(
@@ -64,6 +85,7 @@ class TestStringNodes(unittest.TestCase):
strings_to_remove=strings_to_remove,
match_case=False,
match_whole_string=True,
preserve_punctuation=False,
remove_extra_spaces=True
)[0]
self.assertEqual(result, "")
@@ -77,6 +99,7 @@ class TestStringNodes(unittest.TestCase):
replacement_delimiter="::",
match_case=False,
match_whole_string=True,
preserve_punctuation=True,
remove_extra_spaces=True
)[0]
self.assertEqual(result, "Hello everyone")
@@ -92,6 +115,7 @@ class TestStringNodes(unittest.TestCase):
replacement_delimiter="::",
match_case=False,
match_whole_string=True,
preserve_punctuation=True,
remove_extra_spaces=True
)[0]
self.assertEqual(result, "The slow black cat jumps over the lazy dog")
@@ -99,7 +123,7 @@ class TestStringNodes(unittest.TestCase):
def test_replace_case_sensitivity(self):
input_string = "Hello HELLO hello"
replacement_pairs = "hello::hi"
# Case-insensitive
result1 = self.replace_node.string_replace(
input_string=input_string,
@@ -107,10 +131,11 @@ class TestStringNodes(unittest.TestCase):
replacement_delimiter="::",
match_case=False,
match_whole_string=True,
preserve_punctuation=True,
remove_extra_spaces=True
)[0]
self.assertEqual(result1, "hi hi hi")
# Case-sensitive
result2 = self.replace_node.string_replace(
input_string=input_string,
@@ -118,6 +143,7 @@ class TestStringNodes(unittest.TestCase):
replacement_delimiter="::",
match_case=True,
match_whole_string=True,
preserve_punctuation=True,
remove_extra_spaces=True
)[0]
self.assertEqual(result2, "Hello HELLO hi")
@@ -131,10 +157,41 @@ class TestStringNodes(unittest.TestCase):
replacement_delimiter="->",
match_case=False,
match_whole_string=True,
preserve_punctuation=True,
remove_extra_spaces=True
)[0]
self.assertEqual(result, "Hello everyone")
def test_replace_punctuation_preserved(self):
# Test Issue #6 fix: punctuation preserved in replace
input_string = "Let's meet, Grandma!"
replacement_pairs = "meet::eat"
result = self.replace_node.string_replace(
input_string=input_string,
replacement_pairs=replacement_pairs,
replacement_delimiter="::",
match_case=False,
match_whole_string=True,
preserve_punctuation=True,
remove_extra_spaces=True
)[0]
self.assertEqual(result, "Let's eat, Grandma!")
def test_replace_prompt_syntax_preserved(self):
# Test Issue #6: Prompt control syntax like [A1111:Forge:.5]
input_string = "A prompt with [word1:word2:.5] syntax"
replacement_pairs = "prompt::modified"
result = self.replace_node.string_replace(
input_string=input_string,
replacement_pairs=replacement_pairs,
replacement_delimiter="::",
match_case=False,
match_whole_string=True,
preserve_punctuation=True,
remove_extra_spaces=True
)[0]
self.assertEqual(result, "A modified with [word1:word2:.5] syntax")
def test_extra_spaces_handling(self):
# Test for StringStripNode
input_string = "This is a test string"
@@ -144,11 +201,12 @@ class TestStringNodes(unittest.TestCase):
strings_to_remove=strings_to_remove,
match_case=False,
match_whole_string=True,
preserve_punctuation=True,
remove_extra_spaces=True
)[0]
self.assertEqual(result1, "This is a string")
# Test for StringReplaceNode
# Test for StringMultiReplaceNode
replacement_pairs = "is::was"
result2 = self.replace_node.string_replace(
input_string=input_string,
@@ -156,9 +214,135 @@ class TestStringNodes(unittest.TestCase):
replacement_delimiter="::",
match_case=False,
match_whole_string=True,
preserve_punctuation=True,
remove_extra_spaces=True
)[0]
self.assertEqual(result2, "This was a test string")
# Tests for StringConditionalAppendNode (Issue #5)
def test_append_string_not_found(self):
# String not found, should append
input_string = "Hello world"
result, was_appended = self.append_node.conditional_append(
input_string=input_string,
strings_to_check="goodbye",
position="end",
match_case=False,
separator=", "
)
self.assertEqual(result, "Hello world, goodbye")
self.assertTrue(was_appended)
def test_append_string_found(self):
# String found, should not append
input_string = "Hello world"
result, was_appended = self.append_node.conditional_append(
input_string=input_string,
strings_to_check="world",
position="end",
match_case=False,
separator=", "
)
self.assertEqual(result, "Hello world")
self.assertFalse(was_appended)
def test_append_beginning(self):
# Append at beginning
input_string = "world"
result, was_appended = self.append_node.conditional_append(
input_string=input_string,
strings_to_check="Hello",
position="beginning",
match_case=False,
separator=", "
)
self.assertEqual(result, "Hello, world")
self.assertTrue(was_appended)
def test_append_case_insensitive(self):
# Case insensitive search
input_string = "Hello WORLD"
result, was_appended = self.append_node.conditional_append(
input_string=input_string,
strings_to_check="world",
position="end",
match_case=False,
separator=", "
)
self.assertEqual(result, "Hello WORLD")
self.assertFalse(was_appended)
def test_append_case_sensitive(self):
# Case sensitive search
input_string = "Hello WORLD"
result, was_appended = self.append_node.conditional_append(
input_string=input_string,
strings_to_check="world",
position="end",
match_case=True,
separator=", "
)
self.assertEqual(result, "Hello WORLD, world")
self.assertTrue(was_appended)
def test_append_custom_separator(self):
# Custom separator
input_string = "Hello"
result, was_appended = self.append_node.conditional_append(
input_string=input_string,
strings_to_check="world",
position="end",
match_case=False,
separator=" | "
)
self.assertEqual(result, "Hello | world")
self.assertTrue(was_appended)
def test_append_no_separator(self):
# No separator (empty string)
input_string = "Hello"
result, was_appended = self.append_node.conditional_append(
input_string=input_string,
strings_to_check="world",
position="end",
match_case=False,
separator=""
)
self.assertEqual(result, "Helloworld")
self.assertTrue(was_appended)
def test_append_multiple_strings(self):
# Multiple strings to check, some found, some not
input_string = "Hello world"
strings_to_check = """world
goodbye
thanks"""
result, was_appended = self.append_node.conditional_append(
input_string=input_string,
strings_to_check=strings_to_check,
position="end",
match_case=False,
separator=", "
)
# "world" is found, "goodbye" and "thanks" should be appended
self.assertEqual(result, "Hello world, goodbye, thanks")
self.assertTrue(was_appended)
def test_append_multiple_all_found(self):
# All strings found, nothing appended
input_string = "Hello world goodbye"
strings_to_check = """Hello
world
goodbye"""
result, was_appended = self.append_node.conditional_append(
input_string=input_string,
strings_to_check=strings_to_check,
position="end",
match_case=False,
separator=", "
)
self.assertEqual(result, "Hello world goodbye")
self.assertFalse(was_appended)
if __name__ == '__main__':
unittest.main()