一、概述
本文将对之前的文章:**使用PyQt5+Java+Apache Tika实现简单的文档内容提取程序**所实现的简单的文档内容提取程序进行改进,改进的内容包括:
- 修改jpype对JVM的操作使程序可以重复读取文件。
- 利用正则表达式匹配字符串将固定格式的实验报告的“实验步骤与内容”和“结论分析与体会”两个部分的内容提取出来并显示在文本框中。
二、修改jpype对JVM的操作使程序可以重复读取文件
在之前实现的程序中,对于JVM的开启和关闭,主要实现在“读取文件”按钮所触发的方法choose()中:
这样做看似可行,点击一次“开始读取”按钮,方法就执行一次,JVM就打开并关闭各一次,然后再次点击一次“开始读取”按钮,方法就又执行一次,JVM就又打开并关闭各一次,似乎可以实现重复读取。可实际上,在第一次点击“开始读取”按钮读取一次文件内容之后,就无法再次读取文件内容了,此时如果再次点击“开始读取”按钮,程序则会因有关JVM开启关闭的原因而崩溃。换言之,之前实现的程序是一个“一次性”程序,这与我们实际的想法不符。
一个比较合理的解决办法是:当用户第一次点击“开始读取”按钮时打开JVM,然后JVM一直不关闭,直到用户关闭程序窗口时,再关闭JVM,这样需要改动:
- 改写QWidget类的closeEvent()方法(即关闭窗体所发生事件),在该方法中关闭JVM。
- 删除choose()方法中有关关闭JVM的部分代码。
修改后的代码为:
def choose(self):
# 如果没有选择文件,弹出提示框
if self.filePath.text() == '':
msg_box = QMessageBox(QMessageBox.Warning, '警告', '请先选择文件!')
msg_box.exec_()
else: # 如果选择了文件
# 读取文件信息的jar包
jarpath = r'F:/python_codes/codeChecker/tikaTest.jar'
# 开启JVM 注意每个人的JVM所在路径不同
if not jpype.isJVMStarted():
jpype.startJVM(jpype.getDefaultJVMPath(), "-ea",
"-Djava.class.path=%s" % jarpath)
tkClass = jpype.JClass("Checker") # Checker是自己的类名
tk = tkClass()
results = tk.check(self.fileName) # 调用check()方法读取文本内容,返回值为java.lang.String数组
# 将结果展示在文本框中,注意要将java.lang.String类型转换成string才能显示,否则程序会崩溃
self.metadataText.setText(str(results[0]))
self.contentText.setText(str(results[1]))
#窗体关闭时的事件,重写了QWidget类的closeEvent()方法
def closeEvent(self, event):
super().closeEvent(event)
# 关闭虚拟机
if jpype.isJVMStarted():
jpype.shutdownJVM()
print('JVM已关闭')
这样,当重复点击“开始读取”按钮时,JVM不会再反复地开启关闭,程序也不会再崩溃,实现了反复读取的功能。
二、利用正则表达式提取相应内容
本程序所实现的提取文档内容的功能是实验报告查重程序的一个部分。其中实验报告的模板为山东大学标准实验报告模板:
对姓名班级等信息的查重是无意义的,我们需要将“实验步骤与内容”和“结论分析与体会”两部分内容提取出来,这两部分才是查重的关键内容。根据实验报告格式特点,我们可以利用正则表达式来匹配这两部分内容,需要用到python所提供的re模块。匹配出来的内容显示在界面上,因此我们利用Qt Designer修改之前已经完成的界面,增加两个文本框procedureText和feelingText分别用来存放两部分内容:
实现这一功能需要在choose()方法的最后添加代码,使得在读取到文档内容后,立刻对内容进行匹配处理。
对于“实验步骤与内容”这一部分内容,我们可以匹配处于“实验步骤与内容:”和“结论分析与体会:”之间的字符串,对应的正则表达式为:
".* 实验步骤与内容:(.*)结论分析与体会:.*"
然后将这一部分显示在对应的文本框procedureText内。
#利用正则表达式,匹配实验报告实验步骤与内容,并将其显示在文本框内
pattern1 = re.compile(r".* 实验步骤与内容:\n(.*)结论分析与体会:.*", re.DOTALL) # 注意re.DOTALL
procedure = pattern1.findall(str(results[1]))
for x in procedure:
self.procedureText.setText(x)
利用python的re模块可以实现用正则表达式匹配字符串。首先要利用compile()函数编译正则表达式,生成一个 Pattern 对象。注意我们要匹配的是多行文本,需要在compile()函数中加上参数re.DOTALL,默认情况下,正则表达式中的dot(.),表示所有除了换行的字符,加上re.DOTALL参数后,就是真正的所有字符了,包括换行符(\n)。
compile()函数返回的是一个匹配对象,它单独使用没有任何意义,需要和findall(), search(),match()搭配使用。 此处我们选择findall()函数,它将返回一个符合匹配条件的列表。它的参数是要进行匹配的字符串,我们选择经过提取后的正文内容字符串str(results[1])。注意这里要使用str()函数将java.lang.String转换为python可以操作的string类型。
最后,将列表中的内容输出到文本框即可。对“结论分析与体会:“后的内容提取方法类似,对应的正则表达式为:
".*结论分析与体会:(.*)"
提取代码为:
# 利用正则表达式,匹配实验报告结论分析与体会内容,并将其显示在文本框内
pattern2 = re.compile(r".*结论分析与体会:\n(.*)", re.DOTALL) # 注意re.DOTALL
feeling = pattern2.findall(str(results[1]))
for x in feeling:
self.feelingText.setText(x)
四、测试程序
经过以上一系列修改,新的choose()方法代码为:
# 开始读取文件的方法
def choose(self):
# 如果没有选择文件,弹出提示框
if self.filePath.text() == '':
msg_box = QMessageBox(QMessageBox.Warning, '警告', '请先选择文件!')
msg_box.exec_()
else: # 如果选择了文件
# 读取文件信息的jar包
jarpath = r'F:/python_codes/codeChecker/tikaTest.jar'
# 开启JVM 注意每个人的JVM所在路径不同
if not jpype.isJVMStarted():
jpype.startJVM(jpype.getDefaultJVMPath(), "-ea",
"-Djava.class.path=%s" % jarpath)
tkClass = jpype.JClass("Checker") # Checker是自己的类名
tk = tkClass()
results = tk.check(self.fileName) # 调用check()方法读取文本内容,返回值为java.lang.String数组
# 将结果展示在文本框中,注意要将java.lang.String类型转换成string才能显示,否则程序会崩溃
self.metadataText.setText(str(results[0]))
self.contentText.setText(str(results[1]))
#利用正则表达式,匹配实验报告实验步骤与内容,并将其显示在文本框内
pattern1 = re.compile(r".* 实验步骤与内容:\n(.*)结论分析与体会:.*", re.DOTALL) # 注意re.DOTALL
procedure = pattern1.findall(str(results[1]))
for x in procedure:
self.procedureText.setText(x)
# 利用正则表达式,匹配实验报告结论分析与体会内容,并将其显示在文本框内
pattern2 = re.compile(r".*结论分析与体会:\n(.*)", re.DOTALL) # 注意re.DOTALL
feeling = pattern2.findall(str(results[1]))
for x in feeling:
self.feelingText.setText(x)
测试一下系统:
.docx格式的实验报告:
.pdf格式的实验报告:
可见对应部分的内容都被完整的提取了出来,这为后续的查重奠定了基础。