Minor updates
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# String Essentials - Custom nodes for ComfyUI
|
||||
|
||||
Simple string manipulation nodes for ComfyUI (strip/remove text strings, search and replace text strings, preview modified string outputs). Particularly useful for modifying text from LLM outputs (Ollama, Claude.ai, OpenAI ChatGPT), such as prompts or image-to-text description results. No bloat/additional Python dependencies or external modules required. Nodes use a small amount of basic Python and Javascript code.
|
||||
Simple string manipulation nodes for ComfyUI (strip/remove text strings, search and replace text strings, preview modified string outputs). Useful for modifying text from LLM outputs (Ollama, Claude.ai, OpenAI ChatGPT), such as prompts or image-to-text description results. No additional Python dependencies or external modules required. Nodes use a small amount of basic Python and Javascript code.
|
||||
|
||||
## Nodes included:
|
||||
|
||||
@@ -35,7 +35,7 @@ Removes specified strings from input text. Each line should contain the string t
|
||||
- **`remove_extra_spaces`**: Clean up extra spaces after removal
|
||||
|
||||
## String 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.
|
||||
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.
|
||||
|
||||

|
||||
|
||||
@@ -54,8 +54,9 @@ Simply displays a string output. Can be used to view the results/changes to the
|
||||
|
||||
## Example Usage
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
1. Navigate to the `custom_nodes` directory in your ComfyUI installation path.
|
||||
@@ -67,5 +68,5 @@ git clone https://github.com/bradsec/ComfyUI_StringEssentials.git
|
||||
4. Select node from the `Add Node` > `utils` menu.
|
||||
|
||||
|
||||
### [If you found these nodes useful please ⭐ Star the repo at github.com/bradsec/ComfyUI_StringEssentials.](https://github.com/bradsec/ComfyUI_StringEssentials "BRADSEC ComfyUI_StringEssentials")
|
||||
### [Found these nodes useful? ⭐ Star the repo github.com/bradsec/ComfyUI_StringEssentials.](https://github.com/bradsec/ComfyUI_StringEssentials "BRADSEC ComfyUI_StringEssentials")
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 135 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 200 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 140 KiB |
+1
-1
@@ -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.3"
|
||||
version = "1.0.4"
|
||||
license = {file = "LICENSE"}
|
||||
|
||||
[project.urls]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
class StringPreviewNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {},
|
||||
}
|
||||
|
||||
+26
-11
@@ -2,7 +2,7 @@ import re
|
||||
|
||||
class StringReplaceNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"replacement_pairs": ("STRING", {"multiline": True, "default": "",
|
||||
@@ -23,10 +23,14 @@ class StringReplaceNode:
|
||||
FUNCTION = "string_replace"
|
||||
CATEGORY = "utils"
|
||||
|
||||
def string_replace(self, input_string, replacement_pairs, replacement_delimiter, match_case, match_whole_string, remove_extra_spaces):
|
||||
result = input_string
|
||||
flags = 0 if match_case else re.IGNORECASE
|
||||
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):
|
||||
# Create a list of all replacements
|
||||
replacements = []
|
||||
for line in replacement_pairs.splitlines():
|
||||
line = line.strip()
|
||||
if not line or replacement_delimiter not in line:
|
||||
@@ -38,17 +42,28 @@ class StringReplaceNode:
|
||||
|
||||
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:
|
||||
pattern = re.escape(search_str)
|
||||
pattern = r"(?<!\S)" + pattern + r"(?!\S)"
|
||||
# Enhanced pattern to handle punctuation properly
|
||||
pattern = r'(?:\b|(?<=\s)|^)' + re.escape(search_str) + r'(?:[,;:.])?(?=\s|$)'
|
||||
else:
|
||||
pattern = re.escape(search_str)
|
||||
|
||||
result = re.sub(pattern, replace_str, result, flags=flags)
|
||||
# For non-whole string matching, still handle punctuation
|
||||
pattern = re.escape(search_str) + r'(?:[,;:.])?'
|
||||
|
||||
regex = re.compile(pattern, flags=flags)
|
||||
result = regex.sub(replace_str, result)
|
||||
|
||||
if remove_extra_spaces:
|
||||
result = re.sub(r'\s+', ' ', result)
|
||||
result = result.strip()
|
||||
result = self.cleanup_text(result)
|
||||
|
||||
return (result,)
|
||||
+34
-15
@@ -2,7 +2,7 @@ import re
|
||||
|
||||
class StringStripNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"strings_to_remove": ("STRING", {"multiline": True, "default": "",
|
||||
@@ -21,27 +21,46 @@ class StringStripNode:
|
||||
FUNCTION = "string_strip"
|
||||
CATEGORY = "utils"
|
||||
|
||||
def string_strip(self, input_string, strings_to_remove, remove_extra_spaces, match_case, match_whole_string):
|
||||
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):
|
||||
# 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
|
||||
|
||||
for string_to_remove in strings_to_remove.splitlines():
|
||||
string_to_remove = string_to_remove.strip()
|
||||
if not string_to_remove:
|
||||
continue
|
||||
|
||||
# Compile patterns once for better performance
|
||||
patterns = []
|
||||
for _, string_to_remove in removals:
|
||||
if match_whole_string:
|
||||
pattern = re.escape(string_to_remove)
|
||||
pattern = r"(?<!\S)" + pattern + r"(?!\S)"
|
||||
# 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|$)'
|
||||
else:
|
||||
pattern = re.escape(string_to_remove)
|
||||
# For non-whole string matching, still handle punctuation
|
||||
pattern = re.escape(string_to_remove) + r'(?:[,;:.])?'
|
||||
|
||||
patterns.append(re.compile(pattern, flags=flags))
|
||||
|
||||
result = re.sub(pattern, "", result, flags=flags)
|
||||
# Apply all removal patterns
|
||||
for pattern in patterns:
|
||||
result = pattern.sub('', result)
|
||||
|
||||
if remove_extra_spaces:
|
||||
# Replace multiple spaces with single space
|
||||
result = re.sub(r'\s+', ' ', result)
|
||||
result = self.cleanup_text(result)
|
||||
|
||||
# Strip leading and trailing spaces
|
||||
result = result.strip()
|
||||
return (result,)
|
||||
@@ -1,6 +1,6 @@
|
||||
class StringTextboxNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"input_string": ("STRING", {
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
import unittest
|
||||
from string_strip_node import StringStripNode
|
||||
from string_replace_node import StringReplaceNode
|
||||
|
||||
class TestStringNodes(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.strip_node = StringStripNode()
|
||||
self.replace_node = StringReplaceNode()
|
||||
|
||||
def test_strip_basic_functionality(self):
|
||||
input_string = "This is a test string with test word"
|
||||
strings_to_remove = "test"
|
||||
result = self.strip_node.string_strip(
|
||||
input_string=input_string,
|
||||
strings_to_remove=strings_to_remove,
|
||||
match_case=False,
|
||||
match_whole_string=True,
|
||||
remove_extra_spaces=True
|
||||
)[0]
|
||||
self.assertEqual(result, "This is a string with word")
|
||||
|
||||
def test_strip_multiple_strings(self):
|
||||
input_string = "This is a test string with another test and More Text"
|
||||
strings_to_remove = """test
|
||||
More Text"""
|
||||
result = self.strip_node.string_strip(
|
||||
input_string=input_string,
|
||||
strings_to_remove=strings_to_remove,
|
||||
match_case=False,
|
||||
match_whole_string=True,
|
||||
remove_extra_spaces=True
|
||||
)[0]
|
||||
self.assertEqual(result, "This is a string with another and")
|
||||
|
||||
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,
|
||||
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,
|
||||
remove_extra_spaces=True
|
||||
)[0]
|
||||
self.assertEqual(result2, "Test TEST")
|
||||
|
||||
def test_strip_punctuation_handling(self):
|
||||
input_string = "test, test: test. test; test"
|
||||
strings_to_remove = "test"
|
||||
result = self.strip_node.string_strip(
|
||||
input_string=input_string,
|
||||
strings_to_remove=strings_to_remove,
|
||||
match_case=False,
|
||||
match_whole_string=True,
|
||||
remove_extra_spaces=True
|
||||
)[0]
|
||||
self.assertEqual(result, "")
|
||||
|
||||
def test_replace_basic_functionality(self):
|
||||
input_string = "Hello world"
|
||||
replacement_pairs = "world::everyone"
|
||||
result = self.replace_node.string_replace(
|
||||
input_string=input_string,
|
||||
replacement_pairs=replacement_pairs,
|
||||
replacement_delimiter="::",
|
||||
match_case=False,
|
||||
match_whole_string=True,
|
||||
remove_extra_spaces=True
|
||||
)[0]
|
||||
self.assertEqual(result, "Hello everyone")
|
||||
|
||||
def test_replace_multiple_pairs(self):
|
||||
input_string = "The quick brown fox jumps over the lazy dog"
|
||||
replacement_pairs = """quick::slow
|
||||
brown::black
|
||||
fox::cat"""
|
||||
result = self.replace_node.string_replace(
|
||||
input_string=input_string,
|
||||
replacement_pairs=replacement_pairs,
|
||||
replacement_delimiter="::",
|
||||
match_case=False,
|
||||
match_whole_string=True,
|
||||
remove_extra_spaces=True
|
||||
)[0]
|
||||
self.assertEqual(result, "The slow black cat jumps over the lazy dog")
|
||||
|
||||
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,
|
||||
replacement_pairs=replacement_pairs,
|
||||
replacement_delimiter="::",
|
||||
match_case=False,
|
||||
match_whole_string=True,
|
||||
remove_extra_spaces=True
|
||||
)[0]
|
||||
self.assertEqual(result1, "hi hi hi")
|
||||
|
||||
# Case-sensitive
|
||||
result2 = self.replace_node.string_replace(
|
||||
input_string=input_string,
|
||||
replacement_pairs=replacement_pairs,
|
||||
replacement_delimiter="::",
|
||||
match_case=True,
|
||||
match_whole_string=True,
|
||||
remove_extra_spaces=True
|
||||
)[0]
|
||||
self.assertEqual(result2, "Hello HELLO hi")
|
||||
|
||||
def test_replace_custom_delimiter(self):
|
||||
input_string = "Hello world"
|
||||
replacement_pairs = "world->everyone"
|
||||
result = self.replace_node.string_replace(
|
||||
input_string=input_string,
|
||||
replacement_pairs=replacement_pairs,
|
||||
replacement_delimiter="->",
|
||||
match_case=False,
|
||||
match_whole_string=True,
|
||||
remove_extra_spaces=True
|
||||
)[0]
|
||||
self.assertEqual(result, "Hello everyone")
|
||||
|
||||
def test_extra_spaces_handling(self):
|
||||
# Test for StringStripNode
|
||||
input_string = "This is a test string"
|
||||
strings_to_remove = "test"
|
||||
result1 = self.strip_node.string_strip(
|
||||
input_string=input_string,
|
||||
strings_to_remove=strings_to_remove,
|
||||
match_case=False,
|
||||
match_whole_string=True,
|
||||
remove_extra_spaces=True
|
||||
)[0]
|
||||
self.assertEqual(result1, "This is a string")
|
||||
|
||||
# Test for StringReplaceNode
|
||||
replacement_pairs = "is::was"
|
||||
result2 = self.replace_node.string_replace(
|
||||
input_string=input_string,
|
||||
replacement_pairs=replacement_pairs,
|
||||
replacement_delimiter="::",
|
||||
match_case=False,
|
||||
match_whole_string=True,
|
||||
remove_extra_spaces=True
|
||||
)[0]
|
||||
self.assertEqual(result2, "This was a test string")
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user