Files

432 lines
14 KiB
Python
Raw Permalink Normal View History

2025-05-12 16:29:32 -04:00
import re
2026-04-16 21:20:16 -07:00
import json
2025-08-22 05:03:57 +03:00
from typing_extensions import override
2025-05-12 16:29:32 -04:00
2025-08-22 05:03:57 +03:00
from comfy_api.latest import ComfyExtension, io
2025-05-12 16:29:32 -04:00
2025-08-22 05:03:57 +03:00
class StringConcatenate(io.ComfyNode):
2025-05-12 16:29:32 -04:00
@classmethod
2025-08-22 05:03:57 +03:00
def define_schema(cls):
return io.Schema(
node_id="StringConcatenate",
display_name="Text Concatenate",
2025-08-22 05:03:57 +03:00
category="utils/string",
search_aliases=["Concatenate", "text concat", "join text", "merge text", "combine strings", "concat", "concatenate", "append text", "combine text", "string"],
2025-08-22 05:03:57 +03:00
inputs=[
io.String.Input("string_a", multiline=True),
io.String.Input("string_b", multiline=True),
io.String.Input("delimiter", multiline=False, default=""),
],
outputs=[
io.String.Output(),
]
)
2025-05-12 16:29:32 -04:00
@classmethod
2025-08-22 05:03:57 +03:00
def execute(cls, string_a, string_b, delimiter):
return io.NodeOutput(delimiter.join((string_a, string_b)))
class StringSubstring(io.ComfyNode):
2025-05-12 16:29:32 -04:00
@classmethod
2025-08-22 05:03:57 +03:00
def define_schema(cls):
return io.Schema(
node_id="StringSubstring",
search_aliases=["Substring", "extract text", "text portion"],
display_name="Text Substring",
2025-08-22 05:03:57 +03:00
category="utils/string",
inputs=[
io.String.Input("string", multiline=True),
io.Int.Input("start"),
io.Int.Input("end"),
],
outputs=[
io.String.Output(),
]
)
2025-05-12 16:29:32 -04:00
2025-08-22 05:03:57 +03:00
@classmethod
def execute(cls, string, start, end):
return io.NodeOutput(string[start:end])
2025-05-12 16:29:32 -04:00
2025-08-22 05:03:57 +03:00
class StringLength(io.ComfyNode):
@classmethod
def define_schema(cls):
return io.Schema(
node_id="StringLength",
search_aliases=["character count", "text size", "string length"],
display_name="Text Length",
2025-08-22 05:03:57 +03:00
category="utils/string",
inputs=[
io.String.Input("string", multiline=True),
],
outputs=[
io.Int.Output(display_name="length"),
]
)
2025-05-12 16:29:32 -04:00
@classmethod
2025-08-22 05:03:57 +03:00
def execute(cls, string):
return io.NodeOutput(len(string))
class CaseConverter(io.ComfyNode):
@classmethod
def define_schema(cls):
return io.Schema(
node_id="CaseConverter",
search_aliases=["Case Converter", "text case", "uppercase", "lowercase", "capitalize"],
display_name="Text Case Converter",
2025-08-22 05:03:57 +03:00
category="utils/string",
inputs=[
io.String.Input("string", multiline=True),
io.Combo.Input("mode", options=["UPPERCASE", "lowercase", "Capitalize", "Title Case"]),
],
outputs=[
io.String.Output(),
]
)
@classmethod
def execute(cls, string, mode):
2025-05-12 16:29:32 -04:00
if mode == "UPPERCASE":
result = string.upper()
elif mode == "lowercase":
result = string.lower()
elif mode == "Capitalize":
result = string.capitalize()
elif mode == "Title Case":
result = string.title()
else:
result = string
2025-08-22 05:03:57 +03:00
return io.NodeOutput(result)
2025-05-12 16:29:32 -04:00
2025-08-22 05:03:57 +03:00
class StringTrim(io.ComfyNode):
@classmethod
def define_schema(cls):
return io.Schema(
node_id="StringTrim",
search_aliases=["Trim", "clean whitespace", "remove whitespace", "strip"],
display_name="Text Trim",
2025-08-22 05:03:57 +03:00
category="utils/string",
inputs=[
io.String.Input("string", multiline=True),
io.Combo.Input("mode", options=["Both", "Left", "Right"]),
],
outputs=[
io.String.Output(),
]
)
2025-05-12 16:29:32 -04:00
@classmethod
2025-08-22 05:03:57 +03:00
def execute(cls, string, mode):
2025-05-12 16:29:32 -04:00
if mode == "Both":
result = string.strip()
elif mode == "Left":
result = string.lstrip()
elif mode == "Right":
result = string.rstrip()
else:
result = string
2025-08-22 05:03:57 +03:00
return io.NodeOutput(result)
2025-05-12 16:29:32 -04:00
2025-08-22 05:03:57 +03:00
class StringReplace(io.ComfyNode):
2025-05-12 16:29:32 -04:00
@classmethod
2025-08-22 05:03:57 +03:00
def define_schema(cls):
return io.Schema(
node_id="StringReplace",
search_aliases=["Replace", "find and replace", "substitute", "swap text"],
display_name="Text Replace",
2025-08-22 05:03:57 +03:00
category="utils/string",
inputs=[
io.String.Input("string", multiline=True),
io.String.Input("find", multiline=True),
io.String.Input("replace", multiline=True),
],
outputs=[
io.String.Output(),
]
)
2025-05-12 16:29:32 -04:00
2025-08-22 05:03:57 +03:00
@classmethod
def execute(cls, string, find, replace):
return io.NodeOutput(string.replace(find, replace))
2025-05-12 16:29:32 -04:00
2025-08-22 05:03:57 +03:00
class StringContains(io.ComfyNode):
@classmethod
def define_schema(cls):
return io.Schema(
node_id="StringContains",
search_aliases=["Contains", "text includes", "string includes"],
display_name="Text Contains",
2025-08-22 05:03:57 +03:00
category="utils/string",
inputs=[
io.String.Input("string", multiline=True),
io.String.Input("substring", multiline=True),
io.Boolean.Input("case_sensitive", default=True, advanced=True),
2025-08-22 05:03:57 +03:00
],
outputs=[
io.Boolean.Output(display_name="contains"),
]
)
2025-05-12 16:29:32 -04:00
@classmethod
2025-08-22 05:03:57 +03:00
def execute(cls, string, substring, case_sensitive):
2025-05-12 16:29:32 -04:00
if case_sensitive:
contains = substring in string
else:
contains = substring.lower() in string.lower()
2025-08-22 05:03:57 +03:00
return io.NodeOutput(contains)
2025-05-12 16:29:32 -04:00
2025-08-22 05:03:57 +03:00
class StringCompare(io.ComfyNode):
2025-05-12 16:29:32 -04:00
@classmethod
2025-08-22 05:03:57 +03:00
def define_schema(cls):
return io.Schema(
node_id="StringCompare",
search_aliases=["Compare", "text match", "string equals", "starts with", "ends with"],
display_name="Text Compare",
2025-08-22 05:03:57 +03:00
category="utils/string",
inputs=[
io.String.Input("string_a", multiline=True),
io.String.Input("string_b", multiline=True),
io.Combo.Input("mode", options=["Starts With", "Ends With", "Equal"]),
io.Boolean.Input("case_sensitive", default=True, advanced=True),
2025-08-22 05:03:57 +03:00
],
outputs=[
io.Boolean.Output(),
]
)
@classmethod
def execute(cls, string_a, string_b, mode, case_sensitive):
2025-05-12 16:29:32 -04:00
if case_sensitive:
a = string_a
b = string_b
else:
a = string_a.lower()
b = string_b.lower()
if mode == "Equal":
2025-08-22 05:03:57 +03:00
return io.NodeOutput(a == b)
2025-05-12 16:29:32 -04:00
elif mode == "Starts With":
2025-08-22 05:03:57 +03:00
return io.NodeOutput(a.startswith(b))
2025-05-12 16:29:32 -04:00
elif mode == "Ends With":
2025-08-22 05:03:57 +03:00
return io.NodeOutput(a.endswith(b))
2025-05-12 16:29:32 -04:00
2025-08-22 05:03:57 +03:00
class RegexMatch(io.ComfyNode):
2025-05-12 16:29:32 -04:00
@classmethod
2025-08-22 05:03:57 +03:00
def define_schema(cls):
return io.Schema(
node_id="RegexMatch",
search_aliases=["Regex Match", "regex", "pattern match", "text contains", "string match"],
display_name="Text Match",
2025-08-22 05:03:57 +03:00
category="utils/string",
inputs=[
io.String.Input("string", multiline=True),
io.String.Input("regex_pattern", multiline=True),
io.Boolean.Input("case_insensitive", default=True, advanced=True),
io.Boolean.Input("multiline", default=False, advanced=True),
io.Boolean.Input("dotall", default=False, advanced=True),
2025-08-22 05:03:57 +03:00
],
outputs=[
io.Boolean.Output(display_name="matches"),
]
)
@classmethod
def execute(cls, string, regex_pattern, case_insensitive, multiline, dotall):
2025-05-12 16:29:32 -04:00
flags = 0
if case_insensitive:
flags |= re.IGNORECASE
if multiline:
flags |= re.MULTILINE
if dotall:
flags |= re.DOTALL
try:
match = re.search(regex_pattern, string, flags)
result = match is not None
except re.error:
result = False
2025-08-22 05:03:57 +03:00
return io.NodeOutput(result)
2025-05-12 16:29:32 -04:00
2025-08-22 05:03:57 +03:00
class RegexExtract(io.ComfyNode):
@classmethod
def define_schema(cls):
return io.Schema(
node_id="RegexExtract",
search_aliases=["Regex Extract", "regex", "pattern extract", "text parser", "parse text"],
display_name="Text Extract Substring",
2025-08-22 05:03:57 +03:00
category="utils/string",
inputs=[
io.String.Input("string", multiline=True),
io.String.Input("regex_pattern", multiline=True),
io.Combo.Input("mode", options=["First Match", "All Matches", "First Group", "All Groups"]),
io.Boolean.Input("case_insensitive", default=True, advanced=True),
io.Boolean.Input("multiline", default=False, advanced=True),
io.Boolean.Input("dotall", default=False, advanced=True),
io.Int.Input("group_index", default=1, min=0, max=100, advanced=True),
2025-08-22 05:03:57 +03:00
],
outputs=[
io.String.Output(),
]
)
2025-05-12 16:29:32 -04:00
@classmethod
2025-08-22 05:03:57 +03:00
def execute(cls, string, regex_pattern, mode, case_insensitive, multiline, dotall, group_index):
2025-05-12 16:29:32 -04:00
join_delimiter = "\n"
flags = 0
if case_insensitive:
flags |= re.IGNORECASE
if multiline:
flags |= re.MULTILINE
if dotall:
flags |= re.DOTALL
try:
if mode == "First Match":
match = re.search(regex_pattern, string, flags)
if match:
result = match.group(0)
else:
result = ""
elif mode == "All Matches":
matches = re.findall(regex_pattern, string, flags)
if matches:
if isinstance(matches[0], tuple):
result = join_delimiter.join([m[0] for m in matches])
else:
result = join_delimiter.join(matches)
else:
result = ""
elif mode == "First Group":
match = re.search(regex_pattern, string, flags)
if match and len(match.groups()) >= group_index:
result = match.group(group_index)
else:
result = ""
elif mode == "All Groups":
matches = re.finditer(regex_pattern, string, flags)
results = []
for match in matches:
if match.groups() and len(match.groups()) >= group_index:
results.append(match.group(group_index))
result = join_delimiter.join(results)
else:
result = ""
except re.error:
result = ""
2025-08-22 05:03:57 +03:00
return io.NodeOutput(result)
2025-05-12 16:29:32 -04:00
2025-08-22 05:03:57 +03:00
class RegexReplace(io.ComfyNode):
@classmethod
def define_schema(cls):
return io.Schema(
node_id="RegexReplace",
search_aliases=["Regex Replace", "regex", "pattern replace", "regex replace", "substitution"],
display_name="Text Replace (Regex)",
2025-08-22 05:03:57 +03:00
category="utils/string",
description="Find and replace text using regex patterns.",
inputs=[
io.String.Input("string", multiline=True),
io.String.Input("regex_pattern", multiline=True),
io.String.Input("replace", multiline=True),
io.Boolean.Input("case_insensitive", default=True, optional=True, advanced=True),
io.Boolean.Input("multiline", default=False, optional=True, advanced=True),
io.Boolean.Input("dotall", default=False, optional=True, advanced=True, tooltip="When enabled, the dot (.) character will match any character including newline characters. When disabled, dots won't match newlines."),
io.Int.Input("count", default=0, min=0, max=100, optional=True, advanced=True, tooltip="Maximum number of replacements to make. Set to 0 to replace all occurrences (default). Set to 1 to replace only the first match, 2 for the first two matches, etc."),
2025-08-22 05:03:57 +03:00
],
outputs=[
io.String.Output(),
]
)
@classmethod
2025-08-22 05:03:57 +03:00
def execute(cls, string, regex_pattern, replace, case_insensitive=True, multiline=False, dotall=False, count=0):
flags = 0
if case_insensitive:
flags |= re.IGNORECASE
if multiline:
flags |= re.MULTILINE
if dotall:
flags |= re.DOTALL
result = re.sub(regex_pattern, replace, string, count=count, flags=flags)
2025-08-22 05:03:57 +03:00
return io.NodeOutput(result)
2026-04-16 21:20:16 -07:00
class JsonExtractString(io.ComfyNode):
@classmethod
def define_schema(cls):
return io.Schema(
node_id="JsonExtractString",
display_name="Extract String from JSON",
category="utils/string",
search_aliases=["json", "extract json", "parse json", "json value", "read json"],
inputs=[
io.String.Input("json_string", multiline=True),
io.String.Input("key", multiline=False),
],
outputs=[
io.String.Output(),
]
)
@classmethod
def execute(cls, json_string, key):
try:
data = json.loads(json_string)
if isinstance(data, dict) and key in data:
value = data[key]
if value is None:
return io.NodeOutput("")
return io.NodeOutput(str(value))
return io.NodeOutput("")
except (json.JSONDecodeError, TypeError):
return io.NodeOutput("")
2025-08-22 05:03:57 +03:00
class StringExtension(ComfyExtension):
@override
async def get_node_list(self) -> list[type[io.ComfyNode]]:
return [
StringConcatenate,
StringSubstring,
StringLength,
CaseConverter,
StringTrim,
StringReplace,
StringContains,
StringCompare,
RegexMatch,
RegexExtract,
RegexReplace,
2026-04-16 21:20:16 -07:00
JsonExtractString,
2025-08-22 05:03:57 +03:00
]
async def comfy_entrypoint() -> StringExtension:
return StringExtension()