From e486bf1e7d1a7d3139ba3bc4c223de992138da62 Mon Sep 17 00:00:00 2001 From: MeeeyoAI Date: Sat, 22 Feb 2025 20:53:20 +0800 Subject: [PATCH] ComfyUI_StringOps StringOps is a versatile text processing toolkit built for ComfyUI's node-based workflows. --- README.md | 18 +- __init__.py | 96 ++++ meyo_node_Computational.py | 197 +++++++ meyo_node_File.py | 315 +++++++++++ meyo_node_String.py | 1041 ++++++++++++++++++++++++++++++++++++ requirements.txt | 4 + 6 files changed, 1669 insertions(+), 2 deletions(-) create mode 100644 __init__.py create mode 100644 meyo_node_Computational.py create mode 100644 meyo_node_File.py create mode 100644 meyo_node_String.py create mode 100644 requirements.txt diff --git a/README.md b/README.md index ac1c094..361404d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,16 @@ -# ComfyUI_StringOps -StringOps is a versatile text processing toolkit built for ComfyUI's node-based workflows." +#[ComfyUI_StringOps]# +StringOps is a versatile text processing toolkit built for ComfyUI's node-based workflows. + + +#Install +cd ComfyUI/custom_nodes +git clone https://github.com/MeeeyoAI/ComfyUI_StringOps.git +Or download the zip file and extracted, copy the resulting folder to ComfyUI\custom_ Nodes +Restart ComfyUI. + + +#About me +- [bilibili]:https://space.bilibili.com/3546690300676691 +- [博主主页]:http://www.meeeyo.com +- [知识星球]:https://t.zsxq.com/ufSS2 + diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..ce752ab --- /dev/null +++ b/__init__.py @@ -0,0 +1,96 @@ +from .meyo_node_Computational import StandardSize, CompareInt, FloatToInteger, GenerateNumbers, GetRandomIntegerInRange +from .meyo_node_String import AddPrefixSuffix, ExtractSubstring, ExtractSubstringByIndices, SplitStringByDelimiter, ProcessString, ExtractBeforeAfter, ReplaceNthOccurrence, BatchReplaceStrings, ReplaceMultiple, RandomLineFromText, CheckSubstringPresence, AddPrefixSuffixToLines, ExtractAndCombineLines, FilterLinesBySubstrings, FilterLinesByWordCount, SplitAndExtractText, CountOccurrences, ExtractLinesByIndex, ExtractSpecificLines, RemoveContentBetweenChars, ShuffleTextLines, ConditionalTextOutput, TextConditionCheck, TextConcatenation, ExtractSpecificData, FindFirstLineContent +from .meyo_node_File import FileListAndSuffix, ReadExcelData, WriteExcelData, FindExcelData, ReadExcelRowOrColumnDiff + +NODE_CLASS_MAPPINGS = { + + #运算型节点:meyo_node_Computational + "StandardSize": StandardSize, + "CompareInt": CompareInt, + "FloatToInteger": FloatToInteger, + "GenerateNumbers": GenerateNumbers, + "GetRandomIntegerInRange": GetRandomIntegerInRange, + + #字符串处理:meyo_node_String + "AddPrefixSuffix": AddPrefixSuffix, + "ExtractSubstring": ExtractSubstring, + "ExtractSubstringByIndices": ExtractSubstringByIndices, + "SplitStringByDelimiter": SplitStringByDelimiter, + "ProcessString": ProcessString, + "ExtractBeforeAfter": ExtractBeforeAfter, + "ReplaceNthOccurrence": ReplaceNthOccurrence, + "ReplaceMultiple": ReplaceMultiple, + "BatchReplaceStrings": BatchReplaceStrings, + "RandomLineFromText": RandomLineFromText, + "CheckSubstringPresence": CheckSubstringPresence, + "AddPrefixSuffixToLines": AddPrefixSuffixToLines, + "ExtractAndCombineLines": ExtractAndCombineLines, + "FilterLinesBySubstrings": FilterLinesBySubstrings, + "FilterLinesByWordCount": FilterLinesByWordCount, + "SplitAndExtractText": SplitAndExtractText, + "CountOccurrences": CountOccurrences, + "ExtractLinesByIndex": ExtractLinesByIndex, + "ExtractSpecificLines": ExtractSpecificLines, + "RemoveContentBetweenChars": RemoveContentBetweenChars, + "ShuffleTextLines": ShuffleTextLines, + "ConditionalTextOutput": ConditionalTextOutput, + "TextConditionCheck": TextConditionCheck, + "TextConcatenation": TextConcatenation, + "ExtractSpecificData": ExtractSpecificData, + "FindFirstLineContent": FindFirstLineContent, + + #文件处理:meyo_node_File + "FileListAndSuffix": FileListAndSuffix, + "ReadExcelData": ReadExcelData, + "WriteExcelData": WriteExcelData, + "FindExcelData": FindExcelData, + "ReadExcelRowOrColumnDiff": ReadExcelRowOrColumnDiff, + +} + + +NODE_DISPLAY_NAME_MAPPINGS = { + + #运算型节点:meyo_node_Computational + "StandardSize": "重置尺寸(meeeyo.com)", + "CompareInt": "比较数值(meeeyo.com)", + "FloatToInteger": "规范数值(meeeyo.com)", + "GenerateNumbers": "生成范围数组(meeeyo.com)", + "GetRandomIntegerInRange": "范围内随机数(meeeyo.com)", + + #字符串处理:meyo_node_String + "AddPrefixSuffix": "添加前后缀(meeeyo.com)", + "ExtractSubstring": "提取标签之间(meeeyo.com)", + "ExtractSubstringByIndices": "按数字范围提取(meeeyo.com)", + "SplitStringByDelimiter": "分隔符拆分两边(meeeyo.com)", + "ProcessString": "常规处理字符(meeeyo.com)", + "ExtractBeforeAfter": "提取前后字符(meeeyo.com)", + "ReplaceNthOccurrence": "替换第n次出现(meeeyo.com)", + "ReplaceMultiple": "多次出现依次替换(meeeyo.com)", + "BatchReplaceStrings": "批量替换字符(meeeyo.com)", + "RandomLineFromText": "随机行内容(meeeyo.com)", + "CheckSubstringPresence": "判断是否包含字符(meeeyo.com)", + "AddPrefixSuffixToLines": "段落每行添加前后缀(meeeyo.com)", + "ExtractAndCombineLines": "段落提取指定索引行(meeeyo.com)", + "FilterLinesBySubstrings": "段落提取或移除字符行(meeeyo.com)", + "FilterLinesByWordCount": "段落字数条件过滤行(meeeyo.com)", + "SplitAndExtractText": "按序号提取分割文本(meeeyo.com)", + "CountOccurrences": "文本出现次数(meeeyo.com)", + "ExtractLinesByIndex": "文本拆分(meeeyo.com)", + "ExtractSpecificLines": "提取特定行(meeeyo.com)", + "RemoveContentBetweenChars": "删除标签内的内容(meeeyo.com)", + "ShuffleTextLines": "随机打乱(meeeyo.com)", + "ConditionalTextOutput": "判断返回内容(meeeyo.com)", + "TextConditionCheck": "文本按条件判断(meeeyo.com)", + "TextConcatenation": "文本组合(meeeyo.com)", + "ExtractSpecificData": "提取多层指定数据(meeeyo.com)", + "FindFirstLineContent": "指定字符行参数(meeeyo.com)", + + #文件处理:meyo_node_File + "FileListAndSuffix": "从路径加载(meeeyo.com)", + "ReadExcelData": "读取表格数据(meeeyo.com)", + "WriteExcelData": "写入表格数据(meeeyo.com)", + "FindExcelData": "查找表格数据(meeeyo.com)", + "ReadExcelRowOrColumnDiff": "读取表格数量差(meeeyo.com)", + +} \ No newline at end of file diff --git a/meyo_node_Computational.py b/meyo_node_Computational.py new file mode 100644 index 0000000..4b6d027 --- /dev/null +++ b/meyo_node_Computational.py @@ -0,0 +1,197 @@ +import math +import random + + + + +#======宽度高度调整 +class StandardSize: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "width": ("INT",), + "height": ("INT",), + "size_option": (["小尺寸", "中尺寸", "大尺寸"], {"default": "中尺寸"}), # 下拉选择框 + }, + "optional": {}, + } + + RETURN_TYPES = ("INT", "INT") + FUNCTION = "resize_image" + CATEGORY = "Meyo/Image" + + def resize_image(self, width, height, size_option): + # 根据选择的选项调整尺寸像尺寸 + if size_option == "小尺寸": + if width / height >= 1.23: + return (768, 512) + elif 0.82 < width / height < 1.23: + return (768, 768) + else: + return (512, 768) + elif size_option == "中尺寸": + if width / height >= 1.23: + return (1216, 832) + elif 0.82 < width / height < 1.23: + return (1216, 1216) + else: + return (832, 1216) + elif size_option == "大尺寸": + if width / height >= 1.23: + return (1600, 1120) + elif 0.82 < width / height < 1.23: + return (1600, 1600) + else: + return (1120, 1600) + + + +#======比较数值 +class CompareInt: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_float": ("FLOAT", {"default": 4.0}), + "range": ("STRING", {"default": "3.5-5.5"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "compare_float_to_range" + CATEGORY = "Meeeyo/Number" + + def compare_float_to_range(self, input_float, range): + try: + if '-' in range: + lower_bound, upper_bound = map(float, range.split('-')) + else: + lower_bound = upper_bound = float(range) + if input_float < lower_bound: + return ("小",) + elif input_float > upper_bound: + return ("大",) + else: + return ("中",) + except ValueError: + return ("Error: Invalid input format.",) + +#======规范数值 +class FloatToInteger: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "float_value": ("FLOAT", {"default": 3.14}), + "operation": (["四舍五入", "取大值", "取小值", "最近32倍"], {"default": "四舍五入"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("INT",) + FUNCTION = "convert_float_to_integer" + CATEGORY = "Meeeyo/Number" + + def IS_CHANGED(): + return float("NaN") + + def convert_float_to_integer(self, float_value, operation): + if operation == "四舍五入": + result = round(float_value) + elif operation == "取大值": + result = math.ceil(float_value) + elif operation == "取小值": + result = math.floor(float_value) + elif operation == "最近32倍": + result = round(float_value / 32) * 32 + return (result,) + + +#======生成范围数组 +class GenerateNumbers: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "range_rule": ("STRING", {"default": "3|1-10"}), + "mode": (["顺序", "随机"], {"default": "顺序"}), + "prefix_suffix": ("STRING", {"default": "|"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "generate_numbers" + CATEGORY = "Meeeyo/Number" + + def IS_CHANGED(): + return float("NaN") + + def generate_numbers(self, range_rule, mode, prefix_suffix): + try: + start_str, range_str = range_rule.split('|') + start = int(start_str) + end_range = list(map(int, range_str.split('-'))) + if len(end_range) == 1: + end = end_range[0] + numbers = [str(i).zfill(start) for i in range(1, end + 1)] + else: + start_range, end = end_range + numbers = [str(i).zfill(start) for i in range(start_range, end + 1)] + if prefix_suffix.strip(): + prefix, suffix = prefix_suffix.split('|') + else: + prefix, suffix = "", "" + if mode == "随机": + random.shuffle(numbers) + numbers = [f"{prefix}{num}{suffix}" for num in numbers] + result = '\n'.join(numbers) + return (result,) + except ValueError: + return ("",) + + +#======范围内随机数 +class GetRandomIntegerInRange: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "range_str": ("STRING", {"default": "0-10"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("INT", "STRING") + FUNCTION = "get_random_integer_in_range" + CATEGORY = "Meeeyo/Number" + + def IS_CHANGED(): + return float("NaN") + + def get_random_integer_in_range(self, range_str): + try: + start, end = map(int, range_str.split('-')) + if start > end: + start, end = end, start + random_int = random.randint(start, end) + return (random_int, str(random_int)) + except ValueError: + return (0, "0") \ No newline at end of file diff --git a/meyo_node_File.py b/meyo_node_File.py new file mode 100644 index 0000000..9752e1c --- /dev/null +++ b/meyo_node_File.py @@ -0,0 +1,315 @@ +import csv +import openpyxl +import os + +#======文件路径和后缀统计 +class FileListAndSuffix: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "folder_path": ("STRING",), + "file_extension": (["jpg", "png", "jpg&png", "txt", "csv", "all"], {"default": "jpg"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING", "INT", "LIST") + FUNCTION = "file_list_and_suffix" + CATEGORY = "Meeeyo/File" + + def IS_CHANGED(): + return float("NaN") + + def file_list_and_suffix(self, folder_path, file_extension): + try: + if not os.path.isdir(folder_path): + return ("", 0, []) + + if file_extension == "all": + file_paths = [os.path.join(folder_path, f) for f in os.listdir(folder_path) if os.path.isfile(os.path.join(folder_path, f))] + elif file_extension == "jpg&png": + file_paths = [os.path.join(folder_path, f) for f in os.listdir(folder_path) if f.lower().endswith(('.jpg', '.png'))] + else: + file_paths = [os.path.join(folder_path, f) for f in os.listdir(folder_path) if f.lower().endswith('.' + file_extension)] + + return ('\n'.join(file_paths), len(file_paths), file_paths) + except Exception as e: + return ("", 0, []) + + +#======读取表格数据 +class ReadExcelData: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "excel_path": ("STRING", {"default": "path/to/your/file.xlsx"}), + "sheet_name": ("STRING", {"default": "Sheet1"}), + "row_range": ("STRING", {"default": "2-3"}), + "col_range": ("STRING", {"default": "1-4"}), + } + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "read_excel_data" + CATEGORY = "Meeeyo/File" + + def IS_CHANGED(): + return float("NaN") + + def read_excel_data(self, excel_path, sheet_name, row_range, col_range): + try: + if "-" in row_range: + start_row, end_row = map(int, row_range.split("-")) + else: + start_row = end_row = int(row_range) + + if "-" in col_range: + start_col, end_col = map(int, col_range.split("-")) + else: + start_col = end_col = int(col_range) + + start_row = max(1, start_row) + start_col = max(1, start_col) + + workbook = openpyxl.load_workbook(excel_path, read_only=True, data_only=True) + sheet = workbook[sheet_name] + + output_lines = [] + for row in range(start_row, end_row + 1): + row_data = [] + for col in range(start_col, end_col + 1): + cell_value = sheet.cell(row=row, column=col).value + row_data.append(str(cell_value) if cell_value is not None else "") + output_lines.append("|".join(row_data)) + + workbook.close() + del workbook + + return ("\n".join(output_lines),) + + except Exception as e: + return (f"Error: {str(e)}",) + + +#======写入表格数据 +class WriteExcelData: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "excel_path": ("STRING", {"default": "path/to/your/file.xlsx"}), + "sheet_name": ("STRING", {"default": "Sheet1"}), + "row_range": ("STRING", {"default": "2-3"}), + "col_range": ("STRING", {"default": "1-4"}), + "data": ("STRING", {"default": "", "multiline": True}), + } + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "write_excel_data" + CATEGORY = "Meeeyo/File" + + def IS_CHANGED(): + return float("NaN") + + def write_excel_data(self, excel_path, sheet_name, row_range, col_range, data): + try: + if not os.path.exists(excel_path): + return (f"Error: File does not exist at path: {excel_path}",) + + if not os.access(excel_path, os.W_OK): + return (f"Error: No write permission for file at path: {excel_path}",) + + if "-" in row_range: + start_row, end_row = map(int, row_range.split("-")) + else: + start_row = end_row = int(row_range) + + if "-" in col_range: + start_col, end_col = map(int, col_range.split("-")) + else: + start_col = end_col = int(col_range) + + start_row = max(1, start_row) + start_col = max(1, start_col) + + workbook = openpyxl.load_workbook(excel_path, read_only=False, data_only=True) + sheet = workbook[sheet_name] + + data_lines = data.strip().split("\n") + + for row_index, line in enumerate(data_lines, start=start_row): + if row_index > end_row: + break + + cell_values = line.split("|") + for col_index, cell_value in enumerate(cell_values, start=start_col): + if col_index > end_col: + break + + if cell_value.strip(): + sheet.cell(row=row_index, column=col_index).value = cell_value.strip() + + workbook.save(excel_path) + + workbook.close() + del workbook + + return ("Data written successfully!",) + + except PermissionError as pe: + return (f"Permission Error: {str(pe)}",) + except Exception as e: + return (f"Error: {str(e)}",) + + +#======查找表格数据 +class FindExcelData: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "excel_path": ("STRING", {"default": "path/to/your/file.xlsx"}), + "sheet_name": ("STRING", {"default": "Sheet1"}), + "search_content": ("STRING", {"default": "查找内容"}), + "search_mode": (["精确查找", "模糊查找"], {"default": "精确查找"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING", "INT", "INT") + FUNCTION = "find_excel_data" + CATEGORY = "Meeeyo/File" + + def IS_CHANGED(): + return float("NaN") + + def find_excel_data(self, excel_path, sheet_name, search_content, search_mode): + try: + if not os.path.exists(excel_path): + return (f"Error: File does not exist at path: {excel_path}", None, None) + + if not os.access(excel_path, os.R_OK): + return (f"Error: No read permission for file at path: {excel_path}", None, None) + + workbook = openpyxl.load_workbook(excel_path, read_only=True, data_only=True) + sheet = workbook[sheet_name] + + results = [] + found_row = None + found_col = None + + for row in range(1, sheet.max_row + 1): + for col in range(1, sheet.max_column + 1): + cell = sheet.cell(row=row, column=col) + cell_value = cell.value if cell.value is not None else "" + + if (search_mode == "精确查找" and cell_value == search_content) or \ + (search_mode == "模糊查找" and search_content in cell_value): + results.append(f"{sheet_name}|{row}|{col}|{cell_value}") + found_row = row + found_col = col + + workbook.close() + del workbook + + if not results: + return ("No results found.", None, None) + + return ("\n".join(results), found_row, found_col) + + except Exception as e: + return (f"Error: {str(e)}", None, None) + + +#======读取表格数量差 +class ReadExcelRowOrColumnDiff: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "excel_path": ("STRING", {"default": "path/to/your/file.xlsx"}), + "sheet_name": ("STRING", {"default": "Sheet1"}), + "read_mode": (["读行", "读列"], {"default": "读行"}), + "indices": ("STRING", {"default": "1,3"}), + } + } + + RETURN_TYPES = ("INT",) + FUNCTION = "read_excel_row_or_column_diff" + CATEGORY = "Meeeyo/File" + + def IS_CHANGED(): + return float("NaN") + + def read_excel_row_or_column_diff(self, excel_path, sheet_name, read_mode, indices): + try: + if not os.path.exists(excel_path): + return (f"Error: File does not exist at path: {excel_path}",) + + if not os.access(excel_path, os.R_OK): + return (f"Error: No read permission for file at path: {excel_path}",) + + workbook = openpyxl.load_workbook(excel_path, read_only=True, data_only=True) + sheet = workbook[sheet_name] + + def count_cells(mode, index): + count = 0 + if mode == "读行": + for col in range(1, sheet.max_column + 1): + cell_value = sheet.cell(row=index, column=col).value + if cell_value is not None: + count += 1 + else: + break + elif mode == "读列": + for row in range(1, sheet.max_row + 1): + cell_value = sheet.cell(row=row, column=index).value + if cell_value is not None: + count += 1 + else: + break + return count + + indices = indices.strip() + if "," in indices: + try: + index1, index2 = map(int, indices.split(",")) + except ValueError: + return (f"Error: Invalid indices format. Please use 'number,number' format.",) + + count1 = count_cells(read_mode, index1) + count2 = count_cells(read_mode, index2) + result = count1 - count2 + else: + try: + index = int(indices) + except ValueError: + return (f"Error: Invalid index format. Please enter a valid number.",) + + result = count_cells(read_mode, index) + + workbook.close() + del workbook + + return (result,) + + except Exception as e: + return (f"Error: {str(e)}",) \ No newline at end of file diff --git a/meyo_node_String.py b/meyo_node_String.py new file mode 100644 index 0000000..0a7dde3 --- /dev/null +++ b/meyo_node_String.py @@ -0,0 +1,1041 @@ + +import datetime +import random +import secrets +import requests +import string +import math +import re + + +#======添加前后缀 +class AddPrefixSuffix: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_string": ("STRING", {"default": ""}), + "prefix": ("STRING", {"default": "前缀符"}), + "suffix": ("STRING", {"default": "后缀符"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "add_prefix_suffix" + CATEGORY = "Meeeyo/String" + + def add_prefix_suffix(self, input_string, prefix, suffix): + return (f"{prefix}{input_string}{suffix}",) + +#======提取标签之间 +class ExtractSubstring: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_string": ("STRING", {"multiline": True, "default": ""}), + "pattern": ("STRING", {"default": "标签始|标签尾"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "extract_substring" + CATEGORY = "Meeeyo/String" + + def extract_substring(self, input_string, pattern): + try: + parts = pattern.split('|') + start_str = parts[0] + end_str = parts[1] if len(parts) > 1 and parts[1].strip() else "\n" + + start_index = input_string.index(start_str) + len(start_str) + + end_index = input_string.find(end_str, start_index) + if end_index == -1: + end_index = input_string.find("\n", start_index) + if end_index == -1: + end_index = len(input_string) + + extracted = input_string[start_index:end_index] + + lines = extracted.splitlines() + non_empty_lines = [line for line in lines if line.strip()] + result = '\n'.join(non_empty_lines) + + return (result,) + except ValueError: + return ("",) + +#======按数字范围提取 +class ExtractSubstringByIndices: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_string": ("STRING", {"default": ""}), + "indices": ("STRING", {"default": "2-6"}), + "direction": (["从前面", "从后面"], {"default": "从前面"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "extract_substring_by_indices" + CATEGORY = "Meeeyo/String" + + def extract_substring_by_indices(self, input_string, indices, direction): + try: + if '-' in indices: + start_index, end_index = map(int, indices.split('-')) + else: + start_index = end_index = int(indices) + + start_index -= 1 + end_index -= 1 + + if start_index < 0 or start_index >= len(input_string): + return ("",) + + if end_index < 0 or end_index >= len(input_string): + end_index = len(input_string) - 1 + + if start_index > end_index: + start_index, end_index = end_index, start_index + + if direction == "从前面": + return (input_string[start_index:end_index + 1],) + elif direction == "从后面": + start_index = len(input_string) - start_index - 1 + end_index = len(input_string) - end_index - 1 + if start_index > end_index: + start_index, end_index = end_index, start_index + return (input_string[start_index:end_index + 1],) + except ValueError: + return ("",) + +#======分隔符拆分两边 +class SplitStringByDelimiter: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_string": ("STRING", {"default": "文本|内容"}), + "delimiter": ("STRING", {"default": "|"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING", "STRING") + FUNCTION = "split_string_by_delimiter" + CATEGORY = "Meeeyo/String" + + def split_string_by_delimiter(self, input_string, delimiter): + parts = input_string.split(delimiter, 1) + if len(parts) == 2: + return (parts[0], parts[1]) + else: + return (input_string, "") + +#======常规处理字符 +class ProcessString: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_string": ("STRING", {"multiline": True, "default": ""}), + "option": (["不改变", "取数字", "取字母", "转大写", "转小写", "取中文", "去标点", "去换行", "去空行", "去空格", "去格式", "统计字数"], {"default": "不改变"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "process_string" + CATEGORY = "Meeeyo/String" + + def process_string(self, input_string, option): + if option == "取数字": + result = ''.join(re.findall(r'\d', input_string)) + elif option == "取字母": + result = ''.join(filter(lambda char: char.isalpha() and not self.is_chinese(char), input_string)) + elif option == "转大写": + result = input_string.upper() + elif option == "转小写": + result = input_string.lower() + elif option == "取中文": + result = ''.join(filter(self.is_chinese, input_string)) + elif option == "去标点": + result = re.sub(r'[^\w\s\u4e00-\u9fff]', '', input_string) + elif option == "去换行": + result = input_string.replace('\n', '') + elif option == "去空行": + result = '\n'.join(filter(lambda line: line.strip(), input_string.splitlines())) + elif option == "去空格": + result = input_string.replace(' ', '') + elif option == "去格式": + result = re.sub(r'\s+', '', input_string) + elif option == "统计字数": + result = str(len(input_string)) + else: + result = input_string + + return (result,) + + @staticmethod + def is_chinese(char): + return '\u4e00' <= char <= '\u9fff' + +#======提取前后字符 +class ExtractBeforeAfter: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_string": ("STRING", {"default": ""}), + "pattern": ("STRING", {"default": "标签符"}), + "position": (["保留最初之前", "保留最初之后", "保留最后之前", "保留最后之后"], {"default": "保留最初之前"}), + "include_delimiter": ("BOOLEAN", {"default": False}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "extract_before_after" + CATEGORY = "Meeeyo/String" + + def extract_before_after(self, input_string, pattern, position, include_delimiter): + if position == "保留最初之前": + index = input_string.find(pattern) + if index != -1: + result = input_string[:index + len(pattern) if include_delimiter else index] + return (result,) + elif position == "保留最初之后": + index = input_string.find(pattern) + if index != -1: + result = input_string[index:] if include_delimiter else input_string[index + len(pattern):] + return (result,) + elif position == "保留最后之前": + index = input_string.rfind(pattern) + if index != -1: + result = input_string[:index + len(pattern) if include_delimiter else index] + return (result,) + elif position == "保留最后之后": + index = input_string.rfind(pattern) + if index != -1: + result = input_string[index:] if include_delimiter else input_string[index + len(pattern):] + return (result,) + return ("",) + + +#======替换第n次出现 +class ReplaceNthOccurrence: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "original_text": ("STRING", {"multiline": True, "default": ""}), + "occurrence": ("INT", {"default": 1, "min": 0}), + "search_str": ("STRING", {"default": "替换前字符"}), + "replace_str": ("STRING", {"default": "替换后字符"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "replace_nth_occurrence" + CATEGORY = "Meeeyo/String" + + def replace_nth_occurrence(self, original_text, occurrence, search_str, replace_str): + if occurrence == 0: + result = original_text.replace(search_str, replace_str) + else: + def replace_nth_match(match): + nonlocal occurrence + occurrence -= 1 + return replace_str if occurrence == 0 else match.group(0) + + result = re.sub(re.escape(search_str), replace_nth_match, original_text, count=occurrence) + + return (result,) + + +#======多次出现依次替换 +class ReplaceMultiple: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "original_text": ("STRING", {"multiline": True, "default": ""}), + "replacement_rule": ("STRING", {"default": ""}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "replace_multiple" + CATEGORY = "Meeeyo/String" + + def replace_multiple(self, original_text, replacement_rule): + try: + search_str, replacements = replacement_rule.split('|') + replacements = [rep for rep in replacements.split(',') if rep] + + def replace_match(match): + nonlocal replacements + if replacements: + return replacements.pop(0) + return match.group(0) + + result = re.sub(re.escape(search_str), replace_match, original_text) + + return (result,) + except ValueError: + return ("",) + +#======批量替换字符 +class BatchReplaceStrings: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "original_text": ("STRING", {"multiline": False, "default": "文本内容"}), + "replacement_rules": ("STRING", {"multiline": True, "default": ""}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "batch_replace_strings" + CATEGORY = "Meeeyo/String" + + def batch_replace_strings(self, original_text, replacement_rules): + rules = replacement_rules.strip().split('\n') + for rule in rules: + if '|' in rule: + search_strs, replace_str = rule.split('|', 1) + + search_strs = search_strs.replace("\\n", "\n") + replace_str = replace_str.replace("\\n", "\n") + + search_strs = search_strs.split(',') + + for search_str in search_strs: + original_text = original_text.replace(search_str, replace_str) + return (original_text,) + + +#======随机行内容 +class RandomLineFromText: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_text": ("STRING", {"multiline": True, "default": ""}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "get_random_line" + CATEGORY = "Meeeyo/String" + + def IS_CHANGED(): + return float("NaN") + + def get_random_line(self, input_text): + lines = input_text.strip().splitlines() + if not lines: + return ("",) + return (random.choice(lines),) + +#======判断是否包含字符 +class CheckSubstringPresence: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_text": ("STRING", {"default": "文本内容"}), + "substring": ("STRING", {"default": "查找符1|查找符2"}), + "mode": (["同时满足", "任意满足"], {"default": "任意满足"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("INT",) + FUNCTION = "check_substring_presence" + CATEGORY = "Meeeyo/String" + + def check_substring_presence(self, input_text, substring, mode): + substrings = substring.split('|') + + if mode == "同时满足": + for sub in substrings: + if sub not in input_text: + return (0,) + return (1,) + elif mode == "任意满足": + for sub in substrings: + if sub in input_text: + return (1,) + return (0,) + +#======段落每行添加前后缀 +class AddPrefixSuffixToLines: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_text": ("STRING", {"multiline": True, "default": ""}), + "prefix_suffix": ("STRING", {"default": "前缀符|后缀符"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "add_prefix_suffix_to_lines" + CATEGORY = "Meeeyo/String" + + def add_prefix_suffix_to_lines(self, prefix_suffix, input_text): + try: + prefix, suffix = prefix_suffix.split('|') + lines = input_text.splitlines() + modified_lines = [f"{prefix}{line}{suffix}" for line in lines] + result = '\n'.join(modified_lines) + return (result,) + except ValueError: + return ("",) + +#======段落提取指定索引行 +class ExtractAndCombineLines: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_text": ("STRING", {"multiline": True, "default": ""}), + "line_indices": ("STRING", {"default": "2-3"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "extract_and_combine_lines" + CATEGORY = "Meeeyo/String" + + def extract_and_combine_lines(self, input_text, line_indices): + try: + lines = input_text.splitlines() + result_lines = [] + + if '-' in line_indices: + start, end = map(int, line_indices.split('-')) + start = max(1, start) + end = min(len(lines), end) + result_lines = lines[start - 1:end] + else: + indices = map(int, line_indices.split('|')) + for index in indices: + if 1 <= index <= len(lines): + result_lines.append(lines[index - 1]) + + result = '\n'.join(result_lines) + return (result,) + except ValueError: + return ("",) + + +#======段落提取或移除字符行 +class FilterLinesBySubstrings: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_text": ("STRING", {"multiline": True, "default": ""}), + "substrings": ("STRING", {"default": "查找符1|查找符2"}), + "action": (["保留", "移除"], {"default": "保留"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "filter_lines_by_substrings" + CATEGORY = "Meeeyo/String" + + def filter_lines_by_substrings(self, input_text, substrings, action): + lines = input_text.splitlines() + substring_list = substrings.split('|') + result_lines = [] + + for line in lines: + contains_substring = any(substring in line for substring in substring_list) + if (action == "保留" and contains_substring) or (action == "移除" and not contains_substring): + result_lines.append(line) + + non_empty_lines = [line for line in result_lines if line.strip()] + result = '\n'.join(non_empty_lines) + return (result,) + + +#======根据字数范围过滤文本行 +class FilterLinesByWordCount: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_text": ("STRING", {"multiline": True, "default": ""}), + "word_count_range": ("STRING", {"default": "2-10"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "filter_lines_by_word_count" + CATEGORY = "Meeeyo/String" + + def filter_lines_by_word_count(self, input_text, word_count_range): + try: + lines = input_text.splitlines() + result_lines = [] + + if '-' in word_count_range: + min_count, max_count = map(int, word_count_range.split('-')) + result_lines = [line for line in lines if min_count <= len(line) <= max_count] + + result = '\n'.join(result_lines) + return (result,) + except ValueError: + return ("",) + + +#======按序号提取分割文本 +class SplitAndExtractText: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_text": ("STRING", {"multiline": True, "default": ""}), + "delimiter": ("STRING", {"default": "分隔符"}), + "index": ("INT", {"default": 1, "min": 1}), + "order": (["顺序", "倒序"], {"default": "顺序"}), + "include_delimiter": ("BOOLEAN", {"default": False}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "split_and_extract" + CATEGORY = "Meeeyo/String" + + def split_and_extract(self, input_text, delimiter, index, order, include_delimiter): + try: + if not delimiter: + parts = input_text.splitlines() + else: + parts = input_text.split(delimiter) + + if order == "倒序": + parts = parts[::-1] + + if index > 0 and index <= len(parts): + selected_part = parts[index - 1] + + if include_delimiter and delimiter: + if order == "顺序": + if index > 1: + selected_part = delimiter + selected_part + if index < len(parts): + selected_part += delimiter + elif order == "倒序": + if index > 1: + selected_part += delimiter + if index < len(parts): + selected_part = delimiter + selected_part + + lines = selected_part.splitlines() + non_empty_lines = [line for line in lines if line.strip()] + result = '\n'.join(non_empty_lines) + return (result,) + else: + return ("",) + except ValueError: + return ("",) + +#======文本出现次数 +class CountOccurrences: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_text": ("STRING", {"multiline": True, "default": ""}), + "char": ("STRING", {"default": "查找符"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("INT", "STRING") + FUNCTION = "count_text_segments" + CATEGORY = "Meeeyo/String" + + def count_text_segments(self, input_text, char): + try: + if char == "\\n": + lines = [line for line in input_text.splitlines() if line.strip()] + count = len(lines) + else: + count = input_text.count(char) + return (count, str(count)) + except ValueError: + return (0, "0") + +#======文本拆分:根据索引和特定字符获取多行文本中的行内容 +class ExtractLinesByIndex: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_text": ("STRING", {"multiline": True, "default": ""}), + "delimiter": ("STRING", {"default": "标签符"}), + "index": ("INT", {"default": 1, "min": 1}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING") + FUNCTION = "extract_lines_by_index" + CATEGORY = "Meeeyo/String" + + OUTPUT_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING") + OUTPUT_NAMES = ("文本1", "文本2", "文本3", "文本4", "文本5") + + def extract_lines_by_index(self, input_text, index, delimiter): + try: + if delimiter == "" or delimiter == "\n": + lines = input_text.splitlines() + else: + lines = input_text.split(delimiter) + + result_lines = [] + + for i in range(index - 1, index + 4): + if 0 <= i < len(lines): + result_lines.append(lines[i]) + else: + result_lines.append("") + + return tuple(result_lines) + except ValueError: + return ("", "", "", "", "") + +#======提取特定行 +class ExtractSpecificLines: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_text": ("STRING", {"multiline": True, "default": ""}), + "line_indices": ("STRING", {"default": "1|2"}), + "split_char": ("STRING", {"default": "\n"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING", "STRING") + FUNCTION = "extract_specific_lines" + CATEGORY = "Meeeyo/String" + + def extract_specific_lines(self, input_text, line_indices, split_char): + if not split_char or split_char == "\n": + lines = input_text.split('\n') + else: + lines = input_text.split(split_char) + + indices = [int(index) - 1 for index in line_indices.split('|') if index.isdigit()] + + results = [] + for index in indices: + if 0 <= index < len(lines): + results.append(lines[index]) + else: + results.append("") + + while len(results) < 5: + results.append("") + + non_empty_results = [result for result in results[:5] if result.strip()] + if not split_char or split_char == "\n": + combined_result = '\n'.join(non_empty_results) + else: + combined_result = split_char.join(non_empty_results) + + return tuple(results[:5] + [combined_result]) + +#======删除标签内的内容 +class RemoveContentBetweenChars: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_text": ("STRING", {"multiline": True, "default": ""}), + "chars": ("STRING", {"default": "(|)"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "remove_content_between_chars" + CATEGORY = "Meeeyo/String" + + def remove_content_between_chars(self, input_text, chars): + try: + if len(chars) == 3 and chars[1] == '|': + start_char, end_char = chars[0], chars[2] + else: + return input_text + + pattern = re.escape(start_char) + '.*?' + re.escape(end_char) + result = re.sub(pattern, '', input_text) + + return (result,) + except ValueError: + return ("",) + +#======随机打乱 +class ShuffleTextLines: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_text": ("STRING", {"multiline": True, "default": ""}), + "delimiter": ("STRING", {"default": "分隔符"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "shuffle_text_lines" + CATEGORY = "Meeeyo/String" + + def IS_CHANGED(): + return float("NaN") + + def shuffle_text_lines(self, input_text, delimiter): + if delimiter == "": + lines = input_text.splitlines() + elif delimiter == "\n": + lines = input_text.split("\n") + else: + lines = input_text.split(delimiter) + + lines = [line for line in lines if line.strip()] + + random.shuffle(lines) + + if delimiter == "": + result = "\n".join(lines) + elif delimiter == "\n": + result = "\n".join(lines) + else: + result = delimiter.join(lines) + + return (result,) + +#======判断返回内容 +class ConditionalTextOutput: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "original_content": ("STRING", {"multiline": True, "default": ""}), + "check_text": ("STRING", {"default": "查找字符"}), + "text_if_exists": ("STRING", {"default": "存在返回内容"}), + "text_if_not_exists": ("STRING", {"default": "不存在返回内容"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "conditional_text_output" + CATEGORY = "Meeeyo/String" + + def conditional_text_output(self, original_content, check_text, text_if_exists, text_if_not_exists): + if not check_text: + return ("",) + + if check_text in original_content: + return (text_if_exists,) + else: + return (text_if_not_exists,) + + +#======文本按条件判断 +class TextConditionCheck: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "original_content": ("STRING", {"multiline": True, "default": ""}), # 输入多行文本 + "length_condition": ("STRING", {"default": "3-6"}), + "frequency_condition": ("STRING", {"default": ""}), + }, + "optional": {}, + } + + RETURN_TYPES = ("INT", "STRING") + FUNCTION = "text_condition_check" + CATEGORY = "Meeeyo/String" + + def text_condition_check(self, original_content, length_condition, frequency_condition): + length_valid = self.check_length_condition(original_content, length_condition) + + frequency_valid = self.check_frequency_condition(original_content, frequency_condition) + + if length_valid and frequency_valid: + return (1, "1") + else: + return (0, "0") + + def check_length_condition(self, content, condition): + if '-' in condition: + start, end = map(int, condition.split('-')) + return start <= len(content) <= end + else: + target_length = int(condition) + return len(content) == target_length + + def check_frequency_condition(self, content, condition): + conditions = condition.split('|') + for cond in conditions: + char, count = cond.split(',') + if content.count(char) != int(count): + return False + return True + + +#======文本组合 +class TextConcatenation: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "original_text": ("STRING", {"multiline": True, "default": ""}), + "concatenation_rules": ("STRING", {"multiline": True, "default": ""}), + "split_char": ("STRING", {"default": ""}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING") + FUNCTION = "text_concatenation" + CATEGORY = "Meeeyo/String" + + def text_concatenation(self, original_text, concatenation_rules, split_char): + if split_char: + original_lines = [line.strip() for line in original_text.split(split_char) if line.strip()] + else: + original_lines = [line.strip() for line in original_text.split('\n') if line.strip()] + + rules_lines = [line.strip() for line in concatenation_rules.split('\n') if line.strip()] + + outputs = [] + for rule in rules_lines[:5]: + result = rule + for i, line in enumerate(original_lines, start=1): + result = result.replace(f"[{i}]", line) + outputs.append(result) + + while len(outputs) < 5: + outputs.append("") + + return tuple(outputs) + +#======提取多层指定数据 +class ExtractSpecificData: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_text": ("STRING", {"multiline": True, "default": ""}), + "rule1": ("STRING", {"default": "[3],@|2"}), + "rule2": ("STRING", {"default": "三,【|】"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "extract_specific_data" + CATEGORY = "Meeeyo/String" + + def extract_specific_data(self, input_text, rule1, rule2): + if rule1.strip(): + return self.extract_by_rule1(input_text, rule1) + else: + return self.extract_by_rule2(input_text, rule2) + + def extract_by_rule1(self, input_text, rule): + try: + line_rule, split_rule = rule.split(',') + split_char, group_index = split_rule.split('|') + group_index = int(group_index) - 1 + except ValueError: + return ("",) + + lines = input_text.split('\n') + + if line_rule.startswith('[') and line_rule.endswith(']'): + try: + line_index = int(line_rule[1:-1]) - 1 + if 0 <= line_index < len(lines): + target_line = lines[line_index] + else: + return ("",) + except ValueError: + return ("",) + else: + target_lines = [line for line in lines if line_rule in line] + if not target_lines: + return ("",) + target_line = target_lines[0] + + parts = target_line.split(split_char) + if 0 <= group_index < len(parts): + return (parts[group_index],) + return ("",) + + def extract_by_rule2(self, input_text, rule): + try: + line_rule, tags = rule.split(',') + start_tag, end_tag = tags.split('|') + except ValueError: + return ("",) + + lines = input_text.split('\n') + + if line_rule.startswith('[') and line_rule.endswith(']'): + try: + line_index = int(line_rule[1:-1]) - 1 + if 0 <= line_index < len(lines): + target_line = lines[line_index] + else: + return ("",) + except ValueError: + return ("",) + else: + target_lines = [line for line in lines if line_rule in line] + if not target_lines: + return ("",) + target_line = target_lines[0] + + start_index = target_line.find(start_tag) + end_index = target_line.find(end_tag, start_index) + if start_index != -1 and end_index != -1: + return (target_line[start_index + len(start_tag):end_index],) + return ("",) + + +#======指定字符行参数 +class FindFirstLineContent: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_text": ("STRING", {"multiline": True, "default": ""}), + "target_char": ("STRING", {"default": "数据a"}), + }, + "optional": {}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "find_first_line_content" + CATEGORY = "Meeeyo/String" + + def find_first_line_content(self, input_text, target_char): + try: + lines = input_text.splitlines() + + for line in lines: + if target_char in line: + start_index = line.index(target_char) + result = line[start_index + len(target_char):] + return (result,) + + return ("",) + except Exception as e: + return (f"Error: {str(e)}",) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ee008d4 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +#secrets +requests>=2.25.1,<3.0.0 +ntplib +openpyxl \ No newline at end of file