吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1186|回复: 8
收起左侧

[Python 原创] 文本批量整理工具 适配Windows+银河麒麟系统

  [复制链接]
奇客 发表于 2026-6-30 17:59
以前用过一个文本整理器,不过只能在Windows运行,让豆包帮忙写了一个PY脚本,保留基本功能
图标text.ico放当前目录,这里就不传了
运行前自己看下看看环境依赖

没有版权,拿去自己随便改
文本整理工具.png

[Python] 纯文本查看 复制代码
import sys
import os
import platform

# ===================== 跨平台错误提示函数(兼容麒麟Linux + Windows) =====================
def show_error_msg(msg: str):
    # 判断操作系统
    if platform.system() == "Windows":
        # Windows使用弹窗
        import ctypes
        ctypes.windll.user32.MessageBoxW(0, msg, "运行环境缺失", 0x10)
    else:
        # 银河麒麟/Linux 终端彩色打印提示
        print("\033[31m==================== 运行环境缺失 ====================\033[0m")
        print(f"\033[33m{msg}\033[0m")
        print("\033[31m======================================================\033[0m")

# ===================== 前置环境检测,缺失依赖跨平台提示 =====================
try:
    import re
    # 全部替换为PyQt5
    from PyQt5.QtWidgets import (
        QApplication, QMainWindow, QTextEdit, QPushButton, QTabWidget,
        QWidget, QVBoxLayout, QHBoxLayout, QFileDialog, QGroupBox, QStatusBar,
        QCheckBox, QRadioButton, QLineEdit, QButtonGroup
    )
    from PyQt5.QtCore import Qt
    from PyQt5.QtGui import QFont, QColor, QPalette, QTextCursor, QIcon
    import zhconv
except ModuleNotFoundError as e:
    # 区分系统给出对应安装命令
    if platform.system() == "Linux":
        tip = f"检测到缺少运行依赖库:{e.name}\n银河麒麟系统请打开终端执行两条命令:\n1. sudo apt update\n2. sudo apt install python3-pip python3-pyqt5 libqt5core5a libqt5gui5 libqt5widgets5 qtbase5-dev qttools5-dev-tools libxcb-xinerama0 zhconv -y\n运行程序使用命令:python3 文本整理脚本.py"
    else:
        tip = f"检测到缺少运行依赖库:{e.name}\n请先安装Python,再执行命令:\npip install PyQt5 zhconv\n安装Python时务必勾选 Add Python to PATH"
    show_error_msg(tip)
    sys.exit(1)

# ===================== 文本处理函数 =====================
def remove_all_spaces(text: str) -> str:
    return re.sub(r'\s+', '', text)

def trim_line_space(text: str) -> str:
    lines = text.splitlines()
    return '\n'.join([line.strip() for line in lines])

def remove_empty_lines(text: str) -> str:
    return re.sub(r'\n+', '\n', text).strip('\n')

def traditional_to_simple(text: str) -> str:
    return zhconv.convert(text, 'zh-cn')

def eng_punc_to_cn(text: str) -> str:
    punc_map = {
        ",": ",", ".": "。", "?": "?", "!": "!",
        ":": ":", ";": ";", "(": "(", ")": ")",
        "[": "【", "]": "】"
    }
    for en, cn in punc_map.items():
        text = text.replace(en, cn)
    return text

def replace_content(text: str, old_str: str, new_str: str, use_regex: bool = False, case_sensitive: bool = False) -> str:
    if use_regex:
        flags = 0 if case_sensitive else re.IGNORECASE
        return re.sub(old_str, new_str, text, flags=flags)
    else:
        if case_sensitive:
            return text.replace(old_str, new_str)
        else:
            pattern = re.compile(re.escape(old_str), re.IGNORECASE)
            return pattern.sub(new_str, text)

def fullwidth_to_halfwidth(text: str) -> str:
    result = []
    for char in text:
        code = ord(char)
        if code == 0x3000:
            result.append(chr(0x0020))
        elif 0xFF01 <= code <= 0xFF5E:
            result.append(chr(code - 0xFEE0))
        else:
            result.append(char)
    return ''.join(result)

def count_text_stats(text: str) -> tuple:
    total_chars = len(text)
    pure_text = re.sub(r'\s+', '', text)
    pure_chars = len(pure_text)
    lines = text.count('\n') + 1 if text else 0
    return total_chars, pure_chars, lines

# ===================== 主窗口 =====================
class TextToolMain(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("文本批量整理工具")
        self.resize(1250, 750)
        self.setFont(QFont())
        self.status_bar = QStatusBar()
        self.setStatusBar(self.status_bar)
        self.find_pos = 0
        # 跨系统加载图标(麒麟/Windows通用相对路径)
        ico_path = os.path.join(os.getcwd(), "text.ico")
        if os.path.exists(ico_path):
            self.setWindowIcon(QIcon(ico_path))
        # 全局统一所有分组框样式
        self.setStyleSheet("""
            QGroupBox {
                border: 1px solid #888;
                border-radius: 4px;
                margin-top: 6px;
            }
            QGroupBox::title {
                subcontrol-origin: margin;
                left: 10px;
                padding: 0 4px;
            }
        """)
        self.init_ui()

    def init_ui(self):
        main_container = QWidget()
        main_v_layout = QVBoxLayout(main_container)
        main_v_layout.setContentsMargins(10, 10, 10, 10)
        main_v_layout.setSpacing(10)

        # 上方整体布局:左侧功能区 + 右侧标签编辑区
        top_widget = QWidget()
        top_h_layout = QHBoxLayout(top_widget)
        top_h_layout.setSpacing(10)

        # 左侧功能面板
        left_widget = QWidget()
        left_layout = QVBoxLayout(left_widget)
        left_layout.setSpacing(10)

        # 文件操作组
        file_group = QGroupBox("文件操作")
        file_layout = QVBoxLayout(file_group)
        file_layout.setSpacing(6)
        file_layout.setContentsMargins(10,10,10,10)
        self.btn_open = QPushButton("打开文件")
        self.btn_save = QPushButton("保存文件")
        self.btn_save_as = QPushButton("另存为")
        self.btn_save_all = QPushButton("全部保存")
        file_layout.addWidget(self.btn_open)
        file_layout.addWidget(self.btn_save)
        file_layout.addWidget(self.btn_save_as)
        file_layout.addWidget(self.btn_save_all)

        # 文本整理组
        func_group = QGroupBox("文本整理")
        func_layout = QVBoxLayout(func_group)
        func_layout.setSpacing(6)
        func_layout.setContentsMargins(10,10,10,10)
        self.btn_trim = QPushButton("行首尾去空格")
        self.btn_all_space = QPushButton("清除所有空格")
        self.btn_empty_line = QPushButton("删除空行")
        self.btn_f2j = QPushButton("繁体转简体")
        self.btn_punc = QPushButton("标点转中文")
        self.btn_full2half = QPushButton("全角转半角")
        func_layout.addWidget(self.btn_trim)
        func_layout.addWidget(self.btn_all_space)
        func_layout.addWidget(self.btn_empty_line)
        func_layout.addWidget(self.btn_f2j)
        func_layout.addWidget(self.btn_punc)
        func_layout.addWidget(self.btn_full2half)

        left_layout.addWidget(file_group)
        left_layout.addWidget(func_group)
        left_layout.addStretch()

        # 右侧:标签页
        self.tab_widget = QTabWidget()
        # 右上角:撤消 / 重做 PyQt5枚举修正
        corner_widget = QWidget()
        corner_layout = QHBoxLayout(corner_widget)
        corner_layout.setContentsMargins(0, 0, 5, 0)
        corner_layout.setSpacing(5)
        self.btn_undo = QPushButton("撤消")
        self.btn_redo = QPushButton("重做")
        self.btn_undo.setFixedSize(50, 23)
        self.btn_redo.setFixedSize(50, 23)
        corner_layout.addStretch()
        corner_layout.addWidget(self.btn_undo)
        corner_layout.addWidget(self.btn_redo)
        # PyQt5 枚举:Qt.TopRightCorner 替代 Qt.Corner.TopRightCorner
        self.tab_widget.setCornerWidget(corner_widget, Qt.TopRightCorner)

        self.edit_list = []
        for i in range(1, 5):
            edit = QTextEdit()
            edit.textChanged.connect(self.update_word_count)
            palette = edit.palette()
            palette.setColor(QPalette.Base, QColor(220, 240, 220))
            edit.setPalette(palette)
            edit.setUndoRedoEnabled(True)
            self.tab_widget.addTab(edit, f"文档{i}")
            self.edit_list.append(edit)

        top_h_layout.addWidget(left_widget)
        top_h_layout.addWidget(self.tab_widget, stretch=1)
        main_v_layout.addWidget(top_widget, stretch=1)

        # ===================== 底部横向双栏容器:查找替换 + 批量插入 =====================
        bottom_h_container = QWidget()
        bottom_h_layout = QHBoxLayout(bottom_h_container)
        bottom_h_layout.setSpacing(12)

        # ========== 左侧:查找替换区域 ==========
        find_replace_group = QGroupBox("查找替换")
        fr_layout = QVBoxLayout(find_replace_group)
        fr_layout.setContentsMargins(10,10,10,10)
        fr_layout.setSpacing(8)

        # 查找行
        fr_row1 = QHBoxLayout()
        self.btn_find = QPushButton("查找")
        self.edit_find = QLineEdit()
        self.edit_find.setPlaceholderText("查找内容")
        self.chk_regex = QCheckBox("正则表达式")
        fr_row1.addWidget(self.btn_find)
        fr_row1.addWidget(self.edit_find, stretch=1)
        fr_row1.addWidget(self.chk_regex)

        # 替换行
        fr_row2 = QHBoxLayout()
        self.btn_replace = QPushButton("替换")
        self.edit_replace = QLineEdit()
        self.edit_replace.setPlaceholderText("替换内容")
        right_btn_v = QVBoxLayout()
        self.chk_case = QCheckBox("区分大小写")
        self.btn_replace_all = QPushButton("全部替换")
        self.btn_replace_select = QPushButton("选区替换")
        right_btn_v.addWidget(self.chk_case)
        right_btn_v.addWidget(self.btn_replace_all)
        right_btn_v.addWidget(self.btn_replace_select)
        fr_row2.addWidget(self.btn_replace)
        fr_row2.addWidget(self.edit_replace, stretch=1)
        fr_row2.addLayout(right_btn_v)

        fr_layout.addLayout(fr_row1)
        fr_layout.addLayout(fr_row2)

        # ========== 右侧:批量插入文本区域 ==========
        insert_group = QGroupBox("批量插入文本")
        insert_layout = QVBoxLayout(insert_group)
        insert_layout.setContentsMargins(10,10,10,10)
        insert_layout.setSpacing(8)

        # 插入1行
        row1 = QHBoxLayout()
        self.btn_insert1 = QPushButton("插入1")
        self.line_insert1 = QLineEdit()
        self.line_insert1.setPlaceholderText("输入要插入的内容1")
        row1.addWidget(self.btn_insert1)
        row1.addWidget(self.line_insert1, stretch=1)

        # 插入2行
        row2 = QHBoxLayout()
        self.btn_insert2 = QPushButton("插入2")
        self.line_insert2 = QLineEdit()
        self.line_insert2.setPlaceholderText("输入要插入的内容2")
        row2.addWidget(self.btn_insert2)
        row2.addWidget(self.line_insert2, stretch=1)

        # 单选选项行
        radio_row = QHBoxLayout()
        self.radio_group = QButtonGroup()
        self.radio_cur_start = QRadioButton("在当前段首")
        self.radio_cur_end = QRadioButton("在当前段尾")
        self.radio_all_start = QRadioButton("在所有段首")
        self.radio_all_end = QRadioButton("在所有段尾")
        self.radio_group.addButton(self.radio_cur_start, 1)
        self.radio_group.addButton(self.radio_cur_end, 2)
        self.radio_group.addButton(self.radio_all_start, 3)
        self.radio_group.addButton(self.radio_all_end, 4)
        self.radio_cur_start.setChecked(True)
        radio_row.addWidget(self.radio_cur_start)
        radio_row.addWidget(self.radio_cur_end)
        radio_row.addWidget(self.radio_all_start)
        radio_row.addWidget(self.radio_all_end)
        radio_row.addStretch()

        insert_layout.addLayout(row1)
        insert_layout.addLayout(row2)
        insert_layout.addLayout(radio_row)

        # 将两个分组加入横向布局,左右平分宽度
        bottom_h_layout.addWidget(find_replace_group, stretch=1)
        bottom_h_layout.addWidget(insert_group, stretch=1)
        main_v_layout.addWidget(bottom_h_container)

        self.setCentralWidget(main_container)
        self.bind_event()
        self.update_word_count()

    def bind_event(self):
        # 原有按钮绑定
        self.btn_trim.clicked.connect(self.do_trim_line)
        self.btn_all_space.clicked.connect(self.do_clear_all_space)
        self.btn_empty_line.clicked.connect(self.do_clear_empty_line)
        self.btn_f2j.clicked.connect(self.do_f2j)
        self.btn_punc.clicked.connect(self.do_convert_punc)
        self.btn_full2half.clicked.connect(self.do_full2half)
        self.btn_undo.clicked.connect(self.do_undo)
        self.btn_redo.clicked.connect(self.do_redo)
        self.btn_open.clicked.connect(self.open_file)
        self.btn_save.clicked.connect(self.save_file)
        self.btn_save_as.clicked.connect(self.save_file_as)
        self.btn_save_all.clicked.connect(self.save_all_files)
        self.btn_find.clicked.connect(self.do_find)
        self.btn_replace.clicked.connect(self.do_single_replace)
        self.btn_replace_all.clicked.connect(self.do_all_replace)
        self.btn_replace_select.clicked.connect(self.do_select_replace)
        self.tab_widget.currentChanged.connect(self.reset_find_pos)
        self.tab_widget.currentChanged.connect(self.update_word_count)

        # 新增插入按钮绑定
        self.btn_insert1.clicked.connect(lambda: self.do_insert(self.line_insert1.text()))
        self.btn_insert2.clicked.connect(lambda: self.do_insert(self.line_insert2.text()))

    def get_current_edit(self) -> QTextEdit:
        return self.edit_list[self.tab_widget.currentIndex()]

    def get_current_text(self) -> str:
        return self.get_current_edit().toPlainText()

    # ===================== 核心修复:安全替换文本,保留撤销历史 =====================
    def safe_set_text(self, new_text: str):
        """替代 setPlainText,操作会存入撤销栈,支持Ctrl+Z回退"""
        edit = self.get_current_edit()
        cursor = edit.textCursor()
        # PyQt5 枚举修正:Document 直接用,移除SelectionType层级
        cursor.select(QTextCursor.Document)
        # 删除原有内容,插入新文本(整套操作作为1步撤销记录)
        cursor.insertText(new_text)
        # 重置光标到文档开头
        cursor.movePosition(QTextCursor.Start)
        edit.setTextCursor(cursor)

    # 原废弃函数,改为调用safe_set_text
    def set_current_text(self, text: str):
        self.safe_set_text(text)

    def reset_find_pos(self):
        self.find_pos = 0

    def update_word_count(self):
        text = self.get_current_text()
        total, pure, lines = count_text_stats(text)
        self.status_bar.showMessage(f"总行数:{lines} | 总字符数:{total} | 纯文字数:{pure}")

    def do_undo(self):
        edit = self.get_current_edit()
        if edit.document().isUndoAvailable():
            edit.undo()
            self.status_bar.showMessage("撤消成功")
            self.update_word_count()
        else:
            self.status_bar.showMessage("无可撤消操作")

    def do_redo(self):
        edit = self.get_current_edit()
        if edit.document().isRedoAvailable():
            edit.redo()
            self.status_bar.showMessage("重做成功")
            self.update_word_count()
        else:
            self.status_bar.showMessage("无可重做操作")

    def do_find(self):
        keyword = self.edit_find.text().strip()
        if not keyword:
            return
        edit = self.get_current_edit()
        content = edit.toPlainText()
        use_re = self.chk_regex.isChecked()
        case_sensitive = self.chk_case.isChecked()
        cursor = edit.textCursor()
        cursor.movePosition(QTextCursor.Start)
        if use_re:
            try:
                flags = 0 if case_sensitive else re.IGNORECASE
                pattern = re.compile(keyword, flags)
                match = pattern.search(content, self.find_pos)
                if match:
                    start = match.start()
                    end = match.end()
                    cursor.setPosition(start)
                    cursor.setPosition(end, QTextCursor.KeepAnchor)
                    edit.setTextCursor(cursor)
                    self.find_pos = end
                else:
                    self.find_pos = 0
                    self.status_bar.showMessage("已查找至文档末尾")
            except re.error:
                self.status_bar.showMessage("正则表达式格式错误")
                self.find_pos = 0
        else:
            if case_sensitive:
                idx = content.find(keyword, self.find_pos)
            else:
                idx = content.lower().find(keyword.lower(), self.find_pos)
            if idx != -1:
                cursor.setPosition(idx)
                cursor.setPosition(idx + len(keyword), QTextCursor.KeepAnchor)
                edit.setTextCursor(cursor)
                self.find_pos = idx + len(keyword)
            else:
                self.find_pos = 0
                self.status_bar.showMessage("已查找至文档末尾")

    def do_single_replace(self):
        old = self.edit_find.text()
        new = self.edit_replace.text()
        if not old:
            return
        edit = self.get_current_edit()
        if edit.hasSelectedText():
            edit.insertPlainText(new)
        self.do_find()

    def do_all_replace(self):
        old = self.edit_find.text()
        new = self.edit_replace.text()
        use_re = self.chk_regex.isChecked()
        case_sensitive = self.chk_case.isChecked()
        if not old:
            return
        text = replace_content(self.get_current_text(), old, new, use_re, case_sensitive)
        self.safe_set_text(text)
        self.find_pos = 0
        self.status_bar.showMessage("全部替换完成")

    def do_select_replace(self):
        old = self.edit_find.text()
        new = self.edit_replace.text()
        use_re = self.chk_regex.isChecked()
        case_sensitive = self.chk_case.isChecked()
        edit = self.get_current_edit()
        selected_text = edit.selectedText()
        if not old or not selected_text:
            self.status_bar.showMessage("请先选中内容并填写查找关键词")
            return
        res = replace_content(selected_text, old, new, use_re, case_sensitive)
        edit.insertPlainText(res)
        self.status_bar.showMessage("选区替换完成")

    def do_trim_line(self):
        text = trim_line_space(self.get_current_text())
        self.safe_set_text(text)
        self.status_bar.showMessage("行首尾去空格完成")

    def do_clear_all_space(self):
        text = remove_all_spaces(self.get_current_text())
        self.safe_set_text(text)
        self.status_bar.showMessage("清除所有空格完成")

    def do_clear_empty_line(self):
        text = remove_empty_lines(self.get_current_text())
        self.safe_set_text(text)
        self.status_bar.showMessage("删除空行完成")

    def do_f2j(self):
        text = traditional_to_simple(self.get_current_text())
        self.safe_set_text(text)
        self.status_bar.showMessage("繁体转简体完成")

    def do_convert_punc(self):
        text = eng_punc_to_cn(self.get_current_text())
        self.safe_set_text(text)
        self.status_bar.showMessage("标点转中文完成")

    def do_full2half(self):
        text = fullwidth_to_halfwidth(self.get_current_text())
        self.safe_set_text(text)
        self.status_bar.showMessage("全角转半角完成")

    # ===================== 插入文本核心逻辑 =====================
    def do_insert(self, insert_str: str):
        if not insert_str:
            self.status_bar.showMessage("输入框内容为空,无需插入")
            return
        edit = self.get_current_edit()
        full_text = edit.toPlainText()
        lines = full_text.splitlines(keepends=True)
        select_id = self.radio_group.checkedId()

        # 1. 在当前段首
        if select_id == 1:
            cursor = edit.textCursor()
            block_num = cursor.blockNumber()
            if 0 <= block_num < len(lines):
                lines[block_num] = insert_str + lines[block_num]
        # 2. 在当前段尾
        elif select_id == 2:
            cursor = edit.textCursor()
            block_num = cursor.blockNumber()
            if 0 <= block_num < len(lines):
                lines[block_num] = lines[block_num].rstrip('\n') + insert_str + '\n'
        # 3. 在所有段首
        elif select_id == 3:
            new_lines = [insert_str + line for line in lines]
            lines = new_lines
        # 4. 在所有段尾
        elif select_id == 4:
            new_lines = [line.rstrip('\n') + insert_str + '\n' for line in lines]
            lines = new_lines

        # 写回编辑器(使用安全方法保留撤销)
        new_text = ''.join(lines)
        self.safe_set_text(new_text)
        self.status_bar.showMessage(f"插入内容「{insert_str}」执行完成")

    def open_file(self):
        path, _ = QFileDialog.getOpenFileName(self, "打开文本文件", "", "Text Files (*.txt);;All Files (*)")
        if not path:
            return
        try:
            with open(path, "r", encoding="utf-8") as f:
                content = f.read()
            # 打开文件允许清空撤销栈,属于全新文档加载
            self.get_current_edit().setPlainText(content)
            self.tab_widget.setTabText(self.tab_widget.currentIndex(), os.path.basename(path))
            self.reset_find_pos()
            self.update_word_count()
        except Exception as e:
            self.status_bar.showMessage(f"打开失败:{str(e)}")

    def save_file(self):
        idx = self.tab_widget.currentIndex()
        tab_text = self.tab_widget.tabText(idx)
        if tab_text in ("文档1", "文档2", "文档3", "文档4"):
            self.save_file_as()
            return
        path = tab_text if os.path.isabs(tab_text) else os.path.join(os.getcwd(), tab_text)
        try:
            with open(path, "w", encoding="utf-8") as f:
                f.write(self.get_current_text())
            self.status_bar.showMessage("保存成功")
        except Exception as e:
            self.status_bar.showMessage(f"保存失败:{str(e)}")

    def save_file_as(self):
        path, _ = QFileDialog.getSaveFileName(self, "另存为", "", "Text Files (*.txt);;All Files (*)")
        if not path:
            return
        try:
            with open(path, "w", encoding="utf-8") as f:
                f.write(self.get_current_text())
            self.tab_widget.setTabText(self.tab_widget.currentIndex(), os.path.basename(path))
            self.status_bar.showMessage("另存为成功")
        except Exception as e:
            self.status_bar.showMessage(f"另存为失败:{str(e)}")

    def save_all_files(self):
        for i in range(self.tab_widget.count()):
            self.tab_widget.setCurrentIndex(i)
            self.save_file()
        self.status_bar.showMessage("全部文档保存完成")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    # 跨系统任务栏图标
    ico_path = os.path.join(os.getcwd(), "text.ico")
    if os.path.exists(ico_path):
        app.setWindowIcon(QIcon(ico_path))
    window = TextToolMain()
    window.show()
    sys.exit(app.exec_())



免费评分

参与人数 4吾爱币 +9 热心值 +4 收起 理由
苏紫方璇 + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
papapo + 1 + 1 用心讨论,共获提升!
lpwhd + 1 + 1 能否打包成EXE文件传上来
shengruqing + 1 热心回复!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

husay 发表于 2026-7-1 17:19
没有成品下载地址吗?想下载试用
sai609 发表于 2026-7-1 06:54
ysjd22 发表于 2026-7-1 07:22
Manson9527 发表于 2026-7-1 08:44
先赞后看
nmgjinbin 发表于 2026-7-1 11:36
先赞后看+1
嘿嘿嘿001 发表于 2026-7-1 17:06

先赞后看+1
先赞后看+1
先赞后看+1
wu20080808 发表于 2026-7-1 17:14
感觉好高大尚呀
 楼主| 奇客 发表于 2026-7-2 09:26
sai609 发表于 2026-7-1 06:54
这软件能用于excel,wps表格?

有想法,问问豆包~ 目前仅限纯文本
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-7-10 19:25

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表