Twilight6 发表于 2023-4-23 01:37:11

【源码】MarDisTextConverter 源文本转换器

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

MarDisTextConverter 源码
static/image/hrline/1.gifstatic/image/hrline/1.gif
(温馨提示:要使程序正常运行,需要按照如下项目结构进行配置哦~)

本贴是源码贴,源码过长,所以新建帖子来进行引用。
如果感兴趣请访问:【源文本转换器】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

项目源码

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

TextConverter.py
static/image/hrline/1.gif
import os
import re
from uuid import uuid4
from chardet import detect

from config.ConfigReader import ConfigReader


class TextConverter:
    config_reader = None
    path = os.getcwd()
    DToMDict = {}
    TitleMaxSize = 7
    TitleBold = 1
    EnglishDefaultFont = "Times New Roman"
    # 部分配置文件默认值与参数类型
    ConfigDict = {
      "TitleMaxSize": (int, 7, 5, 7),
      "TitleBold": (int, 1, 0, 1),
      "EnglishDefaultFont": (str, "Times New Roman")
    }
    _ConfigureBackup = {
      'icons': {'icon': r'/assets/img/icon.png', 'about': r'/assets/img/about.png', 'convert': r'/assets/img/convert.png',
                  'exit': r'/assets/img/exit.png', 'help': r'/assets/img/help.png', 'new': r'/assets/img/new.png',
                  'maximize': r'/assets/img/maximize.png', 'minimize': r'/assets/img/minimize.png',
                  'open': r'/assets/img/open.png', 'restore': r'/assets/img/restore.png', 'save': r'/assets/img/save.png'},
      'styles': {'QButton': 'QToolButton,QPushButton{padding-top: 5px; padding-bottom: 5px;}',
                   'QToolBar': 'QToolBar{background-color: #82B7DB; spacing: 15px;}',
                   'textEditBg': 'background-color:#CBDCE9;'},
      'fonts': {'TitleMaxSize': '7', 'TitleBold': '1', 'EnglishDefaultFont': 'Times New Roman'},
      'settings': {'FixedSize': '1'}, 'files': {'RecentFile': '[]'}
    }
    _DefaultConfig = ['styles', 'fonts', 'settings']

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

    @classmethod
    def _configToType(cls, value, name):
      try:
            typeMethod = cls.ConfigDict
            value = typeMethod(value)
            if typeMethod == int:
                value = value if value >= typeMethod and value <= typeMethod else typeMethod
            setattr(TextConverter, name, value)

      except Exception as e:
            pass

    @classmethod
    def _dToMDict(cls):
      for i in range(0, 4):
            cls.DToMDict = "#" * (i + 1)
      cls.DToMDict["Min"] = ""

    @classmethod
    def discuzToMarkdown(cls, text: str) -> str:
      cls._loadConfig()
      def _quote(text):
            text = re.sub(r'\n*(\.*?\)\n*', "\g<1>", text, flags=re.S)
            text = re.sub(r'(\)(.*?)(\)', lambda m: m.group(1) + "\n" + m.group(2).strip() + "\n" + m.group(3), text,
                        flags=re.S)
            text = re.sub(r'(\)(\)', '\g<1>\n\g<2>', text, flags=re.S)
            text = re.sub(r'((?<!\n)\)|(\(?!\n))',
                        lambda m: "\n" + m.group(1) if m.group(1) != None else m.group(2) + "\n", text, flags=re.S)
            text = re.sub(r'\(.*?)\n\', lambda m: m.group(1).replace("\n", "\n> "),text, flags=re.S)
            return text

      text, dict_code = cls._replace(r"\.*?\", text, re.S)
      calcTitle = lambda m: (cls.DToMDict[
            str(cls.TitleMaxSize) if int(m.group(1)) > cls.TitleMaxSize else "Min" if (
                  int(m.group(1)) <= cls.TitleMaxSize - 4) else str(m.group(1))]) + " " + m.group(4)
      tag_list = {
            r'(\(.*?)\)': r'\g<2>',
            r'\((\)?(.*?)(\)?)\': calcTitle,
            r'\\\(.+?)\\\': r'`\g<1>`',
            r'\(.*?)\[\/b\]': r'**\1**',
            r'\(.*?)\[\/i\]': r'*\1*',
            r'\(.*?)\[\/u\]': r'<u>\1</u>',
            r'\(.*?)\[\/align\]': r'<center>\1</center>',
            r'\': '---',
            r'\(.*?)\[\/s\]': r'~~\1~~',
            r'\(.*?)\': r'[\2](\1)',
            r'\\n*([\s\S]*?)\n*\[\/code\]': r'```\n\1\n```'
      }

      for pattern, replace in tag_list.items():
            if "font" in pattern:
                text = re.sub(pattern, replace, text)
            elif "code" in pattern:
                text = cls._recovery(dict_code, text)
                text = re.sub(pattern, replace, text)
            else:
                text = re.sub(pattern, replace, text, flags=re.DOTALL)
      text = _quote(text)
      return text.strip()

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

      def _quote_to_Discuz(text):
            result, temp, new_text = [], [], []
            for i in text.splitlines():
                if i != ">":
                  if temp != []:
                        new_text.append("" + "\n".join(temp) + "")
                        temp = []
                  new_text.append(i)
                  result.append(i)
                else:
                  temp.append(i.strip() == " " else 1:])
            if temp != []:
                new_text.append("" + "\n".join(temp) + "")
            return "\n".join(new_text)

      b_start, b_end = ("", "") if cls.TitleBold else ("", "")
      text = re.sub(r'```\n*(.*?)\n*```', r'\n\1\n', text, flags=re.DOTALL)
      text, dict_code = cls._replace(r"\.*?\", text, re.S)
      tag_list = {
            r'\[(.*?)\]\((http?://[^\s\[\]]+)\)': r'\1',
            r'((\n#+)\s*(.*))|(^(#+)\s*(.*))': lambda
                m: ("" if m.group(1) == None else "\n") + rf"{b_start + (m.group(3) if m.group(3) != None else m.group(6)) + b_end}",
            r'\*\*(.*?)\*\*': r'\1',
            r'\*(.*?)\*': r'\1',
            r'<u>(.*?)</u>': r'\1',
            r'<center>(.*?)</center>': r'\1',
            r'---\n': '\n',
            r'~~(.*?)~~': r'\1',
            r'```\n*(.*?)\n*```': r'\n\1\n[//code]', # 此处若有两个 // 则需要删去一个
            r"`(.+?)`": r"\g<1>"
      }
      for pattern, replace in tag_list.items():
            if "#" in pattern or "http" in pattern:
                text = re.sub(pattern, replace, text)
            else:
                text = re.sub(pattern, replace, text, flags=re.DOTALL)
      # 引用匹配转换
      text = _quote_to_Discuz(text)
      # 、普通 url 临时替换
      text, dict_url = cls._replace(
            r"\*[-A-Za-z0-9+&@#\/%=~_|](?=(?:[^`]*`[^`]*`)*[^`]*$)\].*\",
            text)
      text, dict_url2 = cls._replace(
            r"https?:\/\/[-A-Za-z0-9+&@#\/%?=~_|!:,.;]*[-A-Za-z0-9+&@#\/%=~_|](?=(?:[^`]*`[^`]*`)*[^`]*$)", text)

      # 英文将使用 Times New Roman 字体
      text = re.sub(r"+(?![^\[]*\])", f"\g<0>", text)

      text = cls._recovery(dict_code, text)
      text = cls._recovery(dict_url, text)
      text = cls._recovery(dict_url2, text)

      return text.strip()

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

      return text, codeDict

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

    @staticmethod
    def _getFileEncoding(file):
      with open(file, "rb") as f:
            result = detect(f.read())
            encoding = result["encoding"]
      if encoding:
            return encoding
      return "utf-8"

    # 暂未实现 -> 文本转换时检测类型
    @classmethod
    def _checkType(cls, text):
      pass


TextGUI.py
static/image/hrline/1.gif
from os.path import split, isfile, samefile, exists
from time import sleep

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

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


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

      self._log = log
      self._exe = exePath
      path, _ = split(exePath)
      self.config_path = path + r"\config\config.ini"
      self.config_reader = ConfigReader(self.config_path)
      self._setImg(path)
      try:
            self.FixedSize = int(self.config_reader.get_settings("FixedSize"))
            self.RecentFile = eval(self.config_reader.get_files("RecentFile"))
      except Exception:
            self.FixedSize = 1
            self.RecentFile = []
            self._log.exception(Exception("配置文件异常,部分属性已使用默认值!"))
      self.currentFilePath = ""
      self.msg = ""
      self.error = self.first = 1

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

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

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

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

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

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

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

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

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

      self.widget = QWidget(self)
      layout = QHBoxLayout(self.widget)
      self.menuRLable = QLabel(" " * 120)
      self.menuRLable.setAlignment(Qt.AlignRight)
      layout.addWidget(self.menuRLable)
      layout.setContentsMargins(5, 3, 5, 0)
      self.widget.setLayout(layout)

      self.updateT = self.WorkerThread()
      self.updateT.update_signal.connect(self._outputText)
      self.updateT.start()

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

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

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

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

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

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

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

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

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

      def __init__(self):
            super().__init__()
            self.text = ""
            self.idx = 1

      def run(self):
            while True:
                if self.idx < len(self.text) + 1:
                  self.update_signal.emit(self.text[:self.idx])
                  self.idx += 1
                sleep(0.04)

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

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

    def _clear(self):
      self.textEdit.selectAll()
      self.textEdit.textCursor().removeSelectedText()
    def MarToDis(self):
      self.convertComboBox.setCurrentIndex(0)
      self.convert()

    def DisToMar(self):
      self.convertComboBox.setCurrentIndex(1)
      self.convert()

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

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

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

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

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


    # 添加工具栏按键
    def _addQAcion(self, module=None, name="", img_path="", tip="", key=None, slot=None):
      if img_path:
            action = QAction(QIcon(img_path), name, self)
      else:
            action = QAction(name, self)
      action.setToolTip(tip) if tip else None
      action.setShortcut(key) if key else None
      action.triggered.connect(slot)
      if module != None:
            module.addAction(action)
      else:
            self.addAction(action)
      return action

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

    # 添加到最近文件列表
    def addRecent(self, path):
      if path in self.RecentFile:
            self.RecentFile.remove(path)
      elif len(self.RecentFile) == 5:
            self.RecentFile.pop()
      self.RecentFile.insert(0, path)
      self.updateRecent()

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

    # 打开最近文件
    def open_recent_file(self):
      file = self.sender().text()
      idx, fileName = file.split(". ", 1)
      filePath = self.RecentFile
      self.openFile(filePath)

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

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

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

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

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

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

    # 保存文件
    def saveFile(self, saveAs=0):
      flag = 0
      try:
            path, fileName = "", "FishC.txt"
            if self.currentFilePath:
                path, filePath = split(self.currentFilePath)
            filePath, _ = QFileDialog.getSaveFileName(self, "保存文件", fileName,
                                                      "文本文档 (*.txt);;Markdown 文档 (*.md)")
            if filePath:
                if saveAs or not self.saveConfig(filePath):
                  if saveAs or not exists(filePath):
                        encoding = "utf-8"
                  else:
                        encoding = Tc._getFileEncoding(filePath)
                  with open(filePath, "w", encoding= encoding)as f:
                        f.write(self.textEdit.toPlainText())
                _, fileName = split(filePath)
                self.msg = f"[{fileName}]</b> 文件保存成功!<b>~(= ̄ω ̄=)~</b>"
                if saveAs:
                  self.currentFilePath = filePath
                self.update_file_label(fileName)
                flag = 1
                self.addRecent(filePath)
                self.copypath.setEnabled(True)
      except Exception as e:
            if filePath:
                _, fileName = split(filePath)
                self.msg = f"[{fileName}]</b> 文件保存失败!<b>~(*ㄒoㄒ*)~</b> 异常原因:<spanstyle='color: red; font-weight:bold;'>{str(e)}</span>"
            else:
                self.msg = f"~(*ㄒoㄒ*)~ </b> 文件保存失败!异常原因:<spanstyle='color: red; font-weight:bold;'>{str(e)}</span>"
            self._log.exception(e)
            flag = 1
      if flag:
            self.updateT.setText(self.msg)

    # 保存配置
    def saveConfig(self, path):
      if exists(path) and samefile(self.config_path, path):
            self.config_reader.save_Config(ConfigReader(self.textEdit.toPlainText(), True).config._sections)
            self.msg = "</b> 文件保存成功!<b>~(= ̄ω ̄=)~</b>"
            self.update_file_label("config.ini")
            self.addRecent(path)
            self.copypath.setEnabled(True)
            return True
      return False

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

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

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

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

    # 最大化或还原窗口
    def showMaximizedOrRestore(self):
      if self.isMaximized():
            self.showNormal()
            self.updateT.setText("嗷呜!~(●'◡'●~)</b> 我变回来啦!")
            self.maxButton.setIcon(QIcon(self._Maximize))
            self.maxButton.setText("最大化")
            self.maxButton.setToolTip("最大化窗口(快捷键:Ctrl+D)")
      else:
            self.showMaximized()
            self.updateT.setText("哇嗷!(~●'◡'●)~</b> 我变大啦!")
            self.maxButton.setIcon(QIcon(self._Restore))
            self.maxButton.setText("恢复窗口")
            self.maxButton.setToolTip("恢复窗口大小(快捷键:Ctrl+D)")

    # 窗口栏点击最大化
    def resizeEvent(self, event):
      if self.isMaximized():
            self.updateT.setText("嗷呜!~(●'◡'●~)</b> 我变回来啦!")
      else:
            if not self.first:
                self.updateT.setText("哇嗷!(~●'◡'●)~</b> 我变大啦!")
      self.first = 0
      return super().resizeEvent(event)

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

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

    # 显示关于
    def showAbout(self):
      # 弹出作者信息框
      aboutBox = QMessageBox(self)
      aboutBox.setWindowTitle("关于作者")
      aboutBox.setIconPixmap(QPixmap("UangSC"))
      aboutBox.setStyleSheet("background-color:#CBDCE9;")
      aboutBox.setText(
            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>")
      aboutBox.setTextFormat(Qt.RichText)
      desktop = QDesktopWidget()
      screenWidth = desktop.screenGeometry().width()
      screenHeight = desktop.screenGeometry().height()
      centerX = int(screenWidth / 2)
      centerY = int(screenHeight / 2)
      x = aboutBox.sizeHint().width()
      y = aboutBox.sizeHint().height()
      aboutBox.move(centerX - int((aboutBox.width() + x) / 2), centerY - int((aboutBox.height() + y) / 2))
      aboutBox.exec_()
      self.updateT.setText("(*/ω\*)</b> 主动就会有结果 ❤~")


ConfigReader.py
static/image/hrline/1.gif
import configparser

class ConfigReader:
    def __init__(self, file_path, read_string=False):
      self.configPath = file_path
      self.config = configparser.RawConfigParser(comment_prefixes='/', allow_no_value=True)
      self.config.optionxform = str
      self.restore = 0
      if read_string:
            self.config.read_string(file_path)
      else:
            try:
                with open(file_path) as file:
                  self.config.read_file(file)
            except UnicodeDecodeError:
                with open(file_path, encoding="utf-8") as file:
                  self.config.read_file(file)


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

    def save_Config(self, confDict=None):
      if self.restore:
            recentList = self.config._sections["files"]["RecentFile"]
            self.config.clear()
            self._load_dict_to_dict(confDict)
            self.config._sections["files"]["RecentFile"] = recentList
      elif confDict != None:
            self.config.clear()
            self._load_dict_to_dict(confDict)
      try:
            with open(self.configPath, "w") as file:
                self.config.write(file)
      except:
            with open(self.configPath, "w", encoding="utf-8") as file:
                self.config.write(file)

    def _load_dict_to_dict(self, conf_dict):
      for section_name, section in conf_dict.items():
            self.config.add_section(section_name)
            for key, value in section.items():
                self.config.set(section_name, key, value)

    def get_img(self, img_name):
      """
      获取 节中图片路径
      :param img_name: 配置文件中的属性名
      :return: 返回图片相对路径
      """
      return self.config.get("icons", img_name)


    def get_style(self, style_name):
      """
      获取 节中的对应属性
      :param style_name: 配置文件中的属性名
      :return: 返回属性值
      """
      return self.config.get("styles", style_name)


    def get_fonts(self, font_name):
      """
      获取 节中的属性
      :param font_name: 配置文件中的属性名
      :return: 返回属性值
      """
      return self.config.get("fonts", font_name)


    def get_settings(self, set_name):
      """
      获取 节中的属性
      :param set_name: 配置文件中的属性名
      :return: 返回属性值
      """
      return self.config.get("settings", set_name)

    def get_files(self, file_name):
      """
      获取 节中的属性
      :param file_name: 配置文件中的属性名
      :return: 返回属性值
      """
      return self.config.get("files", file_name)


main.py
static/image/hrline/1.gif
from os import getcwd,sep
from os.path import basename
import sys

from PyQt5.QtWidgets import QApplication
from ui.TextGUI import MyTextEdit
import logging


if __name__ == '__main__':
    logger = logging.getLogger(__name__)
    logging.basicConfig(
      level=logging.DEBUG,
      format='%(asctime)s %(levelname)s %(message)s\n' + "=" * 60,
      handlers=[
            logging.FileHandler('deBug.log', mode='a'),
            logging.StreamHandler()
      ]
    )
    # 关闭 chardet 日志的输出
    logging.getLogger('chardet.charsetprober').disabled = True
    logging.getLogger('chardet.universaldetector').disabled = True
    try:
      app = QApplication(sys.argv)
      app.setStyle("Fusion")
      exePath = getcwd() + sep + basename(sys.argv)
      myTextEdit = MyTextEdit(exePath, logging)
      myTextEdit.show()
      sys.exit(app.exec_())
    except Exception as e:
      logger.exception(e)


帖子创作不易,评个分再走呗~
{:9_240:} {:9_240:} {:9_240:}
https://xxx.ilovefishc.com/album/202003/31/144219wdi4u2t8d33u22x3.gif


liuhongrun2022 发表于 2023-4-23 10:08:22

顶顶顶{:10_257:}

歌者文明清理员 发表于 2023-4-28 13:26:41

seeit

isdkz 发表于 2023-5-30 22:12:00

突然发现告诉gpt转换的规则,gpt能做得更好,何不直接接入chatgpt的api呢,或者让chatgpt给出转换的代码{:10_256:}

Twilight6 发表于 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 能做的更好,虽然是现实,但是在我这听来,反而使我有些心凉...

歌者文明清理员 发表于 2023-6-6 00:23:08

bug:
```python
# hello world
```
{code}
python
# hello world
{/code}
图:

liuhongrun2022 发表于 2023-6-8 13:54:30

歌者文明清理员 发表于 2023-6-6 00:23
bug:




这个我也遇到过

Ewan-Ahiouy 发表于 2023-7-10 10:31:48

{:5_101:}

Mike_python小 发表于 2023-7-19 20:24:36

看看

AbyssKing 发表于 2023-7-19 22:24:38

匠心巨制,求评分:https://fishc.com.cn/thread-231007-1-1.html
马上就能申请精华了,助把力吧
渴望贡献,就差一点贡献了

学习编程中的Ben 发表于 2024-2-3 10:28:09

来了!

zhangjinxuan 发表于 2024-11-27 13:11:18

1
页: [1]
查看完整版本: 【源码】MarDisTextConverter 源文本转换器