refactor: Align UTF8ConverterNode logic with fix_encoding.py for robust cleaning
This commit is contained in:
+43
-212
@@ -1,5 +1,4 @@
|
||||
import os
|
||||
import torch
|
||||
|
||||
def is_binary_file(path):
|
||||
try:
|
||||
@@ -13,240 +12,65 @@ def is_binary_file(path):
|
||||
|
||||
def try_decode(data):
|
||||
"""
|
||||
尝试多种编码格式解码二进制数据
|
||||
Try to decode binary data with multiple encodings
|
||||
尝试多种编码格式解码二进制数据 (对齐 fix_encoding.py)
|
||||
"""
|
||||
# 1. Try UTF-8
|
||||
# 1. UTF-8
|
||||
try:
|
||||
return data.decode("utf-8"), "utf-8"
|
||||
except:
|
||||
pass
|
||||
|
||||
# 2. Try UTF-16
|
||||
# 2. UTF-16 with BOM
|
||||
if data.startswith(b'\xff\xfe') or data.startswith(b'\xfe\xff'):
|
||||
try:
|
||||
return data.decode("utf-16"), "utf-16"
|
||||
except:
|
||||
pass
|
||||
|
||||
# 3. GB18030 (covers GBK and more)
|
||||
try:
|
||||
return data.decode("utf-16"), "utf-16"
|
||||
return data.decode("gb18030"), "gb18030"
|
||||
except:
|
||||
pass
|
||||
|
||||
# 3. Try GBK
|
||||
try:
|
||||
return data.decode("gbk"), "gbk"
|
||||
except:
|
||||
pass
|
||||
|
||||
# 4. Fallback: UTF-8 with ignore
|
||||
|
||||
# 4. Fallback
|
||||
return data.decode("utf-8", errors="ignore"), "fallback-ignore"
|
||||
|
||||
def clean_text(text):
|
||||
def clean_text(text, remove_emoji=True):
|
||||
"""
|
||||
严格清洗文本:
|
||||
- 删除不可见控制字符 (0-31, 保留 \n \t)
|
||||
- 删除代理对 (Surrogates)
|
||||
- 删除 4 字节字符 (Emoji, > 0xFFFF)
|
||||
严格清洗文本 (对齐 fix_encoding.py):
|
||||
- 删除控制字符 (保留换行和制表)
|
||||
- 删除 surrogate 区
|
||||
- 删除 emoji (默认强制删除 > 0xFFFF 的所有 4 字节字符)
|
||||
- 强制统一换行符为 Unix 风格 (\n)
|
||||
"""
|
||||
cleaned = []
|
||||
for c in text:
|
||||
code = ord(c)
|
||||
|
||||
# 删除不可见控制字符 (U+0000 - U+001F),保留 \n (10) 和 \t (9)
|
||||
# 删除控制字符(保留换行和制表)
|
||||
if 0 <= code < 32 and c not in ("\n", "\t"):
|
||||
continue
|
||||
|
||||
# 过滤非法 surrogate 字符 (U+D800 - U+DFFF)
|
||||
# 删除 surrogate 区
|
||||
if 0xD800 <= code <= 0xDFFF:
|
||||
continue
|
||||
|
||||
# 删除 Emoji 和其他 4 字节字符 (> 0xFFFF)
|
||||
# 注意:某些 Emoji(如 ⚠️ U+26A0)位于 BMP (Basic Multilingual Plane) 内,码点 < 0xFFFF
|
||||
# 因此仅判断 code > 0xFFFF 是不够的,需要引入 regex 或 Unicode 范围判断
|
||||
# 这里我们扩展过滤范围,根据 Unicode 标准过滤 Emoji
|
||||
# 简单起见,如果需要严格禁止 Emoji,可以使用 Unicode 范围
|
||||
# ⚠️ (Warning Sign) is U+26A0
|
||||
# ⚡ (High Voltage) is U+26A1
|
||||
# ⚽ (Soccer Ball) is U+26BD
|
||||
# 常见 Emoji 范围包括:
|
||||
# U+1F300-U+1F5FF (Miscellaneous Symbols and Pictographs)
|
||||
# U+1F600-U+1F64F (Emoticons)
|
||||
# U+1F680-U+1F6FF (Transport and Map Symbols)
|
||||
# U+2600-U+26FF (Miscellaneous Symbols) -> 包含 ⚠️, ⚽, ☁️ 等
|
||||
# U+2700-U+27BF (Dingbats) -> 包含 ✈️, ✉️, ✏️ 等
|
||||
#
|
||||
# 用户要求:允许特殊符号如 ★(U+2605), →(U+2192), 《(U+300A), ·(U+00B7)
|
||||
# ⚠️ (U+26A0) 在 Miscellaneous Symbols 区域,与 ★ (U+2605) 同区!
|
||||
# 这是一个难点。
|
||||
# 策略更新:
|
||||
# 1. 删除所有 > 0xFFFF 的字符 (4字节字符) -> 已实现
|
||||
# 2. 针对 BMP 内的 Emoji,我们需要更细致的黑名单或白名单。
|
||||
# 考虑到用户说 "严禁使用任何 Emoji",但 "允许星号、箭头等"。
|
||||
# 星号 ★ U+2605
|
||||
# 警告 ⚠️ U+26A0
|
||||
# 它们非常接近。
|
||||
# 我们可以使用 unicode property 或 regex。但在不引入外部库(如 emoji)的情况下,
|
||||
# 我们可以过滤特定的 Emoji 块,但保留特定的白名单字符。
|
||||
|
||||
# 简单处理:保留 > 0xFFFF 过滤。
|
||||
# 针对 BMP 内的 Emoji,用户提到的 ⚠️ 是 U+26A0。
|
||||
# 许多 Emoji 位于 U+2000-U+2FFF 之间,但也包含数学符号和箭头。
|
||||
# 让我们尝试过滤 variation selectors (U+FE00-U+FE0F)
|
||||
# ⚠️ (U+26A0) 通常后面跟着 U+FE0F (VS16) 变成 Emoji 样式。
|
||||
# 但单字符 ⚠️ 也是存在的。
|
||||
|
||||
# 鉴于用户明确指出 ⚠️ 未删除,我们需要加强过滤。
|
||||
# 常见 BMP Emoji 范围:
|
||||
# U+2600-U+26FF (杂项符号) -> 混合了 Emoji 和 符号(如 ★)
|
||||
# U+2700-U+27BF (Dingbats) -> 混合了 Emoji 和 符号(如 ✂️)
|
||||
|
||||
# 强制过滤列表 (手动列出常见 BMP Emoji 区域或字符)
|
||||
# 或者,更激进地,如果字符属于 "Symbol, Other" (So) 类别且不是白名单?
|
||||
# Python 的 unicodedata 库可以帮忙。
|
||||
|
||||
import unicodedata
|
||||
try:
|
||||
category = unicodedata.category(c)
|
||||
except:
|
||||
category = "Cn" # Not assigned
|
||||
|
||||
# 4字节字符统统删除
|
||||
if code > 0xFFFF:
|
||||
# 删除 emoji(默认删除所有 > 0xFFFF 字符)
|
||||
if remove_emoji and code > 0xFFFF:
|
||||
continue
|
||||
|
||||
# 针对 BMP 字符的特殊过滤
|
||||
# 过滤 Variation Selectors (U+FE00 - U+FE0F)
|
||||
if 0xFE00 <= code <= 0xFE0F:
|
||||
continue
|
||||
|
||||
# 过滤特定 Emoji 字符 (黑名单补丁)
|
||||
# ⚠️ U+26A0, ⚡ U+26A1, ✋ U+270B 等
|
||||
# 这是一个无底洞,但我们可以尝试过滤掉呈现为 Emoji 的符号
|
||||
# 许多现代 Emoji 实际上是基本字符 + VS16。
|
||||
# 如果我们删除了 VS16,它们会变成黑白文本符号。
|
||||
# 但用户希望 "删除",即完全消失。
|
||||
|
||||
# 使用 unicode category 过滤?
|
||||
# ★ (BLACK STAR) -> 'So' (Symbol, Other)
|
||||
# ⚠️ (WARNING SIGN) -> 'So'
|
||||
# 两者类别相同。
|
||||
|
||||
# 既然无法通过类别区分,我们只能依赖码点范围,并设置白名单。
|
||||
# 允许:星号(★ U+2605)、方块(■ U+25A0)、箭头(→ U+2192)
|
||||
#
|
||||
# 让我们检查 ⚠️ (U+26A0)
|
||||
#
|
||||
# 临时方案:增加对常见 BMP Emoji 的过滤,如果不小心误杀,后续再调整。
|
||||
#
|
||||
# 范围 U+2600 - U+26FF (Miscellaneous Symbols)
|
||||
# 包含:
|
||||
# 2600-2604 (太阳云雨等) -> Emoji? Yes (☀️, ☁️)
|
||||
# 2605-2606 (星星) -> 允许 (★, ☆)
|
||||
# 260E (电话 ☎️) -> Emoji
|
||||
# 26A0 (警告 ⚠️) -> Emoji
|
||||
#
|
||||
# 我们可以只过滤那些明显的 Emoji 范围,或者根据用户反馈的 Bad Case (⚠️) 进行定点清除。
|
||||
# 但为了通用性,最好能区分。
|
||||
#
|
||||
# 既然用户要求 "严禁使用任何 Emoji",且 "允许使用的特殊符号仅限 UTF-8收录范围,例如..."
|
||||
# 这是一个比较模糊的边界。
|
||||
#
|
||||
# 让我们引入 `unicodedata` 并结合范围判断。
|
||||
#
|
||||
# 更新逻辑:
|
||||
# 1. 保留 code > 0xFFFF 过滤 (处理了绝大多数 Emoji)
|
||||
# 2. 增加对 BMP Emoji 的过滤。
|
||||
# 由于 Python 标准库没有 is_emoji,我们只能根据 Block 进行粗略过滤,并豁免常用符号。
|
||||
|
||||
# 定义需要检查的 Block (潜在 Emoji 区域)
|
||||
# Dingbats: U+2700–U+27BF
|
||||
# Miscellaneous Symbols: U+2600–U+26FF
|
||||
# Transport and Map Symbols: U+1F680-U+1F6FF (已被 >0xFFFF 覆盖)
|
||||
|
||||
# 针对 BMP 的补充过滤:
|
||||
if 0x2600 <= code <= 0x27BF:
|
||||
# 白名单 (用户明确允许或常见的非Emoji符号)
|
||||
# U+2605 ★, U+2606 ☆
|
||||
# U+25A0-U+25FF (Geometric Shapes) -> 不在 2600-27BF 范围内,安全
|
||||
# U+2190-U+21FF (Arrows) -> 不在范围内,安全
|
||||
# U+300A 《, U+00B7 · -> 不在范围内,安全
|
||||
|
||||
# 允许的例外列表 (Decimal)
|
||||
# 9733 (★), 9734 (☆)
|
||||
allowed_in_range = [9733, 9734]
|
||||
|
||||
if code not in allowed_in_range:
|
||||
# 进一步检查:如果是 ⚠️ (9888) 或其他 Emoji,则过滤
|
||||
# 简单粗暴:在这个范围内,除了白名单,全部视为潜在 Emoji/不常用符号进行过滤?
|
||||
# 这样可能误杀太严重。
|
||||
#
|
||||
# 让我们仅过滤特定的 Emoji 子集。
|
||||
# ⚠️ U+26A0 (9888)
|
||||
# ⚡ U+26A1 (9889)
|
||||
# ⚰️ U+26B0
|
||||
# ⚽ U+26BD
|
||||
# ⛄ U+26C4
|
||||
# ⛳ U+26F3
|
||||
# ...
|
||||
#
|
||||
# 更好的方法:只过滤 > 0xFFFF 的字符 + 代理对 + 控制字符。
|
||||
# 对于 BMP 内的字符,除非用户指定要过滤,否则保留。
|
||||
# 但用户明确反馈 ⚠️ 未删除。
|
||||
#
|
||||
# 让我们针对性地过滤 "Emoji Presentation" 的字符。
|
||||
# 但没有库很难做到。
|
||||
#
|
||||
# 妥协方案:硬编码过滤常见的 BMP Emoji 范围,或仅过滤用户提到的 ⚠️。
|
||||
# 考虑到 "严禁使用任何 Emoji",我们应该扩大过滤范围。
|
||||
|
||||
# U+2600-U+26FF 包含很多天气、星座、棋子等,大多被视为 Emoji。
|
||||
# U+2700-U+27BF (Dingbats) 包含剪刀、飞机、信封等,大多被视为 Emoji。
|
||||
|
||||
# 如果我们过滤掉这两个区段,除了白名单?
|
||||
# 用户提到的 "允许特殊符号":
|
||||
# 星号 ★ (U+2605) -> 在 U+2600-U+26FF
|
||||
# 方块 ■ (U+25A0) -> 不在
|
||||
# 箭头 → (U+2192) -> 不在
|
||||
# 书名号 《 (U+300A) -> 不在
|
||||
# 间隔号 · (U+00B7) -> 不在
|
||||
|
||||
# 结论:只要保护好 ★ (及其他可能的符号),我们可以激进地过滤 U+2600-U+26FF 和 U+2700-U+27BF。
|
||||
pass # 逻辑将在下方实现
|
||||
|
||||
# 实现:
|
||||
# 1. 过滤 Dingbats (U+2700 - U+27BF)
|
||||
if 0x2700 <= code <= 0x27BF:
|
||||
continue
|
||||
|
||||
# 2. 过滤 Miscellaneous Symbols (U+2600 - U+26FF),但保留白名单
|
||||
if 0x2600 <= code <= 0x26FF:
|
||||
# 白名单:
|
||||
# U+2605 ★ (9733)
|
||||
# U+2606 ☆ (9734)
|
||||
whitelist_26xx = {0x2605, 0x2606}
|
||||
if code not in whitelist_26xx:
|
||||
continue
|
||||
|
||||
# 3. 过滤 Miscellaneous Technical 中常见的 Emoji (U+23xx)
|
||||
# ⌚ (231A), ⌛ (231B), ⌨ (2328)
|
||||
# ⏩ (23E9) - ⏳ (23F3)
|
||||
# ⏸ (23F8) - ⏺ (23FA)
|
||||
if code in (0x231A, 0x231B, 0x2328, 0x23CF):
|
||||
continue
|
||||
if 0x23E9 <= code <= 0x23F3:
|
||||
continue
|
||||
if 0x23F8 <= code <= 0x23FA:
|
||||
continue
|
||||
|
||||
# 4. 过滤其他零散的常见 Emoji 符号
|
||||
# ⭐ (2B50), ⭕ (2B55)
|
||||
if code in (0x2B50, 0x2B55):
|
||||
continue
|
||||
|
||||
cleaned.append(c)
|
||||
|
||||
return "".join(cleaned)
|
||||
result_text = "".join(cleaned)
|
||||
# 强制统一换行符为 Unix 风格 (\n),防止 \r 导致打标工具报错
|
||||
result_text = result_text.replace("\r\n", "\n").replace("\r", "\n")
|
||||
return result_text
|
||||
|
||||
class UTF8ConverterNode:
|
||||
"""
|
||||
UTF-8 编码转换与文本清洗节点 (重构版):
|
||||
使用鲁棒的解码策略读取文件或处理文本,并执行严格的字符清洗。
|
||||
UTF-8 编码转换与文本清洗节点 (工业级强力重构版):
|
||||
完全对齐 fix_encoding.py 的逻辑,使用鲁棒的解码策略并执行极其严格的字符清洗。
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
@@ -258,6 +82,12 @@ class UTF8ConverterNode:
|
||||
"default": "",
|
||||
"placeholder": "输入文本 / Input Text"
|
||||
}),
|
||||
"remove_emoji": ("BOOLEAN", {
|
||||
"default": True,
|
||||
"label_on": "是 / Yes",
|
||||
"label_off": "否 / No",
|
||||
"tooltip": "是否强制删除所有 4 字节字符(包含绝大多数 Emoji)"
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
"input_file": ("STRING", {
|
||||
@@ -273,7 +103,7 @@ class UTF8ConverterNode:
|
||||
FUNCTION = "run"
|
||||
CATEGORY = "Rui-Node🐶/文本处理📝"
|
||||
|
||||
def run(self, input_text, input_file=""):
|
||||
def run(self, input_text, remove_emoji, input_file=""):
|
||||
log = []
|
||||
text_content = ""
|
||||
encoding_used = "none"
|
||||
@@ -285,9 +115,8 @@ class UTF8ConverterNode:
|
||||
|
||||
# 检查是否为二进制文件
|
||||
if is_binary_file(input_file):
|
||||
# 虽然是二进制,但我们仍尝试解码,或者直接报错
|
||||
# 根据需求"转化为utf-8",我们尝试强制解码
|
||||
log.append("警告: 检测到可能的二进制文件")
|
||||
log.append("警告: 检测到可能的二进制文件 (跳过)")
|
||||
return ("", "\n".join(log))
|
||||
|
||||
try:
|
||||
with open(input_file, "rb") as f:
|
||||
@@ -310,10 +139,10 @@ class UTF8ConverterNode:
|
||||
|
||||
# 2. 清洗文本
|
||||
log.append("-" * 20)
|
||||
log.append("开始清洗文本...")
|
||||
log.append("开始工业级强力清洗文本...")
|
||||
original_len = len(text_content)
|
||||
|
||||
cleaned_content = clean_text(text_content)
|
||||
cleaned_content = clean_text(text_content, remove_emoji=remove_emoji)
|
||||
|
||||
final_len = len(cleaned_content)
|
||||
removed_count = original_len - final_len
|
||||
@@ -324,7 +153,9 @@ class UTF8ConverterNode:
|
||||
log.append(f"移除字符数: {removed_count}")
|
||||
|
||||
if removed_count > 0:
|
||||
log.append("清洗详情: 已移除所有 Emoji、4字节字符、代理对及非法控制字符。")
|
||||
log.append(f"清洗详情: 已移除非法控制字符、代理对,并统一换行符为 \\n。")
|
||||
if remove_emoji:
|
||||
log.append("已开启 remove_emoji: 删除了所有 4 字节字符。")
|
||||
else:
|
||||
log.append("结果: 文本已符合规范,无需修改。")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user