内容简介:知识储备得差不多了,是时候完成我们的文档转换器了。荀子有云:“君子生非异也,善假于物也”。不要说文档转换,就算是任意一个文档格式,也能写几本大书。所以,我们不可能去阅读各种文档格式的规范,手动实现文档转换。因此,我们需要借助外部的文档转换工具。当然,不可能是一个图形化工具,不然我们的转换器也就没有存在的意义了。我们需要的是一个命令行工具,也就是可以在虚拟终端(控制台)下执行的工具。
知识储备得差不多了,是时候完成我们的文档转换器了。
荀子有云:“君子生非异也,善假于物也”。不要说文档转换,就算是任意一个文档格式,也能写几本大书。所以,我们不可能去阅读各种文档格式的规范,手动实现文档转换。
因此,我们需要借助外部的文档转换工具。当然,不可能是一个图形化工具,不然我们的转换器也就没有存在的意义了。我们需要的是一个命令行工具,也就是可以在虚拟终端(控制台)下执行的工具。
我们选择的 工具 是 pandoc
。这是一个万能文档转换器,几乎支持所有主流文档格式的相互转换。如果要转 PDF
格式, pandoc
需要依赖外部的工具,这里我们选择 wkhtmltopdf
。 pandoc
会将文档转成网页,然后再调用这个工具将网页转成 PDF
。
调用外部工具,涉及到了多进程的概念。 Python
会将外部工具运行在新进程里,然后根这个新进程交互,完成我们的文档转换。
import os
import os.path
import platform
from subprocess import Popen, PIPE
from threading import Thread
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
label: QLabel
wnd: QMainWindow
currentFilename: str = None
dlg: QProgressDialog = None
def loadFile(filename):
if filename is None:
return
if platform.system() == "Windows":
filename = filename[1:]
global currentFilename
currentFilename = filename
label.setText("当前文件: " + filename)
wnd.statusBar().showMessage("文件载入成功: " + filename)
class Converter(QThread):
done = pyqtSignal(bool)
def __init__(self, fmt):
super().__init__()
self.fmt = fmt
def run(self):
fmt = self.fmt
fromFilename = currentFilename
toFilename = os.path.splitext(fromFilename)[0] + "." + fmt
args = ["pandoc", fromFilename]
if fmt == "pdf":
args.extend(["--pdf-engine", "wkhtmltopdf"])
args.extend(["-o", toFilename])
try:
from subprocess import STARTUPINFO, STARTF_USESHOWWINDOW
startupinfo = STARTUPINFO()
startupinfo.dwFlags |= STARTF_USESHOWWINDOW
except:
startupinfo = None
process = Popen(args=args, stdin=PIPE, stdout=PIPE, stderr=PIPE, startupinfo=startupinfo)
err = process.communicate()[1].decode()
code = process.returncode
wnd.statusBar().showMessage(("转换成功: " + toFilename) if code == 0 else "转换失败: " + err)
self.done.emit(True)
def convertFile(fmt):
if currentFilename is None:
return
global dlg
dlg = QProgressDialog(wnd)
dlg.setWindowTitle("转换中...")
dlg.setMaximum(0)
dlg.setCancelButton(None)
dlg.setWindowFlags(Qt.Window | Qt.WindowTitleHint | Qt.CustomizeWindowHint)
dlg.setWindowModality(Qt.WindowModal)
dlg.show()
wnd.statusBar().showMessage("转换中...")
global converter
converter = Converter(fmt)
converter.done.connect(dlg.close)
converter.start()
app = QApplication([])
wnd = QMainWindow()
wnd.resize(400, 400 * 0.618)
wnd.setAcceptDrops(True)
wnd.dragEnterEvent = lambda e: e.accept()
wnd.dropEvent = lambda e: print(e.mimeData().urls())
wnd.dropEvent = lambda e: loadFile(next(iter([x.path() for x in e.mimeData().urls()]), None))
wnd.statusBar().showMessage("就绪.")
payload = QWidget()
label = QLabel("请拖入文件.转换后会自动覆盖同名文件,请谨慎使用")
buttons = QGridLayout()
mapper = QSignalMapper()
for idx, (k, v) in enumerate(
dict(docx="Word", pdf="PDF", pptx="PowerPoint", html="HTML", mobi="Mobi", epub="EPUB").items()):
button = QPushButton(v)
button.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
mapper.setMapping(button, k)
button.clicked.connect(mapper.map)
buttons.addWidget(button, idx / 3 + 1, idx % 3 + 1)
mapper.mapped["QString"].connect(convertFile)
layout = QVBoxLayout()
layout.addWidget(label)
layout.addLayout(buttons)
payload.setLayout(layout)
wnd.setCentralWidget(payload)
wnd.setWindowTitle("麦多文档转换器")
wnd.show()
font = app.font()
font.setPointSize(font.pointSize() / 0.618)
app.setFont(font)
app.exec()
这段代码涉及到的新知识点主要有:
-
为了防止我们的对话框被
Python回收掉,我们使用了global关键字,将对话框定义为全局变量。至于为什么 Python 会将我们好好运行着的对话框回收掉,我想是因为Python系统和Qt系统相互独立,一个对象在Python系统中好像没用了,但是在Qt系统中还在起作用。 -
打开文件对话框定位文件非常麻烦。所以,我们选用拖拽选择文件。Qt支持文件拖拽。在一个控件上调用QWidget.setAcceptDrops(True),即可启用文件拖拽。一次文件拖拽会产生两个事件:拖和放。我们需要处理放事件,但是拖事件不处理,就不会产生放事件。我们需要在QWidget.dragEnterEvent中,调用e.accept()表示接受拖事件,这样才会产生放事件。 -
一次拖放可能拖放多个文件。所以,在拖放事件中,
e.mimeData().urls()可能会有多个文件的URL(QUrl对象)。调用QUrl.path()方法,可以将文件的URL转换成文件的磁盘路径。next(iter(...), None)可以安全地取出列表中的第一个元素,取不到则返回None。 -
除了线性布局外,
Qt还支持网格布局。QGridLayout.addWidget(控件, 第几行, 第几列)用来放置网格中的控件。QGridLayout会根据放置的控件自动调整布局。 -
由于
Windows和macOS的文件路径格式不一致,我们需要根据当前的操作系统类型处理文件路径。platform.system()方法可以获取当前的操作系统。 -
subprocess.Popen方法可以创建新进程,执行指定的程序。我们需要通过这个方法来调用pandoc。为了跟这个进程通信,我们需要将这个进程的输入和输出都通过管道进入我们的程序(比如stdout=PIPE)。 -
process.communicate()方法会执行进程,返回管道列表。第二个管道是标准错误流管道,如果转换出错,我们可以通过这个管道获取错误信息。通过管道获取到的是字节数组,我们需要调用decode()方法解码成字符串。 -
process.returncode是进程的结束代码。一般情况,0表示正常,其他表示出错。 -
pandoc调用格式类似pandoc FROM.doc -o TO.pdf。-o后面表示转换后文件的名称。pandoc会根据这个名称的后缀确定转换格式。如果要转PDF,需要使用选项--pdf-engine指定PDF引擎。 -
在
Windows下,调用控制台程序会拖出一个控制台窗口。为了避免这种情况,Popen函数需要传入Windows操作系统专用的参数startupinfo。由于这个参数对应的类在其他操作系统中不存在,直接import会导致程序无法在其他操作系统下运行。所以,我们将这些逻辑放在一个try...except块中,忽略掉产生的异常,避免程序奔溃。
师父领进门,修行在个人。教程虽短,涉及到的东西可不少,需要多多练习才能逐渐掌握。由于笔者水平太次,找工作屡屡碰壁,需要时间来反省反省。所以,这个系列教程就暂时完结了。希望读者们可以拥抱谷歌大法,创造美好明天!
以上所述就是小编给大家介绍的《面向聪明小白的编程入门教程 完成文档转换器(Part 10)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- SpringAMQP 消息转换器 - MessageConverter
- flask视图函数自定义转换器
- 原 荐 转换器(Converter)设计模式
- Mybatis使用小技巧-自定义类型转换器
- 深入理解 Kafka Connect:转换器和序列化
- OpenAPI Generator 4.0.3 发布,使转换器公开访问
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
TensorFlow实战
黄文坚、唐源 / 电子工业出版社 / 2017-2-1 / 79
Google近日发布了TensorFlow 1.0候选版,这个稳定版将是深度学习框架发展中的里程碑的一步。自TensorFlow于2015年底正式开源,距今已有一年多,这期间TensorFlow不断给人以惊喜,推出了分布式版本,服务框架TensorFlow Serving,可视化工具TensorFlow,上层封装TF.Learn,其他语言(Go、Java、Rust、Haskell)的绑定、Wind......一起来看看 《TensorFlow实战》 这本书的介绍吧!