鱼C论坛

 找回密码
 立即注册
查看: 921|回复: 10

[作品展示] 【源码】MarDisTextConverter 源文本转换器

[复制链接]
发表于 2023-4-23 01:37:11 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本帖最后由 Twilight6 于 2023-4-23 11:36 编辑

MarDisTextConverter 源码

                               
登录/注册后可看大图

                               
登录/注册后可看大图

(温馨提示:要使程序正常运行,需要按照如下项目结构进行配置哦~)

本贴是源码贴,源码过长,所以新建帖子来进行引用。
如果感兴趣请访问:【源文本转换器】Markdown ←→ Discuz 源文本相互转换器,欢迎兄弟们捧场!


Ps: 源码很杂乱,仅供参考,不建议学习...
若拷贝此帖子源码,需要将 s 、code 标签后面的空格删除,否则可能会影响转换(拷贝需要修改,源码无需改动

项目结构




                                                                                                                                                                                        MarDisTextConverter/
                                                                                                                                                                                        │
                                                                                                                                                                                        ├── assets/
                                                                                                                                                                                        │        └── img
                                                                                                                                                                                        │                └── *.png
                                                                                                                                                                                        │
                                                                                                                                                                                        ├──  config/
                                                                                                                                                                                        │        ├── config.ini
                                                                                                                                                                                        │        └──ConfigReader.py
                                                                                                                                                                                        │
                                                                                                                                                                                        ├── converter/
                                                                                                                                                                                        │        └──TextConverter.py
                                                                                                                                                                                        │
                                                                                                                                                                                        ├── ui/
                                                                                                                                                                                        │        └── TextGUI.py
                                                                                                                                                                                        │
                                                                                                                                                                                        ├── main.png
                                                                                                                                                                                        ├── main.py
                                                                                                                                                                                        ├── README.md
                                                                                                                                                                                        └── UangSC

项目源码



源码打包资源下载:
游客,如果您要查看本帖隐藏内容请回复

Ps:免费和付费的是同样的源码都一样,希望大家能支持一下购买附件啦~

TextConverter.py

                               
登录/注册后可看大图

  1. import os
  2. import re
  3. from uuid import uuid4
  4. from chardet import detect

  5. from config.ConfigReader import ConfigReader


  6. class TextConverter:
  7.     config_reader = None
  8.     path = os.getcwd()
  9.     DToMDict = {}
  10.     TitleMaxSize = 7
  11.     TitleBold = 1
  12.     EnglishDefaultFont = "Times New Roman"
  13.     # 部分配置文件默认值与参数类型
  14.     ConfigDict = {
  15.         "TitleMaxSize": (int, 7, 5, 7),
  16.         "TitleBold": (int, 1, 0, 1),
  17.         "EnglishDefaultFont": (str, "Times New Roman")
  18.     }
  19.     _ConfigureBackup = {
  20.         'icons': {'icon': r'/assets/img/icon.png', 'about': r'/assets/img/about.png', 'convert': r'/assets/img/convert.png',
  21.                   'exit': r'/assets/img/exit.png', 'help': r'/assets/img/help.png', 'new': r'/assets/img/new.png',
  22.                   'maximize': r'/assets/img/maximize.png', 'minimize': r'/assets/img/minimize.png',
  23.                   'open': r'/assets/img/open.png', 'restore': r'/assets/img/restore.png', 'save': r'/assets/img/save.png'},
  24.         'styles': {'QButton': 'QToolButton,QPushButton{padding-top: 5px; padding-bottom: 5px;}',
  25.                    'QToolBar': 'QToolBar{background-color: #82B7DB; spacing: 15px;}',
  26.                    'textEditBg': 'background-color:#CBDCE9;'},
  27.         'fonts': {'TitleMaxSize': '7', 'TitleBold': '1', 'EnglishDefaultFont': 'Times New Roman'},
  28.         'settings': {'FixedSize': '1'}, 'files': {'RecentFile': '[]'}
  29.     }
  30.     _DefaultConfig = ['styles', 'fonts', 'settings']

  31.     @classmethod
  32.     def _loadConfig(cls, flag = False):
  33.         if cls.config_reader == None or flag:
  34.             cls.config_reader = ConfigReader(cls.path + "\config\config.ini")
  35.             for confName in cls.ConfigDict:
  36.                 confValue = cls.config_reader.get_fonts(confName)
  37.                 cls._configToType(confValue, confName)
  38.             cls._dToMDict()

  39.     @classmethod
  40.     def _configToType(cls, value, name):
  41.         try:
  42.             typeMethod = cls.ConfigDict[name]
  43.             value = typeMethod[0](value)
  44.             if typeMethod[0] == int:
  45.                 value = value if value >= typeMethod[2] and value <= typeMethod[3] else typeMethod[1]
  46.             setattr(TextConverter, name, value)

  47.         except Exception as e:
  48.             pass

  49.     @classmethod
  50.     def _dToMDict(cls):
  51.         for i in range(0, 4):
  52.             cls.DToMDict[str(cls.TitleMaxSize - i)] = "#" * (i + 1)
  53.         cls.DToMDict["Min"] = ""

  54.     @classmethod
  55.     def discuzToMarkdown(cls, text: str) -> str:
  56.         cls._loadConfig()
  57.         def _quote(text):
  58.             text = re.sub(r'\n*(\[quote\].*?\[/quote\])\n*', "\g<1>", text, flags=re.S)
  59.             text = re.sub(r'(\[quote\])(.*?)(\[/quote\])', lambda m: m.group(1) + "\n" + m.group(2).strip() + "\n" + m.group(3), text,
  60.                           flags=re.S)
  61.             text = re.sub(r'(\[/quote\])(\[quote\])', '\g<1>\n\g<2>', text, flags=re.S)
  62.             text = re.sub(r'((?<!\n)\[quote\])|(\[/quote\](?!\n))',
  63.                           lambda m: "\n" + m.group(1) if m.group(1) != None else m.group(2) + "\n", text, flags=re.S)
  64.             text = re.sub(r'\[quote\](.*?)\n\[/quote\]', lambda m: m.group(1).replace("\n", "\n> "),text, flags=re.S)
  65.             return text

  66.         text, dict_code = cls._replace(r"\[code\].*?\[/code\]", text, re.S)
  67.         calcTitle = lambda m: (cls.DToMDict[
  68.             str(cls.TitleMaxSize) if int(m.group(1)) > cls.TitleMaxSize else "Min" if (
  69.                     int(m.group(1)) <= cls.TitleMaxSize - 4) else str(m.group(1))]) + " " + m.group(4)
  70.         tag_list = {
  71.             r'(\[font=.*?\](.*?)\[/font\])': r'\g<2>',
  72.             r'\[size=(\d+)\]((\[b\])?(.*?)(\[/b\])?)\[/size\]': calcTitle,
  73.             r'\[backcolor=.*?\]\[color=.*?\]\[b\](.+?)\[/b\]\[/color\]\[/backcolor\]': r'`\g<1>`',
  74.             r'\[b\](.*?)\[\/b\]': r'**\1**',
  75.             r'\[i\](.*?)\[\/i\]': r'*\1*',
  76.             r'\[u\](.*?)\[\/u\]': r'<u>\1</u>',
  77.             r'\[align=center\](.*?)\[\/align\]': r'<center>\1</center>',
  78.             r'\[hr\]': '---',
  79.             r'\[s\](.*?)\[\/s\]': r'~~\1~~',
  80.             r'\[url=(.*?)\](.*?)\[/url\]': r'[\2](\1)',
  81.             r'\[code\]\n*([\s\S]*?)\n*\[\/code\]': r'```\n\1\n```'
  82.         }

  83.         for pattern, replace in tag_list.items():
  84.             if "font" in pattern:
  85.                 text = re.sub(pattern, replace, text)
  86.             elif "code" in pattern:
  87.                 text = cls._recovery(dict_code, text)
  88.                 text = re.sub(pattern, replace, text)
  89.             else:
  90.                 text = re.sub(pattern, replace, text, flags=re.DOTALL)
  91.         text = _quote(text)
  92.         return text.strip()

  93.     @classmethod
  94.     def markdownToDiscuz(cls, text: str) -> str:
  95.         cls._loadConfig()

  96.         def _quote_to_Discuz(text):
  97.             result, temp, new_text = [], [], []
  98.             for i in text.splitlines():
  99.                 if i[0:1] != ">":
  100.                     if temp != []:
  101.                         new_text.append("[quote]" + "\n".join(temp) + "[/quote]")
  102.                         temp = []
  103.                     new_text.append(i)
  104.                     result.append(i)
  105.                 else:
  106.                     temp.append(i.strip()[2 if i[1:2] == " " else 1:])
  107.             if temp != []:
  108.                 new_text.append("[quote]" + "\n".join(temp) + "[/quote]")
  109.             return "\n".join(new_text)

  110.         b_start, b_end = ("[b]", "[/b]") if cls.TitleBold else ("", "")
  111.         text = re.sub(r'```\n*(.*?)\n*```', r'[code]\n\1\n[/code ]', text, flags=re.DOTALL)
  112.         text, dict_code = cls._replace(r"\[code\].*?\[/code\]", text, re.S)
  113.         tag_list = {
  114.             r'\[(.*?)\]\((http[s ]?://[^\s\[\]]+)\)': r'[url=\2]\1[/url]',
  115.             r'((\n#+)\s*(.*))|(^(#+)\s*(.*))': lambda
  116.                 m: ("" if m.group(1) == None else "\n") + rf"[size={(cls.TitleMaxSize - m.group(0).count('#') + 1) if (cls.TitleMaxSize - m.group(0).count('#') + 1) > 0 else 1}]{b_start + (m.group(3) if m.group(3) != None else m.group(6)) + b_end}[/size]",
  117.             r'\*\*(.*?)\*\*': r'[b]\1[/b]',
  118.             r'\*(.*?)\*': r'[i]\1[/i]',
  119.             r'<u>(.*?)</u>': r'[u]\1[/u]',
  120.             r'<center>(.*?)</center>': r'[align=center]\1[/align]',
  121.             r'---\n': '[hr]\n',
  122.             r'~~(.*?)~~': r'[s ]\1[/s]',
  123.             r'```\n*(.*?)\n*```': r'[code]\n\1\n[//code]', # 此处若有两个 // 则需要删去一个
  124.             r"`(.+?)`": r"[backcolor=Gainsboro][color=black][b]\g<1>[/b][/color][/backcolor]"
  125.         }
  126.         for pattern, replace in tag_list.items():
  127.             if "#" in pattern or "http" in pattern:
  128.                 text = re.sub(pattern, replace, text)
  129.             else:
  130.                 text = re.sub(pattern, replace, text, flags=re.DOTALL)
  131.         # 引用匹配转换
  132.         text = _quote_to_Discuz(text)
  133.         # [url]、普通 url 临时替换
  134.         text, dict_url = cls._replace(
  135.             r"\[url=?https?:\/\/[-A-Za-z0-9+&@#\/%?=~_|!:,.;]*[-A-Za-z0-9+&@#\/%=~_|](?=(?:[^`]*`[^`]*`)*[^`]*$)\].*\[/url\]",
  136.             text)
  137.         text, dict_url2 = cls._replace(
  138.             r"https?:\/\/[-A-Za-z0-9+&@#\/%?=~_|!:,.;]*[-A-Za-z0-9+&@#\/%=~_|](?=(?:[^`]*`[^`]*`)*[^`]*$)", text)

  139.         # 英文将使用 Times New Roman 字体
  140.         text = re.sub(r"[a-zA-Z]+(?![^\[]*\])", f"[font={cls.EnglishDefaultFont}]\g<0>[/font]", text)

  141.         text = cls._recovery(dict_code, text)
  142.         text = cls._recovery(dict_url, text)
  143.         text = cls._recovery(dict_url2, text)

  144.         return text.strip()

  145.     @staticmethod
  146.     def _replace(pattern, text, flags=0):
  147.         codeDict = {}
  148.         while data := re.search(pattern, text, flags):
  149.             id = str(uuid4())
  150.             text = re.sub(pattern, f"[{id}]", text, count=1, flags=flags)
  151.             codeDict[id] = data.group(0)

  152.         return text, codeDict

  153.     @staticmethod
  154.     def _recovery(codeDict, text):
  155.         for i, j in codeDict.items():
  156.             text = re.sub("\[" + i + "\]", j, text, count=1)
  157.         return text

  158.     @staticmethod
  159.     def _getFileEncoding(file):
  160.         with open(file, "rb") as f:
  161.             result = detect(f.read())
  162.             encoding = result["encoding"]
  163.         if encoding:
  164.             return encoding
  165.         return "utf-8"

  166.     # 暂未实现 -> 文本转换时检测类型
  167.     @classmethod
  168.     def _checkType(cls, text):
  169.         pass
复制代码



TextGUI.py

                               
登录/注册后可看大图

  1. from os.path import split, isfile, samefile, exists
  2. from time import sleep

  3. import chardet
  4. from PyQt5.QtWidgets import QMainWindow, QAction, QTextEdit, QFileDialog, QMessageBox, QDesktopWidget, \
  5.     QComboBox, QWidget, QHBoxLayout, QLabel, QStatusBar, QApplication
  6. from PyQt5.QtGui import QIcon, QColor, QFont, QPixmap, QDesktopServices
  7. from PyQt5.QtCore import Qt, QUrl, QSize, pyqtSignal, QThread

  8. from config.ConfigReader import ConfigReader
  9. from converter.TextConverter import TextConverter as Tc


  10. class MyTextEdit(QMainWindow):
  11.     _version = "v0.1.1"
  12.     def __init__(self, exePath, log):
  13.         super().__init__()

  14.         self._log = log
  15.         self._exe = exePath
  16.         path, _ = split(exePath)
  17.         self.config_path = path + r"\config\config.ini"
  18.         self.config_reader = ConfigReader(self.config_path)
  19.         self._setImg(path)
  20.         try:
  21.             self.FixedSize = int(self.config_reader.get_settings("FixedSize"))
  22.             self.RecentFile = eval(self.config_reader.get_files("RecentFile"))
  23.         except Exception:
  24.             self.FixedSize = 1
  25.             self.RecentFile = []
  26.             self._log.exception(Exception("配置文件异常,部分属性已使用默认值!"))
  27.         self.currentFilePath = ""
  28.         self.msg = ""
  29.         self.error = self.first = 1

  30.         # 设置窗口标题和图标
  31.         self.setWindowTitle("MarDisTextConverter——源文本转换编辑器          &#9829;~\\(≧v≦*~\\)0Oo。")
  32.         icon = QIcon(self._Icon)
  33.         self.setWindowIcon(icon)
  34.         self.windowIcon().addFile("icon.png", QSize(48, 48))

  35.         # 设置窗口大小和位置
  36.         self.setGeometry(300, 200, 820, 760)
  37.         if self.FixedSize:
  38.             self.setFixedSize(820, 760)
  39.         # 透明度
  40.         self.setWindowOpacity(0.95)
  41.         self.center()
  42.         # 添加工具栏和按钮
  43.         toolbar = self.addToolBar("主工具栏")

  44.         # 取消默认主工具栏选项
  45.         self.setContextMenuPolicy(Qt.CustomContextMenu)
  46.         self.customContextMenuRequested.connect(lambda: None)

  47.         # 设置撤回组件
  48.         undoAction = QAction(self)
  49.         undoAction.setShortcut('Ctrl+Z')
  50.         self.addAction(undoAction)

  51.         # 添加菜单栏
  52.         self.menubar = self.menuBar()

  53.         file_menu = self.menubar.addMenu("文件")
  54.         self._addQAcion(file_menu, "新建文件", "", "快捷键:Ctrl+N", "Ctrl+N" ,self.createFile)
  55.         self._addQAcion(file_menu, "打开", "", "快捷键:Ctrl+O", "Ctrl+O" ,self.openFile)
  56.         self._addQAcion(file_menu, "保存", key="Ctrl+S", slot=self.saveCurrentFile)
  57.         self._addQAcion(file_menu, "文件另存为...", key="Ctrl+Shift+S", slot= lambda : self.saveFile(1))
  58.         self.recent_files_menu = file_menu.addMenu('最近打开的文件')
  59.         if self.RecentFile:
  60.             self.updateRecent()

  61.         edit_menu = self.menubar.addMenu("编辑")
  62.         self.textEdit = QTextEdit()
  63.         self.textEdit.textChanged.connect(self.calculate_char_count)
  64.         self._addQAcion(edit_menu, "撤回", key="Ctrl+Z", slot=self.textEdit.undo)
  65.         self._addQAcion(edit_menu, "复制", key="Ctrl+C", slot=self.textEdit.copy)
  66.         self._addQAcion(edit_menu, "剪切", key="Ctrl+X", slot=self.textEdit.cut)
  67.         self._addQAcion(edit_menu, "黏贴", key="Ctrl+V", slot=self.textEdit.paste)
  68.         self._addQAcion(edit_menu, "清空文本框", key="Ctrl+L", slot=self._clear)

  69.         self.copypath = QAction("拷贝文件路径", self)
  70.         self.copypath.triggered.connect(self.copyPath)
  71.         self.copypath.setEnabled(False)
  72.         edit_menu.addAction(self.copypath)

  73.         setup_menu = self.menubar.addMenu("设置")
  74.         self._addQAcion(setup_menu, "打开配置文件", key="Ctrl+Alt+S", slot=lambda: self.openFile(self.config_path))
  75.         self._addQAcion(setup_menu, "恢复默认设置", slot=self._restoreDefaults)

  76.         self.widget = QWidget(self)
  77.         layout = QHBoxLayout(self.widget)
  78.         self.menuRLable = QLabel(" " * 120)
  79.         self.menuRLable.setAlignment(Qt.AlignRight)
  80.         layout.addWidget(self.menuRLable)
  81.         layout.setContentsMargins(5, 3, 5, 0)
  82.         self.widget.setLayout(layout)

  83.         self.updateT = self.WorkerThread()
  84.         self.updateT.update_signal.connect(self._outputText)
  85.         self.updateT.start()

  86.         # 将包含文本标签的布局添加到菜单栏的角
  87.         self.menubar.setCornerWidget(self.widget)

  88.         # 工具栏各按钮添加、描述并设置快捷键
  89.         self.convertComboBox = QComboBox(self)
  90.         self.convertComboBox.addItem("Markdown 转 Discuz")
  91.         self.convertComboBox.addItem("Discuz 转 Markdown")
  92.         self.convertComboBox.setCurrentIndex(0)
  93.         self._addQAcion(key="Ctrl+Shift+D", slot=self.MarToDis)
  94.         self._addQAcion(key="Ctrl+Shift+M", slot=self.DisToMar)
  95.         self.convertComboBox.setToolTip("转换格式")
  96.         toolbar.addWidget(self.convertComboBox)
  97.         self._addQAcion(toolbar, "打开", self._Open, "打开文件(Ctrl+O)", "Ctrl+O", self.openFile)
  98.         self._addQAcion(toolbar,"保存", self._Save, "保存文件(快捷键:Ctrl+S)", "",
  99.                         self.saveCurrentFile)
  100.         self._addQAcion(toolbar,"转换", self._Convert, "转换格式(快捷键:Ctrl+T)", "Ctrl+T",
  101.                         self.convert)
  102.         self._addQAcion(toolbar,"帮助", self._Help, "打开帮助页面(快捷键:Ctrl+H)", "Ctrl+H",
  103.                         self.showHelp)
  104.         self._addQAcion(toolbar,"关于", self._About, "更多信息", slot=self.showAbout)
  105.         self._addQAcion(toolbar,"最小化", self._Minimize, "最小化窗口(快捷键:Ctrl+M)", "Ctrl+M",
  106.                         self.minimizeWindow)
  107.         self.maxButton = self._addQAcion(toolbar,"最大化", self._Maximize, "最大/恢复化窗口(快捷键:Ctrl+D)", "Ctrl+D",
  108.                         self.showMaximizedOrRestore)
  109.         self._addQAcion(toolbar,"退出程序", self._Exit, "退出应用程序(Ctrl+Q)", "Ctrl+Q",
  110.                         self.close)

  111.         if self.FixedSize:
  112.             self.maxButton.setEnabled(False)

  113.         # 设置工具栏按钮占满整个工具栏
  114.         toolbar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
  115.         toolbar.setStyleSheet(self.config_reader.get_style("QButton"))
  116.         toolbar.setStyleSheet(self.config_reader.get_style("QToolBar"))

  117.         # 设置窗口颜色主题
  118.         palette = self.palette()
  119.         palette.setColor(palette.Window, QColor(130, 183, 219))
  120.         self.setPalette(palette)

  121.         # 设置文本编辑框
  122.         self.textEdit.setAcceptRichText(False)
  123.         # 调整字体大小
  124.         self.textEdit.setFont(QFont("Microsoft YaHei", 12))

  125.         self.setCentralWidget(self.textEdit)
  126.         # 设置编辑器背景颜色
  127.         self.textEdit.setStyleSheet(self.config_reader.get_style("textEditBg"))

  128.         # 创建一个状态栏
  129.         self.status_bar = QStatusBar()
  130.         self.setStatusBar(self.status_bar)
  131.         self.count_label = QLabel(f'共计:0 行  字符个数为:0', self.status_bar)
  132.         self.count_label.setAlignment(Qt.AlignRight)
  133.         self.status_bar.addWidget(self.count_label)
  134.         self.status_bar.addPermanentWidget(self.count_label)
  135.         self.file_label = QLabel("<b>当前暂未打开文件...</b>")
  136.         self.file_label.setAlignment(Qt.AlignCenter)
  137.         self.status_bar.addWidget(self.file_label)

  138.     class WorkerThread(QThread):
  139.         update_signal = pyqtSignal(str)  # 自定义信号

  140.         def __init__(self):
  141.             super().__init__()
  142.             self.text = ""
  143.             self.idx = 1

  144.         def run(self):
  145.             while True:
  146.                 if self.idx < len(self.text) + 1:
  147.                     self.update_signal.emit(self.text[:self.idx])
  148.                     self.idx += 1
  149.                 sleep(0.04)

  150.         def setText(self, text):
  151.             self.idx = 1
  152.             self.text = text

  153.     # 读取配置文件获取图片路径
  154.     def _setImg(self, path):
  155.         sections = self.config_reader.config._sections["icons"]
  156.         for k in sections:
  157.             setattr(MyTextEdit, "_" + k[:1].upper() + k[1:], path + sections[k])

  158.     def _clear(self):
  159.         self.textEdit.selectAll()
  160.         self.textEdit.textCursor().removeSelectedText()
  161.     def MarToDis(self):
  162.         self.convertComboBox.setCurrentIndex(0)
  163.         self.convert()

  164.     def DisToMar(self):
  165.         self.convertComboBox.setCurrentIndex(1)
  166.         self.convert()

  167.     # 创建新文件
  168.     def createFile(self):
  169.         msgBox = QMessageBox(self)
  170.         icon = QIcon(self._New)
  171.         msgBox.setIconPixmap(icon.pixmap(48, 48))
  172.         msgBox.setWindowIcon(QIcon(self._Icon))
  173.         msgBox.setWindowTitle("创建新文件")
  174.         msgBox.setText("在新窗口中创建并打开新文件,还是本窗口?")
  175.         msgBox.addButton("新窗口", QMessageBox.YesRole)
  176.         msgBox.addButton("本窗口", QMessageBox.NoRole).setFocus()
  177.         msgBox.addButton("取消", QMessageBox.RejectRole)
  178.         isNew = msgBox.exec_()
  179.         if isNew == 0:
  180.             self.updateT.setText("Biu~</b> (*/ω \*)嘿嘿,又冒出一个我~")
  181.             QDesktopServices.openUrl(QUrl.fromLocalFile(self._exe))
  182.         elif isNew == 1:
  183.             self.textEdit.selectAll()
  184.             self.textEdit.textCursor().removeSelectedText()
  185.             self.currentFilePath = ""
  186.             self.file_label.setText(f" <b>新建文件</b>,暂未保存... ")
  187.             self.updateT.setText("ψ(._.  )、 嘿咻~</b> 我帮你清空了文本框的文字啦~ ")
  188.         else:
  189.             self.updateT.setText("(*^-^*)</b> 你取消了新文件的创建哦~ ")

  190.     # 统计文本数据信息
  191.     def calculate_char_count(self):
  192.         text = self.textEdit.toPlainText()
  193.         char_count = len(text)
  194.         doc = self.textEdit.document()
  195.         line_count = doc.lineCount()
  196.         self.count_label.setText(f'共计: <b>{line_count}</b> 行  字符个数为:<b>{char_count}</b>')

  197.     # 更新左下角当前打开的文件
  198.     def update_file_label(self, fileName):
  199.         self.file_label.setText(f"当前文件:<b>{fileName}</b>")
  200.     # 恢复默认设置
  201.     def _restoreDefaults(self):
  202.         reply = QMessageBox.question(
  203.             self, '是否恢复默认配置?...(*°▽°*)~!',
  204.             '此操作会将配置恢复为<span style="color:red; font-weight:bold;">默认配置</span>!',
  205.             QMessageBox.Yes | QMessageBox.No, QMessageBox.No)

  206.         if reply == QMessageBox.Yes:
  207.             self.config_reader.restore = 1
  208.             self.config_reader.save_Config(Tc._ConfigureBackup)
  209.             Tc._loadConfig(True)
  210.             self.openFile(self.config_path, True)
  211.             self.config_reader.restore = 0
  212.         else:
  213.             self.updateT.setText("(*/ω\*)</b>¥@!呼....好可怕,差点...失忆!")

  214.     # 移动窗口屏幕中央
  215.     def center(self):
  216.         screen = QDesktopWidget().screenGeometry()
  217.         x = (screen.width() - self.width()) // 2
  218.         y = (screen.height() - self.height()) // 2
  219.         self.move(x, y)


  220.     # 添加工具栏按键
  221.     def _addQAcion(self, module=None, name="", img_path="", tip="", key=None, slot=None):
  222.         if img_path:
  223.             action = QAction(QIcon(img_path), name, self)
  224.         else:
  225.             action = QAction(name, self)
  226.         action.setToolTip(tip) if tip else None
  227.         action.setShortcut(key) if key else None
  228.         action.triggered.connect(slot)
  229.         if module != None:
  230.             module.addAction(action)
  231.         else:
  232.             self.addAction(action)
  233.         return action

  234.     # 拷贝文件路径
  235.     def copyPath(self):
  236.         QApplication.clipboard().setText(self.currentFilePath)

  237.     # 添加到最近文件列表
  238.     def addRecent(self, path):
  239.         if path in self.RecentFile:
  240.             self.RecentFile.remove(path)
  241.         elif len(self.RecentFile) == 5:
  242.             self.RecentFile.pop()
  243.         self.RecentFile.insert(0, path)
  244.         self.updateRecent()

  245.     # 更新最近文件列表
  246.     def updateRecent(self):
  247.         self.recent_files_menu.clear()
  248.         for i in range(len(self.RecentFile)):
  249.             file_action = self.recent_files_menu.addAction(f"{i + 1}. {split(self.RecentFile[i])[1]}")
  250.             file_action.triggered.connect(self.open_recent_file)
  251.         self.config_reader.update_recentFile(self.RecentFile)

  252.     # 打开最近文件
  253.     def open_recent_file(self):
  254.         file = self.sender().text()
  255.         idx, fileName = file.split(". ", 1)
  256.         filePath = self.RecentFile[int(idx) - 1]
  257.         self.openFile(filePath)

  258.     # 打开文件
  259.     def openFile(self, filePath="", confmsg=False):
  260.         temp = False
  261.         flag = self.msg
  262.         try:
  263.             if not filePath:
  264.                 filePath, _ = QFileDialog.getOpenFileName(self, "选择文件", "",
  265.                                                           "*.txt, *.md, ... (*.*);;文本文档 (*.txt);;Markdown 文档 (*.md)")
  266.             temp = True
  267.             if filePath != "" and not isfile(filePath):
  268.                 self.msg = f"抱歉嗷~</b> 我似乎找不到文件呢 /( ̄x ̄)/~~?"
  269.             elif filePath:
  270.                 self.currentFilePath = filePath
  271.                 with open(filePath, encoding=Tc._getFileEncoding(filePath)) as f:
  272.                     text = f.read().replace("\u200b", "\t")
  273.                     self.textEdit.setPlainText(text)
  274.                 self.error, fileName = 1, split(filePath)[1]

  275.                 self.msg = f"[{fileName}]</b> 已经打开!<b>o(* ̄v ̄*)o</b>" if not confmsg else f"<b>o(* ̄w ̄*)o</b> 配置已还原~!"

  276.                 self.update_file_label(fileName)
  277.                 self.updateRecent()
  278.                 # 添加入最近打开的文件菜单项
  279.                 self.addRecent(filePath)
  280.                 self.copypath.setEnabled(True)
  281.             elif not self.currentFilePath:
  282.                 self.msg = f"咦 (⊙o⊙)?</b> 似乎没有选择打开文件嗷!"
  283.         except Exception as e:
  284.             if temp and filePath:
  285.                 self.error, fileName = 0, split(filePath)[1]
  286.                 self.msg = f"[{fileName}]</b> 打开失败!<b>/(ㄒoㄒ)/~~</b> 可能<b>不支持</b>此类文件嗷!"
  287.             else:
  288.                 self.msg = f"程序异常,请到程序根目录下的 debug.log 中查看异常信息</b> "
  289.                 raise e
  290.         if self.msg != flag:
  291.             self.updateT.setText(self.msg)

  292.     # 信号槽函数,更新菜单栏右侧 Label
  293.     def _outputText(self, text):
  294.         self.menuRLable.setText("<b>" + text)

  295.     # 覆盖当前打开文件
  296.     def saveCurrentFile(self):
  297.         if isfile(self.currentFilePath) and self.error:
  298.             path, fileName = split(self.currentFilePath)
  299.             try:
  300.                 _isOrNo = QMessageBox.question(
  301.                     self, '保存文件 (~^_^)~',
  302.                     f'是否<span style="color:red; font-weight:bold;">覆盖<b>{fileName}</b>文件</span>(建议<b>备份</b>后保存)进行保存?',
  303.                     QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
  304.                 if _isOrNo == QMessageBox.Yes:
  305.                     if not self.saveConfig(self.currentFilePath):
  306.                         with open(self.currentFilePath, "w", encoding=Tc._getFileEncoding(self.currentFilePath)) as f:
  307.                             f.write(self.textEdit.toPlainText())
  308.                     else:
  309.                         Tc._loadConfig(True)
  310.                     self.msg = f"[{fileName}]</b> 文件保存成功!<b>~(*^_^*)~</b>"
  311.                 else:
  312.                     self.msg = f"取消保存,建议备份文件后保存</b> <b>(~^o^~)</b>"

  313.             except Exception as e:
  314.                 self.msg = f"[{fileName}]</b> 文件保存失败!<b>~(*ㄒoㄒ*)~</b> 异常原因:<span  style='color: red; font-weight:bold;'>{str(e)}</span>"
  315.                 self._log.exception(e)
  316.             self.updateT.setText(self.msg)
  317.         else:
  318.             self.saveFile()

  319.     # 保存文件
  320.     def saveFile(self, saveAs=0):
  321.         flag = 0
  322.         try:
  323.             path, fileName = "", "FishC.txt"
  324.             if self.currentFilePath:
  325.                 path, filePath = split(self.currentFilePath)
  326.             filePath, _ = QFileDialog.getSaveFileName(self, "保存文件", fileName,
  327.                                                       "文本文档 (*.txt);;Markdown 文档 (*.md)")
  328.             if filePath:
  329.                 if saveAs or not self.saveConfig(filePath):
  330.                     if saveAs or not exists(filePath):
  331.                         encoding = "utf-8"
  332.                     else:
  333.                         encoding = Tc._getFileEncoding(filePath)
  334.                     with open(filePath, "w", encoding= encoding)  as f:
  335.                         f.write(self.textEdit.toPlainText())
  336.                 _, fileName = split(filePath)
  337.                 self.msg = f"[{fileName}]</b> 文件保存成功!<b>~(= ̄ω ̄=)~</b>"
  338.                 if saveAs:
  339.                     self.currentFilePath = filePath
  340.                 self.update_file_label(fileName)
  341.                 flag = 1
  342.                 self.addRecent(filePath)
  343.                 self.copypath.setEnabled(True)
  344.         except Exception as e:
  345.             if filePath:
  346.                 _, fileName = split(filePath)
  347.                 self.msg = f"[{fileName}]</b> 文件保存失败!<b>~(*ㄒoㄒ*)~</b> 异常原因:<span  style='color: red; font-weight:bold;'>{str(e)}</span>"
  348.             else:
  349.                 self.msg = f"~(*ㄒoㄒ*)~ </b> 文件保存失败!异常原因:<span  style='color: red; font-weight:bold;'>{str(e)}</span>"
  350.             self._log.exception(e)
  351.             flag = 1
  352.         if flag:
  353.             self.updateT.setText(self.msg)

  354.     # 保存配置
  355.     def saveConfig(self, path):
  356.         if exists(path) and samefile(self.config_path, path):
  357.             self.config_reader.save_Config(ConfigReader(self.textEdit.toPlainText(), True).config._sections)
  358.             self.msg = "[config.ini}]</b> 文件保存成功!<b>~(= ̄ω ̄=)~</b>"
  359.             self.update_file_label("config.ini")
  360.             self.addRecent(path)
  361.             self.copypath.setEnabled(True)
  362.             return True
  363.         return False

  364.     # 转换
  365.     def convert(self):
  366.         try:
  367.             if not self.textEdit.toPlainText().strip():
  368.                 raise Exception("文本框中无内容,转换个空气...给我系内!")
  369.             elif self.currentFilePath and samefile(self.currentFilePath, self.config_path):
  370.                 raise Exception("(ˉ▽ˉ;)... 这是我配置文件,<b>无需</b>转换嗷!")
  371.             currentIndex = self.convertComboBox.currentIndex()
  372.             if currentIndex == 0:
  373.                 # 进行 Markdown 转 Discuz 操作
  374.                 text = Tc.markdownToDiscuz(self.textEdit.toPlainText())
  375.                 self.textEdit.setPlainText(text)
  376.                 self.msg = f"Markdown → Discuz</b> 文件转换成功!<b>(~ ̄▽ ̄)~</b>"
  377.             elif currentIndex == 1:
  378.                 # 进行 Discuz 转 Markdown 操作
  379.                 text = Tc.discuzToMarkdown(self.textEdit.toPlainText())
  380.                 self.textEdit.setPlainText(text)
  381.                 self.msg = f"Discuz → Markdown</b> 文件转换成功!<b>~( ̄▽ ̄~)</b>"

  382.         except Exception as e:
  383.             self.msg = f"可恶</b>,失败了!<b>~(/T^T\)~</b>异常原因:<span style='color=red; font-weight:bold;'>{str(e)}</span>"
  384.             self._log.exception(e)
  385.         self.updateT.setText(self.msg)

  386.     # 显示帮助
  387.     def showHelp(self):
  388.         reply = QMessageBox.question(
  389.             self, '是否跳转?^3^',
  390.             '是否<span style="color:red; font-weight:bold;">跳转</span>到帮助页面?',
  391.             QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
  392.         if reply == QMessageBox.Yes:
  393.             self.updateT.setText(f"mua~</b>偷偷看帮助被我逮着啦!<b>(* ̄3 ̄)~</b>")
  394.             url = QUrl("https://fishc.com.cn/thread-227318-1-1.html")
  395.             QDesktopServices.openUrl(url)
  396.         else:
  397.             self.updateT.setText(f"(o/>^&lt;\o)</b> 难道......你对我不感兴趣吗?")

  398.     # 最小化窗口
  399.     def minimizeWindow(self):
  400.         self.showMinimized()

  401.     # 最大化或还原窗口
  402.     def showMaximizedOrRestore(self):
  403.         if self.isMaximized():
  404.             self.showNormal()
  405.             self.updateT.setText("嗷呜!~(●'&#9697;'●~)</b> 我变回来啦!")
  406.             self.maxButton.setIcon(QIcon(self._Maximize))
  407.             self.maxButton.setText("最大化")
  408.             self.maxButton.setToolTip("最大化窗口(快捷键:Ctrl+D)")
  409.         else:
  410.             self.showMaximized()
  411.             self.updateT.setText("哇嗷!(~●'&#9697;'●)~</b> 我变大啦!")
  412.             self.maxButton.setIcon(QIcon(self._Restore))
  413.             self.maxButton.setText("恢复窗口")
  414.             self.maxButton.setToolTip("恢复窗口大小(快捷键:Ctrl+D)")

  415.     # 窗口栏点击最大化
  416.     def resizeEvent(self, event):
  417.         if self.isMaximized():
  418.             self.updateT.setText("嗷呜!~(●'&#9697;'●~)</b> 我变回来啦!")
  419.         else:
  420.             if not self.first:
  421.                 self.updateT.setText("哇嗷!(~●'&#9697;'●)~</b> 我变大啦!")
  422.         self.first = 0
  423.         return super().resizeEvent(event)

  424.     # 窗口栏点击关闭
  425.     def closeEvent(self, event):
  426.         self.config_reader.save_Config()
  427.         reply = QMessageBox.question(self, '确认', '确定退出程序?', QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
  428.         if reply == QMessageBox.Yes:
  429.             event.accept()
  430.         else:
  431.             event.ignore()

  432.         self.updateT.setText("(/≧▽≦)/</b> 我就知道你不会离开我 &#10084;~")

  433.     # 显示关于
  434.     def showAbout(self):
  435.         # 弹出作者信息框
  436.         aboutBox = QMessageBox(self)
  437.         aboutBox.setWindowTitle("关于作者")
  438.         aboutBox.setIconPixmap(QPixmap("UangSC"))
  439.         aboutBox.setStyleSheet("background-color:#CBDCE9;")
  440.         aboutBox.setText(
  441.             f"<br><div style='text-align:center; font-size:12pt; font-family:STKaiti; font-weight:bold;'>版本号:{self._version}<hr>作者:Twilight6(UangSC)<br>QQ:327981933<br>码云:<a href='https://gitee.com/UangSC/TextConverter'>啥都木有</a><br>论坛:<a href='https://fishc.com.cn/space-uid-854664.html'>鱼C论坛</a><br>Email:UangSC@foxmail.com</div>")
  442.         aboutBox.setTextFormat(Qt.RichText)
  443.         desktop = QDesktopWidget()
  444.         screenWidth = desktop.screenGeometry().width()
  445.         screenHeight = desktop.screenGeometry().height()
  446.         centerX = int(screenWidth / 2)
  447.         centerY = int(screenHeight / 2)
  448.         x = aboutBox.sizeHint().width()
  449.         y = aboutBox.sizeHint().height()
  450.         aboutBox.move(centerX - int((aboutBox.width() + x) / 2), centerY - int((aboutBox.height() + y) / 2))
  451.         aboutBox.exec_()
  452.         self.updateT.setText("(*/ω\*)</b> 主动就会有结果 &#10084;~")
复制代码



ConfigReader.py

                               
登录/注册后可看大图

  1. import configparser

  2. class ConfigReader:
  3.     def __init__(self, file_path, read_string=False):
  4.         self.configPath = file_path
  5.         self.config = configparser.RawConfigParser(comment_prefixes='/', allow_no_value=True)
  6.         self.config.optionxform = str
  7.         self.restore = 0
  8.         if read_string:
  9.             self.config.read_string(file_path)
  10.         else:
  11.             try:
  12.                 with open(file_path) as file:
  13.                     self.config.read_file(file)
  14.             except UnicodeDecodeError:
  15.                 with open(file_path, encoding="utf-8") as file:
  16.                     self.config.read_file(file)


  17.     def update_recentFile(self, recentList: list):
  18.         self.config._sections["files"]["RecentFile"] = recentList

  19.     def save_Config(self, confDict=None):
  20.         if self.restore:
  21.             recentList = self.config._sections["files"]["RecentFile"]
  22.             self.config.clear()
  23.             self._load_dict_to_dict(confDict)
  24.             self.config._sections["files"]["RecentFile"] = recentList
  25.         elif confDict != None:
  26.             self.config.clear()
  27.             self._load_dict_to_dict(confDict)
  28.         try:
  29.             with open(self.configPath, "w") as file:
  30.                 self.config.write(file)
  31.         except:
  32.             with open(self.configPath, "w", encoding="utf-8") as file:
  33.                 self.config.write(file)

  34.     def _load_dict_to_dict(self, conf_dict):
  35.         for section_name, section in conf_dict.items():
  36.             self.config.add_section(section_name)
  37.             for key, value in section.items():
  38.                 self.config.set(section_name, key, value)

  39.     def get_img(self, img_name):
  40.         """
  41.         获取 [icons] 节中图片路径
  42.         :param img_name: 配置文件中的属性名
  43.         :return: 返回图片相对路径
  44.         """
  45.         return self.config.get("icons", img_name)


  46.     def get_style(self, style_name):
  47.         """
  48.         获取 [styles] 节中的对应属性
  49.         :param style_name: 配置文件中的属性名
  50.         :return: 返回属性值
  51.         """
  52.         return self.config.get("styles", style_name)


  53.     def get_fonts(self, font_name):
  54.         """
  55.         获取 [fonts] 节中的属性
  56.         :param font_name: 配置文件中的属性名
  57.         :return: 返回属性值
  58.         """
  59.         return self.config.get("fonts", font_name)


  60.     def get_settings(self, set_name):
  61.         """
  62.         获取 [settings] 节中的属性
  63.         :param set_name: 配置文件中的属性名
  64.         :return: 返回属性值
  65.         """
  66.         return self.config.get("settings", set_name)

  67.     def get_files(self, file_name):
  68.         """
  69.         获取 [files] 节中的属性
  70.         :param file_name: 配置文件中的属性名
  71.         :return: 返回属性值
  72.         """
  73.         return self.config.get("files", file_name)
复制代码



main.py

                               
登录/注册后可看大图

  1. from os import getcwd,sep
  2. from os.path import basename
  3. import sys

  4. from PyQt5.QtWidgets import QApplication
  5. from ui.TextGUI import MyTextEdit
  6. import logging


  7. if __name__ == '__main__':
  8.     logger = logging.getLogger(__name__)
  9.     logging.basicConfig(
  10.         level=logging.DEBUG,
  11.         format='%(asctime)s %(levelname)s %(message)s\n' + "=" * 60,
  12.         handlers=[
  13.             logging.FileHandler('deBug.log', mode='a'),
  14.             logging.StreamHandler()
  15.         ]
  16.     )
  17.     # 关闭 chardet 日志的输出
  18.     logging.getLogger('chardet.charsetprober').disabled = True
  19.     logging.getLogger('chardet.universaldetector').disabled = True
  20.     try:
  21.         app = QApplication(sys.argv)
  22.         app.setStyle("Fusion")
  23.         exePath = getcwd() + sep + basename(sys.argv[0])
  24.         myTextEdit = MyTextEdit(exePath, logging)
  25.         myTextEdit.show()
  26.         sys.exit(app.exec_())
  27.     except Exception as e:
  28.         logger.exception(e)
复制代码



帖子创作不易,评个分再走呗~


                               
登录/注册后可看大图



评分

参与人数 2荣誉 +8 鱼币 +8 贡献 +6 收起 理由
liuhongrun2022 + 5 + 5 + 3
王逗比666 + 3 + 3 + 3

查看全部评分

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2023-4-23 10:08:22 | 显示全部楼层
顶顶顶
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2023-4-28 13:26:41 | 显示全部楼层
seeit
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2023-5-30 22:12:00 | 显示全部楼层
突然发现告诉gpt转换的规则,gpt能做得更好,何不直接接入chatgpt的api呢,或者让chatgpt给出转换的代码
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2023-5-31 09:28:11 | 显示全部楼层
isdkz 发表于 2023-5-30 22:12
突然发现告诉gpt转换的规则,gpt能做得更好,何不直接接入chatgpt的api呢,或者让chatgpt给出转换的代码{:1 ...



这不见得,我并没购买接口,但这代码本就是我不断询问 GPT 后再不断修改的成果

而 3.5 接口,还是存在很多问题,无法满足需求,都需要进行修改,PyQt5 的 GUI 也是他整的

我实际上在编写这个程序时候,实际上就是给予 GPT 一个个小任务,GPT 反馈给我

然后我修复 GPT 的 Bug 和 逻辑上的问题,在将这些小任务全部组装起来而成的

另外,能直接调用 GPT 就写不出这帖子,再编写程序的时候自己也在不断的学习

直接调用接口?当然可以,但我觉得很无聊。

你一句 GPT 能做的更好,虽然是现实,但是在我这听来,反而使我有些心凉...

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2023-6-6 00:23:08 | 显示全部楼层
bug:
  1. ```python
  2. # hello world
  3. ```
复制代码
  1. {code}
  2. python
  3. # hello world
  4. {/code}
复制代码

图:
动画.gif

评分

参与人数 1荣誉 +2 鱼币 +3 贡献 +3 收起 理由
Twilight6 + 2 + 3 + 3 鱼C有你更精彩^_^

查看全部评分

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2023-6-8 13:54:30 | 显示全部楼层
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2023-7-10 10:31:48 | 显示全部楼层
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2023-7-19 20:24:36 | 显示全部楼层
看看
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2023-7-19 22:24:38 | 显示全部楼层
匠心巨制,求评分:https://fishc.com.cn/thread-231007-1-1.html
马上就能申请精华了,助把力吧
渴望贡献,就差一点贡献了
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2024-2-3 10:28:09 | 显示全部楼层
来了!
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2024-3-29 08:08

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

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