python实现markdown编辑器-10

前言

开始

KaTeX \KaTeX KATEX

至于为什么把它们放在最后,因为实现这个可能会稍微麻烦,甚至会改很多代码。

先看行内 KaTeX \KaTeX KATEX,不难发现,就是用一对$包裹的字符串。
这里采用Text控件中插入子控件实现一行内不同格式
所以能给出代码:

from tkinter.filedialog import askopenfilename as aof,asksaveasfilename as asf
from tkinter.messagebox import askyesno as askyn
from PIL import Image,ImageDraw,ImageTk
from tkinter import ttk
import tkinter as tk
import requests
import time
import io
fontname = ...
d = tk.Tk()
display = tk.Text(d,font=(fontname,15,''))
display.grid(row=0,column=0)
writer = tk.Toplevel()
text = tk.Text(writer)
text.grid(row=0,column=0)
cont = ''
def clear():
	global display
	display.delete(0.0,tk.END)
def proc(text):
	res = text.split('$')
	for i in range(len(res)):
		if(i % 2):
			imgTk = get_KaTeX(res[i])
			res[i] = tk.Label(display,image=imgTk)
			res[i].image = imgTk
		else:
			res[i] = tk.Label(display,text=res[i])
	return res
def read_md(file):
 	with open(file) as f:
 			return f.read()
def write_md(file,cont):
 	with open(file,'w') as f:
 		f.write(cont)
def get_KaTeX_io(katex):
	resp = requests.get('https://latex.codecogs.com/png.image?\dpi{110} ' + katex)
	return io.BytesIO(resp.content)
def get_image_io(link):
	resp = requests.get(link)
	return io.BytesIO(resp.content)
def get_KaTeX(katex):
	img_io = get_KaTeX_io(katex)
	img = Image.open(img_io)
	return ImageTk.PhotoImage(img)
def get_image(link):
	img_io = get_image_io(link)
	img = Image.open(img_io)
	return ImageTk.PhotoImage(img)
def get_spliter():
	w = max(1,display.winfo_width() - 6)
	img = Image.new('RGB',(w,1),(221,221,221))
	return ImageTk.PhotoImage(img)
def render_md():
	d.update()
	clear()
	l = 0
	code = False
	buf = ''
	n = 0
	for i in cont.split('\n'):
		if(n):
			n -= 1
			continue
		if(code and i != '```'):
			buf += i + '\n'
			continue
		if(i.startswith('# ')):
			lb = tk.Label(display,text=i[2:],font=(fontname,40,'bold'))
		elif(i.startswith('## ')):
			lb = tk.Label(display,text=i[3:],font=(fontname,35,'bold'))
		elif(i.startswith('### ')):
			lb = tk.Label(display,text=i[4:],font=(fontname,30,'bold'))
		elif(i.startswith('#### ')):
			lb = tk.Label(display,text=i[5:],font=(fontname,25,'bold'))
		elif(i.startswith('##### ')):
			lb = tk.Label(display,text=i[6:],font=(fontname,20,'bold'))
		elif(i.startswith('###### ')):
			lb = tk.Label(display,text=i[7:],font=(fontname,15,'bold'))
		elif(i.startswith('- [ ] ')):
			cbtn = tk.Checkbutton(display,text='',state=tk.DISABLED)
			lb = [cbtn] + proc(i[6:])
		elif(i.startswith('- [x] ')):
			cbtn = tk.Checkbutton(display,text='',state=tk.DISABLED)
			cbtn.select()
			lb = [cbtn] + proc(i[6:])
		elif(i.startswith('- ') or i.startswith('* ') or i.startswith('+ ')):
			i = '● ' + i[2:]
			lb = proc(i)
		elif(i.startswith(': ')):
			lb = proc('\t'+i[2:])
		elif(i.startswith('$$') and i.endswith('$$') and i not in ('$$','$$$')):
			imgTk = get_KaTeX(i[2:-2])
			lb = tk.Label(display,image=imgTk)
			lb.image = imgTk
		elif(set(i) in ({'-'},{'*'}) and len(i) >= 3):
			imgTk = get_spliter()
			lb = tk.Label(display,image=imgTk)
			lb.image = imgTk
		elif(i == '```'):
			if(code):
				lb = tk.Text(display)
				lb.insert(0.0,buf)
				h = float(lb.index(tk.END)) - 2
				lb.config(height=h)
				buf = ''
				code = False
			else:
				code = True
				continue
		else:
			try:
				head = i.split('|')
				check = cont.split('\n')[l + 1].split('|')
				assert len(head) == len(check)
				assert set(''.join(check)) == {'-'}
				assert all(check)
				body = []
				after = cont.split('\n')[l + 2:]
				n = 1
				for j in after:
					if(len(j.split('|')) == len(head)):
						body.append(j.split('|'))
					else:
						break
					l += 1
					n += 1
				assert body
				lb = ttk.Treeview(display,columns=[i for i in range(len(head))],show='headings')
				for j in range(len(head)):
					lb.heading(j,text=head[j])
				for j in body:
					lb.insert('','end',value=j)
			except:
				n = 0
				lb = proc(i)
		if(not isinstance(lb,list)):
			lb = [lb]
		for i in lb:
			display.window_create(tk.INSERT,window=i)
		display.insert(tk.INSERT,'\n')
		l += 1
def update_writer():
	global cont
	writer.update()
	cont = text.get(0.0,tk.END)
def main():
	global cont
	writer.title('writer')
	d.title('displayer')
	file = aof(title='open a markdown file')
	if(file):
		cont = read_md(file)
	text.insert(0.0,cont)
	while(1):
		try:
			update_writer()
			render_md()
		except Exception as e:
			try:
				writer.destroy()
			except:
				pass
			try:
				display.destroy()
			except:
				pass
			if(file):
				write_md(file,cont)
			else:
				tk.Tk().withdraw()
				while(1):
					file = asf(title='save as ...')
					if(not file):
						if(not askyn('Do you want to keep this new document?','Do you want to keep this new document?')):
							exit()
					write_md(file)
if(__name__ == '__main__'):
	main()

使用Text控件的功能函数window_create实现插入子控件,insertdelete实现插入文字以及删除内容。

行内代码片

与行内 KaTeX \KaTeX KATEX类似,行内代码片是以`包裹的一段文字。

所以给出代码:

from tkinter.filedialog import askopenfilename as aof,asksaveasfilename as asf
from tkinter.messagebox import askyesno as askyn
from PIL import Image,ImageDraw,ImageTk
import tkinter.font as tkFont
from tkinter import ttk
import tkinter as tk
import requests
import time
import io
fontname = ...
d = tk.Tk()
display = tk.Text(d,font=(fontname,15,''))
display.grid(row=0,column=0)
writer = tk.Toplevel()
text = tk.Text(writer)
text.grid(row=0,column=0)
cont = ''
def clear():
	global display
	display.delete(0.0,tk.END)
def proc(text):
	res = text.split('$')
	for i in range(len(res)):
		if(i % 2):
			imgTk = get_KaTeX(res[i])
			res[i] = tk.Label(display,image=imgTk)
			res[i].image = imgTk
	newres = []
	for i in range(len(res)):
		if(not isinstance(res[i],str)):
			newres.append(res[i])
			continue
		s = res[i].split('`')
		for j in range(len(s)):
			if(j % 2):
				font = tkFont.nametofont("TkFixedFont")
				newres.append(tk.Label(display,text=s[j],font=font))
			else:
				newres.append(tk.Label(display,text=s[j]))
	return newres
def read_md(file):
 	with open(file) as f:
 			return f.read()
def write_md(file,cont):
 	with open(file,'w') as f:
 		f.write(cont)
def get_KaTeX_io(katex):
	resp = requests.get('https://latex.codecogs.com/png.image?\dpi{110} ' + katex)
	return io.BytesIO(resp.content)
def get_image_io(link):
	resp = requests.get(link)
	return io.BytesIO(resp.content)
def get_KaTeX(katex):
	img_io = get_KaTeX_io(katex)
	img = Image.open(img_io)
	return ImageTk.PhotoImage(img)
def get_image(link):
	img_io = get_image_io(link)
	img = Image.open(img_io)
	return ImageTk.PhotoImage(img)
def get_spliter():
	w = max(1,display.winfo_width() - 6)
	img = Image.new('RGB',(w,1),(221,221,221))
	return ImageTk.PhotoImage(img)
def render_md():
	d.update()
	clear()
	l = 0
	code = False
	buf = ''
	n = 0
	for i in cont.split('\n'):
		if(n):
			n -= 1
			continue
		if(code and i != '```'):
			buf += i + '\n'
			continue
		if(i.startswith('# ')):
			lb = tk.Label(display,text=i[2:],font=(fontname,40,'bold'))
		elif(i.startswith('## ')):
			lb = tk.Label(display,text=i[3:],font=(fontname,35,'bold'))
		elif(i.startswith('### ')):
			lb = tk.Label(display,text=i[4:],font=(fontname,30,'bold'))
		elif(i.startswith('#### ')):
			lb = tk.Label(display,text=i[5:],font=(fontname,25,'bold'))
		elif(i.startswith('##### ')):
			lb = tk.Label(display,text=i[6:],font=(fontname,20,'bold'))
		elif(i.startswith('###### ')):
			lb = tk.Label(display,text=i[7:],font=(fontname,15,'bold'))
		elif(i.startswith('- [ ] ')):
			cbtn = tk.Checkbutton(display,text='',state=tk.DISABLED)
			lb = [cbtn] + proc(i[6:])
		elif(i.startswith('- [x] ')):
			cbtn = tk.Checkbutton(display,text='',state=tk.DISABLED)
			cbtn.select()
			lb = [cbtn] + proc(i[6:])
		elif(i.startswith('- ') or i.startswith('* ') or i.startswith('+ ')):
			i = '● ' + i[2:]
			lb = proc(i)
		elif(i.startswith(': ')):
			lb = proc('\t'+i[2:])
		elif(i.startswith('$$') and i.endswith('$$') and i not in ('$$','$$$')):
			imgTk = get_KaTeX(i[2:-2])
			lb = tk.Label(display,image=imgTk)
			lb.image = imgTk
		elif(set(i) in ({'-'},{'*'}) and len(i) >= 3):
			imgTk = get_spliter()
			lb = tk.Label(display,image=imgTk)
			lb.image = imgTk
		elif(i == '```'):
			if(code):
				lb = tk.Text(display)
				lb.insert(0.0,buf)
				h = float(lb.index(tk.END)) - 2
				lb.config(height=h)
				buf = ''
				code = False
			else:
				code = True
				continue
		else:
			try:
				head = i.split('|')
				check = cont.split('\n')[l + 1].split('|')
				assert len(head) == len(check)
				assert set(''.join(check)) == {'-'}
				assert all(check)
				body = []
				after = cont.split('\n')[l + 2:]
				n = 1
				for j in after:
					if(len(j.split('|')) == len(head)):
						body.append(j.split('|'))
					else:
						break
					l += 1
					n += 1
				assert body
				lb = ttk.Treeview(display,columns=[i for i in range(len(head))],show='headings')
				for j in range(len(head)):
					lb.heading(j,text=head[j])
				for j in body:
					lb.insert('','end',value=j)
			except:
				n = 0
				lb = proc(i)
		if(not isinstance(lb,list)):
			lb = [lb]
		for i in lb:
			display.window_create(tk.INSERT,window=i)
		display.insert(tk.INSERT,'\n')
		l += 1
def update_writer():
	global cont
	writer.update()
	cont = text.get(0.0,tk.END)
def main():
	global cont
	writer.title('writer')
	d.title('displayer')
	file = aof(title='open a markdown file')
	if(file):
		cont = read_md(file)
	text.insert(0.0,cont)
	while(1):
		try:
			update_writer()
			render_md()
		except Exception as e:
			try:
				writer.destroy()
			except:
				pass
			try:
				display.destroy()
			except:
				pass
			if(file):
				write_md(file,cont)
			else:
				tk.Tk().withdraw()
				while(1):
					file = asf(title='save as ...')
					if(not file):
						if(not askyn('Do you want to keep this new document?','Do you want to keep this new document?')):
							exit()
					write_md(file)
if(__name__ == '__main__'):
	main()


字体格式

主要有如下几种字体格式:

  1. 斜体(*a*_a_
  2. 加粗(**a**__a__
  3. 删除线(~~a~~

将能够产生的排列用字典的形式表现出来,值就是对应的正则表达式列表:

{
        "italic":["\*[^\*_~]+\*","_[^\*_~]+_"],
        "bold":["\*\*[^\*_~]+\*\*","__[^\*_~]+__"],
        "overstrike":["~~[^\*_~]+~~"],
        "italic bold":
                [       
                        "\*\*\*[^\*_~]+\*\*\*",
                        "___[^\*_~]+___",
                        "_\*\*[^\*_~]+\*\*_",
                        "\*\*_[^\*_~]+_\*\*",
                        "\*__[^\*_~]+__\*"
                        "__\*[^\*_~]+\*__"
                ],
        "italic overstike":
                [       
                        "~~\*[^\*_~]+\*~~",
                        "\*~~[^\*_~]+~~\*",
                        "~~_[^\*_~]+_~~",
                        "_~~[^\*_~]+~~_"
                ],
                "bold overstike":
                [       
                        "~~\*\*[^\*_~]+\*\*~~",
                        "\*\*~~[^\*_~]+~~\*\*",
                        "~~__[^\*_~]+__~~",
                        "__~~[^\*_~]+~~__"
                ],
        "italic bold overstike":
                [
                        "~~\*\*\*[^\*_~]+\*\*\*~~",
                        "\*~~\*\*[~\*_~]+\*\*~~\*",
                        "\*\*~~\*[^\*_~]+\*\*\*~~",
                        "\*\*\*~~[^\*_~]+\*\*\*~~",
                        "~~___[^\*_~]+___~~",
                        "_~~__[^\*_~]+__~~_",
                        "__~~_[^\*_~]+_~~__",
                        "~~_\*\*[^\*_~]+\*\*_~~",
                        "_~~\*\*[^\*_~]+\*\*~~_",
                        "_\*\*~~[^\*_~]+~~\*\*_",
                        "~~\*__[^\*_~]+__\*~~",
                        "\*~~__[^\*_~]+__~~\*",
                        "\*__~~[^\*_~]+~~__\*",
                ]
}

为了方便分割,我们将它们合并成一个列表,并按长度从大到小排。
所以给出代码:

from tkinter.filedialog import askopenfilename as aof,asksaveasfilename as asf
from tkinter.messagebox import askyesno as askyn
from PIL import Image,ImageDraw,ImageTk
import tkinter.font as tkFont
from tkinter import ttk
import tkinter as tk
import requests
import time
import io
import re
fontname = ...
d = tk.Tk()
display = tk.Text(d,font=(fontname,15,''))
display.grid(row=0,column=0)
writer = tk.Toplevel()
text = tk.Text(writer)
text.grid(row=0,column=0)
cont = ''
def clear():
	global display
	display.delete(0.0,tk.END)
def proc(text):
	res = text.split('$')
	for i in range(len(res)):
		if(i % 2):
			imgTk = get_KaTeX(res[i])
			res[i] = tk.Label(display,image=imgTk)
			res[i].image = imgTk
	newres = []
	for i in range(len(res)):
		if(not isinstance(res[i],str)):
			newres.append(res[i])
			continue
		s = res[i].split('`')
		for j in range(len(s)):
			if(j % 2):
				font = tkFont.nametofont("TkFixedFont")
				newres.append(tk.Label(display,text=s[j],font=font))
			else:
				newres.append(s[j])
	d = {
		"italic":["\*[^\*_~]+\*","_[^\*_~]+_"],
		"bold":["\*\*[^\*_~]+\*\*","__[^\*_~]+__"],
		"overstrike":["~~[^\*_~]+~~"],
		"italic bold":
			[
				"\*\*\*[^\*_~]+\*\*\*",
				"___[^\*_~]+___",
				"_\*\*[^\*_~]+\*\*_",
				"\*\*_[^\*_~]+_\*\*",
				"\*__[^\*_~]+__\*"
				"__\*[^\*_~]+\*__"
			],
		"italic overstrike":
			[
				"~~\*[^\*_~]+\*~~",
				"\*~~[^\*_~]+~~\*",
				"~~_[^\*_~]+_~~",
				"_~~[^\*_~]+~~_"
			],
		"bold overstrike":
			[
				"~~\*\*[^\*_~]+\*\*~~",
				"\*\*~~[^\*_~]+~~\*\*",
				"~~__[^\*_~]+__~~",
				"__~~[^\*_~]+~~__"
			],
		"italic bold overstrike":
			[
				"~~\*\*\*[^\*_~]+\*\*\*~~",
				"\*~~\*\*[~\*_~]+\*\*~~\*",
				"\*\*~~\*[^\*_~]+\*\*\*~~",
				"\*\*\*~~[^\*_~]+\*\*\*~~",
				"~~___[^\*_~]+___~~",
				"_~~__[^\*_~]+__~~_",
				"__~~_[^\*_~]+_~~__",
				"___~~[^\*_~]+~~___",
				"~~_\*\*[^\*_~]+\*\*_~~",
				"_~~\*\*[^\*_~]+\*\*~~_",
				"_\*\*~~[^\*_~]+~~\*\*_",
				"~~\*__[^\*_~]+__\*~~",
				"\*~~__[^\*_~]+__~~\*",
				"\*__~~[^\*_~]+~~__\*",
				"~~__\*[^\*_~]+\*__~~",
				"__~~\*[^\*_~]+\*~~__",
				"__\*~~[^\*_~]+~~\*__",
				"~~\*\*_[^\*_~]+_\*\*~~",
				"\*\*~~_[^\*_~]+_~~\*\*",
				"\*\*_~~[^\*_~]+~~_\*\*"
			]
	}
	def legal(s):
		try:
			f = re.match('[\*_~]+',s).group()
		except:
			return False
		rf = f[::-1]
		f = f.replace('*','\*')
		rf = rf.replace('*','\*')
		pat = f + '[^\*_~]+' + rf
		mat = re.match(pat,s)
		return mat and mat.group() == s
	res = []
	r = []
	for i in d.values():
		r.extend(i)
	r = sorted(r,key=lambda a:-len(a))
	for i in newres:
		if(not isinstance(i,str)):
			res.append(i)
			continue
		sp = [i]
		for j in r:
			_sp = sp[:]
			sp = []
			for k in _sp:
				if(legal(k)):
					sp.append(k)
				else:
					sp.extend(re.split('(%s)'%j,k))
		for j in sp:
			flag = False
			for k in d:
				for l in d[k]:
					if(re.match(l,j)):
						flag = True
						if(k.endswith('overstrike')):
							font = (fontname,15,k[:-10])
							font = tkFont.Font(font=font,overstrike=True)
						else:
							font = (fontname,15,k)
							font = tkFont.Font(font=font)
						txt = re.search('[^\*_~]+',j).group()
						lb = tk.Label(display,text=txt,font=font)
						res.append(lb)
						break
				if(flag):break
			if(not flag):
				lb = tk.Label(display,text=j)
				res.append(lb)
	return res
def read_md(file):
 	with open(file) as f:
 			return f.read()
def write_md(file,cont):
 	with open(file,'w') as f:
 		f.write(cont)
def get_KaTeX_io(katex):
	resp = requests.get('https://latex.codecogs.com/png.image?\dpi{110} ' + katex)
	return io.BytesIO(resp.content)
def get_image_io(link):
	resp = requests.get(link)
	return io.BytesIO(resp.content)
def get_KaTeX(katex):
	img_io = get_KaTeX_io(katex)
	img = Image.open(img_io)
	return ImageTk.PhotoImage(img)
def get_image(link):
	img_io = get_image_io(link)
	img = Image.open(img_io)
	return ImageTk.PhotoImage(img)
def get_spliter():
	w = max(1,display.winfo_width() - 6)
	img = Image.new('RGB',(w,1),(221,221,221))
	return ImageTk.PhotoImage(img)
def render_md():
	d.update()
	clear()
	l = 0
	code = False
	buf = ''
	n = 0
	for i in cont.split('\n'):
		if(n):
			n -= 1
			continue
		if(code and i != '```'):
			buf += i + '\n'
			continue
		if(i.startswith('# ')):
			lb = tk.Label(display,text=i[2:],font=(fontname,40,'bold'))
		elif(i.startswith('## ')):
			lb = tk.Label(display,text=i[3:],font=(fontname,35,'bold'))
		elif(i.startswith('### ')):
			lb = tk.Label(display,text=i[4:],font=(fontname,30,'bold'))
		elif(i.startswith('#### ')):
			lb = tk.Label(display,text=i[5:],font=(fontname,25,'bold'))
		elif(i.startswith('##### ')):
			lb = tk.Label(display,text=i[6:],font=(fontname,20,'bold'))
		elif(i.startswith('###### ')):
			lb = tk.Label(display,text=i[7:],font=(fontname,15,'bold'))
		elif(i.startswith('- [ ] ')):
			cbtn = tk.Checkbutton(display,text='',state=tk.DISABLED)
			lb = [cbtn] + proc(i[6:])
		elif(i.startswith('- [x] ')):
			cbtn = tk.Checkbutton(display,text='',state=tk.DISABLED)
			cbtn.select()
			lb = [cbtn] + proc(i[6:])
		elif(i.startswith('- ') or i.startswith('* ') or i.startswith('+ ')):
			i = '● ' + i[2:]
			lb = proc(i)
		elif(i.startswith(': ')):
			lb = proc('\t'+i[2:])
		elif(i.startswith('$$') and i.endswith('$$') and i not in ('$$','$$$')):
			imgTk = get_KaTeX(i[2:-2])
			lb = tk.Label(display,image=imgTk)
			lb.image = imgTk
		elif(set(i) in ({'-'},{'*'}) and len(i) >= 3):
			imgTk = get_spliter()
			lb = tk.Label(display,image=imgTk)
			lb.image = imgTk
		elif(i == '```'):
			if(code):
				lb = tk.Text(display)
				lb.insert(0.0,buf)
				h = float(lb.index(tk.END)) - 2
				lb.config(height=h)
				buf = ''
				code = False
			else:
				code = True
				continue
		else:
			try:
				head = i.split('|')
				check = cont.split('\n')[l + 1].split('|')
				assert len(head) == len(check)
				assert set(''.join(check)) == {'-'}
				assert all(check)
				body = []
				after = cont.split('\n')[l + 2:]
				n = 1
				for j in after:
					if(len(j.split('|')) == len(head)):
						body.append(j.split('|'))
					else:
						break
					l += 1
					n += 1
				assert body
				lb = ttk.Treeview(display,columns=[i for i in range(len(head))],show='headings')
				for j in range(len(head)):
					lb.heading(j,text=head[j])
				for j in body:
					lb.insert('','end',value=j)
			except:
				n = 0
				lb = proc(i)
		if(not isinstance(lb,list)):
			lb = [lb]
		for i in lb:
			display.window_create(tk.INSERT,window=i)
		display.insert(tk.INSERT,'\n')
		l += 1
def update_writer():
	global cont
	writer.update()
	cont = text.get(0.0,tk.END)
def main():
	global cont
	writer.title('writer')
	d.title('displayer')
	file = aof(title='open a markdown file')
	if(file):
		cont = read_md(file)
	text.insert(0.0,cont)
	while(1):
		try:
			update_writer()
			render_md()
		except:
			try:
				writer.destroy()
			except:
				pass
			try:
				display.destroy()
			except:
				pass
			if(file):
				write_md(file,cont)
			else:
				tk.Tk().withdraw()
				while(1):
					file = asf(title='save as ...')
					if(not file):
						if(not askyn('Do you want to keep this new document?','Do you want to keep this new document?')):
							exit()
					write_md(file)
if(__name__ == '__main__'):
	main()

项目github

github传送门

结尾

这个系列本来想一片就讲完的,但是太长了,给切了。。。。。。


作者

hit-road

拜拜,下课!

hit-road不定期跟新,不见不散!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值