前言
上班了以后经常会有很多奇思妙想,就想用我仅有的一点代码能力在空闲时间实现它们,也不知道能坚持多久,就从这里开始吧!
本文记录了实现一个拥有图形化界面的txt裁剪小程序,可以实现在不需要手动打开这个txt文件的情况下,删除一个txt文件的指定部分行,适合在处理大型txt文本文档时使用。
一、项目背景
公司里的电脑只能连接公司的网络,而且浏览记录都会被网安记录下来,就很不利于摸鱼,领导在旁边又很不利于看手机,于是就有了一个想法:把小说下载下来以txt的格式在电脑上看小说,看上去就像在看资料一样,安静摸鱼,岂不美哉!但问题出现了,txt文件看久了就很难保存看到哪里了,下一次打开很难找到上次的位置,于是就想找个办法,解决这个问题。
二、项目实现
一个自然而然的想法,那就是把我所有看过的内容都删掉就可以啦。但看一段删一段很麻烦,影响阅读连贯性;看很久再一下子删很多又很慢很麻烦,像是看了一会儿小说就要等一会儿广告一样,这很不友好。所以何不写一段简单的小代码来实现它。这个小程序应该需要有以下几个主要的子功能:
1.GUI
本人控制台命令几乎完全不会,又用惯了GUI,既然现在要写一个自己的小程序,那总得有个图形化交互界面吧。于是用半天时间自学了一下python的 tkinter 库,以此为基础做一个简单的界面。因此只会在这部分对使用的函数做一个基本介绍。
在这一部分,主要用到的函数有:
import tkinter as tk
from tkinter import filedialog
from tkinter.messagebox import showwarning, showinfo
window=tk.Tk() # 创建窗口
window.geometry('横x竖') # 设置窗口大小
tk.Text(window, width=宽度, height=高度, bg='背景颜色', font=('字体', 字号)) # 创建文本框
# tk.Text中的内容可以通过Text.insert('insert','要插入的内容')和
# tk.delete('起始行号.列号','起始行号.列号')进行插入以及删除
tk.Label(window, text='标题的内容') # 创建标题
tk.Button(window, text='按钮名', command=点击按钮后触发的函数) # 创建按钮
tk.Checkbutton(window, text="选项名", variable=是否被勾选的信息存储位置,
onvalue=被勾选时的值, offvalue=取消勾选的值, command=勾选后触发的函数) # 创建勾选按钮
# variable参数的值需要是一个tpye为例如tk.BooleanVar()类型的值
tk.Entry(window,textvariable=用户输入值的存放位置,validate=对用户输入值进行检测的时间,validatecommand=(验证函数,验证函数选项))
showwarning(title='警告的标题',message='警告的具体内容') # 显示警告
showinfo(title=...,message=...) # 显示提示
.place(x=...,y=...) # 设置x,y坐标对界面里的元素进行布局
对于这些函数的太具体用法,这里就不多说了,我也并没有理解所有的参数,只是简单的使用了一下,在用户输入行号的部分会对输入值验证做进一步讨论。
2.读取文件地址
创建一个Button按钮,点击后开始选取文件;常见一个Text窗口,显示读取的文件的绝对路径。这一部分主要学习了这个网页上的操作:
参考网页
增加两个Checkbutton选项,选择对txt文件使用常用的’utf-8’解码或者’gbk’解码方式。
3.选择模式
该小程序需要有两个模式,删除txt文件的部分行后保存为新文件,或者保留txt文件的部分行后保存为新文件。因此需要两个Checkbutton分别对应删除模式和保留模式,而且对于删除和保留这两个模式应该是互斥的,一个被选取时,另一个需要自动取消选取。同时还需要一个Text界面实时展示目前所选取的模式。
另一方面,需要一个Checkbutton,让用户勾选是否使用新文件替换掉原文件,或者将新文件保存为同目录下的一个副本。
4.获取行号
需要Entry文本框来获取用户输入的行号,确定删除/保留第几行到第几行。在这里需要对用户的输入进行验证,是否符合行号的要求:输入的只能是整数,且用负数来表示最后一行,即处理到文章末尾,此处可以通过正则表达式的fullmatch实现。
三、代码
程序的具体实现过程如下,尽力写了些注释orz
"""
create time:2023/9/26
author:Zha_lan
"""
import tkinter as tk
from tkinter import filedialog
from tkinter.messagebox import showwarning, showinfo
import os
import math
import re
file_path = '' # 存储文件地址
file_text = ''
def open_file():
"""
打开文件
:return:local_
"""
global file_path
global file_text
file_path = filedialog.askopenfilename(title=u'选择文件', initialdir=(os.path.expanduser('H:/')))
print('打开文件:', file_path)
if file_path is not None:
text1.delete('1.0', tk.END)
file_text = "文件路径为:" + file_path
text1.insert('insert', file_text)
def clear_tx1():
text1.delete('1.0', tk.END)
text1.insert('insert', '文件地址:')
window = tk.Tk() # 创建窗口
window.title('txt裁剪小程序') # 标题
window.geometry('500x300') # 窗口尺寸
"""
子功能1:
选取文件,读取文件地址到file_path中
"""
if not os.path.exists('cache.txt'):
f = open('cache.txt', 'w')
f.close()
f = open('cache.txt', 'r')
last_file = f.readline().strip()
last_mode = list()
if last_file != '':
file_path = last_file
last_mode = [eval(x) for x in f.readline().split()]
text1 = tk.Text(window, width=50, height=5, bg='white', font=('Arial', 12))
text1.insert('insert', '文件地址:')
text1.place(x=23, y=0)
bt0 = tk.Button(window, text='打开文件', width=15, height=2, command=open_file)
bt0.place(x=30, y=100)
bt1 = tk.Button(window, text='清空路径', width=15, height=2, command=clear_tx1)
bt1.place(x=350, y=100)
v_gbk = tk.BooleanVar()
c_gbk = tk.Checkbutton(window, text='gbk编码(非必选)', font=('Arial', 10), variable=v_gbk)
c_gbk.place(x=170, y=120)
"""
子功能2:选择模式,选择删除模式或者保留模式,并且实现两个模式之间的互斥。
"""
mode_text = tk.Text(window, width=20, height=1, bg='gray', font=('Arial', 12))
mode_text.place(x=20, y=160)
mode_text.insert('insert', '选择模式:')
mode_text.config(state='disabled')
mode_var0 = tk.BooleanVar()
mode_var1 = tk.BooleanVar()
mode_var2 = tk.BooleanVar()
# 初始化两个checkbutton
c0 = tk.Checkbutton(window, text="删除", variable=mode_var0)
c1 = tk.Checkbutton(window, text="保留", variable=mode_var1)
c2 = tk.Checkbutton(window, text="是否替换源文件", variable=mode_var2)
c2.place(x=345, y=230)
def display_choice0():
mode_text.config(state='normal')
mode_text.delete('1.0', tk.END)
c1.deselect()
mode_text.insert('insert', '模式:删除')
if not mode_var0.get() and not mode_var1.get():
mode_text.delete('1.0', tk.END)
mode_text.insert('insert', '选择模式:')
mode_text.config(state='disabled')
def display_choice1():
mode_text.config(state='normal')
mode_text.delete('1.0', tk.END)
c0.deselect()
mode_text.insert('insert', '模式:保留')
if not mode_var0.get() and not mode_var1.get():
mode_text.delete('1.0', tk.END)
mode_text.insert('insert', '选择模式:')
mode_text.config(state='disabled')
c0 = tk.Checkbutton(window, text="删除", variable=mode_var0, onvalue=True, offvalue=False,
command=display_choice0, font=('Arial', 12))
c1 = tk.Checkbutton(window, text="保留", variable=mode_var1, onvalue=True, offvalue=False,
command=display_choice1, font=('Arial', 12))
c0.place(x=300, y=155)
c1.place(x=400, y=155)
if last_file != '':
text1.insert('insert', last_file)
mode_text.config(state='normal')
mode_text.delete('1.0', tk.END)
if last_mode[0]:
c0.select()
mode_text.insert('insert', '模式:删除')
else:
c1.select()
mode_text.insert('insert', '模式:保留')
mode_text.config(state='disabled')
if last_mode[1]:
c2.select()
"""
子功能3:实现行的选取的选取,只接受数字输入
"""
start = tk.StringVar()
end = tk.StringVar()
pattern = re.compile(r'-?[0-9]*')
def test(content):
if re.fullmatch(pattern, str(content)) is not None or str(content) == '':
return True
else:
return False
test_dig = window.register(test)
La0 = tk.Label(window, height=1, text='选取行的范围:', font=('Arial', 12))
La0.place(x=20, y=200)
La1 = tk.Label(window, height=1, text='开始:', font=('Arial', 12))
La1.place(x=155, y=200)
start_entry = tk.Entry(window, width=15, textvariable=start, validate='key', validatecommand=(test_dig, '%P'))
start_entry.place(x=200, y=200)
La2 = tk.Label(window, height=1, text='终止:', font=('Arial', 12))
La2.place(x=300, y=200)
end_entry = tk.Entry(window, width=15, textvariable=end, validate='key', validatecommand=(test_dig, '%P'))
end_entry.place(x=345, y=200)
"""
主体功能
"""
def run():
"""
:mode_var0.get(): 程序模式,True为保留,False为删除
:file_path: 文件地址
:start: 起始行
:end: 终止行
:return: 一个新的文件
"""
global start
global end
start_var = start.get()
end_var = end.get()
if start_var == '' or end_var == '' or file_path == '' or (
mode_var0.get() == False and mode_var1.get() == False) or last_mode == []:
showwarning(title='变量不足', message='有变量没有设置,请至少设置文件,模式以及行号')
return
if eval(end_var) < 0:
end_var = math.inf
else:
end_var = int(end_var)
start_var = int(start_var) - 1
for i in range(10):
new_filepath = file_path + '(' + str(i) + ').txt'
if not os.path.exists(new_filepath):
break
else:
showwarning(title='警告', message='该文件所在目录下有太多类似名字的文件')
return
if v_gbk.get():
f = open(file_path, 'r', encoding='gbk')
else:
f = open(file_path, 'r', encoding='utf-8')
new_f = open(new_filepath, 'w', encoding='utf-8')
line = '~'
pointer = 0
while line:
line = f.readline()
if mode_var0.get(): # 删除模式
if pointer < start_var or pointer > end_var:
new_f.write(line)
if pointer == start_var:
new_f.write('\n<删除部分>\n\n')
if not mode_var0.get(): # 保留模式
if start_var <= pointer <= end_var:
new_f.write(line)
if pointer == start_var - 1 or pointer == end_var + 1:
new_f.write('\n<删除部分>\n\n')
pointer += 1
# 进度显示
if pointer % 1000 == 0:
progress_bar.config(state='normal')
progress_bar.delete('1.0', tk.END)
sentence = '已处理' + str(pointer) + '行'
progress_bar.insert('insert', sentence)
f.close()
new_f.close()
if mode_var2.get():
os.remove(file_path)
os.rename(new_filepath, file_path)
showinfo(title='提示', message='文件已处理完成\n新文件为{}'.format(file_path))
else:
showinfo(title='提示', message='文件已处理完成\n新文件为{}'.format(new_filepath))
with open('cache.txt', 'w') as f:
f.write(file_path + '\n')
print(file_path + '\n')
f.write('\t'.join([str(mode_var0.get()), str(mode_var1.get())]))
print('\t'.join([str(mode_var0.get()), str(mode_var1.get())]))
f.close()
RUN = tk.Button(window, text='运行', width=15, height=2, command=run, font=('SimHei', 15))
RUN.place(x=100, y=230)
progress_bar = tk.Text(window, width=20, height=1, bg='gray', font=('Arial', 12))
progress_bar.insert('insert', '已处理0行')
progress_bar.place(x=280, y=260)
progress_bar.config(state='disabled')
window.mainloop() # 显示
四、总结
最后用pyinstaller打包了一个小程序也和源代码一起附在博客资源里了。太久没写代码了,找个地方记录一下自己的上班摸鱼coding生活~~