@@ -26,4 +26,7 @@ build/
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# Claude Code files
|
||||
CLAUDE.md
|
||||
@@ -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.
|
||||
|
||||

|
||||
@@ -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.
|
||||
|
||||

|
||||
|
||||
### 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")
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 29 KiB |
@@ -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);
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)
|
||||
@@ -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,)
|
||||
|
||||
@@ -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,)}
|
||||
@@ -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,)
|
||||
@@ -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,)
|
||||
@@ -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()
|
||||